New '-o' option to configure server or hosts from command line
[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 "protocol.h"
30 #include "utils.h"                              /* for cp */
31 #include "xalloc.h"
32
33 avl_tree_t *config_tree;
34
35 int pinginterval = 0;                   /* seconds between pings */
36 int pingtimeout = 0;                    /* seconds to wait for response */
37 char *confbase = NULL;                  /* directory in which all config files are */
38 char *netname = NULL;                   /* name of the vpn network */
39 list_t *cmdline_conf = NULL;    /* global/host configuration values given at the command line */
40
41
42 static int config_compare(const config_t *a, const config_t *b) {
43         int result;
44
45         result = strcasecmp(a->variable, b->variable);
46
47         if(result)
48                 return result;
49
50         /* give priority to command line options */
51         result = !b->file - !a->file;
52         if (result)
53                 return result;
54
55         result = a->line - b->line;
56
57         if(result)
58                 return result;
59         else
60                 return a->file ? strcmp(a->file, b->file) : 0;
61 }
62
63 void init_configuration(avl_tree_t ** config_tree) {
64         *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
65 }
66
67 void exit_configuration(avl_tree_t ** config_tree) {
68         avl_delete_tree(*config_tree);
69         *config_tree = NULL;
70 }
71
72 config_t *new_config(void) {
73         return xmalloc_and_zero(sizeof(config_t));
74 }
75
76 void free_config(config_t *cfg) {
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         avl_insert(config_tree, cfg);
91 }
92
93 config_t *lookup_config(avl_tree_t *config_tree, char *variable) {
94         config_t cfg, *found;
95
96         cfg.variable = variable;
97         cfg.file = NULL;
98         cfg.line = 0;
99
100         found = avl_search_closest_greater(config_tree, &cfg);
101
102         if(!found)
103                 return NULL;
104
105         if(strcasecmp(found->variable, variable))
106                 return NULL;
107
108         return found;
109 }
110
111 config_t *lookup_config_next(avl_tree_t *config_tree, const config_t *cfg) {
112         avl_node_t *node;
113         config_t *found;
114
115         node = avl_search_node(config_tree, cfg);
116
117         if(node) {
118                 if(node->next) {
119                         found = node->next->data;
120
121                         if(!strcasecmp(found->variable, cfg->variable))
122                                 return found;
123                 }
124         }
125
126         return NULL;
127 }
128
129 bool get_config_bool(const config_t *cfg, bool *result) {
130         if(!cfg)
131                 return false;
132
133         if(!strcasecmp(cfg->value, "yes")) {
134                 *result = true;
135                 return true;
136         } else if(!strcasecmp(cfg->value, "no")) {
137                 *result = false;
138                 return true;
139         }
140
141         logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
142                    cfg->variable, cfg->file, cfg->line);
143
144         return false;
145 }
146
147 bool get_config_int(const config_t *cfg, int *result) {
148         if(!cfg)
149                 return false;
150
151         if(sscanf(cfg->value, "%d", result) == 1)
152                 return true;
153
154         logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
155                    cfg->variable, cfg->file, cfg->line);
156
157         return false;
158 }
159
160 bool get_config_string(const config_t *cfg, char **result) {
161         if(!cfg)
162                 return false;
163
164         *result = xstrdup(cfg->value);
165
166         return true;
167 }
168
169 bool get_config_address(const config_t *cfg, struct addrinfo **result) {
170         struct addrinfo *ai;
171
172         if(!cfg)
173                 return false;
174
175         ai = str2addrinfo(cfg->value, NULL, 0);
176
177         if(ai) {
178                 *result = ai;
179                 return true;
180         }
181
182         logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
183                    cfg->variable, cfg->file, cfg->line);
184
185         return false;
186 }
187
188 bool get_config_subnet(const config_t *cfg, subnet_t ** result) {
189         subnet_t subnet = {0};
190
191         if(!cfg)
192                 return false;
193
194         if(!str2net(&subnet, cfg->value)) {
195                 logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
196                            cfg->variable, cfg->file, cfg->line);
197                 return false;
198         }
199
200         /* Teach newbies what subnets are... */
201
202         if(((subnet.type == SUBNET_IPV4)
203                 && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
204                 || ((subnet.type == SUBNET_IPV6)
205                 && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
206                 logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
207                            cfg->variable, cfg->file, cfg->line);
208                 return false;
209         }
210
211         *(*result = new_subnet()) = subnet;
212
213         return true;
214 }
215
216 /*
217   Read exactly one line and strip the trailing newline if any.
218 */
219 static char *readline(FILE * fp, char *buf, size_t buflen) {
220         char *newline = NULL;
221         char *p;
222
223         if(feof(fp))
224                 return NULL;
225
226         p = fgets(buf, buflen, fp);
227
228         if(!p)
229                 return NULL;
230
231         newline = strchr(p, '\n');
232
233         if(!newline)
234                 return buf;
235
236         *newline = '\0';        /* kill newline */
237         if(newline > p && newline[-1] == '\r')  /* and carriage return if necessary */
238                 newline[-1] = '\0';
239
240         return buf;
241 }
242
243 config_t *parse_config_line(char *line, const char *fname, int lineno) {
244         config_t *cfg;
245         int len;
246         char *variable, *value, *eol;
247         variable = value = line;
248
249         eol = line + strlen(line);
250         while(strchr("\t ", *--eol))
251                 *eol = '\0';
252
253         len = strcspn(value, "\t =");
254         value += len;
255         value += strspn(value, "\t ");
256         if(*value == '=') {
257                 value++;
258                 value += strspn(value, "\t ");
259         }
260         variable[len] = '\0';
261
262         if(!*value) {
263                 const char err[] = "No value for variable";
264                 if (fname)
265                         logger(LOG_ERR, "%s `%s' on line %d while reading config file %s",
266                                 err, variable, lineno, fname);
267                 else
268                         logger(LOG_ERR, "%s `%s' in command line option %d",
269                                 err, variable, lineno);
270                 return NULL;
271         }
272
273         cfg = new_config();
274         cfg->variable = xstrdup(variable);
275         cfg->value = xstrdup(value);
276         cfg->file = fname ? xstrdup(fname) : NULL;
277         cfg->line = lineno;
278
279         return cfg;
280 }
281
282 /*
283   Parse a configuration file and put the results in the configuration tree
284   starting at *base.
285 */
286 bool read_config_file(avl_tree_t *config_tree, const char *fname) {
287         FILE *fp;
288         char buffer[MAX_STRING_SIZE];
289         char *line;
290         int lineno = 0;
291         bool ignore = false;
292         config_t *cfg;
293         bool result = false;
294
295         fp = fopen(fname, "r");
296
297         if(!fp) {
298                 logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
299                 return false;
300         }
301
302         for(;;) {
303                 line = readline(fp, buffer, sizeof buffer);
304
305                 if(!line) {
306                         if(feof(fp))
307                                 result = true;
308                         break;
309                 }
310
311                 lineno++;
312
313                 if(!*line || *line == '#')
314                         continue;
315
316                 if(ignore) {
317                         if(!strncmp(line, "-----END", 8))
318                                 ignore = false;
319                         continue;
320                 }
321                 
322                 if(!strncmp(line, "-----BEGIN", 10)) {
323                         ignore = true;
324                         continue;
325                 }
326
327                 cfg = parse_config_line(line, fname, lineno);
328                 if (!cfg)
329                         break;
330                 config_add(config_tree, cfg);
331         }
332
333         fclose(fp);
334
335         return result;
336 }
337
338 bool read_server_config() {
339         list_node_t *node, *next;
340         char *fname;
341         bool x;
342
343         for(node = cmdline_conf->tail; node; node = next) {
344                 config_t *cfg = (config_t *)node->data;
345                 next = node->prev;
346                 if (!strchr(cfg->variable, '.')) {
347                         config_add(config_tree, cfg);
348                         node->data = NULL;
349                         list_unlink_node(cmdline_conf, node);
350                 }
351         }
352
353         xasprintf(&fname, "%s/tinc.conf", confbase);
354         x = read_config_file(config_tree, fname);
355
356         if(!x) {                                /* System error: complain */
357                 logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
358         }
359
360         free(fname);
361
362         return x;
363 }
364
365 FILE *ask_and_open(const char *filename, const char *what) {
366         FILE *r;
367         char *directory;
368         char line[PATH_MAX];
369         const char *fn;
370
371         /* Check stdin and stdout */
372         if(!isatty(0) || !isatty(1)) {
373                 /* Argh, they are running us from a script or something.  Write
374                    the files to the current directory and let them burn in hell
375                    for ever. */
376                 fn = filename;
377         } else {
378                 /* Ask for a file and/or directory name. */
379                 fprintf(stdout, "Please enter a file to save %s to [%s]: ",
380                                 what, filename);
381                 fflush(stdout);
382
383                 fn = readline(stdin, line, sizeof line);
384
385                 if(!fn) {
386                         fprintf(stderr, "Error while reading stdin: %s\n",
387                                         strerror(errno));
388                         return NULL;
389                 }
390
391                 if(!strlen(fn))
392                         /* User just pressed enter. */
393                         fn = filename;
394         }
395
396 #ifdef HAVE_MINGW
397         if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
398 #else
399         if(fn[0] != '/') {
400 #endif
401                 /* The directory is a relative path or a filename. */
402                 char *p;
403
404                 directory = get_current_dir_name();
405                 xasprintf(&p, "%s/%s", directory, fn);
406                 free(directory);
407                 fn = p;
408         }
409
410         umask(0077);                            /* Disallow everything for group and other */
411
412         /* Open it first to keep the inode busy */
413
414         r = fopen(fn, "r+") ?: fopen(fn, "w+");
415
416         if(!r) {
417                 fprintf(stderr, "Error opening file `%s': %s\n",
418                                 fn, strerror(errno));
419                 return NULL;
420         }
421
422         return r;
423 }
424
425 bool disable_old_keys(FILE *f) {
426         char buf[100];
427         long pos;
428         bool disabled = false;
429
430         rewind(f);
431         pos = ftell(f);
432
433         while(fgets(buf, sizeof buf, f)) {
434                 if(!strncmp(buf, "-----BEGIN RSA", 14)) {       
435                         buf[11] = 'O';
436                         buf[12] = 'L';
437                         buf[13] = 'D';
438                         fseek(f, pos, SEEK_SET);
439                         fputs(buf, f);
440                         disabled = true;
441                 }
442                 else if(!strncmp(buf, "-----END RSA", 12)) {    
443                         buf[ 9] = 'O';
444                         buf[10] = 'L';
445                         buf[11] = 'D';
446                         fseek(f, pos, SEEK_SET);
447                         fputs(buf, f);
448                         disabled = true;
449                 }
450                 pos = ftell(f);
451         }
452
453         return disabled;
454 }