]> git.meshlink.io Git - meshlink/blob - src/conf.c
Lock meshlink.conf to ensure only one instance can run at a time.
[meshlink] / src / conf.c
1 /*
2     conf.c -- configuration code
3     Copyright (C) 2014 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 "splay_tree.h"
24 #include "connection.h"
25 #include "conf.h"
26 #include "list.h"
27 #include "logger.h"
28 #include "meshlink_internal.h"
29 #include "netutl.h"             /* for str2address */
30 #include "protocol.h"
31 #include "utils.h"              /* for cp */
32 #include "xalloc.h"
33
34 static int config_compare(const config_t *a, const config_t *b) {
35         int result;
36
37         result = strcasecmp(a->variable, b->variable);
38
39         if(result) {
40                 return result;
41         }
42
43         return result = a->line - b->line;
44 }
45
46 void init_configuration(splay_tree_t **config_tree) {
47         *config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config);
48 }
49
50 void exit_configuration(splay_tree_t **config_tree) {
51         if(*config_tree) {
52                 splay_delete_tree(*config_tree);
53         }
54
55         *config_tree = NULL;
56 }
57
58 config_t *new_config(void) {
59         return xzalloc(sizeof(config_t));
60 }
61
62 void free_config(config_t *cfg) {
63         free(cfg->variable);
64         free(cfg->value);
65         free(cfg);
66 }
67
68 void config_add(splay_tree_t *config_tree, config_t *cfg) {
69         splay_insert(config_tree, cfg);
70 }
71
72 config_t *lookup_config(splay_tree_t *config_tree, char *variable) {
73         config_t cfg, *found;
74
75         cfg.variable = variable;
76         cfg.line = 0;
77
78         found = splay_search_closest_greater(config_tree, &cfg);
79
80         if(!found) {
81                 return NULL;
82         }
83
84         if(strcasecmp(found->variable, variable)) {
85                 return NULL;
86         }
87
88         return found;
89 }
90
91 config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) {
92         splay_node_t *node;
93         config_t *found;
94
95         node = splay_search_node(config_tree, cfg);
96
97         if(node) {
98                 if(node->next) {
99                         found = node->next->data;
100
101                         if(!strcasecmp(found->variable, cfg->variable)) {
102                                 return found;
103                         }
104                 }
105         }
106
107         return NULL;
108 }
109
110 bool get_config_bool(const config_t *cfg, bool *result) {
111         if(!cfg) {
112                 return false;
113         }
114
115         if(!strcasecmp(cfg->value, "yes")) {
116                 *result = true;
117                 return true;
118         } else if(!strcasecmp(cfg->value, "no")) {
119                 *result = false;
120                 return true;
121         }
122
123         logger(NULL, MESHLINK_ERROR, "\"yes\" or \"no\" expected for configuration variable %s in line %d",
124                cfg->variable, cfg->line);
125
126         return false;
127 }
128
129 bool get_config_int(const config_t *cfg, int *result) {
130         if(!cfg) {
131                 return false;
132         }
133
134         if(sscanf(cfg->value, "%d", result) == 1) {
135                 return true;
136         }
137
138         logger(NULL, MESHLINK_ERROR, "Integer expected for configuration variable %s in line %d",
139                cfg->variable, cfg->line);
140
141         return false;
142 }
143
144 bool set_config_int(config_t *cfg, int val) {
145         if(!cfg) {
146                 return false;
147         }
148
149         char val_str[1024];
150         snprintf(val_str, sizeof(val_str), "%d", val);
151
152         if(cfg->value) {
153                 free(cfg->value);
154         }
155
156         cfg->value = xstrdup(val_str);
157
158         return true;
159 }
160
161 bool get_config_string(const config_t *cfg, char **result) {
162         if(!cfg) {
163                 return false;
164         }
165
166         *result = xstrdup(cfg->value);
167
168         return true;
169 }
170
171 bool set_config_string(config_t *cfg, const char *val) {
172         if(!cfg) {
173                 return false;
174         }
175
176         if(cfg->value) {
177                 free(cfg->value);
178         }
179
180         cfg->value = xstrdup(val);
181
182         return true;
183 }
184
185 bool get_config_address(const config_t *cfg, struct addrinfo **result) {
186         struct addrinfo *ai;
187
188         if(!cfg) {
189                 return false;
190         }
191
192         ai = str2addrinfo(cfg->value, NULL, 0);
193
194         if(ai) {
195                 *result = ai;
196                 return true;
197         }
198
199         logger(NULL, MESHLINK_ERROR, "Hostname or IP address expected for configuration variable %s in line %d",
200                cfg->variable, cfg->line);
201
202         return false;
203 }
204
205 /*
206   Read exactly one line and strip the trailing newline if any.
207 */
208 static char *readline(FILE *fp, char *buf, size_t buflen) {
209         char *newline = NULL;
210         char *p;
211
212         if(feof(fp)) {
213                 return NULL;
214         }
215
216         p = fgets(buf, buflen, fp);
217
218         if(!p) {
219                 return NULL;
220         }
221
222         newline = strchr(p, '\n');
223
224         if(!newline) {
225                 return buf;
226         }
227
228         /* kill newline and carriage return if necessary */
229         *newline = '\0';
230
231         if(newline > p && newline[-1] == '\r') {
232                 newline[-1] = '\0';
233         }
234
235         return buf;
236 }
237
238 config_t *parse_config_line(char *line, const char *fname, int lineno) {
239         config_t *cfg;
240         int len;
241         char *variable, *value, *eol;
242         variable = value = line;
243
244         eol = line + strlen(line);
245
246         while(strchr("\t ", *--eol)) {
247                 *eol = '\0';
248         }
249
250         len = strcspn(value, "\t =");
251         value += len;
252         value += strspn(value, "\t ");
253
254         if(*value == '=') {
255                 value++;
256                 value += strspn(value, "\t ");
257         }
258
259         variable[len] = '\0';
260
261         if(!*value) {
262                 const char err[] = "No value for variable";
263                 logger(NULL, MESHLINK_ERROR, "%s `%s' on line %d while reading config file %s",
264                        err, variable, lineno, fname);
265                 return NULL;
266         }
267
268         cfg = new_config();
269         cfg->variable = xstrdup(variable);
270         cfg->value = xstrdup(value);
271         cfg->line = lineno;
272
273         return cfg;
274 }
275
276 /*
277   Parse a configuration file and put the results in the configuration tree
278   starting at *base.
279 */
280 bool read_config_file(splay_tree_t *config_tree, const char *fname) {
281         FILE *fp;
282         char buffer[MAX_STRING_SIZE];
283         char *line;
284         int lineno = 0;
285         bool ignore = false;
286         config_t *cfg;
287         bool result = false;
288
289         fp = fopen(fname, "r");
290
291         if(!fp) {
292                 logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno));
293                 return false;
294         }
295
296         for(;;) {
297                 line = readline(fp, buffer, sizeof(buffer));
298
299                 if(!line) {
300                         if(feof(fp)) {
301                                 result = true;
302                         }
303
304                         break;
305                 }
306
307                 lineno++;
308
309                 if(!*line || *line == '#') {
310                         continue;
311                 }
312
313                 if(ignore) {
314                         if(!strncmp(line, "-----END", 8)) {
315                                 ignore = false;
316                         }
317
318                         continue;
319                 }
320
321                 if(!strncmp(line, "-----BEGIN", 10)) {
322                         ignore = true;
323                         continue;
324                 }
325
326                 cfg = parse_config_line(line, fname, lineno);
327
328                 if(!cfg) {
329                         break;
330                 }
331
332                 config_add(config_tree, cfg);
333         }
334
335         fclose(fp);
336
337         return result;
338 }
339
340 bool write_config_file(const struct splay_tree_t *config_tree, const char *fname) {
341         FILE *fp;
342
343         fp = fopen(fname, "w+");
344
345         if(!fp) {
346                 logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno));
347                 return false;
348         }
349
350         for splay_each(config_t, cnf, config_tree) {
351                 if(fwrite(cnf->variable, sizeof(char), strlen(cnf->variable), fp) < strlen(cnf->variable)) {
352                         goto error;
353                 }
354
355                 if(fwrite(" = ", sizeof(char), 3, fp) < 3) {
356                         goto error;
357                 }
358
359                 if(fwrite(cnf->value, sizeof(char), strlen(cnf->value), fp) < strlen(cnf->value)) {
360                         goto error;
361                 }
362
363                 if(fwrite("\n", sizeof(char), 1, fp) < 1) {
364                         goto error;
365                 }
366         }
367
368         fclose(fp);
369         return true;
370
371 error:
372         logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno));
373         fclose(fp);
374         return false;
375 }
376
377 bool read_server_config(meshlink_handle_t *mesh) {
378         char filename[PATH_MAX];
379         bool x;
380
381         snprintf(filename, PATH_MAX, "%s" SLASH "meshlink.conf", mesh->confbase);
382         errno = 0;
383         x = read_config_file(mesh->config, filename);
384
385         if(!x && errno) {
386                 logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", filename, strerror(errno));
387         }
388
389         return x;
390 }
391
392 bool read_host_config(meshlink_handle_t *mesh, splay_tree_t *config_tree, const char *name) {
393         char filename[PATH_MAX];
394         bool x;
395
396         snprintf(filename, PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
397         x = read_config_file(config_tree, filename);
398
399         return x;
400 }
401
402 bool write_host_config(struct meshlink_handle *mesh, const struct splay_tree_t *config_tree, const char *name) {
403         char filename[PATH_MAX];
404
405         snprintf(filename, PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
406         return write_config_file(config_tree, filename);
407 }
408
409 bool modify_config_file(struct meshlink_handle *mesh, const char *name, const char *key, const char *value, int trim) {
410         assert(mesh && name && key);
411
412         char filename[PATH_MAX];
413         char tmpname[PATH_MAX];
414         bool error = false;
415
416         if(snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name) >= PATH_MAX) {
417                 logger(mesh, MESHLINK_ERROR, "Filename too long: %s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
418                 return false;
419         }
420
421         if(snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename) >= PATH_MAX) {
422                 logger(mesh, MESHLINK_ERROR, "Filename too long: %s.tmp", filename);
423                 return false;
424         }
425
426         FILE *fr = fopen(filename, "r");
427
428         if(!fr) {
429                 logger(mesh, MESHLINK_ERROR, "Cannot open config file %s: %s", filename, strerror(errno));
430                 return false;
431         }
432
433         FILE *fw = fopen(tmpname, "w");
434
435         if(!fw) {
436                 logger(mesh, MESHLINK_ERROR, "Cannot open temporary file %s: %s", tmpname, strerror(errno));
437                 fclose(fr);
438                 return false;
439         }
440
441         char buf[4096];
442         char *sep;
443         int found = 0;
444
445         if(value) {
446                 fprintf(fw, "%s = %s\n", key, value);
447                 found++;
448         }
449
450         while(readline(fr, buf, sizeof(buf))) {
451                 if(!*buf || *buf == '#') {
452                         goto copy;
453                 }
454
455                 sep = strchr(buf, ' ');
456
457                 if(!sep) {
458                         goto copy;
459                 }
460
461                 *sep = 0;
462
463                 if(strcmp(buf, key)) {
464                         *sep = ' ';
465                         goto copy;
466                 }
467
468                 // We found the key and the value. We already added it at the top, so ignore this one.
469                 if(value && sep[1] == '=' && sep[2] == ' ' && !strcmp(sep + 3, value)) {
470                         continue;
471                 }
472
473                 // We found the key but with a different value, delete it if wanted.
474                 found++;
475
476                 if((!value || trim) && found > trim) {
477                         continue;
478                 }
479
480                 *sep = ' ';
481
482 copy:
483                 fprintf(fw, "%s\n", buf);
484         }
485
486         if(ferror(fr)) {
487                 error = true;
488         }
489
490         fclose(fr);
491
492         if(ferror(fw)) {
493                 error = true;
494         }
495
496         if(fclose(fw)) {
497                 error = true;
498         }
499
500         // If any error occured during reading or writing, exit.
501         if(error) {
502                 unlink(tmpname);
503                 return false;
504         }
505
506         // Try to atomically replace the old config file with the new one.
507 #ifdef HAVE_MINGW
508         char bakname[PATH_MAX];
509         snprintf(bakname, sizeof(bakname), "%s.bak", filename);
510
511         if(rename(filename, bakname) || rename(tmpname, filename)) {
512                 rename(bakname, filename);
513 #else
514
515         if(rename(tmpname, filename)) {
516 #endif
517                 return false;
518         } else {
519 #ifdef HAVE_MINGW
520                 unlink(bakname);
521 #endif
522                 return true;
523         }
524 }
525
526 bool append_config_file(meshlink_handle_t *mesh, const char *name, const char *key, const char *value) {
527         return modify_config_file(mesh, name, key, value, 0);
528 }