chiark / gitweb /
Remove redundant debugging code.
[fwd] / conf.c
1 /* -*-c-*-
2  *
3  * $Id: conf.c,v 1.4 1999/10/15 21:12:36 mdw Exp $
4  *
5  * Configuration parsing
6  *
7  * (c) 1999 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the `fw' port forwarder.
13  *
14  * `fw' is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * `fw' is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with `fw'; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: conf.c,v $
32  * Revision 1.4  1999/10/15 21:12:36  mdw
33  * Remove redundant debugging code.
34  *
35  * Revision 1.3  1999/08/19 18:32:48  mdw
36  * Improve lexical analysis.  In particular, `chmod' patterns don't have to
37  * be quoted any more.
38  *
39  * Revision 1.2  1999/07/26 23:28:39  mdw
40  * Major reconstruction work for new design.
41  *
42  * Revision 1.1.1.1  1999/07/01 08:56:23  mdw
43  * Initial revision.
44  *
45  */
46
47 /*----- Header files ------------------------------------------------------*/
48
49 #include "config.h"
50
51 #include <ctype.h>
52 #include <errno.h>
53 #include <stdarg.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57
58 #include <mLib/dstr.h>
59 #include <mLib/quis.h>
60 #include <mLib/report.h>
61
62 #include "conf.h"
63 #include "scan.h"
64 #include "source.h"
65 #include "target.h"
66
67 #include "exec.h"
68 #include "file.h"
69 #include "socket.h"
70
71 /*----- Source and target tables ------------------------------------------*/
72
73 static source_ops *sources[] = { &xsource_ops, &fsource_ops, &ssource_ops };
74 static target_ops *targets[] = { &xtarget_ops, &ftarget_ops, &starget_ops };
75
76 static const char *notword = 0;
77 static const char *notdelim = 0;
78
79 /*----- Main code ---------------------------------------------------------*/
80
81 /* --- @undelim@ --- *
82  *
83  * Arguments:   @const char *d, dd@ = pointer to characters to escape
84  *
85  * Returns:     ---
86  *
87  * Use:         Modifies the tokenizer.  Characters in the first list will
88  *              always be considered to begin a word.  Characters in the
89  *              second list will always be allowed to continue a word.
90  */
91
92 void undelim(const char *d, const char *dd) { notword = d; notdelim = dd; }
93
94 /* --- @token@ --- *
95  *
96  * Arguments:   @scanner *sc@ = pointer to scanner definition
97  *
98  * Returns:     Type of token scanned.
99  *
100  * Use:         Reads the next token from the character scanner.
101  */
102
103 int token(scanner *sc)
104 {
105 #define SELFDELIM \
106   '{': case '}': case '/': case ',': \
107   case '=': case ':': case ';': \
108   case '.': case '[': case ']'
109
110   int ch;
111
112   DRESET(&sc->d);
113
114   /* --- Main tokenization --- */
115
116   for (;;) {
117     ch = scan(sc);
118
119     /* --- Deal with pushed-back tokens --- */
120
121     if (sc->head->tok) {
122       dstr_puts(&sc->d, sc->head->tok);
123       free(sc->head->tok);
124       sc->head->tok = 0;
125       sc->t = sc->head->t;
126       goto done;
127     }
128
129     else if (isspace((unsigned char)ch))
130       ;
131     else switch (ch) {
132
133       /* --- End of file --- */
134
135       case EOF:
136         sc->t = CTOK_EOF;
137         goto done;
138
139       /* --- Comment character --- */
140
141       case '#':
142         do ch = scan(sc); while (ch != EOF && ch != '\n');
143         break;
144
145       /* --- Various self-delimiting characters --- */
146
147       case SELFDELIM:
148         if (!notword || strchr(notword, ch) == 0) {
149           dstr_putc(&sc->d, ch);
150           dstr_putz(&sc->d);
151           sc->t = ch;
152           goto done;
153         }
154
155       /* --- Bare words --- *
156        *
157        * These aren't as bare any more.  You can now backslash-escape
158        * individual characters, and enclose sections in double-quotes.
159        */
160
161       default: {
162         int q = 0;
163
164         for (;;) {
165           switch (ch) {
166             case EOF:
167               goto word;
168             case '\\':
169               ch = scan(sc);
170               if (ch == EOF)
171                 goto word;
172               DPUTC(&sc->d, ch);
173               break;
174             case '\"':
175               q = !q;
176               break;
177             case SELFDELIM:
178               if (q || (notdelim && strchr(notdelim, ch)))
179                 goto insert;
180               goto word;
181             default:
182               if (!q && isspace((unsigned char)(ch)))
183                 goto word;
184             insert:
185               DPUTC(&sc->d, ch);
186               break;
187           }
188           ch = scan(sc);
189         }
190       word:
191         unscan(sc, ch);
192         DPUTZ(&sc->d);
193         sc->t = CTOK_WORD;
194         goto done;
195       }
196     }
197   }
198
199 done:
200   return (sc->t);
201 }
202
203 /* --- @pushback@ --- *
204  *
205  * Arguments:   @scanner *sc@ = pointer to scanner definition
206  *
207  * Returns:     ---
208  *
209  * Use:         Pushes the current token back.  This is normally a precursor
210  *              to pushing a new scanner source.
211  */
212
213 static void pushback(scanner *sc)
214 {
215   sc->head->tok = xstrdup(sc->d.buf);
216   sc->head->t = sc->t;
217 }
218
219 /* --- @error@ --- *
220  *
221  * Arguments:   @scanner *sc@ = pointer to scanner definition
222  *              @const char *msg@ = message skeleton string
223  *              @...@ = extra arguments for the skeleton
224  *
225  * Returns:     Doesn't
226  *
227  * Use:         Reports an error at the current scanner location.
228  */
229
230 void error(scanner *sc, const char *msg, ...)
231 {
232   va_list ap;
233   va_start(ap, msg);
234   fprintf(stderr, "%s: %s:%i: ", QUIS, sc->head->src, sc->head->line);
235   vfprintf(stderr, msg, ap);
236   fputc('\n', stderr);
237   exit(1);
238 }
239
240 /* --- @conf_enum@ --- *
241  *
242  * Arguments:   @scanner *sc@ = pointer to a scanner object
243  *              @const char *list@ = comma-separated things to allow
244  *              @unsigned f@ = flags for the search
245  *              @const char *err@ = error message if not found
246  *
247  * Returns:     Index into list, zero-based, or @-1@.
248  *
249  * Use:         Checks whether the current token is a string which matches
250  *              one of the comma-separated items given.  If not, an error is
251  *              reported; otherwise the index of the matched item is
252  *              returned.
253  */
254
255 int conf_enum(scanner *sc, const char *list, unsigned f, const char *err)
256 {
257   const char *p, *q;
258   int chosen = -1;
259   int ok;
260   int index;
261
262   /* --- Make sure it's a string --- */
263
264   if (sc->t != CTOK_WORD)
265     error(sc, "parse error, expected %s", err);
266
267   /* --- Grind through the list --- */
268
269   q = sc->d.buf;
270   ok = 1;
271   index = 0;
272   p = list;
273   for (;;) {
274     switch (*p) {
275       case 0:
276         if (ok && !*q) {
277           token(sc);
278           return (index);
279         } else if (chosen != -1) {
280           token(sc);
281           return (chosen);
282         }
283         else if (f & ENUM_NONE)
284           return (-1);
285         else
286           error(sc, "unknown %s `%s'", err, sc->d.buf);
287         break;
288       case ',':
289         if (ok && !*q) {
290           token(sc);
291           return (index);
292         }
293         ok = 1;
294         q = sc->d.buf;
295         index++;
296         break;
297       default:
298         if (!ok)
299           break;
300         if ((f & ENUM_ABBREV) && !*q) {
301           if (chosen != -1)
302             error(sc, "ambiguous %s `%s'", err, sc->d.buf);
303           chosen = index;
304           ok = 0;
305         }
306         if (*p == *q)
307           q++;
308         else
309           ok = 0;
310         break;
311     }
312     p++;
313   }
314 }
315
316 /* --- @conf_prefix@ --- *
317  *
318  * Arguments:   @scanner *sc@ = pointer to a scanner object
319  *              @const char *p@ = pointer to prefix string to check
320  *
321  * Returns:     Nonzero if the prefix matches.
322  *
323  * Use:         If the current token is a word matching the given prefix
324  *              string, then it and an optional `.' character are removed and
325  *              a nonzero result is returned.  Otherwise the current token is
326  *              left as it is, and zero is returned.
327  *
328  *              Typical options parsing code would remove an expected prefix,
329  *              scan an option anyway (since qualifying prefixes are
330  *              optional) and if a match is found, claim the option.  If no
331  *              match is found, and a prefix was stripped, then an error
332  *              should be reported.
333  */
334
335 int conf_prefix(scanner *sc, const char *p)
336 {
337   if (sc->t == CTOK_WORD && strcmp(p, sc->d.buf) == 0) {
338     token(sc);
339     if (sc->t == '.')
340       token(sc);
341     return (1);
342   }
343   return (0);
344 }
345
346 /* --- @conf_name@ --- *
347  *
348  * Arguments:   @scanner *sc@ = pointer to scanner
349  *              @char delim@ = delimiter character to look for
350  *              @dstr *d@ = pointer to dynamic string for output
351  *
352  * Returns:     ---
353  *
354  * Use:         Reads in a compound name consisting of words separated by
355  *              delimiters.  Leading and trailing delimiters are permitted,
356  *              although they'll probably cause confusion if used.  The name
357  *              may be enclosed in square brackets if that helps at all.
358  *
359  *              Examples of compound names are filenames (delimited by `/')
360  *              and IP addresses (delimited by `.').
361  */
362
363 void conf_name(scanner *sc, char delim, dstr *d)
364 {
365   unsigned f = 0;
366   enum {
367     f_ok = 1,
368     f_bra = 2
369   };
370
371   /* --- Read an optional opening bracket --- */
372
373   if (sc->t == '[') {
374     token(sc);
375     f |= f_bra;
376   }
377
378   /* --- Do the main reading sequence --- */
379
380   do {
381     if (sc->t == delim) {
382       DPUTC(d, delim);
383       f |= f_ok;
384       token(sc);
385     }
386     if (sc->t == CTOK_WORD) {
387       DPUTD(d, &sc->d);
388       f |= f_ok;
389       token(sc);
390     }
391   } while (sc->t == delim);
392
393   /* --- Check that the string was OK --- */
394
395   if (!(f & f_ok))
396     error(sc, "parse error, name expected");
397
398   /* --- Read a closing bracket --- */
399
400   if (f & f_bra) {
401     if (sc->t == ']')
402       token(sc);
403     else
404       error(sc, "parse error, missing `]'");
405   }
406   DPUTZ(d);
407 }
408
409 /* --- @conf_parse@ --- *
410  *
411  * Arguments:   @scanner *sc@ = pointer to scanner definition
412  *
413  * Returns:     ---
414  *
415  * Use:         Parses a configuration file from the scanner.
416  */
417
418 void conf_parse(scanner *sc)
419 {
420   token(sc);
421
422   for (;;) {
423     if (sc->t == CTOK_EOF)
424       break;
425     if (sc->t != CTOK_WORD)
426       error(sc, "parse error, keyword expected");
427
428     /* --- Handle a forwarding request --- */
429
430     if (strcmp(sc->d.buf, "forward") == 0 ||
431         strcmp(sc->d.buf, "fw") == 0 ||
432         strcmp(sc->d.buf, "from") == 0) {
433       source *s;
434       target *t;
435
436       token(sc);
437
438       /* --- Read a source description --- */
439
440       {
441         source_ops **sops;
442
443         /* --- Try to find a source type which understands --- */
444
445         s = 0;
446         for (sops = sources; *sops; sops++) {
447           if ((s = (*sops)->read(sc)) != 0)
448             goto found_source;
449         }
450         error(sc, "unknown source name `%s'", sc->d.buf);
451
452         /* --- Read any source-specific options --- */
453
454       found_source:
455         if (sc->t == '{') {
456           token(sc);
457           while (sc->t == CTOK_WORD) {
458             if (!s->ops->option || !s->ops->option(s, sc)) {
459               error(sc, "unknown %s source option `%s'",
460                     s->ops->name, sc->d.buf);
461             }
462             if (sc->t == ';')
463               token(sc);
464           }
465           if (sc->t != '}')
466             error(sc, "parse error, missing `}'");
467           token(sc);
468         }
469       }
470
471       /* --- Read a destination description --- */
472
473       if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
474                                  strcmp(sc->d.buf, "->") == 0))
475         token(sc);
476
477       {
478         target_ops **tops;
479
480         /* --- Try to find a target which understands --- */
481
482         t = 0;
483         for (tops = targets; *tops; tops++) {
484           if ((t = (*tops)->read(sc)) != 0)
485             goto found_target;
486         }
487         error(sc, "unknown target name `%s'", sc->d.buf);
488
489         /* --- Read any target-specific options --- */
490
491       found_target:
492         if (sc->t == '{') {
493           token(sc);
494           while (sc->t == CTOK_WORD) {
495             if (!t->ops->option || !t->ops->option(t, sc)) {
496               error(sc, "unknown %s target option `%s'",
497                     t->ops->name, sc->d.buf);
498             }
499             if (sc->t == ';')
500               token(sc);
501           }
502           if (sc->t != '}')
503             error(sc, "parse error, `}' expected");
504           token(sc);
505         }
506       }
507
508       /* --- Combine the source and target --- */
509
510       s->ops->attach(s, sc, t);
511     }
512
513     /* --- Include configuration from a file --- *
514      *
515      * Slightly tricky.  Scan the optional semicolon from the including
516      * stream, not the included one.
517      */
518
519     else if (strcmp(sc->d.buf, "include") == 0) {
520       FILE *fp;
521       dstr d = DSTR_INIT;
522
523       token(sc);
524       conf_name(sc, '/', &d);
525       if ((fp = fopen(d.buf, "r")) == 0)
526         error(sc, "can't include `%s': %s", d.buf, strerror(errno));
527       if (sc->t == ';')
528         token(sc);
529       pushback(sc);
530       scan_push(sc, scan_file(fp, xstrdup(d.buf), SCF_FREENAME));
531       token(sc);
532       dstr_destroy(&d);
533       continue;                         /* Don't parse a trailing `;' */
534     }
535
536     /* --- Other configuration is handled elsewhere --- */
537
538     else {
539
540       /* --- First try among the sources --- */
541
542       {
543         source_ops **sops;
544
545         for (sops = sources; *sops; sops++) {
546           if ((*sops)->option && (*sops)->option(0, sc))
547             goto found_option;
548         }
549       }
550
551       /* --- Then try among the targets --- */
552
553       {
554         target_ops **tops;
555
556         for (tops = targets; *tops; tops++) {
557           if ((*tops)->option && (*tops)->option(0, sc))
558             goto found_option;
559         }
560       }
561
562       /* --- Nobody wants the option --- */
563
564       error(sc, "unknown global option or prefix `%s'", sc->d.buf);
565
566     found_option:;
567     }
568
569     if (sc->t == ';')
570       token(sc);
571   }
572 }
573
574 /*----- That's all, folks -------------------------------------------------*/