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