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