summaryrefslogtreecommitdiffstats
path: root/lib/isccfg/parser.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/isccfg/parser.c3901
1 files changed, 3901 insertions, 0 deletions
diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c
new file mode 100644
index 0000000..8bee734
--- /dev/null
+++ b/lib/isccfg/parser.c
@@ -0,0 +1,3901 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Copyright (c) 2009-2018 NLNet Labs.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/errno.h>
+#include <isc/formatcheck.h>
+#include <isc/glob.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/netmgr.h>
+#include <isc/netscope.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/symtab.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/log.h>
+
+/* Shorthand */
+#define CAT CFG_LOGCATEGORY_CONFIG
+#define MOD CFG_LOGMODULE_PARSER
+
+#define MAP_SYM 1 /* Unique type for isc_symtab */
+
+#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base)
+
+/* 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
+free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+parse_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+print_list(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+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, bool callback);
+
+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, bool is_warning, unsigned int flags,
+ const char *format, va_list args);
+
+#if defined(HAVE_GEOIP2)
+static isc_result_t
+parse_geoip(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+print_geoip(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type);
+#endif /* HAVE_GEOIP2 */
+
+/*
+ * 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_uint32 = { "uint32", free_noop };
+cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
+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 };
+cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop };
+cfg_rep_t cfg_rep_percentage = { "percentage", free_noop };
+cfg_rep_t cfg_rep_duration = { "duration", free_noop };
+
+/*
+ * Configuration type definitions.
+ */
+
+/*%
+ * An implicit list. These are formed by clauses that occur multiple times.
+ */
+static cfg_type_t cfg_type_implicitlist = { "implicitlist", NULL,
+ print_list, NULL,
+ &cfg_rep_list, NULL };
+
+/* Functions. */
+
+void
+cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ obj->type->print(pctx, obj);
+}
+
+void
+cfg_print_chars(cfg_printer_t *pctx, const char *text, int len) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(text != NULL);
+
+ pctx->f(pctx->closure, text, len);
+}
+
+static void
+print_open(cfg_printer_t *pctx) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_cstr(pctx, "{ ");
+ } else {
+ cfg_print_cstr(pctx, "{\n");
+ pctx->indent++;
+ }
+}
+
+void
+cfg_print_indent(cfg_printer_t *pctx) {
+ int indent = pctx->indent;
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_cstr(pctx, " ");
+ return;
+ }
+ while (indent > 0) {
+ cfg_print_cstr(pctx, "\t");
+ indent--;
+ }
+}
+
+static void
+print_close(cfg_printer_t *pctx) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ pctx->indent--;
+ cfg_print_indent(pctx);
+ }
+ cfg_print_cstr(pctx, "}");
+}
+
+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);
+}
+
+void
+cfg_print(const cfg_obj_t *obj,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ REQUIRE(obj != NULL);
+ REQUIRE(f != NULL);
+
+ cfg_printx(obj, 0, f, closure);
+}
+
+void
+cfg_printx(const cfg_obj_t *obj, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ cfg_printer_t pctx;
+
+ REQUIRE(obj != NULL);
+ REQUIRE(f != NULL);
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+ obj->type->print(&pctx, obj);
+}
+
+/* Tuples. */
+
+isc_result_t
+cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ cfg_obj_t *obj = NULL;
+ unsigned int nfields = 0;
+ int i;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ fields = type->of;
+
+ for (f = fields; f->name != NULL; f++) {
+ nfields++;
+ }
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ obj->value.tuple = isc_mem_get(pctx->mctx,
+ nfields * sizeof(cfg_obj_t *));
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ obj->value.tuple[i] = NULL;
+ }
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (obj != NULL) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ }
+ return (result);
+}
+
+isc_result_t
+cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ cfg_obj_t *obj = NULL;
+ unsigned int i;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[i]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+void
+cfg_print_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ bool need_space = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ fields = obj->type->of;
+
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ const cfg_obj_t *fieldobj = obj->value.tuple[i];
+ if (need_space && fieldobj->type->rep != &cfg_rep_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_print_obj(pctx, fieldobj);
+ need_space = (need_space ||
+ fieldobj->type->print != cfg_print_void);
+ }
+}
+
+void
+cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ bool need_space = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ fields = type->of;
+
+ for (f = fields; f->name != NULL; f++) {
+ if (need_space) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_doc_obj(pctx, f->type);
+ need_space = (f->type->print != cfg_print_void);
+ }
+}
+
+static void
+free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields = obj->type->of;
+ const cfg_tuplefielddef_t *f;
+ unsigned int nfields = 0;
+
+ if (obj->value.tuple == NULL) {
+ return;
+ }
+
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ CLEANUP_OBJ(obj->value.tuple[i]);
+ nfields++;
+ }
+ isc_mem_put(pctx->mctx, obj->value.tuple,
+ nfields * sizeof(cfg_obj_t *));
+}
+
+bool
+cfg_obj_istuple(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_tuple);
+}
+
+const cfg_obj_t *
+cfg_tuple_get(const cfg_obj_t *tupleobj, const char *name) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+
+ REQUIRE(tupleobj != NULL && tupleobj->type->rep == &cfg_rep_tuple);
+ REQUIRE(name != NULL);
+
+ 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]);
+ }
+ }
+ UNREACHABLE();
+}
+
+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,
+ print_list, NULL,
+ &cfg_rep_list, &cfg_type_qstring };
+
+isc_result_t
+cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret) {
+ 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));
+
+ pctx->mctx = NULL;
+ isc_mem_attach(mctx, &pctx->mctx);
+
+ isc_refcount_init(&pctx->references, 1);
+
+ pctx->lctx = lctx;
+ pctx->lexer = NULL;
+ pctx->seen_eof = false;
+ pctx->ungotten = false;
+ pctx->errors = 0;
+ pctx->warnings = 0;
+ pctx->open_files = NULL;
+ pctx->closed_files = NULL;
+ pctx->line = 0;
+ pctx->callback = NULL;
+ pctx->callbackarg = NULL;
+ pctx->token.type = isc_tokentype_unknown;
+ pctx->flags = 0;
+ pctx->buf_name = NULL;
+
+ 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));
+
+ 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);
+ isc_mem_putanddetach(&pctx->mctx, pctx, sizeof(*pctx));
+ return (result);
+}
+
+void
+cfg_parser_setflags(cfg_parser_t *pctx, unsigned int flags, bool turn_on) {
+ REQUIRE(pctx != NULL);
+
+ if (turn_on) {
+ pctx->flags |= flags;
+ } else {
+ pctx->flags &= ~flags;
+ }
+}
+
+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);
+}
+
+void
+cfg_parser_setcallback(cfg_parser_t *pctx, cfg_parsecallback_t callback,
+ void *arg) {
+ REQUIRE(pctx != NULL);
+
+ pctx->callback = callback;
+ pctx->callbackarg = arg;
+}
+
+void
+cfg_parser_reset(cfg_parser_t *pctx) {
+ REQUIRE(pctx != NULL);
+
+ if (pctx->lexer != NULL) {
+ isc_lex_close(pctx->lexer);
+ }
+
+ pctx->seen_eof = false;
+ pctx->ungotten = false;
+ pctx->errors = 0;
+ pctx->warnings = 0;
+ pctx->line = 0;
+}
+
+/*
+ * 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;
+ cfg_listelt_t *elt;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(filename != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(parser_openfile(pctx, filename));
+
+ result = parse2(pctx, type, ret);
+
+ /* Clean up the opened file */
+ 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);
+
+cleanup:
+ return (result);
+}
+
+isc_result_t
+cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer, const char *file,
+ unsigned int line, const cfg_type_t *type, unsigned int flags,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(buffer != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+ REQUIRE((flags & ~(CFG_PCTX_NODEPRECATED)) == 0);
+
+ CHECK(isc_lex_openbuffer(pctx->lexer, buffer));
+
+ pctx->buf_name = file;
+ pctx->flags = flags;
+
+ if (line != 0U) {
+ CHECK(isc_lex_setsourceline(pctx->lexer, line));
+ }
+
+ CHECK(parse2(pctx, type, ret));
+ pctx->buf_name = NULL;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_parser_attach(cfg_parser_t *src, cfg_parser_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+void
+cfg_parser_destroy(cfg_parser_t **pctxp) {
+ cfg_parser_t *pctx;
+
+ REQUIRE(pctxp != NULL && *pctxp != NULL);
+ pctx = *pctxp;
+ *pctxp = NULL;
+
+ if (isc_refcount_decrement(&pctx->references) == 1) {
+ 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);
+ isc_mem_putanddetach(&pctx->mctx, pctx, sizeof(*pctx));
+ }
+}
+
+/*
+ * void
+ */
+isc_result_t
+cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ return (cfg_create_obj(pctx, &cfg_type_void, ret));
+}
+
+void
+cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ UNUSED(pctx);
+ UNUSED(obj);
+}
+
+void
+cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ UNUSED(pctx);
+ UNUSED(type);
+}
+
+bool
+cfg_obj_isvoid(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_void);
+}
+
+cfg_type_t cfg_type_void = { "void", cfg_parse_void, cfg_print_void,
+ cfg_doc_void, &cfg_rep_void, NULL };
+
+/*
+ * percentage
+ */
+isc_result_t
+cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ char *endp;
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ uint64_t percent;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected percentage");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ percent = strtoull(TOKEN_STRING(pctx), &endp, 10);
+ if (*endp != '%' || *(endp + 1) != 0) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected percentage");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_percentage, &obj));
+ obj->value.uint32 = (uint32_t)percent;
+ *ret = obj;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[64];
+ int n;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ n = snprintf(buf, sizeof(buf), "%u%%", obj->value.uint32);
+ INSIST(n > 0 && (size_t)n < sizeof(buf));
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+uint32_t
+cfg_obj_aspercentage(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_percentage);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_percentage = { "percentage", cfg_parse_percentage,
+ cfg_print_percentage, cfg_doc_terminal,
+ &cfg_rep_percentage, NULL };
+
+bool
+cfg_obj_ispercentage(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_percentage);
+}
+
+/*
+ * Fixed point
+ */
+isc_result_t
+cfg_parse_fixedpoint(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ size_t n1, n2, n3, l;
+ const char *p;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected fixed point number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ p = TOKEN_STRING(pctx);
+ l = strlen(p);
+ n1 = strspn(p, "0123456789");
+ n2 = strspn(p + n1, ".");
+ n3 = strspn(p + n1 + n2, "0123456789");
+
+ if ((n1 + n2 + n3 != l) || (n1 + n3 == 0) || n1 > 5 || n2 > 1 || n3 > 2)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected fixed point number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_fixedpoint, &obj));
+
+ obj->value.uint32 = strtoul(p, NULL, 10) * 100;
+ switch (n3) {
+ case 2:
+ obj->value.uint32 += strtoul(p + n1 + n2, NULL, 10);
+ break;
+ case 1:
+ obj->value.uint32 += strtoul(p + n1 + n2, NULL, 10) * 10;
+ break;
+ }
+ *ret = obj;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_fixedpoint(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[64];
+ int n;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ n = snprintf(buf, sizeof(buf), "%u.%02u", obj->value.uint32 / 100,
+ obj->value.uint32 % 100);
+ INSIST(n > 0 && (size_t)n < sizeof(buf));
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+uint32_t
+cfg_obj_asfixedpoint(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_fixedpoint);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_fixedpoint = { "fixedpoint", cfg_parse_fixedpoint,
+ cfg_print_fixedpoint, cfg_doc_terminal,
+ &cfg_rep_fixedpoint, NULL };
+
+bool
+cfg_obj_isfixedpoint(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_fixedpoint);
+}
+
+/*
+ * uint32
+ */
+isc_result_t
+cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj));
+
+ obj->value.uint32 = pctx->token.value.as_ulong;
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_cstr(cfg_printer_t *pctx, const char *s) {
+ cfg_print_chars(pctx, s, strlen(s));
+}
+
+void
+cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u) {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%u", u);
+ cfg_print_cstr(pctx, buf);
+}
+
+void
+cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_rawuint(pctx, obj->value.uint32);
+}
+
+bool
+cfg_obj_isuint32(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_uint32);
+}
+
+uint32_t
+cfg_obj_asuint32(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_uint32 = { "integer", cfg_parse_uint32,
+ cfg_print_uint32, cfg_doc_terminal,
+ &cfg_rep_uint32, NULL };
+
+/*
+ * uint64
+ */
+bool
+cfg_obj_isuint64(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_uint64);
+}
+
+uint64_t
+cfg_obj_asuint64(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64);
+ return (obj->value.uint64);
+}
+
+void
+cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%" PRIu64, obj->value.uint64);
+ cfg_print_cstr(pctx, buf);
+}
+
+cfg_type_t cfg_type_uint64 = { "64_bit_integer", NULL,
+ cfg_print_uint64, cfg_doc_terminal,
+ &cfg_rep_uint64, NULL };
+
+/*
+ * Get the number of digits in a number.
+ */
+static size_t
+numlen(uint32_t num) {
+ uint32_t period = num;
+ size_t count = 0;
+
+ if (period == 0) {
+ return (1);
+ }
+ while (period > 0) {
+ count++;
+ period /= 10;
+ }
+ return (count);
+}
+
+/*
+ * duration
+ */
+void
+cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[CFG_DURATION_MAXLEN];
+ char *str;
+ const char *indicators = "YMWDHMS";
+ int count, i;
+ int durationlen[7] = { 0 };
+ isccfg_duration_t duration;
+ /*
+ * D ? The duration has a date part.
+ * T ? The duration has a time part.
+ */
+ bool D = false, T = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ duration = obj->value.duration;
+
+ /* If this is not an ISO 8601 duration, just print it as a number. */
+ if (!duration.iso8601) {
+ cfg_print_rawuint(pctx, duration.parts[6]);
+ return;
+ }
+
+ /* Calculate length of string. */
+ buf[0] = 'P';
+ buf[1] = '\0';
+ str = &buf[1];
+ count = 2;
+ for (i = 0; i < 6; i++) {
+ if (duration.parts[i] > 0) {
+ durationlen[i] = 1 + numlen(duration.parts[i]);
+ if (i < 4) {
+ D = true;
+ } else {
+ T = true;
+ }
+ count += durationlen[i];
+ }
+ }
+ /*
+ * Special case for seconds which is not taken into account in the
+ * above for loop: Count the length of the seconds part if it is
+ * non-zero, or if all the other parts are also zero. In the latter
+ * case this function will print "PT0S".
+ */
+ if (duration.parts[6] > 0 ||
+ (!D && !duration.parts[4] && !duration.parts[5]))
+ {
+ durationlen[6] = 1 + numlen(duration.parts[6]);
+ T = true;
+ count += durationlen[6];
+ }
+ /* Add one character for the time indicator. */
+ if (T) {
+ count++;
+ }
+ INSIST(count < CFG_DURATION_MAXLEN);
+
+ /* Now print the duration. */
+ for (i = 0; i < 6; i++) {
+ /*
+ * We don't check here if weeks and other time indicator are
+ * used mutually exclusively.
+ */
+ if (duration.parts[i] > 0) {
+ snprintf(str, durationlen[i] + 2, "%u%c",
+ (uint32_t)duration.parts[i], indicators[i]);
+ str += durationlen[i];
+ }
+ if (i == 3 && T) {
+ snprintf(str, 2, "T");
+ str += 1;
+ }
+ }
+ /* Special case for seconds. */
+ if (duration.parts[6] > 0 ||
+ (!D && !duration.parts[4] && !duration.parts[5]))
+ {
+ snprintf(str, durationlen[6] + 2, "%u%c",
+ (uint32_t)duration.parts[6], indicators[6]);
+ }
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+void
+cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isccfg_duration_t duration;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ duration = obj->value.duration;
+
+ if (duration.unlimited) {
+ cfg_print_cstr(pctx, "unlimited");
+ } else {
+ cfg_print_duration(pctx, obj);
+ }
+}
+
+bool
+cfg_obj_isduration(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_duration);
+}
+
+uint32_t
+cfg_obj_asduration(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration);
+ return isccfg_duration_toseconds(&(obj->value.duration));
+}
+
+static isc_result_t
+parse_duration(cfg_parser_t *pctx, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isccfg_duration_t duration;
+
+ result = isccfg_parse_duration(&pctx->token.value.as_textregion,
+ &duration);
+
+ if (result == ISC_R_RANGE) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "duration or TTL out of range");
+ return (result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj));
+ obj->value.duration = duration;
+ *ret = obj;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration or TTL value");
+ return (result);
+}
+
+isc_result_t
+cfg_parse_duration(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) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ return (parse_duration(pctx, ret));
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration or TTL value");
+ return (result);
+}
+
+isc_result_t
+cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isccfg_duration_t duration;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ if (strcmp(TOKEN_STRING(pctx), "unlimited") == 0) {
+ for (int i = 0; i < 7; i++) {
+ duration.parts[i] = 0;
+ }
+ duration.iso8601 = false;
+ duration.unlimited = true;
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj));
+ obj->value.duration = duration;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+ return (parse_duration(pctx, ret));
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration, TTL value, or unlimited");
+ return (result);
+}
+
+/*%
+ * A duration as defined by ISO 8601 (P[n]Y[n]M[n]DT[n]H[n]M[n]S).
+ * - P is the duration indicator ("period") placed at the start.
+ * - Y is the year indicator that follows the value for the number of years.
+ * - M is the month indicator that follows the value for the number of months.
+ * - D is the day indicator that follows the value for the number of days.
+ * - T is the time indicator that precedes the time components.
+ * - H is the hour indicator that follows the value for the number of hours.
+ * - M is the minute indicator that follows the value for the number of
+ * minutes.
+ * - S is the second indicator that follows the value for the number of
+ * seconds.
+ *
+ * A duration can also be a TTL value (number + optional unit).
+ */
+cfg_type_t cfg_type_duration = { "duration", cfg_parse_duration,
+ cfg_print_duration, cfg_doc_terminal,
+ &cfg_rep_duration, NULL };
+cfg_type_t cfg_type_duration_or_unlimited = { "duration_or_unlimited",
+ cfg_parse_duration_or_unlimited,
+ cfg_print_duration_or_unlimited,
+ cfg_doc_terminal,
+ &cfg_rep_duration,
+ NULL };
+
+/*
+ * qstring (quoted string), ustring (unquoted string), astring
+ * (any string), sstring (secret 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 = isc_mem_get(pctx->mctx, len + 1);
+ if (obj->value.string.base == 0) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ return (ISC_R_NOMEMORY);
+ }
+ memmove(obj->value.string.base, contents, len);
+ obj->value.string.base[len] = '\0';
+
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+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);
+}
+
+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);
+}
+
+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 isc_result_t
+parse_btext(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_BTEXT));
+ if (pctx->token.type != isc_tokentype_btext) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected bracketed text");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (create_string(pctx, TOKEN_STRING(pctx),
+ &cfg_type_bracketed_text, ret));
+cleanup:
+ return (result);
+}
+
+static void
+print_btext(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ /*
+ * We need to print "{" instead of running print_open()
+ * in order to preserve the exact original formatting
+ * of the bracketed text. But we increment the indent value
+ * so that print_close() will leave us back in our original
+ * state.
+ */
+ pctx->indent++;
+ cfg_print_cstr(pctx, "{");
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+ print_close(pctx);
+}
+
+static void
+doc_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "{ <unspecified-text> }");
+}
+
+bool
+cfg_is_enum(const char *s, const char *const *enums) {
+ const char *const *p;
+
+ REQUIRE(s != NULL);
+ REQUIRE(enums != NULL);
+
+ for (p = enums; *p != NULL; p++) {
+ if (strcasecmp(*p, s) == 0) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static isc_result_t
+check_enum(cfg_parser_t *pctx, cfg_obj_t *obj, const char *const *enums) {
+ const char *s = obj->value.string.base;
+
+ if (cfg_is_enum(s, enums)) {
+ return (ISC_R_SUCCESS);
+ }
+ cfg_parser_error(pctx, 0, "'%s' unexpected", s);
+ return (ISC_R_UNEXPECTEDTOKEN);
+}
+
+isc_result_t
+cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == 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);
+}
+
+void
+cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const char *const *p;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "( ");
+ for (p = type->of; *p != NULL; p++) {
+ cfg_print_cstr(pctx, *p);
+ if (p[1] != NULL) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ }
+ cfg_print_cstr(pctx, " )");
+}
+
+isc_result_t
+cfg_parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype, cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ cfg_is_enum(TOKEN_STRING(pctx), enumtype->of))
+ {
+ CHECK(cfg_parse_enum(pctx, enumtype, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, othertype, ret));
+ }
+cleanup:
+ return (result);
+}
+
+void
+cfg_doc_enum_or_other(cfg_printer_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype) {
+ const char *const *p;
+ bool first = true;
+
+ /*
+ * If othertype is cfg_type_void, it means that enumtype is
+ * optional.
+ */
+
+ if (othertype == &cfg_type_void) {
+ cfg_print_cstr(pctx, "[ ");
+ }
+ cfg_print_cstr(pctx, "( ");
+ for (p = enumtype->of; *p != NULL; p++) {
+ if (!first) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ first = false;
+ cfg_print_cstr(pctx, *p);
+ }
+ if (othertype != &cfg_type_void) {
+ if (!first) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_doc_terminal(pctx, othertype);
+ }
+ cfg_print_cstr(pctx, " )");
+ if (othertype == &cfg_type_void) {
+ cfg_print_cstr(pctx, " ]");
+ }
+}
+
+void
+cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+}
+
+static void
+print_qstring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "\"");
+ for (size_t i = 0; i < obj->value.string.length; i++) {
+ if (obj->value.string.base[i] == '"') {
+ cfg_print_cstr(pctx, "\\");
+ }
+ cfg_print_chars(pctx, &obj->value.string.base[i], 1);
+ }
+ cfg_print_cstr(pctx, "\"");
+}
+
+static void
+print_sstring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "\"");
+ if ((pctx->flags & CFG_PRINTER_XKEY) != 0) {
+ unsigned int len = obj->value.string.length;
+ while (len-- > 0) {
+ cfg_print_cstr(pctx, "?");
+ }
+ } else {
+ cfg_print_ustring(pctx, obj);
+ }
+ cfg_print_cstr(pctx, "\"");
+}
+
+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);
+}
+
+bool
+cfg_obj_isstring(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (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,
+ 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 };
+
+/*
+ * 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,
+ print_sstring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/*
+ * Text enclosed in brackets. Used to pass a block of configuration
+ * text to dynamic library or external application. Checked for
+ * bracket balance, but not otherwise parsed.
+ */
+cfg_type_t cfg_type_bracketed_text = { "bracketed_text", parse_btext,
+ print_btext, doc_btext,
+ &cfg_rep_string, NULL };
+
+#if defined(HAVE_GEOIP2)
+/*
+ * "geoip" ACL element:
+ * geoip [ db <database> ] search-type <string>
+ */
+static const char *geoiptype_enums[] = {
+ "area", "areacode", "asnum", "city", "continent",
+ "country", "country3", "countryname", "domain", "isp",
+ "metro", "metrocode", "netspeed", "org", "postal",
+ "postalcode", "region", "regionname", "timezone", "tz",
+ NULL
+};
+static cfg_type_t cfg_type_geoiptype = { "geoiptype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &geoiptype_enums };
+
+static cfg_tuplefielddef_t geoip_fields[] = {
+ { "negated", &cfg_type_void, 0 },
+ { "db", &cfg_type_astring, 0 },
+ { "subtype", &cfg_type_geoiptype, 0 },
+ { "search", &cfg_type_astring, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_geoip = { "geoip", parse_geoip, print_geoip,
+ doc_geoip, &cfg_rep_tuple, geoip_fields };
+
+static isc_result_t
+parse_geoip(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const cfg_tuplefielddef_t *fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[0]));
+
+ /* Parse the optional "db" field. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "db") == 0 &&
+ obj->value.tuple[1] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ } else {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ cfg_ungettoken(pctx);
+ }
+ }
+
+ CHECK(cfg_parse_obj(pctx, fields[2].type, &obj->value.tuple[2]));
+ CHECK(cfg_parse_obj(pctx, fields[3].type, &obj->value.tuple[3]));
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_geoip(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->value.tuple[1]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " db ");
+ cfg_print_obj(pctx, obj->value.tuple[1]);
+ }
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+}
+
+static void
+doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ db ");
+ cfg_doc_obj(pctx, &cfg_type_astring);
+ cfg_print_cstr(pctx, " ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_enum(pctx, &cfg_type_geoiptype);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, &cfg_type_astring);
+}
+#endif /* HAVE_GEOIP2 */
+
+static cfg_type_t cfg_type_addrmatchelt;
+static cfg_type_t cfg_type_negated;
+
+static isc_result_t
+parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (pctx->token.type == isc_tokentype_string &&
+ (strcasecmp(TOKEN_STRING(pctx), "key") == 0))
+ {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_keyref, ret));
+ } else if (pctx->token.type == isc_tokentype_string &&
+ (strcasecmp(TOKEN_STRING(pctx), "geoip") == 0))
+ {
+#if defined(HAVE_GEOIP2)
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_obj(pctx, &cfg_type_geoip, ret));
+#else /* if defined(HAVE_GEOIP2) */
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "'geoip' "
+ "not supported in this build");
+ return (ISC_R_UNEXPECTEDTOKEN);
+#endif /* if defined(HAVE_GEOIP2) */
+ } else {
+ if (cfg_lookingat_netaddr(
+ pctx, CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK |
+ CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_netprefix(pctx, NULL, ret));
+ } else {
+ CHECK(cfg_parse_astring(pctx, NULL, ret));
+ }
+ }
+ } else if (pctx->token.type == isc_tokentype_special) {
+ if (pctx->token.value.as_char == '{') {
+ /* Nested match list. */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_aml,
+ ret));
+ } else if (pctx->token.value.as_char == '!') {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "!" */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_negated, ret));
+ } else {
+ goto bad;
+ }
+ } else {
+ bad:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP match list element");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ return (result);
+}
+
+/*%
+ * A negated address match list element (like "! 10.0.0.1").
+ * Somewhat sneakily, the caller is expected to parse the
+ * "!", but not to print it.
+ */
+static cfg_tuplefielddef_t negated_fields[] = {
+ { "negated", &cfg_type_addrmatchelt, 0 }, { NULL, NULL, 0 }
+};
+
+static void
+print_negated(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "!");
+ cfg_print_tuple(pctx, obj);
+}
+
+static cfg_type_t cfg_type_negated = { "negated", cfg_parse_tuple,
+ print_negated, NULL,
+ &cfg_rep_tuple, &negated_fields };
+
+/*% An address match list element */
+
+static cfg_type_t cfg_type_addrmatchelt = { "address_match_element",
+ parse_addrmatchelt,
+ NULL,
+ cfg_doc_terminal,
+ NULL,
+ NULL };
+
+/*%
+ * A bracketed address match list
+ */
+cfg_type_t cfg_type_bracketed_aml = { "bracketed_aml",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_addrmatchelt };
+
+/*
+ * Optional bracketed text
+ */
+static isc_result_t
+parse_optional_btext(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_BTEXT));
+ if (pctx->token.type == isc_tokentype_btext) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_text, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+print_optional_btext(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->type == &cfg_type_void) {
+ return;
+ }
+
+ pctx->indent++;
+ cfg_print_cstr(pctx, "{");
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+ print_close(pctx);
+}
+
+static void
+doc_optional_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "[ { <unspecified-text> } ]");
+}
+
+cfg_type_t cfg_type_optional_bracketed_text = { "optional_btext",
+ parse_optional_btext,
+ print_optional_btext,
+ doc_optional_btext,
+ NULL,
+ NULL };
+
+/*
+ * Booleans
+ */
+
+bool
+cfg_obj_isboolean(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_boolean);
+}
+
+bool
+cfg_obj_asboolean(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean);
+ return (obj->value.boolean);
+}
+
+isc_result_t
+cfg_parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ bool value;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ result = cfg_gettoken(pctx, 0);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (pctx->token.type != isc_tokentype_string) {
+ goto bad_boolean;
+ }
+
+ if ((strcasecmp(TOKEN_STRING(pctx), "true") == 0) ||
+ (strcasecmp(TOKEN_STRING(pctx), "yes") == 0) ||
+ (strcmp(TOKEN_STRING(pctx), "1") == 0))
+ {
+ value = true;
+ } else if ((strcasecmp(TOKEN_STRING(pctx), "false") == 0) ||
+ (strcasecmp(TOKEN_STRING(pctx), "no") == 0) ||
+ (strcmp(TOKEN_STRING(pctx), "0") == 0))
+ {
+ value = false;
+ } else {
+ goto bad_boolean;
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_boolean, &obj));
+ obj->value.boolean = value;
+ *ret = obj;
+ return (result);
+
+bad_boolean:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "boolean expected");
+ return (ISC_R_UNEXPECTEDTOKEN);
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_boolean(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ if (obj->value.boolean) {
+ cfg_print_cstr(pctx, "yes");
+ } else {
+ cfg_print_cstr(pctx, "no");
+ }
+}
+
+cfg_type_t cfg_type_boolean = { "boolean", cfg_parse_boolean,
+ cfg_print_boolean, cfg_doc_terminal,
+ &cfg_rep_boolean, NULL };
+
+/*
+ * Lists.
+ */
+
+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) {
+ cfg_listelt_t *elt;
+
+ elt = isc_mem_get(pctx->mctx, sizeof(*elt));
+ elt->obj = NULL;
+ ISC_LINK_INIT(elt, link);
+ *eltp = elt;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+free_listelt(cfg_parser_t *pctx, cfg_listelt_t *elt) {
+ if (elt->obj != NULL) {
+ cfg_obj_destroy(pctx, &elt->obj);
+ }
+ isc_mem_put(pctx->mctx, elt, sizeof(*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_listelt(pctx, elt);
+ }
+}
+
+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:
+ isc_mem_put(pctx->mctx, elt, sizeof(*elt));
+ return (result);
+}
+
+/*
+ * Parse a homogeneous list whose elements are of type 'elttype'
+ * and where each element is terminated by a semicolon.
+ */
+static isc_result_t
+parse_list(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret) {
+ cfg_obj_t *listobj = NULL;
+ const cfg_type_t *listof = listtype->of;
+ isc_result_t result;
+ cfg_listelt_t *elt = NULL;
+
+ CHECK(cfg_create_list(pctx, listtype, &listobj));
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == /*{*/ '}')
+ {
+ break;
+ }
+ CHECK(cfg_parse_listelt(pctx, listof, &elt));
+ CHECK(parse_semicolon(pctx));
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ elt = NULL;
+ }
+ *ret = listobj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (elt != NULL) {
+ free_listelt(pctx, elt);
+ }
+ CLEANUP_OBJ(listobj);
+ return (result);
+}
+
+static void
+print_list(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_list_t *list = &obj->value.list;
+ const cfg_listelt_t *elt;
+
+ for (elt = ISC_LIST_HEAD(*list); elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_obj(pctx, elt->obj);
+ cfg_print_cstr(pctx, "; ");
+ } else {
+ cfg_print_indent(pctx);
+ cfg_print_obj(pctx, elt->obj);
+ cfg_print_cstr(pctx, ";\n");
+ }
+ }
+}
+
+isc_result_t
+cfg_parse_bracketed_list(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(parse_list(pctx, type, ret));
+ CHECK(cfg_parse_special(pctx, '}'));
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ print_open(pctx);
+ print_list(pctx, obj);
+ print_close(pctx);
+}
+
+void
+cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "{ ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, "; ... }");
+}
+
+/*
+ * Parse a homogeneous list whose elements are of type 'elttype'
+ * and where elements are separated by space. The list ends
+ * before the first semicolon.
+ */
+isc_result_t
+cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *listtype,
+ cfg_obj_t **ret) {
+ cfg_obj_t *listobj = NULL;
+ const cfg_type_t *listof;
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(listtype != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ listof = listtype->of;
+
+ CHECK(cfg_create_list(pctx, listtype, &listobj));
+
+ for (;;) {
+ cfg_listelt_t *elt = NULL;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == ';')
+ {
+ break;
+ }
+ CHECK(cfg_parse_listelt(pctx, listof, &elt));
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ }
+ *ret = listobj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(listobj);
+ return (result);
+}
+
+void
+cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_list_t *list = NULL;
+ const cfg_listelt_t *elt = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ list = &obj->value.list;
+
+ for (elt = ISC_LIST_HEAD(*list); elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ cfg_print_obj(pctx, elt->obj);
+ if (ISC_LIST_NEXT(elt, link) != NULL) {
+ cfg_print_cstr(pctx, " ");
+ }
+ }
+}
+
+bool
+cfg_obj_islist(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (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, bool 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);
+}
+
+cfg_obj_t *
+cfg_listelt_value(const cfg_listelt_t *elt) {
+ REQUIRE(elt != NULL);
+ return (elt->obj);
+}
+
+/*
+ * 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;
+ 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;
+ cfg_list_t *list = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ clausesets = type->of;
+
+ CHECK(create_map(pctx, type, &obj));
+
+ obj->value.map.clausesets = clausesets;
+
+ for (;;) {
+ cfg_listelt_t *elt;
+
+ 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));
+
+ /* Allow include to specify a pattern that follows
+ * the same rules as the shell e.g "/path/zone*.conf" */
+ glob_t glob_obj;
+ CHECK(isc_glob(includename->value.string.base,
+ &glob_obj));
+ cfg_obj_destroy(pctx, &includename);
+
+ for (size_t i = 0; i < glob_obj.gl_pathc; ++i) {
+ CHECK(parser_openfile(pctx,
+ glob_obj.gl_pathv[i]));
+ }
+
+ isc_globfree(&glob_obj);
+
+ 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. */
+
+ /* Issue fatal errors if appropriate */
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0) {
+ cfg_parser_error(pctx, 0,
+ "option '%s' no longer exists",
+ clause->name);
+ CHECK(ISC_R_FAILURE);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_NOTCONFIGURED) != 0) {
+ cfg_parser_error(pctx, 0,
+ "option '%s' was not "
+ "enabled at compile time",
+ clause->name);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ /* Issue warnings if appropriate */
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0 &&
+ (clause->flags & CFG_CLAUSEFLAG_DEPRECATED) != 0)
+ {
+ cfg_parser_warning(pctx, 0, "option '%s' is deprecated",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is obsolete and "
+ "should be removed ",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_EXPERIMENTAL) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is experimental and "
+ "subject to change in the future",
+ clause->name);
+ }
+
+ /* See if the clause already has a value; if not create one. */
+ result = isc_symtab_lookup(obj->value.map.symtab, clause->name,
+ 0, &symval);
+
+ if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+ /* Multivalued clause */
+ cfg_obj_t *listobj = NULL;
+ if (result == ISC_R_NOTFOUND) {
+ CHECK(cfg_create_list(pctx,
+ &cfg_type_implicitlist,
+ &listobj));
+ symval.as_pointer = listobj;
+ result = isc_symtab_define(
+ obj->value.map.symtab, clause->name, 1,
+ symval, isc_symexists_reject);
+ if (result != ISC_R_SUCCESS) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "isc_symtab_define(%s)"
+ " "
+ "failed",
+ clause->name);
+ isc_mem_put(pctx->mctx, list,
+ sizeof(cfg_list_t));
+ goto cleanup;
+ }
+ } else {
+ INSIST(result == ISC_R_SUCCESS);
+ listobj = symval.as_pointer;
+ }
+
+ elt = NULL;
+ CHECK(cfg_parse_listelt(pctx, clause->type, &elt));
+ CHECK(parse_semicolon(pctx));
+
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ } else {
+ /* Single-valued clause */
+ if (result == ISC_R_NOTFOUND) {
+ bool callback = ((clause->flags &
+ CFG_CLAUSEFLAG_CALLBACK) !=
+ 0);
+ CHECK(parse_symtab_elt(
+ pctx, clause->name, clause->type,
+ obj->value.map.symtab, callback));
+ 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, bool callback) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isc_symvalue_t symval;
+
+ CHECK(cfg_parse_obj(pctx, elttype, &obj));
+
+ if (callback && pctx->callback != NULL) {
+ CHECK(pctx->callback(name, obj, pctx->callbackarg));
+ }
+
+ 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; }"
+ */
+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));
+}
+
+/*
+ * Parse a map identified by a network address.
+ * Used to be used for the "server" statement.
+ */
+isc_result_t
+cfg_parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_any_named_map(pctx, &cfg_type_netaddr, type, ret));
+}
+
+/*
+ * Parse a map identified by a network prefix.
+ * Used for the "server" statement.
+ */
+isc_result_t
+cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_any_named_map(pctx, &cfg_type_netprefix, type, ret));
+}
+
+static void
+print_symval(cfg_printer_t *pctx, const char *name, cfg_obj_t *obj) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ cfg_print_indent(pctx);
+ }
+
+ cfg_print_cstr(pctx, name);
+ cfg_print_cstr(pctx, " ");
+ cfg_print_obj(pctx, obj);
+
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ cfg_print_cstr(pctx, ";\n");
+ } else {
+ cfg_print_cstr(pctx, "; ");
+ }
+}
+
+void
+cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ for (clauseset = obj->value.map.clausesets; *clauseset != NULL;
+ clauseset++)
+ {
+ isc_symvalue_t symval;
+ const cfg_clausedef_t *clause;
+
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ isc_result_t result;
+ result = isc_symtab_lookup(obj->value.map.symtab,
+ clause->name, 0, &symval);
+ if (result == ISC_R_SUCCESS) {
+ cfg_obj_t *symobj = symval.as_pointer;
+ if (symobj->type == &cfg_type_implicitlist) {
+ /* Multivalued. */
+ cfg_list_t *list = &symobj->value.list;
+ cfg_listelt_t *elt;
+ for (elt = ISC_LIST_HEAD(*list);
+ elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ print_symval(pctx, clause->name,
+ elt->obj);
+ }
+ } else {
+ /* Single-valued. */
+ print_symval(pctx, clause->name,
+ symobj);
+ }
+ } else if (result == ISC_R_NOTFOUND) {
+ /* do nothing */
+ } else {
+ UNREACHABLE();
+ }
+ }
+ }
+}
+
+static struct flagtext {
+ unsigned int flag;
+ const char *text;
+} flagtexts[] = { { CFG_CLAUSEFLAG_OBSOLETE, "obsolete" },
+ { CFG_CLAUSEFLAG_TESTONLY, "test only" },
+ { CFG_CLAUSEFLAG_NOTCONFIGURED, "not configured" },
+ { CFG_CLAUSEFLAG_MULTI, "may occur multiple times" },
+ { CFG_CLAUSEFLAG_EXPERIMENTAL, "experimental" },
+ { CFG_CLAUSEFLAG_DEPRECATED, "deprecated" },
+ { CFG_CLAUSEFLAG_ANCIENT, "ancient" },
+ { 0, NULL } };
+
+void
+cfg_print_clauseflags(cfg_printer_t *pctx, unsigned int flags) {
+ struct flagtext *p;
+ bool first = true;
+ for (p = flagtexts; p->flag != 0; p++) {
+ if ((flags & p->flag) != 0) {
+ if (first) {
+ cfg_print_cstr(pctx, " // ");
+ } else {
+ cfg_print_cstr(pctx, ", ");
+ }
+ cfg_print_cstr(pctx, p->text);
+ first = false;
+ }
+ }
+}
+
+void
+cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+ (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+ {
+ continue;
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+ (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+ {
+ continue;
+ }
+ cfg_print_cstr(pctx, clause->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, clause->type);
+ cfg_print_cstr(pctx, ";");
+ cfg_print_clauseflags(pctx, clause->flags);
+ cfg_print_cstr(pctx, "\n\n");
+ }
+ }
+}
+
+void
+cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ if (obj->value.map.id != NULL) {
+ cfg_print_obj(pctx, obj->value.map.id);
+ cfg_print_cstr(pctx, " ");
+ }
+ print_open(pctx);
+ cfg_print_mapbody(pctx, obj);
+ print_close(pctx);
+}
+
+void
+cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ if (type->parse == cfg_parse_named_map) {
+ cfg_doc_obj(pctx, &cfg_type_astring);
+ cfg_print_cstr(pctx, " ");
+ } else if (type->parse == cfg_parse_addressed_map) {
+ cfg_doc_obj(pctx, &cfg_type_netaddr);
+ cfg_print_cstr(pctx, " ");
+ } else if (type->parse == cfg_parse_netprefix_map) {
+ cfg_doc_obj(pctx, &cfg_type_netprefix);
+ cfg_print_cstr(pctx, " ");
+ }
+
+ print_open(pctx);
+
+ for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+ (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+ {
+ continue;
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+ (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+ {
+ continue;
+ }
+ cfg_print_indent(pctx);
+ cfg_print_cstr(pctx, clause->name);
+ if (clause->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_doc_obj(pctx, clause->type);
+ cfg_print_cstr(pctx, ";");
+ cfg_print_clauseflags(pctx, clause->flags);
+ cfg_print_cstr(pctx, "\n");
+ }
+ }
+ print_close(pctx);
+}
+
+bool
+cfg_obj_ismap(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_map);
+}
+
+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);
+}
+
+unsigned int
+cfg_map_count(const cfg_obj_t *mapobj) {
+ const cfg_map_t *map;
+
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+
+ map = &mapobj->value.map;
+ return (isc_symtab_count(map->symtab));
+}
+
+const char *
+cfg_map_firstclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx) {
+ cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(map != NULL && map->rep == &cfg_rep_map);
+ REQUIRE(idx != NULL);
+ REQUIRE(clauses != NULL && *clauses == NULL);
+
+ clauseset = map->of;
+ if (*clauseset == NULL) {
+ return (NULL);
+ }
+ *clauses = *clauseset;
+ *idx = 0;
+ while ((*clauseset)[*idx].name == NULL) {
+ *clauses = (*++clauseset);
+ if (*clauses == NULL) {
+ return (NULL);
+ }
+ }
+ return ((*clauseset)[*idx].name);
+}
+
+const char *
+cfg_map_nextclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx) {
+ cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(map != NULL && map->rep == &cfg_rep_map);
+ REQUIRE(idx != NULL);
+ REQUIRE(clauses != NULL && *clauses != NULL);
+
+ clauseset = map->of;
+ while (*clauseset != NULL && *clauseset != *clauses) {
+ clauseset++;
+ }
+ INSIST(*clauseset == *clauses);
+ (*idx)++;
+ while ((*clauseset)[*idx].name == NULL) {
+ *idx = 0;
+ *clauses = (*++clauseset);
+ if (*clauses == NULL) {
+ return (NULL);
+ }
+ }
+ return ((*clauseset)[*idx].name);
+}
+
+/* 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 = isc_mem_get(pctx->mctx, r.length + 1);
+ 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) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ }
+ return (result);
+}
+
+cfg_type_t cfg_type_token = { "token", parse_token,
+ cfg_print_ustring, cfg_doc_terminal,
+ &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_print_spacelist, cfg_doc_terminal,
+ &cfg_rep_list, NULL };
+
+/*
+ * Try interpreting the current token as a network address.
+ *
+ * If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard
+ * and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set. The
+ * "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is
+ * set (including the case where CFG_ADDR_V4OK and CFG_ADDR_V6OK are both set),
+ * and the IPv6 wildcard address otherwise.
+ */
+static isc_result_t
+token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+ char *s;
+ struct in_addr in4a;
+ struct in6_addr in6a;
+
+ if (pctx->token.type != isc_tokentype_string) {
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ s = TOKEN_STRING(pctx);
+ if ((flags & CFG_ADDR_WILDOK) != 0 && strcmp(s, "*") == 0) {
+ if ((flags & CFG_ADDR_V4OK) != 0) {
+ isc_netaddr_any(na);
+ return (ISC_R_SUCCESS);
+ } else if ((flags & CFG_ADDR_V6OK) != 0) {
+ isc_netaddr_any6(na);
+ return (ISC_R_SUCCESS);
+ } else {
+ UNREACHABLE();
+ }
+ } else {
+ if ((flags & (CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK)) != 0) {
+ if (inet_pton(AF_INET, s, &in4a) == 1) {
+ isc_netaddr_fromin(na, &in4a);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if ((flags & CFG_ADDR_V4PREFIXOK) != 0 && strlen(s) <= 15U) {
+ char buf[64];
+ int i;
+
+ strlcpy(buf, s, sizeof(buf));
+ for (i = 0; i < 3; i++) {
+ strlcat(buf, ".0", sizeof(buf));
+ if (inet_pton(AF_INET, buf, &in4a) == 1) {
+ isc_netaddr_fromin(na, &in4a);
+ return (ISC_R_IPV4PREFIX);
+ }
+ }
+ }
+ if ((flags & CFG_ADDR_V6OK) != 0 && strlen(s) <= 127U) {
+ char buf[128]; /* see lib/bind9/getaddresses.c */
+ char *d; /* zone delimiter */
+ uint32_t zone = 0; /* scope zone ID */
+
+ strlcpy(buf, s, sizeof(buf));
+ d = strchr(buf, '%');
+ if (d != NULL) {
+ *d = '\0';
+ }
+
+ if (inet_pton(AF_INET6, buf, &in6a) == 1) {
+ if (d != NULL) {
+ isc_result_t result;
+
+ result = isc_netscope_pton(
+ AF_INET6, d + 1, &in6a, &zone);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ isc_netaddr_fromin6(na, &in6a);
+ isc_netaddr_setzone(na, zone);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ }
+ return (ISC_R_UNEXPECTEDTOKEN);
+}
+
+isc_result_t
+cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+ isc_result_t result;
+ const char *wild = "";
+ const char *prefix = "";
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(na != NULL);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ result = token_addr(pctx, flags, na);
+ if (result == ISC_R_UNEXPECTEDTOKEN) {
+ if ((flags & CFG_ADDR_WILDOK) != 0) {
+ wild = " or '*'";
+ }
+ if ((flags & CFG_ADDR_V4PREFIXOK) != 0) {
+ wild = " or IPv4 prefix";
+ }
+ if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V4OK) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IPv4 address%s%s", prefix,
+ wild);
+ } else if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V6OK) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IPv6 address%s%s", prefix,
+ wild);
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address%s%s", prefix,
+ wild);
+ }
+ }
+cleanup:
+ return (result);
+}
+
+bool
+cfg_lookingat_netaddr(cfg_parser_t *pctx, unsigned int flags) {
+ isc_result_t result;
+ isc_netaddr_t na_dummy;
+
+ REQUIRE(pctx != NULL);
+
+ result = token_addr(pctx, flags, &na_dummy);
+ return (result == ISC_R_SUCCESS || result == ISC_R_IPV4PREFIX);
+}
+
+isc_result_t
+cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(port != NULL);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
+
+ if ((flags & CFG_ADDR_WILDOK) != 0 &&
+ pctx->token.type == isc_tokentype_string &&
+ strcmp(TOKEN_STRING(pctx), "*") == 0)
+ {
+ *port = 0;
+ return (ISC_R_SUCCESS);
+ }
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected port number or '*'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ if (pctx->token.value.as_ulong >= 65536U) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "port number out of range");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ *port = (in_port_t)(pctx->token.value.as_ulong);
+ return (ISC_R_SUCCESS);
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_rawaddr(cfg_printer_t *pctx, const isc_netaddr_t *na) {
+ isc_result_t result;
+ char text[128];
+ isc_buffer_t buf;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(na != NULL);
+
+ isc_buffer_init(&buf, text, sizeof(text));
+ result = isc_netaddr_totext(na, &buf);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ cfg_print_chars(pctx, isc_buffer_base(&buf),
+ isc_buffer_usedlength(&buf));
+}
+
+/* netaddr */
+
+static unsigned int netaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK;
+static unsigned int netaddr4_flags = CFG_ADDR_V4OK;
+static unsigned int netaddr4wild_flags = CFG_ADDR_V4OK | CFG_ADDR_WILDOK;
+static unsigned int netaddr6_flags = CFG_ADDR_V6OK;
+static unsigned int netaddr6wild_flags = CFG_ADDR_V6OK | CFG_ADDR_WILDOK;
+
+static isc_result_t
+parse_netaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isc_netaddr_t netaddr;
+ unsigned int flags = *(const unsigned int *)type->of;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr));
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, 0);
+ obj->value.sockaddrdscp.dscp = -1;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+cfg_doc_netaddr(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp = type->of;
+ int n = 0;
+ if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) {
+ cfg_print_cstr(pctx, "( ");
+ }
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "*");
+ n++;
+ POST(n);
+ }
+ if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) {
+ cfg_print_cstr(pctx, " )");
+ }
+}
+
+cfg_type_t cfg_type_netaddr = { "netaddr", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr_flags };
+
+cfg_type_t cfg_type_netaddr4 = { "netaddr4", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr4_flags };
+
+cfg_type_t cfg_type_netaddr4wild = { "netaddr4wild", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr4wild_flags };
+
+cfg_type_t cfg_type_netaddr6 = { "netaddr6", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr6_flags };
+
+cfg_type_t cfg_type_netaddr6wild = { "netaddr6wild", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr6wild_flags };
+
+/* netprefix */
+
+isc_result_t
+cfg_parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ cfg_obj_t *obj = NULL;
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ unsigned int addrlen = 0, prefixlen;
+ bool expectprefix;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ result = cfg_parse_rawaddr(
+ pctx, CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK | CFG_ADDR_V6OK,
+ &netaddr);
+ if (result != ISC_R_SUCCESS && result != ISC_R_IPV4PREFIX) {
+ CHECK(result);
+ }
+ switch (netaddr.family) {
+ case AF_INET:
+ addrlen = 32;
+ break;
+ case AF_INET6:
+ addrlen = 128;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ expectprefix = (result == ISC_R_IPV4PREFIX);
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '/')
+ {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "/" */
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected prefix length");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ prefixlen = pctx->token.value.as_ulong;
+ if (prefixlen > addrlen) {
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "invalid prefix length");
+ return (ISC_R_RANGE);
+ }
+ result = isc_netaddr_prefixok(&netaddr, prefixlen);
+ if (result != ISC_R_SUCCESS) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&netaddr, buf, sizeof(buf));
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "'%s/%u': address/prefix length "
+ "mismatch",
+ buf, prefixlen);
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ if (expectprefix) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "incomplete IPv4 address or prefix");
+ return (ISC_R_FAILURE);
+ }
+ prefixlen = addrlen;
+ }
+ CHECK(cfg_create_obj(pctx, &cfg_type_netprefix, &obj));
+ obj->value.netprefix.address = netaddr;
+ obj->value.netprefix.prefixlen = prefixlen;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected network prefix");
+ return (result);
+}
+
+static void
+print_netprefix(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_netprefix_t *p = &obj->value.netprefix;
+
+ cfg_print_rawaddr(pctx, &p->address);
+ cfg_print_cstr(pctx, "/");
+ cfg_print_rawuint(pctx, p->prefixlen);
+}
+
+bool
+cfg_obj_isnetprefix(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_netprefix);
+}
+
+void
+cfg_obj_asnetprefix(const cfg_obj_t *obj, isc_netaddr_t *netaddr,
+ unsigned int *prefixlen) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_netprefix);
+ REQUIRE(netaddr != NULL);
+ REQUIRE(prefixlen != NULL);
+
+ *netaddr = obj->value.netprefix.address;
+ *prefixlen = obj->value.netprefix.prefixlen;
+}
+
+cfg_type_t cfg_type_netprefix = { "netprefix", cfg_parse_netprefix,
+ print_netprefix, cfg_doc_terminal,
+ &cfg_rep_netprefix, NULL };
+
+static isc_result_t
+parse_sockaddrsub(cfg_parser_t *pctx, const cfg_type_t *type, int flags,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ in_port_t port = 0;
+ cfg_obj_t *obj = NULL;
+ int have_port = 0, have_dscp = 0;
+ cfg_obj_t *dscp = NULL;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr));
+ obj->value.sockaddrdscp.dscp = -1;
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ if (strcasecmp(TOKEN_STRING(pctx), "port") == 0) {
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) ==
+ 0 &&
+ (flags & CFG_ADDR_PORTOK) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'port' is deprecated");
+ }
+ CHECK(cfg_gettoken(pctx, 0)); /* read "port" */
+ CHECK(cfg_parse_rawport(pctx, flags, &port));
+ ++have_port;
+ } else if ((flags & CFG_ADDR_DSCPOK) != 0 &&
+ strcasecmp(TOKEN_STRING(pctx), "dscp") == 0)
+ {
+ cfg_parser_warning(pctx, 0,
+ "'dscp' is obsolete and "
+ "should be removed");
+ CHECK(cfg_gettoken(pctx, 0)); /* read "dscp" */
+ CHECK(cfg_parse_uint32(pctx, NULL, &dscp));
+ obj->value.sockaddrdscp.dscp =
+ cfg_obj_asuint32(dscp);
+ cfg_obj_destroy(pctx, &dscp);
+ ++have_dscp;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (have_port > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one port");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ if (have_dscp > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one dscp");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static unsigned int sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_PORTOK;
+cfg_type_t cfg_type_sockaddr = { "sockaddr", cfg_parse_sockaddr,
+ cfg_print_sockaddr, cfg_doc_sockaddr,
+ &cfg_rep_sockaddr, &sockaddr_flags };
+
+static unsigned int sockaddrdscp_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_DSCPOK | CFG_ADDR_PORTOK;
+cfg_type_t cfg_type_sockaddrdscp = { "sockaddr", cfg_parse_sockaddr,
+ cfg_print_sockaddr, cfg_doc_sockaddr,
+ &cfg_rep_sockaddr, &sockaddrdscp_flags };
+
+isc_result_t
+cfg_parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ const unsigned int *flagp;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ flagp = type->of;
+
+ return (parse_sockaddrsub(pctx, &cfg_type_sockaddr, *flagp, ret));
+}
+
+void
+cfg_print_sockaddr(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isc_netaddr_t netaddr;
+ in_port_t port;
+ char buf[ISC_NETADDR_FORMATSIZE];
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ isc_netaddr_fromsockaddr(&netaddr, &obj->value.sockaddr);
+ isc_netaddr_format(&netaddr, buf, sizeof(buf));
+ cfg_print_cstr(pctx, buf);
+ port = isc_sockaddr_getport(&obj->value.sockaddr);
+ if (port != 0) {
+ cfg_print_cstr(pctx, " port ");
+ cfg_print_rawuint(pctx, port);
+ }
+ if (obj->value.sockaddrdscp.dscp != -1) {
+ cfg_print_cstr(pctx, " dscp ");
+ cfg_print_rawuint(pctx, obj->value.sockaddrdscp.dscp);
+ }
+}
+
+void
+cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp;
+ int n = 0;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ flagp = type->of;
+
+ cfg_print_cstr(pctx, "( ");
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "*");
+ n++;
+ POST(n);
+ }
+ cfg_print_cstr(pctx, " ) ");
+ if ((*flagp & CFG_ADDR_PORTOK) != 0) {
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ cfg_print_cstr(pctx, "[ port ( <integer> | * ) ]");
+ } else {
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ }
+ }
+}
+
+bool
+cfg_obj_issockaddr(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_sockaddr);
+}
+
+const isc_sockaddr_t *
+cfg_obj_assockaddr(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr);
+ return (&obj->value.sockaddr);
+}
+
+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 = 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 = 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);
+}
+
+void
+cfg_ungettoken(cfg_parser_t *pctx) {
+ REQUIRE(pctx != NULL);
+
+ if (pctx->seen_eof) {
+ return;
+ }
+ isc_lex_ungettoken(pctx->lexer, &pctx->token);
+ pctx->ungotten = true;
+}
+
+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);
+}
+
+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, false, flags, fmt, args);
+ va_end(args);
+ pctx->errors++;
+}
+
+void
+cfg_parser_warning(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, true, flags, fmt, args);
+ va_end(args);
+ pctx->warnings++;
+}
+
+#define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */
+
+static bool
+have_current_file(cfg_parser_t *pctx) {
+ cfg_listelt_t *elt;
+ if (pctx->open_files == NULL) {
+ return (false);
+ }
+
+ elt = ISC_LIST_TAIL(pctx->open_files->value.list);
+ if (elt == NULL) {
+ return (false);
+ }
+
+ return (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, bool 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);
+ } else if (pctx->buf_name != NULL) {
+ snprintf(where, sizeof(where), "%s: ", pctx->buf_name);
+ }
+
+ len = vsnprintf(message, sizeof(message), format, args);
+#define ELLIPSIS " ... "
+ if (len >= sizeof(message)) {
+ message[sizeof(message) - sizeof(ELLIPSIS)] = 0;
+ strlcat(message, ELLIPSIS, 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) != 0) {
+ prep = " near ";
+ } else if ((flags & CFG_LOG_BEFORE) != 0) {
+ prep = " before ";
+ } else {
+ prep = " ";
+ }
+ } else {
+ tokenbuf[0] = '\0';
+ }
+ isc_log_write(pctx->lctx, CAT, MOD, level, "%s%s%s%s", where, message,
+ prep, tokenbuf);
+}
+
+void
+cfg_obj_log(const cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt,
+ ...) {
+ va_list ap;
+ char msgbuf[2048];
+
+ REQUIRE(obj != NULL);
+ REQUIRE(fmt != NULL);
+
+ if (!isc_log_wouldlog(lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ if (obj->file != NULL) {
+ isc_log_write(lctx, CAT, MOD, level, "%s:%u: %s", obj->file,
+ obj->line, msgbuf);
+ } else {
+ isc_log_write(lctx, CAT, MOD, level, "%s", msgbuf);
+ }
+}
+
+const char *
+cfg_obj_file(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+
+ return (obj->file);
+}
+
+unsigned int
+cfg_obj_line(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+
+ return (obj->line);
+}
+
+isc_result_t
+cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ cfg_obj_t *obj;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ obj = isc_mem_get(pctx->mctx, sizeof(cfg_obj_t));
+
+ obj->type = type;
+ obj->file = current_file(pctx);
+ obj->line = pctx->line;
+ obj->pctx = pctx;
+
+ isc_refcount_init(&obj->references, 1);
+
+ *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(pctx->mctx, 5, /* XXX */
+ map_symtabitem_destroy, pctx, false, &symtab));
+ obj->value.map.symtab = symtab;
+ obj->value.map.id = NULL;
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (obj != NULL) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*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);
+}
+
+bool
+cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type) {
+ REQUIRE(obj != NULL);
+ REQUIRE(type != NULL);
+
+ return (obj->type == type);
+}
+
+/*
+ * Destroy 'obj', a configuration object created in 'pctx'.
+ */
+void
+cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) {
+ REQUIRE(objp != NULL && *objp != NULL);
+ REQUIRE(pctx != NULL);
+
+ cfg_obj_t *obj = *objp;
+ *objp = NULL;
+
+ if (isc_refcount_decrement(&obj->references) == 1) {
+ obj->type->rep->free(pctx, obj);
+ isc_refcount_destroy(&obj->references);
+ isc_mem_put(pctx->mctx, obj, sizeof(cfg_obj_t));
+ }
+}
+
+void
+cfg_obj_attach(cfg_obj_t *src, cfg_obj_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+static void
+free_noop(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ UNUSED(pctx);
+ UNUSED(obj);
+}
+
+void
+cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ type->doc(pctx, type);
+}
+
+void
+cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "<");
+ cfg_print_cstr(pctx, type->name);
+ cfg_print_cstr(pctx, ">");
+}
+
+void
+cfg_print_grammar(const cfg_type_t *type, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ cfg_printer_t pctx;
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+ cfg_doc_obj(&pctx, type);
+}
+
+isc_result_t
+cfg_parser_mapadd(cfg_parser_t *pctx, cfg_obj_t *mapobj, cfg_obj_t *obj,
+ const char *clausename) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_map_t *map;
+ isc_symvalue_t symval;
+ cfg_obj_t *destobj = NULL;
+ cfg_listelt_t *elt = NULL;
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+ REQUIRE(obj != NULL);
+ REQUIRE(clausename != NULL);
+
+ map = &mapobj->value.map;
+
+ clause = NULL;
+ for (clauseset = map->clausesets; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (strcasecmp(clause->name, clausename) == 0) {
+ goto breakout;
+ }
+ }
+ }
+
+breakout:
+ if (clause == NULL || clause->name == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ result = isc_symtab_lookup(map->symtab, clausename, 0, &symval);
+ if (result == ISC_R_NOTFOUND) {
+ if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+ CHECK(cfg_create_list(pctx, &cfg_type_implicitlist,
+ &destobj));
+ CHECK(create_listelt(pctx, &elt));
+ cfg_obj_attach(obj, &elt->obj);
+ ISC_LIST_APPEND(destobj->value.list, elt, link);
+ symval.as_pointer = destobj;
+ } else {
+ symval.as_pointer = obj;
+ }
+
+ CHECK(isc_symtab_define(map->symtab, clausename, 1, symval,
+ isc_symexists_reject));
+ } else {
+ cfg_obj_t *destobj2 = symval.as_pointer;
+
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (destobj2->type == &cfg_type_implicitlist) {
+ CHECK(create_listelt(pctx, &elt));
+ cfg_obj_attach(obj, &elt->obj);
+ ISC_LIST_APPEND(destobj2->value.list, elt, link);
+ } else {
+ result = ISC_R_EXISTS;
+ }
+ }
+
+ destobj = NULL;
+ elt = NULL;
+
+cleanup:
+ if (elt != NULL) {
+ free_listelt(pctx, elt);
+ }
+ CLEANUP_OBJ(destobj);
+
+ return (result);
+}
+
+isc_result_t
+cfg_pluginlist_foreach(const cfg_obj_t *config, const cfg_obj_t *list,
+ isc_log_t *lctx, pluginlist_cb_t *callback,
+ void *callback_data) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_listelt_t *element;
+
+ REQUIRE(config != NULL);
+ REQUIRE(callback != NULL);
+
+ for (element = cfg_list_first(list); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *plugin = cfg_listelt_value(element);
+ const cfg_obj_t *obj;
+ const char *type, *library;
+ const char *parameters = NULL;
+
+ /* Get the path to the plugin module. */
+ obj = cfg_tuple_get(plugin, "type");
+ type = cfg_obj_asstring(obj);
+
+ /* Only query plugins are supported currently. */
+ if (strcasecmp(type, "query") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "unsupported plugin type");
+ return (ISC_R_FAILURE);
+ }
+
+ library = cfg_obj_asstring(cfg_tuple_get(plugin, "library"));
+
+ obj = cfg_tuple_get(plugin, "parameters");
+ if (obj != NULL && cfg_obj_isstring(obj)) {
+ parameters = cfg_obj_asstring(obj);
+ }
+
+ result = callback(config, obj, library, parameters,
+ callback_data);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ return (result);
+}