e94a5fe82c5ee6ae50eccac9bee7c5524f5a6580
[libradsec.git] / gconfig.c
1 /*
2  * Copyright (C) 2007, 2008 Stig Venaas <venaas@uninett.no>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  */
8
9 #include <string.h>
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <limits.h>
14 #include <glob.h>
15 #include <sys/types.h>
16 #include <libgen.h>
17 #include <errno.h>
18 #include "debug.h"
19 #include "util.h"
20 #include "gconfig.h"
21
22 /* returns NULL on error, where to continue parsing if token and ok. E.g. "" will return token with empty string */
23 char *strtokenquote(char *s, char **token, char *del, char *quote, char *comment) {
24     char *t = s, *q, *r;
25
26     if (!t || !token || !del)
27         return NULL;
28     while (*t && strchr(del, *t))
29         t++;
30     if (!*t || (comment && strchr(comment, *t))) {
31         *token = NULL;
32         return t + 1; /* needs to be non-NULL, but value doesn't matter */
33     }
34     if (quote && (q = strchr(quote, *t))) {
35         t++;
36         r = t;
37         while (*t && *t != *q)
38             t++;
39         if (!*t || (t[1] && !strchr(del, t[1])))
40             return NULL;
41         *t = '\0';
42         *token = r;
43         return t + 1;
44     }
45     *token = t;
46     t++;
47     while (*t && !strchr(del, *t))
48         t++;
49     *t = '\0';
50     return t + 1;
51 }
52
53 int pushgconfdata(struct gconffile **cf, const char *data) {
54     int i;
55     struct gconffile *newcf;
56
57     if (!*cf) {
58         newcf = malloc(sizeof(struct gconffile) * 2);
59         if (!newcf)
60             return 0;
61         memset(newcf, 0, sizeof(struct gconffile) * 2);
62     } else {
63         for (i = 0; (*cf)[i].data || (*cf)[i].path; i++);
64         newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
65         if (!newcf)
66             return 0;
67         memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
68         memset(newcf, 0, sizeof(struct gconffile));
69     }
70     newcf[0].data = data;
71     *cf = newcf;
72     return 1;
73 }
74
75 FILE *pushgconffile(struct gconffile **cf, FILE *file, const char *description) {
76     int i;
77     struct gconffile *newcf;
78     char *desc;
79
80     if (!file) {
81         debug(DBG_INFO, "could not read config from %s", description);
82         return NULL;
83     }
84     debug(DBG_DBG, "reading config from %s", description);
85
86     desc = stringcopy(description, 0);
87     if (!desc)
88         goto errmalloc;
89     
90     if (!*cf) {
91         newcf = malloc(sizeof(struct gconffile) * 2);
92         if (!newcf)
93             goto errmalloc;
94         memset(newcf, 0, sizeof(struct gconffile) * 2);
95     } else {
96         for (i = 0; (*cf)[i].data || (*cf)[i].path; i++);
97         newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
98         if (!newcf)
99             goto errmalloc;
100         memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
101         memset(newcf, 0, sizeof(struct gconffile));
102     }
103     newcf[0].file = file;
104     newcf[0].path = desc;
105     *cf = newcf;
106     return file;
107     
108  errmalloc:
109     free(desc);
110     fclose(file);
111     debug(DBG_ERR, "malloc failed");
112     return NULL;
113 }
114
115 FILE *pushgconfpath(struct gconffile **cf, const char *path) {
116     FILE *f;
117
118     f = fopen(path, "r");
119     return pushgconffile(cf, f, path);
120 }
121
122 FILE *pushgconfpaths(struct gconffile **cf, const char *cfgpath) {
123     int i;
124     FILE *f = NULL;
125     glob_t globbuf;
126     char *path, *curfile = NULL, *dir;
127     
128     /* if cfgpath is relative, make it relative to current config */
129     if (*cfgpath == '/')
130         path = (char *)cfgpath;
131     else {
132         /* dirname may modify its argument */
133         curfile = stringcopy((*cf)->path, 0);
134         if (!curfile) {
135             debug(DBG_ERR, "malloc failed");
136             goto exit;
137         }
138         dir = dirname(curfile);
139         path = malloc(strlen(dir) + strlen(cfgpath) + 2);
140         if (!path) {
141             debug(DBG_ERR, "malloc failed");
142             goto exit;
143         }
144         strcpy(path, dir);
145         path[strlen(dir)] = '/';
146         strcpy(path + strlen(dir) + 1, cfgpath);
147     }
148     memset(&globbuf, 0, sizeof(glob_t));
149     if (glob(path, 0, NULL, &globbuf)) {
150         debug(DBG_INFO, "could not glob %s", path);
151         goto exit;
152     }
153
154     for (i = globbuf.gl_pathc - 1; i >= 0; i--) {
155         f = pushgconfpath(cf, globbuf.gl_pathv[i]);
156         if (!f)
157             break;
158     }    
159     globfree(&globbuf);
160
161  exit:    
162     if (curfile) {
163         free(curfile);
164         free(path);
165     }
166     return f;
167 }
168
169 int popgconf(struct gconffile **cf) {
170     int i;
171
172     if (!*cf)
173         return 0;
174     for (i = 0; (*cf)[i].data || (*cf)[i].path; i++);
175     if (i && (*cf)[0].file) {
176         fclose((*cf)[0].file);
177         if ((*cf)[0].path) {
178             debug(DBG_DBG, "closing config file %s", (*cf)[0].path);
179             free((*cf)[0].path);
180         }
181     }
182     if (i < 2) {
183         free(*cf);
184         *cf = NULL;
185         return 0;
186     }
187     memmove(*cf, *cf + 1, sizeof(struct gconffile) * i);
188     return 1;
189 }
190
191 void freegconf(struct gconffile **cf) {
192     int i;
193
194     if (!*cf)
195         return;
196     
197     for (i = 0; (*cf)[i].data || (*cf)[i].path; i++) {
198         if ((*cf)[i].file) {
199             fclose((*cf)[i].file);
200             if ((*cf)[i].path) {
201                 debug(DBG_DBG, "closing config file %s", (*cf)[i].path);
202                 free((*cf)[i].path);
203             }
204         }
205     }
206     free(*cf);
207     *cf = NULL;
208 }
209
210 struct gconffile *openconfigfile(const char *file) {
211     struct gconffile *cf = NULL;
212
213     if (!pushgconfpath(&cf, file)) {
214         debug(DBG_ERR, "could not read config file %s\n%s", file, strerror(errno));
215         return NULL;
216     }
217     debug(DBG_DBG, "reading config file %s", file);
218     return cf;
219 }
220
221 /* Parses config with following syntax:
222  * One of these:
223  * option-name value
224  * option-name = value
225  * Or:
226  * option-name value {
227  *     option-name [=] value
228  *     ...
229  * }
230  */
231
232 int getlinefromcf(struct gconffile *cf, char *line, const size_t size) {
233     size_t i, pos;
234     
235     if (!cf)
236         return 0;
237     
238     if (cf->file)
239         return fgets(line, size, cf->file) ? 1 : 0;
240     else if (cf->data) {
241         pos = cf->datapos;
242         if (!cf->data[pos])
243             return 0;
244         for (i = pos; cf->data[i] && cf->data[i] != '\n'; i++);
245         if (cf->data[i] == '\n')
246             i++;
247         if (i - pos > size - 1)
248             i = size - 1 + pos;
249         memcpy(line, cf->data + pos, i - pos);
250         line[i - pos] = '\0';
251         cf->datapos = i;
252         return 1;
253     }
254     return 0;
255 }
256
257 int getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype) {
258     char line[1024];
259     char *tokens[3], *s;
260     int tcount;
261     
262     *opt = NULL;
263     *val = NULL;
264     *conftype = 0;
265     
266     if (!cf || !*cf || (!(*cf)->file && !(*cf)->data))
267         return 1;
268
269     for (;;) {
270         if (!getlinefromcf(*cf, line, 1024)) {
271             if (popgconf(cf))
272                 continue;
273             return 1;
274         }
275         s = line;
276         for (tcount = 0; tcount < 3; tcount++) {
277             s = strtokenquote(s, &tokens[tcount], " \t\r\n", "\"'", tcount ? NULL : "#");
278             if (!s) {
279                 debug(DBG_ERR, "Syntax error in line starting with: %s", line);
280                 return 0;
281             }
282             if (!tokens[tcount])
283                 break;
284         }
285         if (!tcount || **tokens == '#')
286             continue;
287
288         if (**tokens == '}') {
289             if (block)
290                 return 1;
291             debug(DBG_ERR, "configuration error, found } with no matching {");
292             return 0;
293         }
294         break;
295     }
296     
297     switch (tcount) {
298     case 2:
299         *opt = stringcopy(tokens[0], 0);
300         if (!*opt)
301             goto errmalloc;
302         *val = stringcopy(tokens[1], 0);
303         if (!*val)
304             goto errmalloc;
305         *conftype = CONF_STR;
306         break;
307     case 3:
308         if (tokens[1][0] == '=' && tokens[1][1] == '\0') {
309             *opt = stringcopy(tokens[0], 0);
310             if (!*opt)
311                 goto errmalloc;
312             *val = stringcopy(tokens[2], 0);
313             if (!*val)
314                 goto errmalloc;
315             *conftype = CONF_STR;
316             break;
317         }
318         if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
319             *opt = stringcopy(tokens[0], 0);
320             if (!*opt)
321                 goto errmalloc;
322             *val = stringcopy(tokens[1], 0);
323             if (!*val)
324                 goto errmalloc;
325             *conftype = CONF_CBK;
326             break;
327         }
328         /* fall through */
329     default:
330         if (block)
331             debug(DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
332         else
333             debug(DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
334         return 0;
335     }
336
337     if (**val)
338         return 1;
339     
340     debug(DBG_ERR, "configuration error, option %s needs a non-empty value", *opt);
341     goto errexit;
342
343  errmalloc:
344     debug(DBG_ERR, "malloc failed");
345  errexit:    
346     free(*opt);
347     *opt = NULL;
348     free(*val);
349     *val = NULL;
350     return 0;
351 }
352
353 /* returns 1 if ok, 0 on error */
354 /* caller must free returned values also on error */
355 int getgenericconfig(struct gconffile **cf, char *block, ...) {
356     va_list ap;
357     char *opt = NULL, *val, *word, *optval, **str = NULL, ***mstr = NULL, **newmstr, *endptr;
358     uint8_t *bln = NULL;
359     long int *lint = NULL;
360     int type = 0, conftype = 0, n;
361     int (*cbk)(struct gconffile **, void *, char *, char *, char *) = NULL;
362     void *cbkarg = NULL;
363
364     for (;;) {
365         free(opt);
366         if (!getconfigline(cf, block, &opt, &val, &conftype))
367             return 0;
368         if (!opt)
369             return 1;
370
371         if (conftype == CONF_STR && !strcasecmp(opt, "include")) {
372             if (!pushgconfpaths(cf, val)) {
373                 debug(DBG_ERR, "failed to include config file %s", val);
374                 goto errexit;
375             }
376             free(val);
377             continue;
378         }
379             
380         va_start(ap, block);
381         while ((word = va_arg(ap, char *))) {
382             type = va_arg(ap, int);
383             switch (type) {
384             case CONF_STR:
385                 str = va_arg(ap, char **);
386                 if (!str)
387                     goto errparam;
388                 break;
389             case CONF_MSTR:
390                 mstr = va_arg(ap, char ***);
391                 if (!mstr)
392                     goto errparam;
393                 break;
394             case CONF_BLN:
395                 bln = va_arg(ap, uint8_t *);
396                 if (!bln)
397                     goto errparam;
398                 break;
399             case CONF_LINT:
400                 lint = va_arg(ap, long int *);
401                 if (!lint)
402                     goto errparam;
403                 break;
404             case CONF_CBK:
405                 cbk = va_arg(ap, int (*)(struct gconffile **, void *, char *, char *, char *));
406                 if (!cbk)
407                     goto errparam;
408                 cbkarg = va_arg(ap, void *);
409                 break;
410             default:
411                 goto errparam;
412             }
413             if (!strcasecmp(opt, word))
414                 break;
415         }
416         va_end(ap);
417         
418         if (!word) {
419             if (block)
420                 debug(DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
421             debug(DBG_ERR, "configuration error, unknown option %s", opt);
422             goto errexit;
423         }
424
425         if (((type == CONF_STR || type == CONF_MSTR || type == CONF_BLN || type == CONF_LINT) && conftype != CONF_STR) ||
426             (type == CONF_CBK && conftype != CONF_CBK)) {
427             if (block)
428                 debug(DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
429             debug(DBG_ERR, "configuration error, wrong syntax for option %s", opt);
430             goto errexit;
431         }
432
433         switch (type) {
434         case CONF_STR:
435             if (*str) {
436                 debug(DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
437                 goto errexit;
438             }
439             *str = val;
440             break;
441         case CONF_MSTR:
442             if (*mstr)
443                 for (n = 0; (*mstr)[n]; n++);
444             else
445                 n = 0;
446             newmstr = realloc(*mstr, sizeof(char *) * (n + 2));
447             if (!newmstr) {
448                 debug(DBG_ERR, "malloc failed");
449                 goto errexit;
450             }
451             newmstr[n] = val;
452             newmstr[n + 1] = NULL;
453             *mstr = newmstr;
454             break;
455         case CONF_BLN:
456             if (!strcasecmp(val, "on"))
457                 *bln = 1;
458             else if (!strcasecmp(val, "off"))
459                 *bln = 0;
460             else {
461                 if (block)
462                     debug(DBG_ERR, "configuration error in block %s, value for option %s must be on or off, not %s", block, opt, val);
463                 else
464                     debug(DBG_ERR, "configuration error, value for option %s must be on or off, not %s", opt, val);
465                 goto errexit;
466             }
467             break;
468         case CONF_LINT:
469             endptr = NULL;
470             *lint = strtol(val, &endptr, 0);
471             if (*lint == LONG_MIN || *lint == LONG_MAX || !endptr || endptr == val || *endptr != '\0') {
472                 if (block)
473                     debug(DBG_ERR, "configuration error in block %s, value for option %s must be an integer, not %s", block, opt, val);
474                 else
475                     debug(DBG_ERR, "configuration error, value for option %s must be an integer, not %s", opt, val);
476                 goto errexit;
477             }
478             break;
479         case CONF_CBK:
480             optval = malloc(strlen(opt) + strlen(val) + 2);
481             if (!optval) {
482                 debug(DBG_ERR, "malloc failed");
483                 goto errexit;
484             }
485             sprintf(optval, "%s %s", opt, val);
486             if (!cbk(cf, cbkarg, optval, opt, val)) {
487                 free(optval);
488                 goto errexit;
489             }
490             free(val);
491             free(optval);
492             continue;
493         default:
494             goto errparam;
495         }
496         if (block)
497             debug(DBG_DBG, "getgenericconfig: block %s: %s = %s", block, opt, val);
498         else 
499             debug(DBG_DBG, "getgenericconfig: %s = %s", opt, val);
500         if (type == CONF_BLN || type == CONF_LINT)
501             free(val);
502     }
503
504  errparam:
505     debug(DBG_ERR, "getgenericconfig: internal parameter error");
506  errexit:
507     free(opt);
508     free(val);
509     return 0;
510 }