diff options
| author | 2020-02-07 09:58:52 +0000 | |
|---|---|---|
| committer | 2020-02-07 09:58:52 +0000 | |
| commit | 5185a7002a54c85c252cec8ac1712898b2c4d364 (patch) | |
| tree | 85c674e5d41ec98028ad29a3aac4db558edae254 /usr.bin/dig/lib/isccfg/parser.c | |
| parent | It appears we have come full-circle, where source code starts to use (diff) | |
| download | wireguard-openbsd-5185a7002a54c85c252cec8ac1712898b2c4d364.tar.xz wireguard-openbsd-5185a7002a54c85c252cec8ac1712898b2c4d364.zip | |
Move dig(1) and needed DNS libraries into it's own source directory in
usr.bin/dig.
From the beginning when we started to remove unneeded nameserver code,
it was our goal to extract dig functionality from the bind sources,
for everyone's benefit as this is easier to reason about.
In total we removed about 2/3 or over 300.000 lines of code.
We kept the lib/ subdirectory layout but moved the content of bin/ to
the top from the old bind source directory.
Previous sources and history can be found in the src/usr.sbin/bind
Attic.
With & OK deraadt
Proposed directory layout sounds good to sthen
Diffstat (limited to 'usr.bin/dig/lib/isccfg/parser.c')
| -rw-r--r-- | usr.bin/dig/lib/isccfg/parser.c | 1296 |
1 files changed, 1296 insertions, 0 deletions
diff --git a/usr.bin/dig/lib/isccfg/parser.c b/usr.bin/dig/lib/isccfg/parser.c new file mode 100644 index 00000000000..3471c609017 --- /dev/null +++ b/usr.bin/dig/lib/isccfg/parser.c @@ -0,0 +1,1296 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/*! \file */ + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/formatcheck.h> +#include <isc/lex.h> +#include <isc/log.h> +#include <isc/net.h> +#include <isc/netaddr.h> +#include <isc/netscope.h> +#include <isc/sockaddr.h> +#include <isc/symtab.h> +#include <isc/util.h> + +#include <isccfg/cfg.h> +#include <isccfg/grammar.h> + +/*! + * Pass one of these flags to cfg_parser_error() to include the + * token text in log message. + */ +#define CFG_LOG_NEAR 0x00000001 /*%< Say "near <token>" */ +#define CFG_LOG_BEFORE 0x00000002 /*%< Say "before <token>" */ +#define CFG_LOG_NOPREP 0x00000004 /*%< Say just "<token>" */ + +isc_logcategory_t cfg_category = { "config", 0 }; +isc_logmodule_t cfg_module = { "isccfg/parser", 0 }; + +/* Shorthand */ +#define CAT &cfg_category +#define MOD &cfg_module + +#define MAP_SYM 1 /* Unique type for isc_symtab */ + +#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base) + +#define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE) + +/* 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 variables */ +cfg_rep_t cfg_rep_string; +cfg_rep_t cfg_rep_list; + +cfg_type_t cfg_type_qstring; +cfg_type_t cfg_type_ustring; +cfg_type_t cfg_type_sstring; +cfg_type_t cfg_type_token; +cfg_type_t cfg_type_unsupported; + +/* + * Forward declarations of static functions. + */ + +static isc_result_t +cfg_gettoken(cfg_parser_t *pctx, int options); + +static isc_result_t +cfg_peektoken(cfg_parser_t *pctx, int options); + +static void +cfg_ungettoken(cfg_parser_t *pctx); + +static isc_result_t +cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static isc_result_t +cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +cfg_parse_special(cfg_parser_t *pctx, int special); +/*%< Parse a required special character 'special'. */ + +static isc_result_t +cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static isc_result_t +cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype, + cfg_listelt_t **ret); + +static isc_result_t +cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, + const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); + +static void +free_list(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp); + +static isc_result_t +create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, + cfg_obj_t **ret); + +static void +free_string(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static void +free_map(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_symtab_elt(cfg_parser_t *pctx, const char *name, + cfg_type_t *elttype, isc_symtab_t *symtab); + +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). + */ + +cfg_rep_t cfg_rep_string = { "string", free_string }; +cfg_rep_t cfg_rep_map = { "map", free_map }; +cfg_rep_t cfg_rep_list = { "list", free_list }; + +/* + * Configuration type definitions. + */ + +/* Functions. */ + +static isc_result_t +cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + result = type->parse(pctx, type, ret); + if (result != ISC_R_SUCCESS) + return (result); + ENSURE(*ret != NULL); + return (ISC_R_SUCCESS); +} + +static isc_result_t +cfg_parse_special(cfg_parser_t *pctx, int special) { + isc_result_t result; + + REQUIRE(pctx != NULL); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == special) + return (ISC_R_SUCCESS); + + cfg_parser_error(pctx, CFG_LOG_NEAR, "'%c' expected", special); + 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); + + cfg_parser_error(pctx, CFG_LOG_BEFORE, "missing ';'"); + 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); + + cfg_parser_error(pctx, CFG_LOG_NEAR, "syntax error"); + return (ISC_R_UNEXPECTEDTOKEN); + cleanup: + return (result); +} + +/* A list of files, used internally for pctx->files. */ + +static cfg_type_t cfg_type_filelist = { + "filelist", NULL, &cfg_rep_list, + &cfg_type_qstring +}; + +isc_result_t +cfg_parser_create(isc_log_t *lctx, cfg_parser_t **ret) { + isc_result_t result; + cfg_parser_t *pctx; + isc_lexspecials_t specials; + + REQUIRE(ret != NULL && *ret == NULL); + + pctx = malloc(sizeof(*pctx)); + if (pctx == NULL) + return (ISC_R_NOMEMORY); + + result = isc_refcount_init(&pctx->references, 1); + if (result != ISC_R_SUCCESS) { + free(pctx); + return (result); + } + + pctx->lctx = lctx; + pctx->lexer = NULL; + pctx->seen_eof = ISC_FALSE; + pctx->ungotten = ISC_FALSE; + pctx->errors = 0; + pctx->open_files = NULL; + pctx->closed_files = NULL; + pctx->line = 0; + pctx->token.type = isc_tokentype_unknown; + pctx->flags = 0; + + memset(specials, 0, sizeof(specials)); + specials['{'] = 1; + specials['}'] = 1; + specials[';'] = 1; + specials['/'] = 1; + specials['"'] = 1; + specials['!'] = 1; + + CHECK(isc_lex_create(1024, &pctx->lexer)); + + isc_lex_setspecials(pctx->lexer, specials); + isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C | + ISC_LEXCOMMENT_CPLUSPLUS | + ISC_LEXCOMMENT_SHELL)); + + CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files)); + CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->closed_files)); + + *ret = pctx; + return (ISC_R_SUCCESS); + + cleanup: + if (pctx->lexer != NULL) + isc_lex_destroy(&pctx->lexer); + CLEANUP_OBJ(pctx->open_files); + CLEANUP_OBJ(pctx->closed_files); + free(pctx); + return (result); +} + +static isc_result_t +parser_openfile(cfg_parser_t *pctx, const char *filename) { + 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) { + cfg_parser_error(pctx, 0, "open: %s: %s", + filename, isc_result_totext(result)); + goto cleanup; + } + + CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj)); + CHECK(create_listelt(pctx, &elt)); + elt->obj = stringobj; + ISC_LIST_APPEND(pctx->open_files->value.list, elt, link); + + return (ISC_R_SUCCESS); + cleanup: + CLEANUP_OBJ(stringobj); + return (result); +} + +/* + * Parse a configuration using a pctx where a lexer has already + * been set up with a source. + */ +static isc_result_t +parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + + result = cfg_parse_obj(pctx, type, &obj); + + 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. */ + cfg_parser_error(pctx, 0, "parsing failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + CHECK(parse_eof(pctx)); + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +isc_result_t +cfg_parse_file(cfg_parser_t *pctx, const char *filename, + const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(filename != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + CHECK(parser_openfile(pctx, filename)); + CHECK(parse2(pctx, type, ret)); + cleanup: + return (result); +} + +void +cfg_parser_destroy(cfg_parser_t **pctxp) { + cfg_parser_t *pctx; + unsigned int refs; + + REQUIRE(pctxp != NULL && *pctxp != NULL); + + pctx = *pctxp; + *pctxp = NULL; + + isc_refcount_decrement(&pctx->references, &refs); + if (refs == 0) { + isc_lex_destroy(&pctx->lexer); + /* + * 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); + free(pctx); + } +} + +/* + * qstring (quoted string), ustring (unquoted string), astring + * (any string) + */ + +/* Create a string object from a null-terminated C string. */ +static isc_result_t +create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + int len; + + CHECK(cfg_create_obj(pctx, type, &obj)); + len = strlen(contents); + obj->value.string.length = len; + obj->value.string.base = malloc(len + 1); + if (obj->value.string.base == 0) { + free(obj); + return (ISC_R_NOMEMORY); + } + memmove(obj->value.string.base, contents, len); + obj->value.string.base[len] = '\0'; + + *ret = obj; + cleanup: + return (result); +} + +static isc_result_t +cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type != isc_tokentype_qstring) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, TOKEN_STRING(pctx), + &cfg_type_qstring, ret)); + cleanup: + return (result); +} + +static isc_result_t +parse_ustring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected unquoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, + TOKEN_STRING(pctx), + &cfg_type_ustring, + ret)); + cleanup: + return (result); +} + +static isc_result_t +cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + UNUSED(type); + + CHECK(cfg_getstringtoken(pctx)); + return (create_string(pctx, + TOKEN_STRING(pctx), + &cfg_type_qstring, + ret)); + cleanup: + return (result); +} + +static isc_result_t +cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + UNUSED(type); + + CHECK(cfg_getstringtoken(pctx)); + return (create_string(pctx, + TOKEN_STRING(pctx), + &cfg_type_sstring, + ret)); + cleanup: + return (result); +} + +static void +free_string(cfg_parser_t *pctx, cfg_obj_t *obj) { + UNUSED(pctx); + free(obj->value.string.base); +} + +isc_boolean_t +cfg_obj_isstring(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_string)); +} + +const char * +cfg_obj_asstring(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string); + return (obj->value.string.base); +} + +/* Quoted string only */ +cfg_type_t cfg_type_qstring = { + "quoted_string", cfg_parse_qstring, &cfg_rep_string, NULL +}; + +/* Unquoted string only */ +cfg_type_t cfg_type_ustring = { + "string", parse_ustring, &cfg_rep_string, NULL +}; + +/* Any string (quoted or unquoted); printed with quotes */ +cfg_type_t cfg_type_astring = { + "string", cfg_parse_astring, &cfg_rep_string, NULL +}; + +/* + * Any string (quoted or unquoted); printed with quotes. + * If CFG_PRINTER_XKEY is set when printing the string will be '?' out. + */ +cfg_type_t cfg_type_sstring = { + "string", cfg_parse_sstring, &cfg_rep_string, NULL +}; + +/* + * Booleans + */ + +/* + * Lists. + */ + +static isc_result_t +cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) { + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(type != NULL); + REQUIRE(obj != NULL && *obj == NULL); + + CHECK(cfg_create_obj(pctx, type, obj)); + ISC_LIST_INIT((*obj)->value.list); + cleanup: + return (result); +} + +static isc_result_t +create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) { + UNUSED(pctx); + cfg_listelt_t *elt; + + elt = malloc(sizeof(*elt)); + if (elt == NULL) + return (ISC_R_NOMEMORY); + elt->obj = NULL; + ISC_LINK_INIT(elt, link); + *eltp = elt; + return (ISC_R_SUCCESS); +} + +static void +free_list_elt(cfg_parser_t *pctx, cfg_listelt_t *elt) { + cfg_obj_destroy(pctx, &elt->obj); + free(elt); +} + +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); + free_list_elt(pctx, elt); + } +} + +static isc_result_t +cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype, + cfg_listelt_t **ret) +{ + isc_result_t result; + cfg_listelt_t *elt = NULL; + cfg_obj_t *value = NULL; + + REQUIRE(pctx != NULL); + REQUIRE(elttype != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + CHECK(create_listelt(pctx, &elt)); + + result = cfg_parse_obj(pctx, elttype, &value); + if (result != ISC_R_SUCCESS) + goto cleanup; + + elt->obj = value; + + *ret = elt; + return (ISC_R_SUCCESS); + + cleanup: + free(elt); + return (result); +} + +isc_boolean_t +cfg_obj_islist(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_list)); +} + +const cfg_listelt_t * +cfg_list_first(const cfg_obj_t *obj) { + REQUIRE(obj == NULL || obj->type->rep == &cfg_rep_list); + if (obj == NULL) + return (NULL); + return (ISC_LIST_HEAD(obj->value.list)); +} + +const cfg_listelt_t * +cfg_list_next(const cfg_listelt_t *elt) { + REQUIRE(elt != NULL); + return (ISC_LIST_NEXT(elt, link)); +} + +/* + * Return the length of a list object. If obj is NULL or is not + * a list, return 0. + */ +unsigned int +cfg_list_length(const cfg_obj_t *obj, isc_boolean_t recurse) { + const cfg_listelt_t *elt; + unsigned int count = 0; + + if (obj == NULL || !cfg_obj_islist(obj)) + return (0U); + for (elt = cfg_list_first(obj); + elt != NULL; + elt = cfg_list_next(elt)) { + if (recurse && cfg_obj_islist(elt->obj)) { + count += cfg_list_length(elt->obj, recurse); + } else { + count++; + } + } + return (count); +} + +/* + * Maps. + */ + +/* + * 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. + */ +isc_result_t +cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + const cfg_clausedef_t * const *clausesets = type->of; + isc_result_t result; + const cfg_clausedef_t * const *clauseset; + const cfg_clausedef_t *clause; + cfg_obj_t *value = NULL; + cfg_obj_t *obj = NULL; + cfg_obj_t *eltobj = NULL; + cfg_obj_t *includename = NULL; + isc_symvalue_t symval; + + REQUIRE(pctx != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + CHECK(create_map(pctx, type, &obj)); + + obj->value.map.clausesets = clausesets; + + for (;;) { + redo: + /* + * Parse the option name and see if it is known. + */ + CHECK(cfg_gettoken(pctx, 0)); + + if (pctx->token.type != isc_tokentype_string) { + cfg_ungettoken(pctx); + break; + } + + /* + * We accept "include" statements wherever a map body + * clause can occur. + */ + if (strcasecmp(TOKEN_STRING(pctx), "include") == 0) { + /* + * Turn the file name into a temporary configuration + * object just so that it is not overwritten by the + * semicolon token. + */ + CHECK(cfg_parse_obj(pctx, &cfg_type_qstring, &includename)); + CHECK(parse_semicolon(pctx)); + CHECK(parser_openfile(pctx, includename-> + value.string.base)); + cfg_obj_destroy(pctx, &includename); + goto redo; + } + + clause = NULL; + for (clauseset = clausesets; *clauseset != NULL; clauseset++) { + for (clause = *clauseset; + clause->name != NULL; + clause++) { + if (strcasecmp(TOKEN_STRING(pctx), + clause->name) == 0) + goto done; + } + } + done: + if (clause == NULL || clause->name == NULL) { + cfg_parser_error(pctx, CFG_LOG_NOPREP, + "unknown option"); + /* + * Try to recover by parsing this option as an unknown + * option and discarding it. + */ + CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported, + &eltobj)); + cfg_obj_destroy(pctx, &eltobj); + CHECK(parse_semicolon(pctx)); + continue; + } + + /* Clause is known. */ + + /* See if the clause already has a value; if not create one. */ + result = isc_symtab_lookup(obj->value.map.symtab, + clause->name, 0, &symval); + + /* Single-valued clause */ + if (result == ISC_R_NOTFOUND) { + CHECK(parse_symtab_elt(pctx, clause->name, + clause->type, + obj->value.map.symtab)); + CHECK(parse_semicolon(pctx)); + } else if (result == ISC_R_SUCCESS) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "'%s' redefined", + clause->name); + result = ISC_R_EXISTS; + goto cleanup; + } else { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "isc_symtab_define() failed"); + goto cleanup; + } + } + + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(value); + CLEANUP_OBJ(obj); + CLEANUP_OBJ(eltobj); + CLEANUP_OBJ(includename); + return (result); +} + +static isc_result_t +parse_symtab_elt(cfg_parser_t *pctx, const char *name, + cfg_type_t *elttype, isc_symtab_t *symtab) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_symvalue_t symval; + + CHECK(cfg_parse_obj(pctx, elttype, &obj)); + + symval.as_pointer = obj; + CHECK(isc_symtab_define(symtab, name, + 1, symval, + isc_symexists_reject)); + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +/* + * Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }" + */ +static isc_result_t +cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + + REQUIRE(pctx != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + CHECK(cfg_parse_special(pctx, '{')); + CHECK(cfg_parse_mapbody(pctx, type, ret)); + CHECK(cfg_parse_special(pctx, '}')); + cleanup: + return (result); +} + +/* + * Subroutine for cfg_parse_named_map() and cfg_parse_addressed_map(). + */ +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) +{ + isc_result_t result; + cfg_obj_t *idobj = NULL; + cfg_obj_t *mapobj = NULL; + + REQUIRE(pctx != NULL); + REQUIRE(nametype != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + CHECK(cfg_parse_obj(pctx, nametype, &idobj)); + CHECK(cfg_parse_map(pctx, type, &mapobj)); + mapobj->value.map.id = idobj; + *ret = mapobj; + return (result); + cleanup: + CLEANUP_OBJ(idobj); + CLEANUP_OBJ(mapobj); + return (result); +} + +/* + * Parse a map identified by a string name. E.g., "name { foo 1; }". + * Used for the "key" and "channel" statements. + */ +isc_result_t +cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_any_named_map(pctx, &cfg_type_astring, type, ret)); +} + +isc_result_t +cfg_map_get(const cfg_obj_t *mapobj, const char* name, const cfg_obj_t **obj) { + isc_result_t result; + isc_symvalue_t val; + const cfg_map_t *map; + + REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); + REQUIRE(name != NULL); + REQUIRE(obj != NULL && *obj == NULL); + + map = &mapobj->value.map; + + result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val); + if (result != ISC_R_SUCCESS) + return (result); + *obj = val.as_pointer; + return (ISC_R_SUCCESS); +} + +const cfg_obj_t * +cfg_map_getname(const cfg_obj_t *mapobj) { + REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); + return (mapobj->value.map.id); +} + +/* Parse an arbitrary token, storing its raw text representation. */ +static isc_result_t +parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *obj = NULL; + isc_result_t result; + isc_region_t r; + + UNUSED(type); + + CHECK(cfg_create_obj(pctx, &cfg_type_token, &obj)); + CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_eof) { + cfg_ungettoken(pctx); + result = ISC_R_EOF; + goto cleanup; + } + + isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r); + + obj->value.string.base = malloc(r.length + 1); + if (obj->value.string.base == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + obj->value.string.length = r.length; + memmove(obj->value.string.base, r.base, r.length); + obj->value.string.base[r.length] = '\0'; + *ret = obj; + return (result); + + cleanup: + if (obj != NULL) + free(obj); + return (result); +} + +cfg_type_t cfg_type_token = { + "token", parse_token, &cfg_rep_string, NULL +}; + +/* + * An unsupported option. This is just a list of tokens with balanced braces + * ending in a semicolon. + */ + +static isc_result_t +parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *listobj = NULL; + isc_result_t result; + int braces = 0; + + CHECK(cfg_create_list(pctx, type, &listobj)); + + for (;;) { + cfg_listelt_t *elt = NULL; + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special) { + if (pctx->token.value.as_char == '{') + braces++; + else if (pctx->token.value.as_char == '}') + braces--; + else if (pctx->token.value.as_char == ';') + if (braces == 0) + break; + } + if (pctx->token.type == isc_tokentype_eof || braces < 0) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "unexpected token"); + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + CHECK(cfg_parse_listelt(pctx, &cfg_type_token, &elt)); + ISC_LIST_APPEND(listobj->value.list, elt, link); + } + INSIST(braces == 0); + *ret = listobj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(listobj); + return (result); +} + +cfg_type_t cfg_type_unsupported = { + "unsupported", parse_unsupported, &cfg_rep_list, NULL +}; + +static isc_result_t +cfg_gettoken(cfg_parser_t *pctx, int options) { + isc_result_t result; + + REQUIRE(pctx != NULL); + + if (pctx->seen_eof) + return (ISC_R_SUCCESS); + + options |= (ISC_LEXOPT_EOF | ISC_LEXOPT_NOMORE); + + redo: + pctx->token.type = isc_tokentype_unknown; + result = isc_lex_gettoken(pctx->lexer, options, &pctx->token); + pctx->ungotten = ISC_FALSE; + pctx->line = isc_lex_getsourceline(pctx->lexer); + + switch (result) { + case ISC_R_SUCCESS: + if (pctx->token.type == isc_tokentype_eof) { + result = isc_lex_close(pctx->lexer); + INSIST(result == ISC_R_NOMORE || + result == ISC_R_SUCCESS); + + if (isc_lex_getsourcename(pctx->lexer) != NULL) { + /* + * Closed an included file, not the main file. + */ + cfg_listelt_t *elt; + elt = ISC_LIST_TAIL(pctx->open_files-> + value.list); + INSIST(elt != NULL); + ISC_LIST_UNLINK(pctx->open_files-> + value.list, elt, link); + ISC_LIST_APPEND(pctx->closed_files-> + value.list, elt, link); + goto redo; + } + pctx->seen_eof = ISC_TRUE; + } + break; + + case ISC_R_NOSPACE: + /* More understandable than "ran out of space". */ + cfg_parser_error(pctx, CFG_LOG_NEAR, "token too big"); + break; + + case ISC_R_IOERROR: + cfg_parser_error(pctx, 0, "%s", + isc_result_totext(result)); + break; + + default: + cfg_parser_error(pctx, CFG_LOG_NEAR, "%s", + isc_result_totext(result)); + break; + } + return (result); +} + +static void +cfg_ungettoken(cfg_parser_t *pctx) { + REQUIRE(pctx != NULL); + + if (pctx->seen_eof) + return; + isc_lex_ungettoken(pctx->lexer, &pctx->token); + pctx->ungotten = ISC_TRUE; +} + +static isc_result_t +cfg_peektoken(cfg_parser_t *pctx, int options) { + isc_result_t result; + + REQUIRE(pctx != NULL); + + CHECK(cfg_gettoken(pctx, options)); + cfg_ungettoken(pctx); + cleanup: + return (result); +} + +/* + * Get a string token, accepting both the quoted and the unquoted form. + * Log an error if the next token is not a string. + */ +static isc_result_t +cfg_getstringtoken(cfg_parser_t *pctx) { + isc_result_t result; + + result = cfg_gettoken(pctx, CFG_LEXOPT_QSTRING); + if (result != ISC_R_SUCCESS) + return (result); + + if (pctx->token.type != isc_tokentype_string && + pctx->token.type != isc_tokentype_qstring) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (ISC_R_SUCCESS); +} + +static void +cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) { + va_list args; + + REQUIRE(pctx != NULL); + REQUIRE(fmt != NULL); + + va_start(args, fmt); + parser_complain(pctx, ISC_FALSE, flags, fmt, args); + va_end(args); + pctx->errors++; +} + +#define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */ + +static isc_boolean_t +have_current_file(cfg_parser_t *pctx) { + cfg_listelt_t *elt; + if (pctx->open_files == NULL) + return (ISC_FALSE); + + elt = ISC_LIST_TAIL(pctx->open_files->value.list); + if (elt == NULL) + return (ISC_FALSE); + + return (ISC_TRUE); +} + +static char * +current_file(cfg_parser_t *pctx) { + static char none[] = "none"; + cfg_listelt_t *elt; + cfg_obj_t *fileobj; + + if (!have_current_file(pctx)) + return (none); + + elt = ISC_LIST_TAIL(pctx->open_files->value.list); + if (elt == NULL) /* shouldn't be possible, but... */ + return (none); + + fileobj = elt->obj; + INSIST(fileobj->type == &cfg_type_qstring); + return (fileobj->value.string.base); +} + +static void +parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning, + unsigned int flags, const char *format, + va_list args) +{ + char tokenbuf[MAX_LOG_TOKEN + 10]; + static char where[PATH_MAX + 100]; + static char message[2048]; + int level = ISC_LOG_ERROR; + const char *prep = ""; + size_t len; + + if (is_warning) + level = ISC_LOG_WARNING; + + where[0] = '\0'; + if (have_current_file(pctx)) + snprintf(where, sizeof(where), "%s:%u: ", + current_file(pctx), pctx->line); + + len = vsnprintf(message, sizeof(message), format, args); +#define ELIPSIS " ... " + if (len >= sizeof(message)) { + message[sizeof(message) - sizeof(ELIPSIS)] = 0; + strlcat(message, ELIPSIS, sizeof(message)); + } + + if ((flags & (CFG_LOG_NEAR|CFG_LOG_BEFORE|CFG_LOG_NOPREP)) != 0) { + isc_region_t r; + + if (pctx->ungotten) + (void)cfg_gettoken(pctx, 0); + + if (pctx->token.type == isc_tokentype_eof) { + snprintf(tokenbuf, sizeof(tokenbuf), "end of file"); + } else if (pctx->token.type == isc_tokentype_unknown) { + flags = 0; + tokenbuf[0] = '\0'; + } else { + isc_lex_getlasttokentext(pctx->lexer, + &pctx->token, &r); + if (r.length > MAX_LOG_TOKEN) + snprintf(tokenbuf, sizeof(tokenbuf), + "'%.*s...'", MAX_LOG_TOKEN, r.base); + else + snprintf(tokenbuf, sizeof(tokenbuf), + "'%.*s'", (int)r.length, r.base); + } + + /* Choose a preposition. */ + if (flags & CFG_LOG_NEAR) + prep = " near "; + else if (flags & CFG_LOG_BEFORE) + prep = " before "; + else + prep = " "; + } else { + tokenbuf[0] = '\0'; + } + isc_log_write(pctx->lctx, CAT, MOD, level, + "%s%s%s%s", where, message, prep, tokenbuf); +} + +static isc_result_t +cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj; + + REQUIRE(pctx != NULL); + REQUIRE(type != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + obj = malloc(sizeof(cfg_obj_t)); + if (obj == NULL) + return (ISC_R_NOMEMORY); + obj->type = type; + obj->file = current_file(pctx); + obj->line = pctx->line; + result = isc_refcount_init(&obj->references, 1); + if (result != ISC_R_SUCCESS) { + free(obj); + return (result); + } + *ret = obj; + return (ISC_R_SUCCESS); +} + + +static void +map_symtabitem_destroy(char *key, unsigned int type, + isc_symvalue_t symval, void *userarg) +{ + cfg_obj_t *obj = symval.as_pointer; + cfg_parser_t *pctx = (cfg_parser_t *)userarg; + + UNUSED(key); + UNUSED(type); + + cfg_obj_destroy(pctx, &obj); +} + + +static isc_result_t +create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + isc_symtab_t *symtab = NULL; + cfg_obj_t *obj = NULL; + + CHECK(cfg_create_obj(pctx, type, &obj)); + CHECK(isc_symtab_create(5, /* XXX */ + map_symtabitem_destroy, + pctx, ISC_FALSE, &symtab)); + obj->value.map.symtab = symtab; + obj->value.map.id = NULL; + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + if (obj != NULL) + free(obj); + return (result); +} + +static void +free_map(cfg_parser_t *pctx, cfg_obj_t *obj) { + CLEANUP_OBJ(obj->value.map.id); + isc_symtab_destroy(&obj->value.map.symtab); +} + +/* + * Destroy 'obj', a configuration object created in 'pctx'. + */ +void +cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) { + cfg_obj_t *obj; + unsigned int refs; + + REQUIRE(objp != NULL && *objp != NULL); + REQUIRE(pctx != NULL); + + obj = *objp; + + isc_refcount_decrement(&obj->references, &refs); + if (refs == 0) { + obj->type->rep->free(pctx, obj); + isc_refcount_destroy(&obj->references); + free(obj); + } + *objp = NULL; +} |
