Define logger(), cleans up source code and allows us to write log entries
[tinc] / src / conf.c
1 /*
2     conf.c -- configuration code
3     Copyright (C) 1998 Robert van der Meulen
4                   1998-2002 Ivo Timmermans <ivo@o2w.nl>
5                   2000-2002 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.62 2003/07/06 22:11:31 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 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, LOG_ERR, _("Failed to read `%s': %s"), fname, strerror(errno));
425         }
426
427         free(fname);
428
429         return x;
430 }
431
432 int isadir(const char *f)
433 {
434         struct stat s;
435
436         if(stat(f, &s) < 0)
437                 return 0;
438         else
439                 return S_ISDIR(s.st_mode);
440 }
441
442 int is_safe_path(const char *file)
443 {
444         char *p;
445         const char *f;
446         char x;
447         struct stat s;
448         char l[MAXBUFSIZE];
449
450         if(*file != '/') {
451                 logger(DEBUG_ALWAYS, LOG_ERR, _("`%s' is not an absolute path"), file);
452                 return 0;
453         }
454
455         p = strrchr(file, '/');
456
457         if(p == file)                           /* It's in the root */
458                 p++;
459
460         x = *p;
461         *p = '\0';
462
463         f = file;
464
465 check1:
466         if(lstat(f, &s) < 0) {
467                 logger(DEBUG_ALWAYS, LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
468                 return 0;
469         }
470
471         if(s.st_uid != geteuid()) {
472                 logger(DEBUG_ALWAYS, LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
473                            f, s.st_uid, geteuid());
474                 return 0;
475         }
476
477         if(S_ISLNK(s.st_mode)) {
478                 logger(DEBUG_ALWAYS, LOG_WARNING, _("Warning: `%s' is a symlink"), f);
479
480                 if(readlink(f, l, MAXBUFSIZE) < 0) {
481                         logger(DEBUG_ALWAYS, LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
482                                    strerror(errno));
483                         return 0;
484                 }
485
486                 f = l;
487                 goto check1;
488         }
489
490         *p = x;
491         f = file;
492
493 check2:
494         if(lstat(f, &s) < 0 && errno != ENOENT) {
495                 logger(DEBUG_ALWAYS, LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
496                 return 0;
497         }
498
499         if(errno == ENOENT)
500                 return 1;
501
502         if(s.st_uid != geteuid()) {
503                 logger(DEBUG_ALWAYS, LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
504                            f, s.st_uid, geteuid());
505                 return 0;
506         }
507
508         if(S_ISLNK(s.st_mode)) {
509                 logger(DEBUG_ALWAYS, LOG_WARNING, _("Warning: `%s' is a symlink"), f);
510
511                 if(readlink(f, l, MAXBUFSIZE) < 0) {
512                         logger(DEBUG_ALWAYS, LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
513                                    strerror(errno));
514                         return 0;
515                 }
516
517                 f = l;
518                 goto check2;
519         }
520
521         if(s.st_mode & 0007) {
522                 /* Accessible by others */
523                 logger(DEBUG_ALWAYS, LOG_ERR, _("`%s' has unsecure permissions"), f);
524                 return 0;
525         }
526
527         return 1;
528 }
529
530 FILE *ask_and_safe_open(const char *filename, const char *what,
531                                                 const char *mode)
532 {
533         FILE *r;
534         char *directory;
535         char *fn;
536
537         /* Check stdin and stdout */
538         if(!isatty(0) || !isatty(1)) {
539                 /* Argh, they are running us from a script or something.  Write
540                    the files to the current directory and let them burn in hell
541                    for ever. */
542                 fn = xstrdup(filename);
543         } else {
544                 /* Ask for a file and/or directory name. */
545                 fprintf(stdout, _("Please enter a file to save %s to [%s]: "),
546                                 what, filename);
547                 fflush(stdout);
548
549                 fn = readline(stdin, NULL, NULL);
550
551                 if(!fn) {
552                         fprintf(stderr, _("Error while reading stdin: %s\n"),
553                                         strerror(errno));
554                         return NULL;
555                 }
556
557                 if(!strlen(fn))
558                         /* User just pressed enter. */
559                         fn = xstrdup(filename);
560         }
561
562         if(!strchr(fn, '/') || fn[0] != '/') {
563                 /* The directory is a relative path or a filename. */
564                 char *p;
565
566                 directory = get_current_dir_name();
567                 asprintf(&p, "%s/%s", directory, fn);
568                 free(fn);
569                 free(directory);
570                 fn = p;
571         }
572
573         umask(0077);                            /* Disallow everything for group and other */
574
575         /* Open it first to keep the inode busy */
576
577         r = fopen(fn, mode);
578
579         if(!r) {
580                 fprintf(stderr, _("Error opening file `%s': %s\n"),
581                                 fn, strerror(errno));
582                 free(fn);
583                 return NULL;
584         }
585
586         /* Then check the file for nasty attacks */
587         if(!is_safe_path(fn)) {         /* Do not permit any directories that are readable or writeable by other users. */
588                 fprintf(stderr, _("The file `%s' (or any of the leading directories) has unsafe permissions.\n"
589                                  "I will not create or overwrite this file.\n"), fn);
590                 fclose(r);
591                 free(fn);
592                 return NULL;
593         }
594
595         free(fn);
596
597         return r;
598 }