xref: /openbsd/usr.bin/dig/lib/isccfg/parser.c (revision ac19a2a7)
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