Import release 0.1.1
[secnet] / conffile.c
CommitLineData
2fe58dfd
SE
1/*
2 * $Log$
3 */
4
5/* conffile.c - process the configuration file */
6
7/* #define DUMP_PARSE_TREE */
8
8689b3a9 9#include "secnet.h"
2fe58dfd 10#include <stdio.h>
2fe58dfd 11#include <string.h>
2fe58dfd
SE
12#include "conffile.h"
13#include "conffile_internal.h"
14#include "util.h"
15#include "modules.h"
16
17static struct cloc no_loc={"none",0};
18
19struct atomlist {
20 struct atomlist *next;
21 atom_t a;
22};
23
24struct entry {
25 struct entry *next;
26 atom_t key;
27 list_t *val;
28};
29
30struct searchlist {
31 struct dict *d;
32 struct searchlist *next;
33};
34
35struct dict {
36 struct dict *parent;
37 struct searchlist *search;
38 struct entry *entries;
39 uint32_t size;
40};
41
42static struct atomlist *atoms=NULL;
43
44static void process_alist(dict_t *context, struct p_node *c);
45static list_t *process_invocation(dict_t *context, struct p_node *i);
46
47static list_t *dict_ilookup_primitive(dict_t *dict, atom_t key)
48{
49 struct entry *i;
50 for (i=dict->entries; i; i=i->next) {
51 if (key==i->key) return i->val;
52 }
53 return NULL;
54}
55
56static list_t *dict_ilookup(dict_t *dict, atom_t key)
57{
58 dict_t *d;
59 list_t *v;
60
61 v=dict_ilookup_primitive(dict, key);
62 if (v) return v;
63 /* Check dictionaries in search path */
64/* XXX */
65 /* Check lexical parents */
66 for (d=dict; d; d=d->parent) {
67 v=dict_ilookup_primitive(d, key);
68 if (v) return v;
69 }
70 return NULL;
71}
72
73static void dict_iadd(dict_t *dict, atom_t key, list_t *val)
74{
75 struct entry *e;
76 /* XXX May want to permit redefinition of keys in the future */
77 /* (although it could be very confusing) */
78 if (dict_ilookup_primitive(dict, key)) {
79 fatal("duplicate key \"%s\" in dictionary\n",key);
80 }
81 e=safe_malloc(sizeof(*e),"dict_add");
82 e->next=dict->entries;
83 e->key=key;
84 e->val=val;
85 dict->entries=e;
86 dict->size++;
87}
88
89/***** Functions beyond this point are private to the config system *****/
90
91static dict_t *dict_new(dict_t *parent)
92{
93 dict_t *d;
94
95 d=safe_malloc(sizeof(*d),"dict_new");
96 d->parent=parent;
97 d->search=NULL;
98 d->entries=NULL;
99 d->size=0;
100 return d;
101}
102
103static struct p_node *node_copy(struct p_node *n)
104{
105 struct p_node *r;
106 r=safe_malloc(sizeof(*r),"node_copy");
107 *r=*n;
108 return r;
109}
110
111static struct p_node *list_reverse(struct p_node *list)
112{
113 struct p_node *rl=NULL, *i, *n;
114
115 for (i=list; i; i=i->r) {
116 n=node_copy(i);
117 n->r=rl;
118 rl=n;
119 }
120 return rl;
121}
122
123/* Since we use left-recursion in the parser for efficiency, sequences
124 end up "backwards" in the parse tree. Rather than have complicated
125 code for, eg. processing assignments in the right order, we reverse
126 these sequences here. */
127static void ptree_mangle(struct p_node *t)
128{
129 if (!t) return;
130 ptree_mangle(t->l);
131 ptree_mangle(t->r);
132 switch (t->type) {
133 case T_DICT:
134 /* ASSERT !t->l || t->l->type==T_ALIST */
135 /* ASSERT !t->r || t->r->type==T_LISTITEM */
136 t->l=list_reverse(t->l);
137 t->r=list_reverse(t->r);
138 break;
139 case T_ASSIGNMENT:
140 /* ASSERT t->l->type==T_KEY */
141 /* ASSERT t->r->type==T_LISTITEM */
142 t->r=list_reverse(t->r);
143 break;
144 case T_ABSPATH:
145 case T_RELPATH:
146 /* ASSERT t->l==NULL */
147 /* ASSERT t->r->type==T_PATHELEM */
148 t->r=list_reverse(t->r);
149 break;
150 case T_EXEC:
151 /* ASSERT t->l */
152 /* ASSERT t->r->type==T_LISTITEM */
153 t->r=list_reverse(t->r);
154 break;
155 }
156}
157
158#ifdef DUMP_PARSE_TREE
159/* Convert a node type to a string, for parse tree dump */
160static string_t ntype(uint32_t type)
161{
162 switch(type) {
163 case T_STRING: return "T_STRING";
164 case T_NUMBER: return "T_NUMBER";
165 case T_KEY: return "T_KEY";
166 case T_ASSIGNMENT: return "T_ASSIGNMENT";
167 case T_LISTITEM: return "T_LISTITEM";
168 case T_EXEC: return "T_EXEC";
169 case T_PATHELEM: return "T_PATHELEM";
170 case T_ABSPATH: return "T_ABSPATH";
171 case T_RELPATH: return "T_RELPATH";
172 case T_DICT: return "T_DICT";
173 case T_ALIST: return "T_ALIST";
174 case T_ERROR: return "T_ERROR";
175 }
176 return "**unknown**";
177}
178
179static void ptree_indent(uint32_t amount)
180{
181 uint32_t i;
182 for (i=0; i<amount; i++) printf(" . ");
183}
184
185static void ptree_dump(struct p_node *n, uint32_t d)
186{
187 if (!n) {
188 printf("NULL\n");
189 return;
190 }
191
192 if (n->type<10) {
193 switch(n->type) {
194 case T_STRING: printf("T_STRING: \"%s\" (%s line %d)\n",
195 n->data.string,n->loc.file,n->loc.line); break;
196 case T_NUMBER: printf("T_NUMBER: %d (%s line %d)\n",
197 n->data.number, n->loc.file,n->loc.line); break;
198 case T_KEY: printf("T_KEY: %s (%s line %d)\n",
199 n->data.key, n->loc.file,n->loc.line); break;
200 default: printf("**unknown primitive type**\n"); break;
201 }
202 } else {
203 printf("%s: (%s line %d)\n",ntype(n->type),n->loc.file,n->loc.line);
204 ptree_indent(d);
205 printf(" |-"); ptree_dump(n->l, d+1);
206 ptree_indent(d);
207 printf(" +-"); ptree_dump(n->r, d+1);
208 }
209}
210
211#endif /* DUMP_PARSE_TREE */
212
213static dict_t *dict_find_root(dict_t *d)
214{
215 dict_t *i;
216
217 for (i=d; i->parent; i=i->parent);
218 return i;
219}
220
221static list_t *dict_lookup_path(dict_t *context, struct p_node *p)
222{
223 dict_t *i;
224 list_t *l;
225
226 /* ASSERT p->type==T_PATHELEM */
227 /* ASSERT p->l->type==T_KEY */
228 l=dict_ilookup(context, p->l->data.key);
229 if (!l) {
230 cfgfatal(p->loc,"conffile","can't find key %s\n",
231 p->l->data.key);
232 }
233
234 while (p->r) {
235 if (l->item->type != t_dict) {
236 cfgfatal(p->loc,"conffile","path element \"%s\" "
237 "is not a dictionary\n",p->l->data.key);
238 }
239 i=l->item->data.dict; /* First thing in list */
240
241 p=p->r;
242 l=dict_ilookup_primitive(i, p->l->data.key);
243 if (!l) {
244 cfgfatal(p->loc,"conffile","can't find key %s\n",
245 p->l->data.key);
246 }
247 }
248 return l;
249}
250
251static item_t *new_item(enum types type, struct cloc loc)
252{
253 item_t *i;
254
255 i=safe_malloc(sizeof(*i),"new_item");
256 i->type=type;
257 i->loc=loc;
258 return i;
259}
260
261static list_t *process_item(dict_t *context, struct p_node *i)
262{
263 item_t *item=NULL;
264
265 switch (i->type) {
266 case T_STRING:
267 item=new_item(t_string, i->loc);
268 item->data.string=i->data.string; /* XXX maybe strcpy */
269 break;
270 case T_NUMBER:
271 item=new_item(t_number, i->loc);
272 item->data.number=i->data.number;
273 break;
274 case T_ABSPATH:
275 context=dict_find_root(context);
276 /* falls through */
277 case T_RELPATH:
278 return dict_lookup_path(context, i->r);
279 /* returns immediately */
280 break;
281 case T_DICT:
282 item=new_item(t_dict, i->loc);
283 item->data.dict=dict_new(context);
284/* XXX dict_add_searchpath(context,process_ilist(context, i->r)); */
285 process_alist(item->data.dict, i->l);
286 break;
287 case T_EXEC:
288 return process_invocation(context, i);
289 /* returns immediately */
290 break;
291 default:
292#ifdef DUMP_PARSE_TREE
293 ptree_dump(i,0);
294 fatal("process_item: invalid node type for a list item (%s)\n",
295 ntype(i->type));
296#else
297 fatal("process_item: list item has invalid node type %d - recompile "
298 "with DUMP_PARSE_TREE defined in conffile.c for more "
299 "detailed debug output",i->type);
300#endif /* DUMP_PARSE_TREE */
301 break;
302 }
303 return list_append(NULL,item);
304}
305
306static list_t *process_ilist(dict_t *context, struct p_node *l)
307{
308 struct p_node *i;
309 list_t *r;
310
311 /* ASSERT l->type==T_LISTITEM */
312
313 r=list_new();
314
315 for (i=l; i; i=i->r) {
316 r=list_append_list(r,process_item(context,i->l));
317 }
318 return r;
319}
320
321static list_t *process_invocation(dict_t *context, struct p_node *i)
322{
323 list_t *cll;
324 item_t *cl;
325 list_t *args;
326
327 /* ASSERT i->type==T_EXEC */
328 /* ASSERT i->r->type==T_LISTITEM */
329 /* XXX it might be null too */
330 cll=process_item(context,i->l);
331 cl=cll->item;
332 if (cl->type != t_closure) {
333 cfgfatal(i->l->loc,"conffile","only closures can be invoked\n");
334 }
335 args=process_ilist(context, i->r);
336 return cl->data.closure->apply(cl->data.closure, i->loc, context, args);
337}
338
339static void process_alist(dict_t *context, struct p_node *c)
340{
341 struct p_node *i;
342 atom_t k;
343 list_t *l;
344
345 if (!c) return; /* NULL assignment lists are valid (empty dictionary) */
346
347 /* ASSERT c->type==T_ALIST */
348 if (c->type!=T_ALIST) {
349 fatal("invalid node type in assignment list\n");
350 }
351
352 for (i=c; i; i=i->r) {
353 /* ASSERT i->l && i->l->type==T_ASSIGNMENT */
354 /* ASSERT i->l->l->type==T_KEY */
355 /* ASSERT i->l->r->type==T_LISTITEM */
356 k=i->l->l->data.key;
357 l=process_ilist(context, i->l->r);
358 dict_iadd(context, k, l);
359 }
360}
361
362/* Take a list of items; turn any dictionaries in this list into lists */
363static list_t *makelist(closure_t *self, struct cloc loc,
364 dict_t *context, list_t *args)
365{
366 list_t *r=NULL, *i;
367 struct entry *e;
368
369 for (i=args; i; i=i->next) {
370 if (i->item->type==t_dict) {
371 /* Convert */
372 for (e=i->item->data.dict->entries; e; e=e->next) {
373 r=list_append_list(r, e->val);
374 }
375 } else {
376 r=list_append_list(r, list_append(NULL,i->item));
377 }
378 }
379 return r;
380}
381
382/* Read a file and turn it into a string */
383static list_t *readfile(closure_t *self, struct cloc loc,
384 dict_t *context, list_t *args)
385{
386 FILE *f;
387 string_t filename;
388 long length;
389 item_t *r;
390
391 r=list_elem(args,0);
392 if (!r) {
393 cfgfatal(loc,"readfile","you must supply a filename\n");
394 }
395 if (r->type!=t_string) {
396 cfgfatal(loc,"readfile","filename must be a string\n");
397 }
398 filename=r->data.string;
399 f=fopen(filename,"rb");
400 if (!f) {
401 fatal_perror("readfile (%s:%d): cannot open file \"%s\"",
402 loc.file,loc.line, filename);
403 }
404 if (fseek(f, 0, SEEK_END)!=0) {
405 fatal_perror("readfile (%s:%d): fseek(SEEK_END)",loc.file,loc.line);
406 }
407 length=ftell(f);
408 if (length<0) {
409 fatal_perror("readfile (%s:%d): ftell()",loc.file,loc.line);
410 }
411 if (fseek(f, 0, SEEK_SET)!=0) {
412 fatal_perror("readfile (%s:%d): fseek(SEEK_SET)",loc.file,loc.line);
413 }
414 r=new_item(t_string,loc);
415 r->data.string=safe_malloc(length+1,"readfile");
416 if (fread(r->data.string,length,1,f)!=1) {
417 fatal("readfile (%s:%d): fread: could not read all of file\n",
418 loc.file,loc.line);
419 }
420 r->data.string[length]=0;
421 if (fclose(f)!=0) {
422 fatal_perror("readfile (%s:%d): fclose",loc.file,loc.line);
423 }
424 return list_append(NULL,r);
425}
426
427static dict_t *process_config(struct p_node *c)
428{
429 dict_t *root;
430 dict_t *context;
431 item_t *i;
432 list_t *false;
433 list_t *true;
434
435 root=dict_new(NULL);
436 context=root;
437
438 /* Predefined keys for boolean values */
439 i=new_item(t_bool,no_loc);
440 i->data.bool=False;
441 false=list_append(NULL,i);
442 i=new_item(t_bool,no_loc);
443 i->data.bool=True;
444 true=list_append(NULL,i);
445 dict_add(root,"false",false);
446 dict_add(root,"False",false);
447 dict_add(root,"FALSE",false);
448 dict_add(root,"no",false);
449 dict_add(root,"No",false);
450 dict_add(root,"NO",false);
451 dict_add(root,"true",true);
452 dict_add(root,"True",true);
453 dict_add(root,"TRUE",true);
454 dict_add(root,"yes",true);
455 dict_add(root,"Yes",true);
456 dict_add(root,"YES",true);
457
458 add_closure(root,"makelist",makelist);
459 add_closure(root,"readfile",readfile);
460
461 init_builtin_modules(root);
462
463 process_alist(context, c);
464
465 return root;
466}
467
468/***** Externally accessible functions */
469
470atom_t intern(string_t s)
471{
472 struct atomlist *i;
473
474 for (i=atoms; i; i=i->next) {
475 if (strcmp(i->a, s)==0) break;
476 }
477
478 if (!i) {
479 /* Did't find it; create a new one */
480 i=safe_malloc(sizeof(*i),"intern: alloc list entry");
481 i->a=safe_strdup(s,"intern: alloc string");
482 i->next=atoms;
483 atoms=i;
484 }
485 return i->a;
486}
487
488list_t *dict_lookup(dict_t *dict, string_t key)
489{
490 return dict_ilookup(dict, intern(key));
491}
492
493list_t *dict_lookup_primitive(dict_t *dict, string_t key)
494{
495 return dict_ilookup_primitive(dict, intern(key));
496}
497
498void dict_add(dict_t *dict, string_t key, list_t *val)
499{
500 dict_iadd(dict,intern(key),val);
501}
502
503string_t *dict_keys(dict_t *dict)
504{
505 atom_t *r, *j;
506 struct entry *i;
507 r=safe_malloc(sizeof(*r)*(dict->size+1),"dict_keys");
508 for (i=dict->entries, j=r; i; i=i->next, j++) {
509 *j=i->key;
510 }
511 *j=NULL;
512 return r;
513}
514
515
516/* List-related functions */
517
518list_t *list_new(void)
519{
520 return NULL;
521}
522
523list_t *list_append_list(list_t *a, list_t *b)
524{
525 list_t *i;
526
527 if (!a) return b;
528 for (i=a; i->next; i=i->next);
529 i->next=b;
530 return a;
531}
532
533list_t *list_append(list_t *list, item_t *item)
534{
535 list_t *l;
536
537 l=safe_malloc(sizeof(*l),"list_append");
538 l->item=item;
539 l->next=NULL;
540
541 return list_append_list(list,l);
542}
543
544item_t *list_elem(list_t *l, uint32_t index)
545{
546 if (!l) return NULL;
547 if (index==0) return l->item;
548 return list_elem(l->next, index-1);
549}
550
551list_t *new_closure(closure_t *cl)
552{
553 item_t *i;
554
555 i=new_item(t_closure,no_loc);
556 i->data.closure=cl;
557 return list_append(NULL,i);
558}
559
560void add_closure(dict_t *dict, string_t name, apply_fn apply)
561{
562 closure_t *c;
563 c=safe_malloc(sizeof(*c),"add_closure");
564 c->description=name;
565 c->apply=apply;
566 c->interface=NULL;
567
568 dict_add(dict,name,new_closure(c));
569}
570
571void *find_cl_if(dict_t *dict, string_t name, uint32_t type,
572 bool_t fail_if_invalid, string_t desc, struct cloc loc)
573{
574 list_t *l;
575 item_t *i;
576 closure_t *cl;
577
578 l=dict_lookup(dict,name);
579 if (!l) {
580 if (!fail_if_invalid) return NULL;
581 cfgfatal(loc,desc,"closure \"%s\" not found\n",name);
582 }
583 i=list_elem(l,0);
584 if (i->type!=t_closure) {
585 if (!fail_if_invalid) return NULL;
586 cfgfatal(loc,desc,"\"%s\" must be a closure\n",name);
587 }
588 cl=i->data.closure;
589 if (cl->type!=type) {
590 if (!fail_if_invalid) return NULL;
591 cfgfatal(loc,desc,"\"%s\" is the wrong type of closure\n",name);
592 }
593 return cl->interface;
594}
595
596/* Convenience functions for modules reading configuration dictionaries */
597item_t *dict_find_item(dict_t *dict, string_t key, bool_t required,
598 string_t desc, struct cloc loc)
599{
600 list_t *l;
601 item_t *i;
602
603 l=dict_lookup(dict,key);
604 if (!l) {
605 if (!required) return NULL;
606 cfgfatal(loc,desc,"required parameter \"%s\" not found\n",key);
607 }
608 i=list_elem(l,0);
609 return i;
610}
611
612string_t dict_read_string(dict_t *dict, string_t key, bool_t required,
613 string_t desc, struct cloc loc)
614{
615 item_t *i;
616 string_t r;
617
618 i=dict_find_item(dict,key,required,desc,loc);
619 if (!i) return NULL;
620 if (i->type!=t_string) {
621 cfgfatal(loc,desc,"\"%s\" must be a string\n",key);
622 }
623 r=i->data.string;
624 return r;
625}
626
627uint32_t dict_read_number(dict_t *dict, string_t key, bool_t required,
628 string_t desc, struct cloc loc, uint32_t def)
629{
630 item_t *i;
631 uint32_t r;
632
633 i=dict_find_item(dict,key,required,desc,loc);
634 if (!i) return def;
635 if (i->type!=t_number) {
636 cfgfatal(loc,desc,"\"%s\" must be a number\n",key);
637 }
638 r=i->data.number;
639 return r;
640}
641
642bool_t dict_read_bool(dict_t *dict, string_t key, bool_t required,
643 string_t desc, struct cloc loc, bool_t def)
644{
645 item_t *i;
646 bool_t r;
647
648 i=dict_find_item(dict,key,required,desc,loc);
649 if (!i) return def;
650 if (i->type!=t_bool) {
651 cfgfatal(loc,desc,"\"%s\" must be a boolean\n",key);
652 }
653 r=i->data.bool;
654 return r;
655}
656
657static struct subnet string_to_subnet(item_t *i, string_t desc)
658{
659 struct subnet s;
660 uint32_t a, b, c, d, n;
661 uint32_t match;
662
663 /* i is not guaranteed to be a string */
664 if (i->type!=t_string) {
665 cfgfatal(i->loc,desc,"expecting a string (subnet specification)\n");
666 }
667
668 /* We expect strings of the form "a.b.c.d[/n]", i.e. the dots are
669 NOT optional. The subnet mask is optional; if missing it is assumed
670 to be /32. */
671 match=sscanf(i->data.string,"%u.%u.%u.%u/%u", &a, &b, &c, &d, &n);
672 if (match<4) {
673 cfgfatal(i->loc,desc,"\"%s\" is not a valid "
674 "subnet specification\n",i->data.string);
675 }
676 if (match<5) {
677 n=32;
678 }
679 if (a>255 || b>255 || c>255 || d>255 || n>32) {
680 cfgfatal(i->loc,desc,"\"%s\": range error\n",i->data.string);
681 }
682 s.prefix=(a<<24)|(b<<16)|(c<<8)|(d);
683 s.mask=(~0UL << (32-n));
70dc107b 684 s.len=n;
2fe58dfd
SE
685 if (s.prefix & ~s.mask) {
686 cfgfatal(i->loc,desc,"\"%s\": prefix not fully contained "
687 "in mask\n",i->data.string);
688 }
689 return s;
690}
691
692uint32_t string_to_ipaddr(item_t *i, string_t desc)
693{
694 uint32_t a, b, c, d;
695 uint32_t match;
696
697 /* i is not guaranteed to be a string */
698 if (i->type!=t_string) {
699 cfgfatal(i->loc,desc,"expecting a string (IP address)\n");
700 }
701
702 match=sscanf(i->data.string,"%u.%u.%u.%u", &a, &b, &c, &d);
703 if (match<4) {
704 cfgfatal(i->loc,desc,"\"%s\" is not a valid "
705 "IP address\n",i->data.string);
706 }
707 if (a>255 || b>255 || c>255 || d>255) {
708 cfgfatal(i->loc,desc,"\"%s\": range error\n",i->data.string);
709 }
710 return (a<<24)|(b<<16)|(c<<8)|(d);
711}
712
713void dict_read_subnet_list(dict_t *dict, string_t key, bool_t required,
714 string_t desc, struct cloc loc,
715 struct subnet_list *sl)
716{
717 list_t *l, *li;
718 item_t *i;
719 uint32_t e=0;
720
721 sl->entries=0;
722 sl->list=NULL;
723 l=dict_lookup(dict, key);
724 if (!l) {
725 if (!required) return;
726 cfgfatal(loc,desc,"required parameter \"%s\" not found\n",key);
727 }
728 /* Count the items in the list */
729 for (li=l; li; li=li->next) e++;
730 if (e==0) return;
731 sl->entries=e;
732 sl->list=safe_malloc(sizeof(struct subnet)*e, "dict_read_subnet_list");
733 e=0;
734 /* Fill in the list */
735 for (li=l; li; li=li->next) {
736 i=li->item;
737 if (i->type!=t_string) {
738 cfgfatal(loc,desc,"parameter \"%s\": all elements must "
739 "be strings\n",key);
740 }
741 sl->list[e++]=string_to_subnet(i,desc);
742 }
743}
744
745dict_t *read_conffile(char *name)
746{
747 FILE *conffile;
748 struct p_node *config;
749
750 if (strcmp(name,"-")==0) {
751 conffile=stdin;
752 } else {
753 conffile=fopen(name,"r");
754 if (!conffile)
755 fatal_perror("Cannot open configuration file \"%s\"",name);
756 }
757 config_lineno=1;
758 config_file=name;
759 config=parse_conffile(conffile);
760 fclose(conffile);
761
762#ifdef DUMP_PARSE_TREE
763 printf("*** config file parse tree BEFORE MANGLE\n");
764 ptree_dump(config,0);
765#endif /* DUMP_PARSE_TREE */
766 /* The root of the configuration is a T_ALIST, which needs reversing
767 before we mangle because it isn't the child of a T_DICT. */
768 config=list_reverse(config);
769 ptree_mangle(config);
770#ifdef DUMP_PARSE_TREE
771 printf("\n\n*** config file parse tree AFTER MANGLE\n");
772 ptree_dump(config,0);
773#endif /* DUMP_PARSE_TREE */
774 return process_config(config);
775}