]> git.meshlink.io Git - meshlink-tiny/blob - src/conf.c
Use a key/value store with configurable storage callbacks.
[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
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(name_len == 13 && name[8] == '.') {
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->config_new_key;
436         bool result = config_store(mesh, new_name, &config);
437         mesh->config_key = orig_key;
438
439         return result;
440 }
441
442 static bool change_node_key(meshlink_handle_t *mesh, const char *name, size_t len) {
443         if(!check_id(name)) {
444                 return true;
445         }
446
447         return change_key(mesh, name, len);
448 }
449
450 static bool cleanup_old_file(meshlink_handle_t *mesh, const char *name, size_t len) {
451         (void)len;
452         size_t name_len = strlen(name);
453
454         if(name_len < 3 || name[name_len - 2] != '.' || name[name_len - 1] != 'r') {
455                 return true;
456         }
457
458         config_t config;
459
460         if(!config_load(mesh, name, &config)) {
461                 return false;
462         }
463
464         char new_name[name_len - 1];
465         memcpy(new_name, name, name_len - 2);
466         new_name[name_len - 2] = '\0';
467
468         return config_store(mesh, new_name, &config) && store(mesh, name, NULL, 0);
469 }
470
471 bool config_cleanup_old_files(meshlink_handle_t *mesh) {
472         return config_ls(mesh, cleanup_old_file);
473 }
474
475 bool config_change_key(meshlink_handle_t *mesh, void *new_key) {
476         extern bool (*devtool_keyrotate_probe)(int stage);
477         mesh->config_new_key = new_key;
478
479         if(!config_ls(mesh, change_node_key)) {
480                 return false;
481         }
482
483         if(!devtool_keyrotate_probe(1)) {
484                 return false;
485         }
486
487         if(!change_key(mesh, "meshlink.conf", 0)) {
488                 return false;
489         }
490
491         free(mesh->config_key);
492         mesh->config_key = new_key;
493
494         if(!devtool_keyrotate_probe(2)) {
495                 return true;
496         }
497
498         config_cleanup_old_files(mesh);
499
500         devtool_keyrotate_probe(3);
501
502         return true;
503 }
504
505 /// Initialize the configuration directory
506 bool config_init(meshlink_handle_t *mesh, const struct meshlink_open_params *params) {
507         if(!mesh->confbase) {
508                 return true;
509         }
510
511         if(!mesh->load_cb) {
512                 if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
513                         logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno));
514                         meshlink_close(mesh);
515                         meshlink_errno = MESHLINK_ESTORAGE;
516                         return NULL;
517                 }
518
519                 mesh->lockfile = fopen(params->lock_filename, "w+");
520
521                 if(!mesh->lockfile) {
522                         logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", params->lock_filename, strerror(errno));
523                         meshlink_errno = MESHLINK_ESTORAGE;
524                         return false;
525                 }
526
527 #ifdef FD_CLOEXEC
528                 fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC);
529 #endif
530
531 #ifdef HAVE_FLOCK
532
533                 if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) {
534                         logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", params->lock_filename, strerror(errno));
535                         fclose(mesh->lockfile);
536                         mesh->lockfile = NULL;
537                         meshlink_errno = MESHLINK_EBUSY;
538                         return false;
539                 }
540
541 #endif
542         }
543
544         return true;
545 }
546
547 void config_exit(meshlink_handle_t *mesh) {
548         if(mesh->lockfile) {
549                 fclose(mesh->lockfile);
550                 mesh->lockfile = NULL;
551         }
552 }