summaryrefslogtreecommitdiffstats
path: root/lib/isccfg
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
commit3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch)
tree2e1c188dd7b8d7475cd163de9ae02c428343669b /lib/isccfg
parentInitial commit. (diff)
downloadbind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.tar.xz
bind9-0cd617f6bad00b68e380aeb0024ef4dc1985191c.zip
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/isccfg/Makefile.am37
-rw-r--r--lib/isccfg/Makefile.in967
-rw-r--r--lib/isccfg/aclconf.c1010
-rw-r--r--lib/isccfg/dnsconf.c57
-rw-r--r--lib/isccfg/duration.c239
-rw-r--r--lib/isccfg/include/isccfg/aclconf.h91
-rw-r--r--lib/isccfg/include/isccfg/cfg.h609
-rw-r--r--lib/isccfg/include/isccfg/duration.h87
-rw-r--r--lib/isccfg/include/isccfg/grammar.h590
-rw-r--r--lib/isccfg/include/isccfg/kaspconf.h56
-rw-r--r--lib/isccfg/include/isccfg/log.h46
-rw-r--r--lib/isccfg/include/isccfg/namedconf.h54
-rw-r--r--lib/isccfg/kaspconf.c576
-rw-r--r--lib/isccfg/log.c38
-rw-r--r--lib/isccfg/namedconf.c3998
-rw-r--r--lib/isccfg/parser.c3901
16 files changed, 12356 insertions, 0 deletions
diff --git a/lib/isccfg/Makefile.am b/lib/isccfg/Makefile.am
new file mode 100644
index 0000000..0c95c4f
--- /dev/null
+++ b/lib/isccfg/Makefile.am
@@ -0,0 +1,37 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libisccfg.la
+
+libisccfg_ladir = $(includedir)/isccfg
+libisccfg_la_HEADERS = \
+ include/isccfg/aclconf.h \
+ include/isccfg/cfg.h \
+ include/isccfg/duration.h \
+ include/isccfg/grammar.h \
+ include/isccfg/kaspconf.h \
+ include/isccfg/log.h \
+ include/isccfg/namedconf.h
+
+libisccfg_la_SOURCES = \
+ $(libisccfg_la_HEADERS) \
+ aclconf.c \
+ dnsconf.c \
+ duration.c \
+ kaspconf.c \
+ log.c \
+ namedconf.c \
+ parser.c
+
+libisccfg_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS)
+
+libisccfg_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libisccfg_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in
new file mode 100644
index 0000000..38f6b93
--- /dev/null
+++ b/lib/isccfg/Makefile.in
@@ -0,0 +1,967 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Hey Emacs, this is -*- makefile-automake -*- file!
+# vim: filetype=automake
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+@HOST_MACOS_TRUE@am__append_1 = \
+@HOST_MACOS_TRUE@ -Wl,-flat_namespace
+
+subdir = lib/isccfg
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4/ax_jemalloc.m4 \
+ $(top_srcdir)/m4/ax_lib_lmdb.m4 \
+ $(top_srcdir)/m4/ax_perl_module.m4 \
+ $(top_srcdir)/m4/ax_posix_shell.m4 \
+ $(top_srcdir)/m4/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_python_module.m4 \
+ $(top_srcdir)/m4/ax_restore_flags.m4 \
+ $(top_srcdir)/m4/ax_save_flags.m4 $(top_srcdir)/m4/ax_tls.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(libisccfg_la_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisccfg_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libisccfg_la_DEPENDENCIES = $(LIBDNS_LIBS) $(LIBISC_LIBS)
+am__objects_1 =
+am_libisccfg_la_OBJECTS = $(am__objects_1) libisccfg_la-aclconf.lo \
+ libisccfg_la-dnsconf.lo libisccfg_la-duration.lo \
+ libisccfg_la-kaspconf.lo libisccfg_la-log.lo \
+ libisccfg_la-namedconf.lo libisccfg_la-parser.lo
+libisccfg_la_OBJECTS = $(am_libisccfg_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libisccfg_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libisccfg_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libisccfg_la-aclconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-dnsconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-duration.Plo \
+ ./$(DEPDIR)/libisccfg_la-kaspconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-log.Plo \
+ ./$(DEPDIR)/libisccfg_la-namedconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-parser.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libisccfg_la_SOURCES)
+DIST_SOURCES = $(libisccfg_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libisccfg_la_HEADERS)
+am__extra_recursive_targets = test-recursive unit-recursive \
+ doc-recursive
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top \
+ $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CMOCKA_CFLAGS = @CMOCKA_CFLAGS@
+CMOCKA_LIBS = @CMOCKA_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURL = @CURL@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DEVELOPER_MODE = @DEVELOPER_MODE@
+DLLTOOL = @DLLTOOL@
+DNSTAP_CFLAGS = @DNSTAP_CFLAGS@
+DNSTAP_LIBS = @DNSTAP_LIBS@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FSTRM_CAPTURE = @FSTRM_CAPTURE@
+FUZZ_LDFLAGS = @FUZZ_LDFLAGS@
+FUZZ_LOG_COMPILER = @FUZZ_LOG_COMPILER@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JEMALLOC_CFLAGS = @JEMALLOC_CFLAGS@
+JEMALLOC_LIBS = @JEMALLOC_LIBS@
+JSON_C_CFLAGS = @JSON_C_CFLAGS@
+JSON_C_LIBS = @JSON_C_LIBS@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_CONFIG = @KRB5_CONFIG@
+KRB5_LIBS = @KRB5_LIBS@
+LATEXMK = @LATEXMK@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBCAP_LIBS = @LIBCAP_LIBS@
+LIBIDN2_CFLAGS = @LIBIDN2_CFLAGS@
+LIBIDN2_LIBS = @LIBIDN2_LIBS@
+LIBNGHTTP2_CFLAGS = @LIBNGHTTP2_CFLAGS@
+LIBNGHTTP2_LIBS = @LIBNGHTTP2_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUV_CFLAGS = @LIBUV_CFLAGS@
+LIBUV_LIBS = @LIBUV_LIBS@
+LIBXML2_CFLAGS = @LIBXML2_CFLAGS@
+LIBXML2_LIBS = @LIBXML2_LIBS@
+LIPO = @LIPO@
+LMDB_CFLAGS = @LMDB_CFLAGS@
+LMDB_LIBS = @LMDB_LIBS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAXMINDDB_CFLAGS = @MAXMINDDB_CFLAGS@
+MAXMINDDB_LIBS = @MAXMINDDB_LIBS@
+MAXMINDDB_PREFIX = @MAXMINDDB_PREFIX@
+MKDIR_P = @MKDIR_P@
+NC = @NC@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROTOC_C = @PROTOC_C@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_CXX = @PTHREAD_CXX@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PYTEST = @PYTEST@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+READLINE_CFLAGS = @READLINE_CFLAGS@
+READLINE_LIBS = @READLINE_LIBS@
+RELEASE_DATE = @RELEASE_DATE@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINX_BUILD = @SPHINX_BUILD@
+STD_CFLAGS = @STD_CFLAGS@
+STD_CPPFLAGS = @STD_CPPFLAGS@
+STD_LDFLAGS = @STD_LDFLAGS@
+STRIP = @STRIP@
+TEST_CFLAGS = @TEST_CFLAGS@
+VERSION = @VERSION@
+XELATEX = @XELATEX@
+XSLTPROC = @XSLTPROC@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4
+AM_CFLAGS = \
+ $(STD_CFLAGS)
+
+AM_CPPFLAGS = \
+ $(STD_CPPFLAGS) \
+ -include $(top_builddir)/config.h \
+ -I$(srcdir)/include
+
+AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1)
+LDADD =
+LIBISC_CFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/lib/isc/include \
+ -I$(top_builddir)/lib/isc/include
+
+LIBISC_LIBS = $(top_builddir)/lib/isc/libisc.la
+LIBDNS_CFLAGS = \
+ -I$(top_srcdir)/lib/dns/include \
+ -I$(top_builddir)/lib/dns/include
+
+LIBDNS_LIBS = \
+ $(top_builddir)/lib/dns/libdns.la
+
+LIBNS_CFLAGS = \
+ -I$(top_srcdir)/lib/ns/include
+
+LIBNS_LIBS = \
+ $(top_builddir)/lib/ns/libns.la
+
+LIBIRS_CFLAGS = \
+ -I$(top_srcdir)/lib/irs/include
+
+LIBIRS_LIBS = \
+ $(top_builddir)/lib/irs/libirs.la
+
+LIBISCCFG_CFLAGS = \
+ -I$(top_srcdir)/lib/isccfg/include
+
+LIBISCCFG_LIBS = \
+ $(top_builddir)/lib/isccfg/libisccfg.la
+
+LIBISCCC_CFLAGS = \
+ -I$(top_srcdir)/lib/isccc/include/
+
+LIBISCCC_LIBS = \
+ $(top_builddir)/lib/isccc/libisccc.la
+
+LIBBIND9_CFLAGS = \
+ -I$(top_srcdir)/lib/bind9/include
+
+LIBBIND9_LIBS = \
+ $(top_builddir)/lib/bind9/libbind9.la
+
+lib_LTLIBRARIES = libisccfg.la
+libisccfg_ladir = $(includedir)/isccfg
+libisccfg_la_HEADERS = \
+ include/isccfg/aclconf.h \
+ include/isccfg/cfg.h \
+ include/isccfg/duration.h \
+ include/isccfg/grammar.h \
+ include/isccfg/kaspconf.h \
+ include/isccfg/log.h \
+ include/isccfg/namedconf.h
+
+libisccfg_la_SOURCES = \
+ $(libisccfg_la_HEADERS) \
+ aclconf.c \
+ dnsconf.c \
+ duration.c \
+ kaspconf.c \
+ log.c \
+ namedconf.c \
+ parser.c
+
+libisccfg_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS)
+
+libisccfg_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libisccfg_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/isccfg/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/isccfg/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/Makefile.top $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libisccfg.la: $(libisccfg_la_OBJECTS) $(libisccfg_la_DEPENDENCIES) $(EXTRA_libisccfg_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libisccfg_la_LINK) -rpath $(libdir) $(libisccfg_la_OBJECTS) $(libisccfg_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-aclconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-dnsconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-duration.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-kaspconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-namedconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-parser.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libisccfg_la-aclconf.lo: aclconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-aclconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-aclconf.Tpo -c -o libisccfg_la-aclconf.lo `test -f 'aclconf.c' || echo '$(srcdir)/'`aclconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-aclconf.Tpo $(DEPDIR)/libisccfg_la-aclconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='aclconf.c' object='libisccfg_la-aclconf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-aclconf.lo `test -f 'aclconf.c' || echo '$(srcdir)/'`aclconf.c
+
+libisccfg_la-dnsconf.lo: dnsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-dnsconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-dnsconf.Tpo -c -o libisccfg_la-dnsconf.lo `test -f 'dnsconf.c' || echo '$(srcdir)/'`dnsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-dnsconf.Tpo $(DEPDIR)/libisccfg_la-dnsconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnsconf.c' object='libisccfg_la-dnsconf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-dnsconf.lo `test -f 'dnsconf.c' || echo '$(srcdir)/'`dnsconf.c
+
+libisccfg_la-duration.lo: duration.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-duration.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-duration.Tpo -c -o libisccfg_la-duration.lo `test -f 'duration.c' || echo '$(srcdir)/'`duration.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-duration.Tpo $(DEPDIR)/libisccfg_la-duration.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='duration.c' object='libisccfg_la-duration.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-duration.lo `test -f 'duration.c' || echo '$(srcdir)/'`duration.c
+
+libisccfg_la-kaspconf.lo: kaspconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-kaspconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-kaspconf.Tpo -c -o libisccfg_la-kaspconf.lo `test -f 'kaspconf.c' || echo '$(srcdir)/'`kaspconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-kaspconf.Tpo $(DEPDIR)/libisccfg_la-kaspconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kaspconf.c' object='libisccfg_la-kaspconf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-kaspconf.lo `test -f 'kaspconf.c' || echo '$(srcdir)/'`kaspconf.c
+
+libisccfg_la-log.lo: log.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-log.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-log.Tpo -c -o libisccfg_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-log.Tpo $(DEPDIR)/libisccfg_la-log.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libisccfg_la-log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+libisccfg_la-namedconf.lo: namedconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-namedconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-namedconf.Tpo -c -o libisccfg_la-namedconf.lo `test -f 'namedconf.c' || echo '$(srcdir)/'`namedconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-namedconf.Tpo $(DEPDIR)/libisccfg_la-namedconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='namedconf.c' object='libisccfg_la-namedconf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-namedconf.lo `test -f 'namedconf.c' || echo '$(srcdir)/'`namedconf.c
+
+libisccfg_la-parser.lo: parser.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-parser.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-parser.Tpo -c -o libisccfg_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-parser.Tpo $(DEPDIR)/libisccfg_la-parser.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='parser.c' object='libisccfg_la-parser.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libisccfg_laHEADERS: $(libisccfg_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libisccfg_la_HEADERS)'; test -n "$(libisccfg_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libisccfg_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libisccfg_ladir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libisccfg_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libisccfg_ladir)" || exit $$?; \
+ done
+
+uninstall-libisccfg_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libisccfg_la_HEADERS)'; test -n "$(libisccfg_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libisccfg_ladir)'; $(am__uninstall_files_from_dir)
+test-local:
+unit-local:
+doc-local:
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisccfg_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libisccfg_la-aclconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-dnsconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-duration.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-kaspconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-namedconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-parser.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+doc: doc-am
+
+doc-am: doc-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-libisccfg_laHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/libisccfg_la-aclconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-dnsconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-duration.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-kaspconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-namedconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-parser.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+test: test-am
+
+test-am: test-local
+
+uninstall-am: uninstall-libLTLIBRARIES uninstall-libisccfg_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir doc-am doc-local dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libisccfg_laHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am test-am test-local uninstall uninstall-am \
+ uninstall-libLTLIBRARIES uninstall-libisccfg_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c
new file mode 100644
index 0000000..1e3566a
--- /dev/null
+++ b/lib/isccfg/aclconf.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/fixedname.h>
+#include <dns/iptable.h>
+#include <dns/log.h>
+
+#include <isccfg/aclconf.h>
+#include <isccfg/namedconf.h>
+
+#define LOOP_MAGIC ISC_MAGIC('L', 'O', 'O', 'P')
+
+#if defined(HAVE_GEOIP2)
+static const char *geoip_dbnames[] = {
+ "country", "city", "asnum", "isp", "domain", NULL,
+};
+#endif /* if defined(HAVE_GEOIP2) */
+
+isc_result_t
+cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) {
+ cfg_aclconfctx_t *actx;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ actx = isc_mem_get(mctx, sizeof(*actx));
+
+ isc_refcount_init(&actx->references, 1);
+
+ actx->mctx = NULL;
+ isc_mem_attach(mctx, &actx->mctx);
+ ISC_LIST_INIT(actx->named_acl_cache);
+
+#if defined(HAVE_GEOIP2)
+ actx->geoip = NULL;
+#endif /* if defined(HAVE_GEOIP2) */
+
+ *ret = actx;
+ return (ISC_R_SUCCESS);
+}
+
+void
+cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+void
+cfg_aclconfctx_detach(cfg_aclconfctx_t **actxp) {
+ REQUIRE(actxp != NULL && *actxp != NULL);
+ cfg_aclconfctx_t *actx = *actxp;
+ *actxp = NULL;
+
+ if (isc_refcount_decrement(&actx->references) == 1) {
+ dns_acl_t *dacl, *next;
+ isc_refcount_destroy(&actx->references);
+ for (dacl = ISC_LIST_HEAD(actx->named_acl_cache); dacl != NULL;
+ dacl = next)
+ {
+ next = ISC_LIST_NEXT(dacl, nextincache);
+ ISC_LIST_UNLINK(actx->named_acl_cache, dacl,
+ nextincache);
+ dns_acl_detach(&dacl);
+ }
+ isc_mem_putanddetach(&actx->mctx, actx, sizeof(*actx));
+ }
+}
+
+/*
+ * Find the definition of the named acl whose name is "name".
+ */
+static isc_result_t
+get_acl_def(const cfg_obj_t *cctx, const char *name, const cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_obj_t *acls = NULL;
+ const cfg_listelt_t *elt;
+
+ result = cfg_map_get(cctx, "acl", &acls);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (elt = cfg_list_first(acls); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *acl = cfg_listelt_value(elt);
+ const char *aclname =
+ cfg_obj_asstring(cfg_tuple_get(acl, "name"));
+ if (strcasecmp(aclname, name) == 0) {
+ if (ret != NULL) {
+ *ret = cfg_tuple_get(acl, "value");
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+convert_named_acl(const cfg_obj_t *nameobj, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target) {
+ isc_result_t result;
+ const cfg_obj_t *cacl = NULL;
+ dns_acl_t *dacl;
+ dns_acl_t loop;
+ const char *aclname = cfg_obj_asstring(nameobj);
+
+ /* Look for an already-converted version. */
+ for (dacl = ISC_LIST_HEAD(ctx->named_acl_cache); dacl != NULL;
+ dacl = ISC_LIST_NEXT(dacl, nextincache))
+ {
+ if (strcasecmp(aclname, dacl->name) == 0) {
+ if (ISC_MAGIC_VALID(dacl, LOOP_MAGIC)) {
+ cfg_obj_log(nameobj, lctx, ISC_LOG_ERROR,
+ "acl loop detected: %s", aclname);
+ return (ISC_R_FAILURE);
+ }
+ dns_acl_attach(dacl, target);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ /* Not yet converted. Convert now. */
+ result = get_acl_def(cctx, aclname, &cacl);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(nameobj, lctx, ISC_LOG_WARNING,
+ "undefined ACL '%s'", aclname);
+ return (result);
+ }
+ /*
+ * Add a loop detection element.
+ */
+ memset(&loop, 0, sizeof(loop));
+ ISC_LINK_INIT(&loop, nextincache);
+ DE_CONST(aclname, loop.name);
+ loop.magic = LOOP_MAGIC;
+ ISC_LIST_APPEND(ctx->named_acl_cache, &loop, nextincache);
+ result = cfg_acl_fromconfig(cacl, cctx, lctx, ctx, mctx, nest_level,
+ &dacl);
+ ISC_LIST_UNLINK(ctx->named_acl_cache, &loop, nextincache);
+ loop.magic = 0;
+ loop.name = NULL;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dacl->name = isc_mem_strdup(dacl->mctx, aclname);
+ ISC_LIST_APPEND(ctx->named_acl_cache, dacl, nextincache);
+ dns_acl_attach(dacl, target);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+convert_keyname(const cfg_obj_t *keyobj, isc_log_t *lctx, isc_mem_t *mctx,
+ dns_name_t *dnsname) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ dns_fixedname_t fixname;
+ unsigned int keylen;
+ const char *txtname = cfg_obj_asstring(keyobj);
+
+ keylen = strlen(txtname);
+ isc_buffer_constinit(&buf, txtname, keylen);
+ isc_buffer_add(&buf, keylen);
+ dns_fixedname_init(&fixname);
+ result = dns_name_fromtext(dns_fixedname_name(&fixname), &buf,
+ dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(keyobj, lctx, ISC_LOG_WARNING,
+ "key name '%s' is not a valid domain name",
+ txtname);
+ return (result);
+ }
+ dns_name_dup(dns_fixedname_name(&fixname), mctx, dnsname);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Recursively pre-parse an ACL definition to find the total number
+ * of non-IP-prefix elements (localhost, localnets, key) in all nested
+ * ACLs, so that the parent will have enough space allocated for the
+ * elements table after all the nested ACLs have been merged in to the
+ * parent.
+ */
+static isc_result_t
+count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ uint32_t *count, bool *has_negative) {
+ const cfg_listelt_t *elt;
+ isc_result_t result;
+ uint32_t n = 0;
+
+ REQUIRE(count != NULL);
+
+ if (has_negative != NULL) {
+ *has_negative = false;
+ }
+
+ for (elt = cfg_list_first(caml); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *ce = cfg_listelt_value(elt);
+
+ /* might be a negated element, in which case get the value. */
+ if (cfg_obj_istuple(ce)) {
+ const cfg_obj_t *negated = cfg_tuple_get(ce, "negated");
+ if (!cfg_obj_isvoid(negated)) {
+ ce = negated;
+ if (has_negative != NULL) {
+ *has_negative = true;
+ }
+ }
+ }
+
+ if (cfg_obj_istype(ce, &cfg_type_keyref)) {
+ n++;
+ } else if (cfg_obj_islist(ce)) {
+ bool negative;
+ uint32_t sub;
+ result = count_acl_elements(ce, cctx, lctx, ctx, mctx,
+ &sub, &negative);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ n += sub;
+ if (negative) {
+ n++;
+ }
+#if defined(HAVE_GEOIP2)
+ } else if (cfg_obj_istuple(ce) &&
+ cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
+ {
+ n++;
+#endif /* HAVE_GEOIP2 */
+ } else if (cfg_obj_isstring(ce)) {
+ const char *name = cfg_obj_asstring(ce);
+ if (strcasecmp(name, "localhost") == 0 ||
+ strcasecmp(name, "localnets") == 0 ||
+ strcasecmp(name, "none") == 0)
+ {
+ n++;
+ } else if (strcasecmp(name, "any") != 0) {
+ dns_acl_t *inneracl = NULL;
+ /*
+ * Convert any named acls we reference now if
+ * they have not already been converted.
+ */
+ result = convert_named_acl(ce, cctx, lctx, ctx,
+ mctx, 0, &inneracl);
+ if (result == ISC_R_SUCCESS) {
+ if (inneracl->has_negatives) {
+ n++;
+ } else {
+ n += inneracl->length;
+ }
+ dns_acl_detach(&inneracl);
+ } else {
+ return (result);
+ }
+ }
+ }
+ }
+
+ *count = n;
+ return (ISC_R_SUCCESS);
+}
+
+#if defined(HAVE_GEOIP2)
+static dns_geoip_subtype_t
+get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, dns_geoip_subtype_t subtype,
+ const char *dbname) {
+ if (dbname == NULL) {
+ return (subtype);
+ }
+
+ switch (subtype) {
+ case dns_geoip_countrycode:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_countrycode);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_code);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "country search: ignored");
+ return (subtype);
+ case dns_geoip_countryname:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_countryname);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_name);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "country search: ignored");
+ return (subtype);
+ case dns_geoip_continentcode:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_continentcode);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_continentcode);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "continent search: ignored");
+ return (subtype);
+ case dns_geoip_continent:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_continent);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_continent);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "continent search: ignored");
+ return (subtype);
+ case dns_geoip_region:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_region);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "region/subdivision search: ignored");
+ return (subtype);
+ case dns_geoip_regionname:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_regionname);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "region/subdivision search: ignored");
+ return (subtype);
+
+ /*
+ * Log a warning if the wrong database was specified
+ * on an unambiguous query
+ */
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ case dns_geoip_city_timezonecode:
+ if (strcasecmp(dbname, "city") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'city'-only search type: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_isp_name:
+ if (strcasecmp(dbname, "isp") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'isp' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_org_name:
+ if (strcasecmp(dbname, "org") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'org' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_as_asnum:
+ if (strcasecmp(dbname, "asnum") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'asnum' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_domain_name:
+ if (strcasecmp(dbname, "domain") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'domain' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_netspeed_id:
+ if (strcasecmp(dbname, "netspeed") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'netspeed' search: ignoring");
+ }
+ return (subtype);
+ default:
+ UNREACHABLE();
+ }
+}
+
+static bool
+geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
+ if (ctx->geoip == NULL) {
+ return (true);
+ }
+
+ switch (elt->geoip_elem.subtype) {
+ case dns_geoip_countrycode:
+ case dns_geoip_countryname:
+ case dns_geoip_continentcode:
+ case dns_geoip_continent:
+ if (ctx->geoip->country != NULL || ctx->geoip->city != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_country_code:
+ case dns_geoip_country_name:
+ case dns_geoip_country_continentcode:
+ case dns_geoip_country_continent:
+ if (ctx->geoip->country != NULL) {
+ return (true);
+ }
+ /* city db can answer these too, so: */
+ FALLTHROUGH;
+ case dns_geoip_region:
+ case dns_geoip_regionname:
+ case dns_geoip_city_countrycode:
+ case dns_geoip_city_countryname:
+ case dns_geoip_city_region:
+ case dns_geoip_city_regionname:
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ case dns_geoip_city_continentcode:
+ case dns_geoip_city_continent:
+ case dns_geoip_city_timezonecode:
+ if (ctx->geoip->city != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_isp_name:
+ if (ctx->geoip->isp != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_as_asnum:
+ case dns_geoip_org_name:
+ if (ctx->geoip->as != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_domain_name:
+ if (ctx->geoip->domain != NULL) {
+ return (true);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return (false);
+}
+
+static isc_result_t
+parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
+ cfg_aclconfctx_t *ctx, dns_aclelement_t *dep) {
+ const cfg_obj_t *ge;
+ const char *dbname = NULL;
+ const char *stype = NULL, *search = NULL;
+ dns_geoip_subtype_t subtype;
+ dns_aclelement_t de;
+ size_t len;
+
+ REQUIRE(dep != NULL);
+
+ de = *dep;
+
+ ge = cfg_tuple_get(obj, "db");
+ if (!cfg_obj_isvoid(ge)) {
+ int i;
+
+ dbname = cfg_obj_asstring(ge);
+
+ for (i = 0; geoip_dbnames[i] != NULL; i++) {
+ if (strcasecmp(dbname, geoip_dbnames[i]) == 0) {
+ break;
+ }
+ }
+ if (geoip_dbnames[i] == NULL) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "database '%s' is not defined for GeoIP2",
+ dbname);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype"));
+ search = cfg_obj_asstring(cfg_tuple_get(obj, "search"));
+ len = strlen(search);
+
+ if (len == 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "zero-length geoip search field");
+ return (ISC_R_FAILURE);
+ }
+
+ if (strcasecmp(stype, "country") == 0 && len == 2) {
+ /* Two-letter country code */
+ subtype = dns_geoip_countrycode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "country") == 0 && len == 3) {
+ /* Three-letter country code */
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "three-letter country codes are unavailable "
+ "in GeoIP2 databases");
+ return (ISC_R_FAILURE);
+ } else if (strcasecmp(stype, "country") == 0) {
+ /* Country name */
+ subtype = dns_geoip_countryname;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "continent") == 0 && len == 2) {
+ /* Two-letter continent code */
+ subtype = dns_geoip_continentcode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "continent") == 0) {
+ subtype = dns_geoip_continent;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if ((strcasecmp(stype, "region") == 0 ||
+ strcasecmp(stype, "subdivision") == 0) &&
+ len == 2)
+ {
+ /* Two-letter region code */
+ subtype = dns_geoip_region;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "region") == 0 ||
+ strcasecmp(stype, "subdivision") == 0)
+ {
+ /* Region name */
+ subtype = dns_geoip_regionname;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "city") == 0) {
+ /* City name */
+ subtype = dns_geoip_city_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "postal") == 0 ||
+ strcasecmp(stype, "postalcode") == 0)
+ {
+ if (len < 7) {
+ subtype = dns_geoip_city_postalcode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "geoiop postal code (%s) too long", search);
+ return (ISC_R_FAILURE);
+ }
+ } else if (strcasecmp(stype, "metro") == 0 ||
+ strcasecmp(stype, "metrocode") == 0)
+ {
+ subtype = dns_geoip_city_metrocode;
+ de.geoip_elem.as_int = atoi(search);
+ } else if (strcasecmp(stype, "tz") == 0 ||
+ strcasecmp(stype, "timezone") == 0)
+ {
+ subtype = dns_geoip_city_timezonecode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "isp") == 0) {
+ subtype = dns_geoip_isp_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "asnum") == 0) {
+ subtype = dns_geoip_as_asnum;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "org") == 0) {
+ subtype = dns_geoip_org_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "domain") == 0) {
+ subtype = dns_geoip_domain_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "type '%s' is unavailable "
+ "in GeoIP2 databases",
+ stype);
+ return (ISC_R_FAILURE);
+ }
+
+ de.geoip_elem.subtype = get_subtype(obj, lctx, subtype, dbname);
+
+ if (!geoip_can_answer(&de, ctx)) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "no GeoIP2 database installed which can answer "
+ "queries of type '%s'",
+ stype);
+ return (ISC_R_FAILURE);
+ }
+
+ *dep = de;
+
+ return (ISC_R_SUCCESS);
+}
+#endif /* HAVE_GEOIP2 */
+
+isc_result_t
+cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target) {
+ return (cfg_acl_fromconfig2(caml, cctx, lctx, ctx, mctx, nest_level, 0,
+ target));
+}
+
+isc_result_t
+cfg_acl_fromconfig2(const cfg_obj_t *acl_data, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, uint16_t family,
+ dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *dacl = NULL, *inneracl = NULL;
+ dns_aclelement_t *de;
+ const cfg_listelt_t *elt;
+ dns_iptable_t *iptab;
+ int new_nest_level = 0;
+ bool setpos;
+ const cfg_obj_t *caml = NULL;
+ const cfg_obj_t *obj_acl_tuple = NULL;
+ const cfg_obj_t *obj_port = NULL, *obj_transport = NULL;
+ bool is_tuple = false;
+
+ if (nest_level != 0) {
+ new_nest_level = nest_level - 1;
+ }
+
+ REQUIRE(ctx != NULL);
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL || DNS_ACL_VALID(*target));
+
+ REQUIRE(acl_data != NULL);
+ if (cfg_obj_islist(acl_data)) {
+ caml = acl_data;
+ } else {
+ INSIST(cfg_obj_istuple(acl_data));
+ caml = cfg_tuple_get(acl_data, "aml");
+ INSIST(caml != NULL);
+ obj_acl_tuple = cfg_tuple_get(acl_data, "port-transport");
+ INSIST(obj_acl_tuple != NULL);
+ obj_port = cfg_tuple_get(obj_acl_tuple, "port");
+ obj_transport = cfg_tuple_get(obj_acl_tuple, "transport");
+ is_tuple = true;
+ }
+
+ if (*target != NULL) {
+ /*
+ * If target already points to an ACL, then we're being
+ * called recursively to configure a nested ACL. The
+ * nested ACL's contents should just be absorbed into its
+ * parent ACL.
+ */
+ dns_acl_attach(*target, &dacl);
+ dns_acl_detach(target);
+ } else {
+ /*
+ * Need to allocate a new ACL structure. Count the items
+ * in the ACL definition that will require space in the
+ * elements table. (Note that if nest_level is nonzero,
+ * *everything* goes in the elements table.)
+ */
+ uint32_t nelem;
+
+ if (nest_level == 0) {
+ result = count_acl_elements(caml, cctx, lctx, ctx, mctx,
+ &nelem, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ nelem = cfg_list_length(caml, false);
+ }
+
+ result = dns_acl_create(mctx, nelem, &dacl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (is_tuple) {
+ uint16_t port = 0;
+ uint32_t transports = 0;
+ bool encrypted = false;
+
+ if (obj_port != NULL && cfg_obj_isuint32(obj_port)) {
+ port = (uint16_t)cfg_obj_asuint32(obj_port);
+ }
+
+ if (obj_transport != NULL && cfg_obj_isstring(obj_transport)) {
+ if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "udp") == 0)
+ {
+ transports = isc_nm_udpsocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "tcp") == 0)
+ {
+ transports = isc_nm_tcpdnssocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "udp-tcp") == 0)
+ {
+ /* Good ol' DNS over port 53 */
+ transports = isc_nm_tcpdnssocket |
+ isc_nm_udpsocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "tls") == 0)
+ {
+ transports = isc_nm_tlsdnssocket;
+ encrypted = true;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "http") == 0)
+ {
+ transports = isc_nm_httpsocket;
+ encrypted = true;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "http-plain") == 0)
+ {
+ transports = isc_nm_httpsocket;
+ encrypted = false;
+ } else {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ if (port != 0 || transports != 0) {
+ dns_acl_add_port_transports(dacl, port, transports,
+ encrypted, false);
+ }
+ }
+
+ de = dacl->elements;
+ for (elt = cfg_list_first(caml); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *ce = cfg_listelt_value(elt);
+ bool neg = false;
+
+ INSIST(dacl->length <= dacl->alloc);
+
+ if (cfg_obj_istuple(ce)) {
+ /* Might be a negated element */
+ const cfg_obj_t *negated = cfg_tuple_get(ce, "negated");
+ if (!cfg_obj_isvoid(negated)) {
+ neg = true;
+ dacl->has_negatives = true;
+ ce = negated;
+ }
+ }
+
+ /*
+ * If nest_level is nonzero, then every element is
+ * to be stored as a separate, nested ACL rather than
+ * merged into the main iptable.
+ */
+ iptab = dacl->iptable;
+
+ if (nest_level != 0) {
+ result = dns_acl_create(mctx,
+ cfg_list_length(ce, false),
+ &de->nestedacl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ iptab = de->nestedacl->iptable;
+ }
+
+ if (cfg_obj_isnetprefix(ce)) {
+ /* Network prefix */
+ isc_netaddr_t addr;
+ unsigned int bitlen;
+
+ cfg_obj_asnetprefix(ce, &addr, &bitlen);
+ if (family != 0 && family != addr.family) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&addr, buf, sizeof(buf));
+ cfg_obj_log(ce, lctx, ISC_LOG_WARNING,
+ "'%s': incorrect address family; "
+ "ignoring",
+ buf);
+ if (nest_level != 0) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ continue;
+ }
+ result = isc_netaddr_prefixok(&addr, bitlen);
+ if (result != ISC_R_SUCCESS) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&addr, buf, sizeof(buf));
+ cfg_obj_log(ce, lctx, ISC_LOG_ERROR,
+ "'%s/%u': address/prefix length "
+ "mismatch",
+ buf, bitlen);
+ goto cleanup;
+ }
+
+ /*
+ * If nesting ACLs (nest_level != 0), we negate
+ * the nestedacl element, not the iptable entry.
+ */
+ setpos = (nest_level != 0 || !neg);
+ result = dns_iptable_addprefix(iptab, &addr, bitlen,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (nest_level > 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ } else {
+ continue;
+ }
+ } else if (cfg_obj_islist(ce)) {
+ /*
+ * If we're nesting ACLs, put the nested
+ * ACL onto the elements list; otherwise
+ * merge it into *this* ACL. We nest ACLs
+ * in two cases: 1) sortlist, 2) if the
+ * nested ACL contains negated members.
+ */
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ result = cfg_acl_fromconfig(ce, cctx, lctx, ctx, mctx,
+ new_nest_level, &inneracl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ nested_acl:
+ if (nest_level > 0 || inneracl->has_negatives) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ if (de->nestedacl != NULL) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ /*
+ * Merge the port-transports entries from the
+ * nested ACL into its parent.
+ */
+ dns_acl_merge_ports_transports(dacl, inneracl,
+ !neg);
+ dns_acl_attach(inneracl, &de->nestedacl);
+ dns_acl_detach(&inneracl);
+ /* Fall through. */
+ } else {
+ INSIST(dacl->length + inneracl->length <=
+ dacl->alloc);
+ dns_acl_merge(dacl, inneracl, !neg);
+ de += inneracl->length; /* elements added */
+ dns_acl_detach(&inneracl);
+ INSIST(dacl->length <= dacl->alloc);
+ continue;
+ }
+ } else if (cfg_obj_istype(ce, &cfg_type_keyref)) {
+ /* Key name. */
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_keyname;
+ de->negative = neg;
+ dns_name_init(&de->keyname, NULL);
+ result = convert_keyname(ce, lctx, mctx, &de->keyname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+#if defined(HAVE_GEOIP2)
+ } else if (cfg_obj_istuple(ce) &&
+ cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
+ {
+ INSIST(dacl->length < dacl->alloc);
+ result = parse_geoip_element(ce, lctx, ctx, de);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ de->type = dns_aclelementtype_geoip;
+ de->negative = neg;
+#endif /* HAVE_GEOIP2 */
+ } else if (cfg_obj_isstring(ce)) {
+ /* ACL name. */
+ const char *name = cfg_obj_asstring(ce);
+ if (strcasecmp(name, "any") == 0) {
+ /* Iptable entry with zero bit length. */
+ setpos = (nest_level != 0 || !neg);
+ result = dns_iptable_addprefix(iptab, NULL, 0,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (nest_level != 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ } else {
+ continue;
+ }
+ } else if (strcasecmp(name, "none") == 0) {
+ /* none == !any */
+ /*
+ * We don't unconditional set
+ * dacl->has_negatives and
+ * de->negative to true so we can handle
+ * "!none;".
+ */
+ setpos = (nest_level != 0 || neg);
+ result = dns_iptable_addprefix(iptab, NULL, 0,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (!neg) {
+ dacl->has_negatives = !neg;
+ }
+
+ if (nest_level != 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = !neg;
+ } else {
+ continue;
+ }
+ } else if (strcasecmp(name, "localhost") == 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_localhost;
+ de->negative = neg;
+ } else if (strcasecmp(name, "localnets") == 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_localnets;
+ de->negative = neg;
+ } else {
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ /*
+ * This call should just find the cached
+ * of the named acl.
+ */
+ result = convert_named_acl(ce, cctx, lctx, ctx,
+ mctx, new_nest_level,
+ &inneracl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ goto nested_acl;
+ }
+ } else {
+ cfg_obj_log(ce, lctx, ISC_LOG_WARNING,
+ "address match list contains "
+ "unsupported element type");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /*
+ * This should only be reached for localhost, localnets
+ * and keyname elements, and nested ACLs if nest_level is
+ * nonzero (i.e., in sortlists).
+ */
+ if (de->nestedacl != NULL &&
+ de->type != dns_aclelementtype_nestedacl)
+ {
+ dns_acl_detach(&de->nestedacl);
+ }
+
+ dns_acl_node_count(dacl)++;
+ de->node_num = dns_acl_node_count(dacl);
+
+ dacl->length++;
+ de++;
+ INSIST(dacl->length <= dacl->alloc);
+ }
+
+ dns_acl_attach(dacl, target);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ dns_acl_detach(&dacl);
+ return (result);
+}
diff --git a/lib/isccfg/dnsconf.c b/lib/isccfg/dnsconf.c
new file mode 100644
index 0000000..ccd7232
--- /dev/null
+++ b/lib/isccfg/dnsconf.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+/*! \file */
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+
+/*%
+ * A trusted key, as used in the "trusted-keys" statement.
+ */
+static cfg_tuplefielddef_t trustedkey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "flags", &cfg_type_uint32, 0 },
+ { "protocol", &cfg_type_uint32, 0 },
+ { "algorithm", &cfg_type_uint32, 0 },
+ { "key", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_trustedkey = { "trustedkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, trustedkey_fields };
+
+static cfg_type_t cfg_type_trustedkeys = { "trusted-keys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_trustedkey };
+
+/*%
+ * Clauses that can be found within the top level of the dns.conf
+ * file only.
+ */
+static cfg_clausedef_t dnsconf_clauses[] = {
+ { "trusted-keys", &cfg_type_trustedkeys, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+/*% The top-level dns.conf syntax. */
+
+static cfg_clausedef_t *dnsconf_clausesets[] = { dnsconf_clauses, NULL };
+
+cfg_type_t cfg_type_dnsconf = { "dnsconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, dnsconf_clausesets };
diff --git a/lib/isccfg/duration.c b/lib/isccfg/duration.c
new file mode 100644
index 0000000..9ed9d6f
--- /dev/null
+++ b/lib/isccfg/duration.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#include <isccfg/duration.h>
+
+/*
+ * isccfg_duration_fromtext initially taken from OpenDNSSEC code base.
+ * Modified to fit the BIND 9 code.
+ */
+isc_result_t
+isccfg_duration_fromtext(isc_textregion_t *source,
+ isccfg_duration_t *duration) {
+ char buf[CFG_DURATION_MAXLEN] = { 0 };
+ char *P, *X, *T, *W, *str;
+ bool not_weeks = false;
+ int i;
+ long long int lli;
+
+ /*
+ * Copy the buffer as it may not be NULL terminated.
+ */
+ if (source->length > sizeof(buf) - 1) {
+ return (ISC_R_BADNUMBER);
+ }
+ /* Copy source->length bytes and NULL terminate. */
+ snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base);
+ str = buf;
+
+ /* Clear out duration. */
+ for (i = 0; i < 7; i++) {
+ duration->parts[i] = 0;
+ }
+ duration->iso8601 = false;
+ duration->unlimited = false;
+
+ /* Every duration starts with 'P' */
+ if (toupper((unsigned char)str[0]) != 'P') {
+ return (ISC_R_BADNUMBER);
+ }
+ P = str;
+
+ /* Record the time indicator. */
+ T = strpbrk(str, "Tt");
+
+ /* Record years. */
+ X = strpbrk(str, "Yy");
+ if (X != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[0] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record months. */
+ X = strpbrk(str, "Mm");
+
+ /*
+ * M could be months or minutes. This is months if there is no time
+ * part, or this M indicator is before the time indicator.
+ */
+ if (X != NULL && (T == NULL || (size_t)(X - P) < (size_t)(T - P))) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[1] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record days. */
+ X = strpbrk(str, "Dd");
+ if (X != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[3] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Time part? */
+ if (T != NULL) {
+ str = T;
+ not_weeks = true;
+ }
+
+ /* Record hours. */
+ X = strpbrk(str, "Hh");
+ if (X != NULL && T != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[4] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record minutes. */
+ X = strpbrk(str, "Mm");
+
+ /*
+ * M could be months or minutes. This is minutes if there is a time
+ * part and the M indicator is behind the time indicator.
+ */
+ if (X != NULL && T != NULL && (size_t)(X - P) > (size_t)(T - P)) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[5] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record seconds. */
+ X = strpbrk(str, "Ss");
+ if (X != NULL && T != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[6] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Or is the duration configured in weeks? */
+ W = strpbrk(buf, "Ww");
+ if (W != NULL) {
+ if (not_weeks) {
+ /* Mix of weeks and other indicators is not allowed */
+ return (ISC_R_BADNUMBER);
+ } else {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[2] = (uint32_t)lli;
+ str = W;
+ }
+ }
+
+ /* Deal with trailing garbage. */
+ if (str[1] != '\0') {
+ return (ISC_R_BADNUMBER);
+ }
+
+ duration->iso8601 = true;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccfg_parse_duration(isc_textregion_t *source, isccfg_duration_t *duration) {
+ isc_result_t result;
+
+ REQUIRE(duration != NULL);
+
+ duration->unlimited = false;
+ result = isccfg_duration_fromtext(source, duration);
+ if (result == ISC_R_BADNUMBER) {
+ /* Fallback to dns_ttl_fromtext. */
+ uint32_t ttl;
+ result = dns_ttl_fromtext(source, &ttl);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * With dns_ttl_fromtext() the information on optional
+ * units is lost, and is treated as seconds from now on.
+ */
+ duration->iso8601 = false;
+ duration->parts[6] = ttl;
+ }
+ }
+
+ return (result);
+}
+
+uint32_t
+isccfg_duration_toseconds(const isccfg_duration_t *duration) {
+ uint64_t seconds = 0;
+
+ REQUIRE(duration != NULL);
+
+ seconds += (uint64_t)duration->parts[6]; /* Seconds */
+ seconds += (uint64_t)duration->parts[5] * 60; /* Minutes */
+ seconds += (uint64_t)duration->parts[4] * 3600; /* Hours */
+ seconds += (uint64_t)duration->parts[3] * 86400; /* Days */
+ seconds += (uint64_t)duration->parts[2] * 86400 * 7; /* Weeks */
+ /*
+ * The below additions are not entirely correct
+ * because days may vary per month and per year.
+ */
+ seconds += (uint64_t)duration->parts[1] * 86400 * 31; /* Months */
+ seconds += (uint64_t)duration->parts[0] * 86400 * 365; /* Years */
+
+ return (seconds > UINT32_MAX ? UINT32_MAX : (uint32_t)seconds);
+}
diff --git a/lib/isccfg/include/isccfg/aclconf.h b/lib/isccfg/include/isccfg/aclconf.h
new file mode 100644
index 0000000..eb1b9ab
--- /dev/null
+++ b/lib/isccfg/include/isccfg/aclconf.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+
+#include <dns/geoip.h>
+#include <dns/types.h>
+
+#include <isccfg/cfg.h>
+
+typedef struct cfg_aclconfctx {
+ ISC_LIST(dns_acl_t) named_acl_cache;
+ isc_mem_t *mctx;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_databases_t *geoip;
+#endif /* if defined(HAVE_GEOIP2) */
+ isc_refcount_t references;
+} cfg_aclconfctx_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret);
+/*
+ * Creates and initializes an ACL configuration context.
+ */
+
+void
+cfg_aclconfctx_detach(cfg_aclconfctx_t **actxp);
+/*
+ * Removes a reference to an ACL configuration context; when references
+ * reaches zero, clears the contents and deallocate the structure.
+ */
+
+void
+cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest);
+/*
+ * Attaches a pointer to an existing ACL configuration context.
+ */
+
+isc_result_t
+cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target);
+
+isc_result_t
+cfg_acl_fromconfig2(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, uint16_t family,
+ dns_acl_t **target);
+/*
+ * Construct a new dns_acl_t from configuration data in 'caml' and
+ * 'cctx'. Memory is allocated through 'mctx'.
+ *
+ * Any named ACLs referred to within 'caml' will be be converted
+ * into nested dns_acl_t objects. Multiple references to the same
+ * named ACLs will be converted into shared references to a single
+ * nested dns_acl_t object when the referring objects were created
+ * passing the same ACL configuration context 'ctx'.
+ *
+ * cfg_acl_fromconfig() is a backward-compatible version of
+ * cfg_acl_fromconfig2(), which allows an address family to be
+ * specified. If 'family' is not zero, then only addresses/prefixes
+ * of a matching family (AF_INET or AF_INET6) may be configured.
+ *
+ * On success, attach '*target' to the new dns_acl_t object.
+ *
+ * Require:
+ * 'ctx' to be non NULL.
+ * '*target' to be NULL or a valid dns_acl_t.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h
new file mode 100644
index 0000000..c4f2d86
--- /dev/null
+++ b/lib/isccfg/include/isccfg/cfg.h
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isccfg/cfg.h
+ * \brief
+ * This is the new, table-driven, YACC-free configuration file parser.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/list.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A configuration parser.
+ */
+typedef struct cfg_parser cfg_parser_t;
+
+/*%
+ * A configuration type definition object. There is a single
+ * static cfg_type_t object for each data type supported by
+ * the configuration parser.
+ */
+typedef struct cfg_type cfg_type_t;
+
+/*%
+ * A configuration object. This is the basic building block of the
+ * configuration parse tree. It contains a value (which may be
+ * of one of several types) and information identifying the file
+ * and line number the value came from, for printing error
+ * messages.
+ */
+typedef struct cfg_obj cfg_obj_t;
+
+/*%
+ * A configuration object list element.
+ */
+typedef struct cfg_listelt cfg_listelt_t;
+
+/*%
+ * A callback function to be called when parsing an option
+ * that needs to be interpreted at parsing time, like
+ * "directory".
+ */
+typedef isc_result_t (*cfg_parsecallback_t)(const char *clausename,
+ const cfg_obj_t *obj, void *arg);
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_parser_attach(cfg_parser_t *src, cfg_parser_t **dest);
+/*%<
+ * Reference a parser object.
+ */
+
+isc_result_t
+cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret);
+/*%<
+ * Create a configuration file parser. Any warning and error
+ * messages will be logged to 'lctx'.
+ *
+ * The parser object returned can be used for a single call
+ * to cfg_parse_file() or cfg_parse_buffer(). It must not
+ * be reused for parsing multiple files or buffers.
+ */
+
+void
+cfg_parser_setflags(cfg_parser_t *pctx, unsigned int flags, bool turn_on);
+/*%<
+ * Set parser context flags. The flags are not checked for sensibility.
+ * If 'turn_on' is 'true' the flags will be set, otherwise the flags will
+ * be cleared.
+ *
+ * Requires:
+ *\li "pctx" is not NULL.
+ */
+
+void
+cfg_parser_setcallback(cfg_parser_t *pctx, cfg_parsecallback_t callback,
+ void *arg);
+/*%<
+ * Make the parser call 'callback' whenever it encounters
+ * a configuration clause with the callback attribute,
+ * passing it the clause name, the clause value,
+ * and 'arg' as arguments.
+ *
+ * To restore the default of not invoking callbacks, pass
+ * callback==NULL and arg==NULL.
+ */
+
+isc_result_t
+cfg_parse_file(cfg_parser_t *pctx, const char *file, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+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);
+/*%<
+ * Read a configuration containing data of type 'type'
+ * and make '*ret' point to its parse tree.
+ *
+ * The configuration is read from the file 'filename'
+ * (isc_parse_file()) or the buffer 'buffer'
+ * (isc_parse_buffer()).
+ *
+ * If 'file' is not NULL, it is the name of the file, or a name to use
+ * for the buffer in place of the filename, when logging errors.
+ *
+ * If 'line' is not 0, then it is the beginning line number to report
+ * when logging errors. This is useful when passing text that has been
+ * read from the middle of a file.
+ *
+ * Returns an error if the file or buffer does not parse correctly.
+ *
+ * Requires:
+ *\li "filename" is valid.
+ *\li "mem" is valid.
+ *\li "type" is valid.
+ *\li "cfg" is non-NULL and "*cfg" is NULL.
+ *\li "flags" be one or more of CFG_PCTX_NODEPRECATED or zero.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS - success
+ *\li #ISC_R_NOMEMORY - no memory available
+ *\li #ISC_R_INVALIDFILE - file doesn't exist or is unreadable
+ *\li others - file contains errors
+ */
+
+isc_result_t
+cfg_parser_mapadd(cfg_parser_t *pctx, cfg_obj_t *mapobj, cfg_obj_t *obj,
+ const char *clause);
+/*%<
+ * Add the object 'obj' to the specified clause in mapbody 'mapobj'.
+ * Used for adding new zones.
+ *
+ * Require:
+ * \li 'obj' is a valid cfg_obj_t.
+ * \li 'mapobj' is a valid cfg_obj_t of type map.
+ * \li 'pctx' is a valid cfg_parser_t.
+ */
+
+void
+cfg_parser_reset(cfg_parser_t *pctx);
+/*%<
+ * Reset an existing parser so it can be re-used for a new file or
+ * buffer.
+ */
+
+void
+cfg_parser_destroy(cfg_parser_t **pctxp);
+/*%<
+ * Remove a reference to a configuration parser; destroy it if there are no
+ * more references.
+ */
+
+bool
+cfg_obj_isvoid(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of void type (e.g., an optional
+ * value not specified).
+ */
+
+bool
+cfg_obj_ismap(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a map type.
+ */
+
+bool
+cfg_obj_isfixedpoint(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a fixedpoint type.
+ */
+
+bool
+cfg_obj_ispercentage(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a percentage type.
+ */
+
+isc_result_t
+cfg_map_get(const cfg_obj_t *mapobj, const char *name, const cfg_obj_t **obj);
+/*%<
+ * Extract an element from a configuration object, which
+ * must be of a map type.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ * \li 'name' points to a null-terminated string.
+ * \li 'obj' is non-NULL and '*obj' is NULL.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS - success
+ * \li #ISC_R_NOTFOUND - name not found in map
+ */
+
+const cfg_obj_t *
+cfg_map_getname(const cfg_obj_t *mapobj);
+/*%<
+ * Get the name of a named map object, like a server "key" clause.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ *
+ * Returns:
+ * \li A pointer to a configuration object naming the map object,
+ * or NULL if the map object does not have a name.
+ */
+
+unsigned int
+cfg_map_count(const cfg_obj_t *mapobj);
+/*%<
+ * Get the number of elements defined in the symbol table of a map object.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ *
+ * Returns:
+ * \li The number of elements in the map object.
+ */
+
+bool
+cfg_obj_istuple(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a map type.
+ */
+
+const cfg_obj_t *
+cfg_tuple_get(const cfg_obj_t *tupleobj, const char *name);
+/*%<
+ * Extract an element from a configuration object, which
+ * must be of a tuple type.
+ *
+ * Requires:
+ * \li 'tupleobj' points to a valid configuration object of a tuple type.
+ * \li 'name' points to a null-terminated string naming one of the
+ *\li fields of said tuple type.
+ */
+
+bool
+cfg_obj_isuint32(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of integer type.
+ */
+
+uint32_t
+cfg_obj_asuint32(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of 32-bit integer type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of 32-bit integer type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+bool
+cfg_obj_isuint64(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of integer type.
+ */
+
+uint64_t
+cfg_obj_asuint64(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of 64-bit integer type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of 64-bit integer type.
+ *
+ * Returns:
+ * \li A 64-bit unsigned integer.
+ */
+
+uint32_t
+cfg_obj_asfixedpoint(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of fixed point number.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of fixed point type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+uint32_t
+cfg_obj_aspercentage(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of percentage
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of percentage type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+bool
+cfg_obj_isduration(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of duration type.
+ */
+
+uint32_t
+cfg_obj_asduration(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of duration
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of duration type.
+ *
+ * Returns:
+ * \li A duration in seconds.
+ */
+
+bool
+cfg_obj_isstring(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of string type.
+ */
+
+const char *
+cfg_obj_asstring(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of a string type
+ * as a null-terminated string.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a string type.
+ *
+ * Returns:
+ * \li A pointer to a null terminated string.
+ */
+
+bool
+cfg_obj_isboolean(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a boolean type.
+ */
+
+bool
+cfg_obj_asboolean(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of a boolean type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a boolean type.
+ *
+ * Returns:
+ * \li A boolean value.
+ */
+
+bool
+cfg_obj_issockaddr(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is a socket address.
+ */
+
+const isc_sockaddr_t *
+cfg_obj_assockaddr(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object representing a socket address.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a socket address
+ * type.
+ *
+ * Returns:
+ * \li A pointer to a sockaddr. The sockaddr must be copied by the caller
+ * if necessary.
+ */
+
+bool
+cfg_obj_isnetprefix(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is a network prefix.
+ */
+
+void
+cfg_obj_asnetprefix(const cfg_obj_t *obj, isc_netaddr_t *netaddr,
+ unsigned int *prefixlen);
+/*%<
+ * Gets the value of a configuration object representing a network
+ * prefix. The network address is returned through 'netaddr' and the
+ * prefix length in bits through 'prefixlen'.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of network prefix type.
+ *\li 'netaddr' and 'prefixlen' are non-NULL.
+ */
+
+bool
+cfg_obj_islist(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of list type.
+ */
+
+const cfg_listelt_t *
+cfg_list_first(const cfg_obj_t *obj);
+/*%<
+ * Returns the first list element in a configuration object of a list type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a list type or NULL.
+ *
+ * Returns:
+ * \li A pointer to a cfg_listelt_t representing the first list element,
+ * or NULL if the list is empty or nonexistent.
+ */
+
+const cfg_listelt_t *
+cfg_list_next(const cfg_listelt_t *elt);
+/*%<
+ * Returns the next element of a list of configuration objects.
+ *
+ * Requires:
+ * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or
+ * a previous call to cfg_list_next().
+ *
+ * Returns:
+ * \li A pointer to a cfg_listelt_t representing the next element,
+ * or NULL if there are no more elements.
+ */
+
+unsigned int
+cfg_list_length(const cfg_obj_t *obj, bool recurse);
+/*%<
+ * Returns the length of a list of configure objects. If obj is
+ * not a list, returns 0. If recurse is true, add in the length of
+ * all contained lists.
+ */
+
+cfg_obj_t *
+cfg_listelt_value(const cfg_listelt_t *elt);
+/*%<
+ * Returns the configuration object associated with cfg_listelt_t.
+ *
+ * Requires:
+ * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or
+ * cfg_list_next().
+ *
+ * Returns:
+ * \li A non-NULL pointer to a configuration object.
+ */
+
+void
+cfg_print(const cfg_obj_t *obj,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+void
+cfg_printx(const cfg_obj_t *obj, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+
+#define CFG_PRINTER_XKEY 0x1 /* '?' out shared keys. */
+#define CFG_PRINTER_ONELINE 0x2 /* print config as a single line */
+#define CFG_PRINTER_ACTIVEONLY \
+ 0x4 /* print only active configuration \
+ * options, omitting ancient, \
+ * obsolete, nonimplemented, \
+ * and test-only options. */
+
+/*%<
+ * Print the configuration object 'obj' by repeatedly calling the
+ * function 'f', passing 'closure' and a region of text starting
+ * at 'text' and comprising 'textlen' characters.
+ *
+ * If CFG_PRINTER_XKEY the contents of shared keys will be obscured
+ * by replacing them with question marks ('?')
+ */
+
+void
+cfg_print_grammar(const cfg_type_t *type, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+/*%<
+ * Print a summary of the grammar of the configuration type 'type'.
+ */
+
+bool
+cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type);
+/*%<
+ * Return true iff 'obj' is of type 'type'.
+ */
+
+void
+cfg_obj_attach(cfg_obj_t *src, cfg_obj_t **dest);
+/*%<
+ * Reference a configuration object.
+ */
+
+void
+cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **obj);
+/*%<
+ * Delete a reference to a configuration object; destroy the object if
+ * there are no more references.
+ *
+ * Require:
+ * \li '*obj' is a valid cfg_obj_t.
+ * \li 'pctx' is a valid cfg_parser_t.
+ */
+
+void
+cfg_obj_log(const cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt,
+ ...) ISC_FORMAT_PRINTF(4, 5);
+/*%<
+ * Log a message concerning configuration object 'obj' to the logging
+ * channel of 'pctx', at log level 'level'. The message will be prefixed
+ * with the file name(s) and line number where 'obj' was defined.
+ */
+
+const char *
+cfg_obj_file(const cfg_obj_t *obj);
+/*%<
+ * Return the file that defined this object.
+ */
+
+unsigned int
+cfg_obj_line(const cfg_obj_t *obj);
+/*%<
+ * Return the line in file where this object was defined.
+ */
+
+const char *
+cfg_map_firstclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx);
+const char *
+cfg_map_nextclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx);
+
+typedef isc_result_t(pluginlist_cb_t)(const cfg_obj_t *config,
+ const cfg_obj_t *obj,
+ const char *plugin_path,
+ const char *parameters,
+ void *callback_data);
+/*%<
+ * Function prototype for the callback used with cfg_pluginlist_foreach().
+ * Called once for each element of the list passed to cfg_pluginlist_foreach().
+ * If this callback returns anything else than #ISC_R_SUCCESS, no further list
+ * elements will be processed.
+ *
+ * \li 'config' - the 'config' object passed to cfg_pluginlist_foreach()
+ * \li 'obj' - object representing the specific "plugin" stanza to be processed
+ * \li 'plugin_path' - path to the shared object with plugin code
+ * \li 'parameters' - configuration text for the plugin
+ * \li 'callback_data' - the pointer passed to cfg_pluginlist_foreach()
+ */
+
+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);
+/*%<
+ * For every "plugin" stanza present in 'list' (which in turn is a part of
+ * 'config'), invoke the given 'callback', passing 'callback_data' to it along
+ * with a fixed set of arguments (see the definition of the #pluginlist_cb_t
+ * type). Use logging context 'lctx' for logging error messages. Interrupt
+ * processing if 'callback' returns something else than #ISC_R_SUCCESS for any
+ * element of 'list'.
+ *
+ * Requires:
+ *
+ * \li 'config' is not NULL
+ * \li 'callback' is not NULL
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS if 'callback' returned #ISC_R_SUCCESS for all elements of
+ * 'list'
+ * \li first 'callback' return value which was not #ISC_R_SUCCESS otherwise
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/duration.h b/lib/isccfg/include/isccfg/duration.h
new file mode 100644
index 0000000..bd0c35b
--- /dev/null
+++ b/lib/isccfg/include/isccfg/duration.h
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define CFG_DURATION_MAXLEN 80
+
+/*%
+ * A configuration object to store ISO 8601 durations.
+ */
+typedef struct isccfg_duration {
+ /*
+ * The duration is stored in multiple parts:
+ * [0] Years
+ * [1] Months
+ * [2] Weeks
+ * [3] Days
+ * [4] Hours
+ * [5] Minutes
+ * [6] Seconds
+ */
+ uint32_t parts[7];
+ bool iso8601;
+ bool unlimited;
+} isccfg_duration_t;
+
+isc_result_t
+isccfg_duration_fromtext(isc_textregion_t *source, isccfg_duration_t *duration);
+/*%<
+ * Converts an ISO 8601 duration style value.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_BADNUMBER
+ */
+
+isc_result_t
+isccfg_parse_duration(isc_textregion_t *source, isccfg_duration_t *duration);
+/*%<
+ * Converts a duration string to a ISO 8601 duration.
+ * If the string does not start with a P (or p), fall back to TTL-style value.
+ * In that case the duration will be treated in seconds only.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_BADNUMBER
+ *\li DNS_R_BADTTL
+ */
+
+uint32_t
+isccfg_duration_toseconds(const isccfg_duration_t *duration);
+/*%<
+ * Converts an ISO 8601 duration to seconds.
+ * The conversion is approximate:
+ * - Months will be treated as 31 days.
+ * - Years will be treated as 365 days.
+ *
+ * Notes:
+ *\li If the duration in seconds is greater than UINT32_MAX, the return value
+ * will be UINT32_MAX.
+ *
+ * Returns:
+ *\li The duration in seconds.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h
new file mode 100644
index 0000000..83482d9
--- /dev/null
+++ b/lib/isccfg/include/isccfg/grammar.h
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \file isccfg/grammar.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lex.h>
+#include <isc/netaddr.h>
+#include <isc/region.h>
+#include <isc/sockaddr.h>
+#include <isc/types.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/duration.h>
+
+/*
+ * Definitions shared between the configuration parser
+ * and the grammars; not visible to users of the parser.
+ */
+
+/*% Clause may occur multiple times (e.g., "zone") */
+#define CFG_CLAUSEFLAG_MULTI 0x00000001
+/*% Clause is obsolete (logs a warning, but is not a fatal error) */
+#define CFG_CLAUSEFLAG_OBSOLETE 0x00000002
+/* obsolete: #define CFG_CLAUSEFLAG_NOTIMP 0x00000004 */
+/* obsolete: #define CFG_CLAUSEFLAG_NYI 0x00000008 */
+/* obsolete: #define CFG_CLAUSEFLAG_NEWDEFAULT 0x00000010 */
+/*%
+ * Clause needs to be interpreted during parsing
+ * by calling a callback function, like the
+ * "directory" option.
+ */
+#define CFG_CLAUSEFLAG_CALLBACK 0x00000020
+/*% An option that is only used in testing. */
+#define CFG_CLAUSEFLAG_TESTONLY 0x00000040
+/*% A configuration option that was not configured at compile time. */
+#define CFG_CLAUSEFLAG_NOTCONFIGURED 0x00000080
+/*% An option for an experimental feature. */
+#define CFG_CLAUSEFLAG_EXPERIMENTAL 0x00000100
+/*% An option that should be omited from the documentation */
+#define CFG_CLAUSEFLAG_NODOC 0x00000200
+/*% Clause will be obsolete in a future release (logs a warning) */
+#define CFG_CLAUSEFLAG_DEPRECATED 0x00000400
+/*% Clause has been obsolete so long that it's now a fatal error */
+#define CFG_CLAUSEFLAG_ANCIENT 0x00000800
+
+/*%
+ * Zone types for which a clause is valid:
+ * These share space with CFG_CLAUSEFLAG values, but count
+ * down from the top.
+ */
+#define CFG_ZONE_PRIMARY 0x80000000
+#define CFG_ZONE_SECONDARY 0x40000000
+#define CFG_ZONE_STUB 0x20000000
+#define CFG_ZONE_HINT 0x10000000
+#define CFG_ZONE_FORWARD 0x08000000
+#define CFG_ZONE_STATICSTUB 0x04000000
+#define CFG_ZONE_REDIRECT 0x02000000
+#define CFG_ZONE_DELEGATION 0x01000000
+#define CFG_ZONE_INVIEW 0x00800000
+#define CFG_ZONE_MIRROR 0x00400000
+
+typedef struct cfg_clausedef cfg_clausedef_t;
+typedef struct cfg_tuplefielddef cfg_tuplefielddef_t;
+typedef struct cfg_printer cfg_printer_t;
+typedef ISC_LIST(cfg_listelt_t) cfg_list_t;
+typedef struct cfg_map cfg_map_t;
+typedef struct cfg_rep cfg_rep_t;
+
+/*
+ * Function types for configuration object methods
+ */
+
+typedef isc_result_t (*cfg_parsefunc_t)(cfg_parser_t *, const cfg_type_t *type,
+ cfg_obj_t **);
+typedef void (*cfg_printfunc_t)(cfg_printer_t *, const cfg_obj_t *);
+typedef void (*cfg_docfunc_t)(cfg_printer_t *, const cfg_type_t *);
+typedef void (*cfg_freefunc_t)(cfg_parser_t *, cfg_obj_t *);
+
+/*
+ * Structure definitions
+ */
+
+/*%
+ * A configuration printer object. This is an abstract
+ * interface to a destination to which text can be printed
+ * by calling the function 'f'.
+ */
+struct cfg_printer {
+ void (*f)(void *closure, const char *text, int textlen);
+ void *closure;
+ int indent;
+ int flags;
+};
+
+/*% A clause definition. */
+struct cfg_clausedef {
+ const char *name;
+ cfg_type_t *type;
+ unsigned int flags;
+};
+
+/*% A tuple field definition. */
+struct cfg_tuplefielddef {
+ const char *name;
+ cfg_type_t *type;
+ unsigned int flags;
+};
+
+/*% A configuration object type definition. */
+struct cfg_type {
+ const char *name; /*%< For debugging purposes only */
+ cfg_parsefunc_t parse;
+ cfg_printfunc_t print;
+ cfg_docfunc_t doc; /*%< Print grammar description */
+ cfg_rep_t *rep; /*%< Data representation */
+ const void *of; /*%< Additional data for meta-types */
+};
+
+/*% A keyword-type definition, for things like "port <integer>". */
+typedef struct {
+ const char *name;
+ const cfg_type_t *type;
+} keyword_type_t;
+
+struct cfg_map {
+ cfg_obj_t *id; /*%< Used for 'named maps' like
+ * keys, zones, &c */
+ const cfg_clausedef_t *const *clausesets; /*%< The clauses that
+ * can occur in this map;
+ * used for printing */
+ isc_symtab_t *symtab;
+};
+
+typedef struct cfg_netprefix cfg_netprefix_t;
+
+struct cfg_netprefix {
+ isc_netaddr_t address; /* IP4/IP6 */
+ unsigned int prefixlen;
+};
+
+/*%
+ * A configuration data representation.
+ */
+struct cfg_rep {
+ const char *name; /*%< For debugging only */
+ cfg_freefunc_t free; /*%< How to free this kind of data. */
+};
+
+/*%
+ * A configuration object. This is the main building block
+ * of the configuration parse tree.
+ */
+
+struct cfg_obj {
+ const cfg_type_t *type;
+ union {
+ uint32_t uint32;
+ uint64_t uint64;
+ isc_textregion_t string; /*%< null terminated, too */
+ bool boolean;
+ cfg_map_t map;
+ cfg_list_t list;
+ cfg_obj_t **tuple;
+ isc_sockaddr_t sockaddr;
+ struct {
+ isc_sockaddr_t sockaddr;
+ int32_t dscp;
+ } sockaddrdscp;
+ cfg_netprefix_t netprefix;
+ isccfg_duration_t duration;
+ } value;
+ isc_refcount_t references; /*%< reference counter */
+ const char *file;
+ unsigned int line;
+ cfg_parser_t *pctx;
+};
+
+/*% A list element. */
+struct cfg_listelt {
+ cfg_obj_t *obj;
+ ISC_LINK(cfg_listelt_t) link;
+};
+
+/*% The parser object. */
+struct cfg_parser {
+ isc_mem_t *mctx;
+ isc_log_t *lctx;
+ isc_lex_t *lexer;
+ unsigned int errors;
+ unsigned int warnings;
+ isc_token_t token;
+
+ /*% We are at the end of all input. */
+ bool seen_eof;
+
+ /*% The current token has been pushed back. */
+ bool ungotten;
+
+ /*%
+ * The stack of currently active files, represented
+ * as a configuration list of configuration strings.
+ * The head is the top-level file, subsequent elements
+ * (if any) are the nested include files, and the
+ * last element is the file currently being parsed.
+ */
+ cfg_obj_t *open_files;
+
+ /*%
+ * Names of files that we have parsed and closed
+ * and were previously on the open_file list.
+ * We keep these objects around after closing
+ * the files because the file names may still be
+ * referenced from other configuration objects
+ * for use in reporting semantic errors after
+ * parsing is complete.
+ */
+ cfg_obj_t *closed_files;
+
+ /*%
+ * Name of a buffer being parsed; used only for
+ * logging.
+ */
+ char const *buf_name;
+
+ /*%
+ * Current line number. We maintain our own
+ * copy of this so that it is available even
+ * when a file has just been closed.
+ */
+ unsigned int line;
+
+ /*%
+ * Parser context flags, used for maintaining state
+ * from one token to the next.
+ */
+ unsigned int flags;
+
+ /*%< Reference counter */
+ isc_refcount_t references;
+
+ cfg_parsecallback_t callback;
+ void *callbackarg;
+};
+
+/* Parser context flags */
+#define CFG_PCTX_SKIP 0x1
+#define CFG_PCTX_NODEPRECATED 0x2
+
+/*@{*/
+/*%
+ * Flags defining whether to accept certain types of network addresses.
+ */
+#define CFG_ADDR_V4OK 0x00000001
+#define CFG_ADDR_V4PREFIXOK 0x00000002
+#define CFG_ADDR_V6OK 0x00000004
+#define CFG_ADDR_WILDOK 0x00000008
+#define CFG_ADDR_DSCPOK 0x00000010
+#define CFG_ADDR_PORTOK 0x00000020
+#define CFG_ADDR_MASK (CFG_ADDR_V6OK | CFG_ADDR_V4OK)
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined data representation types.
+ */
+extern cfg_rep_t cfg_rep_uint32;
+extern cfg_rep_t cfg_rep_uint64;
+extern cfg_rep_t cfg_rep_string;
+extern cfg_rep_t cfg_rep_boolean;
+extern cfg_rep_t cfg_rep_map;
+extern cfg_rep_t cfg_rep_list;
+extern cfg_rep_t cfg_rep_tuple;
+extern cfg_rep_t cfg_rep_sockaddr;
+extern cfg_rep_t cfg_rep_netprefix;
+extern cfg_rep_t cfg_rep_void;
+extern cfg_rep_t cfg_rep_fixedpoint;
+extern cfg_rep_t cfg_rep_percentage;
+extern cfg_rep_t cfg_rep_duration;
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined configuration object types.
+ */
+extern cfg_type_t cfg_type_boolean;
+extern cfg_type_t cfg_type_uint32;
+extern cfg_type_t cfg_type_uint64;
+extern cfg_type_t cfg_type_qstring;
+extern cfg_type_t cfg_type_astring;
+extern cfg_type_t cfg_type_ustring;
+extern cfg_type_t cfg_type_sstring;
+extern cfg_type_t cfg_type_bracketed_aml;
+extern cfg_type_t cfg_type_bracketed_text;
+extern cfg_type_t cfg_type_optional_bracketed_text;
+extern cfg_type_t cfg_type_keyref;
+extern cfg_type_t cfg_type_sockaddr;
+extern cfg_type_t cfg_type_sockaddrdscp;
+extern cfg_type_t cfg_type_netaddr;
+extern cfg_type_t cfg_type_netaddr4;
+extern cfg_type_t cfg_type_netaddr4wild;
+extern cfg_type_t cfg_type_netaddr6;
+extern cfg_type_t cfg_type_netaddr6wild;
+extern cfg_type_t cfg_type_netprefix;
+extern cfg_type_t cfg_type_void;
+extern cfg_type_t cfg_type_token;
+extern cfg_type_t cfg_type_unsupported;
+extern cfg_type_t cfg_type_fixedpoint;
+extern cfg_type_t cfg_type_percentage;
+extern cfg_type_t cfg_type_duration;
+extern cfg_type_t cfg_type_duration_or_unlimited;
+/*@}*/
+
+isc_result_t
+cfg_gettoken(cfg_parser_t *pctx, int options);
+
+isc_result_t
+cfg_peektoken(cfg_parser_t *pctx, int options);
+
+void
+cfg_ungettoken(cfg_parser_t *pctx);
+
+#define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE)
+
+isc_result_t
+cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+void
+cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u);
+
+isc_result_t
+cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na);
+
+void
+cfg_print_rawaddr(cfg_printer_t *pctx, const isc_netaddr_t *na);
+
+bool
+cfg_lookingat_netaddr(cfg_parser_t *pctx, unsigned int flags);
+
+isc_result_t
+cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port);
+
+isc_result_t
+cfg_parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_sockaddr(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_print_boolean(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_special(cfg_parser_t *pctx, int special);
+/*%< Parse a required special character 'special'. */
+
+isc_result_t
+cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+isc_result_t
+cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+isc_result_t
+cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
+ cfg_listelt_t **ret);
+
+isc_result_t
+cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type);
+
+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);
+
+void
+cfg_doc_enum_or_other(cfg_printer_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype);
+
+void
+cfg_print_chars(cfg_printer_t *pctx, const char *text, int len);
+/*%< Print 'len' characters at 'text' */
+
+void
+cfg_print_cstr(cfg_printer_t *pctx, const char *s);
+/*%< Print the null-terminated string 's' */
+
+isc_result_t
+cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_fixedpoint(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_fixedpoint(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type);
+/*%<
+ * Print a description of the grammar of an arbitrary configuration
+ * type 'type'
+ */
+
+void
+cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type);
+/*%<
+ * Document the type 'type' as a terminal by printing its
+ * name in angle brackets, e.g., &lt;uint32>.
+ */
+
+void
+cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+/*!
+ * Pass one of these flags to cfg_parser_error() to include the
+ * token text in log message.
+ */
+#define CFG_LOG_NEAR 0x00000001 /*%< Say "near <token>" */
+#define CFG_LOG_BEFORE 0x00000002 /*%< Say "before <token>" */
+#define CFG_LOG_NOPREP 0x00000004 /*%< Say just "<token>" */
+
+void
+cfg_parser_warning(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+bool
+cfg_is_enum(const char *s, const char *const *enums);
+/*%< Return true iff the string 's' is one of the strings in 'enums' */
+
+bool
+cfg_clause_validforzone(const char *name, unsigned int ztype);
+/*%<
+ * Check whether an option is legal for the specified zone type.
+ */
+
+void
+cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+/*%<
+ * Print a summary of the grammar of the zone type represented by
+ * 'zonetype'.
+ */
+
+void
+cfg_print_clauseflags(cfg_printer_t *pctx, unsigned int flags);
+/*%<
+ * Print clause flags (e.g. "obsolete", "not implemented", etc) in
+ * human readable form
+ */
+
+void
+cfg_print_indent(cfg_printer_t *pctx);
+/*%<
+ * Print the necessary indent required by the current settings of 'pctx'.
+ */
diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h
new file mode 100644
index 0000000..7b1e075
--- /dev/null
+++ b/lib/isccfg/include/isccfg/kaspconf.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+
+#include <isccfg/cfg.h>
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
+ isc_mem_t *mctx, isc_log_t *logctx,
+ dns_kasplist_t *kasplist, dns_kasp_t **kaspp);
+/*%<
+ * Create and configure a KASP. If 'default_kasp' is not NULL, the built-in
+ * default configuration is used to set values that are not explicitly set in
+ * the policy. If a 'kasplist' is provided, a lookup happens and if a KASP
+ * already exists with the same name, no new KASP is created, and no attach to
+ * 'kaspp' happens.
+ *
+ * Requires:
+ *
+ *\li 'name' is either NULL, or a valid C string.
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'logctx' is a valid logging context.
+ *
+ *\li kaspp != NULL && *kaspp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS If creating and configuring the KASP succeeds.
+ *\li #ISC_R_EXISTS If 'kasplist' already has a kasp structure with 'name'.
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/log.h b/lib/isccfg/include/isccfg/log.h
new file mode 100644
index 0000000..8af3095
--- /dev/null
+++ b/lib/isccfg/include/isccfg/log.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \file isccfg/log.h */
+
+#include <isc/lang.h>
+#include <isc/log.h>
+
+extern isc_logcategory_t cfg_categories[];
+extern isc_logmodule_t cfg_modules[];
+
+#define CFG_LOGCATEGORY_CONFIG (&cfg_categories[0])
+
+#define CFG_LOGMODULE_PARSER (&cfg_modules[0])
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_log_init(isc_log_t *lctx);
+/*%<
+ * Make the libisccfg categories and modules available for use with the
+ * ISC logging library.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li cfg_log_init() is called only once.
+ *
+ * Ensures:
+ * \li The categories and modules defined above are available for
+ * use by isc_log_usechannnel() and isc_log_write().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h
new file mode 100644
index 0000000..f2d0145
--- /dev/null
+++ b/lib/isccfg/include/isccfg/namedconf.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \file isccfg/namedconf.h
+ * \brief
+ * This module defines the named.conf, rndc.conf, and rndc.key grammars.
+ */
+
+#include <isccfg/cfg.h>
+
+/*
+ * Configuration object types.
+ */
+extern cfg_type_t cfg_type_namedconf;
+/*%< A complete named.conf file. */
+
+extern cfg_type_t cfg_type_bindkeys;
+/*%< A bind.keys file. */
+
+extern cfg_type_t cfg_type_newzones;
+/*%< A new-zones file (for zones added by 'rndc addzone'). */
+
+extern cfg_type_t cfg_type_addzoneconf;
+/*%< A single zone passed via the addzone rndc command. */
+
+extern cfg_type_t cfg_type_rndcconf;
+/*%< A complete rndc.conf file. */
+
+extern cfg_type_t cfg_type_rndckey;
+/*%< A complete rndc.key file. */
+
+extern cfg_type_t cfg_type_sessionkey;
+/*%< A complete session.key file. */
+
+extern cfg_type_t cfg_type_keyref;
+/*%< A key reference, used as an ACL element */
+
+/*%< Zone options */
+extern cfg_type_t cfg_type_zoneopts;
+
+/*%< DNSSEC Key and Signing Policy options */
+extern cfg_type_t cfg_type_dnssecpolicyopts;
diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
new file mode 100644
index 0000000..ba36b0d
--- /dev/null
+++ b/lib/isccfg/kaspconf.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/nsec3.h>
+#include <dns/secalg.h>
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/duration.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#define DEFAULT_NSEC3PARAM_ITER 0
+#define DEFAULT_NSEC3PARAM_SALTLEN 0
+
+/*
+ * Utility function for getting a configuration option.
+ */
+static isc_result_t
+confget(cfg_obj_t const *const *maps, const char *name, const cfg_obj_t **obj) {
+ for (size_t i = 0;; i++) {
+ if (maps[i] == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+}
+
+/*
+ * Utility function for parsing durations from string.
+ */
+static uint32_t
+parse_duration(const char *str) {
+ uint32_t time = 0;
+ isccfg_duration_t duration;
+ isc_result_t result;
+ isc_textregion_t tr;
+
+ DE_CONST(str, tr.base);
+ tr.length = strlen(tr.base);
+ result = isccfg_parse_duration(&tr, &duration);
+ if (result == ISC_R_SUCCESS) {
+ time = isccfg_duration_toseconds(&duration);
+ }
+ return (time);
+}
+
+/*
+ * Utility function for configuring durations.
+ */
+static uint32_t
+get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) {
+ const cfg_obj_t *obj;
+ isc_result_t result;
+ obj = NULL;
+
+ result = confget(maps, option, &obj);
+ if (result == ISC_R_NOTFOUND) {
+ return (parse_duration(dfl));
+ }
+ INSIST(result == ISC_R_SUCCESS);
+ return (cfg_obj_asduration(obj));
+}
+
+/*
+ * Create a new kasp key derived from configuration.
+ */
+static isc_result_t
+cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
+ isc_log_t *logctx, uint32_t ksk_min_lifetime,
+ uint32_t zsk_min_lifetime) {
+ isc_result_t result;
+ dns_kasp_key_t *key = NULL;
+
+ /* Create a new key reference. */
+ result = dns_kasp_key_create(kasp, &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (config == NULL) {
+ /* We are creating a key reference for the default kasp. */
+ key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK;
+ key->lifetime = 0; /* unlimited */
+ key->algorithm = DNS_KEYALG_ECDSA256;
+ key->length = -1;
+ } else {
+ const char *rolestr = NULL;
+ const cfg_obj_t *obj = NULL;
+ isc_consttextregion_t alg;
+ bool error = false;
+
+ rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role"));
+ if (strcmp(rolestr, "ksk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_KSK;
+ } else if (strcmp(rolestr, "zsk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ } else if (strcmp(rolestr, "csk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_KSK;
+ key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+
+ key->lifetime = 0; /* unlimited */
+ obj = cfg_tuple_get(config, "lifetime");
+ if (cfg_obj_isduration(obj)) {
+ key->lifetime = cfg_obj_asduration(obj);
+ }
+ if (key->lifetime > 0) {
+ if (key->lifetime < 30 * (24 * 3600)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: key lifetime is "
+ "shorter than 30 days");
+ }
+ if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 &&
+ key->lifetime <= ksk_min_lifetime)
+ {
+ error = true;
+ }
+ if ((key->role & DNS_KASP_KEY_ROLE_ZSK) != 0 &&
+ key->lifetime <= zsk_min_lifetime)
+ {
+ error = true;
+ }
+ if (error) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: key lifetime is "
+ "shorter than the time it takes to "
+ "do a rollover");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ obj = cfg_tuple_get(config, "algorithm");
+ alg.base = cfg_obj_asstring(obj);
+ alg.length = strlen(alg.base);
+ result = dns_secalg_fromtext(&key->algorithm,
+ (isc_textregion_t *)&alg);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: bad algorithm %s",
+ alg.base);
+ result = DNS_R_BADALG;
+ goto cleanup;
+ }
+
+ obj = cfg_tuple_get(config, "length");
+ if (cfg_obj_isuint32(obj)) {
+ uint32_t min, size;
+ size = cfg_obj_asuint32(obj);
+
+ switch (key->algorithm) {
+ case DNS_KEYALG_RSASHA1:
+ case DNS_KEYALG_NSEC3RSASHA1:
+ case DNS_KEYALG_RSASHA256:
+ case DNS_KEYALG_RSASHA512:
+ min = DNS_KEYALG_RSASHA512 ? 1024 : 512;
+ if (size < min || size > 4096) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: key with "
+ "algorithm %s has invalid "
+ "key length %u",
+ alg.base, size);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ break;
+ case DNS_KEYALG_ECDSA256:
+ case DNS_KEYALG_ECDSA384:
+ case DNS_KEYALG_ED25519:
+ case DNS_KEYALG_ED448:
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: key algorithm %s "
+ "has predefined length; ignoring "
+ "length value %u",
+ alg.base, size);
+ default:
+ break;
+ }
+
+ key->length = size;
+ }
+ }
+
+ dns_kasp_addkey(kasp, key);
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ dns_kasp_key_destroy(key);
+ return (result);
+}
+
+static isc_result_t
+cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
+ isc_log_t *logctx) {
+ dns_kasp_key_t *kkey;
+ unsigned int min_keysize = 4096;
+ const cfg_obj_t *obj = NULL;
+ uint32_t iter = DEFAULT_NSEC3PARAM_ITER;
+ uint32_t saltlen = DEFAULT_NSEC3PARAM_SALTLEN;
+ uint32_t badalg = 0;
+ bool optout = false;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ /* How many iterations. */
+ obj = cfg_tuple_get(config, "iterations");
+ if (cfg_obj_isuint32(obj)) {
+ iter = cfg_obj_asuint32(obj);
+ }
+ dns_kasp_freeze(kasp);
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ unsigned int keysize = dns_kasp_key_size(kkey);
+ uint32_t keyalg = dns_kasp_key_algorithm(kkey);
+
+ if (keysize < min_keysize) {
+ min_keysize = keysize;
+ }
+
+ /* NSEC3 cannot be used with certain key algorithms. */
+ if (keyalg == DNS_KEYALG_RSAMD5 || keyalg == DNS_KEYALG_DH ||
+ keyalg == DNS_KEYALG_DSA || keyalg == DNS_KEYALG_RSASHA1)
+ {
+ badalg = keyalg;
+ }
+ }
+ dns_kasp_thaw(kasp);
+
+ if (badalg > 0) {
+ char algstr[DNS_SECALG_FORMATSIZE];
+ dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr));
+ cfg_obj_log(
+ obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: cannot use nsec3 with algorithm '%s'",
+ algstr);
+ return (DNS_R_NSEC3BADALG);
+ }
+
+ if (iter > dns_nsec3_maxiterations()) {
+ ret = DNS_R_NSEC3ITERRANGE;
+ }
+
+ if (ret == DNS_R_NSEC3ITERRANGE) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: nsec3 iterations value %u "
+ "out of range",
+ iter);
+ return (ret);
+ }
+
+ /* Opt-out? */
+ obj = cfg_tuple_get(config, "optout");
+ if (cfg_obj_isboolean(obj)) {
+ optout = cfg_obj_asboolean(obj);
+ }
+
+ /* Salt */
+ obj = cfg_tuple_get(config, "salt-length");
+ if (cfg_obj_isuint32(obj)) {
+ saltlen = cfg_obj_asuint32(obj);
+ }
+ if (saltlen > 0xff) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: nsec3 salt length %u too high",
+ saltlen);
+ return (DNS_R_NSEC3SALTRANGE);
+ }
+
+ dns_kasp_setnsec3param(kasp, iter, optout, saltlen);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
+ isc_mem_t *mctx, isc_log_t *logctx,
+ dns_kasplist_t *kasplist, dns_kasp_t **kaspp) {
+ isc_result_t result;
+ const cfg_obj_t *maps[2];
+ const cfg_obj_t *koptions = NULL;
+ const cfg_obj_t *keys = NULL;
+ const cfg_obj_t *nsec3 = NULL;
+ const cfg_listelt_t *element = NULL;
+ const char *kaspname = NULL;
+ dns_kasp_t *kasp = NULL;
+ size_t i = 0;
+ uint32_t sigrefresh = 0, sigvalidity = 0;
+ uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0;
+ uint32_t publishsafety = 0, retiresafety = 0;
+ uint32_t zonepropdelay = 0, parentpropdelay = 0;
+ uint32_t ipub = 0, iret = 0;
+ uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0;
+
+ REQUIRE(config != NULL);
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ kaspname = cfg_obj_asstring(cfg_tuple_get(config, "name"));
+ INSIST(kaspname != NULL);
+
+ cfg_obj_log(config, logctx, ISC_LOG_DEBUG(1),
+ "dnssec-policy: load policy '%s'", kaspname);
+
+ result = dns_kasplist_find(kasplist, kaspname, &kasp);
+
+ if (result == ISC_R_SUCCESS) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: duplicately named policy found '%s'",
+ kaspname);
+ dns_kasp_detach(&kasp);
+ return (ISC_R_EXISTS);
+ }
+ if (result != ISC_R_NOTFOUND) {
+ return (result);
+ }
+
+ /* No kasp with configured name was found in list, create new one. */
+ INSIST(kasp == NULL);
+ result = dns_kasp_create(mctx, kaspname, &kasp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ INSIST(kasp != NULL);
+
+ /* Now configure. */
+ INSIST(DNS_KASP_VALID(kasp));
+
+ if (config != NULL) {
+ koptions = cfg_tuple_get(config, "options");
+ maps[i++] = koptions;
+ }
+ maps[i] = NULL;
+
+ /* Configuration: Signatures */
+ sigrefresh = get_duration(maps, "signatures-refresh",
+ DNS_KASP_SIG_REFRESH);
+ dns_kasp_setsigrefresh(kasp, sigrefresh);
+
+ sigvalidity = get_duration(maps, "signatures-validity-dnskey",
+ DNS_KASP_SIG_VALIDITY_DNSKEY);
+ if (sigrefresh >= (sigvalidity * 0.9)) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: policy '%s' signatures-refresh must be "
+ "at most 90%% of the signatures-validity-dnskey",
+ kaspname);
+ result = ISC_R_FAILURE;
+ }
+ dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity);
+
+ sigvalidity = get_duration(maps, "signatures-validity",
+ DNS_KASP_SIG_VALIDITY);
+ if (sigrefresh >= (sigvalidity * 0.9)) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: policy '%s' signatures-refresh must be "
+ "at most 90%% of the signatures-validity",
+ kaspname);
+ result = ISC_R_FAILURE;
+ }
+ dns_kasp_setsigvalidity(kasp, sigvalidity);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Configuration: Zone settings */
+ maxttl = get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL);
+ dns_kasp_setzonemaxttl(kasp, maxttl);
+
+ zonepropdelay = get_duration(maps, "zone-propagation-delay",
+ DNS_KASP_ZONE_PROPDELAY);
+ dns_kasp_setzonepropagationdelay(kasp, zonepropdelay);
+
+ /* Configuration: Parent settings */
+ dsttl = get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL);
+ dns_kasp_setdsttl(kasp, dsttl);
+
+ parentpropdelay = get_duration(maps, "parent-propagation-delay",
+ DNS_KASP_PARENT_PROPDELAY);
+ dns_kasp_setparentpropagationdelay(kasp, parentpropdelay);
+
+ /* Configuration: Keys */
+ dnskeyttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL);
+ dns_kasp_setdnskeyttl(kasp, dnskeyttl);
+
+ publishsafety = get_duration(maps, "publish-safety",
+ DNS_KASP_PUBLISH_SAFETY);
+ dns_kasp_setpublishsafety(kasp, publishsafety);
+
+ retiresafety = get_duration(maps, "retire-safety",
+ DNS_KASP_RETIRE_SAFETY);
+ dns_kasp_setretiresafety(kasp, retiresafety);
+
+ dns_kasp_setpurgekeys(
+ kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS));
+
+ ipub = dnskeyttl + publishsafety + zonepropdelay;
+ iret = dsttl + retiresafety + parentpropdelay;
+ ksk_min_lifetime = ISC_MAX(ipub, iret);
+
+ iret = (sigvalidity - sigrefresh) + maxttl + retiresafety +
+ zonepropdelay;
+ zsk_min_lifetime = ISC_MAX(ipub, iret);
+
+ (void)confget(maps, "keys", &keys);
+ if (keys != NULL) {
+ char role[256] = { 0 };
+ bool warn[256][2] = { { false } };
+ dns_kasp_key_t *kkey = NULL;
+
+ for (element = cfg_list_first(keys); element != NULL;
+ element = cfg_list_next(element))
+ {
+ cfg_obj_t *kobj = cfg_listelt_value(element);
+ result = cfg_kaspkey_fromconfig(kobj, kasp, logctx,
+ ksk_min_lifetime,
+ zsk_min_lifetime);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ dns_kasp_freeze(kasp);
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ uint32_t keyalg = dns_kasp_key_algorithm(kkey);
+ INSIST(keyalg < ARRAY_SIZE(role));
+
+ if (dns_kasp_key_zsk(kkey)) {
+ if ((role[keyalg] & DNS_KASP_KEY_ROLE_ZSK) != 0)
+ {
+ warn[keyalg][0] = true;
+ }
+ role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+
+ if (dns_kasp_key_ksk(kkey)) {
+ if ((role[keyalg] & DNS_KASP_KEY_ROLE_KSK) != 0)
+ {
+ warn[keyalg][1] = true;
+ }
+ role[keyalg] |= DNS_KASP_KEY_ROLE_KSK;
+ }
+ }
+ dns_kasp_thaw(kasp);
+ for (i = 0; i < ARRAY_SIZE(role); i++) {
+ if (role[i] == 0) {
+ continue;
+ }
+ if (role[i] !=
+ (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK))
+ {
+ cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: algorithm %zu "
+ "requires both KSK and ZSK roles",
+ i);
+ result = ISC_R_FAILURE;
+ }
+ if (warn[i][0]) {
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: algorithm %zu has "
+ "multiple keys with ZSK role",
+ i);
+ }
+ if (warn[i][1]) {
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: algorithm %zu has "
+ "multiple keys with KSK role",
+ i);
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else if (default_kasp) {
+ dns_kasp_key_t *key, *new_key;
+ /*
+ * If there are no specific keys configured in the policy,
+ * inherit from the default policy (except for the built-in
+ * "insecure" policy).
+ */
+ for (key = ISC_LIST_HEAD(dns_kasp_keys(default_kasp));
+ key != NULL; key = ISC_LIST_NEXT(key, link))
+ {
+ /* Create a new key reference. */
+ new_key = NULL;
+ result = dns_kasp_key_create(kasp, &new_key);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (dns_kasp_key_ksk(key)) {
+ new_key->role |= DNS_KASP_KEY_ROLE_KSK;
+ }
+ if (dns_kasp_key_zsk(key)) {
+ new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+ new_key->lifetime = dns_kasp_key_lifetime(key);
+ new_key->algorithm = dns_kasp_key_algorithm(key);
+ new_key->length = dns_kasp_key_size(key);
+ dns_kasp_addkey(kasp, new_key);
+ }
+ }
+
+ if (strcmp(kaspname, "insecure") == 0) {
+ /* "dnssec-policy insecure": key list must be empty */
+ INSIST(dns_kasp_keylist_empty(kasp));
+ } else if (default_kasp != NULL) {
+ /* There must be keys configured. */
+ INSIST(!(dns_kasp_keylist_empty(kasp)));
+ }
+
+ /* Configuration: NSEC3 */
+ (void)confget(maps, "nsec3param", &nsec3);
+ if (nsec3 == NULL) {
+ if (default_kasp != NULL && dns_kasp_nsec3(default_kasp)) {
+ dns_kasp_setnsec3param(
+ kasp, dns_kasp_nsec3iter(default_kasp),
+ (dns_kasp_nsec3flags(default_kasp) == 0x01),
+ dns_kasp_nsec3saltlen(default_kasp));
+ } else {
+ dns_kasp_setnsec3(kasp, false);
+ }
+ } else {
+ dns_kasp_setnsec3(kasp, true);
+ result = cfg_nsec3param_fromconfig(nsec3, kasp, logctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /* Append it to the list for future lookups. */
+ ISC_LIST_APPEND(*kasplist, kasp, link);
+ INSIST(!(ISC_LIST_EMPTY(*kasplist)));
+
+ /* Success: Attach the kasp to the pointer and return. */
+ dns_kasp_attach(kasp, kaspp);
+
+ /* Don't detach as kasp is on '*kasplist' */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ /* Something bad happened, detach (destroys kasp) and return error. */
+ dns_kasp_detach(&kasp);
+ return (result);
+}
diff --git a/lib/isccfg/log.c b/lib/isccfg/log.c
new file mode 100644
index 0000000..22979ef
--- /dev/null
+++ b/lib/isccfg/log.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+/*! \file */
+
+#include <isc/util.h>
+
+#include <isccfg/log.h>
+
+/*%
+ * When adding a new category, be sure to add the appropriate
+ * \#define to <isccfg/log.h>.
+ */
+isc_logcategory_t cfg_categories[] = { { "config", 0 }, { NULL, 0 } };
+
+/*%
+ * When adding a new module, be sure to add the appropriate
+ * \#define to <isccfg/log.h>.
+ */
+isc_logmodule_t cfg_modules[] = { { "isccfg/parser", 0 }, { NULL, 0 } };
+
+void
+cfg_log_init(isc_log_t *lctx) {
+ REQUIRE(lctx != NULL);
+
+ isc_log_registercategories(lctx, cfg_categories);
+ isc_log_registermodules(lctx, cfg_modules);
+}
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
new file mode 100644
index 0000000..4e4c098
--- /dev/null
+++ b/lib/isccfg/namedconf.c
@@ -0,0 +1,3998 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/log.h>
+#include <isccfg/namedconf.h>
+
+#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 isc_result_t
+parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static isc_result_t
+parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+static isc_result_t
+parse_updatepolicy(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+static void
+print_updatepolicy(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_updatepolicy(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static void
+print_keyvalue(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static void
+doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static isc_result_t
+cfg_parse_kv_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+cfg_print_kv_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+cfg_doc_kv_tuple(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static cfg_type_t cfg_type_acl;
+static cfg_type_t cfg_type_bracketed_dscpsockaddrlist;
+static cfg_type_t cfg_type_bracketed_namesockaddrkeylist;
+static cfg_type_t cfg_type_bracketed_netaddrlist;
+static cfg_type_t cfg_type_bracketed_sockaddrnameportlist;
+static cfg_type_t cfg_type_bracketed_http_endpoint_list;
+static cfg_type_t cfg_type_controls;
+static cfg_type_t cfg_type_controls_sockaddr;
+static cfg_type_t cfg_type_destinationlist;
+static cfg_type_t cfg_type_dialuptype;
+static cfg_type_t cfg_type_dlz;
+static cfg_type_t cfg_type_dnssecpolicy;
+static cfg_type_t cfg_type_dnstap;
+static cfg_type_t cfg_type_dnstapoutput;
+static cfg_type_t cfg_type_dyndb;
+static cfg_type_t cfg_type_http_description;
+static cfg_type_t cfg_type_ixfrdifftype;
+static cfg_type_t cfg_type_ixfrratio;
+static cfg_type_t cfg_type_key;
+static cfg_type_t cfg_type_logfile;
+static cfg_type_t cfg_type_logging;
+static cfg_type_t cfg_type_logseverity;
+static cfg_type_t cfg_type_logsuffix;
+static cfg_type_t cfg_type_logversions;
+static cfg_type_t cfg_type_remoteselement;
+static cfg_type_t cfg_type_maxduration;
+static cfg_type_t cfg_type_minimal;
+static cfg_type_t cfg_type_nameportiplist;
+static cfg_type_t cfg_type_notifytype;
+static cfg_type_t cfg_type_optional_allow;
+static cfg_type_t cfg_type_optional_class;
+static cfg_type_t cfg_type_optional_dscp;
+static cfg_type_t cfg_type_optional_facility;
+static cfg_type_t cfg_type_optional_keyref;
+static cfg_type_t cfg_type_optional_port;
+static cfg_type_t cfg_type_optional_uint32;
+static cfg_type_t cfg_type_optional_tls;
+static cfg_type_t cfg_type_options;
+static cfg_type_t cfg_type_plugin;
+static cfg_type_t cfg_type_portiplist;
+static cfg_type_t cfg_type_printtime;
+static cfg_type_t cfg_type_qminmethod;
+static cfg_type_t cfg_type_querysource4;
+static cfg_type_t cfg_type_querysource6;
+static cfg_type_t cfg_type_querysource;
+static cfg_type_t cfg_type_server;
+static cfg_type_t cfg_type_server_key_kludge;
+static cfg_type_t cfg_type_size;
+static cfg_type_t cfg_type_sizenodefault;
+static cfg_type_t cfg_type_sizeorpercent;
+static cfg_type_t cfg_type_sizeval;
+static cfg_type_t cfg_type_sockaddr4wild;
+static cfg_type_t cfg_type_sockaddr6wild;
+static cfg_type_t cfg_type_statschannels;
+static cfg_type_t cfg_type_tlsconf;
+static cfg_type_t cfg_type_view;
+static cfg_type_t cfg_type_viewopts;
+static cfg_type_t cfg_type_zone;
+
+/*% tkey-dhkey */
+
+static cfg_tuplefielddef_t tkey_dhkey_fields[] = {
+ { "name", &cfg_type_qstring, 0 },
+ { "keyid", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_tkey_dhkey = { "tkey-dhkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, tkey_dhkey_fields };
+
+/*% listen-on */
+
+static cfg_tuplefielddef_t listenon_tuple_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_OBSOLETE | CFG_CLAUSEFLAG_NODOC },
+ { "tls", &cfg_type_astring, 0 },
+#if HAVE_LIBNGHTTP2
+ { "http", &cfg_type_astring, 0 },
+#else
+ { "http", &cfg_type_astring, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_listen_tuple = {
+ "listenon tuple", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, listenon_tuple_fields
+};
+
+static cfg_tuplefielddef_t listenon_fields[] = {
+ { "tuple", &cfg_type_listen_tuple, 0 },
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_listenon = { "listenon", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, listenon_fields };
+
+/*% acl */
+
+/*
+ * Encrypted transfer related definitions
+ */
+
+static cfg_tuplefielddef_t cfg_transport_acl_tuple_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "transport", &cfg_type_astring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_transport_acl_tuple = {
+ "transport-acl tuple", cfg_parse_kv_tuple,
+ cfg_print_kv_tuple, cfg_doc_kv_tuple,
+ &cfg_rep_tuple, cfg_transport_acl_tuple_fields
+};
+
+static cfg_tuplefielddef_t cfg_transport_acl_fields[] = {
+ { "port-transport", &cfg_transport_acl_tuple, 0 },
+ { "aml", &cfg_type_bracketed_aml, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_transport_acl = {
+ "transport-acl", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, cfg_transport_acl_fields
+};
+
+/*
+ * NOTE: To enable syntax which allows specifying port and protocol,
+ * replace 'cfg_type_bracketed_aml' with
+ * 'cfg_type_transport_acl'.
+ *
+ * Example: acl port 853 protocol tls { ... };
+ */
+static cfg_tuplefielddef_t acl_fields[] = { { "name", &cfg_type_astring, 0 },
+ { "value", &cfg_type_bracketed_aml,
+ 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_type_t cfg_type_acl = { "acl", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, acl_fields };
+
+/*% remote servers, used for primaries and parental agents */
+static cfg_tuplefielddef_t remotes_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_remoteservers = { "remote-servers", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, remotes_fields };
+
+/*%
+ * "sockaddrkeylist", a list of socket addresses with optional keys
+ * and an optional default port, as used in the remote-servers option.
+ * E.g.,
+ * "port 1234 { myservers; 10.0.0.1 key foo; 1::2 port 69; }"
+ */
+
+static cfg_tuplefielddef_t namesockaddrkey_fields[] = {
+ { "remoteselement", &cfg_type_remoteselement, 0 },
+ { "key", &cfg_type_optional_keyref, 0 },
+ { "tls", &cfg_type_optional_tls, 0 },
+ { NULL, NULL, 0 },
+};
+
+static cfg_type_t cfg_type_namesockaddrkey = {
+ "namesockaddrkey", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, namesockaddrkey_fields
+};
+
+static cfg_type_t cfg_type_bracketed_namesockaddrkeylist = {
+ "bracketed_namesockaddrkeylist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_namesockaddrkey
+};
+
+static cfg_tuplefielddef_t namesockaddrkeylist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_namesockaddrkeylist = {
+ "sockaddrkeylist", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, namesockaddrkeylist_fields
+};
+
+/*%
+ * A list of socket addresses with an optional default port, as used
+ * in the 'listen-on' option. E.g., "{ 10.0.0.1; 1::2 port 69; }"
+ */
+static cfg_tuplefielddef_t portiplist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_dscpsockaddrlist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_portiplist = { "portiplist", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, portiplist_fields };
+
+/*%
+ * A list of RR types, used in grant statements.
+ * Note that the old parser allows quotes around the RR type names.
+ */
+static cfg_type_t cfg_type_rrtypelist = {
+ "rrtypelist", cfg_parse_spacelist, cfg_print_spacelist,
+ cfg_doc_terminal, &cfg_rep_list, &cfg_type_astring
+};
+
+static const char *mode_enums[] = { "deny", "grant", NULL };
+static cfg_type_t cfg_type_mode = {
+ "mode", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &mode_enums
+};
+
+static isc_result_t
+parse_matchtype(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "zonesub") == 0)
+ {
+ pctx->flags |= CFG_PCTX_SKIP;
+ }
+ return (cfg_parse_enum(pctx, type, ret));
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_matchname(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ if ((pctx->flags & CFG_PCTX_SKIP) != 0) {
+ pctx->flags &= ~CFG_PCTX_SKIP;
+ CHECK(cfg_parse_void(pctx, NULL, &obj));
+ } else {
+ result = cfg_parse_astring(pctx, type, &obj);
+ }
+
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+static void
+doc_matchname(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_print_cstr(pctx, "[ ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static const char *matchtype_enums[] = { "6to4-self",
+ "external",
+ "krb5-self",
+ "krb5-selfsub",
+ "krb5-subdomain",
+ "krb5-subdomain-self-rhs",
+ "ms-self",
+ "ms-selfsub",
+ "ms-subdomain",
+ "ms-subdomain-self-rhs",
+ "name",
+ "self",
+ "selfsub",
+ "selfwild",
+ "subdomain",
+ "tcp-self",
+ "wildcard",
+ "zonesub",
+ NULL };
+
+static cfg_type_t cfg_type_matchtype = { "matchtype", parse_matchtype,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &matchtype_enums };
+
+static cfg_type_t cfg_type_matchname = {
+ "optional_matchname", parse_matchname, cfg_print_ustring,
+ doc_matchname, &cfg_rep_tuple, &cfg_type_ustring
+};
+
+/*%
+ * A grant statement, used in the update policy.
+ */
+static cfg_tuplefielddef_t grant_fields[] = {
+ { "mode", &cfg_type_mode, 0 },
+ { "identity", &cfg_type_astring, 0 }, /* domain name */
+ { "matchtype", &cfg_type_matchtype, 0 },
+ { "name", &cfg_type_matchname, 0 }, /* domain name */
+ { "types", &cfg_type_rrtypelist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_grant = { "grant", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, grant_fields };
+
+static cfg_type_t cfg_type_updatepolicy = {
+ "update_policy", parse_updatepolicy, print_updatepolicy,
+ doc_updatepolicy, &cfg_rep_list, &cfg_type_grant
+};
+
+static isc_result_t
+parse_updatepolicy(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '{')
+ {
+ cfg_ungettoken(pctx);
+ return (cfg_parse_bracketed_list(pctx, type, ret));
+ }
+
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "local") == 0)
+ {
+ cfg_obj_t *obj = NULL;
+ CHECK(cfg_create_obj(pctx, &cfg_type_ustring, &obj));
+ obj->value.string.length = strlen("local");
+ obj->value.string.base =
+ isc_mem_get(pctx->mctx, obj->value.string.length + 1);
+ memmove(obj->value.string.base, "local", 5);
+ obj->value.string.base[5] = '\0';
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_ungettoken(pctx);
+ return (ISC_R_UNEXPECTEDTOKEN);
+
+cleanup:
+ return (result);
+}
+
+static void
+print_updatepolicy(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (cfg_obj_isstring(obj)) {
+ cfg_print_ustring(pctx, obj);
+ } else {
+ cfg_print_bracketed_list(pctx, obj);
+ }
+}
+
+static void
+doc_updatepolicy(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_print_cstr(pctx, "( local | { ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, "; ... } )");
+}
+
+/*%
+ * A view statement.
+ */
+static cfg_tuplefielddef_t view_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_viewopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_view = { "view", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, view_fields };
+
+/*%
+ * A zone statement.
+ */
+static cfg_tuplefielddef_t zone_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_zoneopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_zone = { "zone", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, zone_fields };
+
+/*%
+ * A dnssec-policy statement.
+ */
+static cfg_tuplefielddef_t dnssecpolicy_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "options", &cfg_type_dnssecpolicyopts, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnssecpolicy = {
+ "dnssec-policy", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, dnssecpolicy_fields
+};
+
+/*%
+ * A "category" clause in the "logging" statement.
+ */
+static cfg_tuplefielddef_t category_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "destinations", &cfg_type_destinationlist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_category = { "category", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, category_fields };
+
+static isc_result_t
+parse_maxduration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret));
+}
+
+static void
+doc_maxduration(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_duration);
+}
+
+/*%
+ * A duration or "unlimited", but not "default".
+ */
+static const char *maxduration_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_maxduration = {
+ "maxduration_no_default", parse_maxduration, cfg_print_ustring,
+ doc_maxduration, &cfg_rep_duration, maxduration_enums
+};
+
+/*%
+ * A dnssec key, as used in the "trusted-keys" statement.
+ */
+static cfg_tuplefielddef_t dnsseckey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "anchortype", &cfg_type_void, 0 },
+ { "rdata1", &cfg_type_uint32, 0 },
+ { "rdata2", &cfg_type_uint32, 0 },
+ { "rdata3", &cfg_type_uint32, 0 },
+ { "data", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_dnsseckey = { "dnsseckey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dnsseckey_fields };
+
+/*%
+ * Optional enums.
+ *
+ */
+static isc_result_t
+parse_optional_enum(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_void, ret));
+}
+
+static void
+doc_optional_enum(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ ");
+ cfg_doc_enum(pctx, type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+/*%
+ * A key initialization specifier, as used in the
+ * "trust-anchors" (or synonymous "managed-keys") statement.
+ */
+static const char *anchortype_enums[] = { "static-key", "initial-key",
+ "static-ds", "initial-ds", NULL };
+static cfg_type_t cfg_type_anchortype = { "anchortype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, anchortype_enums };
+static cfg_tuplefielddef_t managedkey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "anchortype", &cfg_type_anchortype, 0 },
+ { "rdata1", &cfg_type_uint32, 0 },
+ { "rdata2", &cfg_type_uint32, 0 },
+ { "rdata3", &cfg_type_uint32, 0 },
+ { "data", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_managedkey = { "managedkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, managedkey_fields };
+
+/*%
+ * DNSSEC key roles.
+ */
+static const char *dnsseckeyrole_enums[] = { "csk", "ksk", "zsk", NULL };
+static cfg_type_t cfg_type_dnsseckeyrole = {
+ "dnssec-key-role", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &dnsseckeyrole_enums
+};
+
+/*%
+ * DNSSEC key storage types.
+ */
+static const char *dnsseckeystore_enums[] = { "key-directory", NULL };
+static cfg_type_t cfg_type_dnsseckeystore = {
+ "dnssec-key-storage", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, dnsseckeystore_enums
+};
+
+/*%
+ * A dnssec key, as used in the "keys" statement in a "dnssec-policy".
+ */
+static keyword_type_t algorithm_kw = { "algorithm", &cfg_type_ustring };
+static cfg_type_t cfg_type_algorithm = { "algorithm", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &algorithm_kw };
+
+static keyword_type_t lifetime_kw = { "lifetime",
+ &cfg_type_duration_or_unlimited };
+static cfg_type_t cfg_type_lifetime = { "lifetime", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_duration, &lifetime_kw };
+
+static cfg_tuplefielddef_t kaspkey_fields[] = {
+ { "role", &cfg_type_dnsseckeyrole, 0 },
+ { "keystore-type", &cfg_type_dnsseckeystore, 0 },
+ { "lifetime", &cfg_type_lifetime, 0 },
+ { "algorithm", &cfg_type_algorithm, 0 },
+ { "length", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_kaspkey = { "kaspkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, kaspkey_fields };
+
+/*%
+ * NSEC3 parameters.
+ */
+static keyword_type_t nsec3iter_kw = { "iterations", &cfg_type_uint32 };
+static cfg_type_t cfg_type_nsec3iter = {
+ "iterations", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &nsec3iter_kw
+};
+
+static keyword_type_t nsec3optout_kw = { "optout", &cfg_type_boolean };
+static cfg_type_t cfg_type_nsec3optout = {
+ "optout", parse_optional_keyvalue,
+ print_keyvalue, doc_optional_keyvalue,
+ &cfg_rep_boolean, &nsec3optout_kw
+};
+
+static keyword_type_t nsec3salt_kw = { "salt-length", &cfg_type_uint32 };
+static cfg_type_t cfg_type_nsec3salt = {
+ "salt-length", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &nsec3salt_kw
+};
+
+static cfg_tuplefielddef_t nsec3param_fields[] = {
+ { "iterations", &cfg_type_nsec3iter, 0 },
+ { "optout", &cfg_type_nsec3optout, 0 },
+ { "salt-length", &cfg_type_nsec3salt, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nsec3 = { "nsec3param", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, nsec3param_fields };
+
+/*%
+ * Wild class, type, name.
+ */
+static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring };
+
+static cfg_type_t cfg_type_optional_wild_class = {
+ "optional_wild_class", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_class_kw
+};
+
+static keyword_type_t wild_type_kw = { "type", &cfg_type_ustring };
+
+static cfg_type_t cfg_type_optional_wild_type = {
+ "optional_wild_type", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_type_kw
+};
+
+static keyword_type_t wild_name_kw = { "name", &cfg_type_qstring };
+
+static cfg_type_t cfg_type_optional_wild_name = {
+ "optional_wild_name", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_name_kw
+};
+
+/*%
+ * An rrset ordering element.
+ */
+static cfg_tuplefielddef_t rrsetorderingelement_fields[] = {
+ { "class", &cfg_type_optional_wild_class, 0 },
+ { "type", &cfg_type_optional_wild_type, 0 },
+ { "name", &cfg_type_optional_wild_name, 0 },
+ { "order", &cfg_type_ustring, 0 }, /* must be literal "order" */
+ { "ordering", &cfg_type_ustring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rrsetorderingelement = {
+ "rrsetorderingelement", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, rrsetorderingelement_fields
+};
+
+/*%
+ * A global or view "check-names" option. Note that the zone
+ * "check-names" option has a different syntax.
+ */
+
+static const char *checktype_enums[] = { "primary", "master", "secondary",
+ "slave", "response", NULL };
+static cfg_type_t cfg_type_checktype = { "checktype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &checktype_enums };
+
+static const char *checkmode_enums[] = { "fail", "warn", "ignore", NULL };
+static cfg_type_t cfg_type_checkmode = { "checkmode", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &checkmode_enums };
+
+static const char *warn_enums[] = { "warn", "ignore", NULL };
+static cfg_type_t cfg_type_warn = {
+ "warn", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &warn_enums
+};
+
+static cfg_tuplefielddef_t checknames_fields[] = {
+ { "type", &cfg_type_checktype, 0 },
+ { "mode", &cfg_type_checkmode, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_checknames = { "checknames", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, checknames_fields };
+
+static cfg_type_t cfg_type_bracketed_dscpsockaddrlist = {
+ "bracketed_sockaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_sockaddrdscp
+};
+
+static cfg_type_t cfg_type_bracketed_netaddrlist = { "bracketed_netaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_netaddr };
+
+static const char *autodnssec_enums[] = { "allow", "maintain", "off", NULL };
+static cfg_type_t cfg_type_autodnssec = {
+ "autodnssec", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &autodnssec_enums
+};
+
+static const char *dnssecupdatemode_enums[] = { "maintain", "no-resign", NULL };
+static cfg_type_t cfg_type_dnssecupdatemode = {
+ "dnssecupdatemode", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &dnssecupdatemode_enums
+};
+
+static const char *updatemethods_enums[] = { "date", "increment", "unixtime",
+ NULL };
+static cfg_type_t cfg_type_updatemethod = {
+ "updatemethod", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &updatemethods_enums
+};
+
+/*
+ * zone-statistics: full, terse, or none.
+ *
+ * for backward compatibility, we also support boolean values.
+ * yes represents "full", no represents "terse". in the future we
+ * may change no to mean "none".
+ */
+static const char *zonestat_enums[] = { "full", "terse", "none", NULL };
+static isc_result_t
+parse_zonestat(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_zonestat(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_zonestat = { "zonestat", parse_zonestat,
+ cfg_print_ustring, doc_zonestat,
+ &cfg_rep_string, zonestat_enums };
+
+static cfg_type_t cfg_type_rrsetorder = { "rrsetorder",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_rrsetorderingelement };
+
+static keyword_type_t dscp_kw = { "dscp", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_optional_dscp = {
+ "optional_dscp", parse_optional_keyvalue, print_keyvalue,
+ cfg_doc_void, &cfg_rep_uint32, &dscp_kw
+};
+
+static keyword_type_t port_kw = { "port", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_optional_port = {
+ "optional_port", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &port_kw
+};
+
+/*% A list of keys, as in the "key" clause of the controls statement. */
+static cfg_type_t cfg_type_keylist = { "keylist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+/*% A list of dnssec keys, as in "trusted-keys". Deprecated. */
+static cfg_type_t cfg_type_trustedkeys = { "trustedkeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_dnsseckey };
+
+/*%
+ * A list of managed trust anchors. Each entry contains a name, a keyword
+ * ("static-key", initial-key", "static-ds" or "initial-ds"), and the
+ * fields associated with either a DNSKEY or a DS record.
+ */
+static cfg_type_t cfg_type_dnsseckeys = { "dnsseckeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_managedkey };
+
+/*%
+ * A list of key entries, used in a DNSSEC Key and Signing Policy.
+ */
+static cfg_type_t cfg_type_kaspkeys = { "kaspkeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_kaspkey };
+
+static const char *forwardtype_enums[] = { "first", "only", NULL };
+static cfg_type_t cfg_type_forwardtype = {
+ "forwardtype", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &forwardtype_enums
+};
+
+static const char *zonetype_enums[] = {
+ "primary", "master", "secondary", "slave",
+ "mirror", "delegation-only", "forward", "hint",
+ "redirect", "static-stub", "stub", NULL
+};
+static cfg_type_t cfg_type_zonetype = { "zonetype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &zonetype_enums };
+
+static const char *loglevel_enums[] = { "critical", "error", "warning",
+ "notice", "info", "dynamic",
+ NULL };
+static cfg_type_t cfg_type_loglevel = { "loglevel", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &loglevel_enums };
+
+static const char *transferformat_enums[] = { "many-answers", "one-answer",
+ NULL };
+static cfg_type_t cfg_type_transferformat = {
+ "transferformat", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &transferformat_enums
+};
+
+/*%
+ * The special keyword "none", as used in the pid-file option.
+ */
+
+static void
+print_none(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "none");
+}
+
+static cfg_type_t cfg_type_none = { "none", NULL, print_none,
+ NULL, &cfg_rep_void, NULL };
+
+/*%
+ * A quoted string or the special keyword "none". Used in the pid-file option.
+ */
+static isc_result_t
+parse_qstringornone(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "none") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_none, ret));
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_qstring(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+doc_qstringornone(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( <quoted_string> | none )");
+}
+
+static cfg_type_t cfg_type_qstringornone = { "qstringornone",
+ parse_qstringornone,
+ NULL,
+ doc_qstringornone,
+ NULL,
+ NULL };
+
+/*%
+ * A boolean ("yes" or "no"), or the special keyword "auto".
+ * Used in the dnssec-validation option.
+ */
+static void
+print_auto(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "auto");
+}
+
+static cfg_type_t cfg_type_auto = { "auto", NULL, print_auto,
+ NULL, &cfg_rep_void, NULL };
+
+static isc_result_t
+parse_boolorauto(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "auto") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_auto, ret));
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_boolean(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+print_boolorauto(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->type->rep == &cfg_rep_void) {
+ cfg_print_cstr(pctx, "auto");
+ } else if (obj->value.boolean) {
+ cfg_print_cstr(pctx, "yes");
+ } else {
+ cfg_print_cstr(pctx, "no");
+ }
+}
+
+static void
+doc_boolorauto(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( yes | no | auto )");
+}
+
+static cfg_type_t cfg_type_boolorauto = {
+ "boolorauto", parse_boolorauto, print_boolorauto, doc_boolorauto, NULL,
+ NULL
+};
+
+/*%
+ * keyword hostname
+ */
+static void
+print_hostname(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "hostname");
+}
+
+static cfg_type_t cfg_type_hostname = { "hostname", NULL,
+ print_hostname, NULL,
+ &cfg_rep_boolean, NULL };
+
+/*%
+ * "server-id" argument.
+ */
+
+static isc_result_t
+parse_serverid(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "none") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_none, ret));
+ }
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "hostname") == 0)
+ {
+ result = cfg_create_obj(pctx, &cfg_type_hostname, ret);
+ if (result == ISC_R_SUCCESS) {
+ (*ret)->value.boolean = true;
+ }
+ return (result);
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_qstring(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+doc_serverid(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( <quoted_string> | none | hostname )");
+}
+
+static cfg_type_t cfg_type_serverid = { "serverid", parse_serverid, NULL,
+ doc_serverid, NULL, NULL };
+
+/*%
+ * Port list.
+ */
+static void
+print_porttuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "range ");
+ cfg_print_tuple(pctx, obj);
+}
+static cfg_tuplefielddef_t porttuple_fields[] = {
+ { "loport", &cfg_type_uint32, 0 },
+ { "hiport", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_porttuple = { "porttuple", cfg_parse_tuple,
+ print_porttuple, cfg_doc_tuple,
+ &cfg_rep_tuple, porttuple_fields };
+
+static isc_result_t
+parse_port(cfg_parser_t *pctx, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_parse_uint32(pctx, NULL, ret));
+ if ((*ret)->value.uint32 > 0xffff) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid port");
+ cfg_obj_destroy(pctx, ret);
+ result = ISC_R_RANGE;
+ }
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_portrange(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(parse_port(pctx, ret));
+ } else {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string ||
+ strcasecmp(TOKEN_STRING(pctx), "range") != 0)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer or 'range'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ CHECK(cfg_create_tuple(pctx, &cfg_type_porttuple, &obj));
+ CHECK(parse_port(pctx, &obj->value.tuple[0]));
+ CHECK(parse_port(pctx, &obj->value.tuple[1]));
+ if (obj->value.tuple[0]->value.uint32 >
+ obj->value.tuple[1]->value.uint32)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "low port '%u' must not be larger "
+ "than high port",
+ obj->value.tuple[0]->value.uint32);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ *ret = obj;
+ obj = NULL;
+ }
+
+cleanup:
+ if (obj != NULL) {
+ cfg_obj_destroy(pctx, &obj);
+ }
+ return (result);
+}
+
+static cfg_type_t cfg_type_portrange = { "portrange", parse_portrange,
+ NULL, cfg_doc_terminal,
+ NULL, NULL };
+
+static cfg_type_t cfg_type_bracketed_portlist = { "bracketed_sockaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_portrange };
+
+static const char *cookiealg_enums[] = { "aes", "siphash24", NULL };
+static cfg_type_t cfg_type_cookiealg = { "cookiealg", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &cookiealg_enums };
+
+/*%
+ * fetch-quota-params
+ */
+
+static cfg_tuplefielddef_t fetchquota_fields[] = {
+ { "frequency", &cfg_type_uint32, 0 },
+ { "low", &cfg_type_fixedpoint, 0 },
+ { "high", &cfg_type_fixedpoint, 0 },
+ { "discount", &cfg_type_fixedpoint, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_fetchquota = { "fetchquota", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, fetchquota_fields };
+
+/*%
+ * fetches-per-server or fetches-per-zone
+ */
+
+static const char *response_enums[] = { "drop", "fail", NULL };
+
+static cfg_type_t cfg_type_responsetype = {
+ "responsetype", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, response_enums
+};
+
+static cfg_tuplefielddef_t fetchesper_fields[] = {
+ { "fetches", &cfg_type_uint32, 0 },
+ { "response", &cfg_type_responsetype, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_fetchesper = { "fetchesper", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, fetchesper_fields };
+
+/*%
+ * Clauses that can be found within the top level of the named.conf
+ * file only.
+ */
+static cfg_clausedef_t namedconf_clauses[] = {
+ { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI },
+ { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI },
+ { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI },
+#if HAVE_LIBNGHTTP2
+ { "http", &cfg_type_http_description, CFG_CLAUSEFLAG_MULTI },
+#else
+ { "http", &cfg_type_http_description,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { "logging", &cfg_type_logging, 0 },
+ { "lwres", NULL, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT },
+ { "masters", &cfg_type_remoteservers,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NODOC },
+ { "options", &cfg_type_options, 0 },
+ { "parental-agents", &cfg_type_remoteservers, CFG_CLAUSEFLAG_MULTI },
+ { "primaries", &cfg_type_remoteservers, CFG_CLAUSEFLAG_MULTI },
+ { "statistics-channels", &cfg_type_statschannels,
+ CFG_CLAUSEFLAG_MULTI },
+ { "tls", &cfg_type_tlsconf, CFG_CLAUSEFLAG_MULTI },
+ { "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can occur at the top level or in the view
+ * statement, but not in the options block.
+ */
+static cfg_clausedef_t namedconf_or_view_clauses[] = {
+ { "dlz", &cfg_type_dlz, CFG_CLAUSEFLAG_MULTI },
+ { "dyndb", &cfg_type_dyndb, CFG_CLAUSEFLAG_MULTI },
+ { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
+ { "managed-keys", &cfg_type_dnsseckeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "plugin", &cfg_type_plugin, CFG_CLAUSEFLAG_MULTI },
+ { "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI },
+ { "trust-anchors", &cfg_type_dnsseckeys, CFG_CLAUSEFLAG_MULTI },
+ { "trusted-keys", &cfg_type_trustedkeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "zone", &cfg_type_zone, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NODOC },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can occur in the bind.keys file.
+ */
+static cfg_clausedef_t bindkeys_clauses[] = {
+ { "managed-keys", &cfg_type_dnsseckeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "trust-anchors", &cfg_type_dnsseckeys, CFG_CLAUSEFLAG_MULTI },
+ { "trusted-keys", &cfg_type_trustedkeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { NULL, NULL, 0 }
+};
+
+static const char *fstrm_model_enums[] = { "mpsc", "spsc", NULL };
+static cfg_type_t cfg_type_fstrm_model = {
+ "model", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &fstrm_model_enums
+};
+
+/*%
+ * Clauses that can be found within the 'options' statement.
+ */
+static cfg_clausedef_t options_clauses[] = {
+ { "answer-cookie", &cfg_type_boolean, 0 },
+ { "automatic-interface-scan", &cfg_type_boolean, 0 },
+ { "avoid-v4-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "avoid-v6-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "bindkeys-file", &cfg_type_qstring, 0 },
+ { "blackhole", &cfg_type_bracketed_aml, 0 },
+ { "cookie-algorithm", &cfg_type_cookiealg, 0 },
+ { "cookie-secret", &cfg_type_sstring, CFG_CLAUSEFLAG_MULTI },
+ { "coresize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "datasize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "deallocate-on-exit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "directory", &cfg_type_qstring, CFG_CLAUSEFLAG_CALLBACK },
+#ifdef HAVE_DNSTAP
+ { "dnstap-output", &cfg_type_dnstapoutput, 0 },
+ { "dnstap-identity", &cfg_type_serverid, 0 },
+ { "dnstap-version", &cfg_type_qstringornone, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "dnstap-output", &cfg_type_dnstapoutput,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnstap-identity", &cfg_type_serverid, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnstap-version", &cfg_type_qstringornone,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef HAVE_DNSTAP */
+ { "dscp", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
+ { "dump-file", &cfg_type_qstring, 0 },
+ { "fake-iquery", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "files", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "flush-zones-on-shutdown", &cfg_type_boolean, 0 },
+#ifdef HAVE_DNSTAP
+ { "fstrm-set-buffer-hint", &cfg_type_uint32, 0 },
+ { "fstrm-set-flush-timeout", &cfg_type_uint32, 0 },
+ { "fstrm-set-input-queue-size", &cfg_type_uint32, 0 },
+ { "fstrm-set-output-notify-threshold", &cfg_type_uint32, 0 },
+ { "fstrm-set-output-queue-model", &cfg_type_fstrm_model, 0 },
+ { "fstrm-set-output-queue-size", &cfg_type_uint32, 0 },
+ { "fstrm-set-reopen-interval", &cfg_type_duration, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "fstrm-set-buffer-hint", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-flush-timeout", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-input-queue-size", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-notify-threshold", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-queue-model", &cfg_type_fstrm_model,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-queue-size", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-reopen-interval", &cfg_type_duration,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_DNSTAP */
+#if defined(HAVE_GEOIP2)
+ { "geoip-directory", &cfg_type_qstringornone, 0 },
+#else /* if defined(HAVE_GEOIP2) */
+ { "geoip-directory", &cfg_type_qstringornone,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_GEOIP2 */
+ { "geoip-use-ecs", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "has-old-clients", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "heartbeat-interval", &cfg_type_uint32, CFG_CLAUSEFLAG_DEPRECATED },
+ { "host-statistics", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "host-statistics-max", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "hostname", &cfg_type_qstringornone, 0 },
+ { "interface-interval", &cfg_type_duration, 0 },
+ { "keep-response-order", &cfg_type_bracketed_aml, 0 },
+ { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
+ { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
+ { "lock-file", &cfg_type_qstringornone, 0 },
+ { "managed-keys-directory", &cfg_type_qstring, 0 },
+ { "match-mapped-addresses", &cfg_type_boolean, 0 },
+ { "max-rsa-exponent-size", &cfg_type_uint32, 0 },
+ { "memstatistics", &cfg_type_boolean, 0 },
+ { "memstatistics-file", &cfg_type_qstring, 0 },
+ { "multiple-cnames", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "named-xfer", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "notify-rate", &cfg_type_uint32, 0 },
+ { "pid-file", &cfg_type_qstringornone, 0 },
+ { "port", &cfg_type_uint32, 0 },
+ { "tls-port", &cfg_type_uint32, 0 },
+#if HAVE_LIBNGHTTP2
+ { "http-port", &cfg_type_uint32, 0 },
+ { "http-listener-clients", &cfg_type_uint32, 0 },
+ { "http-streams-per-connection", &cfg_type_uint32, 0 },
+ { "https-port", &cfg_type_uint32, 0 },
+#else
+ { "http-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "http-listener-clients", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "http-streams-per-connection", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "https-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { "querylog", &cfg_type_boolean, 0 },
+ { "random-device", &cfg_type_qstringornone, CFG_CLAUSEFLAG_OBSOLETE },
+ { "recursing-file", &cfg_type_qstring, 0 },
+ { "recursive-clients", &cfg_type_uint32, 0 },
+ { "reuseport", &cfg_type_boolean, 0 },
+ { "reserved-sockets", &cfg_type_uint32, CFG_CLAUSEFLAG_DEPRECATED },
+ { "secroots-file", &cfg_type_qstring, 0 },
+ { "serial-queries", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "serial-query-rate", &cfg_type_uint32, 0 },
+ { "server-id", &cfg_type_serverid, 0 },
+ { "session-keyalg", &cfg_type_astring, 0 },
+ { "session-keyfile", &cfg_type_qstringornone, 0 },
+ { "session-keyname", &cfg_type_astring, 0 },
+ { "sit-secret", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "stacksize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "startup-notify-rate", &cfg_type_uint32, 0 },
+ { "statistics-file", &cfg_type_qstring, 0 },
+ { "statistics-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "tcp-advertised-timeout", &cfg_type_uint32, 0 },
+ { "tcp-clients", &cfg_type_uint32, 0 },
+ { "tcp-idle-timeout", &cfg_type_uint32, 0 },
+ { "tcp-initial-timeout", &cfg_type_uint32, 0 },
+ { "tcp-keepalive-timeout", &cfg_type_uint32, 0 },
+ { "tcp-listen-queue", &cfg_type_uint32, 0 },
+ { "tcp-receive-buffer", &cfg_type_uint32, 0 },
+ { "tcp-send-buffer", &cfg_type_uint32, 0 },
+ { "tkey-dhkey", &cfg_type_tkey_dhkey, CFG_CLAUSEFLAG_DEPRECATED },
+ { "tkey-domain", &cfg_type_qstring, 0 },
+ { "tkey-gssapi-credential", &cfg_type_qstring, 0 },
+ { "tkey-gssapi-keytab", &cfg_type_qstring, 0 },
+ { "transfer-message-size", &cfg_type_uint32, 0 },
+ { "transfers-in", &cfg_type_uint32, 0 },
+ { "transfers-out", &cfg_type_uint32, 0 },
+ { "transfers-per-ns", &cfg_type_uint32, 0 },
+ { "treat-cr-as-space", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "udp-receive-buffer", &cfg_type_uint32, 0 },
+ { "udp-send-buffer", &cfg_type_uint32, 0 },
+ { "update-quota", &cfg_type_uint32, 0 },
+ { "use-id-pool", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "use-ixfr", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "use-v4-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "use-v6-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "version", &cfg_type_qstringornone, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_namelist = { "namelist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static keyword_type_t exclude_kw = { "exclude", &cfg_type_namelist };
+
+static cfg_type_t cfg_type_optional_exclude = {
+ "optional_exclude", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &exclude_kw
+};
+
+static keyword_type_t exceptionnames_kw = { "except-from", &cfg_type_namelist };
+
+static cfg_type_t cfg_type_optional_exceptionnames = {
+ "optional_allow", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &exceptionnames_kw
+};
+
+static cfg_tuplefielddef_t denyaddresses_fields[] = {
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { "except-from", &cfg_type_optional_exceptionnames, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_denyaddresses = {
+ "denyaddresses", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, denyaddresses_fields
+};
+
+static cfg_tuplefielddef_t denyaliases_fields[] = {
+ { "name", &cfg_type_namelist, 0 },
+ { "except-from", &cfg_type_optional_exceptionnames, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_denyaliases = {
+ "denyaliases", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, denyaliases_fields
+};
+
+static cfg_type_t cfg_type_algorithmlist = { "algorithmlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_tuplefielddef_t disablealgorithm_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "algorithms", &cfg_type_algorithmlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_disablealgorithm = {
+ "disablealgorithm", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, disablealgorithm_fields
+};
+
+static cfg_type_t cfg_type_dsdigestlist = { "dsdigestlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_tuplefielddef_t disabledsdigest_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "digests", &cfg_type_dsdigestlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_disabledsdigest = {
+ "disabledsdigest", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, disabledsdigest_fields
+};
+
+static cfg_tuplefielddef_t mustbesecure_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "value", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_mustbesecure = {
+ "mustbesecure", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, mustbesecure_fields
+};
+
+static const char *masterformat_enums[] = { "raw", "text", NULL };
+static cfg_type_t cfg_type_masterformat = {
+ "masterformat", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &masterformat_enums
+};
+
+static const char *masterstyle_enums[] = { "full", "relative", NULL };
+static cfg_type_t cfg_type_masterstyle = {
+ "masterstyle", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &masterstyle_enums
+};
+
+static keyword_type_t blocksize_kw = { "block-size", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_blocksize = { "blocksize", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_uint32, &blocksize_kw };
+
+static cfg_tuplefielddef_t resppadding_fields[] = {
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { "block-size", &cfg_type_blocksize, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_resppadding = {
+ "resppadding", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, resppadding_fields
+};
+
+/*%
+ * dnstap {
+ * &lt;message type&gt; [query | response] ;
+ * ...
+ * }
+ *
+ * ... where message type is one of: client, resolver, auth, forwarder,
+ * update, all
+ */
+static const char *dnstap_types[] = { "all", "auth", "client",
+ "forwarder", "resolver", "update",
+ NULL };
+
+static const char *dnstap_modes[] = { "query", "response", NULL };
+
+static cfg_type_t cfg_type_dnstap_type = { "dnstap_type", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, dnstap_types };
+
+static cfg_type_t cfg_type_dnstap_mode = {
+ "dnstap_mode", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, dnstap_modes
+};
+
+static cfg_tuplefielddef_t dnstap_fields[] = {
+ { "type", &cfg_type_dnstap_type, 0 },
+ { "mode", &cfg_type_dnstap_mode, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnstap_entry = { "dnstap_value", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dnstap_fields };
+
+static cfg_type_t cfg_type_dnstap = { "dnstap",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_dnstap_entry };
+
+/*%
+ * dnstap-output
+ */
+static isc_result_t
+parse_dtout(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));
+
+ /* Parse the mandatory "mode" and "path" fields */
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+ CHECK(cfg_parse_obj(pctx, fields[1].type, &obj->value.tuple[1]));
+
+ /* Parse "versions" and "size" fields in any order. */
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "size") == 0 &&
+ obj->value.tuple[2] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "versions") ==
+ 0 &&
+ obj->value.tuple[3] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[3].type,
+ &obj->value.tuple[3]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "suffix") ==
+ 0 &&
+ obj->value.tuple[4] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[4].type,
+ &obj->value.tuple[4]));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "unexpected token");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Create void objects for missing optional values. */
+ if (obj->value.tuple[2] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
+ }
+ if (obj->value.tuple[3] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
+ }
+ if (obj->value.tuple[4] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[4]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_dtout(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_obj(pctx, obj->value.tuple[0]); /* mode */
+ cfg_print_obj(pctx, obj->value.tuple[1]); /* file */
+ if (obj->value.tuple[2]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " size ");
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ }
+ if (obj->value.tuple[3]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " versions ");
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+ }
+ if (obj->value.tuple[4]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[4]);
+ }
+}
+
+static void
+doc_dtout(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( file | unix ) <quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ size ( unlimited | <size> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
+}
+
+static const char *dtoutmode_enums[] = { "file", "unix", NULL };
+static cfg_type_t cfg_type_dtmode = { "dtmode", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &dtoutmode_enums };
+
+static cfg_tuplefielddef_t dtout_fields[] = {
+ { "mode", &cfg_type_dtmode, 0 },
+ { "path", &cfg_type_qstring, 0 },
+ { "size", &cfg_type_sizenodefault, 0 },
+ { "versions", &cfg_type_logversions, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnstapoutput = { "dnstapoutput", parse_dtout,
+ print_dtout, doc_dtout,
+ &cfg_rep_tuple, dtout_fields };
+
+/*%
+ * response-policy {
+ * zone &lt;string&gt; [ policy (given|disabled|passthru|drop|tcp-only|
+ * nxdomain|nodata|cname &lt;domain&gt; ) ]
+ * [ recursive-only yes|no ] [ log yes|no ]
+ * [ max-policy-ttl number ]
+ * [ nsip-enable yes|no ] [ nsdname-enable yes|no ];
+ * } [ recursive-only yes|no ] [ max-policy-ttl number ]
+ * [ min-update-interval number ]
+ * [ break-dnssec yes|no ] [ min-ns-dots number ]
+ * [ qname-wait-recurse yes|no ]
+ * [ nsip-enable yes|no ] [ nsdname-enable yes|no ]
+ * [ dnsrps-enable yes|no ]
+ * [ dnsrps-options { DNSRPS configuration string } ];
+ */
+
+static void
+doc_rpz_policy(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const char *const *p;
+ /*
+ * This is cfg_doc_enum() without the trailing " )".
+ */
+ cfg_print_cstr(pctx, "( ");
+ for (p = type->of; *p != NULL; p++) {
+ cfg_print_cstr(pctx, *p);
+ if (p[1] != NULL) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ }
+}
+
+static void
+doc_rpz_cname(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_terminal(pctx, type);
+ cfg_print_cstr(pctx, " )");
+}
+
+/*
+ * Parse
+ * given|disabled|passthru|drop|tcp-only|nxdomain|nodata|cname <domain>
+ */
+static isc_result_t
+cfg_parse_rpz_policy(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;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ fields = type->of;
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+ /*
+ * parse cname domain only after "policy cname"
+ */
+ if (strcasecmp("cname", cfg_obj_asstring(obj->value.tuple[0])) != 0) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ } else {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+/*
+ * Parse a tuple consisting of any kind of required field followed
+ * by 2 or more optional keyvalues that can be in any order.
+ */
+static isc_result_t
+cfg_parse_kv_tuple(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ const cfg_tuplefielddef_t *fields, *f;
+ cfg_obj_t *obj = NULL;
+ int fn;
+ isc_result_t result;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ /*
+ * The zone first field is required and always first.
+ */
+ fields = type->of;
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type != isc_tokentype_string) {
+ break;
+ }
+
+ for (fn = 1, f = &fields[1];; ++fn, ++f) {
+ if (f->name == NULL) {
+ cfg_parser_error(pctx, 0, "unexpected '%s'",
+ TOKEN_STRING(pctx));
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ if (obj->value.tuple[fn] == NULL &&
+ strcasecmp(f->name, TOKEN_STRING(pctx)) == 0)
+ {
+ break;
+ }
+ }
+
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[fn]));
+ }
+
+ for (fn = 1, f = &fields[1]; f->name != NULL; ++fn, ++f) {
+ if (obj->value.tuple[fn] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL,
+ &obj->value.tuple[fn]));
+ }
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+cfg_print_kv_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields, *f;
+ const cfg_obj_t *fieldobj;
+
+ fields = obj->type->of;
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ fieldobj = obj->value.tuple[i];
+ if (fieldobj->type->print == cfg_print_void) {
+ continue;
+ }
+ if (i != 0) {
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, f->name);
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_print_obj(pctx, fieldobj);
+ }
+}
+
+static void
+cfg_doc_kv_tuple(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_tuplefielddef_t *fields, *f;
+
+ fields = type->of;
+ for (f = fields; f->name != NULL; f++) {
+ if ((f->flags & CFG_CLAUSEFLAG_NODOC) != 0) {
+ continue;
+ }
+ if (f != fields) {
+ cfg_print_cstr(pctx, " [ ");
+ cfg_print_cstr(pctx, f->name);
+ if (f->type->doc != cfg_doc_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ }
+ cfg_doc_obj(pctx, f->type);
+ if (f != fields) {
+ cfg_print_cstr(pctx, " ]");
+ }
+ }
+}
+
+static keyword_type_t zone_kw = { "zone", &cfg_type_astring };
+static cfg_type_t cfg_type_rpz_zone = { "zone", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &zone_kw };
+/*
+ * "no-op" is an obsolete equivalent of "passthru".
+ */
+static const char *rpz_policies[] = { "cname", "disabled", "drop",
+ "given", "no-op", "nodata",
+ "nxdomain", "passthru", "tcp-only",
+ NULL };
+static cfg_type_t cfg_type_rpz_policy_name = {
+ "policy name", cfg_parse_enum, cfg_print_ustring,
+ doc_rpz_policy, &cfg_rep_string, &rpz_policies
+};
+static cfg_type_t cfg_type_rpz_cname = {
+ "quoted_string", cfg_parse_astring, NULL,
+ doc_rpz_cname, &cfg_rep_string, NULL
+};
+static cfg_tuplefielddef_t rpz_policy_fields[] = {
+ { "policy name", &cfg_type_rpz_policy_name, 0 },
+ { "cname", &cfg_type_rpz_cname, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz_policy = { "policy tuple", cfg_parse_rpz_policy,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, rpz_policy_fields };
+static cfg_tuplefielddef_t rpz_zone_fields[] = {
+ { "zone name", &cfg_type_rpz_zone, 0 },
+ { "add-soa", &cfg_type_boolean, 0 },
+ { "log", &cfg_type_boolean, 0 },
+ { "max-policy-ttl", &cfg_type_duration, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { "policy", &cfg_type_rpz_policy, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "nsip-enable", &cfg_type_boolean, 0 },
+ { "nsdname-enable", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz_tuple = { "rpz tuple", cfg_parse_kv_tuple,
+ cfg_print_kv_tuple, cfg_doc_kv_tuple,
+ &cfg_rep_tuple, rpz_zone_fields };
+static cfg_type_t cfg_type_rpz_list = { "zone list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_rpz_tuple };
+static cfg_tuplefielddef_t rpz_fields[] = {
+ { "zone list", &cfg_type_rpz_list, 0 },
+ { "add-soa", &cfg_type_boolean, 0 },
+ { "break-dnssec", &cfg_type_boolean, 0 },
+ { "max-policy-ttl", &cfg_type_duration, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { "min-ns-dots", &cfg_type_uint32, 0 },
+ { "nsip-wait-recurse", &cfg_type_boolean, 0 },
+ { "nsdname-wait-recurse", &cfg_type_boolean, 0 },
+ { "qname-wait-recurse", &cfg_type_boolean, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "nsip-enable", &cfg_type_boolean, 0 },
+ { "nsdname-enable", &cfg_type_boolean, 0 },
+#ifdef USE_DNSRPS
+ { "dnsrps-enable", &cfg_type_boolean, 0 },
+ { "dnsrps-options", &cfg_type_bracketed_text, 0 },
+#else /* ifdef USE_DNSRPS */
+ { "dnsrps-enable", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnsrps-options", &cfg_type_bracketed_text,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef USE_DNSRPS */
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz = { "rpz",
+ cfg_parse_kv_tuple,
+ cfg_print_kv_tuple,
+ cfg_doc_kv_tuple,
+ &cfg_rep_tuple,
+ rpz_fields };
+
+/*
+ * Catalog zones
+ */
+static cfg_type_t cfg_type_catz_zone = { "zone", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &zone_kw };
+
+static cfg_tuplefielddef_t catz_zone_fields[] = {
+ { "zone name", &cfg_type_catz_zone, 0 },
+ { "default-masters", &cfg_type_namesockaddrkeylist,
+ CFG_CLAUSEFLAG_NODOC },
+ { "default-primaries", &cfg_type_namesockaddrkeylist, 0 },
+ { "zone-directory", &cfg_type_qstring, 0 },
+ { "in-memory", &cfg_type_boolean, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_catz_tuple = {
+ "catz tuple", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, catz_zone_fields
+};
+static cfg_type_t cfg_type_catz_list = { "zone list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_catz_tuple };
+static cfg_tuplefielddef_t catz_fields[] = {
+ { "zone list", &cfg_type_catz_list, 0 }, { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_catz = {
+ "catz", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, catz_fields
+};
+
+/*
+ * rate-limit
+ */
+static cfg_clausedef_t rrl_clauses[] = {
+ { "all-per-second", &cfg_type_uint32, 0 },
+ { "errors-per-second", &cfg_type_uint32, 0 },
+ { "exempt-clients", &cfg_type_bracketed_aml, 0 },
+ { "ipv4-prefix-length", &cfg_type_uint32, 0 },
+ { "ipv6-prefix-length", &cfg_type_uint32, 0 },
+ { "log-only", &cfg_type_boolean, 0 },
+ { "max-table-size", &cfg_type_uint32, 0 },
+ { "min-table-size", &cfg_type_uint32, 0 },
+ { "nodata-per-second", &cfg_type_uint32, 0 },
+ { "nxdomains-per-second", &cfg_type_uint32, 0 },
+ { "qps-scale", &cfg_type_uint32, 0 },
+ { "referrals-per-second", &cfg_type_uint32, 0 },
+ { "responses-per-second", &cfg_type_uint32, 0 },
+ { "slip", &cfg_type_uint32, 0 },
+ { "window", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rrl_clausesets[] = { rrl_clauses, NULL };
+
+static cfg_type_t cfg_type_rrl = { "rate-limit", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rrl_clausesets };
+
+static isc_result_t
+parse_optional_uint32(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_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_uint32, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_uint32(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <integer> ]");
+}
+
+static cfg_type_t cfg_type_optional_uint32 = { "optional_uint32",
+ parse_optional_uint32,
+ NULL,
+ doc_optional_uint32,
+ NULL,
+ NULL };
+
+static cfg_tuplefielddef_t prefetch_fields[] = {
+ { "trigger", &cfg_type_uint32, 0 },
+ { "eligible", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_prefetch = { "prefetch", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, prefetch_fields };
+/*
+ * DNS64.
+ */
+static cfg_clausedef_t dns64_clauses[] = {
+ { "break-dnssec", &cfg_type_boolean, 0 },
+ { "clients", &cfg_type_bracketed_aml, 0 },
+ { "exclude", &cfg_type_bracketed_aml, 0 },
+ { "mapped", &cfg_type_bracketed_aml, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "suffix", &cfg_type_netaddr6, 0 },
+ { NULL, NULL, 0 },
+};
+
+static cfg_clausedef_t *dns64_clausesets[] = { dns64_clauses, NULL };
+
+static cfg_type_t cfg_type_dns64 = { "dns64", cfg_parse_netprefix_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, dns64_clausesets };
+
+static const char *staleanswerclienttimeout_enums[] = { "disabled", "off",
+ NULL };
+static isc_result_t
+parse_staleanswerclienttimeout(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
+}
+
+static void
+doc_staleanswerclienttimeout(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_uint32);
+}
+
+static cfg_type_t cfg_type_staleanswerclienttimeout = {
+ "staleanswerclienttimeout",
+ parse_staleanswerclienttimeout,
+ cfg_print_ustring,
+ doc_staleanswerclienttimeout,
+ &cfg_rep_string,
+ staleanswerclienttimeout_enums
+};
+
+/*%
+ * Clauses that can be found within the 'view' statement,
+ * with defaults in the 'options' statement.
+ */
+
+static cfg_clausedef_t view_clauses[] = {
+ { "acache-cleaning-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "acache-enable", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "additional-from-auth", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "additional-from-cache", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "allow-new-zones", &cfg_type_boolean, 0 },
+ { "allow-query-cache", &cfg_type_bracketed_aml, 0 },
+ { "allow-query-cache-on", &cfg_type_bracketed_aml, 0 },
+ { "allow-recursion", &cfg_type_bracketed_aml, 0 },
+ { "allow-recursion-on", &cfg_type_bracketed_aml, 0 },
+ { "allow-v6-synthesis", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "attach-cache", &cfg_type_astring, 0 },
+ { "auth-nxdomain", &cfg_type_boolean, 0 },
+ { "cache-file", &cfg_type_qstring, CFG_CLAUSEFLAG_ANCIENT },
+ { "catalog-zones", &cfg_type_catz, 0 },
+ { "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI },
+ { "cleaning-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "clients-per-query", &cfg_type_uint32, 0 },
+ { "deny-answer-addresses", &cfg_type_denyaddresses, 0 },
+ { "deny-answer-aliases", &cfg_type_denyaliases, 0 },
+ { "disable-algorithms", &cfg_type_disablealgorithm,
+ CFG_CLAUSEFLAG_MULTI },
+ { "disable-ds-digests", &cfg_type_disabledsdigest,
+ CFG_CLAUSEFLAG_MULTI },
+ { "disable-empty-zone", &cfg_type_astring, CFG_CLAUSEFLAG_MULTI },
+ { "dns64", &cfg_type_dns64, CFG_CLAUSEFLAG_MULTI },
+ { "dns64-contact", &cfg_type_astring, 0 },
+ { "dns64-server", &cfg_type_astring, 0 },
+#ifdef USE_DNSRPS
+ { "dnsrps-enable", &cfg_type_boolean, 0 },
+ { "dnsrps-options", &cfg_type_bracketed_text, 0 },
+#else /* ifdef USE_DNSRPS */
+ { "dnsrps-enable", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnsrps-options", &cfg_type_bracketed_text,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef USE_DNSRPS */
+ { "dnssec-accept-expired", &cfg_type_boolean, 0 },
+ { "dnssec-enable", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "dnssec-lookaside", NULL,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT },
+ { "dnssec-must-be-secure", &cfg_type_mustbesecure,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "dnssec-validation", &cfg_type_boolorauto, 0 },
+#ifdef HAVE_DNSTAP
+ { "dnstap", &cfg_type_dnstap, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "dnstap", &cfg_type_dnstap, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_DNSTAP */
+ { "dual-stack-servers", &cfg_type_nameportiplist, 0 },
+ { "edns-udp-size", &cfg_type_uint32, 0 },
+ { "empty-contact", &cfg_type_astring, 0 },
+ { "empty-server", &cfg_type_astring, 0 },
+ { "empty-zones-enable", &cfg_type_boolean, 0 },
+ { "fetch-glue", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "fetch-quota-params", &cfg_type_fetchquota, 0 },
+ { "fetches-per-server", &cfg_type_fetchesper, 0 },
+ { "fetches-per-zone", &cfg_type_fetchesper, 0 },
+ { "filter-aaaa", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT },
+ { "filter-aaaa-on-v4", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "glue-cache", &cfg_type_boolean, CFG_CLAUSEFLAG_DEPRECATED },
+ { "ipv4only-enable", &cfg_type_boolean, 0 },
+ { "ipv4only-contact", &cfg_type_astring, 0 },
+ { "ipv4only-server", &cfg_type_astring, 0 },
+ { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 },
+ { "lame-ttl", &cfg_type_duration, 0 },
+#ifdef HAVE_LMDB
+ { "lmdb-mapsize", &cfg_type_sizeval, 0 },
+#else /* ifdef HAVE_LMDB */
+ { "lmdb-mapsize", &cfg_type_sizeval, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef HAVE_LMDB */
+ { "max-acache-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "max-cache-size", &cfg_type_sizeorpercent, 0 },
+ { "max-cache-ttl", &cfg_type_duration, 0 },
+ { "max-clients-per-query", &cfg_type_uint32, 0 },
+ { "max-ncache-ttl", &cfg_type_duration, 0 },
+ { "max-recursion-depth", &cfg_type_uint32, 0 },
+ { "max-recursion-queries", &cfg_type_uint32, 0 },
+ { "max-stale-ttl", &cfg_type_duration, 0 },
+ { "max-udp-size", &cfg_type_uint32, 0 },
+ { "message-compression", &cfg_type_boolean, 0 },
+ { "min-cache-ttl", &cfg_type_duration, 0 },
+ { "min-ncache-ttl", &cfg_type_duration, 0 },
+ { "min-roots", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "minimal-any", &cfg_type_boolean, 0 },
+ { "minimal-responses", &cfg_type_minimal, 0 },
+ { "new-zones-directory", &cfg_type_qstring, 0 },
+ { "no-case-compress", &cfg_type_bracketed_aml, 0 },
+ { "nocookie-udp-size", &cfg_type_uint32, 0 },
+ { "nosit-udp-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "nta-lifetime", &cfg_type_duration, 0 },
+ { "nta-recheck", &cfg_type_duration, 0 },
+ { "nxdomain-redirect", &cfg_type_astring, 0 },
+ { "preferred-glue", &cfg_type_astring, 0 },
+ { "prefetch", &cfg_type_prefetch, 0 },
+ { "provide-ixfr", &cfg_type_boolean, 0 },
+ { "qname-minimization", &cfg_type_qminmethod, 0 },
+ /*
+ * Note that the query-source option syntax is different
+ * from the other -source options.
+ */
+ { "query-source", &cfg_type_querysource4, 0 },
+ { "query-source-v6", &cfg_type_querysource6, 0 },
+ { "queryport-pool-ports", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "queryport-pool-updateinterval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "rate-limit", &cfg_type_rrl, 0 },
+ { "recursion", &cfg_type_boolean, 0 },
+ { "request-nsid", &cfg_type_boolean, 0 },
+ { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "require-server-cookie", &cfg_type_boolean, 0 },
+ { "resolver-nonbackoff-tries", &cfg_type_uint32, 0 },
+ { "resolver-query-timeout", &cfg_type_uint32, 0 },
+ { "resolver-retry-interval", &cfg_type_uint32, 0 },
+ { "response-padding", &cfg_type_resppadding, 0 },
+ { "response-policy", &cfg_type_rpz, 0 },
+ { "rfc2308-type1", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "root-delegation-only", &cfg_type_optional_exclude,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "root-key-sentinel", &cfg_type_boolean, 0 },
+ { "rrset-order", &cfg_type_rrsetorder, 0 },
+ { "send-cookie", &cfg_type_boolean, 0 },
+ { "servfail-ttl", &cfg_type_duration, 0 },
+ { "sortlist", &cfg_type_bracketed_aml, 0 },
+ { "stale-answer-enable", &cfg_type_boolean, 0 },
+ { "stale-answer-client-timeout", &cfg_type_staleanswerclienttimeout,
+ 0 },
+ { "stale-answer-ttl", &cfg_type_duration, 0 },
+ { "stale-cache-enable", &cfg_type_boolean, 0 },
+ { "stale-refresh-time", &cfg_type_duration, 0 },
+ { "suppress-initial-notify", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "synth-from-dnssec", &cfg_type_boolean, 0 },
+ { "topology", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "transfer-format", &cfg_type_transferformat, 0 },
+ { "trust-anchor-telemetry", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_EXPERIMENTAL },
+ { "use-queryport-pool", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "validate-except", &cfg_type_namelist, 0 },
+ { "v6-bias", &cfg_type_uint32, 0 },
+ { "zero-no-soa-ttl-cache", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found within the 'view' statement only.
+ */
+static cfg_clausedef_t view_only_clauses[] = {
+ { "match-clients", &cfg_type_bracketed_aml, 0 },
+ { "match-destinations", &cfg_type_bracketed_aml, 0 },
+ { "match-recursive-only", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Sig-validity-interval.
+ */
+
+static cfg_tuplefielddef_t validityinterval_fields[] = {
+ { "validity", &cfg_type_uint32, 0 },
+ { "re-sign", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_validityinterval = {
+ "validityinterval", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, validityinterval_fields
+};
+
+/*%
+ * Clauses that can be found in a 'dnssec-policy' statement.
+ */
+static cfg_clausedef_t dnssecpolicy_clauses[] = {
+ { "dnskey-ttl", &cfg_type_duration, 0 },
+ { "keys", &cfg_type_kaspkeys, 0 },
+ { "max-zone-ttl", &cfg_type_duration, 0 },
+ { "nsec3param", &cfg_type_nsec3, 0 },
+ { "parent-ds-ttl", &cfg_type_duration, 0 },
+ { "parent-propagation-delay", &cfg_type_duration, 0 },
+ { "parent-registration-delay", &cfg_type_duration,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "publish-safety", &cfg_type_duration, 0 },
+ { "purge-keys", &cfg_type_duration, 0 },
+ { "retire-safety", &cfg_type_duration, 0 },
+ { "signatures-refresh", &cfg_type_duration, 0 },
+ { "signatures-validity", &cfg_type_duration, 0 },
+ { "signatures-validity-dnskey", &cfg_type_duration, 0 },
+ { "zone-propagation-delay", &cfg_type_duration, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found in a 'zone' statement,
+ * with defaults in the 'view' or 'options' statement.
+ *
+ * Note: CFG_ZONE_* options indicate in which zone types this clause is
+ * legal.
+ */
+/*
+ * NOTE: To enable syntax which allows specifying port and protocol
+ * within 'allow-*' clauses, replace 'cfg_type_bracketed_aml' with
+ * 'cfg_type_transport_acl'.
+ *
+ * Example: allow-transfer port 853 protocol tls { ... };
+ */
+static cfg_clausedef_t zone_clauses[] = {
+ { "allow-notify", &cfg_type_bracketed_aml,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "allow-query", &cfg_type_bracketed_aml,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT | CFG_ZONE_STATICSTUB },
+ { "allow-query-on", &cfg_type_bracketed_aml,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT | CFG_ZONE_STATICSTUB },
+ { "allow-transfer", &cfg_type_transport_acl,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "allow-update", &cfg_type_bracketed_aml, CFG_ZONE_PRIMARY },
+ { "allow-update-forwarding", &cfg_type_bracketed_aml,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "also-notify", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "alt-transfer-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "alt-transfer-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "auto-dnssec", &cfg_type_autodnssec,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_CLAUSEFLAG_DEPRECATED },
+ { "check-dup-records", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-integrity", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "check-mx", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-mx-cname", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-sibling", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "check-spf", &cfg_type_warn, CFG_ZONE_PRIMARY },
+ { "check-srv-cname", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-wildcard", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "dialup", &cfg_type_dialuptype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "dnssec-dnskey-kskonly", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-loadkeys-interval", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-policy", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-secure-to-insecure", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "dnssec-update-mode", &cfg_type_dnssecupdatemode,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "forward", &cfg_type_forwardtype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_ZONE_STATICSTUB | CFG_ZONE_FORWARD },
+ { "forwarders", &cfg_type_portiplist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_ZONE_STATICSTUB | CFG_ZONE_FORWARD },
+ { "key-directory", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "maintain-ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "masterfile-format", &cfg_type_masterformat,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT },
+ { "masterfile-style", &cfg_type_masterstyle,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT },
+ { "max-ixfr-log-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "max-ixfr-ratio", &cfg_type_ixfrratio,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "max-journal-size", &cfg_type_size,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "max-records", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
+ { "max-refresh-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-retry-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-idle-in", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-idle-out", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_MIRROR | CFG_ZONE_SECONDARY },
+ { "max-transfer-time-in", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-time-out", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_MIRROR | CFG_ZONE_SECONDARY },
+ { "max-zone-ttl", &cfg_type_maxduration,
+ CFG_ZONE_PRIMARY | CFG_ZONE_REDIRECT },
+ { "min-refresh-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "min-retry-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "multi-master", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "notify", &cfg_type_notifytype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-delay", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-to-soa", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "nsec3-test-zone", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_TESTONLY | CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "parental-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "parental-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "request-expire", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "request-ixfr", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "serial-update-method", &cfg_type_updatemethod, CFG_ZONE_PRIMARY },
+ { "sig-signing-nodes", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-signing-signatures", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-signing-type", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-validity-interval", &cfg_type_validityinterval,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnskey-sig-validity", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "transfer-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "transfer-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "try-tcp-refresh", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "update-check-ksk", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "use-alt-transfer-source", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "zero-no-soa-ttl", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "zone-statistics", &cfg_type_zonestat,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found in a 'zone' statement only.
+ *
+ * Note: CFG_ZONE_* options indicate in which zone types this clause is
+ * legal.
+ */
+static cfg_clausedef_t zone_only_clauses[] = {
+ /*
+ * Note that the format of the check-names option is different between
+ * the zone options and the global/view options. Ugh.
+ */
+ { "type", &cfg_type_zonetype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_DELEGATION |
+ CFG_ZONE_HINT | CFG_ZONE_REDIRECT | CFG_ZONE_FORWARD },
+ { "check-names", &cfg_type_checkmode,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_HINT | CFG_ZONE_STUB },
+ { "database", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB },
+ { "delegation-only", &cfg_type_boolean,
+ CFG_ZONE_HINT | CFG_ZONE_STUB | CFG_ZONE_FORWARD |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "dlz", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_REDIRECT },
+ { "file", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT },
+ { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW },
+ { "inline-signing", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "ixfr-from-differences", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "ixfr-tmp-file", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "journal", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "masters", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_ZONE_REDIRECT | CFG_CLAUSEFLAG_NODOC },
+ { "parental-agents", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "primaries", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_ZONE_REDIRECT },
+ { "pubkey", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "server-addresses", &cfg_type_bracketed_netaddrlist,
+ CFG_ZONE_STATICSTUB },
+ { "server-names", &cfg_type_namelist, CFG_ZONE_STATICSTUB },
+ { "update-policy", &cfg_type_updatepolicy, CFG_ZONE_PRIMARY },
+ { NULL, NULL, 0 }
+};
+
+/*% The top-level named.conf syntax. */
+
+static cfg_clausedef_t *namedconf_clausesets[] = { namedconf_clauses,
+ namedconf_or_view_clauses,
+ NULL };
+cfg_type_t cfg_type_namedconf = { "namedconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, namedconf_clausesets };
+
+/*% The bind.keys syntax (trust-anchors/managed-keys/trusted-keys only). */
+static cfg_clausedef_t *bindkeys_clausesets[] = { bindkeys_clauses, NULL };
+cfg_type_t cfg_type_bindkeys = { "bindkeys", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, bindkeys_clausesets };
+
+/*% The "options" statement syntax. */
+
+static cfg_clausedef_t *options_clausesets[] = { options_clauses, view_clauses,
+ zone_clauses, NULL };
+static cfg_type_t cfg_type_options = { "options", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, options_clausesets };
+
+/*% The "view" statement syntax. */
+
+static cfg_clausedef_t *view_clausesets[] = { view_only_clauses,
+ namedconf_or_view_clauses,
+ view_clauses, zone_clauses,
+ NULL };
+
+static cfg_type_t cfg_type_viewopts = { "view", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, view_clausesets };
+
+/*% The "zone" statement syntax. */
+
+static cfg_clausedef_t *zone_clausesets[] = { zone_only_clauses, zone_clauses,
+ NULL };
+cfg_type_t cfg_type_zoneopts = { "zoneopts", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, zone_clausesets };
+
+/*% The "dnssec-policy" statement syntax. */
+static cfg_clausedef_t *dnssecpolicy_clausesets[] = { dnssecpolicy_clauses,
+ NULL };
+cfg_type_t cfg_type_dnssecpolicyopts = {
+ "dnssecpolicyopts", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, dnssecpolicy_clausesets
+};
+
+/*% The "dynamically loadable zones" statement syntax. */
+
+static cfg_clausedef_t dlz_clauses[] = { { "database", &cfg_type_astring, 0 },
+ { "search", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 } };
+static cfg_clausedef_t *dlz_clausesets[] = { dlz_clauses, NULL };
+static cfg_type_t cfg_type_dlz = { "dlz", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, dlz_clausesets };
+
+/*%
+ * The "dyndb" statement syntax.
+ */
+
+static cfg_tuplefielddef_t dyndb_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "library", &cfg_type_qstring, 0 },
+ { "parameters", &cfg_type_bracketed_text, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dyndb = { "dyndb", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dyndb_fields };
+
+/*%
+ * The "plugin" statement syntax.
+ * Currently only one plugin type is supported: query.
+ */
+
+static const char *plugin_enums[] = { "query", NULL };
+static cfg_type_t cfg_type_plugintype = { "plugintype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, plugin_enums };
+static cfg_tuplefielddef_t plugin_fields[] = {
+ { "type", &cfg_type_plugintype, 0 },
+ { "library", &cfg_type_astring, 0 },
+ { "parameters", &cfg_type_optional_bracketed_text, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_plugin = { "plugin", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, plugin_fields };
+
+/*%
+ * Clauses that can be found within the 'key' statement.
+ */
+static cfg_clausedef_t key_clauses[] = { { "algorithm", &cfg_type_astring, 0 },
+ { "secret", &cfg_type_sstring, 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_clausedef_t *key_clausesets[] = { key_clauses, NULL };
+static cfg_type_t cfg_type_key = { "key", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, key_clausesets };
+
+/*%
+ * Clauses that can be found in a 'server' statement.
+ *
+ * Please update lib/bind9/check.c and
+ * bin/tests/system/checkconf/good-server-christmas-tree.conf.in to
+ * exercise the new clause when adding new clauses.
+ */
+static cfg_clausedef_t server_clauses[] = {
+ { "bogus", &cfg_type_boolean, 0 },
+ { "edns", &cfg_type_boolean, 0 },
+ { "edns-udp-size", &cfg_type_uint32, 0 },
+ { "edns-version", &cfg_type_uint32, 0 },
+ { "keys", &cfg_type_server_key_kludge, 0 },
+ { "max-udp-size", &cfg_type_uint32, 0 },
+ { "notify-source", &cfg_type_sockaddr4wild, 0 },
+ { "notify-source-v6", &cfg_type_sockaddr6wild, 0 },
+ { "padding", &cfg_type_uint32, 0 },
+ { "provide-ixfr", &cfg_type_boolean, 0 },
+ { "query-source", &cfg_type_querysource4, 0 },
+ { "query-source-v6", &cfg_type_querysource6, 0 },
+ { "request-expire", &cfg_type_boolean, 0 },
+ { "request-ixfr", &cfg_type_boolean, 0 },
+ { "request-nsid", &cfg_type_boolean, 0 },
+ { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "send-cookie", &cfg_type_boolean, 0 },
+ { "support-ixfr", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "tcp-keepalive", &cfg_type_boolean, 0 },
+ { "tcp-only", &cfg_type_boolean, 0 },
+ { "transfer-format", &cfg_type_transferformat, 0 },
+ { "transfer-source", &cfg_type_sockaddr4wild, 0 },
+ { "transfer-source-v6", &cfg_type_sockaddr6wild, 0 },
+ { "transfers", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *server_clausesets[] = { server_clauses, NULL };
+static cfg_type_t cfg_type_server = { "server", cfg_parse_netprefix_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, server_clausesets };
+
+/*%
+ * Clauses that can be found in a 'channel' clause in the
+ * 'logging' statement.
+ *
+ * These have some additional constraints that need to be
+ * checked after parsing:
+ * - There must exactly one of file/syslog/null/stderr
+ */
+
+static const char *printtime_enums[] = { "iso8601", "iso8601-utc", "local",
+ NULL };
+static isc_result_t
+parse_printtime(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_printtime(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_printtime = { "printtime", parse_printtime,
+ cfg_print_ustring, doc_printtime,
+ &cfg_rep_string, printtime_enums };
+
+static cfg_clausedef_t channel_clauses[] = {
+ /* Destinations. We no longer require these to be first. */
+ { "file", &cfg_type_logfile, 0 },
+ { "syslog", &cfg_type_optional_facility, 0 },
+ { "null", &cfg_type_void, 0 },
+ { "stderr", &cfg_type_void, 0 },
+ /* Options. We now accept these for the null channel, too. */
+ { "severity", &cfg_type_logseverity, 0 },
+ { "print-time", &cfg_type_printtime, 0 },
+ { "print-severity", &cfg_type_boolean, 0 },
+ { "print-category", &cfg_type_boolean, 0 },
+ { "buffered", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *channel_clausesets[] = { channel_clauses, NULL };
+static cfg_type_t cfg_type_channel = { "channel", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, channel_clausesets };
+
+/*% A list of log destination, used in the "category" clause. */
+static cfg_type_t cfg_type_destinationlist = { "destinationlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+/*%
+ * Clauses that can be found in a 'logging' statement.
+ */
+static cfg_clausedef_t logging_clauses[] = {
+ { "channel", &cfg_type_channel, CFG_CLAUSEFLAG_MULTI },
+ { "category", &cfg_type_category, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *logging_clausesets[] = { logging_clauses, NULL };
+static cfg_type_t cfg_type_logging = { "logging", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, logging_clausesets };
+
+/*%
+ * For parsing an 'addzone' statement
+ */
+static cfg_tuplefielddef_t addzone_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "view", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_zoneopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_addzone = { "zone", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, addzone_fields };
+
+static cfg_clausedef_t addzoneconf_clauses[] = {
+ { "zone", &cfg_type_addzone, CFG_CLAUSEFLAG_MULTI }, { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *addzoneconf_clausesets[] = { addzoneconf_clauses,
+ NULL };
+
+cfg_type_t cfg_type_addzoneconf = { "addzoneconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, addzoneconf_clausesets };
+
+static isc_result_t
+parse_unitstring(char *str, isc_resourcevalue_t *valuep) {
+ char *endp;
+ unsigned int len;
+ uint64_t value;
+ uint64_t unit;
+
+ value = strtoull(str, &endp, 10);
+ if (*endp == 0) {
+ *valuep = value;
+ return (ISC_R_SUCCESS);
+ }
+
+ len = strlen(str);
+ if (len < 2 || endp[1] != '\0') {
+ return (ISC_R_FAILURE);
+ }
+
+ switch (str[len - 1]) {
+ case 'k':
+ case 'K':
+ unit = 1024;
+ break;
+ case 'm':
+ case 'M':
+ unit = 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ unit = 1024 * 1024 * 1024;
+ break;
+ default:
+ return (ISC_R_FAILURE);
+ }
+ if (value > ((uint64_t)UINT64_MAX / unit)) {
+ return (ISC_R_FAILURE);
+ }
+ *valuep = value * unit;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+parse_sizeval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ uint64_t val;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ CHECK(parse_unitstring(TOKEN_STRING(pctx), &val));
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint64, &obj));
+ obj->value.uint64 = val;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer and optional unit");
+ return (result);
+}
+
+static isc_result_t
+parse_sizeval_percent(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 val;
+ uint64_t percent;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ percent = strtoull(TOKEN_STRING(pctx), &endp, 10);
+
+ if (*endp == '%' && *(endp + 1) == 0) {
+ CHECK(cfg_create_obj(pctx, &cfg_type_percentage, &obj));
+ obj->value.uint32 = (uint32_t)percent;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ } else {
+ CHECK(parse_unitstring(TOKEN_STRING(pctx), &val));
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint64, &obj));
+ obj->value.uint64 = val;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer and optional unit or percent");
+ return (result);
+}
+
+static void
+doc_sizeval_percent(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "( ");
+ cfg_doc_terminal(pctx, &cfg_type_size);
+ cfg_print_cstr(pctx, " | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+/*%
+ * A size value (number + optional unit).
+ */
+static cfg_type_t cfg_type_sizeval = { "sizeval", parse_sizeval,
+ cfg_print_uint64, cfg_doc_terminal,
+ &cfg_rep_uint64, NULL };
+
+/*%
+ * A size, "unlimited", or "default".
+ */
+
+static isc_result_t
+parse_size(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_sizeval, ret));
+}
+
+static void
+doc_size(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_sizeval);
+}
+
+static const char *size_enums[] = { "default", "unlimited", NULL };
+static cfg_type_t cfg_type_size = {
+ "size", parse_size, cfg_print_ustring,
+ doc_size, &cfg_rep_string, size_enums
+};
+
+/*%
+ * A size or "unlimited", but not "default".
+ */
+static const char *sizenodefault_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_sizenodefault = {
+ "size_no_default", parse_size, cfg_print_ustring,
+ doc_size, &cfg_rep_string, sizenodefault_enums
+};
+
+/*%
+ * A size in absolute values or percents.
+ */
+static cfg_type_t cfg_type_sizeval_percent = {
+ "sizeval_percent", parse_sizeval_percent, cfg_print_ustring,
+ doc_sizeval_percent, &cfg_rep_string, NULL
+};
+
+/*%
+ * A size in absolute values or percents, or "unlimited", or "default"
+ */
+
+static isc_result_t
+parse_size_or_percent(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_sizeval_percent,
+ ret));
+}
+
+static void
+doc_parse_size_or_percent(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( default | unlimited | ");
+ cfg_doc_terminal(pctx, &cfg_type_sizeval);
+ cfg_print_cstr(pctx, " | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+static const char *sizeorpercent_enums[] = { "default", "unlimited", NULL };
+static cfg_type_t cfg_type_sizeorpercent = {
+ "size_or_percent", parse_size_or_percent, cfg_print_ustring,
+ doc_parse_size_or_percent, &cfg_rep_string, sizeorpercent_enums
+};
+
+/*%
+ * An IXFR size ratio: percentage, or "unlimited".
+ */
+
+static isc_result_t
+parse_ixfrratio(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_percentage, ret));
+}
+
+static void
+doc_ixfrratio(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( unlimited | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+static const char *ixfrratio_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_ixfrratio = { "ixfr_ratio", parse_ixfrratio,
+ NULL, doc_ixfrratio,
+ NULL, ixfrratio_enums };
+
+/*%
+ * optional_keyvalue
+ */
+static isc_result_t
+parse_maybe_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ bool optional, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const keyword_type_t *kw = type->of;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), kw->name) == 0)
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(kw->type->parse(pctx, kw->type, &obj));
+ obj->type = type; /* XXX kludge */
+ } else {
+ if (optional) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected '%s'",
+ kw->name);
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ }
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (parse_maybe_optional_keyvalue(pctx, type, false, ret));
+}
+
+static isc_result_t
+parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_maybe_optional_keyvalue(pctx, type, true, ret));
+}
+
+static void
+print_keyvalue(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const keyword_type_t *kw = obj->type->of;
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ kw->type->print(pctx, obj);
+}
+
+static void
+doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+}
+
+static void
+doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, "[ ");
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static const char *dialup_enums[] = { "notify", "notify-passive", "passive",
+ "refresh", NULL };
+static isc_result_t
+parse_dialup_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_dialup_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_dialuptype = { "dialuptype", parse_dialup_type,
+ cfg_print_ustring, doc_dialup_type,
+ &cfg_rep_string, dialup_enums };
+
+static const char *notify_enums[] = { "explicit", "master-only", "primary-only",
+ NULL };
+static isc_result_t
+parse_notify_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_notify_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_notifytype = {
+ "notifytype", parse_notify_type, cfg_print_ustring,
+ doc_notify_type, &cfg_rep_string, notify_enums,
+};
+
+static const char *minimal_enums[] = { "no-auth", "no-auth-recursive", NULL };
+static isc_result_t
+parse_minimal(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_minimal(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_minimal = {
+ "minimal", parse_minimal, cfg_print_ustring,
+ doc_minimal, &cfg_rep_string, minimal_enums,
+};
+
+static const char *ixfrdiff_enums[] = { "primary", "master", "secondary",
+ "slave", NULL };
+static isc_result_t
+parse_ixfrdiff_type(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_ixfrdiff_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_ixfrdifftype = {
+ "ixfrdiff", parse_ixfrdiff_type, cfg_print_ustring,
+ doc_ixfrdiff_type, &cfg_rep_string, ixfrdiff_enums,
+};
+
+static keyword_type_t key_kw = { "key", &cfg_type_astring };
+
+cfg_type_t cfg_type_keyref = { "keyref", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_string, &key_kw };
+
+static cfg_type_t cfg_type_optional_keyref = {
+ "optional_keyref", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &key_kw
+};
+
+static const char *qminmethod_enums[] = { "strict", "relaxed", "disabled",
+ "off", NULL };
+
+static cfg_type_t cfg_type_qminmethod = { "qminmethod", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, qminmethod_enums };
+
+/*%
+ * A "controls" statement is represented as a map with the multivalued
+ * "inet" and "unix" clauses.
+ */
+
+static keyword_type_t controls_allow_kw = { "allow", &cfg_type_bracketed_aml };
+
+static cfg_type_t cfg_type_controls_allow = {
+ "controls_allow", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_list, &controls_allow_kw
+};
+
+static keyword_type_t controls_keys_kw = { "keys", &cfg_type_keylist };
+
+static cfg_type_t cfg_type_controls_keys = {
+ "controls_keys", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &controls_keys_kw
+};
+
+static keyword_type_t controls_readonly_kw = { "read-only", &cfg_type_boolean };
+
+static cfg_type_t cfg_type_controls_readonly = {
+ "controls_readonly", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_boolean, &controls_readonly_kw
+};
+
+static cfg_tuplefielddef_t inetcontrol_fields[] = {
+ { "address", &cfg_type_controls_sockaddr, 0 },
+ { "allow", &cfg_type_controls_allow, 0 },
+ { "keys", &cfg_type_controls_keys, 0 },
+ { "read-only", &cfg_type_controls_readonly, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_inetcontrol = {
+ "inetcontrol", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, inetcontrol_fields
+};
+
+static keyword_type_t controls_perm_kw = { "perm", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_perm = {
+ "controls_perm", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_perm_kw
+};
+
+static keyword_type_t controls_owner_kw = { "owner", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_owner = {
+ "controls_owner", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_owner_kw
+};
+
+static keyword_type_t controls_group_kw = { "group", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_group = {
+ "controls_allow", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_group_kw
+};
+
+static cfg_tuplefielddef_t unixcontrol_fields[] = {
+ { "path", &cfg_type_qstring, 0 },
+ { "perm", &cfg_type_controls_perm, 0 },
+ { "owner", &cfg_type_controls_owner, 0 },
+ { "group", &cfg_type_controls_group, 0 },
+ { "keys", &cfg_type_controls_keys, 0 },
+ { "read-only", &cfg_type_controls_readonly, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_unixcontrol = {
+ "unixcontrol", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, unixcontrol_fields
+};
+
+static cfg_clausedef_t controls_clauses[] = {
+ { "inet", &cfg_type_inetcontrol, CFG_CLAUSEFLAG_MULTI },
+ { "unix", &cfg_type_unixcontrol, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *controls_clausesets[] = { controls_clauses, NULL };
+static cfg_type_t cfg_type_controls = { "controls", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, &controls_clausesets };
+
+/*%
+ * A "statistics-channels" statement is represented as a map with the
+ * multivalued "inet" clauses.
+ */
+static void
+doc_optional_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, "[ ");
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static cfg_type_t cfg_type_optional_allow = {
+ "optional_allow", parse_optional_keyvalue,
+ print_keyvalue, doc_optional_bracketed_list,
+ &cfg_rep_list, &controls_allow_kw
+};
+
+static cfg_tuplefielddef_t statserver_fields[] = {
+ { "address", &cfg_type_controls_sockaddr, 0 }, /* reuse controls def */
+ { "allow", &cfg_type_optional_allow, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_statschannel = {
+ "statschannel", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, statserver_fields
+};
+
+static cfg_clausedef_t statservers_clauses[] = {
+ { "inet", &cfg_type_statschannel, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *statservers_clausesets[] = { statservers_clauses,
+ NULL };
+
+static cfg_type_t cfg_type_statschannels = {
+ "statistics-channels", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, &statservers_clausesets
+};
+
+/*%
+ * An optional class, as used in view and zone statements.
+ */
+static isc_result_t
+parse_optional_class(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_ustring, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_class(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <class> ]");
+}
+
+static cfg_type_t cfg_type_optional_class = { "optional_class",
+ parse_optional_class,
+ NULL,
+ doc_optional_class,
+ NULL,
+ NULL };
+
+static isc_result_t
+parse_querysource(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;
+ in_port_t port = 0;
+ unsigned int have_address = 0;
+ unsigned int have_port = 0;
+ unsigned int have_dscp = 0;
+ const unsigned int *flagp = type->of;
+ int dscp = -1;
+
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ isc_netaddr_any(&netaddr);
+ } else if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ isc_netaddr_any6(&netaddr);
+ } else {
+ UNREACHABLE();
+ }
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ if (strcasecmp(TOKEN_STRING(pctx), "address") == 0) {
+ /* read "address" */
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_rawaddr(pctx, *flagp,
+ &netaddr));
+ have_address++;
+ } else if (strcasecmp(TOKEN_STRING(pctx), "port") == 0)
+ {
+ /* read "port" */
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'port' is deprecated");
+ }
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_rawport(pctx, CFG_ADDR_WILDOK,
+ &port));
+ have_port++;
+ } else if (strcasecmp(TOKEN_STRING(pctx), "dscp") == 0)
+ {
+ /* read "dscp" */
+ cfg_parser_warning(pctx, 0,
+ "'dscp' is obsolete and "
+ "should be removed");
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_uint32(pctx, NULL, &obj));
+ dscp = cfg_obj_asuint32(obj);
+ cfg_obj_destroy(pctx, &obj);
+ have_dscp++;
+ } else if (have_port == 0 && have_dscp == 0 &&
+ have_address == 0)
+ {
+ return (cfg_parse_sockaddr(pctx, type, ret));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected 'address' "
+ "or 'port'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ } else {
+ break;
+ }
+ }
+ if (have_address > 1 || have_port > 1 || have_address + have_port == 0)
+ {
+ cfg_parser_error(pctx, 0, "expected one address and/or port");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ if (have_dscp > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one dscp");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_querysource, &obj));
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
+ obj->value.sockaddrdscp.dscp = dscp;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid query source");
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_querysource(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isc_netaddr_t na;
+ isc_netaddr_fromsockaddr(&na, &obj->value.sockaddr);
+ cfg_print_cstr(pctx, "address ");
+ cfg_print_rawaddr(pctx, &na);
+ cfg_print_cstr(pctx, " port ");
+ cfg_print_rawuint(pctx, isc_sockaddr_getport(&obj->value.sockaddr));
+ if (obj->value.sockaddrdscp.dscp != -1) {
+ cfg_print_cstr(pctx, " dscp ");
+ cfg_print_rawuint(pctx, obj->value.sockaddrdscp.dscp);
+ }
+}
+
+static void
+doc_querysource(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp = type->of;
+
+ cfg_print_cstr(pctx, "[ address ] ( ");
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ } else if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ } else {
+ UNREACHABLE();
+ }
+ cfg_print_cstr(pctx, " | * )");
+}
+
+static unsigned int sockaddr4wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V4OK |
+ CFG_ADDR_DSCPOK;
+static unsigned int sockaddr6wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V6OK |
+ CFG_ADDR_DSCPOK;
+
+static cfg_type_t cfg_type_querysource4 = {
+ "querysource4", parse_querysource, NULL, doc_querysource,
+ NULL, &sockaddr4wild_flags
+};
+
+static cfg_type_t cfg_type_querysource6 = {
+ "querysource6", parse_querysource, NULL, doc_querysource,
+ NULL, &sockaddr6wild_flags
+};
+
+static cfg_type_t cfg_type_querysource = { "querysource", NULL,
+ print_querysource, NULL,
+ &cfg_rep_sockaddr, NULL };
+
+/*%
+ * The socket address syntax in the "controls" statement is silly.
+ * It allows both socket address families, but also allows "*",
+ * which is gratuitously interpreted as the IPv4 wildcard address.
+ */
+static unsigned int controls_sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_WILDOK | CFG_ADDR_PORTOK;
+static cfg_type_t cfg_type_controls_sockaddr = {
+ "controls_sockaddr", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &controls_sockaddr_flags
+};
+
+/*%
+ * Handle the special kludge syntax of the "keys" clause in the "server"
+ * statement, which takes a single key with or without braces and semicolon.
+ */
+static isc_result_t
+parse_server_key_kludge(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ bool braces = false;
+ UNUSED(type);
+
+ /* Allow opening brace. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '{')
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ braces = true;
+ }
+
+ CHECK(cfg_parse_obj(pctx, &cfg_type_astring, ret));
+
+ if (braces) {
+ /* Skip semicolon if present. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == ';')
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ }
+
+ CHECK(cfg_parse_special(pctx, '}'));
+ }
+cleanup:
+ return (result);
+}
+static cfg_type_t cfg_type_server_key_kludge = {
+ "server_key", parse_server_key_kludge, NULL, cfg_doc_terminal, NULL,
+ NULL
+};
+
+/*%
+ * An optional logging facility.
+ */
+
+static isc_result_t
+parse_optional_facility(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)
+ {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_astring, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_facility(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <syslog_facility> ]");
+}
+
+static cfg_type_t cfg_type_optional_facility = { "optional_facility",
+ parse_optional_facility,
+ NULL,
+ doc_optional_facility,
+ NULL,
+ NULL };
+
+/*%
+ * A log severity. Return as a string, except "debug N",
+ * which is returned as a keyword object.
+ */
+
+static keyword_type_t debug_kw = { "debug", &cfg_type_uint32 };
+static cfg_type_t cfg_type_debuglevel = { "debuglevel", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_uint32, &debug_kw };
+
+static isc_result_t
+parse_logseverity(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "debug") == 0)
+ {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "debug" */
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(cfg_parse_uint32(pctx, NULL, ret));
+ } else {
+ /*
+ * The debug level is optional and defaults to 1.
+ * This makes little sense, but we support it for
+ * compatibility with BIND 8.
+ */
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint32, ret));
+ (*ret)->value.uint32 = 1;
+ }
+ (*ret)->type = &cfg_type_debuglevel; /* XXX kludge */
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_loglevel, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static cfg_type_t cfg_type_logseverity = { "log_severity", parse_logseverity,
+ NULL, cfg_doc_terminal,
+ NULL, NULL };
+
+/*%
+ * The "file" clause of the "channel" statement.
+ * This is yet another special case.
+ */
+
+static const char *logversions_enums[] = { "unlimited", NULL };
+static isc_result_t
+parse_logversions(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
+}
+
+static void
+doc_logversions(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_uint32);
+}
+
+static cfg_type_t cfg_type_logversions = {
+ "logversions", parse_logversions, cfg_print_ustring,
+ doc_logversions, &cfg_rep_string, logversions_enums
+};
+
+static const char *logsuffix_enums[] = { "increment", "timestamp", NULL };
+static cfg_type_t cfg_type_logsuffix = { "logsuffix", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &logsuffix_enums };
+
+static cfg_tuplefielddef_t logfile_fields[] = {
+ { "file", &cfg_type_qstring, 0 },
+ { "versions", &cfg_type_logversions, 0 },
+ { "size", &cfg_type_size, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
+ { NULL, NULL, 0 }
+};
+
+static isc_result_t
+parse_logfile(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));
+
+ /* Parse the mandatory "file" field */
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+
+ /* Parse "versions" and "size" fields in any order. */
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "versions") == 0 &&
+ obj->value.tuple[1] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "size") ==
+ 0 &&
+ obj->value.tuple[2] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "suffix") ==
+ 0 &&
+ obj->value.tuple[3] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[3].type,
+ &obj->value.tuple[3]));
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Create void objects for missing optional values. */
+ if (obj->value.tuple[1] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ }
+ if (obj->value.tuple[2] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
+ }
+ if (obj->value.tuple[3] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_logfile(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_obj(pctx, obj->value.tuple[0]); /* file */
+ if (obj->value.tuple[1]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " versions ");
+ cfg_print_obj(pctx, obj->value.tuple[1]);
+ }
+ if (obj->value.tuple[2]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " size ");
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ }
+ if (obj->value.tuple[3]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+ }
+}
+
+static void
+doc_logfile(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "<quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ size <size> ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
+}
+
+static cfg_type_t cfg_type_logfile = { "log_file", parse_logfile,
+ print_logfile, doc_logfile,
+ &cfg_rep_tuple, logfile_fields };
+
+/*% An IPv4 address, "*" accepted as wildcard. */
+static cfg_type_t cfg_type_sockaddr4wild = {
+ "sockaddr4wild", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr4wild_flags
+};
+
+/*% An IPv6 address, "*" accepted as wildcard. */
+static cfg_type_t cfg_type_sockaddr6wild = {
+ "v6addrportwild", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr6wild_flags
+};
+
+/*%
+ * rndc
+ */
+
+static cfg_clausedef_t rndcconf_options_clauses[] = {
+ { "default-key", &cfg_type_astring, 0 },
+ { "default-port", &cfg_type_uint32, 0 },
+ { "default-server", &cfg_type_astring, 0 },
+ { "default-source-address", &cfg_type_netaddr4wild, 0 },
+ { "default-source-address-v6", &cfg_type_netaddr6wild, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_options_clausesets[] = {
+ rndcconf_options_clauses, NULL
+};
+
+static cfg_type_t cfg_type_rndcconf_options = {
+ "rndcconf_options", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rndcconf_options_clausesets
+};
+
+static cfg_clausedef_t rndcconf_server_clauses[] = {
+ { "key", &cfg_type_astring, 0 },
+ { "port", &cfg_type_uint32, 0 },
+ { "source-address", &cfg_type_netaddr4wild, 0 },
+ { "source-address-v6", &cfg_type_netaddr6wild, 0 },
+ { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_server_clausesets[] = {
+ rndcconf_server_clauses, NULL
+};
+
+static cfg_type_t cfg_type_rndcconf_server = {
+ "rndcconf_server", cfg_parse_named_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rndcconf_server_clausesets
+};
+
+static cfg_clausedef_t rndcconf_clauses[] = {
+ { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
+ { "server", &cfg_type_rndcconf_server, CFG_CLAUSEFLAG_MULTI },
+ { "options", &cfg_type_rndcconf_options, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_clausesets[] = { rndcconf_clauses, NULL };
+
+cfg_type_t cfg_type_rndcconf = { "rndcconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndcconf_clausesets };
+
+static cfg_clausedef_t rndckey_clauses[] = { { "key", &cfg_type_key, 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_clausedef_t *rndckey_clausesets[] = { rndckey_clauses, NULL };
+
+cfg_type_t cfg_type_rndckey = { "rndckey", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndckey_clausesets };
+
+/*
+ * session.key has exactly the same syntax as rndc.key, but it's defined
+ * separately for clarity (and so we can extend it someday, if needed).
+ */
+cfg_type_t cfg_type_sessionkey = { "sessionkey", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndckey_clausesets };
+
+static cfg_tuplefielddef_t nameport_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nameport = { "nameport", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, nameport_fields };
+
+static void
+doc_sockaddrnameport(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( ");
+ cfg_print_cstr(pctx, "<quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " )");
+}
+
+static isc_result_t
+parse_sockaddrnameport(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr,
+ ret));
+ } else {
+ const cfg_tuplefielddef_t *fields =
+ cfg_type_nameport.of;
+ CHECK(cfg_create_tuple(pctx, &cfg_type_nameport, &obj));
+ CHECK(cfg_parse_obj(pctx, fields[0].type,
+ &obj->value.tuple[0]));
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ *ret = obj;
+ obj = NULL;
+ }
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address or hostname");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static cfg_type_t cfg_type_sockaddrnameport = { "sockaddrnameport_element",
+ parse_sockaddrnameport,
+ NULL,
+ doc_sockaddrnameport,
+ NULL,
+ NULL };
+
+static cfg_type_t cfg_type_bracketed_sockaddrnameportlist = {
+ "bracketed_sockaddrnameportlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_sockaddrnameport
+};
+
+/*%
+ * A list of socket addresses or name with an optional default port,
+ * as used in the dual-stack-servers option. E.g.,
+ * "port 1234 { dual-stack-servers.net; 10.0.0.1; 1::2 port 69; }"
+ */
+static cfg_tuplefielddef_t nameportiplist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nameportiplist = {
+ "nameportiplist", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, nameportiplist_fields
+};
+
+/*%
+ * remote servers element.
+ */
+
+static void
+doc_remoteselement(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( ");
+ cfg_print_cstr(pctx, "<remote-servers>");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " )");
+}
+
+static isc_result_t
+parse_remoteselement(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr,
+ ret));
+ } else {
+ CHECK(cfg_parse_astring(pctx, &cfg_type_astring, ret));
+ }
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address or remote servers list "
+ "name");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static cfg_type_t cfg_type_remoteselement = { "remotes_element",
+ parse_remoteselement,
+ NULL,
+ doc_remoteselement,
+ NULL,
+ NULL };
+
+static int
+cmp_clause(const void *ap, const void *bp) {
+ const cfg_clausedef_t *a = (const cfg_clausedef_t *)ap;
+ const cfg_clausedef_t *b = (const cfg_clausedef_t *)bp;
+ return (strcmp(a->name, b->name));
+}
+
+bool
+cfg_clause_validforzone(const char *name, unsigned int ztype) {
+ const cfg_clausedef_t *clause;
+ bool valid = false;
+
+ for (clause = zone_clauses; clause->name != NULL; clause++) {
+ if ((clause->flags & ztype) == 0 ||
+ strcmp(clause->name, name) != 0)
+ {
+ continue;
+ }
+ valid = true;
+ }
+ for (clause = zone_only_clauses; clause->name != NULL; clause++) {
+ if ((clause->flags & ztype) == 0 ||
+ strcmp(clause->name, name) != 0)
+ {
+ continue;
+ }
+ valid = true;
+ }
+
+ return (valid);
+}
+
+void
+cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+#define NCLAUSES \
+ (((sizeof(zone_clauses) + sizeof(zone_only_clauses)) / \
+ sizeof(clause[0])) - \
+ 1)
+
+ cfg_printer_t pctx;
+ cfg_clausedef_t *clause = NULL;
+ cfg_clausedef_t clauses[NCLAUSES];
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+
+ memmove(clauses, zone_clauses, sizeof(zone_clauses));
+ memmove(clauses + sizeof(zone_clauses) / sizeof(zone_clauses[0]) - 1,
+ zone_only_clauses, sizeof(zone_only_clauses));
+ qsort(clauses, NCLAUSES - 1, sizeof(clause[0]), cmp_clause);
+
+ cfg_print_cstr(&pctx, "zone <string> [ <class> ] {\n");
+ pctx.indent++;
+
+ switch (zonetype) {
+ case CFG_ZONE_PRIMARY:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type primary;\n");
+ break;
+ case CFG_ZONE_SECONDARY:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type secondary;\n");
+ break;
+ case CFG_ZONE_MIRROR:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type mirror;\n");
+ break;
+ case CFG_ZONE_STUB:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type stub;\n");
+ break;
+ case CFG_ZONE_HINT:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type hint;\n");
+ break;
+ case CFG_ZONE_FORWARD:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type forward;\n");
+ break;
+ case CFG_ZONE_STATICSTUB:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type static-stub;\n");
+ break;
+ case CFG_ZONE_REDIRECT:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type redirect;\n");
+ break;
+ case CFG_ZONE_DELEGATION:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type delegation-only;\n");
+ break;
+ case CFG_ZONE_INVIEW:
+ /* no zone type is specified for these */
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ for (clause = clauses; 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;
+ }
+
+ if ((clause->flags & zonetype) == 0 ||
+ strcasecmp(clause->name, "type") == 0)
+ {
+ continue;
+ }
+ cfg_print_indent(&pctx);
+ 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");
+ }
+
+ pctx.indent--;
+ cfg_print_cstr(&pctx, "};\n");
+}
+
+/*%
+ * "tls" and related statement syntax.
+ */
+static cfg_type_t cfg_type_tlsprotos = { "tls_protocols",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_clausedef_t tls_clauses[] = {
+ { "key-file", &cfg_type_qstring, 0 },
+ { "cert-file", &cfg_type_qstring, 0 },
+ { "ca-file", &cfg_type_qstring, 0 },
+ { "remote-hostname", &cfg_type_qstring, 0 },
+ { "dhparam-file", &cfg_type_qstring, 0 },
+ { "protocols", &cfg_type_tlsprotos, 0 },
+ { "ciphers", &cfg_type_astring, 0 },
+ { "prefer-server-ciphers", &cfg_type_boolean, 0 },
+ { "session-tickets", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *tls_clausesets[] = { tls_clauses, NULL };
+static cfg_type_t cfg_type_tlsconf = { "tlsconf", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, tls_clausesets };
+
+static keyword_type_t tls_kw = { "tls", &cfg_type_astring };
+static cfg_type_t cfg_type_optional_tls = {
+ "tlsoptional", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &tls_kw
+};
+
+/* http and https */
+
+static cfg_type_t cfg_type_bracketed_http_endpoint_list = {
+ "bracketed_http_endpoint_list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_qstring
+};
+
+static cfg_clausedef_t cfg_http_description_clauses[] = {
+ { "endpoints", &cfg_type_bracketed_http_endpoint_list, 0 },
+ { "listener-clients", &cfg_type_uint32, 0 },
+ { "streams-per-connection", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *http_description_clausesets[] = {
+ cfg_http_description_clauses, NULL
+};
+
+static cfg_type_t cfg_type_http_description = {
+ "http_desc", cfg_parse_named_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, http_description_clausesets
+};
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);
+}