]> git.meshlink.io Git - meshlink/blob - src/conf.c
Clean up after tests.
[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
22 #include <assert.h>
23 #include <sys/types.h>
24 #include <utime.h>
25
26 #include "conf.h"
27 #include "crypto.h"
28 #include "logger.h"
29 #include "meshlink_internal.h"
30 #include "packmsg.h"
31 #include "protocol.h"
32 #include "xalloc.h"
33
34 static bool sync_path(const char *pathname) {
35         assert(pathname);
36
37         int fd = open(pathname, O_RDONLY);
38
39         if(fd < 0) {
40                 logger(NULL, MESHLINK_ERROR, "Failed to open %s: %s\n", pathname, strerror(errno));
41                 meshlink_errno = MESHLINK_ESTORAGE;
42                 return false;
43         }
44
45         if(fsync(fd)) {
46                 logger(NULL, MESHLINK_ERROR, "Failed to sync %s: %s\n", pathname, strerror(errno));
47                 close(fd);
48                 meshlink_errno = MESHLINK_ESTORAGE;
49                 return false;
50         }
51
52         if(close(fd)) {
53                 logger(NULL, MESHLINK_ERROR, "Failed to close %s: %s\n", pathname, strerror(errno));
54                 close(fd);
55                 meshlink_errno = MESHLINK_ESTORAGE;
56                 return false;
57         }
58
59         return true;
60 }
61
62 static bool invalidate_config_file(meshlink_handle_t *mesh, const char *name, size_t len) {
63         (void)len;
64         config_t empty_config = {NULL, 0};
65         return config_store(mesh, name, &empty_config);
66 }
67
68 /// Wipe an existing configuration directory
69 bool config_destroy(const struct meshlink_open_params *params) {
70         if(!params->confbase) {
71                 return true;
72         }
73
74         FILE *lockfile = NULL;
75
76         if(!params->load_cb) {
77                 /* Exit early if the confbase directory itself doesn't exist */
78                 if(access(params->confbase, F_OK) && errno == ENOENT) {
79                         return true;
80                 }
81
82                 /* Take the lock the same way meshlink_open() would. */
83                 lockfile = fopen(params->lock_filename, "w+");
84
85                 if(!lockfile) {
86                         logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno));
87                         meshlink_errno = MESHLINK_ESTORAGE;
88                         return false;
89                 }
90
91 #ifdef FD_CLOEXEC
92                 fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC);
93 #endif
94
95 #ifdef HAVE_MINGW
96                 // TODO: use _locking()?
97 #else
98
99                 if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) {
100                         logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename);
101                         fclose(lockfile);
102                         meshlink_errno = MESHLINK_EBUSY;
103                         return false;
104                 }
105
106 #endif
107         }
108
109         {
110                 meshlink_handle_t tmp_mesh;
111                 memset(&tmp_mesh, 0, sizeof tmp_mesh);
112
113                 tmp_mesh.confbase = params->confbase;
114                 tmp_mesh.name = params->name;
115                 tmp_mesh.load_cb = params->load_cb;
116                 tmp_mesh.store_cb = params->store_cb;
117                 tmp_mesh.ls_cb = params->ls_cb;
118
119                 if(!config_ls(&tmp_mesh, invalidate_config_file)) {
120                         logger(NULL, MESHLINK_ERROR, "Cannot remove configuration files\n");
121                         fclose(lockfile);
122                         meshlink_errno = MESHLINK_ESTORAGE;
123                         return false;
124                 }
125         }
126
127         if(!params->load_cb) {
128                 if(unlink(params->lock_filename) && errno != ENOENT) {
129                         logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno));
130                         fclose(lockfile);
131                         meshlink_errno = MESHLINK_ESTORAGE;
132                         return false;
133                 }
134
135                 fclose(lockfile);
136
137                 if(!sync_path(params->confbase)) {
138                         logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno));
139                         meshlink_errno = MESHLINK_ESTORAGE;
140                         return false;
141                 }
142
143                 rmdir(params->confbase);
144         }
145
146         return true;
147 }
148
149 /// Read a blob of data.
150 static bool load(meshlink_handle_t *mesh, const char *key, void *data, size_t *len) {
151         logger(mesh, MESHLINK_DEBUG, "load(%s, %p, %zu)", key ? key : "(null)", data, *len);
152
153         if(mesh->load_cb) {
154                 if(!mesh->load_cb(mesh, key, data, len)) {
155                         logger(mesh, MESHLINK_ERROR, "Failed to open `%s'\n", key);
156                         meshlink_errno = MESHLINK_ESTORAGE;
157                         return false;
158                 } else {
159                         return true;
160                 }
161         }
162
163         char filename[PATH_MAX];
164         snprintf(filename, sizeof(filename), "%s/%s", mesh->confbase, key);
165
166         FILE *f = fopen(filename, "r");
167
168         if(!f) {
169                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s\n", filename, strerror(errno));
170                 meshlink_errno = MESHLINK_ESTORAGE;
171                 return false;
172         }
173
174         long actual_len;
175
176         if(fseek(f, 0, SEEK_END) || (actual_len = ftell(f)) <= 0 || fseek(f, 0, SEEK_SET)) {
177                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
178                 meshlink_errno = MESHLINK_ESTORAGE;
179                 fclose(f);
180                 return false;
181         }
182
183         size_t todo = (size_t)actual_len < *len ? (size_t)actual_len : *len;
184         *len = actual_len;
185
186         if(!data) {
187                 fclose(f);
188                 return true;
189         }
190
191         if(fread(data, todo, 1, f) != 1) {
192                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
193                 meshlink_errno = MESHLINK_ESTORAGE;
194                 fclose(f);
195                 return false;
196         }
197
198         if(fclose(f)) {
199                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno));
200                 meshlink_errno = MESHLINK_ESTORAGE;
201                 return false;
202         }
203
204         return true;
205 }
206
207 /// Store a blob of data.
208 static bool store(meshlink_handle_t *mesh, const char *key, const void *data, size_t len) {
209         logger(mesh, MESHLINK_DEBUG, "store(%s, %p, %zu)", key ? key : "(null)", data, len);
210
211         if(mesh->store_cb) {
212                 if(!mesh->store_cb(mesh, key, data, len)) {
213                         logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
214                         meshlink_errno = MESHLINK_ESTORAGE;
215                         return false;
216                 } else {
217                         return true;
218                 }
219         }
220
221         char filename[PATH_MAX];
222         snprintf(filename, sizeof(filename), "%s" SLASH "%s", mesh->confbase, key);
223
224         if(!len) {
225                 if(unlink(filename) && errno != ENOENT) {
226                         logger(mesh, MESHLINK_ERROR, "Failed to remove `%s': %s", filename, strerror(errno));
227                         meshlink_errno = MESHLINK_ESTORAGE;
228                         return false;
229                 } else {
230                         return true;
231                 }
232         }
233
234         char tmp_filename[PATH_MAX];
235         snprintf(tmp_filename, sizeof(tmp_filename), "%s" SLASH "%s.tmp", mesh->confbase, key);
236
237         FILE *f = fopen(tmp_filename, "w");
238
239         if(!f) {
240                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_filename, strerror(errno));
241                 meshlink_errno = MESHLINK_ESTORAGE;
242                 return false;
243         }
244
245         if(fwrite(data, len, 1, f) != 1) {
246                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
247                 meshlink_errno = MESHLINK_ESTORAGE;
248                 fclose(f);
249                 return false;
250         }
251
252         if(fflush(f)) {
253                 logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno));
254                 meshlink_errno = MESHLINK_ESTORAGE;
255                 fclose(f);
256                 return false;
257         }
258
259         if(fsync(fileno(f))) {
260                 logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
261                 meshlink_errno = MESHLINK_ESTORAGE;
262                 fclose(f);
263                 return false;
264         }
265
266         if(fclose(f)) {
267                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno));
268                 meshlink_errno = MESHLINK_ESTORAGE;
269                 return false;
270         }
271
272         if(rename(tmp_filename, filename)) {
273                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_filename, filename, strerror(errno));
274                 meshlink_errno = MESHLINK_ESTORAGE;
275                 return false;
276         }
277
278         return true;
279 }
280
281 /// Read a configuration file, decrypting it if necessary.
282 bool config_load(meshlink_handle_t *mesh, const char *name, config_t *config) {
283         size_t buflen = 256;
284         uint8_t *buf = xmalloc(buflen);
285         size_t len = buflen;
286
287         if(!load(mesh, name, buf, &len)) {
288                 return false;
289         }
290
291         buf = xrealloc(buf, len);
292
293         if(len > buflen) {
294                 buflen = len;
295
296                 if(!load(mesh, name, (void **)&buf, &len) || len != buflen) {
297                         meshlink_errno = MESHLINK_ESTORAGE;
298                         return false;
299                 }
300         }
301
302         if(mesh->config_key) {
303                 if(len < 12 + 16) {
304                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
305                         meshlink_errno = MESHLINK_ESTORAGE;
306                         config_free(config);
307                         return false;
308                 }
309
310                 size_t decrypted_len = len - 12 - 16;
311                 uint8_t *decrypted = xmalloc(decrypted_len);
312
313                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
314                 chacha_poly1305_set_key(ctx, mesh->config_key);
315
316                 if(chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
317                         chacha_poly1305_exit(ctx);
318                         free(buf);
319                         buf = decrypted;
320                         len = decrypted_len;
321                 } else {
322                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
323                         meshlink_errno = MESHLINK_ESTORAGE;
324                         chacha_poly1305_exit(ctx);
325                         free(decrypted);
326                         free(buf);
327                         return false;
328                 }
329         }
330
331         config->buf = buf;
332         config->len = len;
333
334         return true;
335 }
336
337 bool config_exists(meshlink_handle_t *mesh, const char *name) {
338         size_t len = 0;
339
340         return load(mesh, name, NULL, &len) && len;
341 }
342
343 /// Write a configuration file, encrypting it if necessary.
344 bool config_store(meshlink_handle_t *mesh, const char *name, const config_t *config) {
345         if(mesh->config_key) {
346                 size_t encrypted_len = config->len + 16; // length of encrypted data
347                 uint8_t encrypted[12 + encrypted_len]; // store sequence number at the start
348
349                 randomize(encrypted, 12);
350                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
351                 chacha_poly1305_set_key(ctx, mesh->config_key);
352
353                 if(!chacha_poly1305_encrypt_iv96(ctx, encrypted, config->buf, config->len, encrypted + 12, &encrypted_len)) {
354                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
355                         meshlink_errno = MESHLINK_ESTORAGE;
356                         chacha_poly1305_exit(ctx);
357                         return false;
358                 }
359
360                 chacha_poly1305_exit(ctx);
361
362                 return store(mesh, name, encrypted, 12 + encrypted_len);
363         }
364
365         return store(mesh, name, config->buf, config->len);
366 }
367
368 /// Free resources of a loaded configuration file.
369 void config_free(config_t *config) {
370         assert(!config->len || config->buf);
371
372         free((uint8_t *)config->buf);
373         config->buf = NULL;
374         config->len = 0;
375 }
376
377 bool config_ls(meshlink_handle_t *mesh, config_scan_action_t action) {
378         logger(mesh, MESHLINK_DEBUG, "ls(%p)", (void *)(intptr_t)action);
379
380         if(!mesh->confbase) {
381                 return true;
382         }
383
384         if(mesh->ls_cb) {
385                 return mesh->ls_cb(mesh, action);
386         }
387
388         DIR *dir;
389         struct dirent *ent;
390
391         dir = opendir(mesh->confbase);
392
393         if(!dir) {
394                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", mesh->confbase, strerror(errno));
395                 meshlink_errno = MESHLINK_ESTORAGE;
396                 return false;
397         }
398
399         while((ent = readdir(dir))) {
400                 if(ent->d_name[0] == '.') {
401                         continue;
402                 }
403
404                 if(!action(mesh, ent->d_name, 0)) {
405                         closedir(dir);
406                         return false;
407                 }
408         }
409
410         closedir(dir);
411         return true;
412 }
413
414 /// Re-encrypt a configuration file.
415 static bool change_key(meshlink_handle_t *mesh, const char *name, size_t len) {
416         (void)len;
417         config_t config;
418
419         if(!config_load(mesh, name, &config)) {
420                 return false;
421         }
422
423         size_t name_len = strlen(name);
424         char new_name[name_len + 3];
425         memcpy(new_name, name, name_len);
426
427         if(!strcmp(name, "meshlink.conf")) {
428                 // Update meshlink.conf in-place
429                 new_name[name_len] = 0;
430         } else {
431                 memcpy(new_name + name_len, ".r", 3);
432         }
433
434         void *orig_key = mesh->config_key;
435         mesh->config_key = mesh->ls_priv;
436         bool result = config_store(mesh, new_name, &config);
437         mesh->config_key = orig_key;
438
439         return result;
440 }
441
442 extern bool (*devtool_keyrotate_probe)(int stage);
443
444 static bool change_node_key(meshlink_handle_t *mesh, const char *name, size_t len) {
445         /* Skip the main config and lock files */
446         if(!strcmp(name, "meshlink.conf") || !strcmp(name, "meshlink.lock")) {
447                 return true;
448         }
449
450         /* Skip any already rotated file */
451         int namelen = strlen(name);
452
453         if(namelen >= 2 && !strcmp(name + namelen - 2, ".r")) {
454                 return true;
455         }
456
457         if(!devtool_keyrotate_probe(0)) {
458                 return false;
459         }
460
461         return change_key(mesh, name, len);
462 }
463
464 static bool cleanup_old_file(meshlink_handle_t *mesh, const char *name, size_t len) {
465         (void)len;
466         size_t name_len = strlen(name);
467
468         if(name_len < 3 || strcmp(name + name_len - 2, ".r")) {
469                 return true;
470         }
471
472         config_t config;
473
474         if(!config_load(mesh, name, &config)) {
475                 store(mesh, name, NULL, 0);
476                 return true;
477         }
478
479         char new_name[name_len - 1];
480         memcpy(new_name, name, name_len - 2);
481         new_name[name_len - 2] = '\0';
482
483         return config_store(mesh, new_name, &config) && store(mesh, name, NULL, 0);
484 }
485
486 bool config_cleanup_old_files(meshlink_handle_t *mesh) {
487         return config_ls(mesh, cleanup_old_file);
488 }
489
490 bool config_change_key(meshlink_handle_t *mesh, void *new_key) {
491         mesh->ls_priv = new_key;
492
493         if(!config_ls(mesh, change_node_key)) {
494                 return false;
495         }
496
497         if(!devtool_keyrotate_probe(1)) {
498                 return false;
499         }
500
501         if(!change_key(mesh, "meshlink.conf", 0)) {
502                 return false;
503         }
504
505         free(mesh->config_key);
506         mesh->config_key = new_key;
507
508         if(!devtool_keyrotate_probe(2)) {
509                 return true;
510         }
511
512         config_cleanup_old_files(mesh);
513
514         devtool_keyrotate_probe(3);
515
516         return true;
517 }
518
519 /// Migrate old format configuration directory
520 static bool config_migrate(meshlink_handle_t *mesh) {
521         char base[PATH_MAX];
522         char path[PATH_MAX];
523         char new_path[PATH_MAX];
524
525         // Check if there we need to migrate
526
527         snprintf(path, sizeof path, "%s/meshlink.conf", mesh->confbase);
528
529         if(access(path, F_OK) == 0) {
530                 return true;
531         }
532
533         snprintf(path, sizeof path, "%s/current/meshlink.conf", mesh->confbase);
534
535         if(access(path, F_OK) == -1) {
536                 return true;
537         }
538
539         // Migrate host config files
540
541         DIR *dir;
542         struct dirent *ent;
543
544         snprintf(base, sizeof base, "%s/current/hosts", mesh->confbase);
545         dir = opendir(base);
546
547         if(!dir) {
548                 logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", base, mesh->confbase, strerror(errno));
549                 return false;
550         }
551
552         while((ent = readdir(dir))) {
553                 if(ent->d_name[0] == '.') {
554                         continue;
555                 }
556
557                 if(!check_id(ent->d_name)) {
558                         continue;
559                 }
560
561                 snprintf(path, sizeof path, "%s/current/hosts/%s", mesh->confbase, ent->d_name);
562                 snprintf(new_path, sizeof new_path, "%s/%s", mesh->confbase, ent->d_name);
563
564                 if(rename(path, new_path) == -1) {
565                         logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno));
566                         closedir(dir);
567                         return false;
568                 }
569         }
570
571         closedir(dir);
572
573         // Migrate invitation files
574
575         snprintf(base, sizeof base, "%s/current/invitations", mesh->confbase);
576         dir = opendir(base);
577
578         if(!dir) {
579                 logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", base, mesh->confbase, strerror(errno));
580                 return false;
581         }
582
583         while((ent = readdir(dir))) {
584                 if(ent->d_name[0] == '.') {
585                         continue;
586                 }
587
588                 snprintf(path, sizeof path, "%s/current/invitations/%s", mesh->confbase, ent->d_name);
589                 snprintf(new_path, sizeof new_path, "%s/%s.inv", mesh->confbase, ent->d_name);
590
591                 if(rename(path, new_path) == -1) {
592                         logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno));
593                         closedir(dir);
594                         return false;
595                 }
596         }
597
598         closedir(dir);
599
600         // Migrate meshlink.conf
601
602         snprintf(path, sizeof path, "%s/current/meshlink.conf", mesh->confbase);
603         snprintf(new_path, sizeof new_path, "%s/meshlink.conf", mesh->confbase);
604
605         if(rename(path, new_path) == -1) {
606                 logger(NULL, MESHLINK_ERROR, "Failed to migrate %s to %s: %s", path, new_path, strerror(errno));
607                 return false;
608         }
609
610         // Remove directories that should now be empty
611
612         snprintf(base, sizeof base, "%s/current/hosts", mesh->confbase);
613         rmdir(base);
614         snprintf(base, sizeof base, "%s/current/invitations", mesh->confbase);
615         rmdir(base);
616         snprintf(base, sizeof base, "%s/current", mesh->confbase);
617         rmdir(base);
618
619         // Done.
620
621
622         return true;
623 }
624
625 /// Initialize the configuration directory
626 bool config_init(meshlink_handle_t *mesh, const struct meshlink_open_params *params) {
627         if(!mesh->confbase) {
628                 return true;
629         }
630
631         if(!mesh->load_cb) {
632                 if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
633                         logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno));
634                         meshlink_close(mesh);
635                         meshlink_errno = MESHLINK_ESTORAGE;
636                         return NULL;
637                 }
638
639                 mesh->lockfile = fopen(params->lock_filename, "w+");
640
641                 if(!mesh->lockfile) {
642                         logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", params->lock_filename, strerror(errno));
643                         meshlink_errno = MESHLINK_ESTORAGE;
644                         return false;
645                 }
646
647 #ifdef FD_CLOEXEC
648                 fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC);
649 #endif
650
651 #ifdef HAVE_FLOCK
652
653                 if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) {
654                         logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", params->lock_filename, strerror(errno));
655                         fclose(mesh->lockfile);
656                         mesh->lockfile = NULL;
657                         meshlink_errno = MESHLINK_EBUSY;
658                         return false;
659                 }
660
661 #endif
662
663                 if(!config_migrate(mesh)) {
664                         return false;
665                 }
666         }
667
668         return true;
669 }
670
671 void config_exit(meshlink_handle_t *mesh) {
672         if(mesh->lockfile) {
673                 fclose(mesh->lockfile);
674                 mesh->lockfile = NULL;
675         }
676 }
677
678 static void make_invitation_path(const char *name, char *path, size_t len) {
679         assert(name);
680         assert(path);
681         assert(len);
682
683         snprintf(path, len, "%s.inv", name);
684 }
685
686 /// Read an invitation file from the confbase sub-directory, and immediately delete it.
687 bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
688         assert(name);
689         assert(config);
690
691         char invitation_name[PATH_MAX];
692         make_invitation_path(name, invitation_name, sizeof(invitation_name));
693
694         if(!config_load(mesh, invitation_name, config)) {
695                 logger(mesh, MESHLINK_ERROR, "Could not read invitation file %s\n", invitation_name);
696                 return false;
697         }
698
699         // Make sure the file is deleted so it cannot be reused
700         if(!invalidate_config_file(mesh, invitation_name, 0)) {
701                 logger(mesh, MESHLINK_ERROR, "Could not delete invitation file %s\n", invitation_name);
702                 config_free(config);
703                 return false;
704         }
705
706         return true;
707 }
708
709 /// Write an invitation file.
710 bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
711         assert(name);
712         assert(config);
713
714         char invitation_name[PATH_MAX];
715         make_invitation_path(name, invitation_name, sizeof(invitation_name));
716
717         if(!config_store(mesh, invitation_name, config)) {
718                 logger(mesh, MESHLINK_ERROR, "Could not write invitation file %s\n", invitation_name);
719                 return false;
720         }
721
722         return true;
723 }
724
725 typedef struct {
726         time_t deadline;
727         const char *node_name;
728         size_t count;
729 } purge_info_t;
730
731 static bool purge_cb(meshlink_handle_t *mesh, const char *name, size_t len) {
732         (void)len;
733         purge_info_t *info = mesh->ls_priv;
734         size_t namelen = strlen(name);
735
736         // Skip anything that is not an invitation
737         if(namelen < 4 || strcmp(name + namelen - 4, ".inv") != 0) {
738                 return true;
739         }
740
741         config_t config;
742
743         if(!config_load(mesh, name, &config)) {
744                 logger(mesh, MESHLINK_ERROR, "Could not read invitation file %s\n", name);
745                 // Purge anything we can't read
746                 invalidate_config_file(mesh, name, 0);
747                 info->count++;
748                 return true;
749         }
750
751         packmsg_input_t in = {config.buf, config.len};
752         uint32_t version = packmsg_get_uint32(&in);
753         time_t timestamp = packmsg_get_int64(&in);
754
755         if(!packmsg_input_ok(&in) || version != MESHLINK_INVITATION_VERSION) {
756                 logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", name);
757                 invalidate_config_file(mesh, name, 0);
758                 info->count++;
759         }
760
761         if(info->node_name) {
762                 char *node_name = packmsg_get_str_dup(&in);
763
764                 if(!node_name || strcmp(node_name, info->node_name) == 0) {
765                         invalidate_config_file(mesh, name, 0);
766                         info->count++;
767                 }
768
769                 free(node_name);
770         } else if(timestamp < info->deadline) {
771                 invalidate_config_file(mesh, name, 0);
772                 info->count++;
773         }
774
775         config_free(&config);
776         return true;
777 }
778
779 /// Purge old invitation files
780 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
781         purge_info_t info = {deadline, NULL, 0};
782         mesh->ls_priv = &info;
783
784         if(!config_ls(mesh, purge_cb)) {
785                 /* Ignore any failure */
786         }
787
788         return info.count;
789 }
790
791 /// Purge invitations for the given node
792 size_t invitation_purge_node(meshlink_handle_t *mesh, const char *node_name) {
793         purge_info_t info = {0, node_name, 0};
794         mesh->ls_priv = &info;
795
796         if(!config_ls(mesh, purge_cb)) {
797                 /* Ignore any failure */
798         }
799
800         return info.count;
801 }