]> git.meshlink.io Git - meshlink/blob - src/conf.c
Call fsync() on the configuration directories where appropriate.
[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 #include <sys/types.h>
23 #include <utime.h>
24
25 #include "conf.h"
26 #include "crypto.h"
27 #include "logger.h"
28 #include "meshlink_internal.h"
29 #include "xalloc.h"
30 #include "packmsg.h"
31
32 /// Generate a path to the main configuration file.
33 static void make_main_path(meshlink_handle_t *mesh, const char *conf_subdir, char *path, size_t len) {
34         snprintf(path, len, "%s" SLASH "%s" SLASH "meshlink.conf", mesh->confbase, conf_subdir);
35 }
36
37 /// Generate a path to a host configuration file.
38 static void make_host_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) {
39         snprintf(path, len, "%s" SLASH "%s" SLASH "hosts" SLASH "%s", mesh->confbase, conf_subdir, name);
40 }
41
42 /// Generate a path to an unused invitation file.
43 static void make_invitation_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) {
44         snprintf(path, len, "%s" SLASH "%s" SLASH "invitations" SLASH "%s", mesh->confbase, conf_subdir, name);
45 }
46
47 /// Generate a path to a used invitation file.
48 static void make_used_invitation_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) {
49         snprintf(path, len, "%s" SLASH "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, conf_subdir, name);
50 }
51
52 /// Remove a directory recursively
53 static void deltree(const char *dirname) {
54         DIR *d = opendir(dirname);
55
56         if(d) {
57                 struct dirent *ent;
58
59                 while((ent = readdir(d))) {
60                         if(ent->d_name[0] == '.') {
61                                 continue;
62                         }
63
64                         char filename[PATH_MAX];
65                         snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
66
67                         if(unlink(filename)) {
68                                 deltree(filename);
69                         }
70                 }
71
72                 closedir(d);
73         }
74
75         rmdir(dirname);
76 }
77
78 static bool sync_path(const char *pathname) {
79         int fd = open(pathname, O_RDONLY);
80
81         if(fd < 0) {
82                 logger(NULL, MESHLINK_ERROR, "Failed to open %s: %s\n", pathname, strerror(errno));
83                 meshlink_errno = MESHLINK_ESTORAGE;
84                 return false;
85         }
86
87         if(fsync(fd)) {
88                 logger(NULL, MESHLINK_ERROR, "Failed to sync %s: %s\n", pathname, strerror(errno));
89                 close(fd);
90                 meshlink_errno = MESHLINK_ESTORAGE;
91                 return false;
92         }
93
94         if(close(fd)) {
95                 logger(NULL, MESHLINK_ERROR, "Failed to close %s: %s\n", pathname, strerror(errno));
96                 close(fd);
97                 meshlink_errno = MESHLINK_ESTORAGE;
98                 return false;
99         }
100
101         return true;
102 }
103
104 /// Try decrypting the main configuration file from the given sub-directory.
105 static bool main_config_decrypt(meshlink_handle_t *mesh, const char *conf_subdir) {
106         if(!mesh->config_key && !mesh->confbase && !conf_subdir) {
107                 return false;
108         }
109
110         config_t config;
111
112         if(!main_config_read(mesh, conf_subdir, &config, mesh->config_key)) {
113                 logger(mesh, MESHLINK_ERROR, "Could not read main configuration file");
114                 return false;
115         }
116
117         packmsg_input_t in = {config.buf, config.len};
118
119         uint32_t version = packmsg_get_uint32(&in);
120         config_free(&config);
121
122         return version == MESHLINK_CONFIG_VERSION;
123 }
124
125 /// Create a fresh configuration directory
126 bool config_init(meshlink_handle_t *mesh, const char *conf_subdir) {
127         if(!mesh->confbase) {
128                 return true;
129         }
130
131         if(!conf_subdir) {
132                 return false;
133         }
134
135         if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
136                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
137                 return false;
138         }
139
140         char path[PATH_MAX];
141
142         // Create "current" sub-directory in the confbase
143         snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir);
144         deltree(path);
145
146         if(mkdir(path, 0700)) {
147                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
148                 return false;
149         }
150
151         make_host_path(mesh, conf_subdir, "", path, sizeof(path));
152
153         if(mkdir(path, 0700)) {
154                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
155                 return false;
156         }
157
158         make_invitation_path(mesh, conf_subdir, "", path, sizeof(path));
159
160         if(mkdir(path, 0700)) {
161                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
162                 return false;
163         }
164
165         return true;
166 }
167
168 /// Wipe an existing configuration directory
169 bool config_destroy(const char *confbase, const char *conf_subdir) {
170         if(!confbase && !conf_subdir) {
171                 return false;
172         }
173
174         struct stat st;
175
176         char path[PATH_MAX];
177
178         // Check the presence of configuration base sub directory.
179         snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
180
181         if(stat(path, &st)) {
182                 if(errno == ENOENT) {
183                         return true;
184                 } else {
185                         logger(NULL, MESHLINK_ERROR, "Cannot stat %s: %s\n", path, strerror(errno));
186                         meshlink_errno = MESHLINK_ESTORAGE;
187                         return false;
188                 }
189         }
190
191         // Remove meshlink.conf
192         snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "meshlink.conf", confbase, conf_subdir);
193
194         if(unlink(path)) {
195                 if(errno != ENOENT) {
196                         logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
197                         meshlink_errno = MESHLINK_ESTORAGE;
198                         return false;
199                 }
200         }
201
202         snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
203         deltree(path);
204         return true;
205 }
206
207 static bool copytree(const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) {
208         if(!src_dir_name || !dst_dir_name) {
209                 return false;
210         }
211
212         char src_filename[PATH_MAX];
213         char dst_filename[PATH_MAX];
214         struct dirent *ent;
215
216         DIR *src_dir = opendir(src_dir_name);
217
218         if(!src_dir) {
219                 logger(NULL, MESHLINK_ERROR, "Could not open directory file %s\n", src_dir_name);
220                 return false;
221         }
222
223         // Delete if already exists and create a new destination directory
224         deltree(dst_dir_name);
225
226         if(mkdir(dst_dir_name, 0700)) {
227                 logger(NULL, MESHLINK_ERROR, "Could not create directory %s\n", dst_filename);
228                 return false;
229         }
230
231         while((ent = readdir(src_dir))) {
232                 if(ent->d_name[0] == '.') {
233                         continue;
234                 }
235
236                 snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", dst_dir_name, ent->d_name);
237                 snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", src_dir_name, ent->d_name);
238
239                 if(ent->d_type == DT_DIR) {
240                         if(!copytree(src_filename, src_key, dst_filename, dst_key)) {
241                                 logger(NULL, MESHLINK_ERROR, "Copying %s to %s failed\n", src_filename, dst_filename);
242                                 return false;
243                         }
244
245                         if(!sync_path(dst_filename)) {
246                                 return false;
247                         }
248                 } else if(ent->d_type == DT_REG) {
249                         struct stat st;
250                         config_t config;
251
252                         if(stat(src_filename, &st)) {
253                                 logger(NULL, MESHLINK_ERROR, "Could not stat file `%s': %s\n", src_filename, strerror(errno));
254                                 return false;
255                         }
256
257                         FILE *f = fopen(src_filename, "r");
258
259                         if(!f) {
260                                 logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s\n", src_filename, strerror(errno));
261                                 return false;
262                         }
263
264                         if(!config_read_file(NULL, f, &config, src_key)) {
265                                 logger(NULL, MESHLINK_ERROR, "Failed to read `%s': %s\n", src_filename, strerror(errno));
266                                 fclose(f);
267                                 return false;
268                         }
269
270                         if(fclose(f)) {
271                                 logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s\n", src_filename, strerror(errno));
272                                 config_free(&config);
273                                 return false;
274                         }
275
276                         f = fopen(dst_filename, "w");
277
278                         if(!f) {
279                                 logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s", dst_filename, strerror(errno));
280                                 config_free(&config);
281                                 return false;
282                         }
283
284                         if(!config_write_file(NULL, f, &config, dst_key)) {
285                                 logger(NULL, MESHLINK_ERROR, "Failed to write `%s': %s", dst_filename, strerror(errno));
286                                 config_free(&config);
287                                 fclose(f);
288                                 return false;
289                         }
290
291                         if(fclose(f)) {
292                                 logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s", dst_filename, strerror(errno));
293                                 config_free(&config);
294                                 return false;
295                         }
296
297                         config_free(&config);
298
299                         struct utimbuf times;
300                         times.modtime = st.st_mtime;
301                         times.actime = st.st_atime;
302
303                         if(utime(dst_filename, &times)) {
304                                 logger(NULL, MESHLINK_ERROR, "Failed to utime `%s': %s", dst_filename, strerror(errno));
305                                 return false;
306                         }
307                 }
308         }
309
310         closedir(src_dir);
311         return true;
312 }
313
314 bool config_copy(meshlink_handle_t *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) {
315         char src_filename[PATH_MAX];
316         char dst_filename[PATH_MAX];
317
318         snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", mesh->confbase, dst_dir_name);
319         snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", mesh->confbase, src_dir_name);
320
321         return copytree(src_filename, src_key, dst_filename, dst_key);
322 }
323
324 /// Check the presence of the main configuration file.
325 bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) {
326         if(!mesh->confbase && !conf_subdir) {
327                 return false;
328         }
329
330         char path[PATH_MAX];
331         make_main_path(mesh, conf_subdir, path, sizeof(path));
332         return access(path, F_OK) == 0;
333 }
334
335 bool config_rename(meshlink_handle_t *mesh, const char *old_conf_subdir, const char *new_conf_subdir) {
336         if(!mesh->confbase && !old_conf_subdir && !new_conf_subdir) {
337                 return false;
338         }
339
340         char old_path[PATH_MAX];
341         char new_path[PATH_MAX];
342
343         snprintf(old_path, sizeof(old_path), "%s" SLASH "%s", mesh->confbase, old_conf_subdir);
344         snprintf(new_path, sizeof(new_path), "%s" SLASH "%s", mesh->confbase, new_conf_subdir);
345
346         return rename(old_path, new_path) == 0;
347 }
348
349 bool config_sync(meshlink_handle_t *mesh, const char *conf_subdir) {
350         if(!mesh->confbase) {
351                 return true;
352         }
353
354         if(!conf_subdir) {
355                 return false;
356         }
357
358         char path[PATH_MAX];
359         snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "hosts", mesh->confbase, conf_subdir);
360
361         if(!sync_path(path)) {
362                 return false;
363         }
364
365         snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir);
366
367         if(!sync_path(path)) {
368                 return false;
369         }
370
371         return true;
372 }
373
374 bool meshlink_confbase_exists(meshlink_handle_t *mesh) {
375         if(!mesh->confbase) {
376                 return false;
377         }
378
379         bool confbase_exists = false;
380         bool confbase_decryptable = false;
381
382         if(main_config_exists(mesh, "current")) {
383                 confbase_exists = true;
384
385                 if(mesh->config_key && main_config_decrypt(mesh, "current")) {
386                         confbase_decryptable = true;
387                 }
388         }
389
390         if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "new")) {
391                 confbase_exists = true;
392
393                 if(main_config_decrypt(mesh, "new")) {
394                         if(!config_destroy(mesh->confbase, "current")) {
395                                 return false;
396                         }
397
398                         if(!config_rename(mesh, "new", "current")) {
399                                 return false;
400                         }
401
402                         confbase_decryptable = true;
403                 }
404         }
405
406         if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) {
407                 confbase_exists = true;
408
409                 if(main_config_decrypt(mesh, "old")) {
410                         if(!config_destroy(mesh->confbase, "current")) {
411                                 return false;
412                         }
413
414                         if(!config_rename(mesh, "old", "current")) {
415                                 return false;
416                         }
417
418                         confbase_decryptable = true;
419                 }
420         }
421
422         // Cleanup if current is existing with old and new
423         if(confbase_exists && confbase_decryptable) {
424                 config_destroy(mesh->confbase, "old");
425                 config_destroy(mesh->confbase, "new");
426         }
427
428         return confbase_exists;
429 }
430
431 /// Lock the main configuration file.
432 bool main_config_lock(meshlink_handle_t *mesh) {
433         if(!mesh->confbase) {
434                 return true;
435         }
436
437         char path[PATH_MAX];
438         make_main_path(mesh, "current", path, sizeof(path));
439
440         mesh->conffile = fopen(path, "r");
441
442         if(!mesh->conffile) {
443                 logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
444                 meshlink_errno = MESHLINK_ESTORAGE;
445                 return false;
446         }
447
448 #ifdef FD_CLOEXEC
449         fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
450 #endif
451
452 #ifdef HAVE_MINGW
453         // TODO: use _locking()?
454 #else
455
456         if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
457                 logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
458                 fclose(mesh->conffile);
459                 mesh->conffile = NULL;
460                 meshlink_errno = MESHLINK_EBUSY;
461                 return false;
462         }
463
464 #endif
465
466         return true;
467 }
468
469 /// Unlock the main configuration file.
470 void main_config_unlock(meshlink_handle_t *mesh) {
471         if(mesh->conffile) {
472                 fclose(mesh->conffile);
473                 mesh->conffile = NULL;
474         }
475 }
476
477 /// Read a configuration file from a FILE handle.
478 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) {
479         long len;
480
481         if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
482                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
483                 meshlink_errno = MESHLINK_ESTORAGE;
484                 fclose(f);
485                 return false;
486         }
487
488         uint8_t *buf = xmalloc(len);
489
490         if(fread(buf, len, 1, f) != 1) {
491                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
492                 meshlink_errno = MESHLINK_ESTORAGE;
493                 fclose(f);
494                 return false;
495         }
496
497         if(key) {
498                 uint8_t *decrypted = xmalloc(len);
499                 size_t decrypted_len = len;
500                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
501                 chacha_poly1305_set_key(ctx, key);
502
503                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
504                         chacha_poly1305_exit(ctx);
505                         free(buf);
506                         config->buf = decrypted;
507                         config->len = decrypted_len;
508                         return true;
509                 } else {
510                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
511                         meshlink_errno = MESHLINK_ESTORAGE;
512                         chacha_poly1305_exit(ctx);
513                         free(decrypted);
514                         free(buf);
515                         return false;
516                 }
517         }
518
519         config->buf = buf;
520         config->len = len;
521
522         return true;
523 }
524
525 /// Write a configuration file to a FILE handle.
526 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) {
527         if(key) {
528                 uint8_t buf[config->len + 16];
529                 size_t len = sizeof(buf);
530                 uint8_t seqbuf[12];
531                 randomize(&seqbuf, sizeof(seqbuf));
532                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
533                 chacha_poly1305_set_key(ctx, key);
534                 bool success = false;
535
536                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
537                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
538                 } else {
539                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
540                         meshlink_errno = MESHLINK_ESTORAGE;
541                 }
542
543                 chacha_poly1305_exit(ctx);
544                 return success;
545         }
546
547         if(fwrite(config->buf, config->len, 1, f) != 1) {
548                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
549                 meshlink_errno = MESHLINK_ESTORAGE;
550                 return false;
551         }
552
553         if(fsync(fileno(f))) {
554                 logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
555                 return false;
556         }
557
558         return true;
559 }
560
561 /// Free resources of a loaded configuration file.
562 void config_free(config_t *config) {
563         free((uint8_t *)config->buf);
564         config->buf = NULL;
565         config->len = 0;
566 }
567
568 /// Check the presence of a host configuration file.
569 bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
570         if(!mesh->confbase && !conf_subdir) {
571                 return false;
572         }
573
574         char path[PATH_MAX];
575         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
576
577         return access(path, F_OK) == 0;
578 }
579
580 /// Read a host configuration file.
581 bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
582         if(!mesh->confbase && !conf_subdir) {
583                 return false;
584         }
585
586         char path[PATH_MAX];
587         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
588
589         FILE *f = fopen(path, "r");
590
591         if(!f) {
592                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
593                 return false;
594         }
595
596         if(!config_read_file(mesh, f, config, key)) {
597                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
598                 fclose(f);
599                 return false;
600         }
601
602         fclose(f);
603
604         return true;
605 }
606
607 bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) {
608         if(!mesh->confbase && !conf_subdir && !conf_type) {
609                 return false;
610         }
611
612         DIR *dir;
613         struct dirent *ent;
614         char dname[PATH_MAX];
615         snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type);
616
617         dir = opendir(dname);
618
619         if(!dir) {
620                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
621                 meshlink_errno = MESHLINK_ESTORAGE;
622                 return false;
623         }
624
625         while((ent = readdir(dir))) {
626                 if(ent->d_name[0] == '.') {
627                         continue;
628                 }
629
630                 if(!action(mesh, ent->d_name, arg)) {
631                         closedir(dir);
632                         return false;
633                 }
634         }
635
636         closedir(dir);
637         return true;
638 }
639
640 /// Write a host configuration file.
641 bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
642         if(!mesh->confbase && !conf_subdir && !name) {
643                 return true;
644         }
645
646         char path[PATH_MAX];
647         char tmp_path[PATH_MAX + 4];
648         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
649         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
650
651         FILE *f = fopen(tmp_path, "w");
652
653         if(!f) {
654                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
655                 return false;
656         }
657
658         if(!config_write_file(mesh, f, config, key)) {
659                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
660                 fclose(f);
661                 return false;
662         }
663
664         if(fsync(fileno(f))) {
665                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", tmp_path, strerror(errno));
666                 fclose(f);
667                 return false;
668         }
669
670         if(fclose(f)) {
671                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
672                 return false;
673         }
674
675         if(rename(tmp_path, path)) {
676                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
677                 return false;
678         }
679
680         return true;
681 }
682
683 /// Read the main configuration file.
684 bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
685         if(!mesh->confbase && !conf_subdir) {
686                 return false;
687         }
688
689         char path[PATH_MAX];
690         make_main_path(mesh, conf_subdir, path, sizeof(path));
691
692         FILE *f = fopen(path, "r");
693
694         if(!f) {
695                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
696                 return false;
697         }
698
699         if(!config_read_file(mesh, f, config, key)) {
700                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
701                 fclose(f);
702                 return false;
703         }
704
705         fclose(f);
706
707         return true;
708 }
709
710 /// Write the main configuration file.
711 bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
712         if(!mesh->confbase && !conf_subdir) {
713                 return true;
714         }
715
716         char path[PATH_MAX];
717         char tmp_path[PATH_MAX + 4];
718         make_main_path(mesh, conf_subdir, path, sizeof(path));
719         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
720
721         FILE *f = fopen(tmp_path, "w");
722
723         if(!f) {
724                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
725                 return false;
726         }
727
728         if(!config_write_file(mesh, f, config, key)) {
729                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
730                 fclose(f);
731                 return false;
732         }
733
734         if(fsync(fileno(f))) {
735                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", tmp_path, strerror(errno));
736                 fclose(f);
737                 return false;
738         }
739
740         if(rename(tmp_path, path)) {
741                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
742                 return false;
743         }
744
745         if(fclose(f)) {
746                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
747                 return false;
748         }
749
750         return true;
751 }
752
753 /// Read an invitation file from the confbase sub-directory, and immediately delete it.
754 bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
755         if(!mesh->confbase && !conf_subdir) {
756                 return false;
757         }
758
759         char path[PATH_MAX];
760         char used_path[PATH_MAX];
761         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
762         make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path));
763
764         // Atomically rename the invitation file
765         if(rename(path, used_path)) {
766                 if(errno == ENOENT) {
767                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
768                 } else {
769                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
770                 }
771
772                 return false;
773         }
774
775         FILE *f = fopen(used_path, "r");
776
777         if(!f) {
778                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
779                 return false;
780         }
781
782         // Check the timestamp
783         struct stat st;
784
785         if(fstat(fileno(f), &st)) {
786                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
787                 fclose(f);
788                 unlink(used_path);
789                 return false;
790         }
791
792         if(mesh->loop.now.tv_sec > st.st_mtime + mesh->invitation_timeout) {
793                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
794                 fclose(f);
795                 unlink(used_path);
796                 return false;
797         }
798
799         if(!config_read_file(mesh, f, config, key)) {
800                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
801                 fclose(f);
802                 unlink(used_path);
803                 return false;
804         }
805
806         fclose(f);
807
808         unlink(used_path);
809         return true;
810 }
811
812 /// Write an invitation file.
813 bool invitation_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
814         if(!mesh->confbase && !conf_subdir) {
815                 return false;
816         }
817
818         char path[PATH_MAX];
819         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
820
821         FILE *f = fopen(path, "w");
822
823         if(!f) {
824                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
825                 return false;
826         }
827
828         if(!config_write_file(mesh, f, config, key)) {
829                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
830                 fclose(f);
831                 return false;
832         }
833
834         if(fsync(fileno(f))) {
835                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno));
836                 return false;
837         }
838
839         if(fclose(f)) {
840                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
841                 return false;
842         }
843
844         return true;
845 }
846
847 /// Purge old invitation files
848 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
849         if(!mesh->confbase) {
850                 return true;
851         }
852
853         char path[PATH_MAX];
854         make_invitation_path(mesh, "current", "", path, sizeof(path));
855
856         DIR *dir = opendir(path);
857
858         if(!dir) {
859                 logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
860                 meshlink_errno = MESHLINK_ESTORAGE;
861                 return 0;
862         }
863
864         errno = 0;
865         size_t count = 0;
866         struct dirent *ent;
867
868         while((ent = readdir(dir))) {
869                 if(strlen(ent->d_name) != 24) {
870                         continue;
871                 }
872
873                 char invname[PATH_MAX];
874                 struct stat st;
875
876                 if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
877                         logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
878                         continue;
879                 }
880
881                 if(!stat(invname, &st)) {
882                         if(mesh->invitation_key && deadline < st.st_mtime) {
883                                 count++;
884                         } else {
885                                 unlink(invname);
886                         }
887                 } else {
888                         logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
889                         errno = 0;
890                 }
891         }
892
893         if(errno) {
894                 logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
895                 closedir(dir);
896                 meshlink_errno = MESHLINK_ESTORAGE;
897                 return 0;
898         }
899
900         closedir(dir);
901
902         return count;
903 }