1 /*
2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 * PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /*! \file */
18
19 #include <limits.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <isc/lex.h>
24 #include <isc/log.h>
25 #include <isc/symtab.h>
26 #include <isc/util.h>
27
28 #include <isccfg/cfg.h>
29 #include <isccfg/grammar.h>
30
31 /*!
32 * Pass one of these flags to cfg_parser_error() to include the
33 * token text in log message.
34 */
35 #define CFG_LOG_NEAR 0x00000001 /*%< Say "near <token>" */
36 #define CFG_LOG_BEFORE 0x00000002 /*%< Say "before <token>" */
37 #define CFG_LOG_NOPREP 0x00000004 /*%< Say just "<token>" */
38
39 isc_logcategory_t cfg_category = { "config", 0 };
40 isc_logmodule_t cfg_module = { "isccfg/parser", 0 };
41
42 /* Shorthand */
43 #define CAT &cfg_category
44 #define MOD &cfg_module
45
46 #define MAP_SYM 1 /* Unique type for isc_symtab */
47
48 #define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base)
49
50 #define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE)
51
52 /* Check a return value. */
53 #define CHECK(op) \
54 do { result = (op); \
55 if (result != ISC_R_SUCCESS) goto cleanup; \
56 } while (0)
57
58 /* Clean up a configuration object if non-NULL. */
59 #define CLEANUP_OBJ(obj) \
60 do { if ((obj) != NULL) cfg_obj_destroy(pctx, &(obj)); } while (0)
61
62 /* Forward declarations of variables */
63 cfg_rep_t cfg_rep_string;
64 cfg_rep_t cfg_rep_list;
65
66 cfg_type_t cfg_type_qstring;
67 cfg_type_t cfg_type_sstring;
68 cfg_type_t cfg_type_token;
69 cfg_type_t cfg_type_unsupported;
70
71 /*
72 * Forward declarations of static functions.
73 */
74
75 static isc_result_t
76 cfg_gettoken(cfg_parser_t *pctx, int options);
77
78 static isc_result_t
79 cfg_peektoken(cfg_parser_t *pctx, int options);
80
81 static void
82 cfg_ungettoken(cfg_parser_t *pctx);
83
84 static isc_result_t
85 cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
86
87 static isc_result_t
88 cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
89
90 static isc_result_t
91 cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
92
93 static isc_result_t
94 cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
95
96 static isc_result_t
97 cfg_parse_special(cfg_parser_t *pctx, int special);
98 /*%< Parse a required special character 'special'. */
99
100 static isc_result_t
101 cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
102
103 static isc_result_t
104 cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
105 cfg_listelt_t **ret);
106
107 static isc_result_t
108 cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
109
110 static isc_result_t
111 cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
112
113 static void
114 cfg_parser_error(cfg_parser_t *pctx, unsigned int flags,
115 const char *fmt, ...) __attribute__((__format__(__printf__, 3, 4)));
116
117 static void
118 free_list(cfg_parser_t *pctx, cfg_obj_t *obj);
119
120 static isc_result_t
121 create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp);
122
123 static isc_result_t
124 create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
125 cfg_obj_t **ret);
126
127 static void
128 free_string(cfg_parser_t *pctx, cfg_obj_t *obj);
129
130 static isc_result_t
131 create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
132
133 static void
134 free_map(cfg_parser_t *pctx, cfg_obj_t *obj);
135
136 static isc_result_t
137 parse_symtab_elt(cfg_parser_t *pctx, const char *name,
138 cfg_type_t *elttype, isc_symtab_t *symtab);
139
140 static isc_result_t
141 cfg_getstringtoken(cfg_parser_t *pctx);
142
143 static void
144 parser_complain(cfg_parser_t *pctx, int is_warning,
145 unsigned int flags, const char *format, va_list args);
146
147 /*
148 * Data representations. These correspond to members of the
149 * "value" union in struct cfg_obj (except "void", which does
150 * not need a union member).
151 */
152
153 cfg_rep_t cfg_rep_string = { "string", free_string };
154 cfg_rep_t cfg_rep_map = { "map", free_map };
155 cfg_rep_t cfg_rep_list = { "list", free_list };
156
157 /*
158 * Configuration type definitions.
159 */
160
161 /* Functions. */
162
163 static isc_result_t
cfg_parse_obj(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)164 cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
165 isc_result_t result;
166
167 REQUIRE(pctx != NULL);
168 REQUIRE(type != NULL);
169 REQUIRE(ret != NULL && *ret == NULL);
170
171 result = type->parse(pctx, type, ret);
172 if (result != ISC_R_SUCCESS)
173 return (result);
174 ENSURE(*ret != NULL);
175 return (ISC_R_SUCCESS);
176 }
177
178 static isc_result_t
cfg_parse_special(cfg_parser_t * pctx,int special)179 cfg_parse_special(cfg_parser_t *pctx, int special) {
180 isc_result_t result;
181
182 REQUIRE(pctx != NULL);
183
184 CHECK(cfg_gettoken(pctx, 0));
185 if (pctx->token.type == isc_tokentype_special &&
186 pctx->token.value.as_char == special)
187 return (ISC_R_SUCCESS);
188
189 cfg_parser_error(pctx, CFG_LOG_NEAR, "'%c' expected", special);
190 return (ISC_R_UNEXPECTEDTOKEN);
191 cleanup:
192 return (result);
193 }
194
195 /*
196 * Parse a required semicolon. If it is not there, log
197 * an error and increment the error count but continue
198 * parsing. Since the next token is pushed back,
199 * care must be taken to make sure it is eventually
200 * consumed or an infinite loop may result.
201 */
202 static isc_result_t
parse_semicolon(cfg_parser_t * pctx)203 parse_semicolon(cfg_parser_t *pctx) {
204 isc_result_t result;
205
206 CHECK(cfg_gettoken(pctx, 0));
207 if (pctx->token.type == isc_tokentype_special &&
208 pctx->token.value.as_char == ';')
209 return (ISC_R_SUCCESS);
210
211 cfg_parser_error(pctx, CFG_LOG_BEFORE, "missing ';'");
212 cfg_ungettoken(pctx);
213 cleanup:
214 return (result);
215 }
216
217 /*
218 * Parse EOF, logging and returning an error if not there.
219 */
220 static isc_result_t
parse_eof(cfg_parser_t * pctx)221 parse_eof(cfg_parser_t *pctx) {
222 isc_result_t result;
223
224 CHECK(cfg_gettoken(pctx, 0));
225
226 if (pctx->token.type == isc_tokentype_eof)
227 return (ISC_R_SUCCESS);
228
229 cfg_parser_error(pctx, CFG_LOG_NEAR, "syntax error");
230 return (ISC_R_UNEXPECTEDTOKEN);
231 cleanup:
232 return (result);
233 }
234
235 /* A list of files, used internally for pctx->files. */
236
237 static cfg_type_t cfg_type_filelist = {
238 "filelist", NULL, &cfg_rep_list,
239 &cfg_type_qstring
240 };
241
242 isc_result_t
cfg_parser_create(isc_log_t * lctx,cfg_parser_t ** ret)243 cfg_parser_create(isc_log_t *lctx, cfg_parser_t **ret) {
244 isc_result_t result;
245 cfg_parser_t *pctx;
246 isc_lexspecials_t specials;
247
248 REQUIRE(ret != NULL && *ret == NULL);
249
250 pctx = malloc(sizeof(*pctx));
251 if (pctx == NULL)
252 return (ISC_R_NOMEMORY);
253
254 pctx->lctx = lctx;
255 pctx->lexer = NULL;
256 pctx->seen_eof = 0;
257 pctx->ungotten = 0;
258 pctx->errors = 0;
259 pctx->open_files = NULL;
260 pctx->closed_files = NULL;
261 pctx->line = 0;
262 pctx->token.type = isc_tokentype_unknown;
263 pctx->flags = 0;
264
265 memset(specials, 0, sizeof(specials));
266 specials['{'] = 1;
267 specials['}'] = 1;
268 specials[';'] = 1;
269 specials['/'] = 1;
270 specials['"'] = 1;
271 specials['!'] = 1;
272
273 CHECK(isc_lex_create(1024, &pctx->lexer));
274
275 isc_lex_setspecials(pctx->lexer, specials);
276 isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C |
277 ISC_LEXCOMMENT_CPLUSPLUS |
278 ISC_LEXCOMMENT_SHELL));
279
280 CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files));
281 CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->closed_files));
282
283 *ret = pctx;
284 return (ISC_R_SUCCESS);
285
286 cleanup:
287 if (pctx->lexer != NULL)
288 isc_lex_destroy(&pctx->lexer);
289 CLEANUP_OBJ(pctx->open_files);
290 CLEANUP_OBJ(pctx->closed_files);
291 free(pctx);
292 return (result);
293 }
294
295 static isc_result_t
parser_openfile(cfg_parser_t * pctx,const char * filename)296 parser_openfile(cfg_parser_t *pctx, const char *filename) {
297 isc_result_t result;
298 cfg_listelt_t *elt = NULL;
299 cfg_obj_t *stringobj = NULL;
300
301 result = isc_lex_openfile(pctx->lexer, filename);
302 if (result != ISC_R_SUCCESS) {
303 cfg_parser_error(pctx, 0, "open: %s: %s",
304 filename, isc_result_totext(result));
305 goto cleanup;
306 }
307
308 CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj));
309 CHECK(create_listelt(pctx, &elt));
310 elt->obj = stringobj;
311 ISC_LIST_APPEND(pctx->open_files->value.list, elt, link);
312
313 return (ISC_R_SUCCESS);
314 cleanup:
315 CLEANUP_OBJ(stringobj);
316 return (result);
317 }
318
319 /*
320 * Parse a configuration using a pctx where a lexer has already
321 * been set up with a source.
322 */
323 static isc_result_t
parse2(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)324 parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
325 isc_result_t result;
326 cfg_obj_t *obj = NULL;
327
328 result = cfg_parse_obj(pctx, type, &obj);
329
330 if (pctx->errors != 0) {
331 /* Errors have been logged. */
332 if (result == ISC_R_SUCCESS)
333 result = ISC_R_FAILURE;
334 goto cleanup;
335 }
336
337 if (result != ISC_R_SUCCESS) {
338 /* Parsing failed but no errors have been logged. */
339 cfg_parser_error(pctx, 0, "parsing failed: %s",
340 isc_result_totext(result));
341 goto cleanup;
342 }
343
344 CHECK(parse_eof(pctx));
345
346 *ret = obj;
347 return (ISC_R_SUCCESS);
348
349 cleanup:
350 CLEANUP_OBJ(obj);
351 return (result);
352 }
353
354 isc_result_t
cfg_parse_file(cfg_parser_t * pctx,const char * filename,const cfg_type_t * type,cfg_obj_t ** ret)355 cfg_parse_file(cfg_parser_t *pctx, const char *filename,
356 const cfg_type_t *type, cfg_obj_t **ret)
357 {
358 isc_result_t result;
359
360 REQUIRE(pctx != NULL);
361 REQUIRE(filename != NULL);
362 REQUIRE(type != NULL);
363 REQUIRE(ret != NULL && *ret == NULL);
364
365 CHECK(parser_openfile(pctx, filename));
366 CHECK(parse2(pctx, type, ret));
367 cleanup:
368 return (result);
369 }
370
371 void
cfg_parser_destroy(cfg_parser_t ** pctxp)372 cfg_parser_destroy(cfg_parser_t **pctxp) {
373 cfg_parser_t *pctx;
374
375 REQUIRE(pctxp != NULL && *pctxp != NULL);
376
377 pctx = *pctxp;
378 *pctxp = NULL;
379
380 isc_lex_destroy(&pctx->lexer);
381 /*
382 * Cleaning up open_files does not
383 * close the files; that was already done
384 * by closing the lexer.
385 */
386 CLEANUP_OBJ(pctx->open_files);
387 CLEANUP_OBJ(pctx->closed_files);
388 free(pctx);
389 }
390
391 /*
392 * qstring (quoted string), ustring (unquoted string), astring
393 * (any string)
394 */
395
396 /* Create a string object from a null-terminated C string. */
397 static isc_result_t
create_string(cfg_parser_t * pctx,const char * contents,const cfg_type_t * type,cfg_obj_t ** ret)398 create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
399 cfg_obj_t **ret)
400 {
401 isc_result_t result;
402 cfg_obj_t *obj = NULL;
403 int len;
404
405 CHECK(cfg_create_obj(pctx, type, &obj));
406 len = strlen(contents);
407 obj->value.string.length = len;
408 obj->value.string.base = malloc(len + 1);
409 if (obj->value.string.base == NULL) {
410 free(obj);
411 return (ISC_R_NOMEMORY);
412 }
413 memmove(obj->value.string.base, contents, len);
414 obj->value.string.base[len] = '\0';
415
416 *ret = obj;
417 cleanup:
418 return (result);
419 }
420
421 static isc_result_t
cfg_parse_qstring(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)422 cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
423 isc_result_t result;
424
425 REQUIRE(pctx != NULL);
426 REQUIRE(ret != NULL && *ret == NULL);
427
428 UNUSED(type);
429
430 CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
431 if (pctx->token.type != isc_tokentype_qstring) {
432 cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string");
433 return (ISC_R_UNEXPECTEDTOKEN);
434 }
435 return (create_string(pctx, TOKEN_STRING(pctx),
436 &cfg_type_qstring, ret));
437 cleanup:
438 return (result);
439 }
440
441 static isc_result_t
cfg_parse_astring(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)442 cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type,
443 cfg_obj_t **ret)
444 {
445 isc_result_t result;
446
447 REQUIRE(pctx != NULL);
448 REQUIRE(ret != NULL && *ret == NULL);
449
450 UNUSED(type);
451
452 CHECK(cfg_getstringtoken(pctx));
453 return (create_string(pctx,
454 TOKEN_STRING(pctx),
455 &cfg_type_qstring,
456 ret));
457 cleanup:
458 return (result);
459 }
460
461 static isc_result_t
cfg_parse_sstring(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)462 cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type,
463 cfg_obj_t **ret)
464 {
465 isc_result_t result;
466
467 REQUIRE(pctx != NULL);
468 REQUIRE(ret != NULL && *ret == NULL);
469
470 UNUSED(type);
471
472 CHECK(cfg_getstringtoken(pctx));
473 return (create_string(pctx,
474 TOKEN_STRING(pctx),
475 &cfg_type_sstring,
476 ret));
477 cleanup:
478 return (result);
479 }
480
481 static void
free_string(cfg_parser_t * pctx,cfg_obj_t * obj)482 free_string(cfg_parser_t *pctx, cfg_obj_t *obj) {
483 UNUSED(pctx);
484 free(obj->value.string.base);
485 }
486
487 const char *
cfg_obj_asstring(const cfg_obj_t * obj)488 cfg_obj_asstring(const cfg_obj_t *obj) {
489 REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string);
490 return (obj->value.string.base);
491 }
492
493 /* Quoted string only */
494 cfg_type_t cfg_type_qstring = {
495 "quoted_string", cfg_parse_qstring, &cfg_rep_string, NULL
496 };
497
498 /* Any string (quoted or unquoted); printed with quotes */
499 cfg_type_t cfg_type_astring = {
500 "string", cfg_parse_astring, &cfg_rep_string, NULL
501 };
502
503 /*
504 * Any string (quoted or unquoted); printed with quotes.
505 * If CFG_PRINTER_XKEY is set when printing the string will be '?' out.
506 */
507 cfg_type_t cfg_type_sstring = {
508 "string", cfg_parse_sstring, &cfg_rep_string, NULL
509 };
510
511 /*
512 * Booleans
513 */
514
515 /*
516 * Lists.
517 */
518
519 static isc_result_t
cfg_create_list(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** obj)520 cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) {
521 isc_result_t result;
522
523 REQUIRE(pctx != NULL);
524 REQUIRE(type != NULL);
525 REQUIRE(obj != NULL && *obj == NULL);
526
527 CHECK(cfg_create_obj(pctx, type, obj));
528 ISC_LIST_INIT((*obj)->value.list);
529 cleanup:
530 return (result);
531 }
532
533 static isc_result_t
create_listelt(cfg_parser_t * pctx,cfg_listelt_t ** eltp)534 create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) {
535 UNUSED(pctx);
536 cfg_listelt_t *elt;
537
538 elt = malloc(sizeof(*elt));
539 if (elt == NULL)
540 return (ISC_R_NOMEMORY);
541 elt->obj = NULL;
542 ISC_LINK_INIT(elt, link);
543 *eltp = elt;
544 return (ISC_R_SUCCESS);
545 }
546
547 static void
free_list_elt(cfg_parser_t * pctx,cfg_listelt_t * elt)548 free_list_elt(cfg_parser_t *pctx, cfg_listelt_t *elt) {
549 cfg_obj_destroy(pctx, &elt->obj);
550 free(elt);
551 }
552
553 static void
free_list(cfg_parser_t * pctx,cfg_obj_t * obj)554 free_list(cfg_parser_t *pctx, cfg_obj_t *obj) {
555 cfg_listelt_t *elt, *next;
556 for (elt = ISC_LIST_HEAD(obj->value.list);
557 elt != NULL;
558 elt = next)
559 {
560 next = ISC_LIST_NEXT(elt, link);
561 free_list_elt(pctx, elt);
562 }
563 }
564
565 static isc_result_t
cfg_parse_listelt(cfg_parser_t * pctx,const cfg_type_t * elttype,cfg_listelt_t ** ret)566 cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
567 cfg_listelt_t **ret)
568 {
569 isc_result_t result;
570 cfg_listelt_t *elt = NULL;
571 cfg_obj_t *value = NULL;
572
573 REQUIRE(pctx != NULL);
574 REQUIRE(elttype != NULL);
575 REQUIRE(ret != NULL && *ret == NULL);
576
577 CHECK(create_listelt(pctx, &elt));
578
579 result = cfg_parse_obj(pctx, elttype, &value);
580 if (result != ISC_R_SUCCESS)
581 goto cleanup;
582
583 elt->obj = value;
584
585 *ret = elt;
586 return (ISC_R_SUCCESS);
587
588 cleanup:
589 free(elt);
590 return (result);
591 }
592
593 /*
594 * Maps.
595 */
596
597 /*
598 * Parse a map body. That's something like
599 *
600 * "foo 1; bar { glub; }; zap true; zap false;"
601 *
602 * i.e., a sequence of option names followed by values and
603 * terminated by semicolons. Used for the top level of
604 * the named.conf syntax, as well as for the body of the
605 * options, view, zone, and other statements.
606 */
607 isc_result_t
cfg_parse_mapbody(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)608 cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
609 {
610 const cfg_clausedef_t * const *clausesets = type->of;
611 isc_result_t result;
612 const cfg_clausedef_t * const *clauseset;
613 const cfg_clausedef_t *clause;
614 cfg_obj_t *value = NULL;
615 cfg_obj_t *obj = NULL;
616 cfg_obj_t *eltobj = NULL;
617 cfg_obj_t *includename = NULL;
618 isc_symvalue_t symval;
619
620 REQUIRE(pctx != NULL);
621 REQUIRE(type != NULL);
622 REQUIRE(ret != NULL && *ret == NULL);
623
624 CHECK(create_map(pctx, type, &obj));
625
626 obj->value.map.clausesets = clausesets;
627
628 for (;;) {
629 redo:
630 /*
631 * Parse the option name and see if it is known.
632 */
633 CHECK(cfg_gettoken(pctx, 0));
634
635 if (pctx->token.type != isc_tokentype_string) {
636 cfg_ungettoken(pctx);
637 break;
638 }
639
640 /*
641 * We accept "include" statements wherever a map body
642 * clause can occur.
643 */
644 if (strcasecmp(TOKEN_STRING(pctx), "include") == 0) {
645 /*
646 * Turn the file name into a temporary configuration
647 * object just so that it is not overwritten by the
648 * semicolon token.
649 */
650 CHECK(cfg_parse_obj(pctx, &cfg_type_qstring, &includename));
651 CHECK(parse_semicolon(pctx));
652 CHECK(parser_openfile(pctx, includename->
653 value.string.base));
654 cfg_obj_destroy(pctx, &includename);
655 goto redo;
656 }
657
658 clause = NULL;
659 for (clauseset = clausesets; *clauseset != NULL; clauseset++) {
660 for (clause = *clauseset;
661 clause->name != NULL;
662 clause++) {
663 if (strcasecmp(TOKEN_STRING(pctx),
664 clause->name) == 0)
665 goto done;
666 }
667 }
668 done:
669 if (clause == NULL || clause->name == NULL) {
670 cfg_parser_error(pctx, CFG_LOG_NOPREP,
671 "unknown option");
672 /*
673 * Try to recover by parsing this option as an unknown
674 * option and discarding it.
675 */
676 CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported,
677 &eltobj));
678 cfg_obj_destroy(pctx, &eltobj);
679 CHECK(parse_semicolon(pctx));
680 continue;
681 }
682
683 /* Clause is known. */
684
685 /* See if the clause already has a value; if not create one. */
686 result = isc_symtab_lookup(obj->value.map.symtab,
687 clause->name, 0, &symval);
688
689 /* Single-valued clause */
690 if (result == ISC_R_NOTFOUND) {
691 CHECK(parse_symtab_elt(pctx, clause->name,
692 clause->type,
693 obj->value.map.symtab));
694 CHECK(parse_semicolon(pctx));
695 } else if (result == ISC_R_SUCCESS) {
696 cfg_parser_error(pctx, CFG_LOG_NEAR, "'%s' redefined",
697 clause->name);
698 result = ISC_R_EXISTS;
699 goto cleanup;
700 } else {
701 cfg_parser_error(pctx, CFG_LOG_NEAR,
702 "isc_symtab_define() failed");
703 goto cleanup;
704 }
705 }
706
707 *ret = obj;
708 return (ISC_R_SUCCESS);
709
710 cleanup:
711 CLEANUP_OBJ(value);
712 CLEANUP_OBJ(obj);
713 CLEANUP_OBJ(eltobj);
714 CLEANUP_OBJ(includename);
715 return (result);
716 }
717
718 static isc_result_t
parse_symtab_elt(cfg_parser_t * pctx,const char * name,cfg_type_t * elttype,isc_symtab_t * symtab)719 parse_symtab_elt(cfg_parser_t *pctx, const char *name,
720 cfg_type_t *elttype, isc_symtab_t *symtab)
721 {
722 isc_result_t result;
723 cfg_obj_t *obj = NULL;
724 isc_symvalue_t symval;
725
726 CHECK(cfg_parse_obj(pctx, elttype, &obj));
727
728 symval.as_pointer = obj;
729 CHECK(isc_symtab_define(symtab, name,
730 1, symval,
731 isc_symexists_reject));
732 return (ISC_R_SUCCESS);
733
734 cleanup:
735 CLEANUP_OBJ(obj);
736 return (result);
737 }
738
739 /*
740 * Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }"
741 */
742 static isc_result_t
cfg_parse_map(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)743 cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
744 isc_result_t result;
745
746 REQUIRE(pctx != NULL);
747 REQUIRE(type != NULL);
748 REQUIRE(ret != NULL && *ret == NULL);
749
750 CHECK(cfg_parse_special(pctx, '{'));
751 CHECK(cfg_parse_mapbody(pctx, type, ret));
752 CHECK(cfg_parse_special(pctx, '}'));
753 cleanup:
754 return (result);
755 }
756
757 /*
758 * Subroutine for cfg_parse_named_map() and cfg_parse_addressed_map().
759 */
760 static isc_result_t
parse_any_named_map(cfg_parser_t * pctx,cfg_type_t * nametype,const cfg_type_t * type,cfg_obj_t ** ret)761 parse_any_named_map(cfg_parser_t *pctx, cfg_type_t *nametype,
762 const cfg_type_t *type, cfg_obj_t **ret)
763 {
764 isc_result_t result;
765 cfg_obj_t *idobj = NULL;
766 cfg_obj_t *mapobj = NULL;
767
768 REQUIRE(pctx != NULL);
769 REQUIRE(nametype != NULL);
770 REQUIRE(type != NULL);
771 REQUIRE(ret != NULL && *ret == NULL);
772
773 CHECK(cfg_parse_obj(pctx, nametype, &idobj));
774 CHECK(cfg_parse_map(pctx, type, &mapobj));
775 mapobj->value.map.id = idobj;
776 *ret = mapobj;
777 return (result);
778 cleanup:
779 CLEANUP_OBJ(idobj);
780 CLEANUP_OBJ(mapobj);
781 return (result);
782 }
783
784 /*
785 * Parse a map identified by a string name. E.g., "name { foo 1; }".
786 * Used for the "key" and "channel" statements.
787 */
788 isc_result_t
cfg_parse_named_map(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)789 cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
790 return (parse_any_named_map(pctx, &cfg_type_astring, type, ret));
791 }
792
793 isc_result_t
cfg_map_get(const cfg_obj_t * mapobj,const char * name,const cfg_obj_t ** obj)794 cfg_map_get(const cfg_obj_t *mapobj, const char* name, const cfg_obj_t **obj) {
795 isc_result_t result;
796 isc_symvalue_t val;
797 const cfg_map_t *map;
798
799 REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
800 REQUIRE(name != NULL);
801 REQUIRE(obj != NULL && *obj == NULL);
802
803 map = &mapobj->value.map;
804
805 result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val);
806 if (result != ISC_R_SUCCESS)
807 return (result);
808 *obj = val.as_pointer;
809 return (ISC_R_SUCCESS);
810 }
811
812 const cfg_obj_t *
cfg_map_getname(const cfg_obj_t * mapobj)813 cfg_map_getname(const cfg_obj_t *mapobj) {
814 REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
815 return (mapobj->value.map.id);
816 }
817
818 /* Parse an arbitrary token, storing its raw text representation. */
819 static isc_result_t
parse_token(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)820 parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
821 cfg_obj_t *obj = NULL;
822 isc_result_t result;
823 isc_region_t r;
824
825 UNUSED(type);
826
827 CHECK(cfg_create_obj(pctx, &cfg_type_token, &obj));
828 CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
829 if (pctx->token.type == isc_tokentype_eof) {
830 cfg_ungettoken(pctx);
831 result = ISC_R_EOF;
832 goto cleanup;
833 }
834
835 isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
836
837 obj->value.string.base = malloc(r.length + 1);
838 if (obj->value.string.base == NULL) {
839 result = ISC_R_NOMEMORY;
840 goto cleanup;
841 }
842 obj->value.string.length = r.length;
843 memmove(obj->value.string.base, r.base, r.length);
844 obj->value.string.base[r.length] = '\0';
845 *ret = obj;
846 return (result);
847
848 cleanup:
849 if (obj != NULL)
850 free(obj);
851 return (result);
852 }
853
854 cfg_type_t cfg_type_token = {
855 "token", parse_token, &cfg_rep_string, NULL
856 };
857
858 /*
859 * An unsupported option. This is just a list of tokens with balanced braces
860 * ending in a semicolon.
861 */
862
863 static isc_result_t
parse_unsupported(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)864 parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
865 cfg_obj_t *listobj = NULL;
866 isc_result_t result;
867 int braces = 0;
868
869 CHECK(cfg_create_list(pctx, type, &listobj));
870
871 for (;;) {
872 cfg_listelt_t *elt = NULL;
873
874 CHECK(cfg_peektoken(pctx, 0));
875 if (pctx->token.type == isc_tokentype_special) {
876 if (pctx->token.value.as_char == '{')
877 braces++;
878 else if (pctx->token.value.as_char == '}')
879 braces--;
880 else if (pctx->token.value.as_char == ';')
881 if (braces == 0)
882 break;
883 }
884 if (pctx->token.type == isc_tokentype_eof || braces < 0) {
885 cfg_parser_error(pctx, CFG_LOG_NEAR, "unexpected token");
886 result = ISC_R_UNEXPECTEDTOKEN;
887 goto cleanup;
888 }
889
890 CHECK(cfg_parse_listelt(pctx, &cfg_type_token, &elt));
891 ISC_LIST_APPEND(listobj->value.list, elt, link);
892 }
893 INSIST(braces == 0);
894 *ret = listobj;
895 return (ISC_R_SUCCESS);
896
897 cleanup:
898 CLEANUP_OBJ(listobj);
899 return (result);
900 }
901
902 cfg_type_t cfg_type_unsupported = {
903 "unsupported", parse_unsupported, &cfg_rep_list, NULL
904 };
905
906 static isc_result_t
cfg_gettoken(cfg_parser_t * pctx,int options)907 cfg_gettoken(cfg_parser_t *pctx, int options) {
908 isc_result_t result;
909
910 REQUIRE(pctx != NULL);
911
912 if (pctx->seen_eof)
913 return (ISC_R_SUCCESS);
914
915 options |= (ISC_LEXOPT_EOF | ISC_LEXOPT_NOMORE);
916
917 redo:
918 pctx->token.type = isc_tokentype_unknown;
919 result = isc_lex_gettoken(pctx->lexer, options, &pctx->token);
920 pctx->ungotten = 0;
921 pctx->line = isc_lex_getsourceline(pctx->lexer);
922
923 switch (result) {
924 case ISC_R_SUCCESS:
925 if (pctx->token.type == isc_tokentype_eof) {
926 result = isc_lex_close(pctx->lexer);
927 INSIST(result == ISC_R_NOMORE ||
928 result == ISC_R_SUCCESS);
929
930 if (isc_lex_getsourcename(pctx->lexer) != NULL) {
931 /*
932 * Closed an included file, not the main file.
933 */
934 cfg_listelt_t *elt;
935 elt = ISC_LIST_TAIL(pctx->open_files->
936 value.list);
937 INSIST(elt != NULL);
938 ISC_LIST_UNLINK(pctx->open_files->
939 value.list, elt, link);
940 ISC_LIST_APPEND(pctx->closed_files->
941 value.list, elt, link);
942 goto redo;
943 }
944 pctx->seen_eof = 1;
945 }
946 break;
947
948 case ISC_R_NOSPACE:
949 /* More understandable than "ran out of space". */
950 cfg_parser_error(pctx, CFG_LOG_NEAR, "token too big");
951 break;
952
953 case ISC_R_IOERROR:
954 cfg_parser_error(pctx, 0, "%s",
955 isc_result_totext(result));
956 break;
957
958 default:
959 cfg_parser_error(pctx, CFG_LOG_NEAR, "%s",
960 isc_result_totext(result));
961 break;
962 }
963 return (result);
964 }
965
966 static void
cfg_ungettoken(cfg_parser_t * pctx)967 cfg_ungettoken(cfg_parser_t *pctx) {
968 REQUIRE(pctx != NULL);
969
970 if (pctx->seen_eof)
971 return;
972 isc_lex_ungettoken(pctx->lexer, &pctx->token);
973 pctx->ungotten = 1;
974 }
975
976 static isc_result_t
cfg_peektoken(cfg_parser_t * pctx,int options)977 cfg_peektoken(cfg_parser_t *pctx, int options) {
978 isc_result_t result;
979
980 REQUIRE(pctx != NULL);
981
982 CHECK(cfg_gettoken(pctx, options));
983 cfg_ungettoken(pctx);
984 cleanup:
985 return (result);
986 }
987
988 /*
989 * Get a string token, accepting both the quoted and the unquoted form.
990 * Log an error if the next token is not a string.
991 */
992 static isc_result_t
cfg_getstringtoken(cfg_parser_t * pctx)993 cfg_getstringtoken(cfg_parser_t *pctx) {
994 isc_result_t result;
995
996 result = cfg_gettoken(pctx, CFG_LEXOPT_QSTRING);
997 if (result != ISC_R_SUCCESS)
998 return (result);
999
1000 if (pctx->token.type != isc_tokentype_string &&
1001 pctx->token.type != isc_tokentype_qstring) {
1002 cfg_parser_error(pctx, CFG_LOG_NEAR, "expected string");
1003 return (ISC_R_UNEXPECTEDTOKEN);
1004 }
1005 return (ISC_R_SUCCESS);
1006 }
1007
1008 static void
cfg_parser_error(cfg_parser_t * pctx,unsigned int flags,const char * fmt,...)1009 cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) {
1010 va_list args;
1011
1012 REQUIRE(pctx != NULL);
1013 REQUIRE(fmt != NULL);
1014
1015 va_start(args, fmt);
1016 parser_complain(pctx, 0, flags, fmt, args);
1017 va_end(args);
1018 pctx->errors++;
1019 }
1020
1021 #define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */
1022
1023 static int
have_current_file(cfg_parser_t * pctx)1024 have_current_file(cfg_parser_t *pctx) {
1025 cfg_listelt_t *elt;
1026 if (pctx->open_files == NULL)
1027 return (0);
1028
1029 elt = ISC_LIST_TAIL(pctx->open_files->value.list);
1030 if (elt == NULL)
1031 return (0);
1032
1033 return (1);
1034 }
1035
1036 static char *
current_file(cfg_parser_t * pctx)1037 current_file(cfg_parser_t *pctx) {
1038 static char none[] = "none";
1039 cfg_listelt_t *elt;
1040 cfg_obj_t *fileobj;
1041
1042 if (!have_current_file(pctx))
1043 return (none);
1044
1045 elt = ISC_LIST_TAIL(pctx->open_files->value.list);
1046 if (elt == NULL) /* shouldn't be possible, but... */
1047 return (none);
1048
1049 fileobj = elt->obj;
1050 INSIST(fileobj->type == &cfg_type_qstring);
1051 return (fileobj->value.string.base);
1052 }
1053
1054 static void
parser_complain(cfg_parser_t * pctx,int is_warning,unsigned int flags,const char * format,va_list args)1055 parser_complain(cfg_parser_t *pctx, int is_warning,
1056 unsigned int flags, const char *format,
1057 va_list args)
1058 {
1059 char tokenbuf[MAX_LOG_TOKEN + 10];
1060 static char where[PATH_MAX + 100];
1061 static char message[2048];
1062 int level = ISC_LOG_ERROR;
1063 const char *prep = "";
1064 size_t len;
1065
1066 if (is_warning)
1067 level = ISC_LOG_WARNING;
1068
1069 where[0] = '\0';
1070 if (have_current_file(pctx))
1071 snprintf(where, sizeof(where), "%s:%u: ",
1072 current_file(pctx), pctx->line);
1073
1074 len = vsnprintf(message, sizeof(message), format, args);
1075 #define ELIPSIS " ... "
1076 if (len >= sizeof(message)) {
1077 message[sizeof(message) - sizeof(ELIPSIS)] = 0;
1078 strlcat(message, ELIPSIS, sizeof(message));
1079 }
1080
1081 if ((flags & (CFG_LOG_NEAR|CFG_LOG_BEFORE|CFG_LOG_NOPREP)) != 0) {
1082 isc_region_t r;
1083
1084 if (pctx->ungotten)
1085 (void)cfg_gettoken(pctx, 0);
1086
1087 if (pctx->token.type == isc_tokentype_eof) {
1088 snprintf(tokenbuf, sizeof(tokenbuf), "end of file");
1089 } else if (pctx->token.type == isc_tokentype_unknown) {
1090 flags = 0;
1091 tokenbuf[0] = '\0';
1092 } else {
1093 isc_lex_getlasttokentext(pctx->lexer,
1094 &pctx->token, &r);
1095 if (r.length > MAX_LOG_TOKEN)
1096 snprintf(tokenbuf, sizeof(tokenbuf),
1097 "'%.*s...'", MAX_LOG_TOKEN, r.base);
1098 else
1099 snprintf(tokenbuf, sizeof(tokenbuf),
1100 "'%.*s'", (int)r.length, r.base);
1101 }
1102
1103 /* Choose a preposition. */
1104 if (flags & CFG_LOG_NEAR)
1105 prep = " near ";
1106 else if (flags & CFG_LOG_BEFORE)
1107 prep = " before ";
1108 else
1109 prep = " ";
1110 } else {
1111 tokenbuf[0] = '\0';
1112 }
1113 isc_log_write(pctx->lctx, CAT, MOD, level,
1114 "%s%s%s%s", where, message, prep, tokenbuf);
1115 }
1116
1117 static isc_result_t
cfg_create_obj(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)1118 cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
1119 cfg_obj_t *obj;
1120
1121 REQUIRE(pctx != NULL);
1122 REQUIRE(type != NULL);
1123 REQUIRE(ret != NULL && *ret == NULL);
1124
1125 obj = malloc(sizeof(cfg_obj_t));
1126 if (obj == NULL)
1127 return (ISC_R_NOMEMORY);
1128 obj->type = type;
1129 obj->file = current_file(pctx);
1130 obj->line = pctx->line;
1131 *ret = obj;
1132 return (ISC_R_SUCCESS);
1133 }
1134
1135 static void
map_symtabitem_destroy(char * key,unsigned int type,isc_symvalue_t symval,void * userarg)1136 map_symtabitem_destroy(char *key, unsigned int type,
1137 isc_symvalue_t symval, void *userarg)
1138 {
1139 cfg_obj_t *obj = symval.as_pointer;
1140 cfg_parser_t *pctx = (cfg_parser_t *)userarg;
1141
1142 UNUSED(key);
1143 UNUSED(type);
1144
1145 cfg_obj_destroy(pctx, &obj);
1146 }
1147
1148 static isc_result_t
create_map(cfg_parser_t * pctx,const cfg_type_t * type,cfg_obj_t ** ret)1149 create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
1150 isc_result_t result;
1151 isc_symtab_t *symtab = NULL;
1152 cfg_obj_t *obj = NULL;
1153
1154 CHECK(cfg_create_obj(pctx, type, &obj));
1155 CHECK(isc_symtab_create(5, /* XXX */
1156 map_symtabitem_destroy,
1157 pctx, 0, &symtab));
1158 obj->value.map.symtab = symtab;
1159 obj->value.map.id = NULL;
1160
1161 *ret = obj;
1162 return (ISC_R_SUCCESS);
1163
1164 cleanup:
1165 if (obj != NULL)
1166 free(obj);
1167 return (result);
1168 }
1169
1170 static void
free_map(cfg_parser_t * pctx,cfg_obj_t * obj)1171 free_map(cfg_parser_t *pctx, cfg_obj_t *obj) {
1172 CLEANUP_OBJ(obj->value.map.id);
1173 isc_symtab_destroy(&obj->value.map.symtab);
1174 }
1175
1176 /*
1177 * Destroy 'obj', a configuration object created in 'pctx'.
1178 */
1179 void
cfg_obj_destroy(cfg_parser_t * pctx,cfg_obj_t ** objp)1180 cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) {
1181 cfg_obj_t *obj;
1182
1183 REQUIRE(objp != NULL && *objp != NULL);
1184 REQUIRE(pctx != NULL);
1185
1186 obj = *objp;
1187
1188 obj->type->rep->free(pctx, obj);
1189 free(obj);
1190 *objp = NULL;
1191 }
1192