implemented config file stack; to be used for include support
[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     if (!*cf) {
60         newcf = malloc(sizeof(struct gconffile) * 2);
61         if (!newcf)
62             debugx(1, DBG_ERR, "malloc failed");
63         newcf[1].path = NULL;
64     } else {
65         for (i = 0; (*cf)[i].path; i++);
66         newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
67         if (!newcf)
68             debugx(1, DBG_ERR, "malloc failed");
69         memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
70     }
71     newcf[0].file = f;
72     newcf[0].path = stringcopy(path, 0);
73     *cf = newcf;
74     return f;
75 }
76
77 FILE *popgconffile(struct gconffile **cf) {
78     int i;
79
80     if (!*cf)
81         return NULL;
82     for (i = 0; (*cf)[i].path; i++);
83     if (i && (*cf)[0].file)
84         fclose((*cf)[0].file);
85     if (i < 2) {
86         free(*cf);
87         *cf = NULL;
88         return NULL;
89     }
90     memmove(*cf, *cf + 1, sizeof(struct gconffile) * i);
91     return (*cf)[0].file;
92 }
93
94 /* Parses config with following syntax:
95  * One of these:
96  * option-name value
97  * option-name = value
98  * Or:
99  * option-name value {
100  *     option-name [=] value
101  *     ...
102  * }
103  */
104 void getgenericconfig(FILE *f, char *block, ...) {
105     va_list ap;
106     char line[1024];
107     /* initialise lots of stuff to avoid stupid compiler warnings */
108     char *tokens[3], *s, *opt = NULL, *val = NULL, *word, *optval, **str = NULL, ***mstr = NULL;
109     int type = 0, tcount, conftype = 0, n;
110     void (*cbk)(FILE *, char *, char *, char *) = NULL;
111         
112     while (fgets(line, 1024, f)) {
113         s = line;
114         for (tcount = 0; tcount < 3; tcount++) {
115             s = strtokenquote(s, &tokens[tcount], " \t\r\n", "\"'", tcount ? NULL : "#");
116             if (!s)
117                 debugx(1, DBG_ERR, "Syntax error in line starting with: %s", line);
118             if (!tokens[tcount])
119                 break;
120         }
121         if (!tcount || **tokens == '#')
122             continue;
123
124         if (**tokens == '}') {
125             if (block)
126                 return;
127             debugx(1, DBG_ERR, "configuration error, found } with no matching {");
128         }
129
130         switch (tcount) {
131         case 2:
132             opt = tokens[0];
133             val = tokens[1];
134             conftype = CONF_STR;
135             break;
136         case 3:
137             if (tokens[1][0] == '=' && tokens[1][1] == '\0') {
138                 opt = tokens[0];
139                 val = tokens[2];
140                 conftype = CONF_STR;
141                 break;
142             }
143             if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
144                 opt = tokens[0];
145                 val = tokens[1];
146                 conftype = CONF_CBK;
147                 break;
148             }
149             /* fall through */
150         default:
151             if (block)
152                 debugx(1, DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
153             debugx(1, DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
154         }
155
156         if (!*val)
157             debugx(1, DBG_ERR, "configuration error, option %s needs a non-empty value", opt);
158         
159         va_start(ap, block);
160         while ((word = va_arg(ap, char *))) {
161             type = va_arg(ap, int);
162             switch (type) {
163             case CONF_STR:
164                 str = va_arg(ap, char **);
165                 if (!str)
166                     debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
167                 break;
168             case CONF_MSTR:
169                 mstr = va_arg(ap, char ***);
170                 if (!mstr)
171                     debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
172                 break;
173             case CONF_CBK:
174                 cbk = va_arg(ap, void (*)(FILE *, char *, char *, char *));
175                 break;
176             default:
177                 debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
178             }
179             if (!strcasecmp(opt, word))
180                 break;
181         }
182         va_end(ap);
183         
184         if (!word) {
185             if (block)
186                 debugx(1, DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
187             debugx(1, DBG_ERR, "configuration error, unknown option %s", opt);
188         }
189
190         if (((type == CONF_STR || type == CONF_MSTR) && conftype != CONF_STR) ||
191             (type == CONF_CBK && conftype != CONF_CBK)) {
192             if (block)
193                 debugx(1, DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
194             debugx(1, DBG_ERR, "configuration error, wrong syntax for option %s", opt);
195         }
196
197         switch (type) {
198         case CONF_STR:
199             if (block)
200                 debug(DBG_DBG, "getgeneralconfig: block %s: %s = %s", block, opt, val);
201             else 
202                 debug(DBG_DBG, "getgeneralconfig: %s = %s", opt, val);
203             if (*str)
204                 debugx(1, DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
205             *str = stringcopy(val, 0);
206             if (!*str)
207                 debugx(1, DBG_ERR, "malloc failed");
208             break;
209         case CONF_MSTR:
210             if (block)
211                 debug(DBG_DBG, "getgeneralconfig: block %s: %s = %s", block, opt, val);
212             else 
213                 debug(DBG_DBG, "getgeneralconfig: %s = %s", opt, val);
214             if (*mstr)
215                 for (n = 0; (*mstr)[n]; n++);
216             else
217                 n = 0;
218             *mstr = realloc(*mstr, sizeof(char *) * (n + 2));
219             if (!*mstr)
220                 debugx(1, DBG_ERR, "malloc failed");
221             (*mstr)[n] = stringcopy(val, 0);
222             (*mstr)[n + 1] = NULL;
223             break;
224         case CONF_CBK:
225             optval = malloc(strlen(opt) + strlen(val) + 2);
226             if (!optval)
227                 debugx(1, DBG_ERR, "malloc failed");
228             sprintf(optval, "%s %s", opt, val);
229             cbk(f, optval, opt, val);
230             free(optval);
231             break;
232         default:
233             debugx(1, DBG_ERR, "getgeneralconfig: internal parameter error");
234         }
235     }
236 }