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