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