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