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