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