parser.c 53.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright (C) 2000, 2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

Mark Andrews's avatar
Mark Andrews committed
18
/* $Id: parser.c,v 1.98 2002/01/04 06:19:20 marka Exp $ */
19 20 21 22 23

#include <config.h>

#include <isc/buffer.h>
#include <isc/dir.h>
24
#include <isc/formatcheck.h>
25 26 27 28 29
#include <isc/lex.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/net.h>
#include <isc/netaddr.h>
30
#include <isc/print.h>
31 32 33 34 35 36
#include <isc/string.h>
#include <isc/sockaddr.h>
#include <isc/util.h>
#include <isc/symtab.h>

#include <isccfg/cfg.h>
37
#include <isccfg/grammar.h>
38
#include <isccfg/log.h>
39

40
/* Shorthand */
41 42
#define CAT CFG_LOGCATEGORY_CONFIG
#define MOD CFG_LOGMODULE_PARSER
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

#define MAP_SYM 1 	/* Unique type for isc_symtab */

/* Check a return value. */
#define CHECK(op) 						\
     	do { result = (op); 					\
		if (result != ISC_R_SUCCESS) goto cleanup; 	\
	} while (0)

/* Clean up a configuration object if non-NULL. */
#define CLEANUP_OBJ(obj) \
	do { if ((obj) != NULL) cfg_obj_destroy(pctx, &(obj)); } while (0)


/*
 * Forward declarations of static functions.
 */

static void
62
free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj);
63

64
static isc_result_t
65
parse_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
66 67 68 69 70

static void
print_list(cfg_printer_t *pctx, cfg_obj_t *obj);

static void
71
free_list(cfg_parser_t *pctx, cfg_obj_t *obj);
72

73
static isc_result_t
74
create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp);
75 76

static isc_result_t
77 78
create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
	      cfg_obj_t **ret);
79 80

static void
81
free_string(cfg_parser_t *pctx, cfg_obj_t *obj);
82

83
static isc_result_t
84
create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
85 86

static void
87
free_map(cfg_parser_t *pctx, cfg_obj_t *obj);
88 89 90

static isc_result_t
parse_symtab_elt(cfg_parser_t *pctx, const char *name,
91 92
		 cfg_type_t *elttype, isc_symtab_t *symtab,
		 isc_boolean_t callback);
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

static void
free_noop(cfg_parser_t *pctx, cfg_obj_t *obj);

static isc_result_t
cfg_getstringtoken(cfg_parser_t *pctx);

static void
parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning,
		unsigned int flags, const char *format, va_list args);

/*
 * Data representations.  These correspond to members of the
 * "value" union in struct cfg_obj (except "void", which does
 * not need a union member).
 */
109

110
cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop };
111
cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
cfg_rep_t cfg_rep_string = { "string", free_string };
cfg_rep_t cfg_rep_boolean = { "boolean", free_noop };
cfg_rep_t cfg_rep_map = { "map", free_map };
cfg_rep_t cfg_rep_list = { "list", free_list };
cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple };
cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop };
cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop };
cfg_rep_t cfg_rep_void = { "void", free_noop };

/*
 * Configuration type definitions.
 */

/*
 * An implicit list.  These are formed by clauses that occur multiple times.
 */
static cfg_type_t cfg_type_implicitlist = {
129
	"implicitlist", NULL, print_list, NULL, &cfg_rep_list, NULL };
130 131 132

/* Functions. */

133 134
void
cfg_print_obj(cfg_printer_t *pctx, cfg_obj_t *obj) {
135 136 137
	obj->type->print(pctx, obj);
}

138 139
void
cfg_print_chars(cfg_printer_t *pctx, const char *text, int len) {
140 141 142 143 144
	pctx->f(pctx->closure, text, len);
}

static void
print_open(cfg_printer_t *pctx) {
145
	cfg_print_chars(pctx, "{\n", 2);
146 147 148 149 150 151 152
	pctx->indent++;
}

static void
print_indent(cfg_printer_t *pctx) {
	int indent = pctx->indent;
	while (indent > 0) {
153
		cfg_print_chars(pctx, "\t", 1);
154 155 156 157 158 159 160 161
		indent--;
	}
}

static void
print_close(cfg_printer_t *pctx) {
	pctx->indent--;
	print_indent(pctx);
162
	cfg_print_chars(pctx, "}", 1);
163 164
}

165 166
isc_result_t
cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
167 168 169 170 171 172 173
	isc_result_t result;
	INSIST(ret != NULL && *ret == NULL);
	result = type->parse(pctx, type, ret);
	if (result != ISC_R_SUCCESS)
		return (result);
	INSIST(*ret != NULL);
	return (ISC_R_SUCCESS);
174 175 176 177 178 179 180 181 182 183 184 185 186 187
}

void
cfg_print(cfg_obj_t *obj,
	  void (*f)(void *closure, const char *text, int textlen),
	  void *closure)
{
	cfg_printer_t pctx;
	pctx.f = f;
	pctx.closure = closure;
	pctx.indent = 0;
	obj->type->print(&pctx, obj);
}

188 189 190

/* Tuples. */
  
191 192
isc_result_t
cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
193
	isc_result_t result;
194 195
	const cfg_tuplefielddef_t *fields = type->of;
	const cfg_tuplefielddef_t *f;
196 197 198
	cfg_obj_t *obj = NULL;
	unsigned int nfields = 0;
	int i;
199

200
	for (f = fields; f->name != NULL; f++)
201 202
		nfields++;

203
	CHECK(cfg_create_obj(pctx, type, &obj));
204 205
	obj->value.tuple = isc_mem_get(pctx->mctx,
				       nfields * sizeof(cfg_obj_t *));
206 207 208 209
	if (obj->value.tuple == NULL) {
		result = ISC_R_NOMEMORY;
		goto cleanup;
	}
210
	for (f = fields, i = 0; f->name != NULL; f++, i++)
211
		obj->value.tuple[i] = NULL;
212 213 214 215 216 217 218 219
	*ret = obj;
	return (ISC_R_SUCCESS);

 cleanup:
	CLEANUP_OBJ(obj);
	return (result);
}

220 221
isc_result_t
cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
222 223
{
	isc_result_t result;
224 225
	const cfg_tuplefielddef_t *fields = type->of;
	const cfg_tuplefielddef_t *f;
226 227
	cfg_obj_t *obj = NULL;
	unsigned int i;
228

229
	CHECK(cfg_create_tuple(pctx, type, &obj));
230
	for (f = fields, i = 0; f->name != NULL; f++, i++)
231
		CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[i]));
232 233 234 235 236 237 238 239 240

	*ret = obj;
	return (ISC_R_SUCCESS);

 cleanup:
	CLEANUP_OBJ(obj);
	return (result);
}

241 242
void
cfg_print_tuple(cfg_printer_t *pctx, cfg_obj_t *obj) {
243
	unsigned int i;
244 245
	const cfg_tuplefielddef_t *fields = obj->type->of;
	const cfg_tuplefielddef_t *f;
246
	isc_boolean_t need_space = ISC_FALSE;
247

248
	for (f = fields, i = 0; f->name != NULL; f++, i++) {
249 250
		cfg_obj_t *fieldobj = obj->value.tuple[i];
		if (need_space)
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
			cfg_print_chars(pctx, " ", 1);
		cfg_print_obj(pctx, fieldobj);
		need_space = ISC_TF(fieldobj->type->print != cfg_print_void);
	}
}

void
cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type) {
	const cfg_tuplefielddef_t *fields = type->of;
	const cfg_tuplefielddef_t *f;
	isc_boolean_t need_space = ISC_FALSE;

	for (f = fields; f->name != NULL; f++) {
		if (need_space)
			cfg_print_chars(pctx, " ", 1);
		cfg_doc_obj(pctx, f->type);
		need_space = ISC_TF(f->type->print != cfg_print_void);
268 269 270 271 272 273
	}
}

static void
free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj) {
	unsigned int i;
274 275
	const cfg_tuplefielddef_t *fields = obj->type->of;
	const cfg_tuplefielddef_t *f;
276 277
	unsigned int nfields = 0;

278 279 280
	if (obj->value.tuple == NULL)
		return;

281
	for (f = fields, i = 0; f->name != NULL; f++, i++) {
282 283 284 285 286 287 288
		CLEANUP_OBJ(obj->value.tuple[i]);
		nfields++;
	}
	isc_mem_put(pctx->mctx, obj->value.tuple,
		    nfields * sizeof(cfg_obj_t *));
}

289 290 291 292 293 294
isc_boolean_t
cfg_obj_istuple(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_tuple));
}

295 296 297
cfg_obj_t *
cfg_tuple_get(cfg_obj_t *tupleobj, const char* name) {
	unsigned int i;
298 299
	const cfg_tuplefielddef_t *fields;
	const cfg_tuplefielddef_t *f;
300 301 302 303 304 305 306 307 308 309 310 311
	
	REQUIRE(tupleobj != NULL && tupleobj->type->rep == &cfg_rep_tuple);

	fields = tupleobj->type->of;
	for (f = fields, i = 0; f->name != NULL; f++, i++) {
		if (strcmp(f->name, name) == 0)
			return (tupleobj->value.tuple[i]);
	}
	INSIST(0);
	return (NULL);
}

312 313
isc_result_t
cfg_parse_special(cfg_parser_t *pctx, int special) {
314 315 316 317 318 319
        isc_result_t result;
	CHECK(cfg_gettoken(pctx, 0));
	if (pctx->token.type == isc_tokentype_special &&
	    pctx->token.value.as_char == special)
		return (ISC_R_SUCCESS);

320
	cfg_parser_error(pctx, CFG_LOG_NEAR, "'%c' expected", special);
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
	return (ISC_R_UNEXPECTEDTOKEN);
 cleanup:
	return (result);
}

/*
 * Parse a required semicolon.  If it is not there, log
 * an error and increment the error count but continue
 * parsing.  Since the next token is pushed back,
 * care must be taken to make sure it is eventually
 * consumed or an infinite loop may result.
 */
static isc_result_t
parse_semicolon(cfg_parser_t *pctx) {
        isc_result_t result;
	CHECK(cfg_gettoken(pctx, 0));
	if (pctx->token.type == isc_tokentype_special &&
	    pctx->token.value.as_char == ';')
		return (ISC_R_SUCCESS);

341
	cfg_parser_error(pctx, CFG_LOG_BEFORE, "missing ';'");
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	cfg_ungettoken(pctx);
 cleanup:
	return (result);
}

/*
 * Parse EOF, logging and returning an error if not there.
 */
static isc_result_t
parse_eof(cfg_parser_t *pctx) {
        isc_result_t result;
	CHECK(cfg_gettoken(pctx, 0));

	if (pctx->token.type == isc_tokentype_eof)
		return (ISC_R_SUCCESS);

358
	cfg_parser_error(pctx, CFG_LOG_NEAR, "syntax error");
359 360 361 362 363 364
	return (ISC_R_UNEXPECTEDTOKEN);
 cleanup:
	return(result);
}

/* A list of files, used internally for pctx->files. */
365

366
static cfg_type_t cfg_type_filelist = {
367
	"filelist", NULL, print_list, NULL, &cfg_rep_list,
368 369 370 371
	&cfg_type_qstring
};

isc_result_t
372
cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret) {
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	isc_result_t result;
	cfg_parser_t *pctx;
	isc_lexspecials_t specials;

	REQUIRE(mctx != NULL);
	REQUIRE(ret != NULL && *ret == NULL);

	pctx = isc_mem_get(mctx, sizeof(*pctx));
	if (pctx == NULL)
		return (ISC_R_NOMEMORY);

	pctx->mctx = mctx;
	pctx->lctx = lctx;
	pctx->lexer = NULL;
	pctx->seen_eof = ISC_FALSE;
	pctx->ungotten = ISC_FALSE;
	pctx->errors = 0;
Mark Andrews's avatar
Mark Andrews committed
390
	pctx->warnings = 0;
391 392
	pctx->open_files = NULL;
	pctx->closed_files = NULL;
393
	pctx->line = 0;
394 395
	pctx->callback = NULL;
	pctx->callbackarg = NULL;
Mark Andrews's avatar
Mark Andrews committed
396
	pctx->token.type = isc_tokentype_unknown;
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

	memset(specials, 0, sizeof(specials));
	specials['{'] = 1;
	specials['}'] = 1;
	specials[';'] = 1;
	specials['/'] = 1;
	specials['"'] = 1;
	specials['!'] = 1;

	CHECK(isc_lex_create(pctx->mctx, 1024, &pctx->lexer));

	isc_lex_setspecials(pctx->lexer, specials);
	isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C |
					 ISC_LEXCOMMENT_CPLUSPLUS |
					 ISC_LEXCOMMENT_SHELL));

413 414
	CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files));
	CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->closed_files));
415 416 417 418 419 420 421

	*ret = pctx;
	return (ISC_R_SUCCESS);

 cleanup:
	if (pctx->lexer != NULL)
		isc_lex_destroy(&pctx->lexer);
422 423
	CLEANUP_OBJ(pctx->open_files);
	CLEANUP_OBJ(pctx->closed_files);
424 425 426 427 428
	isc_mem_put(mctx, pctx, sizeof(*pctx));
	return (result);
}

static isc_result_t
429
parser_openfile(cfg_parser_t *pctx, const char *filename) {
430 431 432 433 434 435
	isc_result_t result;
	cfg_listelt_t *elt = NULL;
	cfg_obj_t *stringobj = NULL;

	result = isc_lex_openfile(pctx->lexer, filename);
	if (result != ISC_R_SUCCESS) {
436
		cfg_parser_error(pctx, 0, "open: %s: %s",
437 438 439 440 441 442 443
			     filename, isc_result_totext(result));
		goto cleanup;
	}

	CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj));
	CHECK(create_listelt(pctx, &elt));
	elt->obj = stringobj;
444
	ISC_LIST_APPEND(pctx->open_files->value.list, elt, link);
445 446 447 448 449 450 451

	return (ISC_R_SUCCESS);
 cleanup:
	CLEANUP_OBJ(stringobj);
	return (result);
}

452 453 454 455 456 457 458 459 460
void
cfg_parser_setcallback(cfg_parser_t *pctx,
		       cfg_parsecallback_t callback,
		       void *arg)
{
	pctx->callback = callback;
	pctx->callbackarg = arg;
}

461 462 463 464 465
/*
 * Parse a configuration using a pctx where a lexer has already
 * been set up with a source.
 */
static isc_result_t
466
parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
467 468 469
	isc_result_t result;
	cfg_obj_t *obj = NULL;

470
	result = cfg_parse_obj(pctx, type, &obj);
471 472 473 474 475 476 477 478 479 480

	if (pctx->errors != 0) {
		/* Errors have been logged. */
		if (result == ISC_R_SUCCESS)
			result = ISC_R_FAILURE;
		goto cleanup;
	}

	if (result != ISC_R_SUCCESS) {
		/* Parsing failed but no errors have been logged. */
481
		cfg_parser_error(pctx, 0, "parsing failed");
482 483 484 485 486 487 488 489 490 491 492 493 494
		goto cleanup;
	}

	CHECK(parse_eof(pctx));

	*ret = obj;
	return (ISC_R_SUCCESS);

 cleanup:
	CLEANUP_OBJ(obj);
	return (result);
}

495 496
isc_result_t
cfg_parse_file(cfg_parser_t *pctx, const char *filename,
497
	       const cfg_type_t *type, cfg_obj_t **ret)
498 499 500 501 502 503 504 505 506 507 508 509 510 511
{
	isc_result_t result;

	REQUIRE(filename != NULL);

	CHECK(parser_openfile(pctx, filename));
	CHECK(parse2(pctx, type, ret));
 cleanup:
	return (result);
}


isc_result_t
cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer,
512
	const cfg_type_t *type, cfg_obj_t **ret)
513 514 515 516 517 518 519 520 521
{
	isc_result_t result;
	REQUIRE(buffer != NULL);
	CHECK(isc_lex_openbuffer(pctx->lexer, buffer));	
	CHECK(parse2(pctx, type, ret));
 cleanup:
	return (result);
}

522 523 524 525
void
cfg_parser_destroy(cfg_parser_t **pctxp) {
	cfg_parser_t *pctx = *pctxp;
	isc_lex_destroy(&pctx->lexer);
526 527 528 529 530 531 532
	/*
	 * Cleaning up open_files does not
	 * close the files; that was already done
	 * by closing the lexer.
	 */
	CLEANUP_OBJ(pctx->open_files);
	CLEANUP_OBJ(pctx->closed_files);
533 534 535 536 537 538 539
	isc_mem_put(pctx->mctx, pctx, sizeof(*pctx));
	*pctxp = NULL;
}

/*
 * void
 */
540 541
isc_result_t
cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
542
	UNUSED(type);
543
	return (cfg_create_obj(pctx, &cfg_type_void, ret));
544 545
}

546 547
void
cfg_print_void(cfg_printer_t *pctx, cfg_obj_t *obj) {
548 549 550
	UNUSED(pctx);
	UNUSED(obj);
}
551

552 553 554 555 556 557
void
cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type) {
	UNUSED(pctx);
	UNUSED(type);
}

558 559 560 561 562 563
isc_boolean_t
cfg_obj_isvoid(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_void));
}

564 565 566
cfg_type_t cfg_type_void = {
	"void", cfg_parse_void, cfg_print_void, cfg_doc_void, &cfg_rep_void,
	NULL };
567 568 569 570 571


/*
 * uint32
 */
572 573
isc_result_t
cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
574 575 576 577
        isc_result_t result;
	cfg_obj_t *obj = NULL;
	UNUSED(type);

578
	CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
579
	if (pctx->token.type != isc_tokentype_number) {
580
		cfg_parser_error(pctx, CFG_LOG_NEAR, "expected number");
581 582 583
		return (ISC_R_UNEXPECTEDTOKEN);
	}

584
	CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj));
585 586 587 588 589 590 591

	obj->value.uint32 = pctx->token.value.as_ulong;
	*ret = obj;
 cleanup:
	return (result);
}

592 593 594
void
cfg_print_cstr(cfg_printer_t *pctx, const char *s) {
	cfg_print_chars(pctx, s, strlen(s));
595 596
}

597 598
void
cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u) {
599
	char buf[32];
600
	snprintf(buf, sizeof(buf), "%u", u);
601
	cfg_print_cstr(pctx, buf);
602 603
}

604 605 606
void
cfg_print_uint32(cfg_printer_t *pctx, cfg_obj_t *obj) {
	cfg_print_rawuint(pctx, obj->value.uint32);
607 608
}

609 610 611 612 613 614
isc_boolean_t
cfg_obj_isuint32(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_uint32));
}

615 616 617 618 619 620
isc_uint32_t
cfg_obj_asuint32(cfg_obj_t *obj) {
	REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32);
	return (obj->value.uint32);
}

621 622 623 624
cfg_type_t cfg_type_uint32 = {
	"integer", cfg_parse_uint32, cfg_print_uint32, cfg_doc_terminal,
	&cfg_rep_uint32, NULL
};
625 626


627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
/*
 * uint64
 */
isc_boolean_t
cfg_obj_isuint64(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_uint64));
}

isc_uint64_t
cfg_obj_asuint64(cfg_obj_t *obj) {
	REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64);
	return (obj->value.uint64);
}

642 643
void
cfg_print_uint64(cfg_printer_t *pctx, cfg_obj_t *obj) {
644 645
	char buf[32];
	sprintf(buf, "%" ISC_PRINT_QUADFORMAT "u", obj->value.uint64);
646
	cfg_print_cstr(pctx, buf);
647 648
}

649 650 651
cfg_type_t cfg_type_uint64 = {
	"64_bit_integer", NULL, cfg_print_uint64, cfg_doc_terminal,
	&cfg_rep_uint64, NULL
652 653
};

654
/*
655 656
 * qstring (quoted string), ustring (unquoted string), astring
 * (any string)
657 658 659 660
 */

/* Create a string object from a null-terminated C string. */
static isc_result_t
661
create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
662 663 664 665 666 667
	      cfg_obj_t **ret)
{
	isc_result_t result;
	cfg_obj_t *obj = NULL;
	int len;

668
	CHECK(cfg_create_obj(pctx, type, &obj));
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
	len = strlen(contents);
	obj->value.string.length = len;
	obj->value.string.base = isc_mem_get(pctx->mctx, len + 1);
	if (obj->value.string.base == 0) {
		CLEANUP_OBJ(obj);
		return (ISC_R_NOMEMORY);
	}
	memcpy(obj->value.string.base, contents, len);
	obj->value.string.base[len] = '\0';

	*ret = obj;
 cleanup:
	return (result);
}

684 685
isc_result_t
cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
686 687 688
        isc_result_t result;
	UNUSED(type);

689
	CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
690
	if (pctx->token.type != isc_tokentype_qstring) {
691
		cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string");
692 693 694 695 696 697 698 699 700 701 702
		return (ISC_R_UNEXPECTEDTOKEN);
	}
	return (create_string(pctx,
			      pctx->token.value.as_pointer,
			      &cfg_type_qstring,
			      ret));
 cleanup:
	return (result);
}

static isc_result_t
703
parse_ustring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
704 705 706 707 708
        isc_result_t result;
	UNUSED(type);

	CHECK(cfg_gettoken(pctx, 0));
	if (pctx->token.type != isc_tokentype_string) {
709
		cfg_parser_error(pctx, CFG_LOG_NEAR, "expected unquoted string");
710 711 712 713 714 715 716 717 718 719
		return (ISC_R_UNEXPECTEDTOKEN);
	}
	return (create_string(pctx,
			      pctx->token.value.as_pointer,
			      &cfg_type_ustring,
			      ret));
 cleanup:
	return (result);
}

720 721
isc_result_t
cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
722 723 724 725 726 727 728 729 730 731 732 733
        isc_result_t result;
	UNUSED(type);

	CHECK(cfg_getstringtoken(pctx));
	return (create_string(pctx,
			      pctx->token.value.as_pointer,
			      &cfg_type_qstring,
			      ret));
 cleanup:
	return (result);
}

734 735
isc_boolean_t
cfg_is_enum(const char *s, const char *const *enums) {
736
	const char * const *p;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
737 738
	for (p = enums; *p != NULL; p++) {
		if (strcasecmp(*p, s) == 0)
739
			return (ISC_TRUE);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
740
	}
741 742 743 744
	return (ISC_FALSE);
}

static isc_result_t
745
check_enum(cfg_parser_t *pctx, cfg_obj_t *obj, const char *const *enums) {
746
	const char *s = obj->value.string.base;
747
	if (cfg_is_enum(s, enums))
748
		return (ISC_R_SUCCESS);
749
	cfg_parser_error(pctx, 0, "'%s' unexpected", s);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
750 751 752
	return (ISC_R_UNEXPECTEDTOKEN);
}

753 754
isc_result_t
cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
755 756 757 758 759 760 761 762 763 764 765
        isc_result_t result;
	cfg_obj_t *obj = NULL;
	CHECK(parse_ustring(pctx, NULL, &obj));
	CHECK(check_enum(pctx, obj, type->of));
	*ret = obj;
	return (ISC_R_SUCCESS);
 cleanup:
	CLEANUP_OBJ(obj);	
	return (result);
}

766 767 768 769 770 771 772 773
void
cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type) {
	const char * const *p;
	cfg_print_chars(pctx, "( ", 2);
	for (p = type->of; *p != NULL; p++) {
		cfg_print_cstr(pctx, *p);
		if (p[1] != NULL)
			cfg_print_chars(pctx, " | ", 3);
774
	}
775
	cfg_print_chars(pctx, " )", 2);
776 777
}

778 779 780
void
cfg_print_ustring(cfg_printer_t *pctx, cfg_obj_t *obj) {
	cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
781 782 783 784
}

static void
print_qstring(cfg_printer_t *pctx, cfg_obj_t *obj) {
785 786 787
	cfg_print_chars(pctx, "\"", 1);
	cfg_print_ustring(pctx, obj);
	cfg_print_chars(pctx, "\"", 1);
788 789 790 791 792 793 794 795
}

static void
free_string(cfg_parser_t *pctx, cfg_obj_t *obj) {
	isc_mem_put(pctx->mctx, obj->value.string.base,
		    obj->value.string.length + 1);
}

796 797 798 799 800 801
isc_boolean_t
cfg_obj_isstring(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_string));
}

802 803 804 805 806 807
char *
cfg_obj_asstring(cfg_obj_t *obj) {
	REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string);
	return (obj->value.string.base);
}

808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
/* Quoted string only */
cfg_type_t cfg_type_qstring = {
	"quoted_string", cfg_parse_qstring, print_qstring, cfg_doc_terminal,
	&cfg_rep_string, NULL
};

/* Unquoted string only */
cfg_type_t cfg_type_ustring = {
	"string", parse_ustring, cfg_print_ustring, cfg_doc_terminal,
	&cfg_rep_string, NULL
};

/* Any string (quoted or unquoted); printed with quotes */
cfg_type_t cfg_type_astring = {
	"string", cfg_parse_astring, print_qstring, cfg_doc_terminal,
	&cfg_rep_string, NULL
};

/*
 * Booleans
 */

830 831 832 833 834 835
isc_boolean_t
cfg_obj_isboolean(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_boolean));
}

Brian Wellington's avatar
Brian Wellington committed
836 837 838 839 840 841
isc_boolean_t
cfg_obj_asboolean(cfg_obj_t *obj) {
	REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean);
	return (obj->value.boolean);
}

842
static isc_result_t
843
parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
844
{
845 846 847 848 849 850 851 852 853 854
        isc_result_t result;
	isc_boolean_t value;
	cfg_obj_t *obj = NULL;
	UNUSED(type);

	result = cfg_gettoken(pctx, 0);
	if (result != ISC_R_SUCCESS)
		return (result);

	if (pctx->token.type != isc_tokentype_string)
855
		goto bad_boolean;
856 857 858 859 860 861 862 863 864 865 866 867 868

	if ((strcasecmp(pctx->token.value.as_pointer, "true") == 0) ||
	    (strcasecmp(pctx->token.value.as_pointer, "yes") == 0) ||
	    (strcmp(pctx->token.value.as_pointer, "1") == 0)) {
		value = ISC_TRUE;
	} else if ((strcasecmp(pctx->token.value.as_pointer, "false") == 0) ||
		   (strcasecmp(pctx->token.value.as_pointer, "no") == 0) ||
		   (strcmp(pctx->token.value.as_pointer, "0") == 0)) {
		value = ISC_FALSE;
	} else {
		goto bad_boolean;
	}

869
	CHECK(cfg_create_obj(pctx, &cfg_type_boolean, &obj));
870 871 872 873 874
	obj->value.boolean = value;
	*ret = obj;
	return (result);

 bad_boolean:
875
	cfg_parser_error(pctx, CFG_LOG_NEAR, "boolean expected");
876 877 878 879 880 881 882 883 884
	return (ISC_R_UNEXPECTEDTOKEN);

 cleanup:
	return (result);
}

static void
print_boolean(cfg_printer_t *pctx, cfg_obj_t *obj) {
	if (obj->value.boolean)
885
		cfg_print_chars(pctx, "yes", 3);
886
	else
887
		cfg_print_chars(pctx, "no", 2);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
888
}
889

890 891 892
cfg_type_t cfg_type_boolean = {
	"boolean", parse_boolean, print_boolean, cfg_doc_terminal,
	&cfg_rep_boolean, NULL
893 894
};

895
/*
896
 * Lists.
897 898
 */

899 900
isc_result_t
cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) {
901
	isc_result_t result;
902
	CHECK(cfg_create_obj(pctx, type, obj));
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
	ISC_LIST_INIT((*obj)->value.list);
 cleanup:
	return (result);
}

static isc_result_t
create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) {
	cfg_listelt_t *elt;
	elt = isc_mem_get(pctx->mctx, sizeof(*elt));
	if (elt == NULL)
		return (ISC_R_NOMEMORY);
	elt->obj = NULL;
	ISC_LINK_INIT(elt, link);
	*eltp = elt;
	return (ISC_R_SUCCESS);
}

920 921 922 923 924 925
static void
free_list_elt(cfg_parser_t *pctx, cfg_listelt_t *elt) {
	cfg_obj_destroy(pctx, &elt->obj);
	isc_mem_put(pctx->mctx, elt, sizeof(*elt));
}

926 927 928 929 930 931 932 933
static void
free_list(cfg_parser_t *pctx, cfg_obj_t *obj) {
	cfg_listelt_t *elt, *next;
	for (elt = ISC_LIST_HEAD(obj->value.list);
	     elt != NULL;
	     elt = next)
	{
		next = ISC_LIST_NEXT(elt, link);
934
		free_list_elt(pctx, elt);
935 936 937
	}
}

938 939 940
isc_result_t
cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
		  cfg_listelt_t **ret)
941 942 943 944 945 946 947
{
	isc_result_t result;
	cfg_listelt_t *elt = NULL;
	cfg_obj_t *value = NULL;

	CHECK(create_listelt(pctx, &elt));

948
	result = cfg_parse_obj(pctx, elttype, &value);
949 950 951 952 953 954 955 956 957 958 959 960 961
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	elt->obj = value;

	*ret = elt;
	return (ISC_R_SUCCESS);

 cleanup:
	isc_mem_put(pctx->mctx, elt, sizeof(*elt));
	return (result);
}

962 963 964 965
/*
 * Parse a homogeneous list whose elements are of type 'elttype'
 * and where each element is terminated by a semicolon.
 */
966
static isc_result_t
967
parse_list(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret)
968 969
{
	cfg_obj_t *listobj = NULL;
970
	const cfg_type_t *listof = listtype->of;
971 972
	isc_result_t result;

973
	CHECK(cfg_create_list(pctx, listtype, &listobj));
974 975 976 977 978 979 980 981

	for (;;) {
		cfg_listelt_t *elt = NULL;

		CHECK(cfg_peektoken(pctx, 0));
		if (pctx->token.type == isc_tokentype_special &&
		    pctx->token.value.as_char == '}')
			break;
982
		CHECK(cfg_parse_listelt(pctx, listof, &elt));
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
		CHECK(parse_semicolon(pctx));
		ISC_LIST_APPEND(listobj->value.list, elt, link);
	}
	*ret = listobj;
	return (ISC_R_SUCCESS);

 cleanup:
	CLEANUP_OBJ(listobj);
	return (result);
}

static void
print_list(cfg_printer_t *pctx, cfg_obj_t *obj) {
	cfg_list_t *list = &obj->value.list;
	cfg_listelt_t *elt;

	for (elt = ISC_LIST_HEAD(*list);
	     elt != NULL;
	     elt = ISC_LIST_NEXT(elt, link)) {
		print_indent(pctx);
1003 1004
		cfg_print_obj(pctx, elt->obj);
		cfg_print_chars(pctx, ";\n", 2);
1005 1006 1007
	}
}

1008 1009 1010
isc_result_t
cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type,
		     cfg_obj_t **ret)
1011 1012
{
	isc_result_t result;
1013
	CHECK(cfg_parse_special(pctx, '{'));
1014
	CHECK(parse_list(pctx, type, ret));
1015
	CHECK(cfg_parse_special(pctx, '}'));
1016 1017 1018 1019
 cleanup:
	return (result);
}

1020 1021
void
cfg_print_bracketed_list(cfg_printer_t *pctx, cfg_obj_t *obj) {
1022 1023 1024 1025 1026
	print_open(pctx);
	print_list(pctx, obj);
	print_close(pctx);
}

1027 1028 1029 1030 1031 1032 1033
void
cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
	cfg_print_chars(pctx, "{ ", 2);
	cfg_doc_obj(pctx, type->of);
	cfg_print_chars(pctx, "; ... }", 7);
}

1034 1035 1036 1037 1038
/*
 * Parse a homogeneous list whose elements are of type 'elttype'
 * and where elements are separated by space.  The list ends
 * before the first semicolon.
 */
1039 1040 1041
isc_result_t
cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *listtype,
		    cfg_obj_t **ret)
1042 1043
{
	cfg_obj_t *listobj = NULL;
1044
	const cfg_type_t *listof = listtype->of;
1045 1046
	isc_result_t result;

1047
	CHECK(cfg_create_list(pctx, listtype, &listobj));
1048 1049 1050 1051 1052 1053 1054 1055

	for (;;) {
		cfg_listelt_t *elt = NULL;

		CHECK(cfg_peektoken(pctx, 0));
		if (pctx->token.type == isc_tokentype_special &&
		    pctx->token.value.as_char == ';')
			break;
1056
		CHECK(cfg_parse_listelt(pctx, listof, &elt));
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
		ISC_LIST_APPEND(listobj->value.list, elt, link);
	}
	*ret = listobj;
	return (ISC_R_SUCCESS);

 cleanup:
	CLEANUP_OBJ(listobj);
	return (result);
}

1067 1068
void
cfg_print_spacelist(cfg_printer_t *pctx, cfg_obj_t *obj) {
1069 1070 1071 1072 1073 1074
	cfg_list_t *list = &obj->value.list;
	cfg_listelt_t *elt;

	for (elt = ISC_LIST_HEAD(*list);
	     elt != NULL;
	     elt = ISC_LIST_NEXT(elt, link)) {
1075
		cfg_print_obj(pctx, elt->obj);
1076
		if (ISC_LIST_NEXT(elt, link) != NULL)
1077
			cfg_print_chars(pctx, " ", 1);
1078 1079 1080
	}
}

1081

1082 1083 1084 1085 1086 1087
isc_boolean_t
cfg_obj_islist(cfg_obj_t *obj) {
	REQUIRE(obj != NULL);
	return (ISC_TF(obj->type->rep == &cfg_rep_list));
}

1088 1089
cfg_listelt_t *
cfg_list_first(cfg_obj_t *obj) {
1090 1091 1092
	REQUIRE(obj == NULL || obj->type->rep == &cfg_rep_list);
	if (obj == NULL)
		return (NULL);
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
	return (ISC_LIST_HEAD(obj->value.list));
}

cfg_listelt_t *
cfg_list_next(cfg_listelt_t *elt) {
	REQUIRE(elt != NULL);
	return (ISC_LIST_NEXT(elt, link));
}

cfg_obj_t *
cfg_listelt_value(cfg_listelt_t *elt) {
	REQUIRE(elt != NULL);
	return (elt->obj);
}

/*
 * Maps.
 */

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
/*
 * Parse a map body.  That's something like
 *
 *   "foo 1; bar { glub; }; zap true; zap false;"
 *
 * i.e., a sequence of option names followed by values and
 * terminated by semicolons.  Used for the top level of
 * the named.conf syntax, as well as for the body of the
 * options, view, zone, and other statements.
 */
1122 1123
isc_result_t
cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
1124
{
1125
	const cfg_clausedef_t * const *clausesets = type->of;
1126
	isc_result_t result;
1127 1128
	const cfg_clausedef_t * const *clauseset;
	const cfg_clausedef_t *clause;
1129
	cfg_obj_t *value = NULL;
1130
	cfg_obj_t *obj = NULL;
1131 1132 1133 1134
	cfg_obj_t *eltobj = NULL;
	cfg_obj_t *includename = NULL;
	isc_symvalue_t symval;
	cfg_list_t *list = NULL;
1135

1136
	CHECK(create_map(pctx, type, &obj));
1137

1138
	obj->value.map.clausesets = clausesets;
1139

1140 1141
	for (;;) {
		cfg_listelt_t *elt;
1142

1143 1144 1145 1146 1147
	redo:
		/*
		 * Parse the option name and see if it is known.
		 */
		CHECK(cfg_gettoken(pctx, 0));
1148

1149 1150 1151 1152
		if (pctx->token.type != isc_tokentype_string) {
			cfg_ungettoken(pctx);
			break;
		}
1153

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
		/*
		 * We accept "include" statements wherever a map body
		 * clause can occur.
		 */
		if (strcasecmp(pctx->token.value.as_pointer, "include") == 0) {
			/*
			 * Turn the file name into a temporary configuration
			 * object just so that it is not overwritten by the
			 * semicolon token.
			 */
1164
			CHECK(cfg_parse_obj(pctx, &cfg_type_qstring, &includename));
1165 1166 1167 1168 1169 1170
			CHECK(parse_semicolon(pctx));
			CHECK(parser_openfile(pctx, includename->
					      value.string.base));
			 cfg_obj_destroy(pctx, &includename);
			 goto redo;
		}
1171

1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
		clause = NULL;
		for (clauseset = clausesets; *clauseset != NULL; clauseset++) {
			for (clause = *clauseset;
			     clause->name != NULL;
			     clause++) {
				if (strcasecmp(pctx->token.value.as_pointer,
					   clause->name) == 0)
					goto done;
			}
		}
	done:
		if (clause == NULL || clause->name == NULL) {
1184
			cfg_parser_error(pctx, CFG_LOG_NOPREP, "unknown option");
1185 1186 1187 1188
			/*
			 * Try to recover by parsing this option as an unknown
			 * option and discarding it.
			 */
1189 1190 1191 1192
			CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported, &eltobj));
			cfg_obj_destroy(pctx, &eltobj);
			CHECK(parse_semicolon(pctx));
			continue;
1193
		}
1194

1195
		/* Clause is known. */
1196

1197 1198
		/* Issue warnings if appropriate */
		if ((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0)
1199
			cfg_parser_warning(pctx, 0, "option '%s' is obsolete",
1200 1201
				       clause->name);
		if ((clause->flags & CFG_CLAUSEFLAG_NOTIMP) != 0)
1202
			cfg_parser_warning(pctx, 0, "option '%s' is "
1203