]> git.meshlink.io Git - meshlink/blob - src/conf.c
c95664f7c286467ff5eba46c4c66b81b9b8e66f5
[meshlink] / 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.73 2003/08/08 12:24:52 guus Exp $
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 pingtimeout = 0;                    /* seconds before timeout */
37 char *confbase = NULL;                  /* directory in which all config files are */
38 char *netname = NULL;                   /* name of the vpn network */
39
40 static int config_compare(const config_t *a, const config_t *b)
41 {
42         int result;
43
44         result = strcasecmp(a->variable, b->variable);
45
46         if(result)
47                 return result;
48
49         result = a->line - b->line;
50
51         if(result)
52                 return result;
53         else
54                 return strcmp(a->file, b->file);
55 }
56
57 void init_configuration(avl_tree_t ** config_tree)
58 {
59         cp();
60
61         *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
62 }
63
64 void exit_configuration(avl_tree_t ** config_tree)
65 {
66         cp();
67
68         avl_delete_tree(*config_tree);
69         *config_tree = NULL;
70 }
71
72 config_t *new_config(void)
73 {
74         cp();
75
76         return (config_t *) xmalloc_and_zero(sizeof(config_t));
77 }
78
79 void free_config(config_t *cfg)
80 {
81         cp();
82
83         if(cfg->variable)
84                 free(cfg->variable);
85
86         if(cfg->value)
87                 free(cfg->value);
88
89         if(cfg->file)
90                 free(cfg->file);
91
92         free(cfg);
93 }
94
95 void config_add(avl_tree_t *config_tree, config_t *cfg)
96 {
97         cp();
98
99         avl_insert(config_tree, cfg);
100 }
101
102 config_t *lookup_config(const avl_tree_t *config_tree, char *variable)
103 {
104         config_t cfg, *found;
105
106         cp();
107
108         cfg.variable = variable;
109         cfg.file = "";
110         cfg.line = 0;
111
112         found = avl_search_closest_greater(config_tree, &cfg);
113
114         if(!found)
115                 return NULL;
116
117         if(strcasecmp(found->variable, variable))
118                 return NULL;
119
120         return found;
121 }
122
123 config_t *lookup_config_next(const avl_tree_t *config_tree, const config_t *cfg)
124 {
125         avl_node_t *node;
126         config_t *found;
127
128         cp();
129
130         node = avl_search_node(config_tree, cfg);
131
132         if(node) {
133                 if(node->next) {
134                         found = (config_t *) node->next->data;
135
136                         if(!strcasecmp(found->variable, cfg->variable))
137                                 return found;
138                 }
139         }
140
141         return NULL;
142 }
143
144 bool get_config_bool(const config_t *cfg, bool *result)
145 {
146         cp();
147
148         if(!cfg)
149                 return false;
150
151         if(!strcasecmp(cfg->value, "yes")) {
152                 *result = true;
153                 return true;
154         } else if(!strcasecmp(cfg->value, "no")) {
155                 *result = false;
156                 return true;
157         }
158
159         logger(LOG_ERR, _("\"yes\" or \"no\" expected for configuration variable %s in %s line %d"),
160                    cfg->variable, cfg->file, cfg->line);
161
162         return false;
163 }
164
165 bool get_config_int(const config_t *cfg, int *result)
166 {
167         cp();
168
169         if(!cfg)
170                 return false;
171
172         if(sscanf(cfg->value, "%d", result) == 1)
173                 return true;
174
175         logger(LOG_ERR, _("Integer expected for configuration variable %s in %s line %d"),
176                    cfg->variable, cfg->file, cfg->line);
177
178         return false;
179 }
180
181 bool get_config_string(const config_t *cfg, char **result)
182 {
183         cp();
184
185         if(!cfg)
186                 return false;
187
188         *result = xstrdup(cfg->value);
189
190         return true;
191 }
192
193 bool get_config_address(const config_t *cfg, struct addrinfo **result)
194 {
195         struct addrinfo *ai;
196
197         cp();
198
199         if(!cfg)
200                 return false;
201
202         ai = str2addrinfo(cfg->value, NULL, 0);
203
204         if(ai) {
205                 *result = ai;
206                 return true;
207         }
208
209         logger(LOG_ERR, _("Hostname or IP address expected for configuration variable %s in %s line %d"),
210                    cfg->variable, cfg->file, cfg->line);
211
212         return false;
213 }
214
215 bool get_config_subnet(const config_t *cfg, subnet_t ** result)
216 {
217         subnet_t *subnet;
218
219         cp();
220
221         if(!cfg)
222                 return false;
223
224         subnet = str2net(cfg->value);
225
226         if(!subnet) {
227                 logger(LOG_ERR, _("Subnet expected for configuration variable %s in %s line %d"),
228                            cfg->variable, cfg->file, cfg->line);
229                 return false;
230         }
231
232         /* Teach newbies what subnets are... */
233
234         if(((subnet->type == SUBNET_IPV4)
235                 && !maskcheck(&subnet->net.ipv4.address, subnet->net.ipv4.prefixlength, sizeof(ipv4_t)))
236                 || ((subnet->type == SUBNET_IPV6)
237                 && !maskcheck(&subnet->net.ipv6.address, subnet->net.ipv6.prefixlength, sizeof(ipv6_t)))) {
238                 logger(LOG_ERR, _ ("Network address and prefix length do not match for configuration variable %s in %s line %d"),
239                            cfg->variable, cfg->file, cfg->line);
240                 free(subnet);
241                 return false;
242         }
243
244         *result = subnet;
245
246         return true;
247 }
248
249 /*
250   Read exactly one line and strip the trailing newline if any.  If the
251   file was on EOF, return NULL. Otherwise, return all the data in a
252   dynamically allocated buffer.
253
254   If line is non-NULL, it will be used as an initial buffer, to avoid
255   unnecessary mallocing each time this function is called.  If buf is
256   given, and buf needs to be expanded, the var pointed to by buflen
257   will be increased.
258 */
259 static char *readline(FILE * fp, char **buf, size_t *buflen)
260 {
261         char *newline = NULL;
262         char *p;
263         char *line;                                     /* The array that contains everything that has been read so far */
264         char *idx;                                      /* Read into this pointer, which points to an offset within line */
265         size_t size, newsize;           /* The size of the current array pointed to by line */
266         size_t maxlen;                          /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
267
268         if(feof(fp))
269                 return NULL;
270
271         if(buf && buflen) {
272                 size = *buflen;
273                 line = *buf;
274         } else {
275                 size = 100;
276                 line = xmalloc(size);
277         }
278
279         maxlen = size;
280         idx = line;
281         *idx = 0;
282
283         for(;;) {
284                 errno = 0;
285                 p = fgets(idx, maxlen, fp);
286
287                 if(!p) {                                /* EOF or error */
288                         if(feof(fp))
289                                 break;
290
291                         /* otherwise: error; let the calling function print an error message if applicable */
292                         free(line);
293                         return NULL;
294                 }
295
296                 newline = strchr(p, '\n');
297
298                 if(!newline) {                  /* We haven't yet read everything to the end of the line */
299                         newsize = size << 1;
300                         line = xrealloc(line, newsize);
301                         idx = &line[size - 1];
302                         maxlen = newsize - size + 1;
303                         size = newsize;
304                 } else {
305                         *newline = '\0';        /* kill newline */
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;
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                 line = readline(fp, &buffer, &bufsize);
349
350                 if(!line) {
351                         err = -1;
352                         break;
353                 }
354
355                 if(feof(fp)) {
356                         err = 0;
357                         break;
358                 }
359
360                 lineno++;
361
362                 if(*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                 len = strcspn(value, "\t =");
379                 value += len;
380                 value += strspn(value, "\t ");
381                 if(*value == '=') {
382                         value++;
383                         value += strspn(value, "\t ");
384                 }
385                 variable[len] = '\0';
386
387                 if(!*value) {
388                         logger(LOG_ERR, _("No value for variable `%s' on line %d while reading config file %s"),
389                                    variable, lineno, fname);
390                         break;
391                 }
392
393                 cfg = new_config();
394                 cfg->variable = xstrdup(variable);
395                 cfg->value = xstrdup(value);
396                 cfg->file = xstrdup(fname);
397                 cfg->line = lineno;
398
399                 config_add(config_tree, cfg);
400         }
401
402         free(buffer);
403         fclose(fp);
404
405         return err;
406 }
407
408 bool read_server_config()
409 {
410         char *fname;
411         int x;
412
413         cp();
414
415         asprintf(&fname, "%s/tinc.conf", confbase);
416         x = read_config_file(config_tree, fname);
417
418         if(x == -1) {                           /* System error: complain */
419                 logger(LOG_ERR, _("Failed to read `%s': %s"), fname, strerror(errno));
420         }
421
422         free(fname);
423
424         return x == 0;
425 }
426
427 bool is_safe_path(const char *file)
428 {
429 #if !(defined(HAVE_CYGWIN) || defined(HAVE_MINGW))
430         char *p;
431         const char *f;
432         char x;
433         struct stat s;
434         char l[MAXBUFSIZE];
435
436         if(*file != '/') {
437                 logger(LOG_ERR, _("`%s' is not an absolute path"), file);
438                 return false;
439         }
440
441         p = strrchr(file, '/');
442
443         if(p == file)                           /* It's in the root */
444                 p++;
445
446         x = *p;
447         *p = '\0';
448
449         f = file;
450
451 check1:
452         if(lstat(f, &s) < 0) {
453                 logger(LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
454                 return false;
455         }
456
457         if(s.st_uid != geteuid()) {
458                 logger(LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
459                            f, s.st_uid, geteuid());
460                 return false;
461         }
462
463         if(S_ISLNK(s.st_mode)) {
464                 logger(LOG_WARNING, _("Warning: `%s' is a symlink"), f);
465
466                 if(readlink(f, l, MAXBUFSIZE) < 0) {
467                         logger(LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
468                                    strerror(errno));
469                         return false;
470                 }
471
472                 f = l;
473                 goto check1;
474         }
475
476         *p = x;
477         f = file;
478
479 check2:
480         if(lstat(f, &s) < 0 && errno != ENOENT) {
481                 logger(LOG_ERR, _("Couldn't stat `%s': %s"), f, strerror(errno));
482                 return false;
483         }
484
485         if(errno == ENOENT)
486                 return true;
487
488         if(s.st_uid != geteuid()) {
489                 logger(LOG_ERR, _("`%s' is owned by UID %d instead of %d"),
490                            f, s.st_uid, geteuid());
491                 return false;
492         }
493
494         if(S_ISLNK(s.st_mode)) {
495                 logger(LOG_WARNING, _("Warning: `%s' is a symlink"), f);
496
497                 if(readlink(f, l, MAXBUFSIZE) < 0) {
498                         logger(LOG_ERR, _("Unable to read symbolic link `%s': %s"), f,
499                                    strerror(errno));
500                         return false;
501                 }
502
503                 f = l;
504                 goto check2;
505         }
506
507         if(s.st_mode & 0007) {
508                 /* Accessible by others */
509                 logger(LOG_ERR, _("`%s' has unsecure permissions"), f);
510                 return false;
511         }
512 #endif
513
514         return true;
515 }
516
517 FILE *ask_and_safe_open(const char *filename, const char *what, bool safe, const char *mode)
518 {
519         FILE *r;
520         char *directory;
521         char *fn;
522
523         /* Check stdin and stdout */
524         if(!isatty(0) || !isatty(1)) {
525                 /* Argh, they are running us from a script or something.  Write
526                    the files to the current directory and let them burn in hell
527                    for ever. */
528                 fn = xstrdup(filename);
529         } else {
530                 /* Ask for a file and/or directory name. */
531                 fprintf(stdout, _("Please enter a file to save %s to [%s]: "),
532                                 what, filename);
533                 fflush(stdout);
534
535                 fn = readline(stdin, NULL, NULL);
536
537                 if(!fn) {
538                         fprintf(stderr, _("Error while reading stdin: %s\n"),
539                                         strerror(errno));
540                         return NULL;
541                 }
542
543                 if(!strlen(fn))
544                         /* User just pressed enter. */
545                         fn = xstrdup(filename);
546         }
547
548 #ifdef HAVE_MINGW
549         if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
550 #else
551         if(fn[0] != '/') {
552 #endif
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(safe) {
578                 if(!is_safe_path(fn)) {         /* Do not permit any directories that are readable or writeable by other users. */
579                         fprintf(stderr, _("The file `%s' (or any of the leading directories) has unsafe permissions.\n"
580                                          "I will not create or overwrite this file.\n"), fn);
581                         fclose(r);
582                         free(fn);
583                         return NULL;
584                 }
585         }
586
587         free(fn);
588
589         return r;
590 }