diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
commit | 3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch) | |
tree | 2e1c188dd7b8d7475cd163de9ae02c428343669b /lib/isccfg | |
parent | Initial commit. (diff) | |
download | bind9-upstream.tar.xz bind9-upstream.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.am | 37 | ||||
-rw-r--r-- | lib/isccfg/Makefile.in | 967 | ||||
-rw-r--r-- | lib/isccfg/aclconf.c | 1010 | ||||
-rw-r--r-- | lib/isccfg/dnsconf.c | 57 | ||||
-rw-r--r-- | lib/isccfg/duration.c | 239 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/aclconf.h | 91 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/cfg.h | 609 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/duration.h | 87 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/grammar.h | 590 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/kaspconf.h | 56 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/log.h | 46 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/namedconf.h | 54 | ||||
-rw-r--r-- | lib/isccfg/kaspconf.c | 576 | ||||
-rw-r--r-- | lib/isccfg/log.c | 38 | ||||
-rw-r--r-- | lib/isccfg/namedconf.c | 3998 | ||||
-rw-r--r-- | lib/isccfg/parser.c | 3901 |
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., <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 { + * <message type> [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 <string> [ policy (given|disabled|passthru|drop|tcp-only| + * nxdomain|nodata|cname <domain> ) ] + * [ 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); +} |