working include support for individual files
[radsecproxy.git] / gconfig.c
1 /*
2  * Copyright (C) 2007 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 <sys/types.h>
14 #include "debug.h"
15 #include "util.h"
16 #include "gconfig.h"
17
18 /* returns NULL on error, where to continue parsing if token and ok. E.g. "" will return token with empty string */
19 char *strtokenquote(char *s, char **token, char *del, char *quote, char *comment) {
20     char *t = s, *q, *r;
21
22     if (!t || !token || !del)
23         return NULL;
24     while (*t && strchr(del, *t))
25         t++;
26     if (!*t || (comment && strchr(comment, *t))) {
27         *token = NULL;
28         return t + 1; /* needs to be non-NULL, but value doesn't matter */
29     }
30     if (quote && (q = strchr(quote, *t))) {
31         t++;
32         r = t;
33         while (*t && *t != *q)
34             t++;
35         if (!*t || (t[1] && !strchr(del, t[1])))
36             return NULL;
37         *t = '\0';
38         *token = r;
39         return t + 1;
40     }
41     *token = t;
42     t++;
43     while (*t && !strchr(del, *t))
44         t++;
45     *t = '\0';
46     return t + 1;
47 }
48
49 FILE *pushgconffile(struct gconffile **cf, const char *path) {
50     int i;
51     struct gconffile *newcf;
52     FILE *f;
53
54     f = fopen(path, "r");
55     if (!f) {
56         debug(DBG_INFO, "could not read config file %s", path);
57         return NULL;
58     }
59     debug(DBG_DBG, "opened config file %s", path);
60     if (!*cf) {
61         newcf = malloc(sizeof(struct gconffile) * 2);
62         if (!newcf)
63             debugx(1, DBG_ERR, "malloc failed");
64         newcf[1].file = NULL;
65         newcf[1].path = NULL;
66     } else {
67         for (i = 0; (*cf)[i].path; i++);
68         newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
69         if (!newcf)
70             debugx(1, DBG_ERR, "malloc failed");
71         memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
72     }
73     newcf[0].file = f;
74     newcf[0].path = stringcopy(path, 0);
75     *cf = newcf;
76     return f;
77 }
78
79 FILE *popgconffile(struct gconffile **cf) {
80     int i;
81
82     if (!*cf)
83         return NULL;
84     for (i = 0; (*cf)[i].path; i++);
85     if (i && (*cf)[0].file) {
86         fclose((*cf)[0].file);
87         debug(DBG_DBG, "closing config file %s", (*cf)[0].path);
88     }
89     if (i < 2) {
90         free(*cf);
91         *cf = NULL;
92         return NULL;
93     }
94     memmove(*cf, *cf + 1, sizeof(struct gconffile) * i);
95     return (*cf)[0].file;
96 }
97
98 /* Parses config with following syntax:
99  * One of these:
100  * option-name value
101  * option-name = value
102  * Or:
103  * option-name value {
104  *     option-name [=] value
105  *     ...
106  * }
107  */
108 void getgenericconfig(struct gconffile **cf, char *block, ...) {
109     va_list ap;
110     char line[1024];
111     /* initialise lots of stuff to avoid stupid compiler warnings */
112     char *tokens[3], *s, *opt = NULL, *val = NULL, *word, *optval, **str = NULL, ***mstr = NULL;
113     int type = 0, tcount, conftype = 0, n;
114
115     void (*cbk)(struct gconffile **, char *, char *, char *) = NULL;
116
117     if (!cf || !*cf || !(*cf)->file)
118         return;
119     for (;;) {
120         if (!fgets(line, 1024, (*cf)->file)) {
121             if (popgconffile(cf))
122                 continue;
123             return;
124         }
125         s = line;
126         for (tcount = 0; tcount < 3; tcount++) {
127             s = strtokenquote(s, &tokens[tcount], " \t\r\n", "\"'", tcount ? NULL : "#");
128             if (!s)
129                 debugx(1, DBG_ERR, "Syntax error in line starting with: %s", line);
130             if (!tokens[tcount])
131                 break;
132         }
133         if (!tcount || **tokens == '#')
134             continue;
135
136         if (**tokens == '}') {
137             if (block)
138                 return;
139             debugx(1, DBG_ERR, "configuration error, found } with no matching {");
140         }
141
142         switch (tcount) {
143         case 2:
144             opt = tokens[0];
145             val = tokens[1];
146             conftype = CONF_STR;
147             break;
148         case 3:
149             if (tokens[1][0] == '=' && tokens[1][1] == '\0') {
150                 opt = tokens[0];
151                 val = tokens[2];
152                 conftype = CONF_STR;
153                 break;
154             }
155             if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
156                 opt = tokens[0];
157                 val = tokens[1];
158                 conftype = CONF_CBK;
159                 break;
160             }
161             /* fall through */
162         default:
163             if (block)
164                 debugx(1, DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
165             debugx(1, DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
166         }
167
168         if (!*val)
169             debugx(1, DBG_ERR, "configuration error, option %s needs a non-empty value", opt);
170
171         if (conftype == CONF_STR && !strcasecmp(opt, "include")) {
172             pushgconffile(cf, val);
173             continue;
174         }
175             
176         va_start(ap, block);
177         while ((word = va_arg(ap, char *))) {
178             type = va_arg(ap, int);
179             switch (type) {
180             case CONF_STR:
181                 str = va_arg(ap, char **);
182                 if (!str)
183                     debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
184                 break;
185             case CONF_MSTR:
186                 mstr = va_arg(ap, char ***);
187                 if (!mstr)
188                     debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
189                 break;
190             case CONF_CBK:
191                 cbk = va_arg(ap, void (*)(struct gconffile **, char *, char *, char *));
192                 break;
193             default:
194                 debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
195             }
196             if (!strcasecmp(opt, word))
197                 break;
198         }
199         va_end(ap);
200         
201         if (!word) {
202             if (block)
203                 debugx(1, DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
204             debugx(1, DBG_ERR, "configuration error, unknown option %s", opt);
205         }
206
207         if (((type == CONF_STR || type == CONF_MSTR) && conftype != CONF_STR) ||
208             (type == CONF_CBK && conftype != CONF_CBK)) {
209             if (block)
210                 debugx(1, DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
211             debugx(1, DBG_ERR, "configuration error, wrong syntax for option %s", opt);
212         }
213
214         switch (type) {
215         case CONF_STR:
216             if (block)
217                 debug(DBG_DBG, "getgeneralconfig: block %s: %s = %s", block, opt, val);
218             else 
219                 debug(DBG_DBG, "getgeneralconfig: %s = %s", opt, val);
220             if (*str)
221                 debugx(1, DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
222             *str = stringcopy(val, 0);
223             if (!*str)
224                 debugx(1, DBG_ERR, "malloc failed");
225             break;
226         case CONF_MSTR:
227             if (block)
228                 debug(DBG_DBG, "getgeneralconfig: block %s: %s = %s", block, opt, val);
229             else 
230                 debug(DBG_DBG, "getgeneralconfig: %s = %s", opt, val);
231             if (*mstr)
232                 for (n = 0; (*mstr)[n]; n++);
233             else
234                 n = 0;
235             *mstr = realloc(*mstr, sizeof(char *) * (n + 2));
236             if (!*mstr)
237                 debugx(1, DBG_ERR, "malloc failed");
238             (*mstr)[n] = stringcopy(val, 0);
239             (*mstr)[n + 1] = NULL;
240             break;
241         case CONF_CBK:
242             optval = malloc(strlen(opt) + strlen(val) + 2);
243             if (!optval)
244                 debugx(1, DBG_ERR, "malloc failed");
245             sprintf(optval, "%s %s", opt, val);
246             cbk(cf, optval, opt, val);
247             free(optval);
248             break;
249         default:
250             debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
251         }
252     }
253 }