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