chiark / gitweb /
Import release 0.1.1
[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 "secnet.h"
10 #include <stdio.h>
11 #include <string.h>
12 #include "conffile.h"
13 #include "conffile_internal.h"
14 #include "util.h"
15 #include "modules.h"
16
17 static struct cloc no_loc={"none",0};
18
19 struct atomlist {
20     struct atomlist *next;
21     atom_t a;
22 };
23
24 struct entry {
25     struct entry *next;
26     atom_t key;
27     list_t *val;
28 };
29
30 struct searchlist {
31     struct dict *d;
32     struct searchlist *next;
33 };
34
35 struct dict {
36     struct dict *parent;
37     struct searchlist *search;
38     struct entry *entries;
39     uint32_t size;
40 };
41
42 static struct atomlist *atoms=NULL;
43
44 static void process_alist(dict_t *context, struct p_node *c);
45 static list_t *process_invocation(dict_t *context, struct p_node *i);
46
47 static 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
56 static 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
73 static 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
91 static 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
103 static 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
111 static 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. */
127 static 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 */
160 static 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
179 static void ptree_indent(uint32_t amount)
180 {
181     uint32_t i;
182     for (i=0; i<amount; i++) printf("  . ");
183 }
184
185 static 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
213 static 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
221 static 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
251 static 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
261 static 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
306 static 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         
321 static 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
339 static 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 */
363 static 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 */
383 static 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     
427 static 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
470 atom_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
488 list_t *dict_lookup(dict_t *dict, string_t key)
489 {
490     return dict_ilookup(dict, intern(key));
491 }
492
493 list_t *dict_lookup_primitive(dict_t *dict, string_t key)
494 {
495     return dict_ilookup_primitive(dict, intern(key));
496 }
497
498 void dict_add(dict_t *dict, string_t key, list_t *val)
499 {
500     dict_iadd(dict,intern(key),val);
501 }
502
503 string_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
518 list_t *list_new(void)
519 {
520     return NULL;
521 }
522
523 list_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
533 list_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
544 item_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
551 list_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
560 void 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
571 void *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 */
597 item_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
612 string_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
627 uint32_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
642 bool_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
657 static 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));
684     s.len=n;
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
692 uint32_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
713 void 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
745 dict_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 }