summaryrefslogtreecommitdiffstats
path: root/usr.bin/dig/lib/isccfg/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/dig/lib/isccfg/parser.c')
-rw-r--r--usr.bin/dig/lib/isccfg/parser.c1296
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;
+}