]> git.meshlink.io Git - meshlink/blob - src/conf.c
Fix potential double fclose().
[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                 return false;
485         }
486
487         uint8_t *buf = xmalloc(len);
488
489         if(fread(buf, len, 1, f) != 1) {
490                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
491                 meshlink_errno = MESHLINK_ESTORAGE;
492                 return false;
493         }
494
495         if(key) {
496                 uint8_t *decrypted = xmalloc(len);
497                 size_t decrypted_len = len;
498                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
499                 chacha_poly1305_set_key(ctx, key);
500
501                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
502                         chacha_poly1305_exit(ctx);
503                         free(buf);
504                         config->buf = decrypted;
505                         config->len = decrypted_len;
506                         return true;
507                 } else {
508                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
509                         meshlink_errno = MESHLINK_ESTORAGE;
510                         chacha_poly1305_exit(ctx);
511                         free(decrypted);
512                         free(buf);
513                         return false;
514                 }
515         }
516
517         config->buf = buf;
518         config->len = len;
519
520         return true;
521 }
522
523 /// Write a configuration file to a FILE handle.
524 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) {
525         if(key) {
526                 uint8_t buf[config->len + 16];
527                 size_t len = sizeof(buf);
528                 uint8_t seqbuf[12];
529                 randomize(&seqbuf, sizeof(seqbuf));
530                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
531                 chacha_poly1305_set_key(ctx, key);
532                 bool success = false;
533
534                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
535                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
536                 } else {
537                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
538                         meshlink_errno = MESHLINK_ESTORAGE;
539                 }
540
541                 chacha_poly1305_exit(ctx);
542                 return success;
543         }
544
545         if(fwrite(config->buf, config->len, 1, f) != 1) {
546                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
547                 meshlink_errno = MESHLINK_ESTORAGE;
548                 return false;
549         }
550
551         if(fsync(fileno(f))) {
552                 logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
553                 return false;
554         }
555
556         return true;
557 }
558
559 /// Free resources of a loaded configuration file.
560 void config_free(config_t *config) {
561         free((uint8_t *)config->buf);
562         config->buf = NULL;
563         config->len = 0;
564 }
565
566 /// Check the presence of a host configuration file.
567 bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
568         if(!mesh->confbase && !conf_subdir) {
569                 return false;
570         }
571
572         char path[PATH_MAX];
573         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
574
575         return access(path, F_OK) == 0;
576 }
577
578 /// Read a host configuration file.
579 bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
580         if(!mesh->confbase && !conf_subdir) {
581                 return false;
582         }
583
584         char path[PATH_MAX];
585         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
586
587         FILE *f = fopen(path, "r");
588
589         if(!f) {
590                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
591                 return false;
592         }
593
594         if(!config_read_file(mesh, f, config, key)) {
595                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
596                 fclose(f);
597                 return false;
598         }
599
600         fclose(f);
601
602         return true;
603 }
604
605 bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) {
606         if(!mesh->confbase && !conf_subdir && !conf_type) {
607                 return false;
608         }
609
610         DIR *dir;
611         struct dirent *ent;
612         char dname[PATH_MAX];
613         snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type);
614
615         dir = opendir(dname);
616
617         if(!dir) {
618                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
619                 meshlink_errno = MESHLINK_ESTORAGE;
620                 return false;
621         }
622
623         while((ent = readdir(dir))) {
624                 if(ent->d_name[0] == '.') {
625                         continue;
626                 }
627
628                 if(!action(mesh, ent->d_name, arg)) {
629                         closedir(dir);
630                         return false;
631                 }
632         }
633
634         closedir(dir);
635         return true;
636 }
637
638 /// Write a host configuration file.
639 bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
640         if(!mesh->confbase && !conf_subdir && !name) {
641                 return true;
642         }
643
644         char path[PATH_MAX];
645         char tmp_path[PATH_MAX + 4];
646         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
647         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
648
649         FILE *f = fopen(tmp_path, "w");
650
651         if(!f) {
652                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
653                 return false;
654         }
655
656         if(!config_write_file(mesh, f, config, key)) {
657                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
658                 fclose(f);
659                 return false;
660         }
661
662         if(fsync(fileno(f))) {
663                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", tmp_path, strerror(errno));
664                 fclose(f);
665                 return false;
666         }
667
668         if(fclose(f)) {
669                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
670                 return false;
671         }
672
673         if(rename(tmp_path, path)) {
674                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
675                 return false;
676         }
677
678         return true;
679 }
680
681 /// Read the main configuration file.
682 bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
683         if(!mesh->confbase && !conf_subdir) {
684                 return false;
685         }
686
687         char path[PATH_MAX];
688         make_main_path(mesh, conf_subdir, path, sizeof(path));
689
690         FILE *f = fopen(path, "r");
691
692         if(!f) {
693                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
694                 return false;
695         }
696
697         if(!config_read_file(mesh, f, config, key)) {
698                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
699                 fclose(f);
700                 return false;
701         }
702
703         fclose(f);
704
705         return true;
706 }
707
708 /// Write the main configuration file.
709 bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
710         if(!mesh->confbase && !conf_subdir) {
711                 return true;
712         }
713
714         char path[PATH_MAX];
715         char tmp_path[PATH_MAX + 4];
716         make_main_path(mesh, conf_subdir, path, sizeof(path));
717         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
718
719         FILE *f = fopen(tmp_path, "w");
720
721         if(!f) {
722                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
723                 return false;
724         }
725
726         if(!config_write_file(mesh, f, config, key)) {
727                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
728                 fclose(f);
729                 return false;
730         }
731
732         if(fsync(fileno(f))) {
733                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", tmp_path, strerror(errno));
734                 fclose(f);
735                 return false;
736         }
737
738         if(rename(tmp_path, path)) {
739                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
740                 fclose(f);
741                 return false;
742         }
743
744         if(fclose(f)) {
745                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
746                 return false;
747         }
748
749         return true;
750 }
751
752 /// Read an invitation file from the confbase sub-directory, and immediately delete it.
753 bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
754         if(!mesh->confbase && !conf_subdir) {
755                 return false;
756         }
757
758         char path[PATH_MAX];
759         char used_path[PATH_MAX];
760         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
761         make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path));
762
763         // Atomically rename the invitation file
764         if(rename(path, used_path)) {
765                 if(errno == ENOENT) {
766                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
767                 } else {
768                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
769                 }
770
771                 return false;
772         }
773
774         FILE *f = fopen(used_path, "r");
775
776         if(!f) {
777                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
778                 return false;
779         }
780
781         // Check the timestamp
782         struct stat st;
783
784         if(fstat(fileno(f), &st)) {
785                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
786                 fclose(f);
787                 unlink(used_path);
788                 return false;
789         }
790
791         if(mesh->loop.now.tv_sec > st.st_mtime + mesh->invitation_timeout) {
792                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
793                 fclose(f);
794                 unlink(used_path);
795                 return false;
796         }
797
798         if(!config_read_file(mesh, f, config, key)) {
799                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
800                 fclose(f);
801                 unlink(used_path);
802                 return false;
803         }
804
805         fclose(f);
806
807         unlink(used_path);
808         return true;
809 }
810
811 /// Write an invitation file.
812 bool invitation_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
813         if(!mesh->confbase && !conf_subdir) {
814                 return false;
815         }
816
817         char path[PATH_MAX];
818         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
819
820         FILE *f = fopen(path, "w");
821
822         if(!f) {
823                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
824                 return false;
825         }
826
827         if(!config_write_file(mesh, f, config, key)) {
828                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
829                 fclose(f);
830                 return false;
831         }
832
833         if(fsync(fileno(f))) {
834                 logger(mesh, MESHLINK_ERROR, "Failed to sync `%s': %s", path, strerror(errno));
835                 fclose(f);
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 }