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