]> git.meshlink.io Git - meshlink-tiny/blob - src/conf.c
Check for the availability of flock().
[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_FLOCK
487
488         if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) {
489                 logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", lock_filename, strerror(errno));
490                 fclose(mesh->lockfile);
491                 mesh->lockfile = NULL;
492                 meshlink_errno = MESHLINK_EBUSY;
493                 return false;
494         }
495
496 #endif
497
498         return true;
499 }
500
501 /// Unlock the main configuration file.
502 void main_config_unlock(meshlink_handle_t *mesh) {
503         if(mesh->lockfile) {
504                 fclose(mesh->lockfile);
505                 mesh->lockfile = NULL;
506         }
507 }
508
509 /// Read a configuration file from a FILE handle.
510 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) {
511         assert(f);
512
513         long len;
514
515         if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
516                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
517                 meshlink_errno = MESHLINK_ESTORAGE;
518                 return false;
519         }
520
521         uint8_t *buf = xmalloc(len);
522
523         if(fread(buf, len, 1, f) != 1) {
524                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
525                 meshlink_errno = MESHLINK_ESTORAGE;
526                 return false;
527         }
528
529         if(key) {
530                 uint8_t *decrypted = xmalloc(len);
531                 size_t decrypted_len = len;
532                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
533                 chacha_poly1305_set_key(ctx, key);
534
535                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
536                         chacha_poly1305_exit(ctx);
537                         free(buf);
538                         config->buf = decrypted;
539                         config->len = decrypted_len;
540                         return true;
541                 } else {
542                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
543                         meshlink_errno = MESHLINK_ESTORAGE;
544                         chacha_poly1305_exit(ctx);
545                         free(decrypted);
546                         free(buf);
547                         return false;
548                 }
549         }
550
551         config->buf = buf;
552         config->len = len;
553
554         return true;
555 }
556
557 /// Write a configuration file to a FILE handle.
558 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) {
559         assert(f);
560
561         if(key) {
562                 uint8_t buf[config->len + 16];
563                 size_t len = sizeof(buf);
564                 uint8_t seqbuf[12];
565                 randomize(&seqbuf, sizeof(seqbuf));
566                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
567                 chacha_poly1305_set_key(ctx, key);
568                 bool success = false;
569
570                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
571                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
572
573                         if(!success) {
574                                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
575                         }
576
577                         meshlink_errno = MESHLINK_ESTORAGE;
578                 } else {
579                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
580                         meshlink_errno = MESHLINK_ESTORAGE;
581                 }
582
583                 chacha_poly1305_exit(ctx);
584                 return success;
585         }
586
587         if(fwrite(config->buf, config->len, 1, f) != 1) {
588                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
589                 meshlink_errno = MESHLINK_ESTORAGE;
590                 return false;
591         }
592
593         if(fflush(f)) {
594                 logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno));
595                 meshlink_errno = MESHLINK_ESTORAGE;
596                 return false;
597         }
598
599         if(fsync(fileno(f))) {
600                 logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
601                 meshlink_errno = MESHLINK_ESTORAGE;
602                 return false;
603         }
604
605         return true;
606 }
607
608 /// Free resources of a loaded configuration file.
609 void config_free(config_t *config) {
610         assert(!config->len || config->buf);
611
612         free((uint8_t *)config->buf);
613         config->buf = NULL;
614         config->len = 0;
615 }
616
617 /// Check the presence of a host configuration file.
618 bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
619         assert(conf_subdir);
620
621         if(!mesh->confbase) {
622                 return false;
623         }
624
625         char path[PATH_MAX];
626         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
627
628         return access(path, F_OK) == 0;
629 }
630
631 /// Read a host configuration file.
632 bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
633         assert(conf_subdir);
634
635         if(!mesh->confbase) {
636                 return false;
637         }
638
639         char path[PATH_MAX];
640         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
641
642         FILE *f = fopen(path, "r");
643
644         if(!f) {
645                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
646                 return false;
647         }
648
649         if(!config_read_file(mesh, f, config, key)) {
650                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
651                 fclose(f);
652                 return false;
653         }
654
655         fclose(f);
656
657         return true;
658 }
659
660 bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) {
661         assert(conf_subdir);
662         assert(conf_type);
663
664         if(!mesh->confbase) {
665                 return true;
666         }
667
668         DIR *dir;
669         struct dirent *ent;
670         char dname[PATH_MAX];
671         snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type);
672
673         dir = opendir(dname);
674
675         if(!dir) {
676                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
677                 meshlink_errno = MESHLINK_ESTORAGE;
678                 return false;
679         }
680
681         while((ent = readdir(dir))) {
682                 if(ent->d_name[0] == '.') {
683                         continue;
684                 }
685
686                 if(!action(mesh, ent->d_name, arg)) {
687                         closedir(dir);
688                         return false;
689                 }
690         }
691
692         closedir(dir);
693         return true;
694 }
695
696 /// Write a host configuration file.
697 bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
698         assert(conf_subdir);
699         assert(name);
700         assert(config);
701
702         if(!mesh->confbase) {
703                 return true;
704         }
705
706         char path[PATH_MAX];
707         char tmp_path[PATH_MAX + 4];
708         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
709         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
710
711         FILE *f = fopen(tmp_path, "w");
712
713         if(!f) {
714                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
715                 meshlink_errno = MESHLINK_ESTORAGE;
716                 return false;
717         }
718
719         if(!config_write_file(mesh, f, config, key)) {
720                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
721                 fclose(f);
722                 return false;
723         }
724
725         if(fclose(f)) {
726                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
727                 meshlink_errno = MESHLINK_ESTORAGE;
728                 return false;
729         }
730
731         if(rename(tmp_path, path)) {
732                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
733                 meshlink_errno = MESHLINK_ESTORAGE;
734                 return false;
735         }
736
737         return true;
738 }
739
740 /// Delete a host configuration file.
741 bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
742         assert(conf_subdir);
743         assert(name);
744
745         if(!mesh->confbase) {
746                 return true;
747         }
748
749         char path[PATH_MAX];
750         make_host_path(mesh, conf_subdir, name, path, sizeof(path));
751
752         if(unlink(path) && errno != ENOENT) {
753                 logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno));
754                 meshlink_errno = MESHLINK_ESTORAGE;
755                 return false;
756         }
757
758         return true;
759 }
760
761 /// Read the main configuration file.
762 bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
763         assert(conf_subdir);
764         assert(config);
765
766         if(!mesh->confbase) {
767                 return false;
768         }
769
770         char path[PATH_MAX];
771         make_main_path(mesh, conf_subdir, path, sizeof(path));
772
773         FILE *f = fopen(path, "r");
774
775         if(!f) {
776                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
777                 return false;
778         }
779
780         if(!config_read_file(mesh, f, config, key)) {
781                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
782                 fclose(f);
783                 return false;
784         }
785
786         fclose(f);
787
788         return true;
789 }
790
791 /// Write the main configuration file.
792 bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
793         assert(conf_subdir);
794         assert(config);
795
796         if(!mesh->confbase) {
797                 return true;
798         }
799
800         char path[PATH_MAX];
801         char tmp_path[PATH_MAX + 4];
802         make_main_path(mesh, conf_subdir, path, sizeof(path));
803         snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
804
805         FILE *f = fopen(tmp_path, "w");
806
807         if(!f) {
808                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
809                 meshlink_errno = MESHLINK_ESTORAGE;
810                 return false;
811         }
812
813         if(!config_write_file(mesh, f, config, key)) {
814                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
815                 fclose(f);
816                 return false;
817         }
818
819         if(rename(tmp_path, path)) {
820                 logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
821                 meshlink_errno = MESHLINK_ESTORAGE;
822                 fclose(f);
823                 return false;
824         }
825
826         if(fclose(f)) {
827                 logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
828                 meshlink_errno = MESHLINK_ESTORAGE;
829                 return false;
830         }
831
832         return true;
833 }