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