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