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