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