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