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