1 /*
2  * atheme-services: A collection of minimalist IRC services
3  * confprocess.c: Generic configuration processing.
4  *
5  * Copyright (c) 2005-2008 Atheme Project (http://www.atheme.org)
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21  * POSSIBILITY OF SUCH DAMAGE.
22  */
23 
24 #include "atheme.h"
25 #include <limits.h>
26 
27 enum conftype
28 {
29 	CONF_HANDLER,
30 	CONF_UINT,
31 	CONF_DURATION,
32 	CONF_DUPSTR,
33 	CONF_BOOL,
34 	CONF_SUBBLOCK
35 };
36 
37 struct ConfTable
38 {
39 	char *name;
40 	enum conftype type;
41 	unsigned int flags;
42 	union
43 	{
44 		int (*handler) (mowgli_config_file_entry_t *);
45 
46 		struct
47 		{
48 			unsigned int *var;
49 			unsigned int min;
50 			unsigned int max;
51 			unsigned int def;
52 		} uint_val;
53 		struct
54 		{
55 			unsigned int *var;
56 			const char *defunit;
57 			unsigned int def;
58 		} duration_val;
59 		struct
60 		{
61 			char **var;
62 			char *def;
63 		} dupstr_val;
64 		struct
65 		{
66 			bool *var;
67 			bool def;
68 		} bool_val;
69 
70 		mowgli_list_t *subblock;
71 	} un;
72 	mowgli_node_t node;
73 };
74 
75 mowgli_heap_t *conftable_heap;
76 
77 mowgli_list_t confblocks;
78 bool conf_need_rehash;
79 
80 /* *INDENT-ON* */
81 
conf_report_warning(mowgli_config_file_entry_t * ce,const char * fmt,...)82 void conf_report_warning(mowgli_config_file_entry_t *ce, const char *fmt, ...)
83 {
84 	va_list va;
85 	char buf[BUFSIZE];
86 	char name[80];
87 
88 	return_if_fail(ce != NULL);
89 	return_if_fail(fmt != NULL);
90 
91 	va_start(va, fmt);
92 	vsnprintf(buf, BUFSIZE, fmt, va);
93 	va_end(va);
94 
95 	if (ce->prevlevel == NULL)
96 		mowgli_strlcpy(name, ce->varname, sizeof name);
97 	else if (ce->prevlevel->prevlevel == NULL)
98 		snprintf(name, sizeof name, "%s::%s",
99 				ce->prevlevel->varname, ce->varname);
100 	else if (ce->prevlevel->prevlevel->prevlevel == NULL)
101 		snprintf(name, sizeof name, "%s::%s::%s",
102 				ce->prevlevel->prevlevel->varname,
103 				ce->prevlevel->varname, ce->varname);
104 	else
105 		snprintf(name, sizeof name, "...::%s::%s::%s",
106 				ce->prevlevel->prevlevel->varname,
107 				ce->prevlevel->varname, ce->varname);
108 	slog(LG_ERROR, "%s:%d: [%s] warning: %s", ce->fileptr->filename, ce->varlinenum, name, buf);
109 }
110 
process_uint_configentry(mowgli_config_file_entry_t * ce,unsigned int * var,unsigned int min,unsigned int max)111 bool process_uint_configentry(mowgli_config_file_entry_t *ce, unsigned int *var,
112 		unsigned int min, unsigned int max)
113 {
114 	unsigned long v;
115 	char *end;
116 
117 	if (ce->vardata == NULL)
118 	{
119 		conf_report_warning(ce, "no parameter for configuration option");
120 		return false;
121 	}
122 	errno = 0;
123 	v = strtoul(ce->vardata, &end, 10);
124 	if (errno != 0 || *end != '\0' || end == ce->vardata)
125 	{
126 		conf_report_warning(ce, "invalid number \"%s\"",
127 				ce->vardata);
128 		return false;
129 	}
130 	if (v > max || v < min)
131 	{
132 		conf_report_warning(ce, "value %lu is out of range [%u,%u]",
133 				v,
134 				min,
135 				max);
136 		return false;
137 	}
138 	*var = v;
139 	return true;
140 }
141 
142 static struct
143 {
144 	const char *name;
145 	unsigned int value;
146 } duration_units[] =
147 {
148 	{ "s", 1 }, /* must be first */
149 	{ "m", 60 },
150 	{ "h", 60 * 60 },
151 	{ "d", 24 * 60 * 60 },
152 	{ "w", 7 * 24 * 60 * 60 },
153 	{ NULL, 0 }
154 };
155 
process_duration_configentry(mowgli_config_file_entry_t * ce,unsigned int * var,const char * defunit)156 bool process_duration_configentry(mowgli_config_file_entry_t *ce, unsigned int *var,
157 		const char *defunit)
158 {
159 	unsigned long v;
160 	unsigned int i;
161 	unsigned int max;
162 	const char *unit;
163 	char *end;
164 
165 	return_val_if_fail(ce != NULL, false);
166 	return_val_if_fail(var != NULL, false);
167 	return_val_if_fail(defunit != NULL, false);
168 
169 	if (ce->vardata == NULL)
170 	{
171 		conf_report_warning(ce, "no parameter for configuration option");
172 		return false;
173 	}
174 	errno = 0;
175 	v = strtoul(ce->vardata, &end, 10);
176 	if (errno != 0 || end == ce->vardata)
177 	{
178 		conf_report_warning(ce, "invalid number \"%s\"",
179 				ce->vardata);
180 		return false;
181 	}
182 	while (*end == ' ' || *end == '\t')
183 		end++;
184 	unit = *end != '\0' ? end : defunit;
185 	if (unit == NULL || *unit == '\0')
186 		i = 0;
187 	else
188 	{
189 		for (i = 0; duration_units[i].name != NULL; i++)
190 			if (!strcasecmp(duration_units[i].name, unit))
191 				break;
192 		if (duration_units[i].name == NULL)
193 		{
194 			conf_report_warning(ce, "invalid unit \"%s\"", unit);
195 			return false;
196 		}
197 	}
198 	max = UINT_MAX / duration_units[i].value;
199 	if (v > max)
200 	{
201 		conf_report_warning(ce, "value %lu%s is out of range [%u%s,%u%s]",
202 				v,
203 				duration_units[i].name,
204 				0,
205 				duration_units[i].name,
206 				max,
207 				duration_units[i].name);
208 		return false;
209 	}
210 	*var = v * duration_units[i].value;
211 	return true;
212 }
213 
set_default(struct ConfTable * ct)214 static void set_default(struct ConfTable *ct)
215 {
216 	return_if_fail(ct != NULL);
217 
218 	if (ct->flags & CONF_NO_REHASH && runflags & RF_REHASHING)
219 		return;
220 
221 	switch (ct->type)
222 	{
223 		case CONF_UINT:
224 			*ct->un.uint_val.var = ct->un.uint_val.def;
225 			break;
226 		case CONF_DURATION:
227 			*ct->un.duration_val.var = ct->un.duration_val.def;
228 			break;
229 		case CONF_DUPSTR:
230 			if (*ct->un.dupstr_val.var)
231 				free(*ct->un.dupstr_val.var);
232 			*ct->un.dupstr_val.var = ct->un.dupstr_val.def ? sstrdup(ct->un.dupstr_val.def) : NULL;
233 			break;
234 		case CONF_BOOL:
235 			*ct->un.bool_val.var = ct->un.bool_val.def;
236 			break;
237 		case CONF_HANDLER:
238 		case CONF_SUBBLOCK:
239 			break;
240 	}
241 }
242 
process_configentry(struct ConfTable * ct,mowgli_config_file_entry_t * ce)243 static void process_configentry(struct ConfTable *ct, mowgli_config_file_entry_t *ce)
244 {
245 	return_if_fail(ct != NULL);
246 	return_if_fail(ce != NULL);
247 
248 	if (ct->flags & CONF_NO_REHASH && runflags & RF_REHASHING)
249 		return;
250 
251 	switch (ct->type)
252 	{
253 		case CONF_HANDLER:
254 			ct->un.handler(ce);
255 			break;
256 		case CONF_UINT:
257 			if (!process_uint_configentry(ce, ct->un.uint_val.var,
258 					ct->un.uint_val.min,
259 					ct->un.uint_val.max))
260 				return;
261 			break;
262 		case CONF_DURATION:
263 			if (!process_duration_configentry(ce,
264 						ct->un.duration_val.var,
265 						ct->un.duration_val.defunit))
266 				return;
267 			break;
268 		case CONF_DUPSTR:
269 			if (ce->vardata == NULL)
270 				conf_report_warning(ce, "no parameter for configuration option");
271 			else
272 			{
273 				if (*ct->un.dupstr_val.var)
274 					free(*ct->un.dupstr_val.var);
275 				*ct->un.dupstr_val.var = sstrdup(ce->vardata);
276 			}
277 			break;
278 		case CONF_BOOL:
279 			if (ce->vardata == NULL ||
280 					!strcasecmp(ce->vardata, "yes") ||
281 					!strcasecmp(ce->vardata, "on") ||
282 					!strcasecmp(ce->vardata, "true") ||
283 					!strcmp(ce->vardata, "1"))
284 				*ct->un.bool_val.var = true;
285 			else if (!strcasecmp(ce->vardata, "no") ||
286 					!strcasecmp(ce->vardata, "off") ||
287 					!strcasecmp(ce->vardata, "false") ||
288 					!strcmp(ce->vardata, "0"))
289 				*ct->un.bool_val.var = false;
290 			else
291 				conf_report_warning(ce, "invalid boolean \"%s\"", ce->vardata);
292 			break;
293 		case CONF_SUBBLOCK:
294 			subblock_handler(ce, ct->un.subblock);
295 			break;
296 	}
297 }
298 
conf_process(mowgli_config_file_t * cfp)299 void conf_process(mowgli_config_file_t *cfp)
300 {
301 	mowgli_config_file_t *cfptr;
302 	mowgli_config_file_entry_t *ce;
303 	mowgli_node_t *tn;
304 	struct ConfTable *ct = NULL;
305 	struct ConfTable *loadmodule = NULL;
306 
307 	return_if_fail(cfp != NULL);
308 
309 	MOWGLI_ITER_FOREACH(tn, confblocks.head)
310 	{
311 		ct = tn->data;
312 
313 		set_default(ct);
314 
315 		if (!strcasecmp(ct->name, "LOADMODULE"))
316 			loadmodule = ct;
317 	}
318 
319 
320 	/* LOADMODULEs may change confblocks, so we must
321 	 * load modules before doing anything else */
322 	MOWGLI_ITER_FOREACH(cfptr, cfp)
323 	{
324 		MOWGLI_ITER_FOREACH(ce, cfptr->entries)
325 		{
326 			if (!strcasecmp(ce->varname, "LOADMODULE"))
327 			{
328 				process_configentry(loadmodule, ce);
329 			}
330 		}
331 	}
332 
333 	MOWGLI_ITER_FOREACH(cfptr, cfp)
334 	{
335 		MOWGLI_ITER_FOREACH(ce, cfptr->entries)
336 		{
337 			MOWGLI_ITER_FOREACH(tn, confblocks.head)
338 			{
339 				ct = tn->data;
340 
341 				if ((!strcasecmp(ct->name, ce->varname)) && (strcasecmp(ce->varname, "LOADMODULE")))
342 				{
343 					process_configentry(ct, ce);
344 					break;
345 				}
346 			}
347 
348 			if (ct == NULL)
349 				conf_report_warning(ce, "invalid configuration option");
350 		}
351 	}
352 
353 	conf_need_rehash = false;
354 }
355 
subblock_handler(mowgli_config_file_entry_t * ce,mowgli_list_t * entries)356 int subblock_handler(mowgli_config_file_entry_t *ce, mowgli_list_t *entries)
357 {
358 	mowgli_node_t *tn;
359 	struct ConfTable *ct = NULL;
360 
361 	return_val_if_fail(ce != NULL, 0);
362 	return_val_if_fail(entries != NULL, 0);
363 
364 	MOWGLI_ITER_FOREACH(tn, entries->head)
365 	{
366 		ct = tn->data;
367 
368 		set_default(ct);
369 	}
370 
371 	MOWGLI_ITER_FOREACH(ce, ce->entries)
372 	{
373 		MOWGLI_ITER_FOREACH(tn, entries->head)
374 		{
375 			ct = tn->data;
376 
377 			if (!strcasecmp(ct->name, ce->varname))
378 			{
379 				process_configentry(ct, ce);
380 				break;
381 			}
382 		}
383 
384 		if (ct == NULL)
385 			conf_report_warning(ce, "invalid configuration option");
386 	}
387 	return 0;
388 }
389 
find_top_conf(const char * name)390 struct ConfTable *find_top_conf(const char *name)
391 {
392 	mowgli_node_t *n;
393 	struct ConfTable *ct;
394 
395 	return_val_if_fail(name != NULL, NULL);
396 
397 	MOWGLI_ITER_FOREACH(n, confblocks.head)
398 	{
399 		ct = n->data;
400 
401 		if (!strcasecmp(ct->name, name))
402 			return ct;
403 	}
404 
405 	return NULL;
406 }
407 
find_conf_item(const char * name,mowgli_list_t * conflist)408 struct ConfTable *find_conf_item(const char *name, mowgli_list_t *conflist)
409 {
410 	mowgli_node_t *n;
411 	struct ConfTable *ct;
412 
413 	return_val_if_fail(name != NULL, NULL);
414 	return_val_if_fail(conflist != NULL, NULL);
415 
416 	MOWGLI_ITER_FOREACH(n, conflist->head)
417 	{
418 		ct = n->data;
419 
420 		if (!strcasecmp(ct->name, name))
421 			return ct;
422 	}
423 
424 	return NULL;
425 }
426 
add_top_conf(const char * name,int (* handler)(mowgli_config_file_entry_t * ce))427 void add_top_conf(const char *name, int (*handler) (mowgli_config_file_entry_t *ce))
428 {
429 	struct ConfTable *ct;
430 
431 	return_if_fail(name != NULL);
432 	return_if_fail(handler != NULL);
433 
434 	if (find_top_conf(name))
435 	{
436 		slog(LG_DEBUG, "add_top_conf(): duplicate config block '%s'.", name);
437 		return;
438 	}
439 
440 	ct = mowgli_heap_alloc(conftable_heap);
441 
442 	ct->name = sstrdup(name);
443 	ct->type = CONF_HANDLER;
444 	ct->flags = 0;
445 	ct->un.handler = handler;
446 
447 	mowgli_node_add(ct, &ct->node, &confblocks);
448 	conf_need_rehash = true;
449 }
450 
add_subblock_top_conf(const char * name,mowgli_list_t * list)451 void add_subblock_top_conf(const char *name, mowgli_list_t *list)
452 {
453 	struct ConfTable *ct;
454 
455 	return_if_fail(name != NULL);
456 	return_if_fail(list != NULL);
457 
458 	if (find_top_conf(name))
459 	{
460 		slog(LG_DEBUG, "add_top_conf(): duplicate config block '%s'.", name);
461 		return;
462 	}
463 
464 	ct = mowgli_heap_alloc(conftable_heap);
465 
466 	ct->name = sstrdup(name);
467 	ct->type = CONF_SUBBLOCK;
468 	ct->flags = 0;
469 	ct->un.subblock = list;
470 
471 	mowgli_node_add(ct, &ct->node, &confblocks);
472 	conf_need_rehash = true;
473 }
474 
add_conf_item(const char * name,mowgli_list_t * conflist,int (* handler)(mowgli_config_file_entry_t * ce))475 void add_conf_item(const char *name, mowgli_list_t *conflist, int (*handler) (mowgli_config_file_entry_t *ce))
476 {
477 	struct ConfTable *ct;
478 
479 	return_if_fail(name != NULL);
480 	return_if_fail(conflist != NULL);
481 	return_if_fail(handler != NULL);
482 
483 	if (find_conf_item(name, conflist))
484 	{
485 		slog(LG_DEBUG, "add_conf_item(): duplicate item %s", name);
486 		return;
487 	}
488 
489 	ct = mowgli_heap_alloc(conftable_heap);
490 
491 	ct->name = sstrdup(name);
492 	ct->type = CONF_HANDLER;
493 	ct->flags = 0;
494 	ct->un.handler = handler;
495 
496 	mowgli_node_add(ct, &ct->node, conflist);
497 	conf_need_rehash = true;
498 }
499 
add_uint_conf_item(const char * name,mowgli_list_t * conflist,unsigned int flags,unsigned int * var,unsigned int min,unsigned int max,unsigned int def)500 void add_uint_conf_item(const char *name, mowgli_list_t *conflist, unsigned int flags, unsigned int *var, unsigned int min, unsigned int max, unsigned int def)
501 {
502 	struct ConfTable *ct;
503 
504 	if (find_conf_item(name, conflist))
505 	{
506 		slog(LG_DEBUG, "add_uint_conf_item(): duplicate item %s", name);
507 		return;
508 	}
509 
510 	ct = mowgli_heap_alloc(conftable_heap);
511 
512 	ct->name = sstrdup(name);
513 	ct->type = CONF_UINT;
514 	ct->flags = flags;
515 	ct->un.uint_val.var = var;
516 	ct->un.uint_val.min = min;
517 	ct->un.uint_val.max = max;
518 	ct->un.uint_val.def = def;
519 
520 	mowgli_node_add(ct, &ct->node, conflist);
521 	conf_need_rehash = true;
522 }
523 
add_duration_conf_item(const char * name,mowgli_list_t * conflist,unsigned int flags,unsigned int * var,const char * defunit,unsigned int def)524 void add_duration_conf_item(const char *name, mowgli_list_t *conflist, unsigned int flags, unsigned int *var, const char *defunit, unsigned int def)
525 {
526 	struct ConfTable *ct;
527 
528 	if (find_conf_item(name, conflist))
529 	{
530 		slog(LG_DEBUG, "add_duration_conf_item(): duplicate item %s", name);
531 		return;
532 	}
533 
534 	ct = mowgli_heap_alloc(conftable_heap);
535 
536 	ct->name = sstrdup(name);
537 	ct->type = CONF_DURATION;
538 	ct->flags = flags;
539 	ct->un.duration_val.var = var;
540 	ct->un.duration_val.defunit = defunit;
541 	ct->un.duration_val.def = def;
542 
543 	mowgli_node_add(ct, &ct->node, conflist);
544 	conf_need_rehash = true;
545 }
546 
add_dupstr_conf_item(const char * name,mowgli_list_t * conflist,unsigned int flags,char ** var,const char * def)547 void add_dupstr_conf_item(const char *name, mowgli_list_t *conflist, unsigned int flags, char **var, const char *def)
548 {
549 	struct ConfTable *ct;
550 
551 	if (find_conf_item(name, conflist))
552 	{
553 		slog(LG_DEBUG, "add_dupstr_conf_item(): duplicate item %s", name);
554 		return;
555 	}
556 
557 	ct = mowgli_heap_alloc(conftable_heap);
558 
559 	ct->name = sstrdup(name);
560 	ct->type = CONF_DUPSTR;
561 	ct->flags = flags;
562 	ct->un.dupstr_val.var = var;
563 	ct->un.dupstr_val.def = sstrdup(def);
564 
565 	mowgli_node_add(ct, &ct->node, conflist);
566 	conf_need_rehash = true;
567 }
568 
add_bool_conf_item(const char * name,mowgli_list_t * conflist,unsigned int flags,bool * var,bool def)569 void add_bool_conf_item(const char *name, mowgli_list_t *conflist, unsigned int flags, bool *var, bool def)
570 {
571 	struct ConfTable *ct;
572 
573 	if (find_conf_item(name, conflist))
574 	{
575 		slog(LG_DEBUG, "add_bool_conf_item(): duplicate item %s", name);
576 		return;
577 	}
578 
579 	ct = mowgli_heap_alloc(conftable_heap);
580 
581 	ct->name = sstrdup(name);
582 	ct->type = CONF_BOOL;
583 	ct->flags = flags;
584 	ct->un.bool_val.var = var;
585 	ct->un.bool_val.def = def;
586 
587 	mowgli_node_add(ct, &ct->node, conflist);
588 	conf_need_rehash = true;
589 }
590 
del_top_conf(const char * name)591 void del_top_conf(const char *name)
592 {
593 	struct ConfTable *ct;
594 
595 	return_if_fail(name != NULL);
596 
597 	if (!(ct = find_top_conf(name)))
598 	{
599 		slog(LG_DEBUG, "del_top_conf(): cannot delete nonexistant block %s", name);
600 		return;
601 	}
602 
603 	mowgli_node_delete(&ct->node, &confblocks);
604 
605 	free(ct->name);
606 
607 	mowgli_heap_free(conftable_heap, ct);
608 }
609 
del_conf_item(const char * name,mowgli_list_t * conflist)610 void del_conf_item(const char *name, mowgli_list_t *conflist)
611 {
612 	struct ConfTable *ct;
613 
614 	return_if_fail(name != NULL);
615 	return_if_fail(conflist != NULL);
616 
617 	if (!(ct = find_conf_item(name, conflist)))
618 	{
619 		slog(LG_DEBUG, "del_conf_item(): cannot delete nonexistant item %s", name);
620 		return;
621 	}
622 
623 	mowgli_node_delete(&ct->node, conflist);
624 
625 	if (ct->type == CONF_DUPSTR && ct->un.dupstr_val.def)
626 	{
627 		free(ct->un.dupstr_val.def);
628 	}
629 
630 	free(ct->name);
631 
632 	mowgli_heap_free(conftable_heap, ct);
633 }
634 
conftable_get_conf_handler(struct ConfTable * ct)635 conf_handler_t conftable_get_conf_handler(struct ConfTable *ct)
636 {
637 	return_val_if_fail(ct != NULL, NULL);
638 
639 	return ct->type == CONF_HANDLER ? ct->un.handler : NULL;
640 }
641 
642 /* stolen from Sentinel */
token_to_value(struct Token token_table[],const char * token)643 int token_to_value(struct Token token_table[], const char *token)
644 {
645 	int i;
646 
647 	return_val_if_fail(token_table != NULL, TOKEN_ERROR);
648 	return_val_if_fail(token != NULL, TOKEN_ERROR);
649 
650 	for (i = 0; token_table[i].text != NULL; i++)
651 	{
652 		if (strcasecmp(token_table[i].text, token) == 0)
653 		{
654 			return token_table[i].value;
655 		}
656 	}
657 
658 	/* Otherwise... */
659 	return TOKEN_UNMATCHED;
660 }
661 
init_confprocess(void)662 void init_confprocess(void)
663 {
664 	conftable_heap = sharedheap_get(sizeof(struct ConfTable));
665 
666 	if (!conftable_heap)
667 	{
668 		slog(LG_ERROR, "init_confprocess(): block allocator failure.");
669 		exit(EXIT_FAILURE);
670 	}
671 }
672 
673 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
674  * vim:ts=8
675  * vim:sw=8
676  * vim:noexpandtab
677  */
678