]> git.meshlink.io Git - meshlink/blob - src/conf.c
Add support for opening a MeshLink instance without permanent storage.
[meshlink] / src / conf.c
1 /*
2     econf.c -- configuration code
3     Copyright (C) 2018 Guus Sliepen <guus@meshlink.io>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21 #include <assert.h>
22
23 #include "conf.h"
24 #include "crypto.h"
25 #include "logger.h"
26 #include "meshlink_internal.h"
27 #include "xalloc.h"
28 #include "packmsg.h"
29
30 /// Generate a path to the main configuration file.
31 static void make_main_path(meshlink_handle_t *mesh, char *path, size_t len) {
32         snprintf(path, len, "%s" SLASH "meshlink.conf", mesh->confbase);
33 }
34
35 /// Generate a path to a host configuration file.
36 static void make_host_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
37         snprintf(path, len, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
38 }
39
40 /// Generate a path to an unused invitation file.
41 static void make_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
42         snprintf(path, len, "%s" SLASH "invitations" SLASH "%s", mesh->confbase, name);
43 }
44
45 /// Generate a path to a used invitation file.
46 static void make_used_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
47         snprintf(path, len, "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, name);
48 }
49
50 /// Remove a directory recursively
51 static void deltree(const char *dirname) {
52         DIR *d = opendir(dirname);
53
54         if(d) {
55                 struct dirent *ent;
56
57                 while((ent = readdir(d))) {
58                         if(ent->d_name[0] == '.') {
59                                 continue;
60                         }
61
62                         char filename[PATH_MAX];
63                         snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
64
65                         if(unlink(filename)) {
66                                 deltree(filename);
67                         }
68                 }
69
70                 closedir(d);
71         }
72
73         rmdir(dirname);
74 }
75
76 /// Create a fresh configuration directory
77 bool config_init(meshlink_handle_t *mesh) {
78         if(!mesh->confbase) {
79                 return true;
80         }
81
82         if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
83                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
84                 return false;
85         }
86
87         char path[PATH_MAX];
88
89         // Remove meshlink.conf
90         snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", mesh->confbase);
91         unlink(path);
92
93         // Remove any host config files
94         snprintf(path, sizeof(path), "%s" SLASH "hosts", mesh->confbase);
95         deltree(path);
96
97         if(mkdir(path, 0700) && errno != EEXIST) {
98                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
99                 return false;
100         }
101
102         // Remove any invitation files
103         snprintf(path, sizeof(path), "%s" SLASH "invitations", mesh->confbase);
104         deltree(path);
105
106         if(mkdir(path, 0700) && errno != EEXIST) {
107                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
108                 return false;
109         }
110
111         return true;
112 }
113
114 /// Wipe an existing configuration directory
115 bool config_destroy(const char *confbase) {
116         char path[PATH_MAX];
117
118         // Remove meshlink.conf
119         snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", confbase);
120
121         if(unlink(path)) {
122                 if(errno == ENOENT) {
123                         meshlink_errno = MESHLINK_ENOENT;
124                         return false;
125                 } else {
126                         logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
127                         meshlink_errno = MESHLINK_ESTORAGE;
128                         return false;
129                 }
130         }
131
132         deltree(confbase);
133         return true;
134 }
135
136 /// Check the presence of the main configuration file.
137 bool main_config_exists(meshlink_handle_t *mesh) {
138         if(!mesh->confbase) {
139                 return false;
140         }
141
142         char path[PATH_MAX];
143         make_main_path(mesh, path, sizeof(path));
144
145         return access(path, F_OK) == 0;
146 }
147
148 /// Lock the main configuration file.
149 bool main_config_lock(meshlink_handle_t *mesh) {
150         if(!mesh->confbase) {
151                 return true;
152         }
153
154         char path[PATH_MAX];
155         make_main_path(mesh, path, sizeof(path));
156
157         mesh->conffile = fopen(path, "r");
158
159         if(!mesh->conffile) {
160                 logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
161                 meshlink_errno = MESHLINK_ESTORAGE;
162                 return false;
163         }
164
165 #ifdef FD_CLOEXEC
166         fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
167 #endif
168
169 #ifdef HAVE_MINGW
170         // TODO: use _locking()?
171 #else
172
173         if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
174                 logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
175                 fclose(mesh->conffile);
176                 mesh->conffile = NULL;
177                 meshlink_errno = MESHLINK_EBUSY;
178                 return false;
179         }
180
181 #endif
182
183         return true;
184 }
185
186 /// Unlock the main configuration file.
187 void main_config_unlock(meshlink_handle_t *mesh) {
188         if(mesh->conffile) {
189                 fclose(mesh->conffile);
190                 mesh->conffile = NULL;
191         }
192 }
193
194 /// Read a configuration file from a FILE handle.
195 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) {
196         if(!mesh->confbase) {
197                 return false;
198         }
199
200         (void)mesh;
201         long len;
202
203         if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
204                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
205                 meshlink_errno = MESHLINK_ESTORAGE;
206                 fclose(f);
207                 return false;
208         }
209
210         uint8_t *buf = xmalloc(len);
211
212         if(fread(buf, len, 1, f) != 1) {
213                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
214                 meshlink_errno = MESHLINK_ESTORAGE;
215                 fclose(f);
216                 return false;
217         }
218
219         if(mesh->config_key) {
220                 uint8_t *decrypted = xmalloc(len);
221                 size_t decrypted_len = len;
222                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
223                 chacha_poly1305_set_key(ctx, mesh->config_key);
224
225                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
226                         free(buf);
227                         config->buf = decrypted;
228                         config->len = decrypted_len;
229                         return true;
230                 } else {
231                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
232                         meshlink_errno = MESHLINK_ESTORAGE;
233                         free(decrypted);
234                         free(buf);
235                         return false;
236                 }
237         }
238
239         config->buf = buf;
240         config->len = len;
241
242         return true;
243 }
244
245 /// Write a configuration file to a FILE handle.
246 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config) {
247         if(!mesh->confbase) {
248                 return true;
249         }
250
251         if(mesh->config_key) {
252                 uint8_t buf[config->len + 16];
253                 size_t len = sizeof(buf);
254                 uint8_t seqbuf[12];
255                 randomize(&seqbuf, sizeof(seqbuf));
256                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
257                 chacha_poly1305_set_key(ctx, mesh->config_key);
258                 bool success = false;
259
260                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
261                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
262                 } else {
263                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
264                         meshlink_errno = MESHLINK_ESTORAGE;
265                 }
266
267                 chacha_poly1305_exit(ctx);
268                 return success;
269         }
270
271         if(fwrite(config->buf, config->len, 1, f) != 1) {
272                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
273                 meshlink_errno = MESHLINK_ESTORAGE;
274                 return false;
275         }
276
277         return true;
278 }
279
280 /// Free resources of a loaded configuration file.
281 void config_free(config_t *config) {
282         free((uint8_t *)config->buf);
283         config->buf = NULL;
284         config->len = 0;
285 }
286
287 /// Check the presence of a host configuration file.
288 bool config_exists(meshlink_handle_t *mesh, const char *name) {
289         if(!mesh->confbase) {
290                 return false;
291         }
292
293         char path[PATH_MAX];
294         make_host_path(mesh, name, path, sizeof(path));
295
296         return access(path, F_OK) == 0;
297 }
298
299 /// Read a host configuration file.
300 bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
301         if(!mesh->confbase) {
302                 return false;
303         }
304
305         char path[PATH_MAX];
306         make_host_path(mesh, name, path, sizeof(path));
307
308         FILE *f = fopen(path, "r");
309
310         if(!f) {
311                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
312                 return false;
313         }
314
315         if(!config_read_file(mesh, f, config)) {
316                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
317                 fclose(f);
318                 return false;
319         }
320
321         fclose(f);
322         return true;
323 }
324
325 void config_scan_all(meshlink_handle_t *mesh, config_scan_action_t action) {
326         if(!mesh->confbase) {
327                 return;
328         }
329
330         DIR *dir;
331         struct dirent *ent;
332         char dname[PATH_MAX];
333         make_host_path(mesh, NULL, dname, sizeof(dname));
334
335         dir = opendir(dname);
336
337         if(!dir) {
338                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
339                 meshlink_errno = MESHLINK_ESTORAGE;
340                 return;
341         }
342
343         while((ent = readdir(dir))) {
344                 action(mesh, ent->d_name);
345         }
346
347         closedir(dir);
348 }
349
350 /// Write a host configuration file.
351 bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
352         if(!mesh->confbase) {
353                 return true;
354         }
355
356         char path[PATH_MAX];
357         make_host_path(mesh, name, path, sizeof(path));
358
359         FILE *f = fopen(path, "w");
360
361         if(!f) {
362                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
363                 return false;
364         }
365
366         if(!config_write_file(mesh, f, config)) {
367                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
368                 fclose(f);
369                 return false;
370         }
371
372         fclose(f);
373         return true;
374 }
375
376 /// Read the main configuration file.
377 bool main_config_read(meshlink_handle_t *mesh, config_t *config) {
378         if(!mesh->confbase) {
379                 return false;
380         }
381
382         char path[PATH_MAX];
383         make_main_path(mesh, path, sizeof(path));
384
385         FILE *f = fopen(path, "r");
386
387         if(!f) {
388                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
389                 return false;
390         }
391
392         if(!config_read_file(mesh, f, config)) {
393                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
394                 fclose(f);
395                 return false;
396         }
397
398         fclose(f);
399         return true;
400 }
401
402 /// Write the main configuration file.
403 bool main_config_write(meshlink_handle_t *mesh, const config_t *config) {
404         if(!mesh->confbase) {
405                 return true;
406         }
407
408         char path[PATH_MAX];
409         make_main_path(mesh, path, sizeof(path));
410
411         FILE *f = fopen(path, "w");
412
413         if(!f) {
414                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
415                 return false;
416         }
417
418         if(!config_write_file(mesh, f, config)) {
419                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
420                 fclose(f);
421                 return false;
422         }
423
424         fclose(f);
425         return true;
426 }
427
428 /// Read an invitation file, and immediately delete it.
429 bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
430         if(!mesh->confbase) {
431                 return false;
432         }
433
434         char path[PATH_MAX];
435         char used_path[PATH_MAX];
436         make_invitation_path(mesh, name, path, sizeof(path));
437         make_used_invitation_path(mesh, name, used_path, sizeof(used_path));
438
439         // Atomically rename the invitation file
440         if(rename(path, used_path)) {
441                 if(errno == ENOENT) {
442                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
443                 } else {
444                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
445                 }
446
447                 return false;
448         }
449
450         FILE *f = fopen(used_path, "r");
451
452         if(!f) {
453                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
454                 return false;
455         }
456
457         // Check the timestamp
458         struct stat st;
459
460         if(fstat(fileno(f), &st)) {
461                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
462                 fclose(f);
463                 unlink(used_path);
464                 return false;
465         }
466
467         if(time(NULL) > st.st_mtime + mesh->invitation_timeout) {
468                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
469                 fclose(f);
470                 unlink(used_path);
471                 return false;
472         }
473
474         if(!config_read_file(mesh, f, config)) {
475                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
476                 fclose(f);
477                 unlink(used_path);
478                 return false;
479         }
480
481         fclose(f);
482         unlink(used_path);
483         return true;
484 }
485
486 /// Write an invitation file.
487 bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
488         if(!mesh->confbase) {
489                 return true;
490         }
491
492         char path[PATH_MAX];
493         make_invitation_path(mesh, name, path, sizeof(path));
494
495         FILE *f = fopen(path, "w");
496
497         if(!f) {
498                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
499                 return false;
500         }
501
502         if(!config_write_file(mesh, f, config)) {
503                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
504                 fclose(f);
505                 return false;
506         }
507
508         fclose(f);
509         return true;
510 }
511
512 /// Purge old invitation files
513 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
514         if(!mesh->confbase) {
515                 return true;
516         }
517
518         char path[PATH_MAX];
519         make_invitation_path(mesh, "", path, sizeof(path));
520
521         DIR *dir = opendir(path);
522
523         if(!dir) {
524                 logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
525                 meshlink_errno = MESHLINK_ESTORAGE;
526                 return 0;
527         }
528
529         errno = 0;
530         size_t count = 0;
531         struct dirent *ent;
532
533         while((ent = readdir(dir))) {
534                 if(strlen(ent->d_name) != 24) {
535                         continue;
536                 }
537
538                 char invname[PATH_MAX];
539                 struct stat st;
540
541                 if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
542                         logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
543                         continue;
544                 }
545
546                 if(!stat(invname, &st)) {
547                         if(mesh->invitation_key && deadline < st.st_mtime) {
548                                 count++;
549                         } else {
550                                 unlink(invname);
551                         }
552                 } else {
553                         logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
554                         errno = 0;
555                 }
556         }
557
558         if(errno) {
559                 logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
560                 closedir(dir);
561                 meshlink_errno = MESHLINK_ESTORAGE;
562                 return 0;
563         }
564
565         closedir(dir);
566
567         return count;
568 }