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