]> git.meshlink.io Git - meshlink/blob - src/conf.c
e0d3595041c2807d7393e912ff935b8584f0751c
[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         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
620
621         FILE *f = fopen(path, "w");
622
623         if(!f) {
624                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
625                 return false;
626         }
627
628         if(!config_write_file(mesh, f, config, key)) {
629                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
630                 fclose(f);
631                 return false;
632         }
633
634         if(fclose(f)) {
635                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
636                 return false;
637         }
638
639         return true;
640 }
641
642 /// Read the main configuration file.
643 bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
644         if(!mesh->confbase && !conf_subdir) {
645                 return false;
646         }
647
648         char path[PATH_MAX];
649         make_main_path(mesh, conf_subdir, path, sizeof(path));
650
651         FILE *f = fopen(path, "r");
652
653         if(!f) {
654                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
655                 return false;
656         }
657
658         if(!config_read_file(mesh, f, config, key)) {
659                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
660                 fclose(f);
661                 return false;
662         }
663
664         fclose(f);
665
666         return true;
667 }
668
669 /// Write the main configuration file.
670 bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
671         if(!mesh->confbase && !conf_subdir) {
672                 return true;
673         }
674
675         char path[PATH_MAX];
676         make_main_path(mesh, conf_subdir, path, sizeof(path));
677
678         FILE *f = fopen(path, "w");
679
680         if(!f) {
681                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
682                 return false;
683         }
684
685         if(!config_write_file(mesh, f, config, key)) {
686                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
687                 fclose(f);
688                 return false;
689         }
690
691         if(fclose(f)) {
692                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
693                 return false;
694         }
695
696         return true;
697 }
698
699 /// Read an invitation file from the confbase sub-directory, and immediately delete it.
700 bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
701         if(!mesh->confbase && !conf_subdir) {
702                 return false;
703         }
704
705         char path[PATH_MAX];
706         char used_path[PATH_MAX];
707         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
708         make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path));
709
710         // Atomically rename the invitation file
711         if(rename(path, used_path)) {
712                 if(errno == ENOENT) {
713                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
714                 } else {
715                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
716                 }
717
718                 return false;
719         }
720
721         FILE *f = fopen(used_path, "r");
722
723         if(!f) {
724                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
725                 return false;
726         }
727
728         // Check the timestamp
729         struct stat st;
730
731         if(fstat(fileno(f), &st)) {
732                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
733                 fclose(f);
734                 unlink(used_path);
735                 return false;
736         }
737
738         if(mesh->loop.now.tv_sec > st.st_mtime + mesh->invitation_timeout) {
739                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
740                 fclose(f);
741                 unlink(used_path);
742                 return false;
743         }
744
745         if(!config_read_file(mesh, f, config, key)) {
746                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
747                 fclose(f);
748                 unlink(used_path);
749                 return false;
750         }
751
752         fclose(f);
753
754         unlink(used_path);
755         return true;
756 }
757
758 /// Write an invitation file.
759 bool invitation_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
760         if(!mesh->confbase && !conf_subdir) {
761                 return false;
762         }
763
764         char path[PATH_MAX];
765         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
766
767         FILE *f = fopen(path, "w");
768
769         if(!f) {
770                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
771                 return false;
772         }
773
774         if(!config_write_file(mesh, f, config, key)) {
775                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
776                 fclose(f);
777                 return false;
778         }
779
780         if(fclose(f)) {
781                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
782                 return false;
783         }
784
785         return true;
786 }
787
788 /// Purge old invitation files
789 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
790         if(!mesh->confbase) {
791                 return true;
792         }
793
794         char path[PATH_MAX];
795         make_invitation_path(mesh, "current", "", path, sizeof(path));
796
797         DIR *dir = opendir(path);
798
799         if(!dir) {
800                 logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
801                 meshlink_errno = MESHLINK_ESTORAGE;
802                 return 0;
803         }
804
805         errno = 0;
806         size_t count = 0;
807         struct dirent *ent;
808
809         while((ent = readdir(dir))) {
810                 if(strlen(ent->d_name) != 24) {
811                         continue;
812                 }
813
814                 char invname[PATH_MAX];
815                 struct stat st;
816
817                 if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
818                         logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
819                         continue;
820                 }
821
822                 if(!stat(invname, &st)) {
823                         if(mesh->invitation_key && deadline < st.st_mtime) {
824                                 count++;
825                         } else {
826                                 unlink(invname);
827                         }
828                 } else {
829                         logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
830                         errno = 0;
831                 }
832         }
833
834         if(errno) {
835                 logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
836                 closedir(dir);
837                 meshlink_errno = MESHLINK_ESTORAGE;
838                 return 0;
839         }
840
841         closedir(dir);
842
843         return count;
844 }