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