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