]> git.meshlink.io Git - meshlink/blob - src/conf.c
1a3c82ae2e8c45e5cf07cceaf2945bcc9eaac6bf
[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
23 #include "conf.h"
24 #include "crypto.h"
25 #include "logger.h"
26 #include "meshlink_internal.h"
27 #include "xalloc.h"
28 #include "packmsg.h"
29
30 /// Generate a path to the main configuration file.
31 static void make_main_path(meshlink_handle_t *mesh, char *path, size_t len) {
32         snprintf(path, len, "%s" SLASH "meshlink.conf", mesh->confbase);
33 }
34
35 /// Generate a path to a host configuration file.
36 static void make_host_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
37         snprintf(path, len, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
38 }
39
40 /// Generate a path to an unused invitation file.
41 static void make_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
42         snprintf(path, len, "%s" SLASH "invitations" SLASH "%s", mesh->confbase, name);
43 }
44
45 /// Generate a path to a used invitation file.
46 static void make_used_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
47         snprintf(path, len, "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, name);
48 }
49
50 /// Remove a directory recursively
51 static void deltree(const char *dirname) {
52         DIR *d = opendir(dirname);
53
54         if(d) {
55                 struct dirent *ent;
56
57                 while((ent = readdir(d))) {
58                         if(ent->d_name[0] == '.') {
59                                 continue;
60                         }
61
62                         char filename[PATH_MAX];
63                         snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
64
65                         if(unlink(filename)) {
66                                 deltree(filename);
67                         }
68                 }
69
70                 closedir(d);
71         }
72
73         rmdir(dirname);
74 }
75
76 /// Create a fresh configuration directory
77 bool config_init(meshlink_handle_t *mesh) {
78         if(!mesh->confbase) {
79                 return true;
80         }
81
82         if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
83                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
84                 return false;
85         }
86
87         char path[PATH_MAX];
88
89         // Remove meshlink.conf
90         snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", mesh->confbase);
91         unlink(path);
92
93         // Remove any host config files
94         snprintf(path, sizeof(path), "%s" SLASH "hosts", mesh->confbase);
95         deltree(path);
96
97         if(mkdir(path, 0700) && errno != EEXIST) {
98                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
99                 return false;
100         }
101
102         // Remove any invitation files
103         snprintf(path, sizeof(path), "%s" SLASH "invitations", mesh->confbase);
104         deltree(path);
105
106         if(mkdir(path, 0700) && errno != EEXIST) {
107                 logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
108                 return false;
109         }
110
111         return true;
112 }
113
114 /// Wipe an existing configuration directory
115 bool config_destroy(const char *confbase) {
116         char path[PATH_MAX];
117
118         // Remove meshlink.conf
119         snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", confbase);
120
121         if(unlink(path)) {
122                 if(errno == ENOENT) {
123                         meshlink_errno = MESHLINK_ENOENT;
124                         return false;
125                 } else {
126                         logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
127                         meshlink_errno = MESHLINK_ESTORAGE;
128                         return false;
129                 }
130         }
131
132         deltree(confbase);
133         return true;
134 }
135
136 /// Check the presence of the main configuration file.
137 bool main_config_exists(meshlink_handle_t *mesh) {
138         if(!mesh->confbase) {
139                 return false;
140         }
141
142         char path[PATH_MAX];
143         make_main_path(mesh, path, sizeof(path));
144
145         return access(path, F_OK) == 0;
146 }
147
148 /// Lock the main configuration file.
149 bool main_config_lock(meshlink_handle_t *mesh) {
150         if(!mesh->confbase) {
151                 return true;
152         }
153
154         char path[PATH_MAX];
155         make_main_path(mesh, path, sizeof(path));
156
157         mesh->conffile = fopen(path, "r");
158
159         if(!mesh->conffile) {
160                 logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
161                 meshlink_errno = MESHLINK_ESTORAGE;
162                 return false;
163         }
164
165 #ifdef FD_CLOEXEC
166         fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
167 #endif
168
169 #ifdef HAVE_MINGW
170         // TODO: use _locking()?
171 #else
172
173         if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
174                 logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
175                 fclose(mesh->conffile);
176                 mesh->conffile = NULL;
177                 meshlink_errno = MESHLINK_EBUSY;
178                 return false;
179         }
180
181 #endif
182
183         return true;
184 }
185
186 /// Unlock the main configuration file.
187 void main_config_unlock(meshlink_handle_t *mesh) {
188         if(mesh->conffile) {
189                 fclose(mesh->conffile);
190                 mesh->conffile = NULL;
191         }
192 }
193
194 /// Read a configuration file from a FILE handle.
195 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) {
196         if(!mesh->confbase) {
197                 return false;
198         }
199
200         (void)mesh;
201         long len;
202
203         if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
204                 logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
205                 meshlink_errno = MESHLINK_ESTORAGE;
206                 fclose(f);
207                 return false;
208         }
209
210         uint8_t *buf = xmalloc(len);
211
212         if(fread(buf, len, 1, f) != 1) {
213                 logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
214                 meshlink_errno = MESHLINK_ESTORAGE;
215                 fclose(f);
216                 return false;
217         }
218
219         if(mesh->config_key) {
220                 uint8_t *decrypted = xmalloc(len);
221                 size_t decrypted_len = len;
222                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
223                 chacha_poly1305_set_key(ctx, mesh->config_key);
224
225                 if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
226                         chacha_poly1305_exit(ctx);
227                         free(buf);
228                         config->buf = decrypted;
229                         config->len = decrypted_len;
230                         return true;
231                 } else {
232                         logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
233                         meshlink_errno = MESHLINK_ESTORAGE;
234                         chacha_poly1305_exit(ctx);
235                         free(decrypted);
236                         free(buf);
237                         return false;
238                 }
239         }
240
241         config->buf = buf;
242         config->len = len;
243
244         return true;
245 }
246
247 /// Write a configuration file to a FILE handle.
248 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config) {
249         if(!mesh->confbase) {
250                 return true;
251         }
252
253         if(mesh->config_key) {
254                 uint8_t buf[config->len + 16];
255                 size_t len = sizeof(buf);
256                 uint8_t seqbuf[12];
257                 randomize(&seqbuf, sizeof(seqbuf));
258                 chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
259                 chacha_poly1305_set_key(ctx, mesh->config_key);
260                 bool success = false;
261
262                 if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
263                         success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
264                 } else {
265                         logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
266                         meshlink_errno = MESHLINK_ESTORAGE;
267                 }
268
269                 chacha_poly1305_exit(ctx);
270                 return success;
271         }
272
273         if(fwrite(config->buf, config->len, 1, f) != 1) {
274                 logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
275                 meshlink_errno = MESHLINK_ESTORAGE;
276                 return false;
277         }
278
279         return true;
280 }
281
282 /// Free resources of a loaded configuration file.
283 void config_free(config_t *config) {
284         free((uint8_t *)config->buf);
285         config->buf = NULL;
286         config->len = 0;
287 }
288
289 /// Check the presence of a host configuration file.
290 bool config_exists(meshlink_handle_t *mesh, const char *name) {
291         if(!mesh->confbase) {
292                 return false;
293         }
294
295         char path[PATH_MAX];
296         make_host_path(mesh, name, path, sizeof(path));
297
298         return access(path, F_OK) == 0;
299 }
300
301 /// Read a host configuration file.
302 bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
303         if(!mesh->confbase) {
304                 return false;
305         }
306
307         char path[PATH_MAX];
308         make_host_path(mesh, name, path, sizeof(path));
309
310         FILE *f = fopen(path, "r");
311
312         if(!f) {
313                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
314                 return false;
315         }
316
317         if(!config_read_file(mesh, f, config)) {
318                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
319                 fclose(f);
320                 return false;
321         }
322
323         fclose(f);
324         return true;
325 }
326
327 void config_scan_all(meshlink_handle_t *mesh, config_scan_action_t action) {
328         if(!mesh->confbase) {
329                 return;
330         }
331
332         DIR *dir;
333         struct dirent *ent;
334         char dname[PATH_MAX];
335         make_host_path(mesh, "", dname, sizeof(dname));
336
337         dir = opendir(dname);
338
339         if(!dir) {
340                 logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
341                 meshlink_errno = MESHLINK_ESTORAGE;
342                 return;
343         }
344
345         while((ent = readdir(dir))) {
346                 action(mesh, ent->d_name);
347         }
348
349         closedir(dir);
350 }
351
352 /// Write a host configuration file.
353 bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
354         if(!mesh->confbase) {
355                 return true;
356         }
357
358         char path[PATH_MAX];
359         make_host_path(mesh, name, path, sizeof(path));
360
361         FILE *f = fopen(path, "w");
362
363         if(!f) {
364                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
365                 return false;
366         }
367
368         if(!config_write_file(mesh, f, config)) {
369                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
370                 fclose(f);
371                 return false;
372         }
373
374         fclose(f);
375         return true;
376 }
377
378 /// Read the main configuration file.
379 bool main_config_read(meshlink_handle_t *mesh, config_t *config) {
380         if(!mesh->confbase) {
381                 return false;
382         }
383
384         char path[PATH_MAX];
385         make_main_path(mesh, path, sizeof(path));
386
387         FILE *f = fopen(path, "r");
388
389         if(!f) {
390                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
391                 return false;
392         }
393
394         if(!config_read_file(mesh, f, config)) {
395                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
396                 fclose(f);
397                 return false;
398         }
399
400         fclose(f);
401         return true;
402 }
403
404 /// Write the main configuration file.
405 bool main_config_write(meshlink_handle_t *mesh, const config_t *config) {
406         if(!mesh->confbase) {
407                 return true;
408         }
409
410         char path[PATH_MAX];
411         make_main_path(mesh, path, sizeof(path));
412
413         FILE *f = fopen(path, "w");
414
415         if(!f) {
416                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
417                 return false;
418         }
419
420         if(!config_write_file(mesh, f, config)) {
421                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
422                 fclose(f);
423                 return false;
424         }
425
426         fclose(f);
427         return true;
428 }
429
430 /// Read an invitation file, and immediately delete it.
431 bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
432         if(!mesh->confbase) {
433                 return false;
434         }
435
436         char path[PATH_MAX];
437         char used_path[PATH_MAX];
438         make_invitation_path(mesh, name, path, sizeof(path));
439         make_used_invitation_path(mesh, name, used_path, sizeof(used_path));
440
441         // Atomically rename the invitation file
442         if(rename(path, used_path)) {
443                 if(errno == ENOENT) {
444                         logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
445                 } else {
446                         logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
447                 }
448
449                 return false;
450         }
451
452         FILE *f = fopen(used_path, "r");
453
454         if(!f) {
455                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
456                 return false;
457         }
458
459         // Check the timestamp
460         struct stat st;
461
462         if(fstat(fileno(f), &st)) {
463                 logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
464                 fclose(f);
465                 unlink(used_path);
466                 return false;
467         }
468
469         if(time(NULL) > st.st_mtime + mesh->invitation_timeout) {
470                 logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
471                 fclose(f);
472                 unlink(used_path);
473                 return false;
474         }
475
476         if(!config_read_file(mesh, f, config)) {
477                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
478                 fclose(f);
479                 unlink(used_path);
480                 return false;
481         }
482
483         fclose(f);
484         unlink(used_path);
485         return true;
486 }
487
488 /// Write an invitation file.
489 bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
490         if(!mesh->confbase) {
491                 return true;
492         }
493
494         char path[PATH_MAX];
495         make_invitation_path(mesh, name, path, sizeof(path));
496
497         FILE *f = fopen(path, "w");
498
499         if(!f) {
500                 logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
501                 return false;
502         }
503
504         if(!config_write_file(mesh, f, config)) {
505                 logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
506                 fclose(f);
507                 return false;
508         }
509
510         fclose(f);
511         return true;
512 }
513
514 /// Purge old invitation files
515 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
516         if(!mesh->confbase) {
517                 return true;
518         }
519
520         char path[PATH_MAX];
521         make_invitation_path(mesh, "", path, sizeof(path));
522
523         DIR *dir = opendir(path);
524
525         if(!dir) {
526                 logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
527                 meshlink_errno = MESHLINK_ESTORAGE;
528                 return 0;
529         }
530
531         errno = 0;
532         size_t count = 0;
533         struct dirent *ent;
534
535         while((ent = readdir(dir))) {
536                 if(strlen(ent->d_name) != 24) {
537                         continue;
538                 }
539
540                 char invname[PATH_MAX];
541                 struct stat st;
542
543                 if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
544                         logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
545                         continue;
546                 }
547
548                 if(!stat(invname, &st)) {
549                         if(mesh->invitation_key && deadline < st.st_mtime) {
550                                 count++;
551                         } else {
552                                 unlink(invname);
553                         }
554                 } else {
555                         logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
556                         errno = 0;
557                 }
558         }
559
560         if(errno) {
561                 logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
562                 closedir(dir);
563                 meshlink_errno = MESHLINK_ESTORAGE;
564                 return 0;
565         }
566
567         closedir(dir);
568
569         return count;
570 }