b6d2b0af835a05dafdf62da11ebf1afcce3d2c39
[oweals/tinc.git] / src / conf.c
1 /*
2     conf.c -- configuration code
3     Copyright (C) 1998 Robert van der Meulen
4                   1998-2003 Ivo Timmermans <ivo@o2w.nl>
5                   2000-2003 Guus Sliepen <guus@sliepen.eu.org>
6                   2000 Cris van Pelt <tribbel@arise.dhs.org>
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: conf.c,v 1.9.4.65 2003/07/12 17:41:45 guus Exp $
23 */
24
25 #include "config.h"
26
27 #include <ctype.h>
28 #include <errno.h>
29 #include <netdb.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #include <string.h>
37
38 #include <xalloc.h>
39 #include <utils.h>                              /* for cp */
40 #include <avl_tree.h>
41
42 #include "conf.h"
43 #include "netutl.h"                             /* for str2address */
44 #include "logger.h"
45
46 #include "system.h"
47
48 avl_tree_t *config_tree;
49
50 int pingtimeout = 0;                    /* seconds before timeout */
51 char *confbase = NULL;                  /* directory in which all config files are */
52 char *netname = NULL;                   /* name of the vpn network */
53
54 static int config_compare(config_t *a, config_t *b)
55 {
56         int result;
57
58         result = strcasecmp(a->variable, b->variable);
59
60         if(result)
61                 return result;
62
63         result = a->line - b->line;
64
65         if(result)
66                 return result;
67         else
68                 return strcmp(a->file, b->file);
69 }
70
71 void init_configuration(avl_tree_t ** config_tree)
72 {
73         cp();
74
75         *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
76 }
77
78 void exit_configuration(avl_tree_t ** config_tree)
79 {
80         cp();
81
82         avl_delete_tree(*config_tree);
83         *config_tree = NULL;
84 }
85
86 config_t *new_config(void)
87 {
88         cp();
89
90         return (config_t *) xmalloc_and_zero(sizeof(config_t));
91 }
92
93 void free_config(config_t *cfg)
94 {
95         cp();
96
97         if(cfg->variable)
98                 free(cfg->variable);
99
100         if(cfg->value)
101                 free(cfg->value);
102
103         if(cfg->file)
104                 free(cfg->file);
105
106         free(cfg);
107 }
108
109 void config_add(avl_tree_t *config_tree, config_t *cfg)
110 {
111         cp();
112
113         avl_insert(config_tree, cfg);
114 }
115
116 config_t *lookup_config(avl_tree_t *config_tree, char *variable)
117 {
118         config_t cfg, *found;
119
120         cp();
121
122         cfg.variable = variable;
123         cfg.file = "";
124         cfg.line = 0;
125
126         found = avl_search_closest_greater(config_tree, &cfg);
127
128         if(!found)
129                 return NULL;
130
131         if(strcasecmp(found->variable, variable))
132                 return NULL;
133
134         return found;
135 }
136
137 config_t *lookup_config_next(avl_tree_t *config_tree, config_t *cfg)
138 {
139         avl_node_t *node;
140         config_t *found;
141
142         cp();
143
144         node = avl_search_node(config_tree, cfg);
145
146         if(node) {
147                 if(node->next) {
148                         found = (config_t *) node->next->data;
149
150                         if(!strcasecmp(found->variable, cfg->variable))
151                                 return found;
152                 }
153         }
154
155         return NULL;
156 }
157
158 int get_config_bool(config_t *cfg, int *result)
159 {
160         cp();
161
162         if(!cfg)
163                 return 0;
164
165         if(!strcasecmp(cfg->value, "yes")) {
166                 *result = 1;
167                 return 1;
168         } else if(!strcasecmp(cfg->value, "no")) {
169                 *result = 0;
170                 return 1;
171         }
172
173         logger(LOG_ERR, _("\"yes\" or \"no\" expected for configuration variable %s in %s line %d"),
174                    cfg->variable, cfg->file, cfg->line);
175
176         return 0;
177 }
178
179 int get_config_int(config_t *cfg, int *result)
180 {
181         cp();
182
183         if(!cfg)
184                 return 0;
185
186         if(sscanf(cfg->value, "%d", result) == 1)
187                 return 1;
188
189         logger(LOG_ERR, _("Integer expected for configuration variable %s in %s line %d"),
190                    cfg->variable, cfg->file, cfg->line);
191
192         return 0;
193 }
194
195 int get_config_string(config_t *cfg, char **result)
196 {
197         cp();
198
199         if(!cfg)
200                 return 0;
201
202         *result = xstrdup(cfg->value);
203
204         return 1;
205 }
206
207 int get_config_address(config_t *cfg, struct addrinfo **result)
208 {
209         struct addrinfo *ai;
210
211         cp();
212
213         if(!cfg)
214                 return 0;
215
216         ai = str2addrinfo(cfg->value, NULL, 0);
217
218         if(ai) {
219                 *result = ai;
220                 return 1;
221         }
222
223         logger(LOG_ERR, _("Hostname or IP address expected for configuration variable %s in %s line %d"),
224                    cfg->variable, cfg->file, cfg->line);
225
226         return 0;
227 }
228
229 int get_config_subnet(config_t *cfg, subnet_t ** result)
230 {
231         subnet_t *subnet;
232
233         cp();
234
235         if(!cfg)
236                 return 0;
237
238         subnet = str2net(cfg->value);
239
240         if(!subnet) {
241                 logger(LOG_ERR, _("Subnet expected for configuration variable %s in %s line %d"),
242                            cfg->variable, cfg->file, cfg->line);
243                 return 0;
244         }
245
246         /* Teach newbies what subnets are... */
247
248         if(((subnet->type == SUBNET_IPV4)
249                 && maskcheck(&subnet->net.ipv4.address, subnet->net.ipv4.prefixlength, sizeof(ipv4_t)))
250                 || ((subnet->type == SUBNET_IPV6)
251                 && maskcheck(&subnet->net.ipv6.address, subnet->net.ipv6.prefixlength, sizeof(ipv6_t)))) {
252                 logger(LOG_ERR, _ ("Network address and prefix length do not match for configuration variable %s in %s line %d"),
253                            cfg->variable, cfg->file, cfg->line);
254                 free(subnet);
255                 return 0;
256         }
257
258         *result = subnet;
259
260         return 1;
261 }
262
263 /*
264   Read exactly one line and strip the trailing newline if any.  If the
265   file was on EOF, return NULL. Otherwise, return all the data in a
266   dynamically allocated buffer.
267
268   If line is non-NULL, it will be used as an initial buffer, to avoid
269   unnecessary mallocing each time this function is called.  If buf is
270   given, and buf needs to be expanded, the var pointed to by buflen
271   will be increased.
272 */
273 static char *readline(FILE * fp, char **buf, size_t *buflen)
274 {
275         char *newline = NULL;
276         char *p;
277         char *line;                                     /* The array that contains everything that has been read so far */
278         char *idx;                                      /* Read into this pointer, which points to an offset within line */
279         size_t size, newsize;           /* The size of the current array pointed to by line */
280         size_t maxlen;                          /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
281
282         if(feof(fp))
283                 return NULL;
284
285         if(buf && buflen) {
286                 size = *buflen;
287                 line = *buf;
288         } else {
289                 size = 100;
290                 line = xmalloc(size);
291         }
292
293         maxlen = size;
294         idx = line;
295         *idx = 0;
296
297         for(;;) {
298                 errno = 0;
299                 p = fgets(idx, maxlen, fp);
300
301                 if(!p) {                                /* EOF or error */
302                         if(feof(fp))
303                                 break;
304
305                         /* otherwise: error; let the calling function print an error message if applicable */
306                         free(line);
307                         return NULL;
308                 }
309
310                 newline = strchr(p, '\n');
311
312                 if(!newline) {                  /* We haven't yet read everything to the end of the line */
313                         newsize = size << 1;
314                         line = xrealloc(line, newsize);
315                         idx = &line[size - 1];
316                         maxlen = newsize - size + 1;
317                         size = newsize;
318                 } else {
319                         *newline = '\0';        /* kill newline */
320                         break;                          /* yay */
321                 }
322         }
323
324         if(buf && buflen) {
325                 *buflen = size;
326                 *buf = line;
327         }
328
329         return line;
330 }
331
332 /*
333   Parse a configuration file and put the results in the configuration tree
334   starting at *base.
335 */
336 int read_config_file(avl_tree_t *config_tree, const char *fname)
337 {
338         int err = -2;                           /* Parse error */
339         FILE *fp;
340         char *buffer, *line;
341         char *variable, *value;
342         int lineno = 0, ignore = 0;
343         config_t *cfg;
344         size_t bufsize;
345
346         cp();
347
348         fp = fopen(fname, "r");
349
350         if(!fp) {
351                 logger(LOG_ERR, _("Cannot open config file %s: %s"), fname,
352                            strerror(errno));
353                 return -3;
354         }
355
356         bufsize = 100;
357         buffer = xmalloc(bufsize);
358
359         for(;;) {
360                 line = readline(fp, &buffer, &bufsize);
361
362                 if(!line) {
363                         err = -1;
364                         break;
365                 }
366
367                 if(feof(fp)) {
368                         err = 0;
369                         break;
370                 }
371
372                 lineno++;
373
374                 variable = strtok(line, "\t =");
375
376                 if(!variable)
377                         continue;                       /* no tokens on this line */
378
379                 if(variable[0] == '#')
380                         continue;                       /* comment: ignore */
381
382                 if(!strcmp(variable, "-----BEGIN"))
383                         ignore = 1;
384
385                 if(!ignore) {
386                         value = strtok(NULL, "\t\n\r =");
387
388                         if(!value || value[0] == '#') {
389                                 logger(LOG_ERR, _("No value for variable `%s' on line %d while reading config file %s"),
390                                            variable, lineno, fname);
391                                 break;
392                         }
393
394                         cfg = new_config();
395                         cfg->variable = xstrdup(variable);
396                         cfg->value = xstrdup(value);
397                         cfg->file = xstrdup(fname);
398                         cfg->line = lineno;
399
400                         config_add(config_tree, cfg);
401                 }
402
403                 if(!strcmp(variable, "-----END"))
404                         ignore = 0;
405         }
406
407         free(buffer);
408         fclose(fp);
409
410         return err;
411 }
412
413 int 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;
430 }
431
432 int is_safe_path(const char *file)
433 {
434         char *p;
435         const char *f;
436         char x;
437         struct stat s;
438         char l[MAXBUFSIZE];
439
440         if(*file != '/') {
441                 logger(LOG_ERR, _("`%s' is not an absolute path"), file);
442                 return 0;
443         }
444
445         p = strrchr(file, '/');
446
447         if(p == file)                           /* It's in the root */
448                 p++;
449
450         x = *p;
451         *p = '\0';
452
453         f = file;
454
455 check1:
456         if(lstat(f, &s) < 0) {
457                 logger(LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
458                 return 0;
459         }
460
461         if(s.st_uid != geteuid()) {
462                 logger(LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
463                            f, s.st_uid, geteuid());
464                 return 0;
465         }
466
467         if(S_ISLNK(s.st_mode)) {
468                 logger(LOG_WARNING, _("Warning: `%s' is a symlink"), f);
469
470                 if(readlink(f, l, MAXBUFSIZE) < 0) {
471                         logger(LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
472                                    strerror(errno));
473                         return 0;
474                 }
475
476                 f = l;
477                 goto check1;
478         }
479
480         *p = x;
481         f = file;
482
483 check2:
484         if(lstat(f, &s) < 0 && errno != ENOENT) {
485                 logger(LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
486                 return 0;
487         }
488
489         if(errno == ENOENT)
490                 return 1;
491
492         if(s.st_uid != geteuid()) {
493                 logger(LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
494                            f, s.st_uid, geteuid());
495                 return 0;
496         }
497
498         if(S_ISLNK(s.st_mode)) {
499                 logger(LOG_WARNING, _("Warning: `%s' is a symlink"), f);
500
501                 if(readlink(f, l, MAXBUFSIZE) < 0) {
502                         logger(LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
503                                    strerror(errno));
504                         return 0;
505                 }
506
507                 f = l;
508                 goto check2;
509         }
510
511         if(s.st_mode & 0007) {
512                 /* Accessible by others */
513                 logger(LOG_ERR, _("`%s' has unsecure permissions"), f);
514                 return 0;
515         }
516
517         return 1;
518 }
519
520 FILE *ask_and_safe_open(const char *filename, const char *what,
521                                                 const char *mode)
522 {
523         FILE *r;
524         char *directory;
525         char *fn;
526
527         /* Check stdin and stdout */
528         if(!isatty(0) || !isatty(1)) {
529                 /* Argh, they are running us from a script or something.  Write
530                    the files to the current directory and let them burn in hell
531                    for ever. */
532                 fn = xstrdup(filename);
533         } else {
534                 /* Ask for a file and/or directory name. */
535                 fprintf(stdout, _("Please enter a file to save %s to [%s]: "),
536                                 what, filename);
537                 fflush(stdout);
538
539                 fn = readline(stdin, NULL, NULL);
540
541                 if(!fn) {
542                         fprintf(stderr, _("Error while reading stdin: %s\n"),
543                                         strerror(errno));
544                         return NULL;
545                 }
546
547                 if(!strlen(fn))
548                         /* User just pressed enter. */
549                         fn = xstrdup(filename);
550         }
551
552         if(!strchr(fn, '/') || fn[0] != '/') {
553                 /* The directory is a relative path or a filename. */
554                 char *p;
555
556                 directory = get_current_dir_name();
557                 asprintf(&p, "%s/%s", directory, fn);
558                 free(fn);
559                 free(directory);
560                 fn = p;
561         }
562
563         umask(0077);                            /* Disallow everything for group and other */
564
565         /* Open it first to keep the inode busy */
566
567         r = fopen(fn, mode);
568
569         if(!r) {
570                 fprintf(stderr, _("Error opening file `%s': %s\n"),
571                                 fn, strerror(errno));
572                 free(fn);
573                 return NULL;
574         }
575
576         /* Then check the file for nasty attacks */
577         if(!is_safe_path(fn)) {         /* Do not permit any directories that are readable or writeable by other users. */
578                 fprintf(stderr, _("The file `%s' (or any of the leading directories) has unsafe permissions.\n"
579                                  "I will not create or overwrite this file.\n"), fn);
580                 fclose(r);
581                 free(fn);
582                 return NULL;
583         }
584
585         free(fn);
586
587         return r;
588 }