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