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