]> git.meshlink.io Git - meshlink/blob - src/conf.c
Forget addresses of unreachable nodes.
[meshlink] / src / conf.c
1 /*
2     conf.c -- configuration code
3     Copyright (C) 1998 Robert van der Meulen
4                   1998-2005 Ivo Timmermans
5                   2000-2009 Guus Sliepen <guus@tinc-vpn.org>
6                   2000 Cris van Pelt
7
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17
18     You should have received a copy of the GNU General Public License along
19     with this program; if not, write to the Free Software Foundation, Inc.,
20     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include "system.h"
24
25 #include "avl_tree.h"
26 #include "conf.h"
27 #include "logger.h"
28 #include "netutl.h"                             /* for str2address */
29 #include "utils.h"                              /* for cp */
30 #include "xalloc.h"
31
32 avl_tree_t *config_tree;
33
34 int pinginterval = 0;                   /* seconds between pings */
35 int pingtimeout = 0;                    /* seconds to wait for response */
36 char *confbase = NULL;                  /* directory in which all config files are */
37 char *netname = NULL;                   /* name of the vpn network */
38
39 static int config_compare(const config_t *a, const config_t *b) {
40         int result;
41
42         result = strcasecmp(a->variable, b->variable);
43
44         if(result)
45                 return result;
46
47         result = a->line - b->line;
48
49         if(result)
50                 return result;
51         else
52                 return strcmp(a->file, b->file);
53 }
54
55 void init_configuration(avl_tree_t ** config_tree) {
56         *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
57 }
58
59 void exit_configuration(avl_tree_t ** config_tree) {
60         avl_delete_tree(*config_tree);
61         *config_tree = NULL;
62 }
63
64 config_t *new_config(void) {
65         return xmalloc_and_zero(sizeof(config_t));
66 }
67
68 void free_config(config_t *cfg) {
69         if(cfg->variable)
70                 free(cfg->variable);
71
72         if(cfg->value)
73                 free(cfg->value);
74
75         if(cfg->file)
76                 free(cfg->file);
77
78         free(cfg);
79 }
80
81 void config_add(avl_tree_t *config_tree, config_t *cfg) {
82         avl_insert(config_tree, cfg);
83 }
84
85 config_t *lookup_config(avl_tree_t *config_tree, char *variable) {
86         config_t cfg, *found;
87
88         cfg.variable = variable;
89         cfg.file = "";
90         cfg.line = 0;
91
92         found = avl_search_closest_greater(config_tree, &cfg);
93
94         if(!found)
95                 return NULL;
96
97         if(strcasecmp(found->variable, variable))
98                 return NULL;
99
100         return found;
101 }
102
103 config_t *lookup_config_next(avl_tree_t *config_tree, const config_t *cfg) {
104         avl_node_t *node;
105         config_t *found;
106
107         node = avl_search_node(config_tree, cfg);
108
109         if(node) {
110                 if(node->next) {
111                         found = node->next->data;
112
113                         if(!strcasecmp(found->variable, cfg->variable))
114                                 return found;
115                 }
116         }
117
118         return NULL;
119 }
120
121 bool get_config_bool(const config_t *cfg, bool *result) {
122         if(!cfg)
123                 return false;
124
125         if(!strcasecmp(cfg->value, "yes")) {
126                 *result = true;
127                 return true;
128         } else if(!strcasecmp(cfg->value, "no")) {
129                 *result = false;
130                 return true;
131         }
132
133         logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
134                    cfg->variable, cfg->file, cfg->line);
135
136         return false;
137 }
138
139 bool get_config_int(const config_t *cfg, int *result) {
140         if(!cfg)
141                 return false;
142
143         if(sscanf(cfg->value, "%d", result) == 1)
144                 return true;
145
146         logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
147                    cfg->variable, cfg->file, cfg->line);
148
149         return false;
150 }
151
152 bool get_config_string(const config_t *cfg, char **result) {
153         if(!cfg)
154                 return false;
155
156         *result = xstrdup(cfg->value);
157
158         return true;
159 }
160
161 bool get_config_address(const config_t *cfg, struct addrinfo **result) {
162         struct addrinfo *ai;
163
164         if(!cfg)
165                 return false;
166
167         ai = str2addrinfo(cfg->value, NULL, 0);
168
169         if(ai) {
170                 *result = ai;
171                 return true;
172         }
173
174         logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
175                    cfg->variable, cfg->file, cfg->line);
176
177         return false;
178 }
179
180 bool get_config_subnet(const config_t *cfg, subnet_t ** result) {
181         subnet_t subnet = {0};
182
183         if(!cfg)
184                 return false;
185
186         if(!str2net(&subnet, cfg->value)) {
187                 logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
188                            cfg->variable, cfg->file, cfg->line);
189                 return false;
190         }
191
192         /* Teach newbies what subnets are... */
193
194         if(((subnet.type == SUBNET_IPV4)
195                 && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
196                 || ((subnet.type == SUBNET_IPV6)
197                 && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
198                 logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
199                            cfg->variable, cfg->file, cfg->line);
200                 return false;
201         }
202
203         *(*result = new_subnet()) = subnet;
204
205         return true;
206 }
207
208 /*
209   Read exactly one line and strip the trailing newline if any.  If the
210   file was on EOF, return NULL. Otherwise, return all the data in a
211   dynamically allocated buffer.
212
213   If line is non-NULL, it will be used as an initial buffer, to avoid
214   unnecessary mallocing each time this function is called.  If buf is
215   given, and buf needs to be expanded, the var pointed to by buflen
216   will be increased.
217 */
218 static char *readline(FILE * fp, char **buf, size_t *buflen) {
219         char *newline = NULL;
220         char *p;
221         char *line;                                     /* The array that contains everything that has been read so far */
222         char *idx;                                      /* Read into this pointer, which points to an offset within line */
223         size_t size, newsize;           /* The size of the current array pointed to by line */
224         size_t maxlen;                          /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
225
226         if(feof(fp))
227                 return NULL;
228
229         if(buf && buflen) {
230                 size = *buflen;
231                 line = *buf;
232         } else {
233                 size = 100;
234                 line = xmalloc(size);
235         }
236
237         maxlen = size;
238         idx = line;
239         *idx = 0;
240
241         for(;;) {
242                 errno = 0;
243                 p = fgets(idx, maxlen, fp);
244
245                 if(!p) {                                /* EOF or error */
246                         if(feof(fp))
247                                 break;
248
249                         /* otherwise: error; let the calling function print an error message if applicable */
250                         free(line);
251                         return NULL;
252                 }
253
254                 newline = strchr(p, '\n');
255
256                 if(!newline) {                  /* We haven't yet read everything to the end of the line */
257                         newsize = size << 1;
258                         line = xrealloc(line, newsize);
259                         idx = &line[size - 1];
260                         maxlen = newsize - size + 1;
261                         size = newsize;
262                 } else {
263                         *newline = '\0';        /* kill newline */
264                         if(newline > p && newline[-1] == '\r')  /* and carriage return if necessary */
265                                 newline[-1] = '\0';
266                         break;                          /* yay */
267                 }
268         }
269
270         if(buf && buflen) {
271                 *buflen = size;
272                 *buf = line;
273         }
274
275         return line;
276 }
277
278 /*
279   Parse a configuration file and put the results in the configuration tree
280   starting at *base.
281 */
282 int read_config_file(avl_tree_t *config_tree, const char *fname) {
283         int err = -2;                           /* Parse error */
284         FILE *fp;
285         char *buffer, *line;
286         char *variable, *value, *eol;
287         int lineno = 0;
288         int len;
289         bool ignore = false;
290         config_t *cfg;
291         size_t bufsize;
292
293         fp = fopen(fname, "r");
294
295         if(!fp) {
296                 logger(LOG_ERR, "Cannot open config file %s: %s", fname,
297                            strerror(errno));
298                 return -3;
299         }
300
301         bufsize = 100;
302         buffer = xmalloc(bufsize);
303
304         for(;;) {
305                 if(feof(fp)) {
306                         err = 0;
307                         break;
308                 }
309
310                 line = readline(fp, &buffer, &bufsize);
311
312                 if(!line) {
313                         err = -1;
314                         break;
315                 }
316
317                 lineno++;
318
319                 if(!*line || *line == '#')
320                         continue;
321
322                 if(ignore) {
323                         if(!strncmp(line, "-----END", 8))
324                                 ignore = false;
325                         continue;
326                 }
327                 
328                 if(!strncmp(line, "-----BEGIN", 10)) {
329                         ignore = true;
330                         continue;
331                 }
332
333                 variable = value = line;
334
335                 eol = line + strlen(line);
336                 while(strchr("\t ", *--eol))
337                         *eol = '\0';
338
339                 len = strcspn(value, "\t =");
340                 value += len;
341                 value += strspn(value, "\t ");
342                 if(*value == '=') {
343                         value++;
344                         value += strspn(value, "\t ");
345                 }
346                 variable[len] = '\0';
347
348         
349                 if(!*value) {
350                         logger(LOG_ERR, "No value for variable `%s' on line %d while reading config file %s",
351                                    variable, lineno, fname);
352                         break;
353                 }
354
355                 cfg = new_config();
356                 cfg->variable = xstrdup(variable);
357                 cfg->value = xstrdup(value);
358                 cfg->file = xstrdup(fname);
359                 cfg->line = lineno;
360
361                 config_add(config_tree, cfg);
362         }
363
364         free(buffer);
365         fclose(fp);
366
367         return err;
368 }
369
370 bool read_server_config() {
371         char *fname;
372         int x;
373
374         xasprintf(&fname, "%s/tinc.conf", confbase);
375         x = read_config_file(config_tree, fname);
376
377         if(x == -1) {                           /* System error: complain */
378                 logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
379         }
380
381         free(fname);
382
383         return x == 0;
384 }
385
386 FILE *ask_and_open(const char *filename, const char *what) {
387         FILE *r;
388         char *directory;
389         char *fn;
390
391         /* Check stdin and stdout */
392         if(!isatty(0) || !isatty(1)) {
393                 /* Argh, they are running us from a script or something.  Write
394                    the files to the current directory and let them burn in hell
395                    for ever. */
396                 fn = xstrdup(filename);
397         } else {
398                 /* Ask for a file and/or directory name. */
399                 fprintf(stdout, "Please enter a file to save %s to [%s]: ",
400                                 what, filename);
401                 fflush(stdout);
402
403                 fn = readline(stdin, NULL, NULL);
404
405                 if(!fn) {
406                         fprintf(stderr, "Error while reading stdin: %s\n",
407                                         strerror(errno));
408                         return NULL;
409                 }
410
411                 if(!strlen(fn))
412                         /* User just pressed enter. */
413                         fn = xstrdup(filename);
414         }
415
416 #ifdef HAVE_MINGW
417         if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
418 #else
419         if(fn[0] != '/') {
420 #endif
421                 /* The directory is a relative path or a filename. */
422                 char *p;
423
424                 directory = get_current_dir_name();
425                 xasprintf(&p, "%s/%s", directory, fn);
426                 free(fn);
427                 free(directory);
428                 fn = p;
429         }
430
431         umask(0077);                            /* Disallow everything for group and other */
432
433         /* Open it first to keep the inode busy */
434
435         r = fopen(fn, "r+") ?: fopen(fn, "w+");
436
437         if(!r) {
438                 fprintf(stderr, "Error opening file `%s': %s\n",
439                                 fn, strerror(errno));
440                 free(fn);
441                 return NULL;
442         }
443
444         free(fn);
445
446         return r;
447 }
448
449 bool disable_old_keys(FILE *f) {
450         char buf[100];
451         long pos;
452         bool disabled = false;
453
454         rewind(f);
455         pos = ftell(f);
456
457         while(fgets(buf, sizeof buf, f)) {
458                 if(!strncmp(buf, "-----BEGIN RSA", 14)) {       
459                         buf[11] = 'O';
460                         buf[12] = 'L';
461                         buf[13] = 'D';
462                         fseek(f, pos, SEEK_SET);
463                         fputs(buf, f);
464                         disabled = true;
465                 }
466                 else if(!strncmp(buf, "-----END RSA", 12)) {    
467                         buf[ 9] = 'O';
468                         buf[10] = 'L';
469                         buf[11] = 'D';
470                         fseek(f, pos, SEEK_SET);
471                         fputs(buf, f);
472                         disabled = true;
473                 }
474                 pos = ftell(f);
475         }
476
477         return disabled;
478 }