rewritten gconfig to not exit on errors
[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 FILE *pushgconffile(struct gconffile **cf, const char *path) {
53     int i;
54     struct gconffile *newcf;
55     char *pathcopy;
56     FILE *f;
57
58     f = fopen(path, "r");
59     if (!f) {
60         debug(DBG_INFO, "could not read config file %s", path);
61         return NULL;
62     }
63     debug(DBG_DBG, "opened config file %s", path);
64
65     pathcopy = stringcopy(path, 0);
66     if (!pathcopy)
67         goto errmalloc;
68     
69     if (!*cf) {
70         newcf = malloc(sizeof(struct gconffile) * 2);
71         if (!newcf)
72             goto errmalloc;
73         newcf[1].file = NULL;
74         newcf[1].path = NULL;
75     } else {
76         for (i = 0; (*cf)[i].path; i++);
77         newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
78         if (!newcf)
79             goto errmalloc;
80         memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
81     }
82     newcf[0].file = f;
83     newcf[0].path = pathcopy;
84     *cf = newcf;
85     return f;
86     
87  errmalloc:
88     free(pathcopy);
89     fclose(f);
90     debug(DBG_ERR, "malloc failed");
91     return NULL;
92 }
93
94 FILE *pushgconffiles(struct gconffile **cf, const char *cfgpath) {
95     int i;
96     FILE *f = NULL;
97     glob_t globbuf;
98     char *path, *curfile = NULL, *dir;
99     
100     /* if cfgpath is relative, make it relative to current config */
101     if (*cfgpath == '/')
102         path = (char *)cfgpath;
103     else {
104         /* dirname may modify its argument */
105         curfile = stringcopy((*cf)->path, 0);
106         if (!curfile) {
107             debug(DBG_ERR, "malloc failed");
108             goto exit;
109         }
110         dir = dirname(curfile);
111         path = malloc(strlen(dir) + strlen(cfgpath) + 2);
112         if (!path) {
113             debug(DBG_ERR, "malloc failed");
114             goto exit;
115         }
116         strcpy(path, dir);
117         path[strlen(dir)] = '/';
118         strcpy(path + strlen(dir) + 1, cfgpath);
119     }
120     memset(&globbuf, 0, sizeof(glob_t));
121     if (glob(path, 0, NULL, &globbuf)) {
122         debug(DBG_INFO, "could not glob %s", path);
123         goto exit;
124     }
125
126     for (i = globbuf.gl_pathc - 1; i >= 0; i--) {
127         f = pushgconffile(cf, globbuf.gl_pathv[i]);
128         if (!f)
129             break;
130     }    
131     globfree(&globbuf);
132
133  exit:    
134     if (curfile) {
135         free(curfile);
136         free(path);
137     }
138     return f;
139 }
140
141 FILE *popgconffile(struct gconffile **cf) {
142     int i;
143
144     if (!*cf)
145         return NULL;
146     for (i = 0; (*cf)[i].path; i++);
147     if (i && (*cf)[0].file) {
148         fclose((*cf)[0].file);
149         debug(DBG_DBG, "closing config file %s", (*cf)[0].path);
150         free((*cf)[0].path);
151     }
152     if (i < 2) {
153         free(*cf);
154         *cf = NULL;
155         return NULL;
156     }
157     memmove(*cf, *cf + 1, sizeof(struct gconffile) * i);
158     return (*cf)[0].file;
159 }
160
161 struct gconffile *openconfigfile(const char *file) {
162     struct gconffile *cf = NULL;
163
164     if (!pushgconffile(&cf, file)) {
165         debug(DBG_ERR, "could not read config file %s\n%s", file, strerror(errno));
166         return NULL;
167     }
168     debug(DBG_DBG, "reading config file %s", file);
169     return cf;
170 }
171
172 /* Parses config with following syntax:
173  * One of these:
174  * option-name value
175  * option-name = value
176  * Or:
177  * option-name value {
178  *     option-name [=] value
179  *     ...
180  * }
181  */
182
183 int getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype) {
184     char line[1024];
185     char *tokens[3], *s;
186     int tcount;
187     
188     *opt = NULL;
189     *val = NULL;
190     *conftype = 0;
191     
192     if (!cf || !*cf || !(*cf)->file)
193         return 1;
194
195     for (;;) {
196         if (!fgets(line, 1024, (*cf)->file)) {
197             if (popgconffile(cf))
198                 continue;
199             return 1;
200         }
201         s = line;
202         for (tcount = 0; tcount < 3; tcount++) {
203             s = strtokenquote(s, &tokens[tcount], " \t\r\n", "\"'", tcount ? NULL : "#");
204             if (!s) {
205                 debug(DBG_ERR, "Syntax error in line starting with: %s", line);
206                 return 0;
207             }
208             if (!tokens[tcount])
209                 break;
210         }
211         if (!tcount || **tokens == '#')
212             continue;
213
214         if (**tokens == '}') {
215             if (block)
216                 return 1;
217             debug(DBG_ERR, "configuration error, found } with no matching {");
218             return 0;
219         }
220         break;
221     }
222     
223     switch (tcount) {
224     case 2:
225         *opt = stringcopy(tokens[0], 0);
226         if (!*opt)
227             goto errmalloc;
228         *val = stringcopy(tokens[1], 0);
229         if (!*val)
230             goto errmalloc;
231         *conftype = CONF_STR;
232         break;
233     case 3:
234         if (tokens[1][0] == '=' && tokens[1][1] == '\0') {
235             *opt = stringcopy(tokens[0], 0);
236             if (!*opt)
237                 goto errmalloc;
238             *val = stringcopy(tokens[2], 0);
239             if (!*val)
240                 goto errmalloc;
241             *conftype = CONF_STR;
242             break;
243         }
244         if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
245             *opt = stringcopy(tokens[0], 0);
246             if (!*opt)
247                 goto errmalloc;
248             *val = stringcopy(tokens[1], 0);
249             if (!*val)
250                 goto errmalloc;
251             *conftype = CONF_CBK;
252             break;
253         }
254         /* fall through */
255     default:
256         if (block)
257             debug(DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
258         else
259             debug(DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
260         return 0;
261     }
262
263     if (**val)
264         return 1;
265     
266     debug(DBG_ERR, "configuration error, option %s needs a non-empty value", *opt);
267     goto errexit;
268
269  errmalloc:
270     debug(DBG_ERR, "malloc failed");
271  errexit:    
272     free(*opt);
273     *opt = NULL;
274     free(*val);
275     *val = NULL;
276     return 0;
277 }
278
279 /* returns 1 if ok, 0 on error */
280 /* caller must free returned values also on error */
281 int getgenericconfig(struct gconffile **cf, char *block, ...) {
282     va_list ap;
283     char *opt = NULL, *val, *word, *optval, **str = NULL, ***mstr = NULL, **newmstr;
284     uint8_t *bln;
285     int type = 0, conftype = 0, n;
286     void (*cbk)(struct gconffile **, void *, char *, char *, char *) = NULL;
287     void *cbkarg = NULL;
288
289     for (;;) {
290         free(opt);
291         if (!getconfigline(cf, block, &opt, &val, &conftype))
292             return 0;
293         if (!opt)
294             return 1;
295
296         if (conftype == CONF_STR && !strcasecmp(opt, "include")) {
297             if (!pushgconffiles(cf, val)) {
298                 debug(DBG_ERR, "failed to include config file %s", val);
299                 goto errexit;
300             }
301             free(val);
302             continue;
303         }
304             
305         va_start(ap, block);
306         while ((word = va_arg(ap, char *))) {
307             type = va_arg(ap, int);
308             switch (type) {
309             case CONF_STR:
310                 str = va_arg(ap, char **);
311                 if (!str)
312                     goto errparam;
313                 break;
314             case CONF_MSTR:
315                 mstr = va_arg(ap, char ***);
316                 if (!mstr)
317                     goto errparam;
318                 break;
319             case CONF_BLN:
320                 bln = va_arg(ap, uint8_t *);
321                 if (!bln)
322                     goto errparam;
323                 break;
324             case CONF_CBK:
325                 cbk = va_arg(ap, void (*)(struct gconffile **, void *, char *, char *, char *));
326                 if (!cbk)
327                     goto errparam;
328                 cbkarg = va_arg(ap, void *);
329                 break;
330             default:
331                 goto errparam;
332             }
333             if (!strcasecmp(opt, word))
334                 break;
335         }
336         va_end(ap);
337         
338         if (!word) {
339             if (block)
340                 debug(DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
341             debug(DBG_ERR, "configuration error, unknown option %s", opt);
342             goto errexit;
343         }
344
345         if (((type == CONF_STR || type == CONF_MSTR || type == CONF_BLN) && conftype != CONF_STR) ||
346             (type == CONF_CBK && conftype != CONF_CBK)) {
347             if (block)
348                 debug(DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
349             debug(DBG_ERR, "configuration error, wrong syntax for option %s", opt);
350             goto errexit;
351         }
352
353         switch (type) {
354         case CONF_STR:
355             if (*str) {
356                 debug(DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
357                 goto errexit;
358             }
359             *str = val;
360             break;
361         case CONF_MSTR:
362             if (*mstr)
363                 for (n = 0; (*mstr)[n]; n++);
364             else
365                 n = 0;
366             newmstr = realloc(*mstr, sizeof(char *) * (n + 2));
367             if (!newmstr) {
368                 debug(DBG_ERR, "malloc failed");
369                 goto errexit;
370             }
371             newmstr[n] = val;
372             newmstr[n + 1] = NULL;
373             *mstr = newmstr;
374             break;
375         case CONF_BLN:
376             if (!strcasecmp(val, "on"))
377                 *bln = 1;
378             else if (!strcasecmp(val, "off"))
379                 *bln = 0;
380             else {
381                 if (block)
382                     debug(DBG_ERR, "configuration error in block %s, value for option %s must be on or off, not %s", block, opt, val);
383                 else
384                     debug(DBG_ERR, "configuration error, value for option %s must be on or off, not %s", opt, val);
385                 goto errexit;
386             }
387             break;
388         case CONF_CBK:
389             optval = malloc(strlen(opt) + strlen(val) + 2);
390             if (!optval) {
391                 debug(DBG_ERR, "malloc failed");
392                 goto errexit;
393             }
394             sprintf(optval, "%s %s", opt, val);
395             cbk(cf, cbkarg, optval, opt, val);
396             free(val);
397             free(optval);
398             continue;
399         default:
400             goto errparam;
401         }
402         if (block)
403             debug(DBG_DBG, "getgenericconfig: block %s: %s = %s", block, opt, val);
404         else 
405             debug(DBG_DBG, "getgenericconfig: %s = %s", opt, val);
406         if (type == CONF_BLN)
407             free(val);
408     }
409
410  errparam:
411     debug(DBG_ERR, "getgenericconfig: internal parameter error");
412  errexit:
413     free(opt);
414     free(val);
415     return 0;
416 }