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