]> git.meshlink.io Git - meshlink/blob - src/conf.c
Modify meshlink configuration base file structre
[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         DIR *src_dir = opendir(src_dir_name);
210
211         if(!src_dir) {
212                 logger(NULL, MESHLINK_ERROR, "Could not open directory file %s\n", src_dir_name);
213                 return false;
214         }
215
216         struct dirent *ent;
217
218         while((ent = readdir(src_dir))) {
219                 if(ent->d_name[0] == '.') {
220                         continue;
221                 }
222
223                 char src_filename[PATH_MAX];
224                 char dst_filename[PATH_MAX];
225
226                 snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", dst_dir_name, ent->d_name);
227                 snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", src_dir_name, ent->d_name);
228
229                 if(ent->d_type == DT_DIR) {
230
231                         // Delete if already exists and create a new destination directory
232                         deltree(dst_filename);
233
234                         if(mkdir(dst_filename, 0700)) {
235                                 logger(NULL, MESHLINK_ERROR, "Could create directory %s\n", dst_filename);
236                                 return false;
237                         }
238
239                         if(!copytree(src_filename, src_key, dst_filename, dst_key)) {
240                                 logger(NULL, MESHLINK_ERROR, "Copying %s to %s failed\n", src_filename, dst_filename);
241                                 return false;
242                         }
243
244                         if(!sync_path(dst_filename)) {
245                                 return false;
246                         }
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         if(main_config_exists(mesh, dst_dir_name)) {
322                 deltree(dst_dir_name);
323         }
324
325         if(mkdir(dst_filename, 0700)) {
326                 logger(NULL, MESHLINK_ERROR, "Could create directory %s\n", dst_filename);
327                 return false;
328         }
329
330         return copytree(src_filename, src_key, dst_filename, dst_key);
331 }
332
333 /// Check the presence of the main configuration file.
334 bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) {
335         if(!mesh->confbase && !conf_subdir) {
336                 return false;
337         }
338
339         char path[PATH_MAX];
340         make_main_path(mesh, conf_subdir, path, sizeof(path));
341         return access(path, F_OK) == 0;
342 }
343
344 bool config_rename(meshlink_handle_t *mesh, const char *old_conf_subdir, const char *new_conf_subdir) {
345         if(!mesh->confbase && !old_conf_subdir && !new_conf_subdir) {
346                 return false;
347         }
348
349         char old_path[PATH_MAX];
350         char new_path[PATH_MAX];
351
352         snprintf(old_path, sizeof(old_path), "%s" SLASH "%s", mesh->confbase, old_conf_subdir);
353         snprintf(new_path, sizeof(new_path), "%s" SLASH "%s", mesh->confbase, new_conf_subdir);
354
355         return rename(old_path, new_path) == 0;
356 }
357
358 bool meshlink_confbase_exists(meshlink_handle_t *mesh) {
359         if(!mesh->confbase) {
360                 return false;
361         }
362
363         bool confbase_exists = false;
364         bool confbase_decryptable = false;
365
366         if(main_config_exists(mesh, "current")) {
367                 confbase_exists = true;
368
369                 if(mesh->config_key && main_config_decrypt(mesh, "current")) {
370                         confbase_decryptable = true;
371                 }
372         }
373
374         if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "new")) {
375                 confbase_exists = true;
376
377                 if(main_config_decrypt(mesh, "new")) {
378                         if(!config_destroy(mesh->confbase, "current")) {
379                                 return false;
380                         }
381
382                         if(!config_rename(mesh, "new", "current")) {
383                                 return false;
384                         }
385
386                         confbase_decryptable = true;
387                 }
388         }
389
390         if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) {
391                 confbase_exists = true;
392
393                 if(main_config_decrypt(mesh, "old")) {
394                         if(!config_destroy(mesh->confbase, "current")) {
395                                 return false;
396                         }
397
398                         if(!config_rename(mesh, "old", "current")) {
399                                 return false;
400                         }
401
402                         confbase_decryptable = true;
403                 }
404         }
405
406         // Cleanup if current is existing with old and new
407         if(confbase_exists && confbase_decryptable) {
408                 config_destroy(mesh->confbase, "old");
409                 config_destroy(mesh->confbase, "new");
410         }
411
412         return confbase_exists;
413 }
414
415 /// Lock the main configuration file.
416 bool main_config_lock(meshlink_handle_t *mesh) {
417         if(!mesh->confbase) {
418                 return true;
419         }
420
421         char path[PATH_MAX];
422         make_main_path(mesh, "current", path, sizeof(path));
423
424         mesh->conffile = fopen(path, "r");
425
426         if(!mesh->conffile) {
427                 logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
428                 meshlink_errno = MESHLINK_ESTORAGE;
429                 return false;
430         }
431
432 #ifdef FD_CLOEXEC
433         fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
434 #endif
435
436 #ifdef HAVE_MINGW
437         // TODO: use _locking()?
438 #else
439
440         if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
441                 logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
442                 fclose(mesh->conffile);
443                 mesh->conffile = NULL;
444                 meshlink_errno = MESHLINK_EBUSY;
445                 return false;
446         }
447
448 #endif
449
450         return true;
451 }
452
453 /// Unlock the main configuration file.
454 void main_config_unlock(meshlink_handle_t *mesh) {
455         if(mesh->conffile) {
456                 fclose(mesh->conffile);
457                 mesh->conffile = NULL;
458         }
459 }
460
461 /// Read a configuration file from a FILE handle.
462 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) {
463         long len;
464
465         if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
466                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
467                 meshlink_errno = MESHLINK_ESTORAGE;
468                 fclose(f);
469                 return false;
470         }
471
472         uint8_t *buf = xmalloc(len);
473
474         if(fread(buf, len, 1, f) != 1) {
475                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
476                 meshlink_errno = MESHLINK_ESTORAGE;
477                 fclose(f);
478                 return false;
479         }
480
481         if(key) {
482                 uint8_t *decrypted = xmalloc(len);
483                 size_t decrypted_len = len;
484                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
485                 chacha_poly1305_set_key(ctx, key);
486
487                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
488                         chacha_poly1305_exit(ctx);
489                         free(buf);
490                         config->buf = decrypted;
491                         config->len = decrypted_len;
492                         return true;
493                 } else {
494                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
495                         meshlink_errno = MESHLINK_ESTORAGE;
496                         chacha_poly1305_exit(ctx);
497                         free(decrypted);
498                         free(buf);
499                         return false;
500                 }
501         }
502
503         config->buf = buf;
504         config->len = len;
505
506         return true;
507 }
508
509 /// Write a configuration file to a FILE handle.
510 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) {
511         if(key) {
512                 uint8_t buf[config->len + 16];
513                 size_t len = sizeof(buf);
514                 uint8_t seqbuf[12];
515                 randomize(&seqbuf, sizeof(seqbuf));
516                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
517                 chacha_poly1305_set_key(ctx, key);
518                 bool success = false;
519
520                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
521                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
522                 } else {
523                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
524                         meshlink_errno = MESHLINK_ESTORAGE;
525                 }
526
527                 chacha_poly1305_exit(ctx);
528                 return success;
529         }
530
531         if(fwrite(config->buf, config->len, 1, f) != 1) {
532                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
533                 meshlink_errno = MESHLINK_ESTORAGE;
534                 return false;
535         }
536
537         if(fsync(fileno(f))) {
538                 logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
539                 return false;
540         }
541
542         return true;
543 }
544
545 /// Free resources of a loaded configuration file.
546 void config_free(config_t *config) {
547         free((uint8_t *)config->buf);
548         config->buf = NULL;
549         config->len = 0;
550 }
551
552 /// Check the presence of a host configuration file.
553 bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
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         return access(path, F_OK) == 0;
562 }
563
564 /// Read a host configuration file.
565 bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
566         if(!mesh->confbase && !conf_subdir) {
567                 return false;
568         }
569
570         char path[PATH_MAX];
571         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
572
573         FILE *f = fopen(path, "r");
574
575         if(!f) {
576                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
577                 return false;
578         }
579
580         if(!config_read_file(mesh, f, config, key)) {
581                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
582                 fclose(f);
583                 return false;
584         }
585
586         if(fclose(f)) {
587                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
588                 return false;
589         }
590
591         return true;
592 }
593
594 bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) {
595         if(!mesh->confbase && !conf_subdir && !conf_type) {
596                 return false;
597         }
598
599         DIR *dir;
600         struct dirent *ent;
601         char dname[PATH_MAX];
602         snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type);
603
604         dir = opendir(dname);
605
606         if(!dir) {
607                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
608                 meshlink_errno = MESHLINK_ESTORAGE;
609                 return false;
610         }
611
612         while((ent = readdir(dir))) {
613                 if(ent->d_name[0] == '.') {
614                         continue;
615                 }
616
617                 if(!action(mesh, ent->d_name, arg)) {
618                         closedir(dir);
619                         return false;
620                 }
621         }
622
623         closedir(dir);
624         return true;
625 }
626
627 /// Write a host configuration file.
628 bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
629         if(!mesh->confbase && !conf_subdir && !name) {
630                 return true;
631         }
632
633         char path[PATH_MAX];
634         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
635
636         FILE *f = fopen(path, "w");
637
638         if(!f) {
639                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
640                 return false;
641         }
642
643         if(!config_write_file(mesh, f, config, key)) {
644                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
645                 fclose(f);
646                 return false;
647         }
648
649         if(fclose(f)) {
650                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
651                 return false;
652         }
653
654         return true;
655 }
656
657 /// Read the main configuration file.
658 bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
659         if(!mesh->confbase && !conf_subdir) {
660                 return false;
661         }
662
663         char path[PATH_MAX];
664         make_main_path(mesh, conf_subdir, path, sizeof(path));
665
666         FILE *f = fopen(path, "r");
667
668         if(!f) {
669                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
670                 return false;
671         }
672
673         if(!config_read_file(mesh, f, config, key)) {
674                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
675                 fclose(f);
676                 return false;
677         }
678
679         if(fclose(f)) {
680                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
681                 return false;
682         }
683
684         return true;
685 }
686
687 /// Write the main configuration file.
688 bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
689         if(!mesh->confbase && !conf_subdir) {
690                 return true;
691         }
692
693         char path[PATH_MAX];
694         make_main_path(mesh, conf_subdir, path, sizeof(path));
695
696         FILE *f = fopen(path, "w");
697
698         if(!f) {
699                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
700                 return false;
701         }
702
703         if(!config_write_file(mesh, f, config, key)) {
704                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
705                 fclose(f);
706                 return false;
707         }
708
709         if(fclose(f)) {
710                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
711                 return false;
712         }
713
714         return true;
715 }
716
717 /// Read an invitation file from the confbase sub-directory, and immediately delete it.
718 bool invitation_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
719         if(!mesh->confbase && !conf_subdir) {
720                 return false;
721         }
722
723         char path[PATH_MAX];
724         char used_path[PATH_MAX];
725         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
726         make_used_invitation_path(mesh, conf_subdir, name, used_path, sizeof(used_path));
727
728         // Atomically rename the invitation file
729         if(rename(path, used_path)) {
730                 if(errno == ENOENT) {
731                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
732                 } else {
733                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
734                 }
735
736                 return false;
737         }
738
739         FILE *f = fopen(used_path, "r");
740
741         if(!f) {
742                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
743                 return false;
744         }
745
746         // Check the timestamp
747         struct stat st;
748
749         if(fstat(fileno(f), &st)) {
750                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
751                 fclose(f);
752                 unlink(used_path);
753                 return false;
754         }
755
756         if(time(NULL) > st.st_mtime + mesh->invitation_timeout) {
757                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
758                 fclose(f);
759                 unlink(used_path);
760                 return false;
761         }
762
763         if(!config_read_file(mesh, f, config, key)) {
764                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
765                 fclose(f);
766                 unlink(used_path);
767                 return false;
768         }
769
770         if(fclose(f)) {
771                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
772                 return false;
773         }
774
775         unlink(used_path);
776         return true;
777 }
778
779 /// Write an invitation file.
780 bool invitation_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
781         if(!mesh->confbase && !conf_subdir) {
782                 return false;
783         }
784
785         char path[PATH_MAX];
786         make_invitation_path(mesh, conf_subdir, name, path, sizeof(path));
787
788         FILE *f = fopen(path, "w");
789
790         if(!f) {
791                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
792                 return false;
793         }
794
795         if(!config_write_file(mesh, f, config, key)) {
796                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
797                 fclose(f);
798                 return false;
799         }
800
801         if(fclose(f)) {
802                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", path, strerror(errno));
803                 return false;
804         }
805
806         return true;
807 }
808
809 /// Purge old invitation files
810 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
811         if(!mesh->confbase) {
812                 return true;
813         }
814
815         char path[PATH_MAX];
816         make_invitation_path(mesh, "current", "", path, sizeof(path));
817
818         DIR *dir = opendir(path);
819
820         if(!dir) {
821                 logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
822                 meshlink_errno = MESHLINK_ESTORAGE;
823                 return 0;
824         }
825
826         errno = 0;
827         size_t count = 0;
828         struct dirent *ent;
829
830         while((ent = readdir(dir))) {
831                 if(strlen(ent->d_name) != 24) {
832                         continue;
833                 }
834
835                 char invname[PATH_MAX];
836                 struct stat st;
837
838                 if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
839                         logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
840                         continue;
841                 }
842
843                 if(!stat(invname, &st)) {
844                         if(mesh->invitation_key && deadline < st.st_mtime) {
845                                 count++;
846                         } else {
847                                 unlink(invname);
848                         }
849                 } else {
850                         logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
851                         errno = 0;
852                 }
853         }
854
855         if(errno) {
856                 logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
857                 closedir(dir);
858                 meshlink_errno = MESHLINK_ESTORAGE;
859                 return 0;
860         }
861
862         closedir(dir);
863
864         return count;
865 }