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