diff options
Diffstat (limited to '')
42 files changed, 35623 insertions, 0 deletions
diff --git a/bin/named/Makefile.am b/bin/named/Makefile.am new file mode 100644 index 0000000..57a023b --- /dev/null +++ b/bin/named/Makefile.am @@ -0,0 +1,123 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + -I$(top_builddir)/include \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) \ + $(LIBNS_CFLAGS) \ + $(LIBISCCC_CFLAGS) \ + $(LIBISCCFG_CFLAGS) \ + $(LIBBIND9_CFLAGS) \ + $(OPENSSL_CFLAGS) \ + $(LIBCAP_CFLAGS) \ + $(LMDB_CFLAGS) \ + $(MAXMINDDB_CFLAGS) \ + $(DNSTAP_CFLAGS) \ + $(LIBUV_CFLAGS) \ + $(ZLIB_CFLAGS) + +if HAVE_JSON_C +AM_CPPFLAGS += \ + $(JSON_C_CFLAGS) +endif HAVE_JSON_C + +if HAVE_LIBNGHTTP2 +AM_CPPFLAGS += \ + $(LIBNGHTTP2_CFLAGS) +endif HAVE_LIBNGHTTP2 + +if HAVE_LIBXML2 +AM_CPPFLAGS += \ + $(LIBXML2_CFLAGS) +endif HAVE_LIBXML2 + +AM_CPPFLAGS += \ + -DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \ + -DNAMED_SYSCONFDIR=\"${sysconfdir}\" + +sbin_PROGRAMS = named + +nodist_named_SOURCES = xsl.c +BUILT_SOURCES = xsl.c +CLEANFILES = xsl.c + +EXTRA_DIST = bind9.xsl + +xsl.c: bind9.xsl Makefile + (echo 'const char xslmsg[] =' && \ + $(SED) -e 's,\",\\\",g' \ + -e 's,^,\",' \ + -e 's,$$,\\n\",' && \ + echo ";") \ + < "${srcdir}/bind9.xsl" > $@ + +named_SOURCES = \ + builtin.c \ + config.c \ + control.c \ + controlconf.c \ + dlz_dlopen_driver.c \ + fuzz.c \ + log.c \ + logconf.c \ + main.c \ + os.c \ + server.c \ + statschannel.c \ + tkeyconf.c \ + transportconf.c \ + tsigconf.c \ + zoneconf.c \ + include/dlz/dlz_dlopen_driver.h \ + include/named/builtin.h \ + include/named/config.h \ + include/named/control.h \ + include/named/fuzz.h \ + include/named/geoip.h \ + include/named/globals.h \ + include/named/log.h \ + include/named/logconf.h \ + include/named/main.h \ + include/named/os.h \ + include/named/server.h \ + include/named/smf_globals.h \ + include/named/statschannel.h \ + include/named/tkeyconf.h \ + include/named/transportconf.h \ + include/named/tsigconf.h \ + include/named/types.h \ + include/named/zoneconf.h \ + xsl_p.h + +if HAVE_GEOIP2 +AM_CPPFLAGS += \ + -DMAXMINDDB_PREFIX=\"@MAXMINDDB_PREFIX@\" +named_SOURCES += \ + geoip.c +endif + +named_LDADD = \ + $(LIBISC_LIBS) \ + $(LIBDNS_LIBS) \ + $(LIBNS_LIBS) \ + $(LIBISCCC_LIBS) \ + $(LIBISCCFG_LIBS) \ + $(LIBBIND9_LIBS) \ + $(OPENSSL_LIBS) \ + $(LIBCAP_LIBS) \ + $(LMDB_LIBS) \ + $(MAXMINDDB_LIBS) \ + $(DNSTAP_LIBS) \ + $(LIBUV_LIBS) \ + $(LIBXML2_LIBS) \ + $(ZLIB_LIBS) + +if HAVE_JSON_C +named_LDADD += \ + $(JSON_C_LIBS) +endif HAVE_JSON_C + +if HAVE_LIBNGHTTP2 +named_LDADD += \ + $(LIBNGHTTP2_LIBS) +endif HAVE_LIBNGHTTP2 diff --git a/bin/named/Makefile.in b/bin/named/Makefile.in new file mode 100644 index 0000000..a38ce3b --- /dev/null +++ b/bin/named/Makefile.in @@ -0,0 +1,964 @@ +# 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 + +@HAVE_JSON_C_TRUE@am__append_2 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_CFLAGS) + +@HAVE_LIBNGHTTP2_TRUE@am__append_3 = \ +@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_CFLAGS) + +@HAVE_LIBXML2_TRUE@am__append_4 = \ +@HAVE_LIBXML2_TRUE@ $(LIBXML2_CFLAGS) + +sbin_PROGRAMS = named$(EXEEXT) +@HAVE_GEOIP2_TRUE@am__append_5 = \ +@HAVE_GEOIP2_TRUE@ -DMAXMINDDB_PREFIX=\"@MAXMINDDB_PREFIX@\" + +@HAVE_GEOIP2_TRUE@am__append_6 = \ +@HAVE_GEOIP2_TRUE@ geoip.c + +@HAVE_JSON_C_TRUE@am__append_7 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_LIBS) + +@HAVE_LIBNGHTTP2_TRUE@am__append_8 = \ +@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_LIBS) + +subdir = bin/named +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 $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" +PROGRAMS = $(sbin_PROGRAMS) +am__named_SOURCES_DIST = builtin.c config.c control.c controlconf.c \ + dlz_dlopen_driver.c fuzz.c log.c logconf.c main.c os.c \ + server.c statschannel.c tkeyconf.c transportconf.c tsigconf.c \ + zoneconf.c include/dlz/dlz_dlopen_driver.h \ + include/named/builtin.h include/named/config.h \ + include/named/control.h include/named/fuzz.h \ + include/named/geoip.h include/named/globals.h \ + include/named/log.h include/named/logconf.h \ + include/named/main.h include/named/os.h include/named/server.h \ + include/named/smf_globals.h include/named/statschannel.h \ + include/named/tkeyconf.h include/named/transportconf.h \ + include/named/tsigconf.h include/named/types.h \ + include/named/zoneconf.h xsl_p.h geoip.c +@HAVE_GEOIP2_TRUE@am__objects_1 = geoip.$(OBJEXT) +am_named_OBJECTS = builtin.$(OBJEXT) config.$(OBJEXT) \ + control.$(OBJEXT) controlconf.$(OBJEXT) \ + dlz_dlopen_driver.$(OBJEXT) fuzz.$(OBJEXT) log.$(OBJEXT) \ + logconf.$(OBJEXT) main.$(OBJEXT) os.$(OBJEXT) server.$(OBJEXT) \ + statschannel.$(OBJEXT) tkeyconf.$(OBJEXT) \ + transportconf.$(OBJEXT) tsigconf.$(OBJEXT) zoneconf.$(OBJEXT) \ + $(am__objects_1) +nodist_named_OBJECTS = xsl.$(OBJEXT) +named_OBJECTS = $(am_named_OBJECTS) $(nodist_named_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_JSON_C_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +@HAVE_LIBNGHTTP2_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) +named_DEPENDENCIES = $(LIBISC_LIBS) $(LIBDNS_LIBS) $(LIBNS_LIBS) \ + $(LIBISCCC_LIBS) $(LIBISCCFG_LIBS) $(LIBBIND9_LIBS) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) +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 = +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)/builtin.Po ./$(DEPDIR)/config.Po \ + ./$(DEPDIR)/control.Po ./$(DEPDIR)/controlconf.Po \ + ./$(DEPDIR)/dlz_dlopen_driver.Po ./$(DEPDIR)/fuzz.Po \ + ./$(DEPDIR)/geoip.Po ./$(DEPDIR)/log.Po ./$(DEPDIR)/logconf.Po \ + ./$(DEPDIR)/main.Po ./$(DEPDIR)/os.Po ./$(DEPDIR)/server.Po \ + ./$(DEPDIR)/statschannel.Po ./$(DEPDIR)/tkeyconf.Po \ + ./$(DEPDIR)/transportconf.Po ./$(DEPDIR)/tsigconf.Po \ + ./$(DEPDIR)/xsl.Po ./$(DEPDIR)/zoneconf.Po +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 = $(named_SOURCES) $(nodist_named_SOURCES) +DIST_SOURCES = $(am__named_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +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 -I$(top_builddir)/include $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) $(LIBNS_CFLAGS) $(LIBISCCC_CFLAGS) \ + $(LIBISCCFG_CFLAGS) $(LIBBIND9_CFLAGS) $(OPENSSL_CFLAGS) \ + $(LIBCAP_CFLAGS) $(LMDB_CFLAGS) $(MAXMINDDB_CFLAGS) \ + $(DNSTAP_CFLAGS) $(LIBUV_CFLAGS) $(ZLIB_CFLAGS) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + -DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \ + -DNAMED_SYSCONFDIR=\"${sysconfdir}\" $(am__append_5) +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 + +nodist_named_SOURCES = xsl.c +BUILT_SOURCES = xsl.c +CLEANFILES = xsl.c +EXTRA_DIST = bind9.xsl +named_SOURCES = builtin.c config.c control.c controlconf.c \ + dlz_dlopen_driver.c fuzz.c log.c logconf.c main.c os.c \ + server.c statschannel.c tkeyconf.c transportconf.c tsigconf.c \ + zoneconf.c include/dlz/dlz_dlopen_driver.h \ + include/named/builtin.h include/named/config.h \ + include/named/control.h include/named/fuzz.h \ + include/named/geoip.h include/named/globals.h \ + include/named/log.h include/named/logconf.h \ + include/named/main.h include/named/os.h include/named/server.h \ + include/named/smf_globals.h include/named/statschannel.h \ + include/named/tkeyconf.h include/named/transportconf.h \ + include/named/tsigconf.h include/named/types.h \ + include/named/zoneconf.h xsl_p.h $(am__append_6) +named_LDADD = $(LIBISC_LIBS) $(LIBDNS_LIBS) $(LIBNS_LIBS) \ + $(LIBISCCC_LIBS) $(LIBISCCFG_LIBS) $(LIBBIND9_LIBS) \ + $(OPENSSL_LIBS) $(LIBCAP_LIBS) $(LMDB_LIBS) $(MAXMINDDB_LIBS) \ + $(DNSTAP_LIBS) $(LIBUV_LIBS) $(LIBXML2_LIBS) $(ZLIB_LIBS) \ + $(am__append_7) $(am__append_8) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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 bin/named/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bin/named/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-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +named$(EXEEXT): $(named_OBJECTS) $(named_DEPENDENCIES) $(EXTRA_named_DEPENDENCIES) + @rm -f named$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(named_OBJECTS) $(named_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/builtin.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/control.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/controlconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlz_dlopen_driver.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/geoip.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/os.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statschannel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tkeyconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transportconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsigconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xsl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zoneconf.Po@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 $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +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: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(sbindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +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." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/builtin.Po + -rm -f ./$(DEPDIR)/config.Po + -rm -f ./$(DEPDIR)/control.Po + -rm -f ./$(DEPDIR)/controlconf.Po + -rm -f ./$(DEPDIR)/dlz_dlopen_driver.Po + -rm -f ./$(DEPDIR)/fuzz.Po + -rm -f ./$(DEPDIR)/geoip.Po + -rm -f ./$(DEPDIR)/log.Po + -rm -f ./$(DEPDIR)/logconf.Po + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/os.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/statschannel.Po + -rm -f ./$(DEPDIR)/tkeyconf.Po + -rm -f ./$(DEPDIR)/transportconf.Po + -rm -f ./$(DEPDIR)/tsigconf.Po + -rm -f ./$(DEPDIR)/xsl.Po + -rm -f ./$(DEPDIR)/zoneconf.Po + -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-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-sbinPROGRAMS + +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)/builtin.Po + -rm -f ./$(DEPDIR)/config.Po + -rm -f ./$(DEPDIR)/control.Po + -rm -f ./$(DEPDIR)/controlconf.Po + -rm -f ./$(DEPDIR)/dlz_dlopen_driver.Po + -rm -f ./$(DEPDIR)/fuzz.Po + -rm -f ./$(DEPDIR)/geoip.Po + -rm -f ./$(DEPDIR)/log.Po + -rm -f ./$(DEPDIR)/logconf.Po + -rm -f ./$(DEPDIR)/main.Po + -rm -f ./$(DEPDIR)/os.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/statschannel.Po + -rm -f ./$(DEPDIR)/tkeyconf.Po + -rm -f ./$(DEPDIR)/transportconf.Po + -rm -f ./$(DEPDIR)/tsigconf.Po + -rm -f ./$(DEPDIR)/xsl.Po + -rm -f ./$(DEPDIR)/zoneconf.Po + -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-sbinPROGRAMS + +unit: unit-am + +unit-am: unit-local + +.MAKE: all check install install-am install-exec install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-sbinPROGRAMS 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-man install-pdf \ + install-pdf-am install-ps install-ps-am install-sbinPROGRAMS \ + 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-sbinPROGRAMS unit-am unit-local + +.PRECIOUS: Makefile + + +xsl.c: bind9.xsl Makefile + (echo 'const char xslmsg[] =' && \ + $(SED) -e 's,\",\\\",g' \ + -e 's,^,\",' \ + -e 's,$$,\\n\",' && \ + echo ";") \ + < "${srcdir}/bind9.xsl" > $@ + +# 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/bin/named/bind9.xsl b/bin/named/bind9.xsl new file mode 100644 index 0000000..9dda61d --- /dev/null +++ b/bin/named/bind9.xsl @@ -0,0 +1,1106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + - 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. +--> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" version="1.0"> + <xsl:output method="html" indent="yes" version="4.0"/> + <!-- the version number **below** must match version in bin/named/statschannel.c --> + <!-- don't forget to update "/xml/v<STATS_XML_VERSION_MAJOR>" in the HTTP endpoints listed below --> + <xsl:template match="statistics[@version="3.13"]"> + <html> + <head> + <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> + <script type="text/javascript"> + $(function($) { + var wid=0; + $('table.zones').each(function(i) { if( $(this).width() > wid ) wid = $(this).width(); return true; }); + $('table.zones').css('min-width', wid ); + $("h2+table,h3+table,h4+table,h2+div,h3+div,h2+script,h3+script").prev().append(' <a class="tabletoggle" href="#" style="font-size:small">Show/Hide</a>'); + $(".tabletoggle").click(function(){ + var n = $(this).closest("h2,h3,h4").next(); + if (n.is("script")) { n = n.next(); } + if (n.is("div")) { n.toggleClass("hidden"); n = n.next(); } + if (n.is("table")) { n.toggleClass("hidden"); } + return false; + }); + }); + </script> + + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <script type="text/javascript" src="https://www.google.com/jsapi"/> + <script type="text/javascript"> + + google.load("visualization", "1", {packages:["corechart"]}); + google.setOnLoadCallback(loadGraphs); + + var graphs=[]; + + function drawChart(chart_title,target,style,data) { + var data = google.visualization.arrayToDataTable(data); + + var options = { + title: chart_title + }; + + var chart; + if (style == "barchart") { + chart = new google.visualization.BarChart(document.getElementById(target)); + chart.draw(data, options); + } else if (style == "piechart") { + chart = new google.visualization.PieChart(document.getElementById(target)); + chart.draw(data, options); + } + } + + function loadGraphs(){ + var g; + + while(g = graphs.shift()){ + // alert("going for: " + g.target); + if(g.data.length > 1){ + drawChart(g.title,g.target,g.style,g.data); + } + } + } + + <xsl:if test="server/counters[@type="qtype"]/counter"> + // Server Incoming Query Types + graphs.push({ + 'title' : "Server Incoming Query Types", + 'target': 'chart_incoming_qtypes', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="server/counters[@type="qtype"]/counter">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + </xsl:if> + + <xsl:if test="server/counters[@type="opcode"]/counter"> + // Server Incoming Requests by opcode + graphs.push({ + 'title' : "Server Incoming Requests by DNS Opcode", + 'target': 'chart_incoming_opcodes', + 'style': 'barchart', + 'data': [['Opcode','Counter'],<xsl:for-each select="server/counters[@type="opcode"]/counter[. > 0 or substring(@name,1,3) != 'RES']">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>]}); + </xsl:if> + </script> + </xsl:if> + <style type="text/css"> + body { + font-family: sans-serif; + background-color: #ffffff; + color: #000000; + font-size: 10pt; + } + + .hidden{ + display: none; + } + + .odd{ + background-color: #f0f0f0; + } + + .even{ + background-color: #ffffff; + } + + p.footer{ + font-style:italic; + color: grey; + } + + table { + border-collapse: collapse; + border: 1px solid grey; + } + + table.counters{ + border: 1px solid grey; + width: 500px; + } + table.counters th { + text-align: right; + border: 1px solid grey; + width: 150px; + } + table.counters td { + text-align: right; + font-family: monospace; + } + table.counters tr:hover{ + background-color: #99ddff; + } + + table.info { + border: 1px solid grey; + width: 500px; + } + table.info th { + text-align: center; + border: 1px solid grey; + width: 150px; + } + table.info td { + text-align: center; + } + table.info tr:hover{ + background-color: #99ddff; + } + + table.tasks { + border: 1px solid grey; + width: 500px; + } + table.tasks th { + text-align: center; + border: 1px solid grey; + width: 150px; + } + table.tasks td { + text-align: right; + font-family: monospace; + } + table.tasks td:nth-child(2) { + text-align: center; + } + table.tasks td:nth-child(4) { + text-align: center; + } + table.tasks tr:hover{ + background-color: #99ddff; + } + + table.netstat { + border: 1px solid grey; + width: 500px; + } + table.netstat th { + text-align: center; + border: 1px solid grey; + width: 150px; + } + table.netstat td { + text-align: center; + } + table.netstat td:nth-child(4) { + text-align: right; + font-family: monospace; + } + table.netstat td:nth-child(7) { + text-align: left; + } + table.netstat tr:hover{ + background-color: #99ddff; + } + + table.mctx { + border: 1px solid grey; + width: 500px; + } + table.mctx th { + text-align: center; + border: 1px solid grey; + } + table.mctx td { + text-align: right; + font-family: monospace; + } + table.mctx td:nth-child(-n+2) { + text-align: left; + width: 100px; + } + table.mctx tr:hover{ + background-color: #99ddff; + } + + table.zones { + border: 1px solid grey; + width: 500px; + } + table.zones th { + text-align: center; + border: 1px solid grey; + } + table.zones td { + text-align: center; + font-family: monospace; + } + table.zones td:nth-child(1) { + text-align: right; + } + table.zones td:nth-child(4) { + text-align: right; + } + + .totals { + background-color: rgb(1,169,206); + color: #ffffff; + } + table.zones { + border: 1px solid grey; + } + table.zones td { + text-align: right; + font-family: monospace; + } + table.zones td:nth-child(2) { + text-align: center; + } + table.zones td:nth-child(3) { + text-align: left; + } + table.zones tr:hover{ + background-color: #99ddff; + } + + td, th { + padding-right: 5px; + padding-left: 5px; + border: 1px solid grey; + } + + .header h1 { + color: rgb(1,169,206); + padding: 0px; + } + + .content { + background-color: #ffffff; + color: #000000; + padding: 4px; + } + + .item { + padding: 4px; + text-align: right; + } + + .value { + padding: 4px; + font-weight: bold; + } + + + h2 { + color: grey; + font-size: 14pt; + width:500px; + text-align:center; + } + + h3 { + color: #444444; + font-size: 12pt; + width:500px; + text-align:center; + } + h4 { + color: rgb(1,169,206); + font-size: 10pt; + width:500px; + text-align:center; + } + + .pie { + width:500px; + height: 500px; + } + + </style> + <title>ISC BIND 9 Statistics</title> + </head> + <body> + <div class="header"> + <h1>ISC Bind 9 Configuration and Statistics</h1> + </div> + <p>Alternate statistics views: <a href="/">All</a>, + <a href="/xml/v3/status">Status</a>, + <a href="/xml/v3/server">Server</a>, + <a href="/xml/v3/zones">Zones</a>, + <a href="/xml/v3/net">Network</a>, + <a href="/xml/v3/tasks">Tasks</a>, + <a href="/xml/v3/mem">Memory</a> and + <a href="/xml/v3/traffic">Traffic Size</a></p> + <hr/> + <h2>Server Status</h2> + <table class="info"> + <tr class="odd"> + <th>Boot time:</th> + <td> + <xsl:value-of select="server/boot-time"/> + </td> + </tr> + <tr class="even"> + <th>Last reconfigured:</th> + <td> + <xsl:value-of select="server/config-time"/> + </td> + </tr> + <tr class="odd"> + <th>Current time:</th> + <td> + <xsl:value-of select="server/current-time"/> + </td> + </tr> + <tr class="even"> + <th>Server version:</th> + <td> + <xsl:value-of select="server/version"/> + </td> + </tr> + </table> + <br/> + <xsl:if test="server/counters[@type="opcode"]/counter[. > 0]"> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <h2>Incoming Requests by DNS Opcode</h2> + <!-- Non Mozilla specific markup --> + <div class="pie" id="chart_incoming_opcodes"> + [cannot display chart] + </div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="server/counters[@type="opcode"]/counter[. > 0 or substring(@name,1,3) != 'RES']"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class0"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class0}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + <tr> + <th class="totals">Total:</th> + <td class="totals"> + <xsl:value-of select="sum(server/counters[@type="opcode"]/counter)"/> + </td> + </tr> + </table> + <br/> + </xsl:if> + <xsl:if test="server/counters[@type="qtype"]/counter"> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <h3>Incoming Queries by Query Type</h3> + <div class="pie" id="chart_incoming_qtypes"> + [cannot display chart] + </div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="server/counters[@type="qtype"]/counter"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + <tr> + <th class="totals">Total:</th> + <td class="totals"> + <xsl:value-of select="sum(server/counters[@type="qtype"]/counter)"/> + </td> + </tr> + </table> + <br/> + </xsl:if> + <xsl:if test="views/view[count(counters[@type="resqtype"]/counter) > 0]"> + <h2>Outgoing Queries per view</h2> + <xsl:for-each select="views/view[count(counters[@type="resqtype"]/counter) > 0]"> + <h3>View <xsl:value-of select="@name"/></h3> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <script type="text/javascript"> + graphs.push({ + 'title': "Outgoing Queries for view: <xsl:value-of select="@name"/>", + 'target': 'chart_outgoing_queries_view_<xsl:value-of select="@name"/>', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="counters[@type="resqtype"]/counter">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + </script> + <xsl:variable name="target"> + <xsl:value-of select="@name"/> + </xsl:variable> + <div class="pie" id="chart_outgoing_queries_view_{$target}">[no data to display]</div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="counters[@type="resqtype"]/counter"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class1"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class1}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:for-each> + </xsl:if> + <xsl:if test="server/counters[@type="nsstat"]/counter[.>0]"> + <h2>Server Statistics</h2> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <script type="text/javascript"> + graphs.push({ + 'title' : "Server Counters", + 'target': 'chart_server_nsstat_restype', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="server/counters[@type="nsstat"]/counter[.>0]">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + </script> + <div class="pie" id="chart_server_nsstat_restype">[no data to display]</div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="server/counters[@type="nsstat"]/counter[.>0]"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class2"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class2}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="server/counters[@type="zonestat"]/counter[.>0]"> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <h2>Zone Maintenance Statistics</h2> + <script type="text/javascript"> + graphs.push({ + 'title' : "Zone Maintenance Stats", + 'target': 'chart_server_zone_maint', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="server/counters[@type="zonestat"]/counter[.>0]">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + </script> + <!-- Non Mozilla specific markup --> + <div class="pie" id="chart_server_zone_maint">[no data to display]</div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="server/counters[@type="zonestat"]/counter"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class3"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class3}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + <xsl:if test="server/counters[@type="resstat"]/counter[.>0]"> + <h2>Resolver Statistics (Common)</h2> + <table class="counters"> + <xsl:for-each select="server/counters[@type="resstat"]/counter"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class4"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class4}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + <xsl:for-each select="views/view"> + <xsl:if test="counters[@type="resstats"]/counter[.>0]"> + <h3>Resolver Statistics for View <xsl:value-of select="@name"/></h3> + <table class="counters"> + <xsl:for-each select="counters[@type="resstats"]/counter[.>0]"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class5"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class5}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + <xsl:for-each select="views/view"> + <xsl:if test="counters[@type="adbstat"]/counter[.>0]"> + <h3>ADB Statistics for View <xsl:value-of select="@name"/></h3> + <table class="counters"> + <xsl:for-each select="counters[@type="adbstat"]/counter[.>0]"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class5"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class5}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + <xsl:for-each select="views/view"> + <xsl:if test="counters[@type="cachestats"]/counter[.>0]"> + <h3>Cache Statistics for View <xsl:value-of select="@name"/></h3> + <table class="counters"> + <xsl:for-each select="counters[@type="cachestats"]/counter[.>0]"> + <xsl:sort select="." data-type="number" order="descending"/> + <xsl:variable name="css-class5"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class5}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + <xsl:for-each select="views/view"> + <xsl:if test="cache/rrset"> + <h3>Cache DB RRsets for View <xsl:value-of select="@name"/></h3> + <table class="counters"> + <xsl:for-each select="cache/rrset"> + <xsl:variable name="css-class6"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class6}"> + <th> + <xsl:value-of select="name"/> + </th> + <td> + <xsl:value-of select="counter"/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + </xsl:for-each> + <xsl:if test="traffic//udp/counters[@type="request-size"]/counter[.>0] or traffic//udp/counters[@type="response-size"]/counter[.>0] or traffic//tcp/counters[@type="request-size"]/counter[.>0] or traffic//tcp/counters[@type="response-size"]/counter[.>0]"> + <h2>Traffic Size Statistics</h2> + </xsl:if> + <xsl:if test="traffic//udp/counters[@type="request-size"]/counter[.>0]"> + <h4>UDP Requests Received</h4> + <table class="counters"> + <xsl:for-each select="traffic//udp/counters[@type="request-size"]/counter[.>0]"> + <xsl:variable name="css-class7"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class7}"> + <th><xsl:value-of select="local-name(../../..)"/></th> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="traffic//udp/counters[@type="response-size"]/counter[.>0]"> + <h4>UDP Responses Sent</h4> + <table class="counters"> + <xsl:for-each select="traffic//udp/counters[@type="response-size"]/counter[.>0]"> + <xsl:variable name="css-class7"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class7}"> + <th><xsl:value-of select="local-name(../../..)"/></th> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="traffic//tcp/counters[@type="request-size"]/counter[.>0]"> + <h4>TCP Requests Received</h4> + <table class="counters"> + <xsl:for-each select="traffic//tcp/counters[@type="request-size"]/counter[.>0]"> + <xsl:variable name="css-class7"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class7}"> + <th><xsl:value-of select="local-name(../../..)"/></th> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="traffic//tcp/counters[@type="response-size"]/counter[.>0]"> + <h4>TCP Responses Sent</h4> + <table class="counters"> + <xsl:for-each select="traffic//tcp/counters[@type="response-size"]/counter[.>0]"> + <xsl:variable name="css-class7"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class7}"> + <th><xsl:value-of select="local-name(../../..)"/></th> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="server/counters[@type="sockstat"]/counter[.>0]"> + <h2>Socket I/O Statistics</h2> + <table class="counters"> + <xsl:for-each select="server/counters[@type="sockstat"]/counter[.>0]"> + <xsl:variable name="css-class7"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class7}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="views/view/zones/zone"> + <xsl:for-each select="views/view"> + <h3>Zones for View <xsl:value-of select="@name"/></h3> + <table class="zones"> + <thead><tr><th>Name</th><th>Class</th><th>Type</th><th>Serial</th><th>Loaded</th><th>Expires</th><th>Refresh</th></tr></thead> + <tbody> + <xsl:for-each select="zones/zone"> + <xsl:variable name="css-class15"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class15}"> + <td><xsl:value-of select="@name"/></td> + <td><xsl:value-of select="@rdataclass"/></td> + <td><xsl:value-of select="type"/></td> + <td><xsl:value-of select="serial"/></td> + <td><xsl:value-of select="loaded"/></td> + <td><xsl:value-of select="expires"/></td> + <td><xsl:value-of select="refresh"/></td></tr> + </xsl:for-each> + </tbody> + </table> + </xsl:for-each> + </xsl:if> + <xsl:if test="views/view[zones/zone/counters[@type="qtype"]/counter >0]"> + <h2>Received QTYPES per view/zone</h2> + <xsl:for-each select="views/view[zones/zone/counters[@type="qtype"]/counter >0]"> + <h3>View <xsl:value-of select="@name"/></h3> + <xsl:variable name="thisview"> + <xsl:value-of select="@name"/> + </xsl:variable> + <xsl:for-each select="zones/zone"> + <xsl:if test="counters[@type="qtype"]/counter[count(.) > 0]"> + <h4>Zone <xsl:value-of select="@name"/></h4> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <script type="text/javascript"> + graphs.push({ + 'title': "Query types for zone <xsl:value-of select="@name"/>", + 'target': 'chart_qtype_<xsl:value-of select="../../@name"/>_<xsl:value-of select="@name"/>', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="counters[@type="qtype"]/counter[.>0 and @name != "QryAuthAns"]">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + + </script> + <xsl:variable name="target"> + <xsl:value-of select="@name"/> + </xsl:variable> + <div class="pie" id="chart_qtype_{$thisview}_{$target}">[no data to display]</div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="counters[@type="qtype"]/counter"> + <xsl:sort select="."/> + <xsl:variable name="css-class10"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class10}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + </xsl:for-each> + </xsl:if> + <xsl:if test="views/view[zones/zone/counters[@type="rcode"]/counter >0]"> + <h2>Response Codes per view/zone</h2> + <xsl:for-each select="views/view[zones/zone/counters[@type="rcode"]/counter >0]"> + <h3>View <xsl:value-of select="@name"/></h3> + <xsl:variable name="thisview2"> + <xsl:value-of select="@name"/> + </xsl:variable> + <xsl:for-each select="zones/zone"> + <xsl:if test="counters[@type="rcode"]/counter[. > 0]"> + <h4>Zone <xsl:value-of select="@name"/></h4> + <xsl:if test="system-property('xsl:vendor')!='Transformiix'"> + <!-- Non Mozilla specific markup --> + <script type="text/javascript"> + graphs.push({ + 'title': "Response codes for zone <xsl:value-of select="@name"/>", + 'target': 'chart_rescode_<xsl:value-of select="../../@name"/>_<xsl:value-of select="@name"/>', + 'style': 'barchart', + 'data': [['Type','Counter'],<xsl:for-each select="counters[@type="rcode"]/counter[.>0 and @name != "QryAuthAns"]">['<xsl:value-of select="@name"/>',<xsl:value-of select="."/>],</xsl:for-each>] + }); + + </script> + <xsl:variable name="target"> + <xsl:value-of select="@name"/> + </xsl:variable> + <div class="pie" id="chart_rescode_{$thisview2}_{$target}">[no data to display]</div> + </xsl:if> + <table class="counters"> + <xsl:for-each select="counters[@type="rcode"]/counter[.>0 and @name != "QryAuthAns"]"> + <xsl:sort select="."/> + <xsl:variable name="css-class11"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class11}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + </xsl:for-each> + </xsl:if> + <xsl:if test="views/view[zones/zone/counters[@type="gluecache"]/counter >0]"> + <h2>Glue cache statistics</h2> + <xsl:for-each select="views/view[zones/zone/counters[@type="gluecache"]/counter >0]"> + <h3>View <xsl:value-of select="@name"/></h3> + <xsl:variable name="thisview2"> + <xsl:value-of select="@name"/> + </xsl:variable> + <xsl:for-each select="zones/zone"> + <xsl:if test="counters[@type="gluecache"]/counter[. > 0]"> + <h4>Zone <xsl:value-of select="@name"/></h4> + <table class="counters"> + <xsl:for-each select="counters[@type="gluecache"]/counter[. > 0]"> + <xsl:sort select="."/> + <xsl:variable name="css-class11"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class11}"> + <th> + <xsl:value-of select="@name"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + </xsl:for-each> + </xsl:for-each> + </xsl:if> + <xsl:if test="taskmgr/thread-model/type"> + <h2>Task Manager Configuration</h2> + <table class="counters"> + <tr> + <th class="even">Thread-Model</th> + <td> + <xsl:value-of select="taskmgr/thread-model/type"/> + </td> + </tr> + <tr class="odd"> + <th>Worker Threads</th> + <td> + <xsl:value-of select="taskmgr/thread-model/worker-threads"/> + </td> + </tr> + <tr class="even"> + <th>Default Quantum</th> + <td> + <xsl:value-of select="taskmgr/thread-model/default-quantum"/> + </td> + </tr> + <tr class="odd"> + <th>Tasks Running</th> + <td> + <xsl:value-of select="taskmgr/thread-model/tasks-running"/> + </td> + </tr> + <tr class="even"> + <th>Tasks Ready</th> + <td> + <xsl:value-of select="taskmgr/thread-model/tasks-ready"/> + </td> + </tr> + </table> + <br/> + </xsl:if> + <xsl:if test="taskmgr/tasks/task"> + <h2>Tasks</h2> + <table class="tasks"> + <tr> + <th>ID</th> + <th>Name</th> + <th>References</th> + <th>State</th> + <th>Quantum</th> + <th>Events</th> + </tr> + <xsl:for-each select="taskmgr/tasks/task"> + <xsl:sort select="name"/> + <xsl:variable name="css-class14"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class14}"> + <td> + <xsl:value-of select="id"/> + </td> + <td> + <xsl:value-of select="name"/> + </td> + <td> + <xsl:value-of select="references"/> + </td> + <td> + <xsl:value-of select="state"/> + </td> + <td> + <xsl:value-of select="quantum"/> + </td> + <td> + <xsl:value-of select="events"/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="memory/summary"> + <h2>Memory Usage Summary</h2> + <table class="counters"> + <xsl:for-each select="memory/summary/*"> + <xsl:variable name="css-class13"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class13}"> + <th> + <xsl:value-of select="name()"/> + </th> + <td> + <xsl:value-of select="."/> + </td> + </tr> + </xsl:for-each> + </table> + <br/> + </xsl:if> + <xsl:if test="memory/contexts/context"> + <h2>Memory Contexts</h2> + <table class="mctx"> + <tr> + <th>ID</th> + <th>Name</th> + <th>References</th> + <th>TotalUse</th> + <th>InUse</th> + <th>MaxUse</th> + <th>Malloced</th> + <th>MaxMalloced</th> + <th>BlockSize</th> + <th>Pools</th> + <th>HiWater</th> + <th>LoWater</th> + </tr> + <xsl:for-each select="memory/contexts/context"> + <xsl:sort select="total" data-type="number" order="descending"/> + <xsl:variable name="css-class14"> + <xsl:choose> + <xsl:when test="position() mod 2 = 0">even</xsl:when> + <xsl:otherwise>odd</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <tr class="{$css-class14}"> + <td> + <xsl:value-of select="id"/> + </td> + <td> + <xsl:value-of select="name"/> + </td> + <td> + <xsl:value-of select="references"/> + </td> + <td> + <xsl:value-of select="total"/> + </td> + <td> + <xsl:value-of select="inuse"/> + </td> + <td> + <xsl:value-of select="maxinuse"/> + </td> + <td> + <xsl:value-of select="malloced"/> + </td> + <td> + <xsl:value-of select="maxmalloced"/> + </td> + <td> + <xsl:value-of select="blocksize"/> + </td> + <td> + <xsl:value-of select="pools"/> + </td> + <td> + <xsl:value-of select="hiwater"/> + </td> + <td> + <xsl:value-of select="lowater"/> + </td> + </tr> + </xsl:for-each> + </table> + </xsl:if> + <hr/> + <p class="footer">Internet Systems Consortium Inc.<br/><a href="http://www.isc.org">http://www.isc.org</a></p> + </body> + </html> + </xsl:template> +</xsl:stylesheet> diff --git a/bin/named/builtin.c b/bin/named/builtin.c new file mode 100644 index 0000000..26348fd --- /dev/null +++ b/bin/named/builtin.c @@ -0,0 +1,650 @@ +/* + * 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 + * \brief + * The built-in "version", "hostname", "id", "authors" and "empty" databases. + */ + +#include <stdio.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/sdb.h> + +#include <named/builtin.h> +#include <named/globals.h> +#include <named/os.h> +#include <named/server.h> + +typedef struct builtin builtin_t; + +static isc_result_t +do_authors_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_dns64_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_empty_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_hostname_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_id_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_ipv4only_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_ipv4reverse_lookup(dns_sdblookup_t *lookup); +static isc_result_t +do_version_lookup(dns_sdblookup_t *lookup); + +/* + * We can't use function pointers as the db_data directly + * because ANSI C does not guarantee that function pointers + * can safely be cast to void pointers and back. + */ + +struct builtin { + isc_result_t (*do_lookup)(dns_sdblookup_t *lookup); + char *server; + char *contact; +}; + +static builtin_t authors_builtin = { do_authors_lookup, NULL, NULL }; +static builtin_t dns64_builtin = { do_dns64_lookup, NULL, NULL }; +static builtin_t empty_builtin = { do_empty_lookup, NULL, NULL }; +static builtin_t hostname_builtin = { do_hostname_lookup, NULL, NULL }; +static builtin_t id_builtin = { do_id_lookup, NULL, NULL }; +static builtin_t ipv4only_builtin = { do_ipv4only_lookup, NULL, NULL }; +static builtin_t ipv4reverse_builtin = { do_ipv4reverse_lookup, NULL, NULL }; +static builtin_t version_builtin = { do_version_lookup, NULL, NULL }; + +static dns_sdbimplementation_t *builtin_impl; +static dns_sdbimplementation_t *dns64_impl; + +/* + * Pre computed HEX * 16 or 1 table. + */ +static const unsigned char hex16[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*00*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*10*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*20*/ + 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 1, 1, 1, 1, 1, 1, /*30*/ + 1, 160, 176, 192, 208, 224, 240, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*40*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*50*/ + 1, 160, 176, 192, 208, 224, 240, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*60*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*70*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*80*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*90*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*A0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*B0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*C0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*D0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*E0*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /*F0*/ +}; + +static const unsigned char decimal[] = "0123456789"; +static const unsigned char ipv4only[] = "\010ipv4only\004arpa"; + +static size_t +dns64_rdata(unsigned char *v, size_t start, unsigned char *rdata) { + size_t i, j = 0; + + for (i = 0; i < 4U; i++) { + unsigned char c = v[start++]; + if (start == 7U) { + start++; + } + if (c > 99) { + rdata[j++] = 3; + rdata[j++] = decimal[c / 100]; + c = c % 100; + rdata[j++] = decimal[c / 10]; + c = c % 10; + rdata[j++] = decimal[c]; + } else if (c > 9) { + rdata[j++] = 2; + rdata[j++] = decimal[c / 10]; + c = c % 10; + rdata[j++] = decimal[c]; + } else { + rdata[j++] = 1; + rdata[j++] = decimal[c]; + } + } + memmove(&rdata[j], "\07in-addr\04arpa", 14); + return (j + 14); +} + +static isc_result_t +dns64_cname(const dns_name_t *zone, const dns_name_t *name, + dns_sdblookup_t *lookup) { + size_t zlen, nlen, j, len; + unsigned char v[16], n; + unsigned int i; + unsigned char rdata[sizeof("123.123.123.123.in-addr.arpa.")]; + unsigned char *ndata; + + /* + * The combined length of the zone and name is 74. + * + * The minimum zone length is 10 ((3)ip6(4)arpa(0)). + * + * The length of name should always be even as we are expecting + * a series of nibbles. + */ + zlen = zone->length; + nlen = name->length; + if ((zlen + nlen) > 74U || zlen < 10U || (nlen % 2) != 0U) { + return (ISC_R_NOTFOUND); + } + + /* + * We assume the zone name is well formed. + */ + + /* + * XXXMPA We could check the dns64 suffix here if we need to. + */ + /* + * Check that name is a series of nibbles. + * Compute the byte values that correspond to the nibbles as we go. + * + * Shift the final result 4 bits, by setting 'i' to 1, if we if we + * have a odd number of nibbles so that "must be zero" tests below + * are byte aligned and we correctly return ISC_R_NOTFOUND or + * ISC_R_SUCCESS. We will not generate a CNAME in this case. + */ + ndata = name->ndata; + i = (nlen % 4) == 2U ? 1 : 0; + j = nlen; + memset(v, 0, sizeof(v)); + while (j != 0U) { + INSIST((i / 2) < sizeof(v)); + if (ndata[0] != 1) { + return (ISC_R_NOTFOUND); + } + n = hex16[ndata[1] & 0xff]; + if (n == 1) { + return (ISC_R_NOTFOUND); + } + v[i / 2] = n | (v[i / 2] >> 4); + j -= 2; + ndata += 2; + i++; + } + + /* + * If we get here then we know name only consisted of nibbles. + * Now we need to determine if the name exists or not and whether + * it corresponds to a empty node in the zone or there should be + * a CNAME. + */ +#define ZLEN(x) (10 + (x) / 2) + switch (zlen) { + case ZLEN(32): /* prefix len 32 */ + /* + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (nlen > 16U && v[(nlen - 1) / 4 - 4] != 0) { + return (ISC_R_NOTFOUND); + } + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 8, rdata); + break; + case ZLEN(40): /* prefix len 40 */ + /* + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (nlen > 12U && v[(nlen - 1) / 4 - 3] != 0) { + return (ISC_R_NOTFOUND); + } + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 6, rdata); + break; + case ZLEN(48): /* prefix len 48 */ + /* + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (nlen > 8U && v[(nlen - 1) / 4 - 2] != 0) { + return (ISC_R_NOTFOUND); + } + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 5, rdata); + break; + case ZLEN(56): /* prefix len 56 */ + /* + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (nlen > 4U && v[(nlen - 1) / 4 - 1] != 0) { + return (ISC_R_NOTFOUND); + } + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 4, rdata); + break; + case ZLEN(64): /* prefix len 64 */ + /* + * The nibbles that map to this byte must be zero for 'name' + * to exist in the zone. + */ + if (v[(nlen - 1) / 4] != 0) { + return (ISC_R_NOTFOUND); + } + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 3, rdata); + break; + case ZLEN(96): /* prefix len 96 */ + /* + * If the total length is not 74 then this is a empty node + * so return success. + */ + if (nlen + zlen != 74U) { + return (ISC_R_SUCCESS); + } + len = dns64_rdata(v, 0, rdata); + break; + default: + /* + * This should never be reached unless someone adds a + * zone declaration with this internal type to named.conf. + */ + return (ISC_R_NOTFOUND); + } + + /* + * Reverse of 192.0.0.170 or 192.0.0.171 maps to ipv4only.arpa. + */ + if ((v[0] == 170 || v[0] == 171) && v[1] == 0 && v[2] == 0 && + v[3] == 192) + { + return (dns_sdb_putrdata(lookup, dns_rdatatype_ptr, 3600, + ipv4only, sizeof(ipv4only))); + } + + return (dns_sdb_putrdata(lookup, dns_rdatatype_cname, 600, rdata, + (unsigned int)len)); +} + +static isc_result_t +builtin_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + builtin_t *b = (builtin_t *)dbdata; + + UNUSED(zone); + UNUSED(methods); + UNUSED(clientinfo); + + if (strcmp(name, "@") == 0) { + return (b->do_lookup(lookup)); + } else { + return (ISC_R_NOTFOUND); + } +} + +static isc_result_t +dns64_lookup(const dns_name_t *zone, const dns_name_t *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + builtin_t *b = (builtin_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + if (name->labels == 0 && name->length == 0) { + return (b->do_lookup(lookup)); + } else { + return (dns64_cname(zone, name, lookup)); + } +} + +static isc_result_t +put_txt(dns_sdblookup_t *lookup, const char *text) { + unsigned char buf[256]; + unsigned int len = strlen(text); + if (len > 255) { + len = 255; /* Silently truncate */ + } + buf[0] = len; + memmove(&buf[1], text, len); + return (dns_sdb_putrdata(lookup, dns_rdatatype_txt, 0, buf, len + 1)); +} + +static isc_result_t +do_version_lookup(dns_sdblookup_t *lookup) { + if (named_g_server->version_set) { + if (named_g_server->version == NULL) { + return (ISC_R_SUCCESS); + } else { + return (put_txt(lookup, named_g_server->version)); + } + } else { + return (put_txt(lookup, PACKAGE_VERSION)); + } +} + +static isc_result_t +do_hostname_lookup(dns_sdblookup_t *lookup) { + if (named_g_server->hostname_set) { + if (named_g_server->hostname == NULL) { + return (ISC_R_SUCCESS); + } else { + return (put_txt(lookup, named_g_server->hostname)); + } + } else { + char buf[256]; + if (gethostname(buf, sizeof(buf)) != 0) { + return (ISC_R_FAILURE); + } + return (put_txt(lookup, buf)); + } +} + +static isc_result_t +do_authors_lookup(dns_sdblookup_t *lookup) { + isc_result_t result; + const char **p; + static const char *authors[] = { + "Mark Andrews", "Curtis Blackburn", "James Brister", + "Ben Cottrell", "John H. DuBois III", "Francis Dupont", + "Michael Graff", "Andreas Gustafsson", "Bob Halley", + "Evan Hunt", "JINMEI Tatuya", "Witold Krecicki", + "David Lawrence", "Scott Mann", "Danny Mayer", + "Damien Neil", "Matt Nelson", "Jeremy C. Reed", + "Michael Sawyer", "Brian Wellington", NULL + }; + + /* + * If a version string is specified, disable the authors.bind zone. + */ + if (named_g_server->version_set) { + return (ISC_R_SUCCESS); + } + + for (p = authors; *p != NULL; p++) { + result = put_txt(lookup, *p); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +do_id_lookup(dns_sdblookup_t *lookup) { + if (named_g_server->sctx->usehostname) { + char buf[256]; + if (gethostname(buf, sizeof(buf)) != 0) { + return (ISC_R_FAILURE); + } + return (put_txt(lookup, buf)); + } else if (named_g_server->sctx->server_id != NULL) { + return (put_txt(lookup, named_g_server->sctx->server_id)); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +do_dns64_lookup(dns_sdblookup_t *lookup) { + UNUSED(lookup); + return (ISC_R_SUCCESS); +} + +static isc_result_t +do_empty_lookup(dns_sdblookup_t *lookup) { + UNUSED(lookup); + return (ISC_R_SUCCESS); +} + +static isc_result_t +do_ipv4only_lookup(dns_sdblookup_t *lookup) { + isc_result_t result; + unsigned char data[2][4] = { { 192, 0, 0, 170 }, { 192, 0, 0, 171 } }; + + for (int i = 0; i < 2; i++) { + result = dns_sdb_putrdata(lookup, dns_rdatatype_a, 3600, + data[i], 4); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +do_ipv4reverse_lookup(dns_sdblookup_t *lookup) { + isc_result_t result; + + result = dns_sdb_putrdata(lookup, dns_rdatatype_ptr, 3600, ipv4only, + sizeof(ipv4only)); + return (result); +} + +static isc_result_t +builtin_authority(const char *zone, void *dbdata, dns_sdblookup_t *lookup) { + isc_result_t result; + const char *contact = "hostmaster"; + const char *server = "@"; + builtin_t *b = (builtin_t *)dbdata; + + UNUSED(zone); + UNUSED(dbdata); + + if (b == &empty_builtin) { + server = "."; + contact = "."; + } else { + if (b->server != NULL) { + server = b->server; + } + if (b->contact != NULL) { + contact = b->contact; + } + } + + result = dns_sdb_putsoa(lookup, server, contact, 0); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + result = dns_sdb_putrr(lookup, "ns", 0, server); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +builtin_create(const char *zone, int argc, char **argv, void *driverdata, + void **dbdata) { + REQUIRE(argc >= 1); + + UNUSED(zone); + UNUSED(driverdata); + + if (strcmp(argv[0], "dns64") == 0 || strcmp(argv[0], "empty") == 0 || + strcmp(argv[0], "ipv4only") == 0 || + strcmp(argv[0], "ipv4reverse") == 0) + { + if (argc != 3) { + return (DNS_R_SYNTAX); + } + } else if (argc != 1) { + return (DNS_R_SYNTAX); + } + + if (strcmp(argv[0], "authors") == 0) { + *dbdata = &authors_builtin; + } else if (strcmp(argv[0], "hostname") == 0) { + *dbdata = &hostname_builtin; + } else if (strcmp(argv[0], "id") == 0) { + *dbdata = &id_builtin; + } else if (strcmp(argv[0], "version") == 0) { + *dbdata = &version_builtin; + } else if (strcmp(argv[0], "dns64") == 0 || + strcmp(argv[0], "empty") == 0 || + strcmp(argv[0], "ipv4only") == 0 || + strcmp(argv[0], "ipv4reverse") == 0) + { + builtin_t *empty; + char *server; + char *contact; + + if (argc != 3) { + return (DNS_R_SYNTAX); + } + + /* + * We don't want built-in zones to fail. Fallback to + * the static configuration if memory allocation fails. + */ + empty = isc_mem_get(named_g_mctx, sizeof(*empty)); + server = isc_mem_strdup(named_g_mctx, argv[1]); + contact = isc_mem_strdup(named_g_mctx, argv[2]); + if (empty == NULL || server == NULL || contact == NULL) { + if (strcmp(argv[0], "dns64") == 0) { + *dbdata = &dns64_builtin; + } else if (strcmp(argv[0], "empty") == 0) { + *dbdata = &empty_builtin; + } else if (strcmp(argv[0], "ipv4only") == 0) { + *dbdata = &ipv4only_builtin; + } else { + *dbdata = &ipv4reverse_builtin; + } + if (server != NULL) { + isc_mem_free(named_g_mctx, server); + } + if (contact != NULL) { + isc_mem_free(named_g_mctx, contact); + } + if (empty != NULL) { + isc_mem_put(named_g_mctx, empty, + sizeof(*empty)); + } + } else { + if (strcmp(argv[0], "dns64") == 0) { + memmove(empty, &dns64_builtin, + sizeof(empty_builtin)); + } else if (strcmp(argv[0], "empty") == 0) { + memmove(empty, &empty_builtin, + sizeof(empty_builtin)); + } else if (strcmp(argv[0], "ipv4only") == 0) { + memmove(empty, &ipv4only_builtin, + sizeof(empty_builtin)); + } else { + memmove(empty, &ipv4reverse_builtin, + sizeof(empty_builtin)); + } + empty->server = server; + empty->contact = contact; + *dbdata = empty; + } + } else { + return (ISC_R_NOTIMPLEMENTED); + } + return (ISC_R_SUCCESS); +} + +static void +builtin_destroy(const char *zone, void *driverdata, void **dbdata) { + builtin_t *b = (builtin_t *)*dbdata; + + UNUSED(zone); + UNUSED(driverdata); + + /* + * Don't free the static versions. + */ + if (*dbdata == &authors_builtin || *dbdata == &dns64_builtin || + *dbdata == &empty_builtin || *dbdata == &hostname_builtin || + *dbdata == &id_builtin || *dbdata == &ipv4only_builtin || + *dbdata == &ipv4reverse_builtin || *dbdata == &version_builtin) + { + return; + } + + isc_mem_free(named_g_mctx, b->server); + isc_mem_free(named_g_mctx, b->contact); + isc_mem_put(named_g_mctx, b, sizeof(*b)); +} + +static dns_sdbmethods_t builtin_methods = { + builtin_lookup, builtin_authority, NULL, /* allnodes */ + builtin_create, builtin_destroy, NULL +}; + +static dns_sdbmethods_t dns64_methods = { + NULL, builtin_authority, NULL, /* allnodes */ + builtin_create, builtin_destroy, dns64_lookup, +}; + +isc_result_t +named_builtin_init(void) { + RUNTIME_CHECK(dns_sdb_register("_builtin", &builtin_methods, NULL, + DNS_SDBFLAG_RELATIVEOWNER | + DNS_SDBFLAG_RELATIVERDATA, + named_g_mctx, + &builtin_impl) == ISC_R_SUCCESS); + RUNTIME_CHECK(dns_sdb_register("_dns64", &dns64_methods, NULL, + DNS_SDBFLAG_RELATIVEOWNER | + DNS_SDBFLAG_RELATIVERDATA | + DNS_SDBFLAG_DNS64, + named_g_mctx, + &dns64_impl) == ISC_R_SUCCESS); + return (ISC_R_SUCCESS); +} + +void +named_builtin_deinit(void) { + dns_sdb_unregister(&builtin_impl); + dns_sdb_unregister(&dns64_impl); +} diff --git a/bin/named/config.c b/bin/named/config.c new file mode 100644 index 0000000..7f318a2 --- /dev/null +++ b/bin/named/config.c @@ -0,0 +1,1034 @@ +/* + * 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 <bind.keys.h> +#include <inttypes.h> +#include <stdlib.h> + +#include <isc/buffer.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/parseint.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/fixedname.h> +#include <dns/kasp.h> +#include <dns/name.h> +#include <dns/rdataclass.h> +#include <dns/rdatatype.h> +#include <dns/tsig.h> +#include <dns/zone.h> + +#include <dst/dst.h> + +#include <isccfg/grammar.h> +#include <isccfg/namedconf.h> + +#include <named/config.h> +#include <named/globals.h> + +/*% default configuration */ +static char defaultconf[] = "\ +options {\n\ + answer-cookie true;\n\ + automatic-interface-scan yes;\n\ + bindkeys-file \"" NAMED_SYSCONFDIR "/bind.keys\";\n\ +# blackhole {none;};\n" + " cookie-algorithm siphash24;\n" + " coresize default;\n\ + datasize default;\n" + "\ +# directory <none>\n\ + dnssec-policy \"none\";\n\ + dump-file \"named_dump.db\";\n\ + edns-udp-size 1232;\n\ + files unlimited;\n" +#if defined(HAVE_GEOIP2) + "\ + geoip-directory \"" MAXMINDDB_PREFIX "/share/GeoIP\";\n" +#elif defined(HAVE_GEOIP2) + "\ + geoip-directory \".\";\n" +#endif /* if defined(HAVE_GEOIP2) */ + "\ + heartbeat-interval 60;\n\ + interface-interval 60;\n\ +# keep-response-order {none;};\n\ + listen-on {any;};\n\ + listen-on-v6 {any;};\n\ +# lock-file \"" NAMED_LOCALSTATEDIR "/run/named/named.lock\";\n\ + match-mapped-addresses no;\n\ + max-ixfr-ratio 100%;\n\ + max-rsa-exponent-size 0; /* no limit */\n\ + max-udp-size 1232;\n\ + memstatistics-file \"named.memstats\";\n\ + nocookie-udp-size 4096;\n\ + notify-rate 20;\n\ + nta-lifetime 3600;\n\ + nta-recheck 300;\n\ +# pid-file \"" NAMED_LOCALSTATEDIR "/run/named/named.pid\"; \n\ + port 53;\n" +#if HAVE_SO_REUSEPORT_LB + "\ + reuseport yes;\n" +#else + "\ + reuseport no;\n" +#endif + "\ + tls-port 853;\n" +#if HAVE_LIBNGHTTP2 + "\ + http-port 80;\n\ + https-port 443;\n\ + http-listener-clients 300;\n\ + http-streams-per-connection 100;\n" +#endif + "\ + prefetch 2 9;\n\ + recursing-file \"named.recursing\";\n\ + recursive-clients 1000;\n\ + request-nsid false;\n\ + reserved-sockets 512;\n\ + resolver-query-timeout 10;\n\ + rrset-order { order random; };\n\ + secroots-file \"named.secroots\";\n\ + send-cookie true;\n\ + serial-query-rate 20;\n\ + server-id none;\n\ + session-keyalg hmac-sha256;\n\ +# session-keyfile \"" NAMED_LOCALSTATEDIR "/run/named/session.key\";\n\ + session-keyname local-ddns;\n\ + stacksize default;\n\ + startup-notify-rate 20;\n\ + statistics-file \"named.stats\";\n\ + tcp-advertised-timeout 300;\n\ + tcp-clients 150;\n\ + tcp-idle-timeout 300;\n\ + tcp-initial-timeout 300;\n\ + tcp-keepalive-timeout 300;\n\ + tcp-listen-queue 10;\n\ + tcp-receive-buffer 0;\n\ + tcp-send-buffer 0;\n\ +# tkey-dhkey <none>\n\ +# tkey-domain <none>\n\ +# tkey-gssapi-credential <none>\n\ + transfer-message-size 20480;\n\ + transfers-in 10;\n\ + transfers-out 10;\n\ + transfers-per-ns 2;\n\ + trust-anchor-telemetry yes;\n\ + udp-receive-buffer 0;\n\ + udp-send-buffer 0;\n\ + update-quota 100;\n\ +\n\ + /* view */\n\ + allow-new-zones no;\n\ + allow-notify {none;};\n\ + allow-query-cache { localnets; localhost; };\n\ + allow-query-cache-on { any; };\n\ + allow-recursion { localnets; localhost; };\n\ + allow-recursion-on { any; };\n\ + allow-update-forwarding {none;};\n\ + auth-nxdomain false;\n\ + check-dup-records warn;\n\ + check-mx warn;\n\ + check-names primary fail;\n\ + check-names response ignore;\n\ + check-names secondary warn;\n\ + check-spf warn;\n\ + clients-per-query 10;\n\ + dnssec-accept-expired no;\n\ + dnssec-validation " VALIDATION_DEFAULT "; \n" +#ifdef HAVE_DNSTAP + " dnstap-identity hostname;\n" +#endif /* ifdef HAVE_DNSTAP */ + "\ + fetch-quota-params 100 0.1 0.3 0.7;\n\ + fetches-per-server 0;\n\ + fetches-per-zone 0;\n\ + glue-cache yes;\n\ + lame-ttl 0;\n" +#ifdef HAVE_LMDB + " lmdb-mapsize 32M;\n" +#endif /* ifdef HAVE_LMDB */ + " max-cache-size 90%;\n\ + max-cache-ttl 604800; /* 1 week */\n\ + max-clients-per-query 100;\n\ + max-ncache-ttl 10800; /* 3 hours */\n\ + max-recursion-depth 7;\n\ + max-recursion-queries 100;\n\ + max-stale-ttl 86400; /* 1 day */\n\ + message-compression yes;\n\ + min-ncache-ttl 0; /* 0 hours */\n\ + min-cache-ttl 0; /* 0 seconds */\n\ + minimal-any false;\n\ + minimal-responses no-auth-recursive;\n\ + notify-source *;\n\ + notify-source-v6 *;\n\ + nsec3-test-zone no;\n\ + parental-source *;\n\ + parental-source-v6 *;\n\ + provide-ixfr true;\n\ + qname-minimization relaxed;\n\ + query-source address *;\n\ + query-source-v6 address *;\n\ + recursion true;\n\ + request-expire true;\n\ + request-ixfr true;\n\ + require-server-cookie no;\n\ + resolver-nonbackoff-tries 3;\n\ + resolver-retry-interval 800; /* in milliseconds */\n\ + root-key-sentinel yes;\n\ + servfail-ttl 1;\n\ +# sortlist <none>\n\ + stale-answer-client-timeout off;\n\ + stale-answer-enable false;\n\ + stale-answer-ttl 30; /* 30 seconds */\n\ + stale-cache-enable false;\n\ + stale-refresh-time 30; /* 30 seconds */\n\ + synth-from-dnssec yes;\n\ +# topology <none>\n\ + transfer-format many-answers;\n\ + v6-bias 50;\n\ + zero-no-soa-ttl-cache no;\n\ +\n\ + /* zone */\n\ + allow-query {any;};\n\ + allow-query-on {any;};\n\ + allow-transfer {any;};\n\ +# also-notify <none>\n\ + alt-transfer-source *;\n\ + alt-transfer-source-v6 *;\n\ + check-integrity yes;\n\ + check-mx-cname warn;\n\ + check-sibling yes;\n\ + check-srv-cname warn;\n\ + check-wildcard yes;\n\ + dialup no;\n\ + dnssec-dnskey-kskonly yes;\n\ + dnssec-loadkeys-interval 60;\n\ + dnssec-secure-to-insecure no;\n\ + dnssec-update-mode maintain;\n\ +# forward <none>\n\ +# forwarders <none>\n\ +# inline-signing no;\n\ + ixfr-from-differences false;\n\ + max-journal-size default;\n\ + max-records 0;\n\ + max-refresh-time 2419200; /* 4 weeks */\n\ + max-retry-time 1209600; /* 2 weeks */\n\ + max-transfer-idle-in 60;\n\ + max-transfer-idle-out 60;\n\ + max-transfer-time-in 120;\n\ + max-transfer-time-out 120;\n\ + min-refresh-time 300;\n\ + min-retry-time 500;\n\ + multi-master no;\n\ + notify yes;\n\ + notify-delay 5;\n\ + notify-to-soa no;\n\ + serial-update-method increment;\n\ + sig-signing-nodes 100;\n\ + sig-signing-signatures 10;\n\ + sig-signing-type 65534;\n\ + sig-validity-interval 30; /* days */\n\ + dnskey-sig-validity 0; /* default: sig-validity-interval */\n\ + transfer-source *;\n\ + transfer-source-v6 *;\n\ + try-tcp-refresh yes; /* BIND 8 compat */\n\ + update-check-ksk yes;\n\ + zero-no-soa-ttl yes;\n\ + zone-statistics terse;\n\ +};\n\ +" + + "#\n\ +# Zones in the \"_bind\" view are NOT counted in the count of zones.\n\ +#\n\ +view \"_bind\" chaos {\n\ + recursion no;\n\ + notify no;\n\ + allow-new-zones no;\n\ + max-cache-size 2M;\n\ +\n\ + # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ + rate-limit {\n\ + responses-per-second 3;\n\ + slip 0;\n\ + min-table-size 10;\n\ + };\n\ +\n\ + zone \"version.bind\" chaos {\n\ + type primary;\n\ + database \"_builtin version\";\n\ + };\n\ +\n\ + zone \"hostname.bind\" chaos {\n\ + type primary;\n\ + database \"_builtin hostname\";\n\ + };\n\ +\n\ + zone \"authors.bind\" chaos {\n\ + type primary;\n\ + database \"_builtin authors\";\n\ + };\n\ +\n\ + zone \"id.server\" chaos {\n\ + type primary;\n\ + database \"_builtin id\";\n\ + };\n\ +};\n\ +" + "#\n\ +# Built-in DNSSEC key and signing policies.\n\ +#\n\ +dnssec-policy \"default\" {\n\ + keys {\n\ + csk key-directory lifetime unlimited algorithm 13;\n\ + };\n\ +\n\ + dnskey-ttl " DNS_KASP_KEY_TTL ";\n\ + publish-safety " DNS_KASP_PUBLISH_SAFETY "; \n\ + retire-safety " DNS_KASP_RETIRE_SAFETY "; \n\ + purge-keys " DNS_KASP_PURGE_KEYS "; \n\ + signatures-refresh " DNS_KASP_SIG_REFRESH "; \n\ + signatures-validity " DNS_KASP_SIG_VALIDITY "; \n\ + signatures-validity-dnskey " DNS_KASP_SIG_VALIDITY_DNSKEY "; \n\ + max-zone-ttl " DNS_KASP_ZONE_MAXTTL "; \n\ + zone-propagation-delay " DNS_KASP_ZONE_PROPDELAY "; \n\ + parent-ds-ttl " DNS_KASP_DS_TTL "; \n\ + parent-propagation-delay " DNS_KASP_PARENT_PROPDELAY "; \n\ +};\n\ +\n\ +dnssec-policy \"insecure\" {\n\ + max-zone-ttl 0; \n\ + keys { };\n\ +};\n\ +\n\ +" + "#\n\ +# Default trusted key(s), used if \n\ +# \"dnssec-validation auto;\" is set and\n\ +# " NAMED_SYSCONFDIR "/bind.keys doesn't exist).\n\ +#\n\ +# BEGIN TRUST ANCHORS\n" + + /* Imported from bind.keys.h: */ + TRUST_ANCHORS + + "# END TRUST ANCHORS\n\ +\n\ +primaries " DEFAULT_IANA_ROOT_ZONE_PRIMARIES " {\n\ + 2001:500:200::b; # b.root-servers.net\n\ + 2001:500:2::c; # c.root-servers.net\n\ + 2001:500:2f::f; # f.root-servers.net\n\ + 2001:500:12::d0d; # g.root-servers.net\n\ + 2001:7fd::1; # k.root-servers.net\n\ + 2620:0:2830:202::132; # xfr.cjr.dns.icann.org\n\ + 2620:0:2d0:202::132; # xfr.lax.dns.icann.org\n\ + 199.9.14.201; # b.root-servers.net\n\ + 192.33.4.12; # c.root-servers.net\n\ + 192.5.5.241; # f.root-servers.net\n\ + 192.112.36.4; # g.root-servers.net\n\ + 193.0.14.129; # k.root-servers.net\n\ + 192.0.47.132; # xfr.cjr.dns.icann.org\n\ + 192.0.32.132; # xfr.lax.dns.icann.org\n\ +};\n\ +"; + +isc_result_t +named_config_parsedefaults(cfg_parser_t *parser, cfg_obj_t **conf) { + isc_buffer_t b; + + isc_buffer_init(&b, defaultconf, sizeof(defaultconf) - 1); + isc_buffer_add(&b, sizeof(defaultconf) - 1); + return (cfg_parse_buffer(parser, &b, __FILE__, 0, &cfg_type_namedconf, + CFG_PCTX_NODEPRECATED, conf)); +} + +const char * +named_config_getdefault(void) { + return (defaultconf); +} + +isc_result_t +named_config_get(cfg_obj_t const *const *maps, const char *name, + const cfg_obj_t **obj) { + int i; + + for (i = 0; maps[i] != NULL; i++) { + if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +isc_result_t +named_checknames_get(const cfg_obj_t **maps, const char *const names[], + const cfg_obj_t **obj) { + const cfg_listelt_t *element; + const cfg_obj_t *checknames; + const cfg_obj_t *type; + const cfg_obj_t *value; + int i; + + REQUIRE(maps != NULL); + REQUIRE(names != NULL); + REQUIRE(obj != NULL && *obj == NULL); + + for (i = 0; maps[i] != NULL; i++) { + checknames = NULL; + if (cfg_map_get(maps[i], "check-names", &checknames) == + ISC_R_SUCCESS) + { + /* + * Zone map entry is not a list. + */ + if (checknames != NULL && !cfg_obj_islist(checknames)) { + *obj = checknames; + return (ISC_R_SUCCESS); + } + for (element = cfg_list_first(checknames); + element != NULL; element = cfg_list_next(element)) + { + value = cfg_listelt_value(element); + type = cfg_tuple_get(value, "type"); + + for (size_t j = 0; names[j] != NULL; j++) { + if (strcasecmp(cfg_obj_asstring(type), + names[j]) == 0) + { + *obj = cfg_tuple_get(value, + "mode"); + return (ISC_R_SUCCESS); + } + } + } + } + } + return (ISC_R_NOTFOUND); +} + +int +named_config_listcount(const cfg_obj_t *list) { + const cfg_listelt_t *e; + int i = 0; + + for (e = cfg_list_first(list); e != NULL; e = cfg_list_next(e)) { + i++; + } + + return (i); +} + +isc_result_t +named_config_getclass(const cfg_obj_t *classobj, dns_rdataclass_t defclass, + dns_rdataclass_t *classp) { + isc_textregion_t r; + isc_result_t result; + + if (!cfg_obj_isstring(classobj)) { + *classp = defclass; + return (ISC_R_SUCCESS); + } + DE_CONST(cfg_obj_asstring(classobj), r.base); + r.length = strlen(r.base); + result = dns_rdataclass_fromtext(classp, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(classobj, named_g_lctx, ISC_LOG_ERROR, + "unknown class '%s'", r.base); + } + return (result); +} + +isc_result_t +named_config_gettype(const cfg_obj_t *typeobj, dns_rdatatype_t deftype, + dns_rdatatype_t *typep) { + isc_textregion_t r; + isc_result_t result; + + if (!cfg_obj_isstring(typeobj)) { + *typep = deftype; + return (ISC_R_SUCCESS); + } + DE_CONST(cfg_obj_asstring(typeobj), r.base); + r.length = strlen(r.base); + result = dns_rdatatype_fromtext(typep, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(typeobj, named_g_lctx, ISC_LOG_ERROR, + "unknown type '%s'", r.base); + } + return (result); +} + +dns_zonetype_t +named_config_getzonetype(const cfg_obj_t *zonetypeobj) { + dns_zonetype_t ztype = dns_zone_none; + const char *str; + + str = cfg_obj_asstring(zonetypeobj); + if (strcasecmp(str, "primary") == 0 || strcasecmp(str, "master") == 0) { + ztype = dns_zone_primary; + } else if (strcasecmp(str, "secondary") == 0 || + strcasecmp(str, "slave") == 0) + { + ztype = dns_zone_secondary; + } else if (strcasecmp(str, "mirror") == 0) { + ztype = dns_zone_mirror; + } else if (strcasecmp(str, "stub") == 0) { + ztype = dns_zone_stub; + } else if (strcasecmp(str, "static-stub") == 0) { + ztype = dns_zone_staticstub; + } else if (strcasecmp(str, "redirect") == 0) { + ztype = dns_zone_redirect; + } else { + UNREACHABLE(); + } + return (ztype); +} + +isc_result_t +named_config_getiplist(const cfg_obj_t *config, const cfg_obj_t *list, + in_port_t defport, isc_mem_t *mctx, + isc_sockaddr_t **addrsp, uint32_t *countp) { + int count, i = 0; + const cfg_obj_t *addrlist = NULL; + const cfg_obj_t *portobj = NULL; + const cfg_listelt_t *element = NULL; + isc_sockaddr_t *addrs = NULL; + in_port_t port; + isc_result_t result; + + INSIST(addrsp != NULL && *addrsp == NULL); + INSIST(countp != NULL); + + addrlist = cfg_tuple_get(list, "addresses"); + count = named_config_listcount(addrlist); + + portobj = cfg_tuple_get(list, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t)val; + } else if (defport != 0) { + port = defport; + } else { + result = named_config_getport(config, "port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + addrs = isc_mem_get(mctx, count * sizeof(isc_sockaddr_t)); + + for (element = cfg_list_first(addrlist); element != NULL; + element = cfg_list_next(element), i++) + { + const cfg_obj_t *addr; + INSIST(i < count); + addr = cfg_listelt_value(element); + addrs[i] = *cfg_obj_assockaddr(addr); + if (isc_sockaddr_getport(&addrs[i]) == 0) { + isc_sockaddr_setport(&addrs[i], port); + } + } + INSIST(i == count); + + *addrsp = addrs; + *countp = count; + + return (ISC_R_SUCCESS); +} + +void +named_config_putiplist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, + uint32_t count) { + INSIST(addrsp != NULL && *addrsp != NULL); + + isc_mem_put(mctx, *addrsp, count * sizeof(isc_sockaddr_t)); + *addrsp = NULL; +} + +static isc_result_t +getremotesdef(const cfg_obj_t *cctx, const char *list, const char *name, + const cfg_obj_t **ret) { + isc_result_t result; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *elt; + + REQUIRE(cctx != NULL); + REQUIRE(name != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + result = cfg_map_get(cctx, list, &obj); + if (result != ISC_R_SUCCESS) { + return (result); + } + elt = cfg_list_first(obj); + while (elt != NULL) { + obj = cfg_listelt_value(elt); + if (strcasecmp(cfg_obj_asstring(cfg_tuple_get(obj, "name")), + name) == 0) + { + *ret = obj; + return (ISC_R_SUCCESS); + } + elt = cfg_list_next(elt); + } + return (ISC_R_NOTFOUND); +} + +isc_result_t +named_config_getremotesdef(const cfg_obj_t *cctx, const char *list, + const char *name, const cfg_obj_t **ret) { + isc_result_t result; + + if (strcmp(list, "parental-agents") == 0) { + return (getremotesdef(cctx, list, name, ret)); + } else if (strcmp(list, "primaries") == 0) { + result = getremotesdef(cctx, list, name, ret); + if (result != ISC_R_SUCCESS) { + result = getremotesdef(cctx, "masters", name, ret); + } + return (result); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +named_config_getname(isc_mem_t *mctx, const cfg_obj_t *obj, + dns_name_t **namep) { + REQUIRE(namep != NULL && *namep == NULL); + + const char *objstr; + isc_result_t result; + isc_buffer_t b; + dns_fixedname_t fname; + + if (!cfg_obj_isstring(obj)) { + *namep = NULL; + return (ISC_R_SUCCESS); + } + + *namep = isc_mem_get(mctx, sizeof(**namep)); + dns_name_init(*namep, NULL); + + objstr = cfg_obj_asstring(obj); + isc_buffer_constinit(&b, objstr, strlen(objstr)); + isc_buffer_add(&b, strlen(objstr)); + dns_fixedname_init(&fname); + result = dns_name_fromtext(dns_fixedname_name(&fname), &b, dns_rootname, + 0, NULL); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, *namep, sizeof(**namep)); + *namep = NULL; + return (result); + } + dns_name_dup(dns_fixedname_name(&fname), mctx, *namep); + + return (ISC_R_SUCCESS); +} + +#define grow_array(mctx, array, newlen, oldlen) \ + if (newlen >= oldlen) { \ + size_t newsize = (newlen + 16) * sizeof(array[0]); \ + size_t oldsize = oldlen * sizeof(array[0]); \ + void *tmp = isc_mem_get(mctx, newsize); \ + memset(tmp, 0, newsize); \ + if (oldlen != 0) { \ + memmove(tmp, array, oldsize); \ + isc_mem_put(mctx, array, oldsize); \ + } \ + array = tmp; \ + oldlen = newlen + 16; \ + } + +#define shrink_array(mctx, array, newlen, oldlen) \ + if (newlen < oldlen) { \ + void *tmp = NULL; \ + size_t newsize = newlen * sizeof(array[0]); \ + size_t oldsize = oldlen * sizeof(array[0]); \ + if (newlen != 0) { \ + tmp = isc_mem_get(mctx, newsize); \ + memset(tmp, 0, newsize); \ + memmove(tmp, array, newsize); \ + } else { \ + tmp = NULL; \ + } \ + isc_mem_put(mctx, array, oldsize); \ + array = tmp; \ + oldlen = newlen; \ + } + +isc_result_t +named_config_getipandkeylist(const cfg_obj_t *config, const char *listtype, + const cfg_obj_t *list, isc_mem_t *mctx, + dns_ipkeylist_t *ipkl) { + uint32_t addrcount = 0, keycount = 0, tlscount = 0, i = 0; + uint32_t listcount = 0, l = 0, j; + uint32_t stackcount = 0, pushed = 0; + isc_result_t result; + const cfg_listelt_t *element; + const cfg_obj_t *addrlist; + const cfg_obj_t *portobj; + in_port_t port = (in_port_t)0; + in_port_t def_port; + in_port_t def_tlsport; + isc_sockaddr_t *addrs = NULL; + dns_name_t **keys = NULL; + dns_name_t **tlss = NULL; + struct { + const char *name; + in_port_t port; + isc_sockaddr_t *src4s; + isc_sockaddr_t *src6s; + } *lists = NULL; + struct { + const cfg_listelt_t *element; + in_port_t port; + } *stack = NULL; + + REQUIRE(ipkl != NULL); + REQUIRE(ipkl->count == 0); + REQUIRE(ipkl->addrs == NULL); + REQUIRE(ipkl->keys == NULL); + REQUIRE(ipkl->tlss == NULL); + REQUIRE(ipkl->labels == NULL); + REQUIRE(ipkl->allocated == 0); + + /* + * Get system defaults. + */ + result = named_config_getport(config, "port", &def_port); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = named_config_getport(config, "tls-port", &def_tlsport); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + +newlist: + addrlist = cfg_tuple_get(list, "addresses"); + portobj = cfg_tuple_get(list, "port"); + + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + result = ISC_R_RANGE; + goto cleanup; + } + port = (in_port_t)val; + } + + result = ISC_R_NOMEMORY; + + element = cfg_list_first(addrlist); +resume: + for (; element != NULL; element = cfg_list_next(element)) { + const cfg_obj_t *addr; + const cfg_obj_t *key; + const cfg_obj_t *tls; + + addr = cfg_tuple_get(cfg_listelt_value(element), + "remoteselement"); + key = cfg_tuple_get(cfg_listelt_value(element), "key"); + tls = cfg_tuple_get(cfg_listelt_value(element), "tls"); + + if (!cfg_obj_issockaddr(addr)) { + const char *listname = cfg_obj_asstring(addr); + isc_result_t tresult; + + /* Grow lists? */ + grow_array(mctx, lists, l, listcount); + + /* Seen? */ + for (j = 0; j < l; j++) { + if (strcasecmp(lists[j].name, listname) == 0) { + break; + } + } + if (j < l) { + continue; + } + list = NULL; + tresult = named_config_getremotesdef(config, listtype, + listname, &list); + if (tresult == ISC_R_NOTFOUND) { + cfg_obj_log(addr, named_g_lctx, ISC_LOG_ERROR, + "%s \"%s\" not found", listtype, + listname); + + result = tresult; + goto cleanup; + } + if (tresult != ISC_R_SUCCESS) { + goto cleanup; + } + lists[l++].name = listname; + /* Grow stack? */ + grow_array(mctx, stack, pushed, stackcount); + /* + * We want to resume processing this list on the + * next element. + */ + stack[pushed].element = cfg_list_next(element); + stack[pushed].port = port; + pushed++; + goto newlist; + } + + grow_array(mctx, addrs, i, addrcount); + grow_array(mctx, keys, i, keycount); + grow_array(mctx, tlss, i, tlscount); + + addrs[i] = *cfg_obj_assockaddr(addr); + + result = named_config_getname(mctx, key, &keys[i]); + if (result != ISC_R_SUCCESS) { + i++; /* Increment here so that cleanup on error works. + */ + goto cleanup; + } + + result = named_config_getname(mctx, tls, &tlss[i]); + if (result != ISC_R_SUCCESS) { + i++; /* Increment here so that cleanup on error works. + */ + goto cleanup; + } + + /* If the port is unset, take it from one of the upper levels */ + if (isc_sockaddr_getport(&addrs[i]) == 0) { + in_port_t addr_port = port; + + /* If unset, use the default port or tls-port */ + if (addr_port == 0) { + if (tlss[i] != NULL) { + addr_port = def_tlsport; + } else { + addr_port = def_port; + } + } + + isc_sockaddr_setport(&addrs[i], addr_port); + } + + i++; + } + if (pushed != 0) { + pushed--; + element = stack[pushed].element; + port = stack[pushed].port; + goto resume; + } + + shrink_array(mctx, addrs, i, addrcount); + shrink_array(mctx, keys, i, keycount); + shrink_array(mctx, tlss, i, tlscount); + + if (lists != NULL) { + isc_mem_put(mctx, lists, listcount * sizeof(lists[0])); + } + if (stack != NULL) { + isc_mem_put(mctx, stack, stackcount * sizeof(stack[0])); + } + + INSIST(keycount == addrcount); + INSIST(tlscount == addrcount); + + ipkl->addrs = addrs; + ipkl->keys = keys; + ipkl->tlss = tlss; + ipkl->count = addrcount; + ipkl->allocated = addrcount; + + return (ISC_R_SUCCESS); + +cleanup: + if (addrs != NULL) { + isc_mem_put(mctx, addrs, addrcount * sizeof(addrs[0])); + } + if (keys != NULL) { + for (j = 0; j < i; j++) { + if (keys[j] == NULL) { + continue; + } + if (dns_name_dynamic(keys[j])) { + dns_name_free(keys[j], mctx); + } + isc_mem_put(mctx, keys[j], sizeof(*keys[j])); + } + isc_mem_put(mctx, keys, keycount * sizeof(keys[0])); + } + if (tlss != NULL) { + for (j = 0; j < i; j++) { + if (tlss[j] == NULL) { + continue; + } + if (dns_name_dynamic(tlss[j])) { + dns_name_free(tlss[j], mctx); + } + isc_mem_put(mctx, tlss[j], sizeof(*tlss[j])); + } + isc_mem_put(mctx, tlss, tlscount * sizeof(tlss[0])); + } + if (lists != NULL) { + isc_mem_put(mctx, lists, listcount * sizeof(lists[0])); + } + if (stack != NULL) { + isc_mem_put(mctx, stack, stackcount * sizeof(stack[0])); + } + return (result); +} + +isc_result_t +named_config_getport(const cfg_obj_t *config, const char *type, + in_port_t *portp) { + const cfg_obj_t *maps[3]; + const cfg_obj_t *options = NULL; + const cfg_obj_t *portobj = NULL; + isc_result_t result; + int i; + + (void)cfg_map_get(config, "options", &options); + i = 0; + if (options != NULL) { + maps[i++] = options; + } + maps[i++] = named_g_defaults; + maps[i] = NULL; + + result = named_config_get(maps, type, &portobj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_asuint32(portobj) >= UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", + cfg_obj_asuint32(portobj)); + return (ISC_R_RANGE); + } + *portp = (in_port_t)cfg_obj_asuint32(portobj); + return (ISC_R_SUCCESS); +} + +struct keyalgorithms { + const char *str; + enum { + hmacnone, + hmacmd5, + hmacsha1, + hmacsha224, + hmacsha256, + hmacsha384, + hmacsha512 + } hmac; + unsigned int type; + uint16_t size; +} algorithms[] = { { "hmac-md5", hmacmd5, DST_ALG_HMACMD5, 128 }, + { "hmac-md5.sig-alg.reg.int", hmacmd5, DST_ALG_HMACMD5, 0 }, + { "hmac-md5.sig-alg.reg.int.", hmacmd5, DST_ALG_HMACMD5, 0 }, + { "hmac-sha1", hmacsha1, DST_ALG_HMACSHA1, 160 }, + { "hmac-sha224", hmacsha224, DST_ALG_HMACSHA224, 224 }, + { "hmac-sha256", hmacsha256, DST_ALG_HMACSHA256, 256 }, + { "hmac-sha384", hmacsha384, DST_ALG_HMACSHA384, 384 }, + { "hmac-sha512", hmacsha512, DST_ALG_HMACSHA512, 512 }, + { NULL, hmacnone, DST_ALG_UNKNOWN, 0 } }; + +isc_result_t +named_config_getkeyalgorithm(const char *str, const dns_name_t **name, + uint16_t *digestbits) { + return (named_config_getkeyalgorithm2(str, name, NULL, digestbits)); +} + +isc_result_t +named_config_getkeyalgorithm2(const char *str, const dns_name_t **name, + unsigned int *typep, uint16_t *digestbits) { + int i; + size_t len = 0; + uint16_t bits; + isc_result_t result; + + for (i = 0; algorithms[i].str != NULL; i++) { + len = strlen(algorithms[i].str); + if (strncasecmp(algorithms[i].str, str, len) == 0 && + (str[len] == '\0' || + (algorithms[i].size != 0 && str[len] == '-'))) + { + break; + } + } + if (algorithms[i].str == NULL) { + return (ISC_R_NOTFOUND); + } + if (str[len] == '-') { + result = isc_parse_uint16(&bits, str + len + 1, 10); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (bits > algorithms[i].size) { + return (ISC_R_RANGE); + } + } else if (algorithms[i].size == 0) { + bits = 128; + } else { + bits = algorithms[i].size; + } + + if (name != NULL) { + switch (algorithms[i].hmac) { + case hmacmd5: + *name = dns_tsig_hmacmd5_name; + break; + case hmacsha1: + *name = dns_tsig_hmacsha1_name; + break; + case hmacsha224: + *name = dns_tsig_hmacsha224_name; + break; + case hmacsha256: + *name = dns_tsig_hmacsha256_name; + break; + case hmacsha384: + *name = dns_tsig_hmacsha384_name; + break; + case hmacsha512: + *name = dns_tsig_hmacsha512_name; + break; + default: + UNREACHABLE(); + } + } + if (typep != NULL) { + *typep = algorithms[i].type; + } + if (digestbits != NULL) { + *digestbits = bits; + } + return (ISC_R_SUCCESS); +} diff --git a/bin/named/control.c b/bin/named/control.c new file mode 100644 index 0000000..454c128 --- /dev/null +++ b/bin/named/control.c @@ -0,0 +1,307 @@ +/* + * 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 <stdbool.h> + +#include <isc/app.h> +#include <isc/event.h> +#include <isc/lex.h> +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <isccc/alist.h> +#include <isccc/cc.h> + +#include <named/control.h> +#include <named/globals.h> +#include <named/log.h> +#include <named/os.h> +#include <named/server.h> +#ifdef HAVE_LIBSCF +#include <named/smf_globals.h> +#endif /* ifdef HAVE_LIBSCF */ + +static isc_result_t +getcommand(isc_lex_t *lex, char **cmdp) { + isc_result_t result; + isc_token_t token; + + REQUIRE(cmdp != NULL && *cmdp == NULL); + + result = isc_lex_gettoken(lex, ISC_LEXOPT_EOF, &token); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_lex_ungettoken(lex, &token); + + if (token.type != isc_tokentype_string) { + return (ISC_R_FAILURE); + } + + *cmdp = token.value.as_textregion.base; + + return (ISC_R_SUCCESS); +} + +static bool +command_compare(const char *str, const char *command) { + return (strcasecmp(str, command) == 0); +} + +/*% + * This function is called to process the incoming command + * when a control channel message is received. + */ +isc_result_t +named_control_docommand(isccc_sexpr_t *message, bool readonly, + isc_buffer_t **text) { + isccc_sexpr_t *data; + char *cmdline = NULL; + char *command = NULL; + isc_result_t result; + int log_level; + isc_buffer_t src; + isc_lex_t *lex = NULL; +#ifdef HAVE_LIBSCF + named_smf_want_disable = 0; +#endif /* ifdef HAVE_LIBSCF */ + + data = isccc_alist_lookup(message, "_data"); + if (!isccc_alist_alistp(data)) { + /* + * No data section. + */ + return (ISC_R_FAILURE); + } + + result = isccc_cc_lookupstring(data, "type", &cmdline); + if (result != ISC_R_SUCCESS) { + /* + * We have no idea what this is. + */ + return (result); + } + + result = isc_lex_create(named_g_mctx, strlen(cmdline), &lex); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_init(&src, cmdline, strlen(cmdline)); + isc_buffer_add(&src, strlen(cmdline)); + result = isc_lex_openbuffer(lex, &src); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = getcommand(lex, &command); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Compare the 'command' parameter against all known control commands. + */ + if ((command_compare(command, NAMED_COMMAND_NULL) && + strlen(cmdline) == 4) || + command_compare(command, NAMED_COMMAND_STATUS)) + { + log_level = ISC_LOG_DEBUG(1); + } else { + log_level = ISC_LOG_INFO; + } + + /* + * If this listener should have read-only access, reject + * restricted commands here. rndc nta is handled specially + * below. + */ + if (readonly && !command_compare(command, NAMED_COMMAND_NTA) && + !command_compare(command, NAMED_COMMAND_NULL) && + !command_compare(command, NAMED_COMMAND_STATUS) && + !command_compare(command, NAMED_COMMAND_SHOWZONE) && + !command_compare(command, NAMED_COMMAND_TESTGEN) && + !command_compare(command, NAMED_COMMAND_ZONESTATUS)) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, log_level, + "rejecting restricted control channel " + "command '%s'", + cmdline); + result = ISC_R_FAILURE; + goto cleanup; + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, log_level, + "received control channel command '%s'", cmdline); + + /* + * After the lengthy "halt" and "stop", the commands are + * handled in alphabetical order of the NAMED_COMMAND_ macros. + */ + if (command_compare(command, NAMED_COMMAND_HALT)) { +#ifdef HAVE_LIBSCF + /* + * If we are managed by smf(5), AND in chroot, then + * we cannot connect to the smf repository, so just + * return with an appropriate message back to rndc. + */ + if (named_smf_got_instance == 1 && named_smf_chroot == 1) { + result = named_smf_add_message(text); + goto cleanup; + } + /* + * If we are managed by smf(5) but not in chroot, + * try to disable ourselves the smf way. + */ + if (named_smf_got_instance == 1 && named_smf_chroot == 0) { + named_smf_want_disable = 1; + } + /* + * If named_smf_got_instance = 0, named_smf_chroot + * is not relevant and we fall through to + * isc_app_shutdown below. + */ +#endif /* ifdef HAVE_LIBSCF */ + /* Do not flush master files */ + named_server_flushonshutdown(named_g_server, false); + named_os_shutdownmsg(cmdline, *text); + result = ISC_R_SHUTTINGDOWN; + } else if (command_compare(command, NAMED_COMMAND_STOP)) { + /* + * "stop" is the same as "halt" except it does + * flush master files. + */ +#ifdef HAVE_LIBSCF + if (named_smf_got_instance == 1 && named_smf_chroot == 1) { + result = named_smf_add_message(text); + goto cleanup; + } + if (named_smf_got_instance == 1 && named_smf_chroot == 0) { + named_smf_want_disable = 1; + } +#endif /* ifdef HAVE_LIBSCF */ + named_server_flushonshutdown(named_g_server, true); + named_os_shutdownmsg(cmdline, *text); + result = ISC_R_SHUTTINGDOWN; + } else if (command_compare(command, NAMED_COMMAND_ADDZONE) || + command_compare(command, NAMED_COMMAND_MODZONE)) + { + result = named_server_changezone(named_g_server, cmdline, text); + } else if (command_compare(command, NAMED_COMMAND_DELZONE)) { + result = named_server_delzone(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_DNSSEC)) { + result = named_server_dnssec(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_DNSTAP) || + command_compare(command, NAMED_COMMAND_DNSTAPREOPEN)) + { + result = named_server_dnstap(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_DUMPDB)) { + named_server_dumpdb(named_g_server, lex, text); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NAMED_COMMAND_DUMPSTATS)) { + result = named_server_dumpstats(named_g_server); + } else if (command_compare(command, NAMED_COMMAND_FLUSH)) { + result = named_server_flushcache(named_g_server, lex); + } else if (command_compare(command, NAMED_COMMAND_FLUSHNAME)) { + result = named_server_flushnode(named_g_server, lex, false); + } else if (command_compare(command, NAMED_COMMAND_FLUSHTREE)) { + result = named_server_flushnode(named_g_server, lex, true); + } else if (command_compare(command, NAMED_COMMAND_FREEZE)) { + result = named_server_freeze(named_g_server, true, lex, text); + } else if (command_compare(command, NAMED_COMMAND_LOADKEYS) || + command_compare(command, NAMED_COMMAND_SIGN)) + { + result = named_server_rekey(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_MKEYS)) { + result = named_server_mkeys(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_NOTIFY)) { + result = named_server_notifycommand(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_NOTRACE)) { + named_g_debuglevel = 0; + isc_log_setdebuglevel(named_g_lctx, named_g_debuglevel); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NAMED_COMMAND_NTA)) { + result = named_server_nta(named_g_server, lex, readonly, text); + } else if (command_compare(command, NAMED_COMMAND_NULL)) { + result = ISC_R_SUCCESS; + } else if (command_compare(command, NAMED_COMMAND_QUERYLOG)) { + result = named_server_togglequerylog(named_g_server, lex); + } else if (command_compare(command, NAMED_COMMAND_RECONFIG)) { + result = named_server_reconfigcommand(named_g_server); + } else if (command_compare(command, NAMED_COMMAND_RECURSING)) { + result = named_server_dumprecursing(named_g_server); + } else if (command_compare(command, NAMED_COMMAND_REFRESH)) { + result = named_server_refreshcommand(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_RELOAD)) { + result = named_server_reloadcommand(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_RETRANSFER)) { + result = named_server_retransfercommand(named_g_server, lex, + text); + } else if (command_compare(command, NAMED_COMMAND_SCAN)) { + named_server_scan_interfaces(named_g_server); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NAMED_COMMAND_SECROOTS)) { + result = named_server_dumpsecroots(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_SERVESTALE)) { + result = named_server_servestale(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_SHOWZONE)) { + result = named_server_showzone(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_SIGNING)) { + result = named_server_signing(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_STATUS)) { + result = named_server_status(named_g_server, text); + } else if (command_compare(command, NAMED_COMMAND_SYNC)) { + result = named_server_sync(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_TCPTIMEOUTS)) { + result = named_server_tcptimeouts(lex, text); + } else if (command_compare(command, NAMED_COMMAND_TESTGEN)) { + result = named_server_testgen(lex, text); + } else if (command_compare(command, NAMED_COMMAND_THAW) || + command_compare(command, NAMED_COMMAND_UNFREEZE)) + { + result = named_server_freeze(named_g_server, false, lex, text); + } else if (command_compare(command, NAMED_COMMAND_TIMERPOKE)) { + isc_timermgr_poke(named_g_timermgr); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NAMED_COMMAND_TRACE)) { + result = named_server_setdebuglevel(named_g_server, lex); + } else if (command_compare(command, NAMED_COMMAND_TSIGDELETE)) { + result = named_server_tsigdelete(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_TSIGLIST)) { + result = named_server_tsiglist(named_g_server, text); + } else if (command_compare(command, NAMED_COMMAND_VALIDATION)) { + result = named_server_validation(named_g_server, lex, text); + } else if (command_compare(command, NAMED_COMMAND_ZONESTATUS)) { + result = named_server_zonestatus(named_g_server, lex, text); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "unknown control channel command '%s'", command); + result = DNS_R_UNKNOWNCOMMAND; + } + +cleanup: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + + return (result); +} diff --git a/bin/named/controlconf.c b/bin/named/controlconf.c new file mode 100644 index 0000000..d402155 --- /dev/null +++ b/bin/named/controlconf.c @@ -0,0 +1,1494 @@ +/* + * 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 <isc/app.h> +#include <isc/base64.h> +#include <isc/buffer.h> +#include <isc/event.h> +#include <isc/file.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/net.h> +#include <isc/netaddr.h> +#include <isc/netmgr.h> +#include <isc/nonce.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/stdtime.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/util.h> + +#include <isccc/alist.h> +#include <isccc/cc.h> +#include <isccc/ccmsg.h> +#include <isccc/events.h> +#include <isccc/sexpr.h> +#include <isccc/symtab.h> +#include <isccc/util.h> + +#include <isccfg/namedconf.h> + +#include <bind9/check.h> + +#include <named/config.h> +#include <named/control.h> +#include <named/log.h> +#include <named/server.h> + +typedef struct controlkey controlkey_t; +typedef ISC_LIST(controlkey_t) controlkeylist_t; + +typedef struct controlconnection controlconnection_t; +typedef ISC_LIST(controlconnection_t) controlconnectionlist_t; + +typedef struct controllistener controllistener_t; +typedef ISC_LIST(controllistener_t) controllistenerlist_t; + +struct controlkey { + char *keyname; + uint32_t algorithm; + isc_region_t secret; + ISC_LINK(controlkey_t) link; +}; + +struct controlconnection { + isc_nmhandle_t *readhandle; + isc_nmhandle_t *sendhandle; + isc_nmhandle_t *cmdhandle; + isccc_ccmsg_t ccmsg; + bool reading; + bool sending; + controllistener_t *listener; + isccc_sexpr_t *ctrl; + isc_buffer_t *buffer; + isc_buffer_t *text; + isccc_sexpr_t *request; + isccc_sexpr_t *response; + uint32_t alg; + isccc_region_t secret; + uint32_t nonce; + isc_stdtime_t now; + isc_result_t result; + ISC_LINK(controlconnection_t) link; +}; + +struct controllistener { + named_controls_t *controls; + isc_mem_t *mctx; + isc_sockaddr_t address; + isc_nmsocket_t *sock; + dns_acl_t *acl; + bool exiting; + isc_refcount_t refs; + controlkeylist_t keys; + isc_mutex_t connections_lock; + controlconnectionlist_t connections; + isc_socktype_t type; + uint32_t perm; + uint32_t owner; + uint32_t group; + bool readonly; + ISC_LINK(controllistener_t) link; +}; + +struct named_controls { + named_server_t *server; + controllistenerlist_t listeners; + atomic_bool shuttingdown; + isc_mutex_t symtab_lock; + isccc_symtab_t *symtab; +}; + +static isc_result_t +control_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg); +static void +control_recvmessage(isc_nmhandle_t *handle, isc_result_t result, void *arg); + +#define CLOCKSKEW 300 + +static void +free_controlkey(controlkey_t *key, isc_mem_t *mctx) { + if (key->keyname != NULL) { + isc_mem_free(mctx, key->keyname); + } + if (key->secret.base != NULL) { + isc_mem_put(mctx, key->secret.base, key->secret.length); + } + isc_mem_put(mctx, key, sizeof(*key)); +} + +static void +free_controlkeylist(controlkeylist_t *keylist, isc_mem_t *mctx) { + while (!ISC_LIST_EMPTY(*keylist)) { + controlkey_t *key = ISC_LIST_HEAD(*keylist); + ISC_LIST_UNLINK(*keylist, key, link); + free_controlkey(key, mctx); + } +} + +static void +free_listener(controllistener_t *listener) { + INSIST(listener->exiting); + INSIST(ISC_LIST_EMPTY(listener->connections)); + + isc_refcount_destroy(&listener->refs); + + if (listener->sock != NULL) { + isc_nmsocket_close(&listener->sock); + } + + free_controlkeylist(&listener->keys, listener->mctx); + + if (listener->acl != NULL) { + dns_acl_detach(&listener->acl); + } + isc_mutex_destroy(&listener->connections_lock); + + isc_mem_putanddetach(&listener->mctx, listener, sizeof(*listener)); +} + +static void +maybe_free_listener(controllistener_t *listener) { + if (isc_refcount_decrement(&listener->refs) == 1) { + free_listener(listener); + } +} + +static void +shutdown_listener(controllistener_t *listener) { + if (!listener->exiting) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + + ISC_LIST_UNLINK(listener->controls->listeners, listener, link); + + isc_sockaddr_format(&listener->address, socktext, + sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_NOTICE, + "stopping command channel on %s", socktext); +#if 0 + /* XXX: no unix domain socket support */ + if (listener->type == isc_socktype_unix) { + isc_socket_cleanunix(&listener->address, true); + } +#endif + listener->exiting = true; + } + + isc_nm_stoplistening(listener->sock); + maybe_free_listener(listener); +} + +static bool +address_ok(isc_sockaddr_t *sockaddr, controllistener_t *listener) { + dns_aclenv_t *env = + ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + isc_netaddr_t netaddr; + isc_result_t result; + int match; + + /* ACL doesn't apply to unix domain sockets */ + if (listener->type != isc_socktype_tcp) { + return (true); + } + + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + + result = dns_acl_match(&netaddr, NULL, listener->acl, env, &match, + NULL); + return (result == ISC_R_SUCCESS && match > 0); +} + +static void +control_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + controlconnection_t *conn = (controlconnection_t *)arg; + controllistener_t *listener = conn->listener; + isc_sockaddr_t peeraddr = isc_nmhandle_peeraddr(handle); + + REQUIRE(conn->sending); + + conn->sending = false; + + if (conn->result == ISC_R_SHUTTINGDOWN) { + isc_app_shutdown(); + goto cleanup_sendhandle; + } + + if (atomic_load_acquire(&listener->controls->shuttingdown) || + result == ISC_R_SHUTTINGDOWN) + { + goto cleanup_sendhandle; + } else if (result != ISC_R_SUCCESS) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "error sending command response to %s: %s", + socktext, isc_result_totext(result)); + goto cleanup_sendhandle; + } + + isc_nmhandle_attach(handle, &conn->readhandle); + conn->reading = true; + + isc_nmhandle_detach(&conn->sendhandle); + + isccc_ccmsg_readmessage(&conn->ccmsg, control_recvmessage, conn); + return; + +cleanup_sendhandle: + isc_nmhandle_detach(&conn->sendhandle); +} + +static void +log_invalid(isccc_ccmsg_t *ccmsg, isc_result_t result) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t peeraddr = isc_nmhandle_peeraddr(ccmsg->handle); + + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_ERROR, + "invalid command from %s: %s", socktext, + isc_result_totext(result)); +} + +static void +conn_cleanup(controlconnection_t *conn) { + controllistener_t *listener = conn->listener; + + if (conn->response != NULL) { + isccc_sexpr_free(&conn->response); + } + if (conn->request != NULL) { + isccc_sexpr_free(&conn->request); + } + if (conn->secret.rstart != NULL) { + isc_mem_put(listener->mctx, conn->secret.rstart, + REGION_SIZE(conn->secret)); + } + if (conn->text != NULL) { + isc_buffer_free(&conn->text); + } +} + +static void +control_respond(isc_nmhandle_t *handle, controlconnection_t *conn) { + controllistener_t *listener = conn->listener; + isccc_sexpr_t *data = NULL; + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + result = isccc_cc_createresponse(conn->request, conn->now, + conn->now + 60, &conn->response); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (conn->result == ISC_R_SHUTTINGDOWN) { + result = ISC_R_SUCCESS; + } else { + result = conn->result; + } + + data = isccc_alist_lookup(conn->response, "_data"); + if (data != NULL) { + if (isccc_cc_defineuint32(data, "result", result) == NULL) { + goto cleanup; + } + } + + if (result != ISC_R_SUCCESS) { + if (data != NULL) { + const char *estr = isc_result_totext(result); + if (isccc_cc_definestring(data, "err", estr) == NULL) { + goto cleanup; + } + } + } + + if (isc_buffer_usedlength(conn->text) > 0) { + if (data != NULL) { + char *str = (char *)isc_buffer_base(conn->text); + if (isccc_cc_definestring(data, "text", str) == NULL) { + goto cleanup; + } + } + } + + conn->ctrl = isccc_alist_lookup(conn->response, "_ctrl"); + if (conn->ctrl == NULL || + isccc_cc_defineuint32(conn->ctrl, "_nonce", conn->nonce) == NULL) + { + goto cleanup; + } + + if (conn->buffer == NULL) { + isc_buffer_allocate(listener->mctx, &conn->buffer, 2 * 2048); + } + + isc_buffer_clear(conn->buffer); + /* Skip the length field (4 bytes) */ + isc_buffer_add(conn->buffer, 4); + + result = isccc_cc_towire(conn->response, &conn->buffer, conn->alg, + &conn->secret); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_buffer_init(&b, conn->buffer->base, 4); + isc_buffer_putuint32(&b, conn->buffer->used - 4); + + r.base = conn->buffer->base; + r.length = conn->buffer->used; + + isc_nmhandle_attach(handle, &conn->sendhandle); + conn->sending = true; + conn_cleanup(conn); + + isc_nmhandle_detach(&conn->cmdhandle); + + isc_nm_send(conn->sendhandle, &r, control_senddone, conn); + + return; + +cleanup: + conn_cleanup(conn); + isc_nmhandle_detach(&conn->cmdhandle); +} + +static void +control_command(isc_task_t *task, isc_event_t *event) { + controlconnection_t *conn = event->ev_arg; + controllistener_t *listener = conn->listener; + + UNUSED(task); + + if (atomic_load_acquire(&listener->controls->shuttingdown)) { + conn_cleanup(conn); + isc_nmhandle_detach(&conn->cmdhandle); + goto done; + } + + conn->result = named_control_docommand(conn->request, + listener->readonly, &conn->text); + control_respond(conn->cmdhandle, conn); + +done: + isc_event_free(&event); +} + +static void +control_recvmessage(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + controlconnection_t *conn = (controlconnection_t *)arg; + controllistener_t *listener = conn->listener; + controlkey_t *key = NULL; + isc_event_t *event = NULL; + isccc_time_t sent; + isccc_time_t exp; + uint32_t nonce; + + conn->reading = false; + + /* Is the server shutting down? */ + if (atomic_load_acquire(&listener->controls->shuttingdown)) { + goto cleanup_readhandle; + } + + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_SHUTTINGDOWN) { + atomic_store_release(&listener->controls->shuttingdown, + true); + } else if (result != ISC_R_EOF) { + log_invalid(&conn->ccmsg, result); + } + + goto cleanup_readhandle; + } + + for (key = ISC_LIST_HEAD(listener->keys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + isccc_region_t ccregion; + + ccregion.rstart = isc_buffer_base(conn->ccmsg.buffer); + ccregion.rend = isc_buffer_used(conn->ccmsg.buffer); + conn->secret.rstart = isc_mem_get(listener->mctx, + key->secret.length); + memmove(conn->secret.rstart, key->secret.base, + key->secret.length); + conn->secret.rend = conn->secret.rstart + key->secret.length; + conn->alg = key->algorithm; + result = isccc_cc_fromwire(&ccregion, &conn->request, conn->alg, + &conn->secret); + if (result == ISC_R_SUCCESS) { + break; + } + isc_mem_put(listener->mctx, conn->secret.rstart, + REGION_SIZE(conn->secret)); + } + + if (key == NULL) { + log_invalid(&conn->ccmsg, ISCCC_R_BADAUTH); + goto cleanup; + } + + /* We shouldn't be getting a reply. */ + if (isccc_cc_isreply(conn->request)) { + log_invalid(&conn->ccmsg, ISC_R_FAILURE); + goto cleanup; + } + + isc_stdtime_get(&conn->now); + + /* + * Limit exposure to replay attacks. + */ + conn->ctrl = isccc_alist_lookup(conn->request, "_ctrl"); + if (!isccc_alist_alistp(conn->ctrl)) { + log_invalid(&conn->ccmsg, ISC_R_FAILURE); + goto cleanup; + } + + if (isccc_cc_lookupuint32(conn->ctrl, "_tim", &sent) == ISC_R_SUCCESS) { + if ((sent + CLOCKSKEW) < conn->now || + (sent - CLOCKSKEW) > conn->now) + { + log_invalid(&conn->ccmsg, ISCCC_R_CLOCKSKEW); + goto cleanup; + } + } else { + log_invalid(&conn->ccmsg, ISC_R_FAILURE); + goto cleanup; + } + + /* + * Expire messages that are too old. + */ + if (isccc_cc_lookupuint32(conn->ctrl, "_exp", &exp) == ISC_R_SUCCESS && + conn->now > exp) + { + log_invalid(&conn->ccmsg, ISCCC_R_EXPIRED); + goto cleanup; + } + + /* + * Duplicate suppression (required for UDP). + */ + LOCK(&listener->controls->symtab_lock); + isccc_cc_cleansymtab(listener->controls->symtab, conn->now); + result = isccc_cc_checkdup(listener->controls->symtab, conn->request, + conn->now); + UNLOCK(&listener->controls->symtab_lock); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_EXISTS) { + result = ISCCC_R_DUPLICATE; + } + log_invalid(&conn->ccmsg, result); + goto cleanup; + } + + if (conn->nonce != 0 && + (isccc_cc_lookupuint32(conn->ctrl, "_nonce", &nonce) != + ISC_R_SUCCESS || + conn->nonce != nonce)) + { + log_invalid(&conn->ccmsg, ISCCC_R_BADAUTH); + goto cleanup; + } + + isc_buffer_allocate(listener->mctx, &conn->text, 2 * 2048); + + isc_nmhandle_attach(handle, &conn->cmdhandle); + isc_nmhandle_detach(&conn->readhandle); + + if (conn->nonce == 0) { + /* + * Establish nonce. + */ + while (conn->nonce == 0) { + isc_nonce_buf(&conn->nonce, sizeof(conn->nonce)); + } + conn->result = ISC_R_SUCCESS; + control_respond(handle, conn); + return; + } + + /* + * Trigger the command. + */ + + event = isc_event_allocate(listener->mctx, conn, NAMED_EVENT_COMMAND, + control_command, conn, sizeof(isc_event_t)); + isc_task_send(named_g_server->task, &event); + + return; + +cleanup: + conn_cleanup(conn); + +cleanup_readhandle: + /* + * readhandle could be NULL if we're shutting down, + * but if not we need to detach it. + */ + if (conn->readhandle != NULL) { + isc_nmhandle_detach(&conn->readhandle); + } +} + +static void +conn_reset(void *arg) { + controlconnection_t *conn = (controlconnection_t *)arg; + controllistener_t *listener = conn->listener; + + if (conn->buffer != NULL) { + isc_buffer_free(&conn->buffer); + } + + if (conn->reading) { + isccc_ccmsg_cancelread(&conn->ccmsg); + return; + } + + LOCK(&listener->connections_lock); + ISC_LIST_UNLINK(listener->connections, conn, link); + UNLOCK(&listener->connections_lock); +#ifdef ENABLE_AFL + if (named_g_fuzz_type == isc_fuzz_rndc) { + named_fuzz_notify(); + } +#endif /* ifdef ENABLE_AFL */ + + isccc_ccmsg_invalidate(&conn->ccmsg); +} + +static void +conn_put(void *arg) { + controlconnection_t *conn = (controlconnection_t *)arg; + controllistener_t *listener = conn->listener; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_DEBUG(3), + "freeing control connection"); + maybe_free_listener(listener); +} + +static void +newconnection(controllistener_t *listener, isc_nmhandle_t *handle) { + controlconnection_t *conn = NULL; + + conn = isc_nmhandle_getdata(handle); + if (conn == NULL) { + conn = isc_nmhandle_getextra(handle); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_DEBUG(3), + "allocate new control connection"); + isc_nmhandle_setdata(handle, conn, conn_reset, conn_put); + isc_refcount_increment(&listener->refs); + } + + *conn = (controlconnection_t){ .listener = listener, + .reading = false, + .alg = DST_ALG_UNKNOWN }; + + isccc_ccmsg_init(listener->mctx, handle, &conn->ccmsg); + + /* Set a 32 KiB upper limit on incoming message. */ + isccc_ccmsg_setmaxsize(&conn->ccmsg, 32768); + + LOCK(&listener->connections_lock); + ISC_LIST_INITANDAPPEND(listener->connections, conn, link); + UNLOCK(&listener->connections_lock); + + isc_nmhandle_attach(handle, &conn->readhandle); + conn->reading = true; + + isccc_ccmsg_readmessage(&conn->ccmsg, control_recvmessage, conn); +} + +static isc_result_t +control_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + controllistener_t *listener = arg; + isc_sockaddr_t peeraddr; + + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_SHUTTINGDOWN) { + shutdown_listener(listener); + } + return (result); + } + + peeraddr = isc_nmhandle_peeraddr(handle); + if (!address_ok(&peeraddr, listener)) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "rejected command channel message from %s", + socktext); + return (ISC_R_FAILURE); + } + + newconnection(listener, handle); + return (ISC_R_SUCCESS); +} + +static void +controls_shutdown(named_controls_t *controls) { + controllistener_t *listener = NULL; + controllistener_t *next = NULL; + + for (listener = ISC_LIST_HEAD(controls->listeners); listener != NULL; + listener = next) + { + /* + * This is asynchronous. As listeners shut down, they will + * call their callbacks. + */ + next = ISC_LIST_NEXT(listener, link); + shutdown_listener(listener); + } +} + +void +named_controls_shutdown(named_controls_t *controls) { + controls_shutdown(controls); + atomic_store_release(&controls->shuttingdown, true); +} + +static isc_result_t +cfgkeylist_find(const cfg_obj_t *keylist, const char *keyname, + const cfg_obj_t **objp) { + const cfg_listelt_t *element = NULL; + const char *str = NULL; + const cfg_obj_t *obj = NULL; + + for (element = cfg_list_first(keylist); element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_map_getname(obj)); + if (strcasecmp(str, keyname) == 0) { + break; + } + } + if (element == NULL) { + return (ISC_R_NOTFOUND); + } + obj = cfg_listelt_value(element); + *objp = obj; + return (ISC_R_SUCCESS); +} + +static void +controlkeylist_fromcfg(const cfg_obj_t *keylist, isc_mem_t *mctx, + controlkeylist_t *keyids) { + const cfg_listelt_t *element = NULL; + char *newstr = NULL; + const char *str = NULL; + const cfg_obj_t *obj = NULL; + controlkey_t *key = NULL; + + for (element = cfg_list_first(keylist); element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + newstr = isc_mem_strdup(mctx, str); + key = isc_mem_get(mctx, sizeof(*key)); + key->keyname = newstr; + key->algorithm = DST_ALG_UNKNOWN; + key->secret.base = NULL; + key->secret.length = 0; + ISC_LINK_INIT(key, link); + ISC_LIST_APPEND(*keyids, key, link); + newstr = NULL; + } +} + +static void +register_keys(const cfg_obj_t *control, const cfg_obj_t *keylist, + controlkeylist_t *keyids, isc_mem_t *mctx, const char *socktext) { + controlkey_t *keyid = NULL, *next = NULL; + const cfg_obj_t *keydef = NULL; + char secret[1024]; + isc_buffer_t b; + isc_result_t result; + + /* + * Find the keys corresponding to the keyids used by this listener. + */ + for (keyid = ISC_LIST_HEAD(*keyids); keyid != NULL; keyid = next) { + next = ISC_LIST_NEXT(keyid, link); + + result = cfgkeylist_find(keylist, keyid->keyname, &keydef); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't find key '%s' for use with " + "command channel %s", + keyid->keyname, socktext); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + } else { + const cfg_obj_t *algobj = NULL; + const cfg_obj_t *secretobj = NULL; + const char *algstr = NULL; + const char *secretstr = NULL; + unsigned int algtype; + + (void)cfg_map_get(keydef, "algorithm", &algobj); + (void)cfg_map_get(keydef, "secret", &secretobj); + INSIST(algobj != NULL && secretobj != NULL); + + algstr = cfg_obj_asstring(algobj); + secretstr = cfg_obj_asstring(secretobj); + + result = named_config_getkeyalgorithm2(algstr, NULL, + &algtype, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(control, named_g_lctx, + ISC_LOG_WARNING, + "unsupported algorithm '%s' in " + "key '%s' for use with command " + "channel %s", + algstr, keyid->keyname, socktext); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + continue; + } + + keyid->algorithm = algtype; + isc_buffer_init(&b, secret, sizeof(secret)); + result = isc_base64_decodestring(secretstr, &b); + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(keydef, named_g_lctx, + ISC_LOG_WARNING, + "secret for key '%s' on " + "command channel %s: %s", + keyid->keyname, socktext, + isc_result_totext(result)); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + continue; + } + + keyid->secret.length = isc_buffer_usedlength(&b); + keyid->secret.base = isc_mem_get(mctx, + keyid->secret.length); + memmove(keyid->secret.base, isc_buffer_base(&b), + keyid->secret.length); + } + } +} + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +static isc_result_t +get_rndckey(isc_mem_t *mctx, controlkeylist_t *keyids) { + isc_result_t result; + cfg_parser_t *pctx = NULL; + cfg_obj_t *config = NULL; + const cfg_obj_t *key = NULL; + const cfg_obj_t *algobj = NULL; + const cfg_obj_t *secretobj = NULL; + const char *algstr = NULL; + const char *secretstr = NULL; + controlkey_t *keyid = NULL; + char secret[1024]; + unsigned int algtype; + isc_buffer_t b; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_INFO, + "configuring command channel from '%s'", named_g_keyfile); + if (!isc_file_exists(named_g_keyfile)) { + return (ISC_R_FILENOTFOUND); + } + + CHECK(cfg_parser_create(mctx, named_g_lctx, &pctx)); + CHECK(cfg_parse_file(pctx, named_g_keyfile, &cfg_type_rndckey, + &config)); + CHECK(cfg_map_get(config, "key", &key)); + + keyid = isc_mem_get(mctx, sizeof(*keyid)); + keyid->keyname = isc_mem_strdup(mctx, + cfg_obj_asstring(cfg_map_getname(key))); + keyid->secret.base = NULL; + keyid->secret.length = 0; + keyid->algorithm = DST_ALG_UNKNOWN; + ISC_LINK_INIT(keyid, link); + if (keyid->keyname == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + CHECK(bind9_check_key(key, named_g_lctx)); + + (void)cfg_map_get(key, "algorithm", &algobj); + (void)cfg_map_get(key, "secret", &secretobj); + INSIST(algobj != NULL && secretobj != NULL); + + algstr = cfg_obj_asstring(algobj); + secretstr = cfg_obj_asstring(secretobj); + + result = named_config_getkeyalgorithm2(algstr, NULL, &algtype, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(key, named_g_lctx, ISC_LOG_WARNING, + "unsupported algorithm '%s' in " + "key '%s' for use with command " + "channel", + algstr, keyid->keyname); + goto cleanup; + } + + keyid->algorithm = algtype; + isc_buffer_init(&b, secret, sizeof(secret)); + result = isc_base64_decodestring(secretstr, &b); + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(key, named_g_lctx, ISC_LOG_WARNING, + "secret for key '%s' on command channel: %s", + keyid->keyname, isc_result_totext(result)); + goto cleanup; + } + + keyid->secret.length = isc_buffer_usedlength(&b); + keyid->secret.base = isc_mem_get(mctx, keyid->secret.length); + memmove(keyid->secret.base, isc_buffer_base(&b), keyid->secret.length); + ISC_LIST_APPEND(*keyids, keyid, link); + keyid = NULL; + result = ISC_R_SUCCESS; + +cleanup: + if (keyid != NULL) { + free_controlkey(keyid, mctx); + } + if (config != NULL) { + cfg_obj_destroy(pctx, &config); + } + if (pctx != NULL) { + cfg_parser_destroy(&pctx); + } + return (result); +} + +/* + * Ensures that both '*global_keylistp' and '*control_keylistp' are + * valid or both are NULL. + */ +static void +get_key_info(const cfg_obj_t *config, const cfg_obj_t *control, + const cfg_obj_t **global_keylistp, + const cfg_obj_t **control_keylistp) { + isc_result_t result; + const cfg_obj_t *control_keylist = NULL; + const cfg_obj_t *global_keylist = NULL; + + REQUIRE(global_keylistp != NULL && *global_keylistp == NULL); + REQUIRE(control_keylistp != NULL && *control_keylistp == NULL); + + control_keylist = cfg_tuple_get(control, "keys"); + + if (!cfg_obj_isvoid(control_keylist) && + cfg_list_first(control_keylist) != NULL) + { + result = cfg_map_get(config, "key", &global_keylist); + + if (result == ISC_R_SUCCESS) { + *global_keylistp = global_keylist; + *control_keylistp = control_keylist; + } + } +} + +static void +update_listener(named_controls_t *cp, controllistener_t **listenerp, + const cfg_obj_t *control, const cfg_obj_t *config, + isc_sockaddr_t *addr, cfg_aclconfctx_t *aclconfctx, + const char *socktext, isc_socktype_t type) { + controllistener_t *listener = NULL; + const cfg_obj_t *allow = NULL; + const cfg_obj_t *global_keylist = NULL; + const cfg_obj_t *control_keylist = NULL; + dns_acl_t *new_acl = NULL; + controlkeylist_t keys; + isc_result_t result = ISC_R_SUCCESS; + + for (listener = ISC_LIST_HEAD(cp->listeners); listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + if (isc_sockaddr_equal(addr, &listener->address)) { + break; + } + } + + if (listener == NULL) { + *listenerp = NULL; + return; + } + + /* + * There is already a listener for this sockaddr. + * Update the access list and key information. + * + * First try to deal with the key situation. There are a few + * possibilities: + * (a) It had an explicit keylist and still has an explicit keylist. + * (b) It had an automagic key and now has an explicit keylist. + * (c) It had an explicit keylist and now needs an automagic key. + * (d) It has an automagic key and still needs the automagic key. + * + * (c) and (d) are the annoying ones. The caller needs to know + * that it should use the automagic configuration for key information + * in place of the named.conf configuration. + * + * XXXDCL There is one other hazard that has not been dealt with, + * the problem that if a key change is being caused by a control + * channel reload, then the response will be with the new key + * and not able to be decrypted by the client. + */ + if (control != NULL) { + get_key_info(config, control, &global_keylist, + &control_keylist); + } + + if (control_keylist != NULL) { + INSIST(global_keylist != NULL); + + ISC_LIST_INIT(keys); + controlkeylist_fromcfg(control_keylist, listener->mctx, &keys); + free_controlkeylist(&listener->keys, listener->mctx); + listener->keys = keys; + register_keys(control, global_keylist, &listener->keys, + listener->mctx, socktext); + } else { + free_controlkeylist(&listener->keys, listener->mctx); + result = get_rndckey(listener->mctx, &listener->keys); + } + + if (result != ISC_R_SUCCESS && global_keylist != NULL) { + /* + * This message might be a little misleading since the + * "new keys" might in fact be identical to the old ones, + * but tracking whether they are identical just for the + * sake of avoiding this message would be too much trouble. + */ + if (control != NULL) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't install new keys for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "couldn't install new keys for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } + } + + /* + * Now, keep the old access list unless a new one can be made. + */ + if (control != NULL && type == isc_socktype_tcp) { + allow = cfg_tuple_get(control, "allow"); + result = cfg_acl_fromconfig(allow, config, named_g_lctx, + aclconfctx, listener->mctx, 0, + &new_acl); + } else { + result = dns_acl_any(listener->mctx, &new_acl); + } + + if (control != NULL) { + const cfg_obj_t *readonly = NULL; + + readonly = cfg_tuple_get(control, "read-only"); + if (!cfg_obj_isvoid(readonly)) { + listener->readonly = cfg_obj_asboolean(readonly); + } + } + + if (result == ISC_R_SUCCESS) { + dns_acl_detach(&listener->acl); + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + /* XXXDCL say the old acl is still used? */ + } else if (control != NULL) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't install new acl for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "couldn't install new acl for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } + +#if 0 + /* XXX: no unix socket support yet */ + if (result == ISC_R_SUCCESS && type == isc_socktype_unix) { + uint32_t perm, owner, group; + perm = cfg_obj_asuint32(cfg_tuple_get(control, "perm")); + owner = cfg_obj_asuint32(cfg_tuple_get(control, "owner")); + group = cfg_obj_asuint32(cfg_tuple_get(control, "group")); + result = ISC_R_SUCCESS; + if (listener->perm != perm || listener->owner != owner || + listener->group != group) + { + result = isc_socket_permunix(&listener->address, perm, + owner, group); + } + if (result == ISC_R_SUCCESS) { + listener->perm = perm; + listener->owner = owner; + listener->group = group; + } else if (control != NULL) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't update ownership/permission for " + "command channel %s", + socktext); + } + } +#endif + + *listenerp = listener; +} + +static void +add_listener(named_controls_t *cp, controllistener_t **listenerp, + const cfg_obj_t *control, const cfg_obj_t *config, + isc_sockaddr_t *addr, cfg_aclconfctx_t *aclconfctx, + const char *socktext, isc_socktype_t type) { + isc_mem_t *mctx = cp->server->mctx; + controllistener_t *listener = NULL; + const cfg_obj_t *allow = NULL; + const cfg_obj_t *global_keylist = NULL; + const cfg_obj_t *control_keylist = NULL; + dns_acl_t *new_acl = NULL; + isc_result_t result = ISC_R_SUCCESS; + int pf; + + listener = isc_mem_get(mctx, sizeof(*listener)); + *listener = (controllistener_t){ .controls = cp, + .address = *addr, + .type = type }; + isc_mem_attach(mctx, &listener->mctx); + isc_mutex_init(&listener->connections_lock); + ISC_LINK_INIT(listener, link); + ISC_LIST_INIT(listener->keys); + ISC_LIST_INIT(listener->connections); + isc_refcount_init(&listener->refs, 1); + + /* + * Make the ACL. + */ + if (control != NULL && type == isc_socktype_tcp) { + const cfg_obj_t *readonly = NULL; + + allow = cfg_tuple_get(control, "allow"); + CHECK(cfg_acl_fromconfig(allow, config, named_g_lctx, + aclconfctx, mctx, 0, &new_acl)); + + readonly = cfg_tuple_get(control, "read-only"); + if (!cfg_obj_isvoid(readonly)) { + listener->readonly = cfg_obj_asboolean(readonly); + } + } else { + CHECK(dns_acl_any(mctx, &new_acl)); + } + + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + + if (config != NULL) { + get_key_info(config, control, &global_keylist, + &control_keylist); + } + + if (control_keylist != NULL) { + controlkeylist_fromcfg(control_keylist, listener->mctx, + &listener->keys); + register_keys(control, global_keylist, &listener->keys, + listener->mctx, socktext); + } else { + result = get_rndckey(mctx, &listener->keys); + if (result != ISC_R_SUCCESS && control != NULL) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't install keys for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } + } + + pf = isc_sockaddr_pf(&listener->address); + if ((pf == AF_INET && isc_net_probeipv4() != ISC_R_SUCCESS) || + (pf == AF_UNIX && isc_net_probeunix() != ISC_R_SUCCESS) || + (pf == AF_INET6 && isc_net_probeipv6() != ISC_R_SUCCESS)) + { + CHECK(ISC_R_FAMILYNOSUPPORT); + } + +#if 0 + /* XXX: no unix socket support yet */ + if (type == isc_socktype_unix) { + isc_socket_cleanunix(&listener->address, false); + } +#endif + + CHECK(isc_nm_listentcp( + named_g_netmgr, &listener->address, control_newconn, listener, + sizeof(controlconnection_t), 5, NULL, &listener->sock)); +#if 0 + /* XXX: no unix socket support yet */ + if (type == isc_socktype_unix) { + listener->perm = + cfg_obj_asuint32(cfg_tuple_get(control, "perm")); + listener->owner = + cfg_obj_asuint32(cfg_tuple_get(control, "owner")); + listener->group = + cfg_obj_asuint32(cfg_tuple_get(control, "group")); + result = isc_socket_permunix(&listener->address, listener->perm, + listener->owner, listener->group); + } +#endif + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_NOTICE, + "command channel listening on %s", socktext); + *listenerp = listener; + return; + +cleanup: + isc_refcount_decrement(&listener->refs); + listener->exiting = true; + free_listener(listener); + + if (control != NULL) { + cfg_obj_log(control, named_g_lctx, ISC_LOG_WARNING, + "couldn't add command channel %s: %s", socktext, + isc_result_totext(result)); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_NOTICE, + "couldn't add command channel %s: %s", socktext, + isc_result_totext(result)); + } + + *listenerp = NULL; + + /* XXXDCL return error results? fail hard? */ +} + +isc_result_t +named_controls_configure(named_controls_t *cp, const cfg_obj_t *config, + cfg_aclconfctx_t *aclconfctx) { + controllistener_t *listener = NULL; + controllistenerlist_t new_listeners; + const cfg_obj_t *controlslist = NULL; + const cfg_listelt_t *element, *element2; + char socktext[ISC_SOCKADDR_FORMATSIZE]; + + ISC_LIST_INIT(new_listeners); + + /* + * Get the list of named.conf 'controls' statements. + */ + (void)cfg_map_get(config, "controls", &controlslist); + + /* + * Run through the new control channel list, noting sockets that + * are already being listened on and moving them to the new list. + * + * Identifying duplicate addr/port combinations is left to either + * the underlying config code, or to the bind attempt getting an + * address-in-use error. + */ + if (controlslist != NULL) { + for (element = cfg_list_first(controlslist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *controls = NULL; + const cfg_obj_t *inetcontrols = NULL; + + controls = cfg_listelt_value(element); + (void)cfg_map_get(controls, "inet", &inetcontrols); + if (inetcontrols == NULL) { + continue; + } + + for (element2 = cfg_list_first(inetcontrols); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + const cfg_obj_t *control = NULL; + const cfg_obj_t *obj = NULL; + isc_sockaddr_t addr; + + /* + * The parser handles BIND 8 configuration file + * syntax, so it allows unix phrases as well + * inet phrases with no keys{} clause. + */ + control = cfg_listelt_value(element2); + + obj = cfg_tuple_get(control, "address"); + addr = *cfg_obj_assockaddr(obj); + if (isc_sockaddr_getport(&addr) == 0) { + isc_sockaddr_setport( + &addr, NAMED_CONTROL_PORT); + } + + isc_sockaddr_format(&addr, socktext, + sizeof(socktext)); + + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, + ISC_LOG_DEBUG(9), + "processing control channel %s", + socktext); + + update_listener(cp, &listener, control, config, + &addr, aclconfctx, socktext, + isc_socktype_tcp); + + if (listener != NULL) { + /* + * Remove the listener from the old + * list, so it won't be shut down. + */ + ISC_LIST_UNLINK(cp->listeners, listener, + link); + } else { + /* + * This is a new listener. + */ + add_listener(cp, &listener, control, + config, &addr, aclconfctx, + socktext, + isc_socktype_tcp); + } + + if (listener != NULL) { + ISC_LIST_APPEND(new_listeners, listener, + link); + } + } + } + for (element = cfg_list_first(controlslist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *controls = NULL; + const cfg_obj_t *unixcontrols = NULL; + + controls = cfg_listelt_value(element); + (void)cfg_map_get(controls, "unix", &unixcontrols); + if (unixcontrols == NULL) { + continue; + } + + cfg_obj_log(controls, named_g_lctx, ISC_LOG_ERROR, + "UNIX domain sockets not yet supported"); + return (ISC_R_FAILURE); + +#if 0 + /* XXX: no unix domain socket support in netmgr */ + for (element2 = cfg_list_first(unixcontrols); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + const cfg_obj_t *control = NULL; + const cfg_obj_t *path = NULL; + isc_sockaddr_t addr; + isc_result_t result; + + /* + * The parser handles BIND 8 configuration file + * syntax, so it allows unix phrases as well + * inet phrases with no keys{} clause. + */ + control = cfg_listelt_value(element2); + + path = cfg_tuple_get(control, "path"); + result = isc_sockaddr_frompath( + &addr, cfg_obj_asstring(path)); + if (result != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, + ISC_LOG_DEBUG(9), + "control channel '%s': %s", + cfg_obj_asstring(path), + isc_result_totext(result)); + continue; + } + + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, + ISC_LOG_DEBUG(9), + "processing control channel '%s'", + cfg_obj_asstring(path)); + + update_listener(cp, &listener, control, config, + &addr, aclconfctx, + cfg_obj_asstring(path), + isc_socktype_unix); + + if (listener != NULL) { + /* + * Remove the listener from the old + * list, so it won't be shut down. + */ + ISC_LIST_UNLINK(cp->listeners, listener, + link); + } else { + /* + * This is a new listener. + */ + add_listener(cp, &listener, control, + config, &addr, aclconfctx, + cfg_obj_asstring(path), + isc_socktype_unix); + } + + if (listener != NULL) { + ISC_LIST_APPEND(new_listeners, listener, + link); + } + } +#endif + } + } else { + int i; + + for (i = 0; i < 2; i++) { + isc_sockaddr_t addr; + + if (i == 0) { + struct in_addr localhost; + + if (isc_net_probeipv4() != ISC_R_SUCCESS) { + continue; + } + localhost.s_addr = htonl(INADDR_LOOPBACK); + isc_sockaddr_fromin(&addr, &localhost, 0); + } else { + if (isc_net_probeipv6() != ISC_R_SUCCESS) { + continue; + } + isc_sockaddr_fromin6(&addr, &in6addr_loopback, + 0); + } + isc_sockaddr_setport(&addr, NAMED_CONTROL_PORT); + + isc_sockaddr_format(&addr, socktext, sizeof(socktext)); + + update_listener(cp, &listener, NULL, NULL, &addr, NULL, + socktext, isc_socktype_tcp); + + if (listener != NULL) { + /* + * Remove the listener from the old + * list, so it won't be shut down. + */ + ISC_LIST_UNLINK(cp->listeners, listener, link); + } else { + /* + * This is a new listener. + */ + add_listener(cp, &listener, NULL, NULL, &addr, + NULL, socktext, isc_socktype_tcp); + } + + if (listener != NULL) { + ISC_LIST_APPEND(new_listeners, listener, link); + } + } + } + + /* + * named_control_shutdown() will stop whatever is on the global + * listeners list, which currently only has whatever sockaddrs + * were in the previous configuration (if any) that do not + * remain in the current configuration. + */ + controls_shutdown(cp); + + /* + * Put all of the valid listeners on the listeners list. + * Anything already on listeners in the process of shutting + * down will be taken care of by listen_done(). + */ + ISC_LIST_APPENDLIST(cp->listeners, new_listeners, link); + return (ISC_R_SUCCESS); +} + +isc_result_t +named_controls_create(named_server_t *server, named_controls_t **ctrlsp) { + isc_mem_t *mctx = server->mctx; + isc_result_t result; + named_controls_t *controls = isc_mem_get(mctx, sizeof(*controls)); + + *controls = (named_controls_t){ + .server = server, + }; + + ISC_LIST_INIT(controls->listeners); + + atomic_init(&controls->shuttingdown, false); + isc_mutex_init(&controls->symtab_lock); + LOCK(&controls->symtab_lock); + result = isccc_cc_createsymtab(&controls->symtab); + UNLOCK(&controls->symtab_lock); + + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&controls->symtab_lock); + isc_mem_put(server->mctx, controls, sizeof(*controls)); + return (result); + } + *ctrlsp = controls; + return (ISC_R_SUCCESS); +} + +void +named_controls_destroy(named_controls_t **ctrlsp) { + named_controls_t *controls = *ctrlsp; + *ctrlsp = NULL; + + REQUIRE(ISC_LIST_EMPTY(controls->listeners)); + + LOCK(&controls->symtab_lock); + isccc_symtab_destroy(&controls->symtab); + UNLOCK(&controls->symtab_lock); + isc_mutex_destroy(&controls->symtab_lock); + isc_mem_put(controls->server->mctx, controls, sizeof(*controls)); +} diff --git a/bin/named/dlz_dlopen_driver.c b/bin/named/dlz_dlopen_driver.c new file mode 100644 index 0000000..f636cd2 --- /dev/null +++ b/bin/named/dlz_dlopen_driver.c @@ -0,0 +1,552 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <uv.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/dlz_dlopen.h> +#include <dns/log.h> + +#include <dlz/dlz_dlopen_driver.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_dlopen = NULL; + +typedef struct dlopen_data { + isc_mem_t *mctx; + char *dl_path; + char *dlzname; + uv_lib_t dl_handle; + void *dbdata; + unsigned int flags; + isc_mutex_t lock; + int version; + bool in_configure; + + dlz_dlopen_version_t *dlz_version; + dlz_dlopen_create_t *dlz_create; + dlz_dlopen_findzonedb_t *dlz_findzonedb; + dlz_dlopen_lookup_t *dlz_lookup; + dlz_dlopen_authority_t *dlz_authority; + dlz_dlopen_allnodes_t *dlz_allnodes; + dlz_dlopen_allowzonexfr_t *dlz_allowzonexfr; + dlz_dlopen_newversion_t *dlz_newversion; + dlz_dlopen_closeversion_t *dlz_closeversion; + dlz_dlopen_configure_t *dlz_configure; + dlz_dlopen_ssumatch_t *dlz_ssumatch; + dlz_dlopen_addrdataset_t *dlz_addrdataset; + dlz_dlopen_subrdataset_t *dlz_subrdataset; + dlz_dlopen_delrdataset_t *dlz_delrdataset; + dlz_dlopen_destroy_t *dlz_destroy; +} dlopen_data_t; + +/* Modules can choose whether they are lock-safe or not. */ +#define MAYBE_LOCK(cd) \ + do { \ + if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \ + !cd->in_configure) \ + LOCK(&cd->lock); \ + } while (0) + +#define MAYBE_UNLOCK(cd) \ + do { \ + if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \ + !cd->in_configure) \ + UNLOCK(&cd->lock); \ + } while (0) + +/* + * Log a message at the given level. + */ +static void +dlopen_log(int level, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(level), fmt, ap); + va_end(ap); +} + +/* + * SDLZ methods + */ + +static isc_result_t +dlopen_dlz_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_allnodes == NULL) { + return (ISC_R_NOPERM); + } + + MAYBE_LOCK(cd); + result = cd->dlz_allnodes(zone, cd->dbdata, allnodes); + MAYBE_UNLOCK(cd); + return (result); +} + +static isc_result_t +dlopen_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_allowzonexfr == NULL) { + return (ISC_R_NOPERM); + } + + MAYBE_LOCK(cd); + result = cd->dlz_allowzonexfr(cd->dbdata, name, client); + MAYBE_UNLOCK(cd); + return (result); +} + +static isc_result_t +dlopen_dlz_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_authority == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + MAYBE_LOCK(cd); + result = cd->dlz_authority(zone, cd->dbdata, lookup); + MAYBE_UNLOCK(cd); + return (result); +} + +static isc_result_t +dlopen_dlz_findzonedb(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + MAYBE_LOCK(cd); + result = cd->dlz_findzonedb(cd->dbdata, name, methods, clientinfo); + MAYBE_UNLOCK(cd); + return (result); +} + +static isc_result_t +dlopen_dlz_lookup(const char *zone, const char *name, void *driverarg, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + MAYBE_LOCK(cd); + result = cd->dlz_lookup(zone, name, cd->dbdata, lookup, methods, + clientinfo); + MAYBE_UNLOCK(cd); + return (result); +} + +/* + * Load a symbol from the library + */ +static void * +dl_load_symbol(dlopen_data_t *cd, const char *symbol, bool mandatory) { + void *ptr = NULL; + int r = uv_dlsym(&cd->dl_handle, symbol, &ptr); + if (r != 0) { + const char *errmsg = uv_dlerror(&cd->dl_handle); + if (errmsg == NULL) { + errmsg = "returned function pointer is NULL"; + } + if (mandatory) { + dlopen_log(ISC_LOG_ERROR, + "dlz_dlopen: library '%s' is missing " + "required symbol '%s': %s", + cd->dl_path, symbol, errmsg); + } + } + + return (ptr); +} + +static void +dlopen_dlz_destroy(void *driverarg, void *dbdata); + +/* + * Called at startup for each dlopen zone in named.conf + */ +static isc_result_t +dlopen_dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + dlopen_data_t *cd; + isc_mem_t *mctx = NULL; + isc_result_t result = ISC_R_FAILURE; + int r; + + UNUSED(driverarg); + + if (argc < 2) { + dlopen_log(ISC_LOG_ERROR, + "dlz_dlopen driver for '%s' needs a path to " + "the shared library", + dlzname); + return (ISC_R_FAILURE); + } + + isc_mem_create(&mctx); + cd = isc_mem_get(mctx, sizeof(*cd)); + memset(cd, 0, sizeof(*cd)); + + cd->mctx = mctx; + + cd->dl_path = isc_mem_strdup(cd->mctx, argv[1]); + cd->dlzname = isc_mem_strdup(cd->mctx, dlzname); + + /* Initialize the lock */ + isc_mutex_init(&cd->lock); + + r = uv_dlopen(cd->dl_path, &cd->dl_handle); + if (r != 0) { + const char *errmsg = uv_dlerror(&cd->dl_handle); + if (errmsg == NULL) { + errmsg = "unknown error"; + } + dlopen_log(ISC_LOG_ERROR, + "dlz_dlopen failed to open library '%s': %s", + cd->dl_path, errmsg); + result = ISC_R_FAILURE; + goto failed; + } + + /* Find the symbols */ + cd->dlz_version = + (dlz_dlopen_version_t *)dl_load_symbol(cd, "dlz_version", true); + cd->dlz_create = (dlz_dlopen_create_t *)dl_load_symbol(cd, "dlz_create", + true); + cd->dlz_lookup = (dlz_dlopen_lookup_t *)dl_load_symbol(cd, "dlz_lookup", + true); + cd->dlz_findzonedb = (dlz_dlopen_findzonedb_t *)dl_load_symbol( + cd, "dlz_findzonedb", true); + + if (cd->dlz_create == NULL || cd->dlz_version == NULL || + cd->dlz_lookup == NULL || cd->dlz_findzonedb == NULL) + { + /* We're missing a required symbol */ + result = ISC_R_FAILURE; + goto failed; + } + + cd->dlz_allowzonexfr = (dlz_dlopen_allowzonexfr_t *)dl_load_symbol( + cd, "dlz_allowzonexfr", false); + cd->dlz_allnodes = (dlz_dlopen_allnodes_t *)dl_load_symbol( + cd, "dlz_allnodes", (cd->dlz_allowzonexfr != NULL)); + cd->dlz_authority = (dlz_dlopen_authority_t *)dl_load_symbol( + cd, "dlz_authority", false); + cd->dlz_newversion = (dlz_dlopen_newversion_t *)dl_load_symbol( + cd, "dlz_newversion", false); + cd->dlz_closeversion = (dlz_dlopen_closeversion_t *)dl_load_symbol( + cd, "dlz_closeversion", (cd->dlz_newversion != NULL)); + cd->dlz_configure = (dlz_dlopen_configure_t *)dl_load_symbol( + cd, "dlz_configure", false); + cd->dlz_ssumatch = (dlz_dlopen_ssumatch_t *)dl_load_symbol( + cd, "dlz_ssumatch", false); + cd->dlz_addrdataset = (dlz_dlopen_addrdataset_t *)dl_load_symbol( + cd, "dlz_addrdataset", false); + cd->dlz_subrdataset = (dlz_dlopen_subrdataset_t *)dl_load_symbol( + cd, "dlz_subrdataset", false); + cd->dlz_delrdataset = (dlz_dlopen_delrdataset_t *)dl_load_symbol( + cd, "dlz_delrdataset", false); + cd->dlz_destroy = (dlz_dlopen_destroy_t *)dl_load_symbol( + cd, "dlz_destroy", false); + + /* Check the version of the API is the same */ + cd->version = cd->dlz_version(&cd->flags); + if (cd->version < (DLZ_DLOPEN_VERSION - DLZ_DLOPEN_AGE) || + cd->version > DLZ_DLOPEN_VERSION) + { + dlopen_log(ISC_LOG_ERROR, + "dlz_dlopen: %s: incorrect driver API version %d, " + "requires %d", + cd->dl_path, cd->version, DLZ_DLOPEN_VERSION); + result = ISC_R_FAILURE; + goto failed; + } + + /* + * Call the library's create function. Note that this is an + * extended version of dlz create, with the addition of + * named function pointers for helper functions that the + * driver will need. This avoids the need for the backend to + * link the BIND9 libraries + */ + MAYBE_LOCK(cd); + result = cd->dlz_create(dlzname, argc - 1, argv + 1, &cd->dbdata, "log", + dlopen_log, "putrr", dns_sdlz_putrr, + "putnamedrr", dns_sdlz_putnamedrr, + "writeable_zone", dns_dlz_writeablezone, NULL); + MAYBE_UNLOCK(cd); + if (result != ISC_R_SUCCESS) { + goto failed; + } + + *dbdata = cd; + + return (ISC_R_SUCCESS); + +failed: + dlopen_log(ISC_LOG_ERROR, "dlz_dlopen of '%s' failed", dlzname); + + dlopen_dlz_destroy(NULL, cd); + + return (result); +} + +/* + * Called when bind is shutting down + */ +static void +dlopen_dlz_destroy(void *driverarg, void *dbdata) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + + UNUSED(driverarg); + + if (cd->dlz_destroy && cd->dbdata) { + MAYBE_LOCK(cd); + cd->dlz_destroy(cd->dbdata); + MAYBE_UNLOCK(cd); + } + + uv_dlclose(&cd->dl_handle); + isc_mutex_destroy(&cd->lock); + isc_mem_free(cd->mctx, cd->dl_path); + isc_mem_free(cd->mctx, cd->dlzname); + isc_mem_putanddetach(&cd->mctx, cd, sizeof(*cd)); +} + +/* + * Called to start a transaction + */ +static isc_result_t +dlopen_dlz_newversion(const char *zone, void *driverarg, void *dbdata, + void **versionp) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_newversion == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + MAYBE_LOCK(cd); + result = cd->dlz_newversion(zone, cd->dbdata, versionp); + MAYBE_UNLOCK(cd); + return (result); +} + +/* + * Called to end a transaction + */ +static void +dlopen_dlz_closeversion(const char *zone, bool commit, void *driverarg, + void *dbdata, void **versionp) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + + UNUSED(driverarg); + + if (cd->dlz_newversion == NULL) { + *versionp = NULL; + return; + } + + MAYBE_LOCK(cd); + cd->dlz_closeversion(zone, commit, cd->dbdata, versionp); + MAYBE_UNLOCK(cd); +} + +/* + * Called on startup to configure any writeable zones + */ +static isc_result_t +dlopen_dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *driverarg, + void *dbdata) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_configure == NULL) { + return (ISC_R_SUCCESS); + } + + MAYBE_LOCK(cd); + cd->in_configure = true; + result = cd->dlz_configure(view, dlzdb, cd->dbdata); + cd->in_configure = false; + MAYBE_UNLOCK(cd); + + return (result); +} + +/* + * Check for authority to change a name. + */ +static bool +dlopen_dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr, + const char *type, const char *key, uint32_t keydatalen, + unsigned char *keydata, void *driverarg, void *dbdata) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + bool ret; + + UNUSED(driverarg); + + if (cd->dlz_ssumatch == NULL) { + return (false); + } + + MAYBE_LOCK(cd); + ret = cd->dlz_ssumatch(signer, name, tcpaddr, type, key, keydatalen, + keydata, cd->dbdata); + MAYBE_UNLOCK(cd); + + return (ret); +} + +/* + * Add an rdataset. + */ +static isc_result_t +dlopen_dlz_addrdataset(const char *name, const char *rdatastr, void *driverarg, + void *dbdata, void *version) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_addrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + MAYBE_LOCK(cd); + result = cd->dlz_addrdataset(name, rdatastr, cd->dbdata, version); + MAYBE_UNLOCK(cd); + + return (result); +} + +/* + * Subtract an rdataset. + */ +static isc_result_t +dlopen_dlz_subrdataset(const char *name, const char *rdatastr, void *driverarg, + void *dbdata, void *version) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_subrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + MAYBE_LOCK(cd); + result = cd->dlz_subrdataset(name, rdatastr, cd->dbdata, version); + MAYBE_UNLOCK(cd); + + return (result); +} + +/* + * Delete a rdataset. + */ +static isc_result_t +dlopen_dlz_delrdataset(const char *name, const char *type, void *driverarg, + void *dbdata, void *version) { + dlopen_data_t *cd = (dlopen_data_t *)dbdata; + isc_result_t result; + + UNUSED(driverarg); + + if (cd->dlz_delrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + MAYBE_LOCK(cd); + result = cd->dlz_delrdataset(name, type, cd->dbdata, version); + MAYBE_UNLOCK(cd); + + return (result); +} + +static dns_sdlzmethods_t dlz_dlopen_methods = { + dlopen_dlz_create, dlopen_dlz_destroy, dlopen_dlz_findzonedb, + dlopen_dlz_lookup, dlopen_dlz_authority, dlopen_dlz_allnodes, + dlopen_dlz_allowzonexfr, dlopen_dlz_newversion, dlopen_dlz_closeversion, + dlopen_dlz_configure, dlopen_dlz_ssumatch, dlopen_dlz_addrdataset, + dlopen_dlz_subrdataset, dlopen_dlz_delrdataset +}; + +/* + * Register driver with BIND + */ +isc_result_t +dlz_dlopen_init(isc_mem_t *mctx) { + isc_result_t result; + + dlopen_log(2, "Registering DLZ_dlopen driver"); + + result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + mctx, &dlz_dlopen); + + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/* + * Unregister the driver + */ +void +dlz_dlopen_clear(void) { + dlopen_log(2, "Unregistering DLZ_dlopen driver"); + if (dlz_dlopen != NULL) { + dns_sdlzunregister(&dlz_dlopen); + } +} diff --git a/bin/named/fuzz.c b/bin/named/fuzz.c new file mode 100644 index 0000000..fb0d56f --- /dev/null +++ b/bin/named/fuzz.c @@ -0,0 +1,782 @@ +/* + * 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 <named/fuzz.h> + +#ifdef ENABLE_AFL +#include <arpa/inet.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/condition.h> +#include <isc/mutex.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include <dns/log.h> + +#include <named/globals.h> +#include <named/log.h> +#include <named/server.h> + +/* + * We are using pthreads directly because we might be using it with + * unthreaded version of BIND, where all thread functions are + * mocks. Since AFL for now only works on Linux it's not a problem. + */ +static pthread_cond_t cond; +static pthread_mutex_t mutex; +static bool ready; + +/* + * In "client:" mode, this thread reads fuzzed query messages from AFL + * from standard input and sends it to named's listening port (DNS) that + * is passed in the -A client:<address>:<port> option. It can be used to + * test named from the client side. + */ +static void * +fuzz_thread_client(void *arg) { + char *host; + char *port; + struct sockaddr_in servaddr; + int sockfd; + void *buf; + + UNUSED(arg); + + /* + * Parse named -A argument in the "address:port" syntax. Due to + * the syntax used, this only supports IPv4 addresses. + */ + host = strdup(named_g_fuzz_addr); + RUNTIME_CHECK(host != NULL); + + port = strchr(host, ':'); + RUNTIME_CHECK(port != NULL); + *port = 0; + ++port; + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + RUNTIME_CHECK(inet_pton(AF_INET, host, &servaddr.sin_addr) == 1); + servaddr.sin_port = htons(atoi(port)); + + free(host); + + /* + * Wait for named to start. This is set in run_server() in the + * named thread. + */ + while (!named_g_run_done) { + usleep(10000); + } + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + RUNTIME_CHECK(sockfd != -1); + + buf = malloc(65536); + RUNTIME_CHECK(buf != NULL); + + /* + * Processing fuzzed packets 100,000 times before shutting down + * the app. + */ +#ifdef __AFL_LOOP + for (int loop = 0; loop < 100000; loop++) { +#else /* ifdef __AFL_LOOP */ + { +#endif /* ifdef __AFL_LOOP */ + ssize_t length; + ssize_t sent; + + length = read(0, buf, 65536); + if (length <= 0) { + usleep(1000000); + goto next; + } + + /* + * Ignore packets that are larger than 4096 bytes. + */ + if (length > 4096) { + /* + * AFL_CMIN doesn't support persistent mode, so + * shutdown the server. + */ + if (getenv("AFL_CMIN")) { + free(buf); + close(sockfd); + named_server_flushonshutdown(named_g_server, + false); + isc_app_shutdown(); + return (NULL); + } + raise(SIGSTOP); + goto next; + } + + RUNTIME_CHECK(pthread_mutex_lock(&mutex) == 0); + + ready = false; + + sent = sendto(sockfd, buf, length, 0, + (struct sockaddr *)&servaddr, sizeof(servaddr)); + RUNTIME_CHECK(sent == length); + + /* + * Read the reply message from named to unclog it. Don't + * bother if there isn't a reply. + */ + (void)recvfrom(sockfd, buf, 65536, MSG_DONTWAIT, NULL, NULL); + + while (!ready) { + pthread_cond_wait(&cond, &mutex); + } + + RUNTIME_CHECK(pthread_mutex_unlock(&mutex) == 0); + next:; + } + + free(buf); + close(sockfd); + + named_server_flushonshutdown(named_g_server, false); + isc_app_shutdown(); + + return (NULL); +} + +/* + * In "resolver:" mode, this thread reads fuzzed reply messages from AFL + * from standard input. It also sets up a listener as a remote + * authoritative server and sends a driver query to the client side of + * named(resolver). When named(resolver) connects to this authoritative + * server, this thread writes the fuzzed reply message from AFL to it. + * + * -A resolver:<saddress>:<sport>:<raddress>:<rport> + * + * Here, <saddress>:<sport> is where named(resolver) is listening on. + * <raddress>:<rport> is where the thread is supposed to setup the + * authoritative server. This address should be configured via the root + * zone to be the authoritiative server for aaaaaaaaaa.example. + * + * named(resolver) when being fuzzed will not cache answers. + */ +static void * +fuzz_thread_resolver(void *arg) { + char *sqtype, *shost, *sport, *rhost, *rport; + struct sockaddr_in servaddr, recaddr, recvaddr; + /* + * Query for aaaaaaaaaa.example./A in wire format with RD=1, + * EDNS and DO=1. 0x88, 0x0c at the start is the ID field which + * will be updated for each query. + */ + char respacket[] = { 0x88, 0x0c, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x0a, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x07, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 }; + /* + * Response for example./DNSKEY in wire format. Note that RRSIGs + * were generated with this DNSKEY that are used as seeds for + * AFL in the DNSSEC fuzzing job. So the DNSKEY content of this + * message must not change, or the corresponding RRSIGs will + * have to be updated. 0x8d, 0xf6 at the start is the ID field + * which will be made to match the query. + */ + const uint8_t dnskey_wf[] = { + 0x8d, 0xf6, 0x84, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x01, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x00, 0x00, 0x30, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x30, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x2c, 0x01, 0x08, 0x01, 0x00, 0x03, + 0x08, 0x03, 0x01, 0x00, 0x01, 0xbd, 0x81, 0xdc, 0x7f, 0x16, + 0xd4, 0x81, 0x7c, 0x1f, 0x9f, 0x6a, 0x68, 0xdd, 0xd4, 0xda, + 0x48, 0xd9, 0x1c, 0xbd, 0xa6, 0x46, 0x1a, 0xf0, 0xb4, 0xb9, + 0xec, 0x3d, 0x6c, 0x0b, 0x57, 0xc7, 0xd6, 0x54, 0x66, 0xe6, + 0x6c, 0xd5, 0x90, 0x3a, 0x78, 0x7d, 0x7f, 0x78, 0x80, 0xa2, + 0x89, 0x61, 0x6d, 0x8a, 0x2b, 0xcd, 0x0a, 0x77, 0x7a, 0xad, + 0xc9, 0x61, 0x53, 0x53, 0x8c, 0x99, 0x72, 0x86, 0x14, 0x74, + 0x9c, 0x49, 0x2a, 0x47, 0x23, 0xf7, 0x02, 0x07, 0x73, 0x1c, + 0x5c, 0x2e, 0xb4, 0x9a, 0xa4, 0xd7, 0x98, 0x42, 0xc3, 0xd2, + 0xfe, 0xbf, 0xf3, 0xb3, 0x6a, 0x52, 0x92, 0xd5, 0xfa, 0x47, + 0x00, 0xe3, 0xd9, 0x59, 0x31, 0x95, 0x48, 0x40, 0xfc, 0x06, + 0x73, 0x90, 0xc6, 0x73, 0x96, 0xba, 0x29, 0x91, 0xe2, 0xac, + 0xa3, 0xa5, 0x6d, 0x91, 0x6d, 0x52, 0xb9, 0x34, 0xba, 0x68, + 0x4f, 0xad, 0xf0, 0xc3, 0xf3, 0x1d, 0x6d, 0x61, 0x76, 0xe5, + 0x3d, 0xa3, 0x9b, 0x2a, 0x0c, 0x92, 0xb3, 0x78, 0x6b, 0xf1, + 0x20, 0xd6, 0x90, 0xb7, 0xac, 0xe2, 0xf8, 0x2b, 0x94, 0x10, + 0x79, 0xce, 0xa8, 0x60, 0x42, 0xea, 0x6a, 0x18, 0x2f, 0xc0, + 0xd8, 0x05, 0x0a, 0x3b, 0x06, 0x0f, 0x02, 0x7e, 0xff, 0x33, + 0x46, 0xee, 0xb6, 0x21, 0x25, 0x90, 0x63, 0x4b, 0x3b, 0x5e, + 0xb2, 0x72, 0x3a, 0xcb, 0x91, 0x41, 0xf4, 0x20, 0x50, 0x78, + 0x1c, 0x93, 0x95, 0xda, 0xfa, 0xae, 0x85, 0xc5, 0xd7, 0x6b, + 0x92, 0x0c, 0x70, 0x6b, 0xe4, 0xb7, 0x29, 0x3a, 0x2e, 0x18, + 0x88, 0x82, 0x33, 0x7c, 0xa8, 0xea, 0xb8, 0x31, 0x8f, 0xaf, + 0x50, 0xc5, 0x9c, 0x08, 0x56, 0x8f, 0x09, 0x76, 0x4e, 0xdf, + 0x97, 0x75, 0x9d, 0x00, 0x52, 0x7f, 0xdb, 0xec, 0x30, 0xcb, + 0x1c, 0x4c, 0x2a, 0x21, 0x93, 0xc4, 0x6d, 0x85, 0xa9, 0x40, + 0x3b, 0xc0, 0x0c, 0x00, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x2c, 0x01, 0x1b, 0x00, 0x30, 0x08, 0x01, 0x00, 0x00, 0x01, + 0x2c, 0x67, 0x74, 0x85, 0x80, 0x58, 0xb3, 0xc5, 0x17, 0x36, + 0x90, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, + 0x45, 0xac, 0xd3, 0x82, 0x69, 0xf3, 0x10, 0x3a, 0x97, 0x2c, + 0x6a, 0xa9, 0x78, 0x99, 0xea, 0xb0, 0xcc, 0xf7, 0xaf, 0x33, + 0x51, 0x5b, 0xdf, 0x77, 0x04, 0x18, 0x14, 0x99, 0x61, 0xeb, + 0x8d, 0x76, 0x3f, 0xd1, 0x71, 0x14, 0x43, 0x80, 0x53, 0xc2, + 0x3b, 0x9f, 0x09, 0x4f, 0xb3, 0x51, 0x04, 0x89, 0x0e, 0xc8, + 0x54, 0x12, 0xcd, 0x07, 0x20, 0xbe, 0x94, 0xc2, 0xda, 0x99, + 0xdd, 0x1e, 0xf8, 0xb0, 0x84, 0x2e, 0xf9, 0x19, 0x35, 0x36, + 0xf5, 0xd0, 0x5d, 0x82, 0x18, 0x74, 0xa0, 0x00, 0xb6, 0x15, + 0x57, 0x40, 0x5f, 0x78, 0x2d, 0x27, 0xac, 0xc7, 0x8a, 0x29, + 0x55, 0xa9, 0xcd, 0xbc, 0xf7, 0x3e, 0xff, 0xae, 0x1a, 0x5a, + 0x1d, 0xac, 0x0d, 0x78, 0x0e, 0x08, 0x33, 0x6c, 0x59, 0x70, + 0x40, 0xb9, 0x65, 0xbd, 0x35, 0xbb, 0x9a, 0x70, 0xdc, 0x93, + 0x66, 0xb0, 0xef, 0xfe, 0xf0, 0x32, 0xa6, 0xee, 0xb7, 0x03, + 0x89, 0xa2, 0x4d, 0xe0, 0xf1, 0x20, 0xdf, 0x39, 0xe8, 0xe3, + 0xcc, 0x95, 0xe9, 0x9a, 0xad, 0xbf, 0xbd, 0x7c, 0xf7, 0xd7, + 0xde, 0x47, 0x9e, 0xf6, 0x17, 0xbb, 0x84, 0xa9, 0xed, 0xf2, + 0x45, 0x61, 0x6d, 0x13, 0x0b, 0x06, 0x29, 0x50, 0xde, 0xfd, + 0x42, 0xb0, 0x66, 0x2c, 0x1c, 0x2b, 0x63, 0xcb, 0x4e, 0xb9, + 0x31, 0xc4, 0xea, 0xd2, 0x07, 0x3a, 0x08, 0x79, 0x19, 0x4b, + 0x4c, 0x50, 0x97, 0x02, 0xd7, 0x26, 0x41, 0x2f, 0xdd, 0x57, + 0xaa, 0xb0, 0xa0, 0x21, 0x4e, 0x74, 0xb6, 0x97, 0x4b, 0x8b, + 0x09, 0x9c, 0x3d, 0x29, 0xfb, 0x12, 0x27, 0x47, 0x8f, 0xb8, + 0xc5, 0x8e, 0x65, 0xcd, 0xca, 0x2f, 0xba, 0xf5, 0x3e, 0xec, + 0x56, 0xc3, 0xc9, 0xa1, 0x62, 0x7d, 0xf2, 0x9f, 0x90, 0x16, + 0x1d, 0xbf, 0x97, 0x28, 0xe1, 0x92, 0xb1, 0x53, 0xab, 0xc4, + 0xe0, 0x99, 0xbb, 0x19, 0x90, 0x7c, 0x00, 0x00, 0x29, 0x10, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 + }; + + int sockfd; + int listenfd; + int loop; + uint16_t qtype; + char *buf, *rbuf; + char *nameptr; + unsigned int i; + uint8_t llen; + uint64_t seed; + + UNUSED(arg); + + /* + * Parse named -A argument in the "qtype:saddress:sport:raddress:rport" + * syntax. Due to the syntax used, this only supports IPv4 addresses. + */ + sqtype = strdup(named_g_fuzz_addr); + RUNTIME_CHECK(sqtype != NULL); + + shost = strchr(sqtype, ':'); + RUNTIME_CHECK(shost != NULL); + *shost = 0; + shost++; + + sport = strchr(shost, ':'); + RUNTIME_CHECK(sport != NULL); + *sport = 0; + sport++; + + rhost = strchr(sport, ':'); + RUNTIME_CHECK(rhost != NULL); + *rhost = 0; + rhost++; + + rport = strchr(rhost, ':'); + RUNTIME_CHECK(rport != NULL); + *rport = 0; + rport++; + + /* + * Patch in the qtype into the question section of respacket. + */ + qtype = atoi(sqtype); + respacket[32] = (qtype >> 8) & 0xff; + respacket[33] = qtype & 0xff; + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + RUNTIME_CHECK(inet_pton(AF_INET, shost, &servaddr.sin_addr) == 1); + servaddr.sin_port = htons(atoi(sport)); + + memset(&recaddr, 0, sizeof(recaddr)); + recaddr.sin_family = AF_INET; + RUNTIME_CHECK(inet_pton(AF_INET, rhost, &recaddr.sin_addr) == 1); + recaddr.sin_port = htons(atoi(rport)); + + free(sqtype); + + /* + * Wait for named to start. This is set in run_server() in the + * named thread. + */ + while (!named_g_run_done) { + usleep(10000); + } + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + RUNTIME_CHECK(sockfd != -1); + + listenfd = socket(AF_INET, SOCK_DGRAM, 0); + RUNTIME_CHECK(listenfd != -1); + + RUNTIME_CHECK(bind(listenfd, (struct sockaddr *)&recaddr, + sizeof(struct sockaddr_in)) == 0); + + buf = malloc(65536); + rbuf = malloc(65536); + RUNTIME_CHECK(buf != NULL); + RUNTIME_CHECK(rbuf != NULL); + + seed = 42; + + /* + * Processing fuzzed packets 100,000 times before shutting down + * the app. + */ + for (loop = 0; loop < 100000; loop++) { + ssize_t length; + ssize_t sent; + unsigned short id; + socklen_t socklen; + + memset(buf, 0, 12); + length = read(0, buf, 65536); + if (length <= 0) { + usleep(1000000); + continue; + } + + if (length > 4096) { + if (getenv("AFL_CMIN")) { + free(buf); + free(rbuf); + close(sockfd); + close(listenfd); + named_server_flushonshutdown(named_g_server, + false); + isc_app_shutdown(); + return (NULL); + } + raise(SIGSTOP); + continue; + } + + if (length < 12) { + length = 12; + } + + RUNTIME_CHECK(pthread_mutex_lock(&mutex) == 0); + + ready = false; + + /* Use a unique query ID. */ + seed = 1664525 * seed + 1013904223; + id = seed & 0xffff; + respacket[0] = (id >> 8) & 0xff; + respacket[1] = id & 0xff; + + /* + * Flush any pending data on the authoritative server. + */ + socklen = sizeof(recvaddr); + (void)recvfrom(listenfd, rbuf, 65536, MSG_DONTWAIT, + (struct sockaddr *)&recvaddr, &socklen); + + /* + * Send a fixed client query to named(resolver) of + * aaaaaaaaaa.example./A. This is the starting query + * driver. + */ + sent = sendto(sockfd, respacket, sizeof(respacket), 0, + (struct sockaddr *)&servaddr, sizeof(servaddr)); + RUNTIME_CHECK(sent == sizeof(respacket)); + + /* + * named(resolver) will process the query above and send + * an upstream query to the authoritative server. We + * handle that here as the upstream authoritative server + * on listenfd. + */ + socklen = sizeof(recvaddr); + sent = recvfrom(listenfd, rbuf, 65536, 0, + (struct sockaddr *)&recvaddr, &socklen); + RUNTIME_CHECK(sent > 0); + + /* + * Copy QID and set QR so that response is always + * accepted by named(resolver). + */ + buf[0] = rbuf[0]; + buf[1] = rbuf[1]; + buf[2] |= 0x80; + + /* + * NOTE: We are not copying the QNAME or setting + * rcode=NOERROR each time. So the resolver may fail the + * client query (driver) / wander due to this. AA flag + * may also not be set based on the contents of the AFL + * fuzzed packet. + */ + + /* + * A hack - set QTYPE to the one from query so that we + * can easily share packets between instances. If we + * write over something else we'll get FORMERR anyway. + */ + + /* Skip DNS header to get to the name */ + nameptr = buf + 12; + + /* Skip the name to get to the qtype */ + i = 0; + while (((llen = nameptr[i]) != 0) && (i < 255) && + (((nameptr + i + 1 + llen) - buf) < length)) + { + i += 1 + llen; + } + + if (i <= 255) { + nameptr += 1 + i; + /* Patch the qtype */ + if ((nameptr - buf) < (length - 2)) { + *nameptr++ = (qtype >> 8) & 0xff; + *nameptr++ = qtype & 0xff; + } + /* Patch the qclass */ + if ((nameptr - buf) < (length - 2)) { + *nameptr++ = 0; + *nameptr++ = 1; + } + } + + /* + * Send the reply to named(resolver). + */ + sent = sendto(listenfd, buf, length, 0, + (struct sockaddr *)&recvaddr, sizeof(recvaddr)); + RUNTIME_CHECK(sent == length); + + /* We might get additional questions here (e.g. for CNAME). */ + for (;;) { + fd_set fds; + struct timeval tv; + int rv; + int max; + + FD_ZERO(&fds); + FD_SET(listenfd, &fds); + FD_SET(sockfd, &fds); + tv.tv_sec = 10; + tv.tv_usec = 0; + max = (listenfd > sockfd ? listenfd : sockfd) + 1; + + rv = select(max, &fds, NULL, NULL, &tv); + RUNTIME_CHECK(rv > 0); + + if (FD_ISSET(sockfd, &fds)) { + /* + * It's the reply from named(resolver) + * to the client(query driver), so we're + * done. + */ + (void)recvfrom(sockfd, buf, 65536, 0, NULL, + NULL); + break; + } + + /* + * We've got additional question (eg. due to + * CNAME). Bounce it - setting QR flag and + * NOERROR rcode and sending it back. + */ + length = recvfrom(listenfd, buf, 65536, 0, + (struct sockaddr *)&recvaddr, + &socklen); + + /* + * If this is a DNSKEY query, send the DNSKEY, + * otherwise, bounce the query. + */ + + /* Skip DNS header to get to the name */ + nameptr = buf + 12; + + /* Skip the name to get to the qtype */ + i = 0; + while (((llen = nameptr[i]) != 0) && (i < 255) && + (((nameptr + i + 1 + llen) - buf) < length)) + { + i += 1 + llen; + } + + if (i <= 255) { + nameptr += 1 + i; + /* + * Patch in the DNSKEY reply without + * touching the ID field. Note that we + * don't compare the name in the + * question section in the query, but we + * don't expect to receive any query for + * type DNSKEY but for the name + * "example." + */ + if ((nameptr - buf) < (length - 2)) { + uint8_t hb, lb; + hb = *nameptr++; + lb = *nameptr++; + qtype = (hb << 8) | lb; + + if (qtype == 48) { + memmove(buf + 2, dnskey_wf + 2, + sizeof(dnskey_wf) - 2); + length = sizeof(dnskey_wf); + } + } + } + + buf[2] |= 0x80; + buf[3] &= 0xF0; + sent = sendto(listenfd, buf, length, 0, + (struct sockaddr *)&recvaddr, + sizeof(recvaddr)); + RUNTIME_CHECK(sent == length); + } + + while (!ready) { + pthread_cond_wait(&cond, &mutex); + } + + RUNTIME_CHECK(pthread_mutex_unlock(&mutex) == 0); + } + + free(buf); + free(rbuf); + close(sockfd); + close(listenfd); + named_server_flushonshutdown(named_g_server, false); + isc_app_shutdown(); + +#ifdef __AFL_LOOP + /* + * This is here just for the signature, that's how AFL detects + * if it's a 'persistent mode' binary. It has to occur somewhere + * in the file, that's all. < wpk_> AFL checks the binary for + * this signature ("##SIG_AFL_PERSISTENT##") and runs the binary + * in persistent mode if it's present. + */ + __AFL_LOOP(0); +#endif /* ifdef __AFL_LOOP */ + + return (NULL); +} + +/* + * In "tcp:", "http:" and "rndc:" modes, this thread reads fuzzed query + * blobs from AFL from standard input and sends it to the corresponding + * TCP listening port of named (port 53 DNS, or the HTTP statistics + * channels listener or the rndc port) that is passed in the -A + * <mode>:<address>:<port> option. It can be used to test named from the + * client side. + */ +static void * +fuzz_thread_tcp(void *arg) { + char *host; + char *port; + struct sockaddr_in servaddr; + int sockfd; + char *buf; + int loop; + + UNUSED(arg); + + /* + * Parse named -A argument in the "address:port" syntax. Due to + * the syntax used, this only supports IPv4 addresses. + */ + host = strdup(named_g_fuzz_addr); + RUNTIME_CHECK(host != NULL); + + port = strchr(host, ':'); + RUNTIME_CHECK(port != NULL); + *port = 0; + ++port; + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + RUNTIME_CHECK(inet_pton(AF_INET, host, &servaddr.sin_addr) == 1); + servaddr.sin_port = htons(atoi(port)); + + free(host); + + /* + * Wait for named to start. This is set in run_server() in the + * named thread. + */ + while (!named_g_run_done) { + usleep(10000); + } + + buf = malloc(65539); + RUNTIME_CHECK(buf != NULL); + + /* + * Processing fuzzed packets 100,000 times before shutting down + * the app. + */ + for (loop = 0; loop < 100000; loop++) { + ssize_t length; + ssize_t sent; + int yes; + int r; + + if (named_g_fuzz_type == isc_fuzz_tcpclient) { + /* + * To fuzz DNS TCP client we have to put 16-bit + * message length preceding the start of packet. + */ + length = read(0, buf + 2, 65535); + buf[0] = (length >> 8) & 0xff; + buf[1] = length & 0xff; + length += 2; + } else { + /* + * Other types of TCP clients such as HTTP, etc. + */ + length = read(0, buf, 65535); + } + if (length <= 0) { + usleep(1000000); + continue; + } + if (named_g_fuzz_type == isc_fuzz_http) { + /* + * This guarantees that the request will be + * processed. + */ + INSIST(length <= 65535); + buf[length++] = '\r'; + buf[length++] = '\n'; + buf[length++] = '\r'; + buf[length++] = '\n'; + } + + RUNTIME_CHECK(pthread_mutex_lock(&mutex) == 0); + + ready = false; + yes = 1; + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + RUNTIME_CHECK(sockfd != -1); + RUNTIME_CHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == 0); + + do { + r = connect(sockfd, (struct sockaddr *)&servaddr, + sizeof(servaddr)); + if (r != 0) { + usleep(10000); + } + } while (r != 0); + + /* + * Send the fuzzed query blob to the target server. + */ + sent = write(sockfd, buf, length); + RUNTIME_CHECK(sent == length); + + close(sockfd); + + while (!ready) { + pthread_cond_wait(&cond, &mutex); + } + + RUNTIME_CHECK(pthread_mutex_unlock(&mutex) == 0); + } + + free(buf); + close(sockfd); + named_server_flushonshutdown(named_g_server, false); + isc_app_shutdown(); + + return (NULL); +} + +#endif /* ENABLE_AFL */ + +/* + * named has finished processing a message and has sent the + * reply. Signal the fuzz thread using the condition variable, to read + * and process the next item from AFL. + */ +void +named_fuzz_notify(void) { +#ifdef ENABLE_AFL + if (getenv("AFL_CMIN")) { + named_server_flushonshutdown(named_g_server, false); + isc_app_shutdown(); + return; + } + + raise(SIGSTOP); + + RUNTIME_CHECK(pthread_mutex_lock(&mutex) == 0); + + ready = true; + + RUNTIME_CHECK(pthread_cond_signal(&cond) == 0); + RUNTIME_CHECK(pthread_mutex_unlock(&mutex) == 0); +#endif /* ENABLE_AFL */ +} + +void +named_fuzz_setup(void) { +#ifdef ENABLE_AFL + if (getenv("__AFL_PERSISTENT") || getenv("AFL_CMIN")) { + pthread_t thread; + void *(fn) = NULL; + + switch (named_g_fuzz_type) { + case isc_fuzz_client: + fn = fuzz_thread_client; + break; + + case isc_fuzz_http: + case isc_fuzz_tcpclient: + case isc_fuzz_rndc: + fn = fuzz_thread_tcp; + break; + + case isc_fuzz_resolver: + fn = fuzz_thread_resolver; + break; + + default: + RUNTIME_CHECK(fn != NULL); + } + + RUNTIME_CHECK(pthread_mutex_init(&mutex, NULL) == 0); + RUNTIME_CHECK(pthread_cond_init(&cond, NULL) == 0); + RUNTIME_CHECK(pthread_create(&thread, NULL, fn, NULL) == 0); + } +#endif /* ENABLE_AFL */ +} diff --git a/bin/named/geoip.c b/bin/named/geoip.c new file mode 100644 index 0000000..0ba4ef0 --- /dev/null +++ b/bin/named/geoip.c @@ -0,0 +1,147 @@ +/* + * 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 */ + +#if defined(HAVE_GEOIP2) +#include <maxminddb.h> +#endif /* if defined(HAVE_GEOIP2) */ + +#include <isc/dir.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/geoip.h> + +#include <named/geoip.h> +#include <named/log.h> + +static dns_geoip_databases_t geoip_table; + +#if defined(HAVE_GEOIP2) +static MMDB_s geoip_country, geoip_city, geoip_as, geoip_isp, geoip_domain; + +static MMDB_s * +open_geoip2(const char *dir, const char *dbfile, MMDB_s *mmdb) { + char pathbuf[PATH_MAX]; + unsigned int n; + int ret; + + n = snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, dbfile); + if (n >= sizeof(pathbuf)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "GeoIP2 database '%s/%s': path too long", dir, + dbfile); + return (NULL); + } + + ret = MMDB_open(pathbuf, MMDB_MODE_MMAP, mmdb); + if (ret == MMDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "opened GeoIP2 database '%s'", pathbuf); + return (mmdb); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "unable to open GeoIP2 database '%s' (status %d)", + pathbuf, ret); + + return (NULL); +} +#endif /* HAVE_GEOIP2 */ + +void +named_geoip_init(void) { +#if defined(HAVE_GEOIP2) + if (named_g_geoip == NULL) { + named_g_geoip = &geoip_table; + } +#else /* if defined(HAVE_GEOIP2) */ + return; +#endif /* if defined(HAVE_GEOIP2) */ +} + +void +named_geoip_load(char *dir) { +#if defined(HAVE_GEOIP2) + REQUIRE(dir != NULL); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "looking for GeoIP2 databases in '%s'", dir); + + named_g_geoip->country = open_geoip2(dir, "GeoIP2-Country.mmdb", + &geoip_country); + if (named_g_geoip->country == NULL) { + named_g_geoip->country = open_geoip2( + dir, "GeoLite2-Country.mmdb", &geoip_country); + } + + named_g_geoip->city = open_geoip2(dir, "GeoIP2-City.mmdb", &geoip_city); + if (named_g_geoip->city == NULL) { + named_g_geoip->city = open_geoip2(dir, "GeoLite2-City.mmdb", + &geoip_city); + } + + named_g_geoip->as = open_geoip2(dir, "GeoIP2-ASN.mmdb", &geoip_as); + if (named_g_geoip->as == NULL) { + named_g_geoip->as = open_geoip2(dir, "GeoLite2-ASN.mmdb", + &geoip_as); + } + + named_g_geoip->isp = open_geoip2(dir, "GeoIP2-ISP.mmdb", &geoip_isp); + named_g_geoip->domain = open_geoip2(dir, "GeoIP2-Domain.mmdb", + &geoip_domain); +#else /* if defined(HAVE_GEOIP2) */ + UNUSED(dir); + + return; +#endif /* if defined(HAVE_GEOIP2) */ +} + +void +named_geoip_unload(void) { +#ifdef HAVE_GEOIP2 + if (named_g_geoip->country != NULL) { + MMDB_close(named_g_geoip->country); + named_g_geoip->country = NULL; + } + if (named_g_geoip->city != NULL) { + MMDB_close(named_g_geoip->city); + named_g_geoip->city = NULL; + } + if (named_g_geoip->as != NULL) { + MMDB_close(named_g_geoip->as); + named_g_geoip->as = NULL; + } + if (named_g_geoip->isp != NULL) { + MMDB_close(named_g_geoip->isp); + named_g_geoip->isp = NULL; + } + if (named_g_geoip->domain != NULL) { + MMDB_close(named_g_geoip->domain); + named_g_geoip->domain = NULL; + } +#endif /* ifdef HAVE_GEOIP2 */ +} + +void +named_geoip_shutdown(void) { +#ifdef HAVE_GEOIP2 + named_geoip_unload(); +#endif /* HAVE_GEOIP2 */ +} diff --git a/bin/named/include/dlz/dlz_dlopen_driver.h b/bin/named/include/dlz/dlz_dlopen_driver.h new file mode 100644 index 0000000..64d6388 --- /dev/null +++ b/bin/named/include/dlz/dlz_dlopen_driver.h @@ -0,0 +1,20 @@ +/* + * 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 + +isc_result_t +dlz_dlopen_init(isc_mem_t *mctx); + +void +dlz_dlopen_clear(void); diff --git a/bin/named/include/named/builtin.h b/bin/named/include/named/builtin.h new file mode 100644 index 0000000..fbfc599 --- /dev/null +++ b/bin/named/include/named/builtin.h @@ -0,0 +1,24 @@ +/* + * 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 <isc/types.h> + +isc_result_t +named_builtin_init(void); + +void +named_builtin_deinit(void); diff --git a/bin/named/include/named/config.h b/bin/named/include/named/config.h new file mode 100644 index 0000000..d9c5aa3 --- /dev/null +++ b/bin/named/include/named/config.h @@ -0,0 +1,82 @@ +/* + * 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 <dns/types.h> +#include <dns/zone.h> + +#include <isccfg/cfg.h> + +#define DEFAULT_IANA_ROOT_ZONE_PRIMARIES "_default_iana_root_zone_primaries" + +isc_result_t +named_config_parsedefaults(cfg_parser_t *parser, cfg_obj_t **conf); + +const char * +named_config_getdefault(void); + +isc_result_t +named_config_get(cfg_obj_t const *const *maps, const char *name, + const cfg_obj_t **obj); + +isc_result_t +named_checknames_get(const cfg_obj_t **maps, const char *const names[], + const cfg_obj_t **obj); + +int +named_config_listcount(const cfg_obj_t *list); + +isc_result_t +named_config_getclass(const cfg_obj_t *classobj, dns_rdataclass_t defclass, + dns_rdataclass_t *classp); + +isc_result_t +named_config_gettype(const cfg_obj_t *typeobj, dns_rdatatype_t deftype, + dns_rdatatype_t *typep); + +dns_zonetype_t +named_config_getzonetype(const cfg_obj_t *zonetypeobj); + +isc_result_t +named_config_getiplist(const cfg_obj_t *config, const cfg_obj_t *list, + in_port_t defport, isc_mem_t *mctx, + isc_sockaddr_t **addrsp, uint32_t *countp); + +void +named_config_putiplist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, + uint32_t count); + +isc_result_t +named_config_getremotesdef(const cfg_obj_t *cctx, const char *list, + const char *name, const cfg_obj_t **ret); + +isc_result_t +named_config_getipandkeylist(const cfg_obj_t *config, const char *listtype, + const cfg_obj_t *list, isc_mem_t *mctx, + dns_ipkeylist_t *ipkl); + +isc_result_t +named_config_getport(const cfg_obj_t *config, const char *type, + in_port_t *portp); + +isc_result_t +named_config_getkeyalgorithm(const char *str, const dns_name_t **name, + uint16_t *digestbits); +isc_result_t +named_config_getkeyalgorithm2(const char *str, const dns_name_t **name, + unsigned int *typep, uint16_t *digestbits); diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h new file mode 100644 index 0000000..29b5677 --- /dev/null +++ b/bin/named/include/named/control.h @@ -0,0 +1,108 @@ +/* + * 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 + * \brief + * The name server command channel. + */ + +#include <stdbool.h> + +#include <isccfg/aclconf.h> + +#include <isccc/types.h> +#include <named/types.h> + +#define NAMED_CONTROL_PORT 953 + +#define NAMED_COMMAND_STOP "stop" +#define NAMED_COMMAND_HALT "halt" +#define NAMED_COMMAND_RELOAD "reload" +#define NAMED_COMMAND_RECONFIG "reconfig" +#define NAMED_COMMAND_REFRESH "refresh" +#define NAMED_COMMAND_RETRANSFER "retransfer" +#define NAMED_COMMAND_DUMPSTATS "stats" +#define NAMED_COMMAND_QUERYLOG "querylog" +#define NAMED_COMMAND_DUMPDB "dumpdb" +#define NAMED_COMMAND_SECROOTS "secroots" +#define NAMED_COMMAND_TRACE "trace" +#define NAMED_COMMAND_NOTRACE "notrace" +#define NAMED_COMMAND_FLUSH "flush" +#define NAMED_COMMAND_FLUSHNAME "flushname" +#define NAMED_COMMAND_FLUSHTREE "flushtree" +#define NAMED_COMMAND_STATUS "status" +#define NAMED_COMMAND_TSIGLIST "tsig-list" +#define NAMED_COMMAND_TSIGDELETE "tsig-delete" +#define NAMED_COMMAND_FREEZE "freeze" +#define NAMED_COMMAND_UNFREEZE "unfreeze" +#define NAMED_COMMAND_THAW "thaw" +#define NAMED_COMMAND_TIMERPOKE "timerpoke" +#define NAMED_COMMAND_RECURSING "recursing" +#define NAMED_COMMAND_NULL "null" +#define NAMED_COMMAND_NOTIFY "notify" +#define NAMED_COMMAND_VALIDATION "validation" +#define NAMED_COMMAND_SCAN "scan" +#define NAMED_COMMAND_SIGN "sign" +#define NAMED_COMMAND_LOADKEYS "loadkeys" +#define NAMED_COMMAND_ADDZONE "addzone" +#define NAMED_COMMAND_MODZONE "modzone" +#define NAMED_COMMAND_DELZONE "delzone" +#define NAMED_COMMAND_SHOWZONE "showzone" +#define NAMED_COMMAND_SYNC "sync" +#define NAMED_COMMAND_SIGNING "signing" +#define NAMED_COMMAND_DNSSEC "dnssec" +#define NAMED_COMMAND_ZONESTATUS "zonestatus" +#define NAMED_COMMAND_NTA "nta" +#define NAMED_COMMAND_TESTGEN "testgen" +#define NAMED_COMMAND_MKEYS "managed-keys" +#define NAMED_COMMAND_DNSTAPREOPEN "dnstap-reopen" +#define NAMED_COMMAND_DNSTAP "dnstap" +#define NAMED_COMMAND_TCPTIMEOUTS "tcp-timeouts" +#define NAMED_COMMAND_SERVESTALE "serve-stale" + +isc_result_t +named_controls_create(named_server_t *server, named_controls_t **ctrlsp); +/*%< + * Create an initial, empty set of command channels for 'server'. + */ + +void +named_controls_destroy(named_controls_t **ctrlsp); +/*%< + * Destroy a set of command channels. + * + * Requires: + * Shutdown of the channels has completed. + */ + +isc_result_t +named_controls_configure(named_controls_t *controls, const cfg_obj_t *config, + cfg_aclconfctx_t *aclconfctx); +/*%< + * Configure zero or more command channels into 'controls' + * as defined in the configuration parse tree 'config'. + * The channels will evaluate ACLs in the context of + * 'aclconfctx'. + */ + +void +named_controls_shutdown(named_controls_t *controls); +/*%< + * Initiate shutdown of all the command channels in 'controls'. + */ + +isc_result_t +named_control_docommand(isccc_sexpr_t *message, bool readonly, + isc_buffer_t **text); diff --git a/bin/named/include/named/fuzz.h b/bin/named/include/named/fuzz.h new file mode 100644 index 0000000..69af8da --- /dev/null +++ b/bin/named/include/named/fuzz.h @@ -0,0 +1,22 @@ +/* + * 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 <isc/fuzz.h> + +#pragma once + +void +named_fuzz_notify(void); + +void +named_fuzz_setup(void); diff --git a/bin/named/include/named/geoip.h b/bin/named/include/named/geoip.h new file mode 100644 index 0000000..d1852ef --- /dev/null +++ b/bin/named/include/named/geoip.h @@ -0,0 +1,28 @@ +/* + * 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 + +extern dns_geoip_databases_t *named_g_geoip; + +void +named_geoip_init(void); + +void +named_geoip_load(char *dir); + +void +named_geoip_unload(void); + +void +named_geoip_shutdown(void); diff --git a/bin/named/include/named/globals.h b/bin/named/include/named/globals.h new file mode 100644 index 0000000..c65e933 --- /dev/null +++ b/bin/named/include/named/globals.h @@ -0,0 +1,163 @@ +/* + * 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 <stdbool.h> + +#include <isc/log.h> +#include <isc/net.h> +#include <isc/netmgr.h> +#include <isc/rwlock.h> + +#include <dns/acl.h> +#include <dns/zone.h> + +#include <isccfg/aclconf.h> +#include <isccfg/cfg.h> + +#include <dst/dst.h> +#include <named/fuzz.h> +#include <named/types.h> + +#undef EXTERN +#undef INIT +#ifdef NAMED_MAIN +#define EXTERN +#define INIT(v) = (v) +#else /* ifdef NAMED_MAIN */ +#define EXTERN extern +#define INIT(v) +#endif /* ifdef NAMED_MAIN */ + +#ifndef NAMED_RUN_PID_DIR +#define NAMED_RUN_PID_DIR 1 +#endif /* ifndef NAMED_RUN_PID_DIR */ + +EXTERN isc_mem_t *named_g_mctx INIT(NULL); +EXTERN unsigned int named_g_cpus INIT(0); +EXTERN unsigned int named_g_udpdisp INIT(0); +EXTERN isc_taskmgr_t *named_g_taskmgr INIT(NULL); +EXTERN dns_dispatchmgr_t *named_g_dispatchmgr INIT(NULL); +EXTERN unsigned int named_g_cpus_detected INIT(1); + +#ifdef ENABLE_AFL +EXTERN bool named_g_run_done INIT(false); +#endif /* ifdef ENABLE_AFL */ +/* + * XXXRTH We're going to want multiple timer managers eventually. One + * for really short timers, another for client timers, and one + * for zone timers. + */ +EXTERN isc_timermgr_t *named_g_timermgr INIT(NULL); +EXTERN isc_nm_t *named_g_netmgr INIT(NULL); +EXTERN cfg_parser_t *named_g_parser INIT(NULL); +EXTERN cfg_parser_t *named_g_addparser INIT(NULL); +EXTERN const char *named_g_version INIT(PACKAGE_VERSION); +EXTERN const char *named_g_product INIT(PACKAGE_NAME); +EXTERN const char *named_g_description INIT(PACKAGE_DESCRIPTION); +EXTERN const char *named_g_srcid INIT(PACKAGE_SRCID); +EXTERN const char *named_g_configargs INIT(PACKAGE_CONFIGARGS); +EXTERN const char *named_g_builder INIT(PACKAGE_BUILDER); +EXTERN in_port_t named_g_port INIT(0); +EXTERN in_port_t named_g_tlsport INIT(0); +EXTERN in_port_t named_g_httpsport INIT(0); +EXTERN in_port_t named_g_httpport INIT(0); + +EXTERN in_port_t named_g_http_listener_clients INIT(0); +EXTERN in_port_t named_g_http_streams_per_conn INIT(0); + +EXTERN named_server_t *named_g_server INIT(NULL); + +/* + * Logging. + */ +EXTERN isc_log_t *named_g_lctx INIT(NULL); +EXTERN isc_logcategory_t *named_g_categories INIT(NULL); +EXTERN isc_logmodule_t *named_g_modules INIT(NULL); +EXTERN unsigned int named_g_debuglevel INIT(0); + +/* + * Current configuration information. + */ +EXTERN cfg_obj_t *named_g_config INIT(NULL); +EXTERN const cfg_obj_t *named_g_defaults INIT(NULL); +EXTERN const char *named_g_conffile INIT(NAMED_SYSCONFDIR "/named.conf"); +EXTERN const char *named_g_defaultbindkeys INIT(NAMED_SYSCONFDIR "/bind.keys"); +EXTERN const char *named_g_keyfile INIT(NAMED_SYSCONFDIR "/rndc.key"); + +EXTERN dns_tsigkey_t *named_g_sessionkey INIT(NULL); +EXTERN dns_name_t named_g_sessionkeyname; +EXTERN bool named_g_conffileset INIT(false); +EXTERN cfg_aclconfctx_t *named_g_aclconfctx INIT(NULL); + +/* + * Initial resource limits. + */ +EXTERN isc_resourcevalue_t named_g_initstacksize INIT(0); +EXTERN isc_resourcevalue_t named_g_initdatasize INIT(0); +EXTERN isc_resourcevalue_t named_g_initcoresize INIT(0); +EXTERN isc_resourcevalue_t named_g_initopenfiles INIT(0); + +/* + * Misc. + */ +EXTERN bool named_g_coreok INIT(true); +EXTERN const char *named_g_chrootdir INIT(NULL); +EXTERN bool named_g_foreground INIT(false); +EXTERN bool named_g_logstderr INIT(false); +EXTERN bool named_g_nosyslog INIT(false); +EXTERN const char *named_g_logfile INIT(NULL); + +EXTERN const char *named_g_defaultsessionkeyfile INIT(NAMED_LOCALSTATEDIR + "/run/named/" + "session.key"); +EXTERN const char *named_g_defaultlockfile INIT(NAMED_LOCALSTATEDIR "/run/" + "named/" + "named." + "lock"); +EXTERN bool named_g_forcelock INIT(false); + +#if NAMED_RUN_PID_DIR +EXTERN const char *named_g_defaultpidfile INIT(NAMED_LOCALSTATEDIR "/run/named/" + "named.pid"); +#else /* if NAMED_RUN_PID_DIR */ +EXTERN const char *named_g_defaultpidfile INIT(NAMED_LOCALSTATEDIR "/run/" + "named.pid"); +#endif /* if NAMED_RUN_PID_DIR */ + +EXTERN const char *named_g_username INIT(NULL); + +EXTERN const char *named_g_engine INIT(NULL); + +EXTERN isc_time_t named_g_boottime; +EXTERN isc_time_t named_g_configtime; +EXTERN bool named_g_memstatistics INIT(false); +EXTERN bool named_g_keepstderr INIT(false); + +EXTERN unsigned int named_g_tat_interval INIT(24 * 3600); +EXTERN unsigned int named_g_maxcachesize INIT(0); + +#if defined(HAVE_GEOIP2) +EXTERN dns_geoip_databases_t *named_g_geoip INIT(NULL); +#endif /* if defined(HAVE_GEOIP2) */ + +EXTERN const char *named_g_fuzz_addr INIT(NULL); +EXTERN isc_fuzztype_t named_g_fuzz_type INIT(isc_fuzz_none); + +EXTERN dns_acl_t *named_g_mapped INIT(NULL); + +#undef EXTERN +#undef INIT diff --git a/bin/named/include/named/log.h b/bin/named/include/named/log.h new file mode 100644 index 0000000..f18e93a --- /dev/null +++ b/bin/named/include/named/log.h @@ -0,0 +1,84 @@ +/* + * 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 <isc/log.h> +#include <isc/types.h> + +#include <dns/log.h> + +#include <named/globals.h> /* Required for named_g_(categories|modules). */ + +/* Unused slot 0. */ +#define NAMED_LOGCATEGORY_UNMATCHED (&named_g_categories[1]) + +/* + * Backwards compatibility. + */ +#define NAMED_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL + +#define NAMED_LOGMODULE_MAIN (&named_g_modules[0]) +#define NAMED_LOGMODULE_SERVER (&named_g_modules[1]) +#define NAMED_LOGMODULE_CONTROL (&named_g_modules[2]) + +isc_result_t +named_log_init(bool safe); +/*% + * Initialize the logging system and set up an initial default + * logging default configuration that will be used until the + * config file has been read. + * + * If 'safe' is true, use a default configuration that refrains + * from opening files. This is to avoid creating log files + * as root. + */ + +void +named_log_setdefaultchannels(isc_logconfig_t *lcfg); +/*% + * Set up logging channels according to the named defaults, which + * may differ from the logging library defaults. Currently, + * this just means setting up default_debug. + */ + +void +named_log_setsafechannels(isc_logconfig_t *lcfg); +/*% + * Like named_log_setdefaultchannels(), but omits any logging to files. + */ + +void +named_log_setdefaultsslkeylogfile(isc_logconfig_t *lcfg); +/*% + * If the SSLKEYLOGFILE environment variable is set, sets up a default + * logging channel for writing TLS pre-master secrets to the path stored + * in that environment variable (for debugging purposes). + */ + +isc_result_t +named_log_setdefaultcategory(isc_logconfig_t *lcfg); +/*% + * Set up "category default" to go to the right places. + */ + +isc_result_t +named_log_setunmatchedcategory(isc_logconfig_t *lcfg); +/*% + * Set up "category unmatched" to go to the right places. + */ + +void +named_log_shutdown(void); diff --git a/bin/named/include/named/logconf.h b/bin/named/include/named/logconf.h new file mode 100644 index 0000000..65add46 --- /dev/null +++ b/bin/named/include/named/logconf.h @@ -0,0 +1,25 @@ +/* + * 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 <isc/log.h> + +isc_result_t +named_logconfig(isc_logconfig_t *logconf, const cfg_obj_t *logstmt); +/*%< + * Set up the logging configuration in '*logconf' according to + * the named.conf data in 'logstmt'. + */ diff --git a/bin/named/include/named/main.h b/bin/named/include/named/main.h new file mode 100644 index 0000000..42fd138 --- /dev/null +++ b/bin/named/include/named/main.h @@ -0,0 +1,36 @@ +/* + * 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/attributes.h> + +/*! \file */ + +#ifdef ISC_MAIN_HOOK +#define main(argc, argv) bindmain(argc, argv) +#endif /* ifdef ISC_MAIN_HOOK */ + +/* + * Commandline arguments for named; + */ +#define NAMED_MAIN_ARGS "46A:c:Cd:D:E:fFgL:M:m:n:N:p:sS:t:T:U:u:vVx:X:" + +noreturn void +named_main_earlyfatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +void +named_main_earlywarning(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +void +named_main_setmemstats(const char *); diff --git a/bin/named/include/named/os.h b/bin/named/include/named/os.h new file mode 100644 index 0000000..0f7c1c5 --- /dev/null +++ b/bin/named/include/named/os.h @@ -0,0 +1,75 @@ +/* + * 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 <pwd.h> +#include <stdbool.h> + +#include <isc/types.h> + +void +named_os_init(const char *progname); + +void +named_os_daemonize(void); + +void +named_os_opendevnull(void); + +void +named_os_closedevnull(void); + +void +named_os_chroot(const char *root); + +void +named_os_inituserinfo(const char *username); + +void +named_os_changeuser(void); + +uid_t +ns_os_uid(void); + +void +named_os_adjustnofile(void); + +void +named_os_minprivs(void); + +FILE * +named_os_openfile(const char *filename, mode_t mode, bool switch_user); + +void +named_os_writepidfile(const char *filename, bool first_time); + +bool +named_os_issingleton(const char *filename); + +void +named_os_shutdown(void); + +void +named_os_shutdownmsg(char *command, isc_buffer_t *text); + +void +named_os_tzset(void); + +void +named_os_started(void); + +const char * +named_os_uname(void); diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h new file mode 100644 index 0000000..075e2ec --- /dev/null +++ b/bin/named/include/named/server.h @@ -0,0 +1,396 @@ +/* + * 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/log.h> +#include <isc/magic.h> +#include <isc/quota.h> +#include <isc/sockaddr.h> +#include <isc/tls.h> +#include <isc/types.h> + +#include <dns/acl.h> +#include <dns/dnstap.h> +#include <dns/stats.h> +#include <dns/types.h> + +#include <ns/interfacemgr.h> +#include <ns/server.h> +#include <ns/stats.h> +#include <ns/types.h> + +#include <named/types.h> + +#define NAMED_EVENTCLASS ISC_EVENTCLASS(0x4E43) +#define NAMED_EVENT_RELOAD (NAMED_EVENTCLASS + 0) +#define NAMED_EVENT_DELZONE (NAMED_EVENTCLASS + 1) +#define NAMED_EVENT_COMMAND (NAMED_EVENTCLASS + 2) +#define NAMED_EVENT_TATSEND (NAMED_EVENTCLASS + 3) + +/*% + * Name server state. Better here than in lots of separate global variables. + */ +struct named_server { + unsigned int magic; + isc_mem_t *mctx; + + ns_server_t *sctx; + + isc_task_t *task; + + char *statsfile; /*%< Statistics file name */ + char *dumpfile; /*%< Dump file name */ + char *secrootsfile; /*%< Secroots file name */ + char *bindkeysfile; /*%< bind.keys file name + * */ + char *recfile; /*%< Recursive file name */ + bool version_set; /*%< User has set version + * */ + char *version; /*%< User-specified version */ + bool hostname_set; /*%< User has set hostname + * */ + char *hostname; /*%< User-specified hostname + * */ + + /* Server data structures. */ + dns_loadmgr_t *loadmgr; + dns_zonemgr_t *zonemgr; + dns_viewlist_t viewlist; + dns_kasplist_t kasplist; + ns_interfacemgr_t *interfacemgr; + dns_db_t *in_roothints; + + isc_timer_t *interface_timer; + isc_timer_t *heartbeat_timer; + isc_timer_t *pps_timer; + isc_timer_t *tat_timer; + + uint32_t interface_interval; + uint32_t heartbeat_interval; + + atomic_int reload_status; + + bool flushonshutdown; + + named_cachelist_t cachelist; /*%< Possibly shared caches + * */ + isc_stats_t *zonestats; /*% Zone management stats */ + isc_stats_t *resolverstats; /*% Resolver stats */ + isc_stats_t *sockstats; /*%< Socket stats */ + + named_controls_t *controls; /*%< Control channels */ + unsigned int dispatchgen; + named_dispatchlist_t dispatches; + + named_statschannellist_t statschannels; + + dst_key_t *sessionkey; + char *session_keyfile; + dns_name_t *session_keyname; + unsigned int session_keyalg; + uint16_t session_keybits; + bool interface_auto; + unsigned char secret[32]; /*%< Server Cookie Secret */ + ns_cookiealg_t cookiealg; + + dns_dtenv_t *dtenv; /*%< Dnstap environment */ + + char *lockfile; + + isc_tlsctx_cache_t *tlsctx_server_cache; + isc_tlsctx_cache_t *tlsctx_client_cache; +}; + +#define NAMED_SERVER_MAGIC ISC_MAGIC('S', 'V', 'E', 'R') +#define NAMED_SERVER_VALID(s) ISC_MAGIC_VALID(s, NAMED_SERVER_MAGIC) + +void +named_server_create(isc_mem_t *mctx, named_server_t **serverp); +/*%< + * Create a server object with default settings. + * This function either succeeds or causes the program to exit + * with a fatal error. + */ + +void +named_server_destroy(named_server_t **serverp); +/*%< + * Destroy a server object, freeing its memory. + */ + +void +named_server_reloadwanted(named_server_t *server); +/*%< + * Inform a server that a reload is wanted. This function + * may be called asynchronously, from outside the server's task. + * If a reload is already scheduled or in progress, the call + * is ignored. + */ + +void +named_server_scan_interfaces(named_server_t *server); +/*%< + * Trigger a interface scan. + * Must only be called when running under server->task. + */ + +void +named_server_flushonshutdown(named_server_t *server, bool flush); +/*%< + * Inform the server that the zones should be flushed to disk on shutdown. + */ + +isc_result_t +named_server_reloadcommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); +/*%< + * Act on a "reload" command from the command channel. + */ + +isc_result_t +named_server_reconfigcommand(named_server_t *server); +/*%< + * Act on a "reconfig" command from the command channel. + */ + +isc_result_t +named_server_notifycommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); +/*%< + * Act on a "notify" command from the command channel. + */ + +isc_result_t +named_server_refreshcommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); +/*%< + * Act on a "refresh" command from the command channel. + */ + +isc_result_t +named_server_retransfercommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); +/*%< + * Act on a "retransfer" command from the command channel. + */ + +isc_result_t +named_server_togglequerylog(named_server_t *server, isc_lex_t *lex); +/*%< + * Enable/disable logging of queries. (Takes "yes" or "no" argument, + * but can also be used as a toggle for backward comptibility.) + */ + +/*% + * Save the current NTAs for all views to files. + */ +isc_result_t +named_server_saventa(named_server_t *server); + +/*% + * Load NTAs for all views from files. + */ +isc_result_t +named_server_loadnta(named_server_t *server); + +/*% + * Dump the current statistics to the statistics file. + */ +isc_result_t +named_server_dumpstats(named_server_t *server); + +/*% + * Dump the current cache to the dump file. + */ +isc_result_t +named_server_dumpdb(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Dump the current security roots to the secroots file. + */ +isc_result_t +named_server_dumpsecroots(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Change or increment the server debug level. + */ +isc_result_t +named_server_setdebuglevel(named_server_t *server, isc_lex_t *lex); + +/*% + * Flush the server's cache(s) + */ +isc_result_t +named_server_flushcache(named_server_t *server, isc_lex_t *lex); + +/*% + * Flush a particular name from the server's cache. If 'tree' is false, + * also flush the name from the ADB and badcache. If 'tree' is true, also + * flush all the names under the specified name. + */ +isc_result_t +named_server_flushnode(named_server_t *server, isc_lex_t *lex, bool tree); + +/*% + * Report the server's status. + */ +isc_result_t +named_server_status(named_server_t *server, isc_buffer_t **text); + +/*% + * Report a list of dynamic and static tsig keys, per view. + */ +isc_result_t +named_server_tsiglist(named_server_t *server, isc_buffer_t **text); + +/*% + * Delete a specific key (with optional view). + */ +isc_result_t +named_server_tsigdelete(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Enable or disable updates for a zone. + */ +isc_result_t +named_server_freeze(named_server_t *server, bool freeze, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Dump zone updates to disk, optionally removing the journal file + */ +isc_result_t +named_server_sync(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Update a zone's DNSKEY set from the key repository. If + * the command that triggered the call to this function was "sign", + * then force a full signing of the zone. If it was "loadkeys", + * then don't sign the zone; any needed changes to signatures can + * take place incrementally. + */ +isc_result_t +named_server_rekey(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Dump the current recursive queries. + */ +isc_result_t +named_server_dumprecursing(named_server_t *server); + +/*% + * Maintain a list of dispatches that require reserved ports. + */ +void +named_add_reserved_dispatch(named_server_t *server, const isc_sockaddr_t *addr); + +/*% + * Enable or disable dnssec validation. + */ +isc_result_t +named_server_validation(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Add a zone to a running process, or modify an existing zone + */ +isc_result_t +named_server_changezone(named_server_t *server, char *command, + isc_buffer_t **text); + +/*% + * Deletes a zone from a running process + */ +isc_result_t +named_server_delzone(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Show current configuration for a given zone + */ +isc_result_t +named_server_showzone(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Lists the status of the signing records for a given zone. + */ +isc_result_t +named_server_signing(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Lists the DNSSEC status for a given zone. + */ +isc_result_t +named_server_dnssec(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Lists status information for a given zone (e.g., name, type, files, + * load time, expiry, etc). + */ +isc_result_t +named_server_zonestatus(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Adds/updates a Negative Trust Anchor (NTA) for a specified name and + * duration, in a particular view if specified, or in all views. + */ +isc_result_t +named_server_nta(named_server_t *server, isc_lex_t *lex, bool readonly, + isc_buffer_t **text); + +/*% + * Generates a test sequence that is only for use in system tests. The + * argument is the size of required output in bytes. + */ +isc_result_t +named_server_testgen(isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Force fefresh or print status for managed keys zones. + */ +isc_result_t +named_server_mkeys(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Close and reopen DNSTAP output file. + */ +isc_result_t +named_server_dnstap(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + +/*% + * Display or update tcp-{initial,idle,keepalive,advertised}-timeout options. + */ +isc_result_t +named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Control whether stale answers are served or not when configured in + * named.conf. + */ +isc_result_t +named_server_servestale(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); diff --git a/bin/named/include/named/smf_globals.h b/bin/named/include/named/smf_globals.h new file mode 100644 index 0000000..b052822 --- /dev/null +++ b/bin/named/include/named/smf_globals.h @@ -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. + */ + +#pragma once + +#include <libscf.h> + +#undef EXTERN +#undef INIT +#ifdef NAMED_MAIN +#define EXTERN +#define INIT(v) = (v) +#else /* ifdef NAMED_MAIN */ +#define EXTERN extern +#define INIT(v) +#endif /* ifdef NAMED_MAIN */ + +EXTERN unsigned int named_smf_got_instance INIT(0); +EXTERN unsigned int named_smf_chroot INIT(0); +EXTERN unsigned int named_smf_want_disable INIT(0); + +isc_result_t +named_smf_add_message(isc_buffer_t **text); +isc_result_t +named_smf_get_instance(char **name, int debug, isc_mem_t *mctx); + +#undef EXTERN +#undef INIT diff --git a/bin/named/include/named/statschannel.h b/bin/named/include/named/statschannel.h new file mode 100644 index 0000000..8240dc1 --- /dev/null +++ b/bin/named/include/named/statschannel.h @@ -0,0 +1,51 @@ +/* + * 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 + * \brief + * The statistics channels built-in the name server. + */ + +#include <isccfg/aclconf.h> + +#include <isccc/types.h> +#include <named/types.h> + +#define NAMED_STATSCHANNEL_HTTPPORT 80 + +isc_result_t +named_statschannels_configure(named_server_t *server, const cfg_obj_t *config, + cfg_aclconfctx_t *aclconfctx); +/*%< + * [Re]configure the statistics channels. + * + * If it is no longer there but was previously configured, destroy + * it here. + * + * If the IP address or port has changed, destroy the old server + * and create a new one. + */ + +void +named_statschannels_shutdown(named_server_t *server); +/*%< + * Initiate shutdown of all the statistics channel listeners. + */ + +isc_result_t +named_stats_dump(named_server_t *server, FILE *fp); +/*%< + * Dump statistics counters managed by the server to the file fp. + */ diff --git a/bin/named/include/named/tkeyconf.h b/bin/named/include/named/tkeyconf.h new file mode 100644 index 0000000..79639d6 --- /dev/null +++ b/bin/named/include/named/tkeyconf.h @@ -0,0 +1,43 @@ +/* + * 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 <isc/lang.h> +#include <isc/types.h> + +#include <isccfg/cfg.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +named_tkeyctx_fromconfig(const cfg_obj_t *options, isc_mem_t *mctx, + dns_tkeyctx_t **tctxp); +/*%< + * Create a TKEY context and configure it, including the default DH key + * and default domain, according to 'options'. + * + * Requires: + *\li 'cfg' is a valid configuration options object. + *\li 'mctx' is not NULL + *\li 'tctx' is not NULL + *\li '*tctx' is NULL + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + */ + +ISC_LANG_ENDDECLS diff --git a/bin/named/include/named/transportconf.h b/bin/named/include/named/transportconf.h new file mode 100644 index 0000000..1e472ff --- /dev/null +++ b/bin/named/include/named/transportconf.h @@ -0,0 +1,43 @@ +/* + * 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 <isc/lang.h> +#include <isc/types.h> + +#include <dns/transport.h> + +#include <isccfg/cfg.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +named_transports_fromconfig(const cfg_obj_t *config, const cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_transport_list_t **listp); +/*%< + * Create a list of transport objects (DoT or DoH) and configure them + * according to 'key-file', 'cert-file', 'ca-file' or 'hostname' + * statements. + * + * Requires: + * \li 'config' is not NULL. + * \li 'vconfig' is not NULL. + * \li 'mctx' is not NULL + * \li 'listp' is not NULL, and '*listp' is NULL + * + */ + +ISC_LANG_ENDDECLS diff --git a/bin/named/include/named/tsigconf.h b/bin/named/include/named/tsigconf.h new file mode 100644 index 0000000..32a0120 --- /dev/null +++ b/bin/named/include/named/tsigconf.h @@ -0,0 +1,41 @@ +/* + * 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 <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +named_tsigkeyring_fromconfig(const cfg_obj_t *config, const cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_tsig_keyring_t **ringp); +/*%< + * Create a TSIG key ring and configure it according to the 'key' + * statements in the global and view configuration objects. + * + * Requires: + * \li 'config' is not NULL. + * \li 'vconfig' is not NULL. + * \li 'mctx' is not NULL + * \li 'ringp' is not NULL, and '*ringp' is NULL + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + */ + +ISC_LANG_ENDDECLS diff --git a/bin/named/include/named/types.h b/bin/named/include/named/types.h new file mode 100644 index 0000000..585c141 --- /dev/null +++ b/bin/named/include/named/types.h @@ -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. + */ + +#pragma once + +/*! \file */ + +#include <dns/types.h> + +typedef struct named_cache named_cache_t; +typedef ISC_LIST(named_cache_t) named_cachelist_t; +typedef struct named_server named_server_t; +typedef struct named_xmld named_xmld_t; +typedef struct named_xmldmgr named_xmldmgr_t; +typedef struct named_controls named_controls_t; +typedef struct named_dispatch named_dispatch_t; +typedef ISC_LIST(named_dispatch_t) named_dispatchlist_t; +typedef struct named_statschannel named_statschannel_t; +typedef ISC_LIST(named_statschannel_t) named_statschannellist_t; + +/*% + * Used for server->reload_status as printed by `rndc status` + */ +typedef enum { + NAMED_RELOAD_DONE, + NAMED_RELOAD_IN_PROGRESS, + NAMED_RELOAD_FAILED, +} named_reload_t; diff --git a/bin/named/include/named/zoneconf.h b/bin/named/include/named/zoneconf.h new file mode 100644 index 0000000..387d8a1 --- /dev/null +++ b/bin/named/include/named/zoneconf.h @@ -0,0 +1,76 @@ +/* + * 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 <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +#include <isccfg/aclconf.h> +#include <isccfg/cfg.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, + const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, + dns_kasplist_t *kasplist, dns_zone_t *zone, + dns_zone_t *raw); +/*%< + * Configure or reconfigure a zone according to the named.conf + * data. + * + * The zone origin is not configured, it is assumed to have been set + * at zone creation time. + * + * Require: + * \li 'ac' to point to an initialized cfg_aclconfctx_t. + * \li 'kasplist' to be initialized. + * \li 'zone' to be initialized. + */ + +bool +named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig); +/*%< + * If 'zone' can be safely reconfigured according to the configuration + * data in 'zconfig', return true. If the configuration data is so + * different from the current zone state that the zone needs to be destroyed + * and recreated, return false. + */ + +bool +named_zone_inlinesigning(const cfg_obj_t *zconfig); +/*%< + * Determine if zone uses inline-signing. This is true if inline-signing + * is set to yes. + */ + +isc_result_t +named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone, + dns_rdataclass_t rdclass, dns_name_t *name); +/*%> + * configure a DLZ zone, setting up the database methods and calling + * postload to load the origin values + * + * Require: + * \li 'dlzdatabase' to be a valid dlz database + * \li 'zone' to be initialized. + * \li 'rdclass' to be a valid rdataclass + * \li 'name' to be a valid zone origin name + */ + +ISC_LANG_ENDDECLS diff --git a/bin/named/log.c b/bin/named/log.c new file mode 100644 index 0000000..6efea02 --- /dev/null +++ b/bin/named/log.c @@ -0,0 +1,253 @@ +/* + * 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 <stdlib.h> + +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/log.h> + +#include <isccfg/log.h> + +#include <ns/log.h> + +#include <named/log.h> + +#ifndef ISC_FACILITY +#define ISC_FACILITY LOG_DAEMON +#endif /* ifndef ISC_FACILITY */ + +/*% + * When adding a new category, be sure to add the appropriate + * \#define to <named/log.h> and to update the list in + * bin/check/check-tool.c. + */ +static isc_logcategory_t categories[] = { { "", 0 }, + { "unmatched", 0 }, + { NULL, 0 } }; + +/*% + * When adding a new module, be sure to add the appropriate + * \#define to <dns/log.h>. + */ +static isc_logmodule_t modules[] = { + { "main", 0 }, { "server", 0 }, { "control", 0 }, { NULL, 0 } +}; + +isc_result_t +named_log_init(bool safe) { + isc_result_t result; + isc_logconfig_t *lcfg = NULL; + + named_g_categories = categories; + named_g_modules = modules; + + /* + * Setup a logging context. + */ + isc_log_create(named_g_mctx, &named_g_lctx, &lcfg); + + /* + * named-checktool.c:setup_logging() needs to be kept in sync. + */ + isc_log_registercategories(named_g_lctx, named_g_categories); + isc_log_registermodules(named_g_lctx, named_g_modules); + isc_log_setcontext(named_g_lctx); + dns_log_init(named_g_lctx); + dns_log_setcontext(named_g_lctx); + cfg_log_init(named_g_lctx); + ns_log_init(named_g_lctx); + ns_log_setcontext(named_g_lctx); + + if (safe) { + named_log_setsafechannels(lcfg); + } else { + named_log_setdefaultchannels(lcfg); + } + + result = named_log_setdefaultcategory(lcfg); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + named_log_setdefaultsslkeylogfile(lcfg); + + return (ISC_R_SUCCESS); + +cleanup: + isc_log_destroy(&named_g_lctx); + isc_log_setcontext(NULL); + dns_log_setcontext(NULL); + + return (result); +} + +void +named_log_setdefaultchannels(isc_logconfig_t *lcfg) { + isc_logdestination_t destination; + + /* + * By default, the logging library makes "default_debug" log to + * stderr. In BIND, we want to override this and log to named.run + * instead, unless the -g option was given. + */ + if (!named_g_logstderr) { + destination.file.stream = NULL; + destination.file.name = "named.run"; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(lcfg, "default_debug", ISC_LOG_TOFILE, + ISC_LOG_DYNAMIC, &destination, + ISC_LOG_PRINTTIME | ISC_LOG_DEBUGONLY); + } + + if (named_g_logfile != NULL) { + destination.file.stream = NULL; + destination.file.name = named_g_logfile; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(lcfg, "default_logfile", ISC_LOG_TOFILE, + ISC_LOG_DYNAMIC, &destination, + ISC_LOG_PRINTTIME | + ISC_LOG_PRINTCATEGORY | + ISC_LOG_PRINTLEVEL); + } + +#if ISC_FACILITY != LOG_DAEMON + destination.facility = ISC_FACILITY; + isc_log_createchannel(lcfg, "default_syslog", ISC_LOG_TOSYSLOG, + ISC_LOG_INFO, &destination, 0); +#endif /* if ISC_FACILITY != LOG_DAEMON */ + + /* + * Set the initial debug level. + */ + isc_log_setdebuglevel(named_g_lctx, named_g_debuglevel); +} + +void +named_log_setsafechannels(isc_logconfig_t *lcfg) { + isc_logdestination_t destination; + + if (!named_g_logstderr) { + isc_log_createchannel(lcfg, "default_debug", ISC_LOG_TONULL, + ISC_LOG_DYNAMIC, NULL, 0); + + /* + * Setting the debug level to zero should get the output + * discarded a bit faster. + */ + isc_log_setdebuglevel(named_g_lctx, 0); + } else { + isc_log_setdebuglevel(named_g_lctx, named_g_debuglevel); + } + + if (named_g_logfile != NULL) { + destination.file.stream = NULL; + destination.file.name = named_g_logfile; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(lcfg, "default_logfile", ISC_LOG_TOFILE, + ISC_LOG_DYNAMIC, &destination, + ISC_LOG_PRINTTIME | + ISC_LOG_PRINTCATEGORY | + ISC_LOG_PRINTLEVEL); + } + +#if ISC_FACILITY != LOG_DAEMON + destination.facility = ISC_FACILITY; + isc_log_createchannel(lcfg, "default_syslog", ISC_LOG_TOSYSLOG, + ISC_LOG_INFO, &destination, 0); +#endif /* if ISC_FACILITY != LOG_DAEMON */ +} + +/* + * If the SSLKEYLOGFILE environment variable is set, TLS pre-master secrets are + * logged (for debugging purposes) to the file whose path is provided in that + * variable. Set up a default logging channel which maintains up to 10 files + * containing TLS pre-master secrets, each up to 100 MB in size. If the + * SSLKEYLOGFILE environment variable is set to the string "config", suppress + * creation of the default channel, allowing custom logging channel + * configuration for TLS pre-master secrets to be provided via the "logging" + * stanza in the configuration file. + */ +void +named_log_setdefaultsslkeylogfile(isc_logconfig_t *lcfg) { + const char *sslkeylogfile_path = getenv("SSLKEYLOGFILE"); + isc_logdestination_t destination = { + .file = { + .name = sslkeylogfile_path, + .versions = 10, + .suffix = isc_log_rollsuffix_timestamp, + .maximum_size = 100 * 1024 * 1024, + }, + }; + isc_result_t result; + + if (sslkeylogfile_path == NULL || + strcmp(sslkeylogfile_path, "config") == 0) + { + return; + } + + isc_log_createchannel(lcfg, "default_sslkeylogfile", ISC_LOG_TOFILE, + ISC_LOG_INFO, &destination, 0); + result = isc_log_usechannel(lcfg, "default_sslkeylogfile", + ISC_LOGCATEGORY_SSLKEYLOG, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +isc_result_t +named_log_setdefaultcategory(isc_logconfig_t *lcfg) { + isc_result_t result = ISC_R_SUCCESS; + + result = isc_log_usechannel(lcfg, "default_debug", + ISC_LOGCATEGORY_DEFAULT, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (!named_g_logstderr) { + if (named_g_logfile != NULL) { + result = isc_log_usechannel(lcfg, "default_logfile", + ISC_LOGCATEGORY_DEFAULT, + NULL); + } else if (!named_g_nosyslog) { + result = isc_log_usechannel(lcfg, "default_syslog", + ISC_LOGCATEGORY_DEFAULT, + NULL); + } + } + +cleanup: + return (result); +} + +isc_result_t +named_log_setunmatchedcategory(isc_logconfig_t *lcfg) { + isc_result_t result; + + result = isc_log_usechannel(lcfg, "null", NAMED_LOGCATEGORY_UNMATCHED, + NULL); + return (result); +} + +void +named_log_shutdown(void) { + isc_log_destroy(&named_g_lctx); + isc_log_setcontext(NULL); + dns_log_setcontext(NULL); +} diff --git a/bin/named/logconf.c b/bin/named/logconf.c new file mode 100644 index 0000000..5fd0904 --- /dev/null +++ b/bin/named/logconf.c @@ -0,0 +1,374 @@ +/* + * 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 <isc/file.h> +#include <isc/offset.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/syslog.h> +#include <isc/util.h> + +#include <isccfg/cfg.h> +#include <isccfg/log.h> + +#include <named/log.h> +#include <named/logconf.h> + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +/*% + * Set up a logging category according to the named.conf data + * in 'ccat' and add it to 'logconfig'. + */ +static isc_result_t +category_fromconf(const cfg_obj_t *ccat, isc_logconfig_t *logconfig) { + isc_result_t result; + const char *catname; + isc_logcategory_t *category; + isc_logmodule_t *module; + const cfg_obj_t *destinations = NULL; + const cfg_listelt_t *element = NULL; + + catname = cfg_obj_asstring(cfg_tuple_get(ccat, "name")); + category = isc_log_categorybyname(named_g_lctx, catname); + if (category == NULL) { + cfg_obj_log(ccat, named_g_lctx, ISC_LOG_ERROR, + "unknown logging category '%s' ignored", catname); + /* + * Allow further processing by returning success. + */ + return (ISC_R_SUCCESS); + } + + if (logconfig == NULL) { + return (ISC_R_SUCCESS); + } + + module = NULL; + + destinations = cfg_tuple_get(ccat, "destinations"); + for (element = cfg_list_first(destinations); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *channel = cfg_listelt_value(element); + const char *channelname = cfg_obj_asstring(channel); + + result = isc_log_usechannel(logconfig, channelname, category, + module); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, CFG_LOGCATEGORY_CONFIG, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "logging channel '%s': %s", channelname, + isc_result_totext(result)); + return (result); + } + } + return (ISC_R_SUCCESS); +} + +/*% + * Set up a logging channel according to the named.conf data + * in 'cchan' and add it to 'logconfig'. + */ +static isc_result_t +channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig) { + isc_result_t result = ISC_R_SUCCESS; + isc_logdestination_t dest; + unsigned int type; + unsigned int flags = 0; + int level; + const char *channelname; + const cfg_obj_t *fileobj = NULL; + const cfg_obj_t *syslogobj = NULL; + const cfg_obj_t *nullobj = NULL; + const cfg_obj_t *stderrobj = NULL; + const cfg_obj_t *severity = NULL; + int i; + + channelname = cfg_obj_asstring(cfg_map_getname(channel)); + + (void)cfg_map_get(channel, "file", &fileobj); + (void)cfg_map_get(channel, "syslog", &syslogobj); + (void)cfg_map_get(channel, "null", &nullobj); + (void)cfg_map_get(channel, "stderr", &stderrobj); + + i = 0; + if (fileobj != NULL) { + i++; + } + if (syslogobj != NULL) { + i++; + } + if (nullobj != NULL) { + i++; + } + if (stderrobj != NULL) { + i++; + } + + if (i != 1) { + cfg_obj_log(channel, named_g_lctx, ISC_LOG_ERROR, + "channel '%s': exactly one of file, syslog, " + "null, and stderr must be present", + channelname); + return (ISC_R_FAILURE); + } + + type = ISC_LOG_TONULL; + + if (fileobj != NULL) { + const cfg_obj_t *pathobj = cfg_tuple_get(fileobj, "file"); + const cfg_obj_t *sizeobj = cfg_tuple_get(fileobj, "size"); + const cfg_obj_t *versionsobj = cfg_tuple_get(fileobj, + "versions"); + const cfg_obj_t *suffixobj = cfg_tuple_get(fileobj, "suffix"); + int32_t versions = ISC_LOG_ROLLNEVER; + isc_log_rollsuffix_t suffix = isc_log_rollsuffix_increment; + isc_offset_t size = 0; + uint64_t maxoffset; + + /* + * isc_offset_t is a signed integer type, so the maximum + * value is all 1s except for the MSB. + */ + switch (sizeof(isc_offset_t)) { + case 4: + maxoffset = 0x7fffffffULL; + break; + case 8: + maxoffset = 0x7fffffffffffffffULL; + break; + default: + UNREACHABLE(); + } + + type = ISC_LOG_TOFILE; + + if (versionsobj != NULL && cfg_obj_isuint32(versionsobj)) { + versions = cfg_obj_asuint32(versionsobj); + } else if (versionsobj != NULL && + cfg_obj_isstring(versionsobj) && + strcasecmp(cfg_obj_asstring(versionsobj), + "unlimited") == 0) + { + versions = ISC_LOG_ROLLINFINITE; + } + if (sizeobj != NULL && cfg_obj_isuint64(sizeobj) && + cfg_obj_asuint64(sizeobj) < maxoffset) + { + size = (isc_offset_t)cfg_obj_asuint64(sizeobj); + } + if (suffixobj != NULL && cfg_obj_isstring(suffixobj) && + strcasecmp(cfg_obj_asstring(suffixobj), "timestamp") == 0) + { + suffix = isc_log_rollsuffix_timestamp; + } + + dest.file.stream = NULL; + dest.file.name = cfg_obj_asstring(pathobj); + dest.file.versions = versions; + dest.file.suffix = suffix; + dest.file.maximum_size = size; + } else if (syslogobj != NULL) { + int facility = LOG_DAEMON; + + type = ISC_LOG_TOSYSLOG; + + if (cfg_obj_isstring(syslogobj)) { + const char *facilitystr = cfg_obj_asstring(syslogobj); + (void)isc_syslog_facilityfromstring(facilitystr, + &facility); + } + dest.facility = facility; + } else if (stderrobj != NULL) { + type = ISC_LOG_TOFILEDESC; + dest.file.stream = stderr; + dest.file.name = NULL; + dest.file.versions = ISC_LOG_ROLLNEVER; + dest.file.suffix = isc_log_rollsuffix_increment; + dest.file.maximum_size = 0; + } + + /* + * Munge flags. + */ + { + const cfg_obj_t *printcat = NULL; + const cfg_obj_t *printsev = NULL; + const cfg_obj_t *printtime = NULL; + const cfg_obj_t *buffered = NULL; + + (void)cfg_map_get(channel, "print-category", &printcat); + (void)cfg_map_get(channel, "print-severity", &printsev); + (void)cfg_map_get(channel, "print-time", &printtime); + (void)cfg_map_get(channel, "buffered", &buffered); + + if (printcat != NULL && cfg_obj_asboolean(printcat)) { + flags |= ISC_LOG_PRINTCATEGORY; + } + if (printsev != NULL && cfg_obj_asboolean(printsev)) { + flags |= ISC_LOG_PRINTLEVEL; + } + if (buffered != NULL && cfg_obj_asboolean(buffered)) { + flags |= ISC_LOG_BUFFERED; + } + if (printtime != NULL && cfg_obj_isboolean(printtime)) { + if (cfg_obj_asboolean(printtime)) { + flags |= ISC_LOG_PRINTTIME; + } + } else if (printtime != NULL) { /* local/iso8601/iso8601-utc */ + const char *s = cfg_obj_asstring(printtime); + flags |= ISC_LOG_PRINTTIME; + if (strcasecmp(s, "iso8601") == 0) { + flags |= ISC_LOG_ISO8601; + } else if (strcasecmp(s, "iso8601-utc") == 0) { + flags |= ISC_LOG_ISO8601 | ISC_LOG_UTC; + } + } + } + + level = ISC_LOG_INFO; + if (cfg_map_get(channel, "severity", &severity) == ISC_R_SUCCESS) { + if (cfg_obj_isstring(severity)) { + const char *str = cfg_obj_asstring(severity); + if (strcasecmp(str, "critical") == 0) { + level = ISC_LOG_CRITICAL; + } else if (strcasecmp(str, "error") == 0) { + level = ISC_LOG_ERROR; + } else if (strcasecmp(str, "warning") == 0) { + level = ISC_LOG_WARNING; + } else if (strcasecmp(str, "notice") == 0) { + level = ISC_LOG_NOTICE; + } else if (strcasecmp(str, "info") == 0) { + level = ISC_LOG_INFO; + } else if (strcasecmp(str, "dynamic") == 0) { + level = ISC_LOG_DYNAMIC; + } + } else { + /* debug */ + level = cfg_obj_asuint32(severity); + } + } + + if (logconfig != NULL) { + isc_log_createchannel(logconfig, channelname, type, level, + &dest, flags); + } + + if (type == ISC_LOG_TOFILE) { + FILE *fp; + + /* + * Test to make sure that file is a plain file. + * Fix defect #22771 + */ + result = isc_file_isplainfile(dest.file.name); + if (result == ISC_R_SUCCESS || result == ISC_R_FILENOTFOUND) { + /* + * Test that the file can be opened, since + * isc_log_open() can't effectively report + * failures when called in isc_log_doit(). + */ + result = isc_stdio_open(dest.file.name, "a", &fp); + if (result != ISC_R_SUCCESS) { + if (logconfig != NULL && !named_g_nosyslog) { + syslog(LOG_ERR, + "isc_stdio_open '%s' failed: " + "%s", + dest.file.name, + isc_result_totext(result)); + } + } else { + (void)isc_stdio_close(fp); + } + goto done; + } + if (logconfig != NULL && !named_g_nosyslog) { + syslog(LOG_ERR, "isc_file_isplainfile '%s' failed: %s", + dest.file.name, isc_result_totext(result)); + } + } + +done: + return (result); +} + +isc_result_t +named_logconfig(isc_logconfig_t *logconfig, const cfg_obj_t *logstmt) { + isc_result_t result; + const cfg_obj_t *channels = NULL; + const cfg_obj_t *categories = NULL; + const cfg_listelt_t *element; + bool default_set = false; + bool unmatched_set = false; + const cfg_obj_t *catname; + + if (logconfig != NULL) { + named_log_setdefaultchannels(logconfig); + named_log_setdefaultsslkeylogfile(logconfig); + } + + (void)cfg_map_get(logstmt, "channel", &channels); + for (element = cfg_list_first(channels); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *channel = cfg_listelt_value(element); + CHECK(channel_fromconf(channel, logconfig)); + } + + (void)cfg_map_get(logstmt, "category", &categories); + for (element = cfg_list_first(categories); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *category = cfg_listelt_value(element); + CHECK(category_fromconf(category, logconfig)); + if (!default_set) { + catname = cfg_tuple_get(category, "name"); + if (strcmp(cfg_obj_asstring(catname), "default") == 0) { + default_set = true; + } + } + if (!unmatched_set) { + catname = cfg_tuple_get(category, "name"); + if (strcmp(cfg_obj_asstring(catname), "unmatched") == 0) + { + unmatched_set = true; + } + } + } + + if (logconfig != NULL && !default_set) { + CHECK(named_log_setdefaultcategory(logconfig)); + } + + if (logconfig != NULL && !unmatched_set) { + CHECK(named_log_setunmatchedcategory(logconfig)); + } + + return (ISC_R_SUCCESS); + +cleanup: + return (result); +} diff --git a/bin/named/main.c b/bin/named/main.c new file mode 100644 index 0000000..154e17e --- /dev/null +++ b/bin/named/main.c @@ -0,0 +1,1663 @@ +/* + * 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 <inttypes.h> +#include <locale.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <uv.h> + +#ifdef HAVE_DNSTAP +#include <protobuf-c/protobuf-c.h> +#endif + +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/backtrace.h> +#include <isc/commandline.h> +#include <isc/dir.h> +#include <isc/file.h> +#include <isc/hash.h> +#include <isc/httpd.h> +#include <isc/managers.h> +#include <isc/netmgr.h> +#include <isc/os.h> +#include <isc/print.h> +#include <isc/resource.h> +#include <isc/result.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/dispatch.h> +#include <dns/dyndb.h> +#include <dns/name.h> +#include <dns/resolver.h> +#include <dns/view.h> + +#include <dlz/dlz_dlopen_driver.h> + +#ifdef HAVE_GPERFTOOLS_PROFILER +#include <gperftools/profiler.h> +#endif /* ifdef HAVE_GPERFTOOLS_PROFILER */ + +#ifdef HAVE_JSON_C +#include <json_c_version.h> +#endif /* HAVE_JSON_C */ + +#ifdef HAVE_GEOIP2 +#include <maxminddb.h> +#endif /* ifdef HAVE_GEOIP2 */ + +/* + * Defining NAMED_MAIN provides storage declarations (rather than extern) + * for variables in named/globals.h. + */ +#define NAMED_MAIN 1 + +#include <ns/interfacemgr.h> + +#include <named/builtin.h> +#include <named/config.h> +#include <named/control.h> +#include <named/fuzz.h> +#include <named/globals.h> /* Explicit, though named/log.h includes it. */ +#include <named/log.h> +#include <named/main.h> +#include <named/os.h> +#include <named/server.h> +#ifdef HAVE_LIBSCF +#include <named/smf_globals.h> +#endif /* ifdef HAVE_LIBSCF */ + +#include <openssl/crypto.h> +#include <openssl/opensslv.h> +#ifdef HAVE_LIBXML2 +#include <libxml/parser.h> +#include <libxml/xmlversion.h> +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif /* ifdef HAVE_ZLIB */ +#ifdef HAVE_LIBNGHTTP2 +#include <nghttp2/nghttp2.h> +#endif +/* + * Include header files for database drivers here. + */ +/* #include "xxdb.h" */ + +/* + * The maximum number of stack frames to dump on assertion failure. + */ +#ifndef BACKTRACE_MAXFRAME +#define BACKTRACE_MAXFRAME 128 +#endif /* ifndef BACKTRACE_MAXFRAME */ + +extern unsigned int dns_zone_mkey_hour; +extern unsigned int dns_zone_mkey_day; +extern unsigned int dns_zone_mkey_month; + +static bool want_stats = false; +static char program_name[NAME_MAX] = "named"; +static char absolute_conffile[PATH_MAX]; +static char saved_command_line[4096] = { 0 }; +static char ellipsis[5] = { 0 }; +static char version[512]; +static int maxudp = 0; + +/* + * -T options: + */ +static bool dropedns = false; +static bool ednsformerr = false; +static bool ednsnotimp = false; +static bool ednsrefused = false; +static bool fixedlocal = false; +static bool noaa = false; +static bool noedns = false; +static bool nonearest = false; +static bool nosoa = false; +static bool notcp = false; +static bool sigvalinsecs = false; +static bool transferinsecs = false; +static bool transferslowly = false; +static bool transferstuck = false; + +/* + * -4 and -6 + */ +static bool disable6 = false; +static bool disable4 = false; + +void +named_main_earlywarning(const char *format, ...) { + va_list args; + + va_start(args, format); + if (named_g_lctx != NULL) { + isc_log_vwrite(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_WARNING, format, + args); + } else { + fprintf(stderr, "%s: ", program_name); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + va_end(args); +} + +void +named_main_earlyfatal(const char *format, ...) { + va_list args; + + va_start(args, format); + if (named_g_lctx != NULL) { + isc_log_vwrite(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, format, + args); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, + "exiting (due to early fatal error)"); + } else { + fprintf(stderr, "%s: ", program_name); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + va_end(args); + + exit(1); +} + +noreturn static void +assertion_failed(const char *file, int line, isc_assertiontype_t type, + const char *cond); + +static void +assertion_failed(const char *file, int line, isc_assertiontype_t type, + const char *cond) { + void *tracebuf[BACKTRACE_MAXFRAME]; + int nframes; + + /* + * Handle assertion failures. + */ + + if (named_g_lctx != NULL) { + /* + * Reset the assertion callback in case it is the log + * routines causing the assertion. + */ + isc_assertion_setcallback(NULL); + + nframes = isc_backtrace(tracebuf, BACKTRACE_MAXFRAME); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, + "%s:%d: %s(%s) failed%s", file, line, + isc_assertion_typetotext(type), cond, + (nframes > 0) ? ", back trace" : ""); + if (nframes > 0) { + char **strs = isc_backtrace_symbols(tracebuf, nframes); + if (strs != NULL) { + for (int i = 0; i < nframes; i++) { + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, + ISC_LOG_CRITICAL, "%s", + strs[i]); + } + } + } + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, + "exiting (due to assertion failure)"); + } else { + fprintf(stderr, "%s:%d: %s(%s) failed\n", file, line, + isc_assertion_typetotext(type), cond); + fflush(stderr); + } + + if (named_g_coreok) { + abort(); + } + exit(1); +} + +noreturn static void +library_fatal_error(const char *file, int line, const char *func, + const char *format, va_list args) ISC_FORMAT_PRINTF(3, 0); + +static void +library_fatal_error(const char *file, int line, const char *func, + const char *format, va_list args) { + /* + * Handle isc_error_fatal() calls from our libraries. + */ + + if (named_g_lctx != NULL) { + /* + * Reset the error callback in case it is the log + * routines causing the assertion. + */ + isc_error_setfatal(NULL); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, + "%s:%d:%s(): fatal error: ", file, line, func); + isc_log_vwrite(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, format, + args); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_CRITICAL, + "exiting (due to fatal error in library)"); + } else { + fprintf(stderr, "%s:%d:%s(): fatal error: ", file, line, func); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + + if (named_g_coreok) { + abort(); + } + exit(1); +} + +static void +library_unexpected_error(const char *file, int line, const char *func, + const char *format, va_list args) + ISC_FORMAT_PRINTF(3, 0); + +static void +library_unexpected_error(const char *file, int line, const char *func, + const char *format, va_list args) { + /* + * Handle isc_error_unexpected() calls from our libraries. + */ + + if (named_g_lctx != NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_ERROR, + "%s:%d:%s(): unexpected error: ", file, line, + func); + isc_log_vwrite(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_ERROR, format, + args); + } else { + fprintf(stderr, "%s:%d:%s(): fatal error: ", file, line, func); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +static void +usage(void) { + fprintf(stderr, "usage: named [-4|-6] [-c conffile] [-d debuglevel] " + "[-D comment] [-E engine]\n" + " [-f|-g] [-L logfile] [-n number_of_cpus] " + "[-p port] [-s]\n" + " [-S sockets] [-t chrootdir] [-u " + "username] [-U listeners]\n" + " [-X lockfile] [-m " + "{usage|trace|record|size|mctx}]\n" + " [-M fill|nofill]\n" + "usage: named [-v|-V|-C]\n"); +} + +static void +save_command_line(int argc, char *argv[]) { + int i; + char *dst = saved_command_line; + char *eob = saved_command_line + sizeof(saved_command_line) - 1; + char *rollback = dst; + + for (i = 1; i < argc && dst < eob; i++) { + char *src = argv[i]; + bool quoted = false; + + rollback = dst; + *dst++ = ' '; + + while (*src != '\0' && dst < eob) { + if (isalnum(*(unsigned char *)src) || *src == ',' || + *src == '-' || *src == '_' || *src == '.' || + *src == '/') + { + *dst++ = *src++; + } else if (isprint(*(unsigned char *)src)) { + if (dst + 2 >= eob) { + goto add_ellipsis; + } + *dst++ = '\\'; + *dst++ = *src++; + } else { + /* + * Control character found in the input, + * quote the whole arg and restart + */ + if (!quoted) { + dst = rollback; + src = argv[i]; + + if (dst + 3 >= eob) { + goto add_ellipsis; + } + + *dst++ = ' '; + *dst++ = '$'; + *dst++ = '\''; + + quoted = true; + continue; + } else { + char tmp[5]; + int c = snprintf(tmp, sizeof(tmp), + "\\%03o", *src++); + if (dst + c >= eob) { + goto add_ellipsis; + } + memmove(dst, tmp, c); + dst += c; + } + } + } + if (quoted) { + if (dst == eob) { + goto add_ellipsis; + } + *dst++ = '\''; + } + } + + if (dst < eob) { + return; + } +add_ellipsis: + dst = rollback; + *dst = '\0'; + strlcpy(ellipsis, " ...", sizeof(ellipsis)); +} + +static int +parse_int(char *arg, const char *desc) { + char *endp; + int tmp; + long int ltmp; + + ltmp = strtol(arg, &endp, 10); + tmp = (int)ltmp; + if (*endp != '\0') { + named_main_earlyfatal("%s '%s' must be numeric", desc, arg); + } + if (tmp < 0 || tmp != ltmp) { + named_main_earlyfatal("%s '%s' out of range", desc, arg); + } + return (tmp); +} + +static struct flag_def { + const char *name; + unsigned int value; + bool negate; +} mem_debug_flags[] = { { "none", 0, false }, + { "trace", ISC_MEM_DEBUGTRACE, false }, + { "record", ISC_MEM_DEBUGRECORD, false }, + { "usage", ISC_MEM_DEBUGUSAGE, false }, + { NULL, 0, false } }, + mem_context_flags[] = { { "fill", ISC_MEMFLAG_FILL, false }, + { "nofill", ISC_MEMFLAG_FILL, true }, + { NULL, 0, false } }; + +static void +set_flags(const char *arg, struct flag_def *defs, unsigned int *ret) { + bool clear = false; + + for (;;) { + const struct flag_def *def; + const char *end = strchr(arg, ','); + int arglen; + if (end == NULL) { + end = arg + strlen(arg); + } + arglen = (int)(end - arg); + for (def = defs; def->name != NULL; def++) { + if (arglen == (int)strlen(def->name) && + memcmp(arg, def->name, arglen) == 0) + { + if (def->value == 0) { + clear = true; + } + if (def->negate) { + *ret &= ~(def->value); + } else { + *ret |= def->value; + } + goto found; + } + } + named_main_earlyfatal("unrecognized flag '%.*s'", arglen, arg); + found: + if (clear || (*end == '\0')) { + break; + } + arg = end + 1; + } + + if (clear) { + *ret = 0; + } +} + +static void +list_dnssec_algorithms(isc_buffer_t *b) { + for (dst_algorithm_t i = DST_ALG_UNKNOWN; i < DST_MAX_ALGS; i++) { + if (i == DST_ALG_DH || i == DST_ALG_GSSAPI || + (i >= DST_ALG_HMAC_FIRST && i <= DST_ALG_HMAC_LAST)) + { + continue; + } + if (dst_algorithm_supported(i)) { + isc_buffer_putstr(b, " "); + (void)dns_secalg_totext(i, b); + } + } +} + +static void +list_ds_algorithms(isc_buffer_t *b) { + for (size_t i = 0; i < 256; i++) { + if (dst_ds_digest_supported(i)) { + isc_buffer_putstr(b, " "); + (void)dns_dsdigest_totext(i, b); + } + } +} + +static void +list_hmac_algorithms(isc_buffer_t *b) { + isc_buffer_t sb = *b; + for (dst_algorithm_t i = DST_ALG_HMAC_FIRST; i <= DST_ALG_HMAC_LAST; + i++) + { + if (i == DST_ALG_GSSAPI) { + continue; + } + if (dst_algorithm_supported(i)) { + isc_buffer_putstr(b, " "); + isc_buffer_putstr(b, dst_hmac_algorithm_totext(i)); + } + } + for (unsigned char *s = isc_buffer_used(&sb); s != isc_buffer_used(b); + s++) + { + *s = toupper(*s); + } +} + +static void +logit(isc_buffer_t *b) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, "%.*s", + (int)isc_buffer_usedlength(b), + (char *)isc_buffer_base(b)); +} + +static void +printit(isc_buffer_t *b) { + printf("%.*s\n", (int)isc_buffer_usedlength(b), + (char *)isc_buffer_base(b)); +} + +static void +format_supported_algorithms(void (*emit)(isc_buffer_t *b)) { + isc_buffer_t b; + char buf[512]; + + isc_buffer_init(&b, buf, sizeof(buf)); + isc_buffer_putstr(&b, "DNSSEC algorithms:"); + list_dnssec_algorithms(&b); + (*emit)(&b); + + isc_buffer_init(&b, buf, sizeof(buf)); + isc_buffer_putstr(&b, "DS algorithms:"); + list_ds_algorithms(&b); + (*emit)(&b); + + isc_buffer_init(&b, buf, sizeof(buf)); + isc_buffer_putstr(&b, "HMAC algorithms:"); + list_hmac_algorithms(&b); + (*emit)(&b); + + isc_buffer_init(&b, buf, sizeof(buf)); + isc_buffer_printf(&b, "TKEY mode 2 support (Diffie-Hellman): %s", + (dst_algorithm_supported(DST_ALG_DH) && + dst_algorithm_supported(DST_ALG_HMACMD5)) + ? "yes" + : "non"); + (*emit)(&b); + + isc_buffer_init(&b, buf, sizeof(buf)); + isc_buffer_printf(&b, "TKEY mode 3 support (GSS-API): %s", + dst_algorithm_supported(DST_ALG_GSSAPI) ? "yes" + : "no"); + (*emit)(&b); +} + +static void +printversion(bool verbose) { + char rndcconf[PATH_MAX], *dot = NULL; + isc_mem_t *mctx = NULL; + isc_result_t result; + isc_buffer_t b; + char buf[512]; +#if defined(HAVE_GEOIP2) + cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL; + const cfg_obj_t *defaults = NULL, *obj = NULL; +#endif /* if defined(HAVE_GEOIP2) */ + + printf("%s%s <id:%s>\n", PACKAGE_STRING, PACKAGE_DESCRIPTION, + PACKAGE_SRCID); + + if (!verbose) { + return; + } + + printf("running on %s\n", named_os_uname()); + printf("built by %s with %s\n", PACKAGE_BUILDER, PACKAGE_CONFIGARGS); +#ifdef __clang__ + printf("compiled by CLANG %s\n", __VERSION__); +#else /* ifdef __clang__ */ +#if defined(__ICC) || defined(__INTEL_COMPILER) + printf("compiled by ICC %s\n", __VERSION__); +#else /* if defined(__ICC) || defined(__INTEL_COMPILER) */ +#ifdef __GNUC__ + printf("compiled by GCC %s\n", __VERSION__); +#endif /* ifdef __GNUC__ */ +#endif /* if defined(__ICC) || defined(__INTEL_COMPILER) */ +#endif /* ifdef __clang__ */ +#ifdef _MSC_VER + printf("compiled by MSVC %d\n", _MSC_VER); +#endif /* ifdef _MSC_VER */ +#ifdef __SUNPRO_C + printf("compiled by Solaris Studio %x\n", __SUNPRO_C); +#endif /* ifdef __SUNPRO_C */ + printf("compiled with OpenSSL version: %s\n", OPENSSL_VERSION_TEXT); +#if !defined(LIBRESSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10100000L /* 1.1.0 or higher */ + printf("linked to OpenSSL version: %s\n", + OpenSSL_version(OPENSSL_VERSION)); + +#else /* if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= \ + * 0x10100000L */ + printf("linked to OpenSSL version: %s\n", + SSLeay_version(SSLEAY_VERSION)); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + printf("compiled with libuv version: %d.%d.%d\n", UV_VERSION_MAJOR, + UV_VERSION_MINOR, UV_VERSION_PATCH); + printf("linked to libuv version: %s\n", uv_version_string()); +#if HAVE_LIBNGHTTP2 + nghttp2_info *nginfo = NULL; + printf("compiled with libnghttp2 version: %s\n", NGHTTP2_VERSION); + nginfo = nghttp2_version(1); + printf("linked to libnghttp2 version: %s\n", nginfo->version_str); +#endif +#ifdef HAVE_LIBXML2 + printf("compiled with libxml2 version: %s\n", LIBXML_DOTTED_VERSION); + printf("linked to libxml2 version: %s\n", xmlParserVersion); +#endif /* ifdef HAVE_LIBXML2 */ +#if defined(HAVE_JSON_C) + printf("compiled with json-c version: %s\n", JSON_C_VERSION); + printf("linked to json-c version: %s\n", json_c_version()); +#endif /* if defined(HAVE_JSON_C) */ +#if defined(HAVE_ZLIB) && defined(ZLIB_VERSION) + printf("compiled with zlib version: %s\n", ZLIB_VERSION); + printf("linked to zlib version: %s\n", zlibVersion()); +#endif /* if defined(HAVE_ZLIB) && defined(ZLIB_VERSION) */ +#if defined(HAVE_GEOIP2) + /* Unfortunately, no version define on link time */ + printf("linked to maxminddb version: %s\n", MMDB_lib_version()); +#endif /* if defined(HAVE_GEOIP2) */ +#if defined(HAVE_DNSTAP) + printf("compiled with protobuf-c version: %s\n", PROTOBUF_C_VERSION); + printf("linked to protobuf-c version: %s\n", protobuf_c_version()); +#endif /* if defined(HAVE_DNSTAP) */ + printf("threads support is enabled\n"); + + isc_mem_create(&mctx); + result = dst_lib_init(mctx, named_g_engine); + if (result == ISC_R_SUCCESS) { + isc_buffer_init(&b, buf, sizeof(buf)); + format_supported_algorithms(printit); + printf("\n"); + dst_lib_destroy(); + } else { + printf("DST initialization failure: %s\n", + isc_result_totext(result)); + } + + /* + * The default rndc.conf and rndc.key paths are in the same + * directory, but named only has rndc.key defined internally. + * We construct the rndc.conf path from it. + */ + strlcpy(rndcconf, named_g_keyfile, sizeof(rndcconf)); + dot = strrchr(rndcconf, '.'); + if (dot != NULL) { + size_t len = dot - rndcconf + 1; + snprintf(dot + 1, PATH_MAX - len, "conf"); + } + + /* + * Print default configuration paths. + */ + printf("default paths:\n"); + printf(" named configuration: %s\n", named_g_conffile); + printf(" rndc configuration: %s\n", rndcconf); + printf(" DNSSEC root key: %s\n", named_g_defaultbindkeys); + printf(" nsupdate session key: %s\n", named_g_defaultsessionkeyfile); + printf(" named PID file: %s\n", named_g_defaultpidfile); + printf(" named lock file: %s\n", named_g_defaultlockfile); +#if defined(HAVE_GEOIP2) +#define RTC(x) RUNTIME_CHECK((x) == ISC_R_SUCCESS) + RTC(cfg_parser_create(mctx, named_g_lctx, &parser)); + RTC(named_config_parsedefaults(parser, &config)); + RTC(cfg_map_get(config, "options", &defaults)); + RTC(cfg_map_get(defaults, "geoip-directory", &obj)); + if (cfg_obj_isstring(obj)) { + printf(" geoip-directory: %s\n", cfg_obj_asstring(obj)); + } + cfg_obj_destroy(parser, &config); + cfg_parser_destroy(&parser); + isc_mem_detach(&mctx); +#endif /* HAVE_GEOIP2 */ +} + +static void +parse_fuzz_arg(void) { + if (!strncmp(isc_commandline_argument, "client:", 7)) { + named_g_fuzz_addr = isc_commandline_argument + 7; + named_g_fuzz_type = isc_fuzz_client; + } else if (!strncmp(isc_commandline_argument, "tcp:", 4)) { + named_g_fuzz_addr = isc_commandline_argument + 4; + named_g_fuzz_type = isc_fuzz_tcpclient; + } else if (!strncmp(isc_commandline_argument, "resolver:", 9)) { + named_g_fuzz_addr = isc_commandline_argument + 9; + named_g_fuzz_type = isc_fuzz_resolver; + } else if (!strncmp(isc_commandline_argument, "http:", 5)) { + named_g_fuzz_addr = isc_commandline_argument + 5; + named_g_fuzz_type = isc_fuzz_http; + } else if (!strncmp(isc_commandline_argument, "rndc:", 5)) { + named_g_fuzz_addr = isc_commandline_argument + 5; + named_g_fuzz_type = isc_fuzz_rndc; + } else { + named_main_earlyfatal("unknown fuzzing type '%s'", + isc_commandline_argument); + } +} + +static void +parse_T_opt(char *option) { + const char *p; + char *last = NULL; + /* + * force the server to behave (or misbehave) in + * specified ways for testing purposes. + */ + if (!strcmp(option, "dropedns")) { + dropedns = true; + } else if (!strcmp(option, "ednsformerr")) { + ednsformerr = true; + } else if (!strcmp(option, "ednsnotimp")) { + ednsnotimp = true; + } else if (!strcmp(option, "ednsrefused")) { + ednsrefused = true; + } else if (!strcmp(option, "fixedlocal")) { + fixedlocal = true; + } else if (!strcmp(option, "keepstderr")) { + named_g_keepstderr = true; + } else if (!strcmp(option, "noaa")) { + noaa = true; + } else if (!strcmp(option, "noedns")) { + noedns = true; + } else if (!strcmp(option, "nonearest")) { + nonearest = true; + } else if (!strcmp(option, "nosoa")) { + nosoa = true; + } else if (!strcmp(option, "nosyslog")) { + named_g_nosyslog = true; + } else if (!strcmp(option, "notcp")) { + notcp = true; + } else if (!strncmp(option, "maxcachesize=", 13)) { + named_g_maxcachesize = atoi(option + 13); + } else if (!strcmp(option, "maxudp512")) { + maxudp = 512; + } else if (!strcmp(option, "maxudp1460")) { + maxudp = 1460; + } else if (!strncmp(option, "maxudp=", 7)) { + maxudp = atoi(option + 7); + if (maxudp <= 0) { + named_main_earlyfatal("bad maxudp"); + } + } else if (!strncmp(option, "mkeytimers=", 11)) { + p = strtok_r(option + 11, "/", &last); + if (p == NULL) { + named_main_earlyfatal("bad mkeytimer"); + } + + dns_zone_mkey_hour = atoi(p); + if (dns_zone_mkey_hour == 0) { + named_main_earlyfatal("bad mkeytimer"); + } + + p = strtok_r(NULL, "/", &last); + if (p == NULL) { + dns_zone_mkey_day = (24 * dns_zone_mkey_hour); + dns_zone_mkey_month = (30 * dns_zone_mkey_day); + return; + } + + dns_zone_mkey_day = atoi(p); + if (dns_zone_mkey_day < dns_zone_mkey_hour) { + named_main_earlyfatal("bad mkeytimer"); + } + + p = strtok_r(NULL, "/", &last); + if (p == NULL) { + dns_zone_mkey_month = (30 * dns_zone_mkey_day); + return; + } + + dns_zone_mkey_month = atoi(p); + if (dns_zone_mkey_month < dns_zone_mkey_day) { + named_main_earlyfatal("bad mkeytimer"); + } + } else if (!strcmp(option, "sigvalinsecs")) { + sigvalinsecs = true; + } else if (!strcmp(option, "transferinsecs")) { + transferinsecs = true; + } else if (!strcmp(option, "transferslowly")) { + transferslowly = true; + } else if (!strcmp(option, "transferstuck")) { + transferstuck = true; + } else if (!strncmp(option, "tat=", 4)) { + named_g_tat_interval = atoi(option + 4); + } else { + fprintf(stderr, "unknown -T flag '%s'\n", option); + } +} + +static void +parse_port(char *arg) { + enum { DNSPORT, TLSPORT, HTTPSPORT, HTTPPORT } ptype = DNSPORT; + char *value = arg; + int port; + + if (strncmp(arg, "dns=", 4) == 0) { + value = arg + 4; + } else if (strncmp(arg, "tls=", 4) == 0) { + value = arg + 4; + ptype = TLSPORT; + } else if (strncmp(arg, "https=", 6) == 0) { + value = arg + 6; + ptype = HTTPSPORT; + } else if (strncmp(arg, "http=", 5) == 0) { + value = arg + 6; + ptype = HTTPPORT; + } + + port = parse_int(value, "port"); + if (port < 1 || port > 65535) { + named_main_earlyfatal("port '%s' out of range", value); + } + + switch (ptype) { + case DNSPORT: + named_g_port = port; + break; + case TLSPORT: + named_g_tlsport = port; + break; + case HTTPSPORT: + named_g_httpsport = port; + break; + case HTTPPORT: + named_g_httpport = port; + break; + default: + UNREACHABLE(); + } +} + +static void +parse_command_line(int argc, char *argv[]) { + int ch; + const char *p; + + save_command_line(argc, argv); + + /* + * NAMED_MAIN_ARGS is defined in main.h, so that it can be used + * both by named and by ntservice hooks. + */ + isc_commandline_errprint = false; + while ((ch = isc_commandline_parse(argc, argv, NAMED_MAIN_ARGS)) != -1) + { + switch (ch) { + case '4': + if (disable4) { + named_main_earlyfatal("cannot specify " + "-4 and -6"); + } + if (isc_net_probeipv4() != ISC_R_SUCCESS) { + named_main_earlyfatal("IPv4 not supported " + "by OS"); + } + isc_net_disableipv6(); + disable6 = true; + break; + case '6': + if (disable6) { + named_main_earlyfatal("cannot specify " + "-4 and -6"); + } + if (isc_net_probeipv6() != ISC_R_SUCCESS) { + named_main_earlyfatal("IPv6 not supported " + "by OS"); + } + isc_net_disableipv4(); + disable4 = true; + break; + case 'A': + parse_fuzz_arg(); + break; + case 'c': + named_g_conffile = isc_commandline_argument; + named_g_conffileset = true; + break; + case 'C': + printf("# Built-in default values. " + "This is NOT the run-time configuration!\n"); + printf("%s", named_config_getdefault()); + exit(0); + case 'd': + named_g_debuglevel = parse_int(isc_commandline_argument, + "debug " + "level"); + break; + case 'D': + /* Descriptive comment for 'ps'. */ + break; + case 'E': + named_g_engine = isc_commandline_argument; + break; + case 'f': + named_g_foreground = true; + break; + case 'g': + named_g_foreground = true; + named_g_logstderr = true; + break; + case 'L': + named_g_logfile = isc_commandline_argument; + break; + case 'M': + set_flags(isc_commandline_argument, mem_context_flags, + &isc_mem_defaultflags); + break; + case 'm': + set_flags(isc_commandline_argument, mem_debug_flags, + &isc_mem_debugging); + break; + case 'N': /* Deprecated. */ + case 'n': + named_g_cpus = parse_int(isc_commandline_argument, + "number of cpus"); + if (named_g_cpus == 0) { + named_g_cpus = 1; + } + break; + case 'p': + parse_port(isc_commandline_argument); + break; + case 's': + /* XXXRTH temporary syntax */ + want_stats = true; + break; + case 'S': + /* Formerly maxsocks */ + break; + case 't': + /* XXXJAB should we make a copy? */ + named_g_chrootdir = isc_commandline_argument; + break; + case 'T': /* NOT DOCUMENTED */ + parse_T_opt(isc_commandline_argument); + break; + case 'U': + named_g_udpdisp = parse_int(isc_commandline_argument, + "number of UDP listeners " + "per interface"); + break; + case 'u': + named_g_username = isc_commandline_argument; + break; + case 'v': + printversion(false); + exit(0); + case 'V': + printversion(true); + exit(0); + case 'x': + /* Obsolete. No longer in use. Ignore. */ + break; + case 'X': + named_g_forcelock = true; + if (strcasecmp(isc_commandline_argument, "none") != 0) { + named_g_defaultlockfile = + isc_commandline_argument; + } else { + named_g_defaultlockfile = NULL; + } + break; + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + usage(); + if (isc_commandline_option == '?') { + exit(0); + } + p = strchr(NAMED_MAIN_ARGS, isc_commandline_option); + if (p == NULL || *++p != ':') { + named_main_earlyfatal("unknown option '-%c'", + isc_commandline_option); + } else { + named_main_earlyfatal("option '-%c' requires " + "an argument", + isc_commandline_option); + } + FALLTHROUGH; + default: + named_main_earlyfatal("parsing options returned %d", + ch); + } + } + + argc -= isc_commandline_index; + argv += isc_commandline_index; + POST(argv); + + if (argc > 0) { + usage(); + named_main_earlyfatal("extra command line arguments"); + } +} + +static isc_result_t +create_managers(void) { + isc_result_t result; + + INSIST(named_g_cpus_detected > 0); + + if (named_g_cpus == 0) { + named_g_cpus = named_g_cpus_detected; + } + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, + ISC_LOG_INFO, "found %u CPU%s, using %u worker thread%s", + named_g_cpus_detected, named_g_cpus_detected == 1 ? "" : "s", + named_g_cpus, named_g_cpus == 1 ? "" : "s"); + if (named_g_udpdisp == 0) { + named_g_udpdisp = named_g_cpus_detected; + } + if (named_g_udpdisp > named_g_cpus) { + named_g_udpdisp = named_g_cpus; + } + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "using %u UDP listener%s per interface", named_g_udpdisp, + named_g_udpdisp == 1 ? "" : "s"); + + result = isc_managers_create(named_g_mctx, named_g_cpus, + 0 /* quantum */, &named_g_netmgr, + &named_g_taskmgr, &named_g_timermgr); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_nm_maxudp(named_g_netmgr, maxudp); + + return (ISC_R_SUCCESS); +} + +static void +destroy_managers(void) { + isc_managers_destroy(&named_g_netmgr, &named_g_taskmgr, + &named_g_timermgr); +} + +static void +setup(void) { + isc_result_t result; + isc_resourcevalue_t old_openfiles; + ns_server_t *sctx; +#ifdef HAVE_LIBSCF + char *instance = NULL; +#endif /* ifdef HAVE_LIBSCF */ + + /* + * Get the user and group information before changing the root + * directory, so the administrator does not need to keep a copy + * of the user and group databases in the chroot'ed environment. + */ + named_os_inituserinfo(named_g_username); + + /* + * Initialize time conversion information + */ + named_os_tzset(); + + named_os_opendevnull(); + +#ifdef HAVE_LIBSCF + /* Check if named is under smf control, before chroot. */ + result = named_smf_get_instance(&instance, 0, named_g_mctx); + /* We don't care about instance, just check if we got one. */ + if (result == ISC_R_SUCCESS) { + named_smf_got_instance = 1; + } else { + named_smf_got_instance = 0; + } + if (instance != NULL) { + isc_mem_free(named_g_mctx, instance); + } +#endif /* HAVE_LIBSCF */ + + /* + * Check for the number of cpu's before named_os_chroot(). + */ + named_g_cpus_detected = isc_os_ncpus(); + + named_os_chroot(named_g_chrootdir); + + /* + * For operating systems which have a capability mechanism, now + * is the time to switch to minimal privs and change our user id. + * On traditional UNIX systems, this call will be a no-op, and we + * will change the user ID after reading the config file the first + * time. (We need to read the config file to know which possibly + * privileged ports to bind() to.) + */ + named_os_minprivs(); + + result = named_log_init(named_g_username != NULL); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("named_log_init() failed: %s", + isc_result_totext(result)); + } + + /* + * Now is the time to daemonize (if we're not running in the + * foreground). We waited until now because we wanted to get + * a valid logging context setup. We cannot daemonize any later, + * because calling create_managers() will create threads, which + * would be lost after fork(). + */ + if (!named_g_foreground) { + named_os_daemonize(); + } + + /* + * We call isc_app_start() here as some versions of FreeBSD's fork() + * destroys all the signal handling it sets up. + */ + result = isc_app_start(); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("isc_app_start() failed: %s", + isc_result_totext(result)); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "starting %s%s <id:%s>", PACKAGE_STRING, + PACKAGE_DESCRIPTION, PACKAGE_SRCID); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, "running on %s", + named_os_uname()); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, "built with %s", + PACKAGE_CONFIGARGS); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "running as: %s%s%s", program_name, saved_command_line, + ellipsis); +#ifdef __clang__ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled by CLANG %s", __VERSION__); +#else /* ifdef __clang__ */ +#if defined(__ICC) || defined(__INTEL_COMPILER) + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled by ICC %s", __VERSION__); +#else /* if defined(__ICC) || defined(__INTEL_COMPILER) */ +#ifdef __GNUC__ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled by GCC %s", __VERSION__); +#endif /* ifdef __GNUC__ */ +#endif /* if defined(__ICC) || defined(__INTEL_COMPILER) */ +#endif /* ifdef __clang__ */ +#ifdef _MSC_VER + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled by MSVC %d", _MSC_VER); +#endif /* ifdef _MSC_VER */ +#ifdef __SUNPRO_C + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled by Solaris Studio %x", __SUNPRO_C); +#endif /* ifdef __SUNPRO_C */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled with OpenSSL version: %s", + OPENSSL_VERSION_TEXT); +#if !defined(LIBRESSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10100000L /* 1.1.0 or higher */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to OpenSSL version: %s", + OpenSSL_version(OPENSSL_VERSION)); +#else /* if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= \ + * 0x10100000L */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to OpenSSL version: %s", + SSLeay_version(SSLEAY_VERSION)); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled with libuv version: %d.%d.%d", UV_VERSION_MAJOR, + UV_VERSION_MINOR, UV_VERSION_PATCH); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to libuv version: %s", uv_version_string()); +#ifdef HAVE_LIBXML2 + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled with libxml2 version: %s", + LIBXML_DOTTED_VERSION); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to libxml2 version: %s", xmlParserVersion); +#endif /* ifdef HAVE_LIBXML2 */ +#if defined(HAVE_JSON_C) + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled with json-c version: %s", JSON_C_VERSION); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to json-c version: %s", json_c_version()); +#endif /* if defined(HAVE_JSON_C) */ +#if defined(HAVE_ZLIB) && defined(ZLIB_VERSION) + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "compiled with zlib version: %s", ZLIB_VERSION); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "linked to zlib version: %s", zlibVersion()); +#endif /* if defined(HAVE_ZLIB) && defined(ZLIB_VERSION) */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "----------------------------------------------------"); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "BIND 9 is maintained by Internet Systems Consortium,"); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "Inc. (ISC), a non-profit 501(c)(3) public-benefit "); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "corporation. Support and training for BIND 9 are "); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "available at https://www.isc.org/support"); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "----------------------------------------------------"); + + /* + * Get the initial resource limits. + */ + RUNTIME_CHECK(isc_resource_getlimit(isc_resource_stacksize, + &named_g_initstacksize) == + ISC_R_SUCCESS); + RUNTIME_CHECK(isc_resource_getlimit(isc_resource_datasize, + &named_g_initdatasize) == + ISC_R_SUCCESS); + RUNTIME_CHECK(isc_resource_getlimit(isc_resource_coresize, + &named_g_initcoresize) == + ISC_R_SUCCESS); + RUNTIME_CHECK(isc_resource_getlimit(isc_resource_openfiles, + &named_g_initopenfiles) == + ISC_R_SUCCESS); + + /* + * System resources cannot effectively be tuned on some systems. + * Raise the limit in such cases for safety. + */ + old_openfiles = named_g_initopenfiles; + named_os_adjustnofile(); + RUNTIME_CHECK(isc_resource_getlimit(isc_resource_openfiles, + &named_g_initopenfiles) == + ISC_R_SUCCESS); + if (old_openfiles != named_g_initopenfiles) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, + "adjusted limit on open files from " + "%" PRIu64 " to " + "%" PRIu64, + old_openfiles, named_g_initopenfiles); + } + + /* + * If the named configuration filename is relative, prepend the current + * directory's name before possibly changing to another directory. + */ + if (!isc_file_isabsolute(named_g_conffile)) { + result = isc_file_absolutepath(named_g_conffile, + absolute_conffile, + sizeof(absolute_conffile)); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("could not construct " + "absolute path " + "of configuration file: %s", + isc_result_totext(result)); + } + named_g_conffile = absolute_conffile; + } + + /* + * Record the server's startup time. + */ + result = isc_time_now(&named_g_boottime); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("isc_time_now() failed: %s", + isc_result_totext(result)); + } + + result = create_managers(); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("create_managers() failed: %s", + isc_result_totext(result)); + } + + named_builtin_init(); + + /* + * Add calls to register sdb drivers here. + */ + /* xxdb_init(); */ + + /* + * Register the DLZ "dlopen" driver. + */ + result = dlz_dlopen_init(named_g_mctx); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("dlz_dlopen_init() failed: %s", + isc_result_totext(result)); + } + + named_server_create(named_g_mctx, &named_g_server); + ENSURE(named_g_server != NULL); + sctx = named_g_server->sctx; + + /* + * Report supported algorithms now that dst_lib_init() has + * been called via named_server_create(). + */ + format_supported_algorithms(logit); + + /* + * Modify server context according to command line options + */ + if (disable4) { + ns_server_setoption(sctx, NS_SERVER_DISABLE4, true); + } + if (disable6) { + ns_server_setoption(sctx, NS_SERVER_DISABLE6, true); + } + if (dropedns) { + ns_server_setoption(sctx, NS_SERVER_DROPEDNS, true); + } + if (ednsformerr) { /* STD13 server */ + ns_server_setoption(sctx, NS_SERVER_EDNSFORMERR, true); + } + if (ednsnotimp) { + ns_server_setoption(sctx, NS_SERVER_EDNSNOTIMP, true); + } + if (ednsrefused) { + ns_server_setoption(sctx, NS_SERVER_EDNSREFUSED, true); + } + if (fixedlocal) { + ns_server_setoption(sctx, NS_SERVER_FIXEDLOCAL, true); + } + if (noaa) { + ns_server_setoption(sctx, NS_SERVER_NOAA, true); + } + if (noedns) { + ns_server_setoption(sctx, NS_SERVER_NOEDNS, true); + } + if (nonearest) { + ns_server_setoption(sctx, NS_SERVER_NONEAREST, true); + } + if (nosoa) { + ns_server_setoption(sctx, NS_SERVER_NOSOA, true); + } + if (notcp) { + ns_server_setoption(sctx, NS_SERVER_NOTCP, true); + } + if (sigvalinsecs) { + ns_server_setoption(sctx, NS_SERVER_SIGVALINSECS, true); + } + if (transferinsecs) { + ns_server_setoption(sctx, NS_SERVER_TRANSFERINSECS, true); + } + if (transferslowly) { + ns_server_setoption(sctx, NS_SERVER_TRANSFERSLOWLY, true); + } + if (transferstuck) { + ns_server_setoption(sctx, NS_SERVER_TRANSFERSTUCK, true); + } +} + +static void +cleanup(void) { + destroy_managers(); + + if (named_g_mapped != NULL) { + dns_acl_detach(&named_g_mapped); + } + + named_server_destroy(&named_g_server); + + named_builtin_deinit(); + + /* + * Add calls to unregister sdb drivers here. + */ + /* xxdb_clear(); */ + + /* + * Unregister "dlopen" DLZ driver. + */ + dlz_dlopen_clear(); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_MAIN, ISC_LOG_NOTICE, "exiting"); + named_log_shutdown(); +} + +static char *memstats = NULL; + +void +named_main_setmemstats(const char *filename) { + /* + * Caller has to ensure locking. + */ + + if (memstats != NULL) { + free(memstats); + memstats = NULL; + } + + if (filename == NULL) { + return; + } + + memstats = strdup(filename); +} + +#ifdef HAVE_LIBSCF +/* + * Get FMRI for the named process. + */ +isc_result_t +named_smf_get_instance(char **ins_name, int debug, isc_mem_t *mctx) { + scf_handle_t *h = NULL; + int namelen; + char *instance; + + REQUIRE(ins_name != NULL && *ins_name == NULL); + + if ((h = scf_handle_create(SCF_VERSION)) == NULL) { + if (debug) { + UNEXPECTED_ERROR("scf_handle_create() failed: %s", + scf_strerror(scf_error())); + } + return (ISC_R_FAILURE); + } + + if (scf_handle_bind(h) == -1) { + if (debug) { + UNEXPECTED_ERROR("scf_handle_bind() failed: %s", + scf_strerror(scf_error())); + } + scf_handle_destroy(h); + return (ISC_R_FAILURE); + } + + if ((namelen = scf_myname(h, NULL, 0)) == -1) { + if (debug) { + UNEXPECTED_ERROR("scf_myname() failed: %s", + scf_strerror(scf_error())); + } + scf_handle_destroy(h); + return (ISC_R_FAILURE); + } + + if ((instance = isc_mem_allocate(mctx, namelen + 1)) == NULL) { + UNEXPECTED_ERROR("named_smf_get_instance memory " + "allocation failed: %s", + isc_result_totext(ISC_R_NOMEMORY)); + scf_handle_destroy(h); + return (ISC_R_FAILURE); + } + + if (scf_myname(h, instance, namelen + 1) == -1) { + if (debug) { + UNEXPECTED_ERROR("scf_myname() failed: %s", + scf_strerror(scf_error())); + } + scf_handle_destroy(h); + isc_mem_free(mctx, instance); + return (ISC_R_FAILURE); + } + + scf_handle_destroy(h); + *ins_name = instance; + return (ISC_R_SUCCESS); +} +#endif /* HAVE_LIBSCF */ + +/* main entry point, possibly hooked */ + +int +main(int argc, char *argv[]) { + isc_result_t result; +#ifdef HAVE_LIBSCF + char *instance = NULL; +#endif /* ifdef HAVE_LIBSCF */ + +#ifdef HAVE_GPERFTOOLS_PROFILER + (void)ProfilerStart(NULL); +#endif /* ifdef HAVE_GPERFTOOLS_PROFILER */ + +#ifdef HAVE_LIBXML2 + xmlInitParser(); +#endif /* HAVE_LIBXML2 */ + + /* + * Technically, this call is superfluous because on startup of the main + * program, the portable "C" locale is selected by default. This + * explicit call here is for a reference that the BIND 9 code base is + * not locale aware and the locale MUST be set to "C" (or "POSIX") when + * calling any BIND 9 library code. If you are calling external + * libraries that use locale, such calls must be wrapped into + * setlocale(LC_ALL, ""); before the call and setlocale(LC_ALL, "C"); + * after the call, and no BIND 9 library calls must be made in between. + */ + setlocale(LC_ALL, "C"); + + /* + * Record version in core image. + * strings named.core | grep "named version:" + */ + strlcat(version, +#if defined(NO_VERSION_DATE) || !defined(__DATE__) + "named version: BIND " PACKAGE_VERSION " <" PACKAGE_SRCID ">", +#else + "named version: BIND " PACKAGE_VERSION " <" PACKAGE_SRCID + "> (" __DATE__ ")", +#endif + sizeof(version)); + result = isc_file_progname(*argv, program_name, sizeof(program_name)); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("program name too long"); + } + + isc_assertion_setcallback(assertion_failed); + isc_error_setfatal(library_fatal_error); + isc_error_setunexpected(library_unexpected_error); + + named_os_init(program_name); + + parse_command_line(argc, argv); + +#ifdef ENABLE_AFL + if (named_g_fuzz_type != isc_fuzz_none) { + named_fuzz_setup(); + } + + if (named_g_fuzz_type == isc_fuzz_resolver) { + dns_resolver_setfuzzing(); + } else if (named_g_fuzz_type == isc_fuzz_http) { + isc_httpd_setfinishhook(named_fuzz_notify); + } +#endif /* ifdef ENABLE_AFL */ + /* + * Warn about common configuration error. + */ + if (named_g_chrootdir != NULL) { + int len = strlen(named_g_chrootdir); + if (strncmp(named_g_chrootdir, named_g_conffile, len) == 0 && + (named_g_conffile[len] == '/' || + named_g_conffile[len] == '\\')) + { + named_main_earlywarning("config filename (-c %s) " + "contains chroot path (-t %s)", + named_g_conffile, + named_g_chrootdir); + } + } + + isc_mem_create(&named_g_mctx); + isc_mem_setname(named_g_mctx, "main"); + + setup(); + INSIST(named_g_server != NULL); + + /* + * Start things running and then wait for a shutdown request + * or reload. + */ + do { + result = isc_app_run(); + + if (result == ISC_R_RELOAD) { + named_server_reloadwanted(named_g_server); + } else if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_app_run(): %s", + isc_result_totext(result)); + /* + * Force exit. + */ + result = ISC_R_SUCCESS; + } + } while (result != ISC_R_SUCCESS); + +#ifdef HAVE_LIBSCF + if (named_smf_want_disable == 1) { + result = named_smf_get_instance(&instance, 1, named_g_mctx); + if (result == ISC_R_SUCCESS && instance != NULL) { + if (smf_disable_instance(instance, 0) != 0) { + UNEXPECTED_ERROR("smf_disable_instance() " + "failed for %s : %s", + instance, + scf_strerror(scf_error())); + } + } + if (instance != NULL) { + isc_mem_free(named_g_mctx, instance); + } + } +#endif /* HAVE_LIBSCF */ + + cleanup(); + + if (want_stats) { + isc_mem_stats(named_g_mctx, stdout); + } + + if (named_g_memstatistics && memstats != NULL) { + FILE *fp = NULL; + result = isc_stdio_open(memstats, "w", &fp); + if (result == ISC_R_SUCCESS) { + isc_mem_stats(named_g_mctx, fp); + (void)isc_stdio_close(fp); + } + } + isc_mem_destroy(&named_g_mctx); + isc_mem_checkdestroyed(stderr); + + named_main_setmemstats(NULL); + + isc_app_finish(); + + named_os_closedevnull(); + + named_os_shutdown(); + +#ifdef HAVE_LIBXML2 + xmlCleanupParser(); +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_GPERFTOOLS_PROFILER + ProfilerStop(); +#endif /* ifdef HAVE_GPERFTOOLS_PROFILER */ + + return (0); +} diff --git a/bin/named/named.conf.rst b/bin/named/named.conf.rst new file mode 100644 index 0000000..8e93f8b --- /dev/null +++ b/bin/named/named.conf.rst @@ -0,0 +1,67 @@ +.. 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. + +.. highlight: console + +.. iscman:: named.conf + +named.conf - configuration file for **named** +--------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`named.conf` + +Description +~~~~~~~~~~~ + +:file:`named.conf` is the configuration file for :iscman:`named`. + +For complete documentation about the configuration statements, please refer to +the Configuration Reference section in the BIND 9 Administrator Reference +Manual. + +Statements are enclosed in braces and terminated with a semi-colon. +Clauses in the statements are also semi-colon terminated. The usual +comment styles are supported: + +C style: /\* \*/ + +C++ style: // to end of line + +Unix style: # to end of line + +.. literalinclude:: ../../doc/misc/options + +Any of these zone statements can also be set inside the view statement. + +.. literalinclude:: ../../doc/misc/primary.zoneopt +.. literalinclude:: ../../doc/misc/secondary.zoneopt +.. literalinclude:: ../../doc/misc/mirror.zoneopt +.. literalinclude:: ../../doc/misc/forward.zoneopt +.. literalinclude:: ../../doc/misc/hint.zoneopt +.. literalinclude:: ../../doc/misc/redirect.zoneopt +.. literalinclude:: ../../doc/misc/static-stub.zoneopt +.. literalinclude:: ../../doc/misc/stub.zoneopt +.. literalinclude:: ../../doc/misc/delegation-only.zoneopt +.. literalinclude:: ../../doc/misc/in-view.zoneopt + +Files +~~~~~ + +|named_conf| + +See Also +~~~~~~~~ + +:iscman:`named(8) <named>`, :iscman:`named-checkconf(8) <named-checkconf>`, :iscman:`rndc(8) <rndc>`, :iscman:`rndc-confgen(8) <rndc-confgen>`, :iscman:`tsig-keygen(8) <tsig-keygen>`, BIND 9 Administrator Reference Manual. + diff --git a/bin/named/named.rst b/bin/named/named.rst new file mode 100644 index 0000000..dc6e46d --- /dev/null +++ b/bin/named/named.rst @@ -0,0 +1,254 @@ +.. 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. + +.. highlight: console + +.. iscman:: named +.. program:: named +.. _man_named: + +named - Internet domain name server +----------------------------------- + +Synopsis +~~~~~~~~ + +:program:`named` [ [**-4**] | [**-6**] ] [**-c** config-file] [**-C**] [**-d** debug-level] [**-D** string] [**-E** engine-name] [**-f**] [**-g**] [**-L** logfile] [**-M** option] [**-m** flag] [**-n** #cpus] [**-p** port] [**-s**] [**-t** directory] [**-U** #listeners] [**-u** user] [**-v**] [**-V**] [**-X** lock-file] + +Description +~~~~~~~~~~~ + +:program:`named` is a Domain Name System (DNS) server, part of the BIND 9 +distribution from ISC. For more information on the DNS, see :rfc:`1033`, +:rfc:`1034`, and :rfc:`1035`. + +When invoked without arguments, :program:`named` reads the default +configuration file |named_conf|, reads any initial data, and +listens for queries. + +Options +~~~~~~~ + +.. option:: -4 + + This option tells :program:`named` to use only IPv4, even if the host machine is capable of IPv6. :option:`-4` and + :option:`-6` are mutually exclusive. + +.. option:: -6 + + This option tells :program:`named` to use only IPv6, even if the host machine is capable of IPv4. :option:`-4` and + :option:`-6` are mutually exclusive. + +.. option:: -c config-file + + This option tells :program:`named` to use ``config-file`` as its configuration file instead of the default, + |named_conf|. To ensure that the configuration file + can be reloaded after the server has changed its working directory + due to to a possible ``directory`` option in the configuration file, + ``config-file`` should be an absolute pathname. + +.. option:: -C + + This option prints out the default built-in configuration and exits. + + NOTE: This is for debugging purposes only and is not an + accurate representation of the actual configuration used by :iscman:`named` + at runtime. + +.. option:: -d debug-level + + This option sets the daemon's debug level to ``debug-level``. Debugging traces from + :program:`named` become more verbose as the debug level increases. + +.. option:: -D string + + This option specifies a string that is used to identify a instance of :program:`named` + in a process listing. The contents of ``string`` are not examined. + +.. option:: -E engine-name + + When applicable, this option specifies the hardware to use for cryptographic + operations, such as a secure key store used for signing. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). + +.. option:: -f + + This option runs the server in the foreground (i.e., do not daemonize). + +.. option:: -g + + This option runs the server in the foreground and forces all logging to ``stderr``. + +.. option:: -L logfile + + This option sets the log to the file ``logfile`` by default, instead of the system log. + +.. option:: -M option + + This option sets the default (comma-separated) memory context + options. The possible flags are: + + - ``fill``: fill blocks of memory with tag values when they are + allocated or freed, to assist debugging of memory problems; this is + the implicit default if :program:`named` has been compiled with + ``--enable-developer``. + + - ``nofill``: disable the behavior enabled by ``fill``; this is the + implicit default unless :program:`named` has been compiled with + ``--enable-developer``. + +.. option:: -m flag + + This option turns on memory usage debugging flags. Possible flags are ``usage``, + ``trace``, ``record``, ``size``, and ``mctx``. These correspond to the + ``ISC_MEM_DEBUGXXXX`` flags described in ``<isc/mem.h>``. + +.. option:: -n #cpus + + This option creates ``#cpus`` worker threads to take advantage of multiple CPUs. If + not specified, :program:`named` tries to determine the number of CPUs + present and creates one thread per CPU. If it is unable to determine + the number of CPUs, a single worker thread is created. + +.. option:: -p value + + This option specifies the port(s) on which the server will listen + for queries. If ``value`` is of the form ``<portnum>`` or + ``dns=<portnum>``, the server will listen for DNS queries on + ``portnum``; if not not specified, the default is port 53. If + ``value`` is of the form ``tls=<portnum>``, the server will + listen for TLS queries on ``portnum``; the default is 853. + If ``value`` is of the form ``https=<portnum>``, the server will + listen for HTTPS queries on ``portnum``; the default is 443. + If ``value`` is of the form ``http=<portnum>``, the server will + listen for HTTP queries on ``portnum``; the default is 80. + +.. option:: -s + + This option writes memory usage statistics to ``stdout`` on exit. + +.. note:: + + This option is mainly of interest to BIND 9 developers and may be + removed or changed in a future release. + +.. option:: -S #max-socks + + This option is deprecated and no longer has any function. + +.. warning:: + + This option should be unnecessary for the vast majority of users. + The use of this option could even be harmful, because the specified + value may exceed the limitation of the underlying system API. It + is therefore set only when the default configuration causes + exhaustion of file descriptors and the operational environment is + known to support the specified number of sockets. Note also that + the actual maximum number is normally slightly fewer than the + specified value, because :program:`named` reserves some file descriptors + for its internal use. + +.. option:: -t directory + + This option tells :program:`named` to chroot to ``directory`` after processing the command-line arguments, but + before reading the configuration file. + +.. warning:: + + This option should be used in conjunction with the :option:`-u` option, + as chrooting a process running as root doesn't enhance security on + most systems; the way ``chroot`` is defined allows a process + with root privileges to escape a chroot jail. + +.. option:: -U #listeners + + This option tells :program:`named` the number of ``#listeners`` worker threads to listen on, for incoming UDP packets on + each address. If not specified, :program:`named` calculates a default + value based on the number of detected CPUs: 1 for 1 CPU, and the + number of detected CPUs minus one for machines with more than 1 CPU. + This cannot be increased to a value higher than the number of CPUs. + If :option:`-n` has been set to a higher value than the number of detected + CPUs, then :option:`-U` may be increased as high as that value, but no + higher. + +.. option:: -u user + + This option sets the setuid to ``user`` after completing privileged operations, such as + creating sockets that listen on privileged ports. + +.. note:: + + On Linux, :program:`named` uses the kernel's capability mechanism to drop + all root privileges except the ability to ``bind`` to a + privileged port and set process resource limits. Unfortunately, + this means that the :option:`-u` option only works when :program:`named` is run + on kernel 2.2.18 or later, or kernel 2.3.99-pre3 or later, since + previous kernels did not allow privileges to be retained after + ``setuid``. + +.. option:: -v + + This option reports the version number and exits. + +.. option:: -V + + This option reports the version number, build options, supported + cryptographics algorithms, and exits. + +.. option:: -X lock-file + + This option acquires a lock on the specified file at runtime; this helps to + prevent duplicate :program:`named` instances from running simultaneously. + Use of this option overrides the ``lock-file`` option in + :iscman:`named.conf`. If set to ``none``, the lock file check is disabled. + +Signals +~~~~~~~ + +In routine operation, signals should not be used to control the +nameserver; :iscman:`rndc` should be used instead. + +SIGHUP + This signal forces a reload of the server. + +SIGINT, SIGTERM + These signals shut down the server. + +The result of sending any other signals to the server is undefined. + +Configuration +~~~~~~~~~~~~~ + +The :program:`named` configuration file is too complex to describe in detail +here. A complete description is provided in the BIND 9 Administrator +Reference Manual. + +:program:`named` inherits the ``umask`` (file creation mode mask) from the +parent process. If files created by :program:`named`, such as journal files, +need to have custom permissions, the ``umask`` should be set explicitly +in the script used to start the :program:`named` process. + +Files +~~~~~ + +|named_conf| + The default configuration file. + +|named_pid| + The default process-id file. + +See Also +~~~~~~~~ + +:rfc:`1033`, :rfc:`1034`, :rfc:`1035`, :iscman:`named-checkconf(8) <named-checkconf>`, :iscman:`named-checkzone(8) <named-checkzone>`, :iscman:`rndc(8) <rndc>`, :iscman:`named.conf(5) <named.conf>`, BIND 9 Administrator Reference Manual. diff --git a/bin/named/os.c b/bin/named/os.c new file mode 100644 index 0000000..7af4729 --- /dev/null +++ b/bin/named/os.c @@ -0,0 +1,932 @@ +/* + * 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 <stdarg.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> /* dev_t FreeBSD 2.1 */ +#ifdef HAVE_UNAME +#include <sys/utsname.h> +#endif /* ifdef HAVE_UNAME */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#ifdef HAVE_TZSET +#include <time.h> +#endif /* ifdef HAVE_TZSET */ +#include <unistd.h> + +#include <isc/buffer.h> +#include <isc/file.h> +#include <isc/print.h> +#include <isc/resource.h> +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <named/globals.h> +#include <named/main.h> +#include <named/os.h> +#ifdef HAVE_LIBSCF +#include <named/smf_globals.h> +#endif /* ifdef HAVE_LIBSCF */ + +static char *pidfile = NULL; +static char *lockfile = NULL; +static int devnullfd = -1; +static int singletonfd = -1; + +#ifndef ISC_FACILITY +#define ISC_FACILITY LOG_DAEMON +#endif /* ifndef ISC_FACILITY */ + +static struct passwd *runas_pw = NULL; +static bool done_setuid = false; +static int dfd[2] = { -1, -1 }; + +#ifdef HAVE_SYS_CAPABILITY_H + +static bool non_root = false; +static bool non_root_caps = false; + +#include <sys/capability.h> +#include <sys/prctl.h> + +static void +linux_setcaps(cap_t caps) { + char strbuf[ISC_STRERRORSIZE]; + + if ((getuid() != 0 && !non_root_caps) || non_root) { + return; + } + if (cap_set_proc(caps) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("cap_set_proc() failed: %s:" + " please ensure that the capset kernel" + " module is loaded. see insmod(8)", + strbuf); + } +} + +#define SET_CAP(flag) \ + do { \ + cap_flag_value_t curval; \ + capval = (flag); \ + err = cap_get_flag(curcaps, capval, CAP_PERMITTED, &curval); \ + if (err != -1 && curval) { \ + err = cap_set_flag(caps, CAP_EFFECTIVE, 1, &capval, \ + CAP_SET); \ + if (err == -1) { \ + strerror_r(errno, strbuf, sizeof(strbuf)); \ + named_main_earlyfatal("cap_set_proc failed: " \ + "%s", \ + strbuf); \ + } \ + \ + err = cap_set_flag(caps, CAP_PERMITTED, 1, &capval, \ + CAP_SET); \ + if (err == -1) { \ + strerror_r(errno, strbuf, sizeof(strbuf)); \ + named_main_earlyfatal("cap_set_proc failed: " \ + "%s", \ + strbuf); \ + } \ + } \ + } while (0) +#define INIT_CAP \ + do { \ + caps = cap_init(); \ + if (caps == NULL) { \ + strerror_r(errno, strbuf, sizeof(strbuf)); \ + named_main_earlyfatal("cap_init failed: %s", strbuf); \ + } \ + curcaps = cap_get_proc(); \ + if (curcaps == NULL) { \ + strerror_r(errno, strbuf, sizeof(strbuf)); \ + named_main_earlyfatal("cap_get_proc failed: %s", \ + strbuf); \ + } \ + } while (0) +#define FREE_CAP \ + { \ + cap_free(caps); \ + cap_free(curcaps); \ + } \ + while (0) + +static void +linux_initialprivs(void) { + cap_t caps; + cap_t curcaps; + cap_value_t capval; + char strbuf[ISC_STRERRORSIZE]; + int err; + + /*% + * We don't need most privileges, so we drop them right away. + * Later on linux_minprivs() will be called, which will drop our + * capabilities to the minimum needed to run the server. + */ + INIT_CAP; + + /* + * We need to be able to bind() to privileged ports, notably port 53! + */ + SET_CAP(CAP_NET_BIND_SERVICE); + + /* + * We need chroot() initially too. + */ + SET_CAP(CAP_SYS_CHROOT); + + /* + * We need setuid() as the kernel supports keeping capabilities after + * setuid(). + */ + SET_CAP(CAP_SETUID); + + /* + * Since we call initgroups, we need this. + */ + SET_CAP(CAP_SETGID); + + /* + * Without this, we run into problems reading a configuration file + * owned by a non-root user and non-world-readable on startup. + */ + SET_CAP(CAP_DAC_READ_SEARCH); + + /* + * XXX We might want to add CAP_SYS_RESOURCE, though it's not + * clear it would work right given the way linuxthreads work. + * XXXDCL But since we need to be able to set the maximum number + * of files, the stack size, data size, and core dump size to + * support named.conf options, this is now being added to test. + */ + SET_CAP(CAP_SYS_RESOURCE); + + /* + * We need to be able to set the ownership of the containing + * directory of the pid file when we create it. + */ + SET_CAP(CAP_CHOWN); + + linux_setcaps(caps); + + FREE_CAP; +} + +static void +linux_minprivs(void) { + cap_t caps; + cap_t curcaps; + cap_value_t capval; + char strbuf[ISC_STRERRORSIZE]; + int err; + + INIT_CAP; + /*% + * Drop all privileges except the ability to bind() to privileged + * ports. + * + * It's important that we drop CAP_SYS_CHROOT. If we didn't, it + * chroot() could be used to escape from the chrooted area. + */ + + SET_CAP(CAP_NET_BIND_SERVICE); + + /* + * XXX We might want to add CAP_SYS_RESOURCE, though it's not + * clear it would work right given the way linuxthreads work. + * XXXDCL But since we need to be able to set the maximum number + * of files, the stack size, data size, and core dump size to + * support named.conf options, this is now being added to test. + */ + SET_CAP(CAP_SYS_RESOURCE); + + linux_setcaps(caps); + + FREE_CAP; +} + +static void +linux_keepcaps(void) { + char strbuf[ISC_STRERRORSIZE]; + /*% + * Ask the kernel to allow us to keep our capabilities after we + * setuid(). + */ + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + if (errno != EINVAL) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("prctl() failed: %s", strbuf); + } + } else { + non_root_caps = true; + if (getuid() != 0) { + non_root = true; + } + } +} + +#endif /* HAVE_SYS_CAPABILITY_H */ + +static void +setup_syslog(const char *progname) { + int options; + + options = LOG_PID; +#ifdef LOG_NDELAY + options |= LOG_NDELAY; +#endif /* ifdef LOG_NDELAY */ + openlog(isc_file_basename(progname), options, ISC_FACILITY); +} + +void +named_os_init(const char *progname) { + setup_syslog(progname); +#ifdef HAVE_SYS_CAPABILITY_H + linux_initialprivs(); +#endif /* ifdef HAVE_SYS_CAPABILITY_H */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif /* ifdef SIGXFSZ */ +} + +void +named_os_daemonize(void) { + pid_t pid; + char strbuf[ISC_STRERRORSIZE]; + + if (pipe(dfd) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("pipe(): %s", strbuf); + } + + pid = fork(); + if (pid == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("fork(): %s", strbuf); + } + if (pid != 0) { + int n; + /* + * Wait for the child to finish loading for the first time. + * This would be so much simpler if fork() worked once we + * were multi-threaded. + */ + (void)close(dfd[1]); + do { + char buf; + n = read(dfd[0], &buf, 1); + if (n == 1) { + _exit(0); + } + } while (n == -1 && errno == EINTR); + _exit(1); + } + (void)close(dfd[0]); + + /* + * We're the child. + */ + + if (setsid() == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("setsid(): %s", strbuf); + } + + /* + * Try to set stdin, stdout, and stderr to /dev/null, but press + * on even if it fails. + * + * XXXMLG The close() calls here are unneeded on all but NetBSD, but + * are harmless to include everywhere. dup2() is supposed to close + * the FD if it is in use, but unproven-pthreads-0.16 is broken + * and will end up closing the wrong FD. This will be fixed eventually, + * and these calls will be removed. + */ + if (devnullfd != -1) { + if (devnullfd != STDIN_FILENO) { + (void)close(STDIN_FILENO); + (void)dup2(devnullfd, STDIN_FILENO); + } + if (devnullfd != STDOUT_FILENO) { + (void)close(STDOUT_FILENO); + (void)dup2(devnullfd, STDOUT_FILENO); + } + if (devnullfd != STDERR_FILENO && !named_g_keepstderr) { + (void)close(STDERR_FILENO); + (void)dup2(devnullfd, STDERR_FILENO); + } + } +} + +void +named_os_started(void) { + char buf = 0; + + /* + * Signal to the parent that we started successfully. + */ + if (dfd[0] != -1 && dfd[1] != -1) { + if (write(dfd[1], &buf, 1) != 1) { + named_main_earlyfatal("unable to signal parent that we " + "otherwise started " + "successfully."); + } + close(dfd[1]); + dfd[0] = dfd[1] = -1; + } +} + +void +named_os_opendevnull(void) { + devnullfd = open("/dev/null", O_RDWR, 0); +} + +void +named_os_closedevnull(void) { + if (devnullfd != STDIN_FILENO && devnullfd != STDOUT_FILENO && + devnullfd != STDERR_FILENO) + { + close(devnullfd); + devnullfd = -1; + } +} + +static bool +all_digits(const char *s) { + if (*s == '\0') { + return (false); + } + while (*s != '\0') { + if (!isdigit((unsigned char)(*s))) { + return (false); + } + s++; + } + return (true); +} + +void +named_os_chroot(const char *root) { + char strbuf[ISC_STRERRORSIZE]; +#ifdef HAVE_LIBSCF + named_smf_chroot = 0; +#endif /* ifdef HAVE_LIBSCF */ + if (root != NULL) { +#ifdef HAVE_CHROOT + if (chroot(root) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("chroot(): %s", strbuf); + } +#else /* ifdef HAVE_CHROOT */ + named_main_earlyfatal("chroot(): disabled"); +#endif /* ifdef HAVE_CHROOT */ + if (chdir("/") < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("chdir(/): %s", strbuf); + } +#ifdef HAVE_LIBSCF + /* Set named_smf_chroot flag on successful chroot. */ + named_smf_chroot = 1; +#endif /* ifdef HAVE_LIBSCF */ + } +} + +void +named_os_inituserinfo(const char *username) { + if (username == NULL) { + return; + } + + if (all_digits(username)) { + runas_pw = getpwuid((uid_t)atoi(username)); + } else { + runas_pw = getpwnam(username); + } + endpwent(); + + if (runas_pw == NULL) { + named_main_earlyfatal("user '%s' unknown", username); + } + + if (getuid() == 0) { + char strbuf[ISC_STRERRORSIZE]; + if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("initgroups(): %s", strbuf); + } + } +} + +void +named_os_changeuser(void) { + char strbuf[ISC_STRERRORSIZE]; + if (runas_pw == NULL || done_setuid) { + return; + } + + done_setuid = true; + + if (setgid(runas_pw->pw_gid) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("setgid(): %s", strbuf); + } + + if (setuid(runas_pw->pw_uid) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("setuid(): %s", strbuf); + } + +#if defined(HAVE_SYS_CAPABILITY_H) + /* + * Restore the ability of named to drop core after the setuid() + * call has disabled it. + */ + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("prctl(PR_SET_DUMPABLE) failed: %s", + strbuf); + } + + linux_minprivs(); +#endif /* if defined(HAVE_SYS_CAPABILITY_H) */ +} + +uid_t +ns_os_uid(void) { + if (runas_pw == NULL) { + return (0); + } + return (runas_pw->pw_uid); +} + +void +named_os_adjustnofile(void) { +#if defined(__linux__) || defined(__sun) + isc_result_t result; + isc_resourcevalue_t newvalue; + + /* + * Linux: max number of open files specified by one thread doesn't seem + * to apply to other threads on Linux. + * Sun: restriction needs to be removed sooner when hundreds of CPUs + * are available. + */ + newvalue = ISC_RESOURCE_UNLIMITED; + + result = isc_resource_setlimit(isc_resource_openfiles, newvalue); + if (result != ISC_R_SUCCESS) { + named_main_earlywarning("couldn't adjust limit on open files"); + } +#endif /* if defined(__linux__) || defined(__sun) */ +} + +void +named_os_minprivs(void) { +#if defined(HAVE_SYS_CAPABILITY_H) + linux_keepcaps(); + named_os_changeuser(); + linux_minprivs(); +#endif /* if defined(HAVE_SYS_CAPABILITY_H) */ +} + +static int +safe_open(const char *filename, mode_t mode, bool append) { + int fd; + struct stat sb; + + if (stat(filename, &sb) == -1) { + if (errno != ENOENT) { + return (-1); + } + } else if ((sb.st_mode & S_IFREG) == 0) { + errno = EOPNOTSUPP; + return (-1); + } + + if (append) { + fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, mode); + } else { + if (unlink(filename) < 0 && errno != ENOENT) { + return (-1); + } + fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, mode); + } + return (fd); +} + +static void +cleanup_pidfile(void) { + int n; + if (pidfile != NULL) { + n = unlink(pidfile); + if (n == -1 && errno != ENOENT) { + named_main_earlywarning("unlink '%s': failed", pidfile); + } + free(pidfile); + } + pidfile = NULL; +} + +static void +cleanup_lockfile(void) { + if (singletonfd != -1) { + close(singletonfd); + singletonfd = -1; + } + + if (lockfile != NULL) { + int n = unlink(lockfile); + if (n == -1 && errno != ENOENT) { + named_main_earlywarning("unlink '%s': failed", + lockfile); + } + free(lockfile); + lockfile = NULL; + } +} + +/* + * Ensure that a directory exists. + * NOTE: This function overwrites the '/' characters in 'filename' with + * nulls. The caller should copy the filename to a fresh buffer first. + */ +static int +mkdirpath(char *filename, void (*report)(const char *, ...)) { + char *slash = strrchr(filename, '/'); + char strbuf[ISC_STRERRORSIZE]; + unsigned int mode; + + if (slash != NULL && slash != filename) { + struct stat sb; + *slash = '\0'; + + if (stat(filename, &sb) == -1) { + if (errno != ENOENT) { + strerror_r(errno, strbuf, sizeof(strbuf)); + (*report)("couldn't stat '%s': %s", filename, + strbuf); + goto error; + } + if (mkdirpath(filename, report) == -1) { + goto error; + } + /* + * Handle "//", "/./" and "/../" in path. + */ + if (!strcmp(slash + 1, "") || !strcmp(slash + 1, ".") || + !strcmp(slash + 1, "..")) + { + *slash = '/'; + return (0); + } + mode = S_IRUSR | S_IWUSR | S_IXUSR; /* u=rwx */ + mode |= S_IRGRP | S_IXGRP; /* g=rx */ + mode |= S_IROTH | S_IXOTH; /* o=rx */ + if (mkdir(filename, mode) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + (*report)("couldn't mkdir '%s': %s", filename, + strbuf); + goto error; + } + if (runas_pw != NULL && + chown(filename, runas_pw->pw_uid, + runas_pw->pw_gid) == -1) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + (*report)("couldn't chown '%s': %s", filename, + strbuf); + } + } + *slash = '/'; + } + return (0); + +error: + *slash = '/'; + return (-1); +} + +#if !HAVE_SYS_CAPABILITY_H +static void +setperms(uid_t uid, gid_t gid) { +#if defined(HAVE_SETEGID) || defined(HAVE_SETRESGID) + char strbuf[ISC_STRERRORSIZE]; +#endif /* if defined(HAVE_SETEGID) || defined(HAVE_SETRESGID) */ +#if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID) + gid_t oldgid, tmpg; +#endif /* if !defined(HAVE_SETEGID) && defined(HAVE_SETRESGID) */ +#if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID) + uid_t olduid, tmpu; +#endif /* if !defined(HAVE_SETEUID) && defined(HAVE_SETRESUID) */ +#if defined(HAVE_SETEGID) + if (getegid() != gid && setegid(gid) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("unable to set effective " + "gid to %ld: %s", + (long)gid, strbuf); + } +#elif defined(HAVE_SETRESGID) + if (getresgid(&tmpg, &oldgid, &tmpg) == -1 || oldgid != gid) { + if (setresgid(-1, gid, -1) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("unable to set effective " + "gid to %d: %s", + gid, strbuf); + } + } +#endif /* if defined(HAVE_SETEGID) */ + +#if defined(HAVE_SETEUID) + if (geteuid() != uid && seteuid(uid) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("unable to set effective " + "uid to %ld: %s", + (long)uid, strbuf); + } +#elif defined(HAVE_SETRESUID) + if (getresuid(&tmpu, &olduid, &tmpu) == -1 || olduid != uid) { + if (setresuid(-1, uid, -1) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("unable to set effective " + "uid to %d: %s", + uid, strbuf); + } + } +#endif /* if defined(HAVE_SETEUID) */ +} +#endif /* !HAVE_SYS_CAPABILITY_H */ + +FILE * +named_os_openfile(const char *filename, mode_t mode, bool switch_user) { + char strbuf[ISC_STRERRORSIZE], *f; + FILE *fp; + int fd; + + /* + * Make the containing directory if it doesn't exist. + */ + f = strdup(filename); + if (f == NULL) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("couldn't strdup() '%s': %s", filename, + strbuf); + return (NULL); + } + if (mkdirpath(f, named_main_earlywarning) == -1) { + free(f); + return (NULL); + } + free(f); + + if (switch_user && runas_pw != NULL) { + uid_t olduid = getuid(); + gid_t oldgid = getgid(); +#if HAVE_SYS_CAPABILITY_H + REQUIRE(olduid == runas_pw->pw_uid); + REQUIRE(oldgid == runas_pw->pw_gid); +#else /* HAVE_SYS_CAPABILITY_H */ + /* Set UID/GID to the one we'll be running with eventually */ + setperms(runas_pw->pw_uid, runas_pw->pw_gid); +#endif + fd = safe_open(filename, mode, false); + +#if !HAVE_SYS_CAPABILITY_H + /* Restore UID/GID to previous uid/gid */ + setperms(olduid, oldgid); +#endif + + if (fd == -1) { + fd = safe_open(filename, mode, false); + if (fd != -1) { + named_main_earlywarning("Required root " + "permissions to open " + "'%s'.", + filename); + } else { + named_main_earlywarning("Could not open " + "'%s'.", + filename); + } + named_main_earlywarning("Please check file and " + "directory permissions " + "or reconfigure the filename."); + } + } else { + fd = safe_open(filename, mode, false); + } + + if (fd < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("could not open file '%s': %s", + filename, strbuf); + return (NULL); + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlywarning("could not fdopen() file '%s': %s", + filename, strbuf); + } + + return (fp); +} + +void +named_os_writepidfile(const char *filename, bool first_time) { + FILE *fh; + pid_t pid; + char strbuf[ISC_STRERRORSIZE]; + void (*report)(const char *, ...); + + /* + * The caller must ensure any required synchronization. + */ + + report = first_time ? named_main_earlyfatal : named_main_earlywarning; + + cleanup_pidfile(); + + if (filename == NULL) { + return; + } + + pidfile = strdup(filename); + if (pidfile == NULL) { + strerror_r(errno, strbuf, sizeof(strbuf)); + (*report)("couldn't strdup() '%s': %s", filename, strbuf); + return; + } + + fh = named_os_openfile(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, + first_time); + if (fh == NULL) { + cleanup_pidfile(); + return; + } + pid = getpid(); + if (fprintf(fh, "%ld\n", (long)pid) < 0) { + (*report)("fprintf() to pid file '%s' failed", filename); + (void)fclose(fh); + cleanup_pidfile(); + return; + } + if (fflush(fh) == EOF) { + (*report)("fflush() to pid file '%s' failed", filename); + (void)fclose(fh); + cleanup_pidfile(); + return; + } + (void)fclose(fh); +} + +bool +named_os_issingleton(const char *filename) { + char strbuf[ISC_STRERRORSIZE]; + struct flock lock; + + if (singletonfd != -1) { + return (true); + } + + if (strcasecmp(filename, "none") == 0) { + return (true); + } + + /* + * Make the containing directory if it doesn't exist. + */ + lockfile = strdup(filename); + if (lockfile == NULL) { + strerror_r(errno, strbuf, sizeof(strbuf)); + named_main_earlyfatal("couldn't allocate memory for '%s': %s", + filename, strbuf); + } else { + int ret = mkdirpath(lockfile, named_main_earlywarning); + if (ret == -1) { + named_main_earlywarning("couldn't create '%s'", + filename); + cleanup_lockfile(); + return (false); + } + } + + /* + * named_os_openfile() uses safeopen() which removes any existing + * files. We can't use that here. + */ + singletonfd = open(filename, O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (singletonfd == -1) { + cleanup_lockfile(); + return (false); + } + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 1; + + /* Non-blocking (does not wait for lock) */ + if (fcntl(singletonfd, F_SETLK, &lock) == -1) { + close(singletonfd); + singletonfd = -1; + return (false); + } + + return (true); +} + +void +named_os_shutdown(void) { + closelog(); + cleanup_pidfile(); + cleanup_lockfile(); +} + +void +named_os_shutdownmsg(char *command, isc_buffer_t *text) { + char *last, *ptr; + pid_t pid; + + /* Skip the command name. */ + if (strtok_r(command, " \t", &last) == NULL) { + return; + } + + if ((ptr = strtok_r(NULL, " \t", &last)) == NULL) { + return; + } + + if (strcmp(ptr, "-p") != 0) { + return; + } + + pid = getpid(); + + (void)isc_buffer_printf(text, "pid: %ld", (long)pid); +} + +void +named_os_tzset(void) { +#ifdef HAVE_TZSET + tzset(); +#endif /* ifdef HAVE_TZSET */ +} + +#ifdef HAVE_UNAME +static char unamebuf[sizeof(struct utsname)]; +#else +static const char unamebuf[] = { "unknown architecture" }; +#endif +static const char *unamep = NULL; + +static void +getuname(void) { +#ifdef HAVE_UNAME + struct utsname uts; + + memset(&uts, 0, sizeof(uts)); + if (uname(&uts) < 0) { + snprintf(unamebuf, sizeof(unamebuf), "unknown architecture"); + return; + } + + snprintf(unamebuf, sizeof(unamebuf), "%s %s %s %s", uts.sysname, + uts.machine, uts.release, uts.version); +#endif /* ifdef HAVE_UNAME */ + unamep = unamebuf; +} + +const char * +named_os_uname(void) { + if (unamep == NULL) { + getuname(); + } + return (unamep); +} diff --git a/bin/named/server.c b/bin/named/server.c new file mode 100644 index 0000000..2f21fc5 --- /dev/null +++ b/bin/named/server.c @@ -0,0 +1,16755 @@ +/* + * 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 <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#ifdef HAVE_DNSTAP +#include <fstrm.h> +#endif + +#include <isc/aes.h> +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/base64.h> +#include <isc/commandline.h> +#include <isc/dir.h> +#include <isc/file.h> +#include <isc/hash.h> +#include <isc/hex.h> +#include <isc/hmac.h> +#include <isc/httpd.h> +#include <isc/lex.h> +#include <isc/meminfo.h> +#include <isc/netmgr.h> +#include <isc/nonce.h> +#include <isc/parseint.h> +#include <isc/portset.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/resource.h> +#include <isc/result.h> +#include <isc/siphash.h> +#include <isc/stat.h> +#include <isc/stats.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/adb.h> +#include <dns/badcache.h> +#include <dns/cache.h> +#include <dns/catz.h> +#include <dns/db.h> +#include <dns/dispatch.h> +#include <dns/dlz.h> +#include <dns/dns64.h> +#include <dns/dnsrps.h> +#include <dns/dnssec.h> +#include <dns/dyndb.h> +#include <dns/events.h> +#include <dns/fixedname.h> +#include <dns/forward.h> +#include <dns/geoip.h> +#include <dns/journal.h> +#include <dns/kasp.h> +#include <dns/keymgr.h> +#include <dns/keytable.h> +#include <dns/keyvalues.h> +#include <dns/master.h> +#include <dns/masterdump.h> +#include <dns/nsec3.h> +#include <dns/nta.h> +#include <dns/order.h> +#include <dns/peer.h> +#include <dns/private.h> +#include <dns/rbt.h> +#include <dns/rdataclass.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/resolver.h> +#include <dns/rootns.h> +#include <dns/rriterator.h> +#include <dns/secalg.h> +#include <dns/soa.h> +#include <dns/stats.h> +#include <dns/time.h> +#include <dns/tkey.h> +#include <dns/tsig.h> +#include <dns/ttl.h> +#include <dns/view.h> +#include <dns/zone.h> +#include <dns/zt.h> + +#include <dst/dst.h> + +#include <isccfg/grammar.h> +#include <isccfg/kaspconf.h> +#include <isccfg/namedconf.h> + +#include <ns/client.h> +#include <ns/hooks.h> +#include <ns/interfacemgr.h> +#include <ns/listenlist.h> + +#include <bind9/check.h> + +#include <named/config.h> +#include <named/control.h> +#if defined(HAVE_GEOIP2) +#include <named/geoip.h> +#endif /* HAVE_GEOIP2 */ +#include <named/log.h> +#include <named/logconf.h> +#include <named/main.h> +#include <named/os.h> +#include <named/server.h> +#include <named/statschannel.h> +#include <named/tkeyconf.h> +#include <named/transportconf.h> +#include <named/tsigconf.h> +#include <named/zoneconf.h> +#ifdef HAVE_LIBSCF +#include <stdlib.h> + +#include <named/smf_globals.h> +#endif /* ifdef HAVE_LIBSCF */ + +#ifdef HAVE_LMDB +#include <lmdb.h> +#define count_newzones count_newzones_db +#define configure_newzones configure_newzones_db +#define dumpzone dumpzone_db +#else /* HAVE_LMDB */ +#define count_newzones count_newzones_file +#define configure_newzones configure_newzones_file +#define dumpzone dumpzone_file +#endif /* HAVE_LMDB */ + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif /* ifndef SIZE_MAX */ + +#ifndef SIZE_AS_PERCENT +#define SIZE_AS_PERCENT ((size_t)-2) +#endif /* ifndef SIZE_AS_PERCENT */ + +#ifdef TUNE_LARGE +#define RESOLVER_NTASKS_PERCPU 32 +#else +#define RESOLVER_NTASKS_PERCPU 8 +#endif /* TUNE_LARGE */ + +/* RFC7828 defines timeout as 16-bit value specified in units of 100 + * milliseconds, so the maximum and minimum advertised and keepalive + * timeouts are capped by the data type (it's ~109 minutes) + */ +#define MIN_INITIAL_TIMEOUT UINT32_C(2500) /* 2.5 seconds */ +#define MAX_INITIAL_TIMEOUT UINT32_C(120000) /* 2 minutes */ +#define MIN_IDLE_TIMEOUT UINT32_C(100) /* 0.1 seconds */ +#define MAX_IDLE_TIMEOUT UINT32_C(120000) /* 2 minutes */ +#define MIN_KEEPALIVE_TIMEOUT UINT32_C(100) /* 0.1 seconds */ +#define MAX_KEEPALIVE_TIMEOUT UINT32_C(UINT16_MAX * 100) +#define MIN_ADVERTISED_TIMEOUT UINT32_C(0) /* No minimum */ +#define MAX_ADVERTISED_TIMEOUT UINT32_C(UINT16_MAX * 100) + +/*% + * Check an operation for failure. Assumes that the function + * using it has a 'result' variable and a 'cleanup' label. + */ +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +#define TCHECK(op) \ + do { \ + tresult = (op); \ + if (tresult != ISC_R_SUCCESS) { \ + isc_buffer_clear(*text); \ + goto cleanup; \ + } \ + } while (0) + +#define CHECKM(op, msg) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, \ + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, \ + "%s: %s", msg, \ + isc_result_totext(result)); \ + goto cleanup; \ + } \ + } while (0) + +#define CHECKMF(op, msg, file) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, \ + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, \ + "%s '%s': %s", msg, file, \ + isc_result_totext(result)); \ + goto cleanup; \ + } \ + } while (0) + +#define CHECKFATAL(op, msg) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + fatal(server, msg, result); \ + } while (0) + +/*% + * Maximum ADB size for views that share a cache. Use this limit to suppress + * the total of memory footprint, which should be the main reason for sharing + * a cache. Only effective when a finite max-cache-size is specified. + * This is currently defined to be 8MB. + */ +#define MAX_ADB_SIZE_FOR_CACHESHARE 8388608U + +struct named_dispatch { + isc_sockaddr_t addr; + unsigned int dispatchgen; + dns_dispatch_t *dispatch; + ISC_LINK(struct named_dispatch) link; +}; + +struct named_cache { + dns_cache_t *cache; + dns_view_t *primaryview; + bool needflush; + bool adbsizeadjusted; + dns_rdataclass_t rdclass; + ISC_LINK(named_cache_t) link; +}; + +struct dumpcontext { + isc_mem_t *mctx; + bool dumpcache; + bool dumpzones; + bool dumpadb; + bool dumpbad; + bool dumpexpired; + bool dumpfail; + FILE *fp; + ISC_LIST(struct viewlistentry) viewlist; + struct viewlistentry *view; + struct zonelistentry *zone; + dns_dumpctx_t *mdctx; + dns_db_t *db; + dns_db_t *cache; + isc_task_t *task; + dns_dbversion_t *version; +}; + +struct viewlistentry { + dns_view_t *view; + ISC_LINK(struct viewlistentry) link; + ISC_LIST(struct zonelistentry) zonelist; +}; + +struct zonelistentry { + dns_zone_t *zone; + ISC_LINK(struct zonelistentry) link; +}; + +/*% + * Configuration context to retain for each view that allows + * new zones to be added at runtime. + */ +typedef struct ns_cfgctx { + isc_mem_t *mctx; + cfg_parser_t *conf_parser; + cfg_parser_t *add_parser; + cfg_obj_t *config; + cfg_obj_t *vconfig; + cfg_obj_t *nzf_config; + cfg_aclconfctx_t *actx; +} ns_cfgctx_t; + +/*% + * A function to write out added-zone configuration to the new_zone_file + * specified in 'view'. Maybe called by delete_zoneconf(). + */ +typedef isc_result_t (*nzfwriter_t)(const cfg_obj_t *config, dns_view_t *view); + +/*% + * Holds state information for the initial zone loading process. + * Uses the isc_refcount structure to count the number of views + * with pending zone loads, dereferencing as each view finishes. + */ +typedef struct { + named_server_t *server; + bool reconfig; + isc_refcount_t refs; +} ns_zoneload_t; + +typedef struct { + named_server_t *server; +} catz_cb_data_t; + +typedef struct catz_chgzone_event { + ISC_EVENT_COMMON(struct catz_chgzone_event); + dns_catz_entry_t *entry; + dns_catz_zone_t *origin; + dns_view_t *view; + catz_cb_data_t *cbd; + bool mod; +} catz_chgzone_event_t; + +typedef struct { + unsigned int magic; +#define DZARG_MAGIC ISC_MAGIC('D', 'z', 'a', 'r') + isc_buffer_t **text; + isc_result_t result; +} ns_dzarg_t; + +/* + * These zones should not leak onto the Internet. + */ +const char *empty_zones[] = { + /* RFC 1918 */ + "10.IN-ADDR.ARPA", "16.172.IN-ADDR.ARPA", "17.172.IN-ADDR.ARPA", + "18.172.IN-ADDR.ARPA", "19.172.IN-ADDR.ARPA", "20.172.IN-ADDR.ARPA", + "21.172.IN-ADDR.ARPA", "22.172.IN-ADDR.ARPA", "23.172.IN-ADDR.ARPA", + "24.172.IN-ADDR.ARPA", "25.172.IN-ADDR.ARPA", "26.172.IN-ADDR.ARPA", + "27.172.IN-ADDR.ARPA", "28.172.IN-ADDR.ARPA", "29.172.IN-ADDR.ARPA", + "30.172.IN-ADDR.ARPA", "31.172.IN-ADDR.ARPA", "168.192.IN-ADDR.ARPA", + + /* RFC 6598 */ + "64.100.IN-ADDR.ARPA", "65.100.IN-ADDR.ARPA", "66.100.IN-ADDR.ARPA", + "67.100.IN-ADDR.ARPA", "68.100.IN-ADDR.ARPA", "69.100.IN-ADDR.ARPA", + "70.100.IN-ADDR.ARPA", "71.100.IN-ADDR.ARPA", "72.100.IN-ADDR.ARPA", + "73.100.IN-ADDR.ARPA", "74.100.IN-ADDR.ARPA", "75.100.IN-ADDR.ARPA", + "76.100.IN-ADDR.ARPA", "77.100.IN-ADDR.ARPA", "78.100.IN-ADDR.ARPA", + "79.100.IN-ADDR.ARPA", "80.100.IN-ADDR.ARPA", "81.100.IN-ADDR.ARPA", + "82.100.IN-ADDR.ARPA", "83.100.IN-ADDR.ARPA", "84.100.IN-ADDR.ARPA", + "85.100.IN-ADDR.ARPA", "86.100.IN-ADDR.ARPA", "87.100.IN-ADDR.ARPA", + "88.100.IN-ADDR.ARPA", "89.100.IN-ADDR.ARPA", "90.100.IN-ADDR.ARPA", + "91.100.IN-ADDR.ARPA", "92.100.IN-ADDR.ARPA", "93.100.IN-ADDR.ARPA", + "94.100.IN-ADDR.ARPA", "95.100.IN-ADDR.ARPA", "96.100.IN-ADDR.ARPA", + "97.100.IN-ADDR.ARPA", "98.100.IN-ADDR.ARPA", "99.100.IN-ADDR.ARPA", + "100.100.IN-ADDR.ARPA", "101.100.IN-ADDR.ARPA", "102.100.IN-ADDR.ARPA", + "103.100.IN-ADDR.ARPA", "104.100.IN-ADDR.ARPA", "105.100.IN-ADDR.ARPA", + "106.100.IN-ADDR.ARPA", "107.100.IN-ADDR.ARPA", "108.100.IN-ADDR.ARPA", + "109.100.IN-ADDR.ARPA", "110.100.IN-ADDR.ARPA", "111.100.IN-ADDR.ARPA", + "112.100.IN-ADDR.ARPA", "113.100.IN-ADDR.ARPA", "114.100.IN-ADDR.ARPA", + "115.100.IN-ADDR.ARPA", "116.100.IN-ADDR.ARPA", "117.100.IN-ADDR.ARPA", + "118.100.IN-ADDR.ARPA", "119.100.IN-ADDR.ARPA", "120.100.IN-ADDR.ARPA", + "121.100.IN-ADDR.ARPA", "122.100.IN-ADDR.ARPA", "123.100.IN-ADDR.ARPA", + "124.100.IN-ADDR.ARPA", "125.100.IN-ADDR.ARPA", "126.100.IN-ADDR.ARPA", + "127.100.IN-ADDR.ARPA", + + /* RFC 5735 and RFC 5737 */ + "0.IN-ADDR.ARPA", /* THIS NETWORK */ + "127.IN-ADDR.ARPA", /* LOOPBACK */ + "254.169.IN-ADDR.ARPA", /* LINK LOCAL */ + "2.0.192.IN-ADDR.ARPA", /* TEST NET */ + "100.51.198.IN-ADDR.ARPA", /* TEST NET 2 */ + "113.0.203.IN-ADDR.ARPA", /* TEST NET 3 */ + "255.255.255.255.IN-ADDR.ARPA", /* BROADCAST */ + + /* Local IPv6 Unicast Addresses */ + "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6." + "ARPA", + "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6." + "ARPA", + /* LOCALLY ASSIGNED LOCAL ADDRESS SCOPE */ + "D.F.IP6.ARPA", "8.E.F.IP6.ARPA", /* LINK LOCAL */ + "9.E.F.IP6.ARPA", /* LINK LOCAL */ + "A.E.F.IP6.ARPA", /* LINK LOCAL */ + "B.E.F.IP6.ARPA", /* LINK LOCAL */ + + /* Example Prefix, RFC 3849. */ + "8.B.D.0.1.0.0.2.IP6.ARPA", + + /* RFC 7534 */ + "EMPTY.AS112.ARPA", + + /* RFC 8375 */ + "HOME.ARPA", + + NULL +}; + +noreturn static void +fatal(named_server_t *server, const char *msg, isc_result_t result); + +static void +named_server_reload(isc_task_t *task, isc_event_t *event); + +#ifdef HAVE_LIBNGHTTP2 +static isc_result_t +listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, + const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, + isc_mem_t *mctx, ns_listenelt_t **target); +#endif + +static isc_result_t +listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target); + +static isc_result_t +listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, + ns_listenlist_t **target); + +static isc_result_t +configure_forward(const cfg_obj_t *config, dns_view_t *view, + const dns_name_t *origin, const cfg_obj_t *forwarders, + const cfg_obj_t *forwardtype); + +static isc_result_t +configure_alternates(const cfg_obj_t *config, dns_view_t *view, + const cfg_obj_t *alternates); + +static isc_result_t +configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, + const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, + bool modify); + +static void +configure_zone_setviewcommit(isc_result_t result, const cfg_obj_t *zconfig, + dns_view_t *view); + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx); + +static isc_result_t +add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx); + +static void +end_reserved_dispatches(named_server_t *server, bool all); + +static void +newzone_cfgctx_destroy(void **cfgp); + +static isc_result_t +putstr(isc_buffer_t **b, const char *str); + +static isc_result_t +putmem(isc_buffer_t **b, const char *str, size_t len); + +static isc_result_t +putuint8(isc_buffer_t **b, uint8_t val); + +static isc_result_t +putnull(isc_buffer_t **b); + +static int +count_zones(const cfg_obj_t *conf); + +#ifdef HAVE_LMDB +static isc_result_t +migrate_nzf(dns_view_t *view); + +static isc_result_t +nzd_writable(dns_view_t *view); + +static isc_result_t +nzd_open(dns_view_t *view, unsigned int flags, MDB_txn **txnp, MDB_dbi *dbi); + +static isc_result_t +nzd_env_reopen(dns_view_t *view); + +static void +nzd_env_close(dns_view_t *view); + +static isc_result_t +nzd_close(MDB_txn **txnp, bool commit); + +static isc_result_t +nzd_count(dns_view_t *view, int *countp); +#else /* ifdef HAVE_LMDB */ +static isc_result_t +nzf_append(dns_view_t *view, const cfg_obj_t *zconfig); +#endif /* ifdef HAVE_LMDB */ + +/*% + * Configure a single view ACL at '*aclp'. Get its configuration from + * 'vconfig' (for per-view configuration) and maybe from 'config' + */ +static isc_result_t +configure_view_acl(const cfg_obj_t *vconfig, const cfg_obj_t *config, + const cfg_obj_t *gconfig, const char *aclname, + const char *acltuplename, cfg_aclconfctx_t *actx, + isc_mem_t *mctx, dns_acl_t **aclp) { + isc_result_t result; + const cfg_obj_t *maps[4]; + const cfg_obj_t *aclobj = NULL; + int i = 0; + + if (*aclp != NULL) { + dns_acl_detach(aclp); + } + if (vconfig != NULL) { + maps[i++] = cfg_tuple_get(vconfig, "options"); + } + if (config != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + if (gconfig != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(gconfig, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + maps[i] = NULL; + + (void)named_config_get(maps, aclname, &aclobj); + if (aclobj == NULL) { + /* + * No value available. *aclp == NULL. + */ + return (ISC_R_SUCCESS); + } + + if (acltuplename != NULL) { + /* + * If the ACL is given in an optional tuple, retrieve it. + * The parser should have ensured that a valid object be + * returned. + */ + aclobj = cfg_tuple_get(aclobj, acltuplename); + } + + result = cfg_acl_fromconfig(aclobj, config, named_g_lctx, actx, mctx, 0, + aclp); + + return (result); +} + +/*% + * Configure a sortlist at '*aclp'. Essentially the same as + * configure_view_acl() except it calls cfg_acl_fromconfig with a + * nest_level value of 2. + */ +static isc_result_t +configure_view_sortlist(const cfg_obj_t *vconfig, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + dns_acl_t **aclp) { + isc_result_t result; + const cfg_obj_t *maps[3]; + const cfg_obj_t *aclobj = NULL; + int i = 0; + + if (*aclp != NULL) { + dns_acl_detach(aclp); + } + if (vconfig != NULL) { + maps[i++] = cfg_tuple_get(vconfig, "options"); + } + if (config != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + maps[i] = NULL; + + (void)named_config_get(maps, "sortlist", &aclobj); + if (aclobj == NULL) { + return (ISC_R_SUCCESS); + } + + /* + * Use a nest level of 3 for the "top level" of the sortlist; + * this means each entry in the top three levels will be stored + * as lists of separate, nested ACLs, rather than merged together + * into IP tables as is usually done with ACLs. + */ + result = cfg_acl_fromconfig(aclobj, config, named_g_lctx, actx, mctx, 3, + aclp); + + return (result); +} + +static isc_result_t +configure_view_nametable(const cfg_obj_t *vconfig, const cfg_obj_t *config, + const char *confname, const char *conftuplename, + isc_mem_t *mctx, dns_rbt_t **rbtp) { + isc_result_t result; + const cfg_obj_t *maps[3]; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *element; + int i = 0; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + const char *str; + const cfg_obj_t *nameobj; + + if (*rbtp != NULL) { + dns_rbt_destroy(rbtp); + } + if (vconfig != NULL) { + maps[i++] = cfg_tuple_get(vconfig, "options"); + } + if (config != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + maps[i] = NULL; + + (void)named_config_get(maps, confname, &obj); + if (obj == NULL) { + /* + * No value available. *rbtp == NULL. + */ + return (ISC_R_SUCCESS); + } + + if (conftuplename != NULL) { + obj = cfg_tuple_get(obj, conftuplename); + if (cfg_obj_isvoid(obj)) { + return (ISC_R_SUCCESS); + } + } + + result = dns_rbt_create(mctx, NULL, NULL, rbtp); + if (result != ISC_R_SUCCESS) { + return (result); + } + + name = dns_fixedname_initname(&fixed); + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + nameobj = cfg_listelt_value(element); + str = cfg_obj_asstring(nameobj); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + /* + * We don't need the node data, but need to set dummy data to + * avoid a partial match with an empty node. For example, if + * we have foo.example.com and bar.example.com, we'd get a match + * for baz.example.com, which is not the expected result. + * We simply use (void *)1 as the dummy data. + */ + result = dns_rbt_addname(*rbtp, name, (void *)1); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(nameobj, named_g_lctx, ISC_LOG_ERROR, + "failed to add %s for %s: %s", str, + confname, isc_result_totext(result)); + goto cleanup; + } + } + + return (result); + +cleanup: + dns_rbt_destroy(rbtp); + return (result); +} + +static isc_result_t +ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, + unsigned char *digest, dns_rdata_ds_t *ds) { + isc_result_t result; + dns_rdata_dnskey_t keystruct; + dns_rdata_t rdata = DNS_RDATA_INIT; + uint32_t rdata1, rdata2, rdata3; + const char *datastr = NULL, *namestr = NULL; + unsigned char data[4096]; + isc_buffer_t databuf; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_region_t r; + dns_fixedname_t fname; + dns_name_t *name = NULL; + isc_buffer_t namebuf; + const char *atstr = NULL; + enum { + INIT_DNSKEY, + STATIC_DNSKEY, + INIT_DS, + STATIC_DS, + TRUSTED + } anchortype; + + REQUIRE(namestrp != NULL && *namestrp == NULL); + REQUIRE(ds != NULL); + + /* if DNSKEY, flags; if DS, key tag */ + rdata1 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata1")); + + /* if DNSKEY, protocol; if DS, algorithm */ + rdata2 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata2")); + + /* if DNSKEY, algorithm; if DS, digest type */ + rdata3 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata3")); + + namestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); + *namestrp = namestr; + + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&namebuf, namestr, strlen(namestr)); + isc_buffer_add(&namebuf, strlen(namestr)); + CHECK(dns_name_fromtext(name, &namebuf, dns_rootname, 0, NULL)); + + if (*initialp) { + atstr = cfg_obj_asstring(cfg_tuple_get(key, "anchortype")); + + if (strcasecmp(atstr, "static-key") == 0) { + *initialp = false; + anchortype = STATIC_DNSKEY; + } else if (strcasecmp(atstr, "static-ds") == 0) { + *initialp = false; + anchortype = STATIC_DS; + } else if (strcasecmp(atstr, "initial-key") == 0) { + anchortype = INIT_DNSKEY; + } else if (strcasecmp(atstr, "initial-ds") == 0) { + anchortype = INIT_DS; + } else { + cfg_obj_log(key, named_g_lctx, ISC_LOG_ERROR, + "key '%s': " + "invalid initialization method '%s'", + namestr, atstr); + result = ISC_R_FAILURE; + goto cleanup; + } + } else { + anchortype = TRUSTED; + } + + isc_buffer_init(&databuf, data, sizeof(data)); + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + + *ds = (dns_rdata_ds_t){ .common.rdclass = dns_rdataclass_in, + .common.rdtype = dns_rdatatype_ds }; + + ISC_LINK_INIT(&ds->common, link); + + switch (anchortype) { + case INIT_DNSKEY: + case STATIC_DNSKEY: + case TRUSTED: + /* + * This function should never be reached for view + * class other than IN + */ + keystruct.common.rdclass = dns_rdataclass_in; + keystruct.common.rdtype = dns_rdatatype_dnskey; + + /* + * The key data in keystruct is not dynamically allocated. + */ + keystruct.mctx = NULL; + + ISC_LINK_INIT(&keystruct.common, link); + + if (rdata1 > 0xffff) { + CHECKM(ISC_R_RANGE, "key flags"); + } + if (rdata1 & DNS_KEYFLAG_REVOKE) { + CHECKM(DST_R_BADKEYTYPE, "key flags revoke bit set"); + } + if (rdata2 > 0xff) { + CHECKM(ISC_R_RANGE, "key protocol"); + } + if (rdata3 > 0xff) { + CHECKM(ISC_R_RANGE, "key algorithm"); + } + + keystruct.flags = (uint16_t)rdata1; + keystruct.protocol = (uint8_t)rdata2; + keystruct.algorithm = (uint8_t)rdata3; + + if (!dst_algorithm_supported(keystruct.algorithm)) { + CHECK(DST_R_UNSUPPORTEDALG); + } + + datastr = cfg_obj_asstring(cfg_tuple_get(key, "data")); + CHECK(isc_base64_decodestring(datastr, &databuf)); + isc_buffer_usedregion(&databuf, &r); + keystruct.datalen = r.length; + keystruct.data = r.base; + + CHECK(dns_rdata_fromstruct(&rdata, keystruct.common.rdclass, + keystruct.common.rdtype, &keystruct, + &rrdatabuf)); + CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, + digest, ds)); + break; + + case INIT_DS: + case STATIC_DS: + if (rdata1 > 0xffff) { + CHECKM(ISC_R_RANGE, "key tag"); + } + if (rdata2 > 0xff) { + CHECKM(ISC_R_RANGE, "key algorithm"); + } + if (rdata3 > 0xff) { + CHECKM(ISC_R_RANGE, "digest type"); + } + + ds->key_tag = (uint16_t)rdata1; + ds->algorithm = (uint8_t)rdata2; + ds->digest_type = (uint8_t)rdata3; + + datastr = cfg_obj_asstring(cfg_tuple_get(key, "data")); + CHECK(isc_hex_decodestring(datastr, &databuf)); + isc_buffer_usedregion(&databuf, &r); + + switch (ds->digest_type) { + case DNS_DSDIGEST_SHA1: + if (r.length != ISC_SHA1_DIGESTLENGTH) { + CHECK(ISC_R_UNEXPECTEDEND); + } + break; + case DNS_DSDIGEST_SHA256: + if (r.length != ISC_SHA256_DIGESTLENGTH) { + CHECK(ISC_R_UNEXPECTEDEND); + } + break; + case DNS_DSDIGEST_SHA384: + if (r.length != ISC_SHA384_DIGESTLENGTH) { + CHECK(ISC_R_UNEXPECTEDEND); + } + break; + default: + cfg_obj_log(key, named_g_lctx, ISC_LOG_ERROR, + "key '%s': " + "unknown ds digest type %u", + namestr, ds->digest_type); + result = ISC_R_FAILURE; + goto cleanup; + break; + } + + ds->length = r.length; + ds->digest = digest; + memmove(ds->digest, r.base, r.length); + + break; + + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +cleanup: + return (result); +} + +static void +sfd_add(const dns_name_t *name, void *arg) { + if (arg != NULL) { + dns_view_sfd_add(arg, name); + } +} + +/*% + * Parse 'key' in the context of view configuration 'vconfig'. If successful, + * add the key to 'secroots' if both of the following conditions are true: + * + * - 'keyname_match' is NULL or it matches the owner name of 'key', + * - support for the algorithm used by 'key' is not disabled by 'resolver' + * for the owner name of 'key'. + * + * 'managed' is true for managed keys and false for trusted keys. 'mctx' is + * the memory context to use for allocating memory. + */ +static isc_result_t +process_key(const cfg_obj_t *key, dns_keytable_t *secroots, + const dns_name_t *keyname_match, dns_view_t *view, bool managed) { + dns_fixedname_t fkeyname; + dns_name_t *keyname = NULL; + const char *namestr = NULL; + dns_rdata_ds_t ds; + isc_result_t result; + bool initializing = managed; + unsigned char digest[ISC_MAX_MD_SIZE]; + isc_buffer_t b; + + result = ta_fromconfig(key, &initializing, &namestr, digest, &ds); + + switch (result) { + case ISC_R_SUCCESS: + /* + * Trust anchor was parsed correctly. + */ + isc_buffer_constinit(&b, namestr, strlen(namestr)); + isc_buffer_add(&b, strlen(namestr)); + keyname = dns_fixedname_initname(&fkeyname); + result = dns_name_fromtext(keyname, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + break; + case DST_R_UNSUPPORTEDALG: + case DST_R_BADKEYTYPE: + /* + * Key was parsed correctly, but it cannot be used; this is not + * a fatal error - log a warning about this key being ignored, + * but do not prevent any further ones from being processed. + */ + cfg_obj_log(key, named_g_lctx, ISC_LOG_WARNING, + "ignoring %s for '%s': %s", + initializing ? "initial-key" : "static-key", + namestr, isc_result_totext(result)); + return (ISC_R_SUCCESS); + case DST_R_NOCRYPTO: + /* + * Crypto support is not available. + */ + cfg_obj_log(key, named_g_lctx, ISC_LOG_ERROR, + "ignoring %s for '%s': no crypto support", + initializing ? "initial-key" : "static-key", + namestr); + return (result); + default: + /* + * Something unexpected happened; we have no choice but to + * indicate an error so that the configuration loading process + * is interrupted. + */ + cfg_obj_log(key, named_g_lctx, ISC_LOG_ERROR, + "configuring %s for '%s': %s", + initializing ? "initial-key" : "static-key", + namestr, isc_result_totext(result)); + return (ISC_R_FAILURE); + } + + /* + * If the caller requested to only load keys for a specific name and + * the owner name of this key does not match the requested name, do not + * load it. + */ + if (keyname_match != NULL && !dns_name_equal(keyname_match, keyname)) { + goto done; + } + + /* + * Ensure that 'resolver' allows using the algorithm of this key for + * its owner name. If it does not, do not load the key and log a + * warning, but do not prevent further keys from being processed. + */ + if (!dns_resolver_algorithm_supported(view->resolver, keyname, + ds.algorithm)) + { + cfg_obj_log(key, named_g_lctx, ISC_LOG_WARNING, + "ignoring %s for '%s': algorithm is disabled", + initializing ? "initial-key" : "static-key", + namestr); + goto done; + } + + /* + * Add the key to 'secroots'. Keys from a "trust-anchors" or + * "managed-keys" statement may be either static or initializing + * keys. If it's not initializing, we don't want to treat it as + * managed, so we use 'initializing' twice here, for both the + * 'managed' and 'initializing' arguments to dns_keytable_add(). + */ + result = dns_keytable_add(secroots, initializing, initializing, keyname, + &ds, sfd_add, view); + +done: + return (result); +} + +/* + * Load keys from configuration into key table. If 'keyname' is specified, + * only load keys matching that name. If 'managed' is true, load the key as + * an initializing key. + */ +static isc_result_t +load_view_keys(const cfg_obj_t *keys, dns_view_t *view, bool managed, + const dns_name_t *keyname) { + const cfg_listelt_t *elt, *elt2; + const cfg_obj_t *keylist; + isc_result_t result; + dns_keytable_t *secroots = NULL; + + CHECK(dns_view_getsecroots(view, &secroots)); + + for (elt = cfg_list_first(keys); elt != NULL; elt = cfg_list_next(elt)) + { + keylist = cfg_listelt_value(elt); + + for (elt2 = cfg_list_first(keylist); elt2 != NULL; + elt2 = cfg_list_next(elt2)) + { + CHECK(process_key(cfg_listelt_value(elt2), secroots, + keyname, view, managed)); + } + } + +cleanup: + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + if (result == DST_R_NOCRYPTO) { + result = ISC_R_SUCCESS; + } + return (result); +} + +/*% + * Check whether a key has been successfully loaded. + */ +static bool +keyloaded(dns_view_t *view, const dns_name_t *name) { + isc_result_t result; + dns_keytable_t *secroots = NULL; + dns_keynode_t *keynode = NULL; + + result = dns_view_getsecroots(view, &secroots); + if (result != ISC_R_SUCCESS) { + return (false); + } + + result = dns_keytable_find(secroots, name, &keynode); + + if (keynode != NULL) { + dns_keytable_detachkeynode(secroots, &keynode); + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + + return (result == ISC_R_SUCCESS); +} + +/*% + * Configure DNSSEC keys for a view. + * + * The per-view configuration values and the server-global defaults are read + * from 'vconfig' and 'config'. + */ +static isc_result_t +configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, + const cfg_obj_t *config, const cfg_obj_t *bindkeys, + bool auto_root, isc_mem_t *mctx) { + isc_result_t result = ISC_R_SUCCESS; + const cfg_obj_t *view_keys = NULL; + const cfg_obj_t *global_keys = NULL; + const cfg_obj_t *view_managed_keys = NULL; + const cfg_obj_t *view_trust_anchors = NULL; + const cfg_obj_t *global_managed_keys = NULL; + const cfg_obj_t *global_trust_anchors = NULL; + const cfg_obj_t *maps[4]; + const cfg_obj_t *voptions = NULL; + const cfg_obj_t *options = NULL; + const cfg_obj_t *obj = NULL; + const char *directory; + int i = 0; + + /* We don't need trust anchors for the _bind view */ + if (strcmp(view->name, "_bind") == 0 && + view->rdclass == dns_rdataclass_chaos) + { + return (ISC_R_SUCCESS); + } + + if (vconfig != NULL) { + voptions = cfg_tuple_get(vconfig, "options"); + if (voptions != NULL) { + (void)cfg_map_get(voptions, "trusted-keys", &view_keys); + + /* managed-keys and trust-anchors are synonyms. */ + (void)cfg_map_get(voptions, "managed-keys", + &view_managed_keys); + (void)cfg_map_get(voptions, "trust-anchors", + &view_trust_anchors); + + maps[i++] = voptions; + } + } + + if (config != NULL) { + (void)cfg_map_get(config, "trusted-keys", &global_keys); + + /* managed-keys and trust-anchors are synonyms. */ + (void)cfg_map_get(config, "managed-keys", &global_managed_keys); + (void)cfg_map_get(config, "trust-anchors", + &global_trust_anchors); + + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + + maps[i++] = named_g_defaults; + maps[i] = NULL; + + result = dns_view_initsecroots(view, mctx); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "couldn't create keytable"); + return (ISC_R_UNEXPECTED); + } + + result = dns_view_initntatable(view, named_g_taskmgr, named_g_timermgr); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "couldn't create NTA table"); + return (ISC_R_UNEXPECTED); + } + + if (auto_root && view->rdclass == dns_rdataclass_in) { + const cfg_obj_t *builtin_keys = NULL; + + /* + * If bind.keys exists and is populated, it overrides + * the trust-anchors clause hard-coded in named_g_config. + */ + if (bindkeys != NULL) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "obtaining root key for view %s " + "from '%s'", + view->name, named_g_server->bindkeysfile); + + (void)cfg_map_get(bindkeys, "trust-anchors", + &builtin_keys); + + if (builtin_keys == NULL) { + isc_log_write( + named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "dnssec-validation auto: " + "WARNING: root zone key " + "not found"); + } + } + + if (builtin_keys == NULL) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "using built-in root key for view %s", + view->name); + + (void)cfg_map_get(named_g_config, "trust-anchors", + &builtin_keys); + } + + if (builtin_keys != NULL) { + CHECK(load_view_keys(builtin_keys, view, true, + dns_rootname)); + } + + if (!keyloaded(view, dns_rootname)) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "root key not loaded"); + result = ISC_R_FAILURE; + goto cleanup; + } + } + + if (view->rdclass == dns_rdataclass_in) { + CHECK(load_view_keys(view_keys, view, false, NULL)); + CHECK(load_view_keys(view_trust_anchors, view, true, NULL)); + CHECK(load_view_keys(view_managed_keys, view, true, NULL)); + + CHECK(load_view_keys(global_keys, view, false, NULL)); + CHECK(load_view_keys(global_trust_anchors, view, true, NULL)); + CHECK(load_view_keys(global_managed_keys, view, true, NULL)); + } + + /* + * Add key zone for managed keys. + */ + obj = NULL; + (void)named_config_get(maps, "managed-keys-directory", &obj); + directory = (obj != NULL ? cfg_obj_asstring(obj) : NULL); + if (directory != NULL) { + result = isc_file_isdirectory(directory); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "invalid managed-keys-directory %s: %s", + directory, isc_result_totext(result)); + goto cleanup; + } else if (directory != NULL) { + if (!isc_file_isdirwritable(directory)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "managed-keys-directory '%s' " + "is not writable", + directory); + result = ISC_R_NOPERM; + goto cleanup; + } + } + + CHECK(add_keydata_zone(view, directory, named_g_mctx)); + +cleanup: + return (result); +} + +static isc_result_t +mustbesecure(const cfg_obj_t *mbs, dns_resolver_t *resolver) { + const cfg_listelt_t *element; + const cfg_obj_t *obj; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + bool value; + isc_result_t result; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + for (element = cfg_list_first(mbs); element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + value = cfg_obj_asboolean(cfg_tuple_get(obj, "value")); + CHECK(dns_resolver_setmustbesecure(resolver, name, value)); + } + + result = ISC_R_SUCCESS; + +cleanup: + return (result); +} + +/*% + * Get a dispatch appropriate for the resolver of a given view. + */ +static isc_result_t +get_view_querysource_dispatch(const cfg_obj_t **maps, int af, + dns_dispatch_t **dispatchp, bool is_firstview) { + isc_result_t result = ISC_R_FAILURE; + dns_dispatch_t *disp = NULL; + isc_sockaddr_t sa; + const cfg_obj_t *obj = NULL; + + switch (af) { + case AF_INET: + result = named_config_get(maps, "query-source", &obj); + INSIST(result == ISC_R_SUCCESS); + break; + case AF_INET6: + result = named_config_get(maps, "query-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS); + break; + default: + UNREACHABLE(); + } + + sa = *(cfg_obj_assockaddr(obj)); + INSIST(isc_sockaddr_pf(&sa) == af); + + /* + * If we don't support this address family, we're done! + */ + switch (af) { + case AF_INET: + result = isc_net_probeipv4(); + break; + case AF_INET6: + result = isc_net_probeipv6(); + break; + default: + UNREACHABLE(); + } + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + /* + * Try to find a dispatcher that we can share. + */ + if (isc_sockaddr_getport(&sa) != 0) { + INSIST(obj != NULL); + if (is_firstview) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_INFO, + "using specific query-source port " + "suppresses port randomization and can be " + "insecure."); + } + } + + result = dns_dispatch_createudp(named_g_dispatchmgr, &sa, &disp); + if (result != ISC_R_SUCCESS) { + isc_sockaddr_t any; + char buf[ISC_SOCKADDR_FORMATSIZE]; + + switch (af) { + case AF_INET: + isc_sockaddr_any(&any); + break; + case AF_INET6: + isc_sockaddr_any6(&any); + break; + } + if (isc_sockaddr_equal(&sa, &any)) { + return (ISC_R_SUCCESS); + } + isc_sockaddr_format(&sa, buf, sizeof(buf)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not get query source dispatcher (%s)", + buf); + return (result); + } + + *dispatchp = disp; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_order(dns_order_t *order, const cfg_obj_t *ent) { + dns_rdataclass_t rdclass; + dns_rdatatype_t rdtype; + const cfg_obj_t *obj; + dns_fixedname_t fixed; + unsigned int mode = 0; + const char *str; + isc_buffer_t b; + isc_result_t result; + bool addroot; + + result = named_config_getclass(cfg_tuple_get(ent, "class"), + dns_rdataclass_any, &rdclass); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = named_config_gettype(cfg_tuple_get(ent, "type"), + dns_rdatatype_any, &rdtype); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = cfg_tuple_get(ent, "name"); + if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + } else { + str = "*"; + } + addroot = (strcmp(str, "*") == 0); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + dns_fixedname_init(&fixed); + result = dns_name_fromtext(dns_fixedname_name(&fixed), &b, dns_rootname, + 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = cfg_tuple_get(ent, "ordering"); + INSIST(cfg_obj_isstring(obj)); + str = cfg_obj_asstring(obj); + if (!strcasecmp(str, "fixed")) { +#if DNS_RDATASET_FIXED + mode = DNS_RDATASETATTR_FIXEDORDER; +#else /* if DNS_RDATASET_FIXED */ + mode = DNS_RDATASETATTR_CYCLIC; +#endif /* DNS_RDATASET_FIXED */ + } else if (!strcasecmp(str, "random")) { + mode = DNS_RDATASETATTR_RANDOMIZE; + } else if (!strcasecmp(str, "cyclic")) { + mode = DNS_RDATASETATTR_CYCLIC; + } else if (!strcasecmp(str, "none")) { + mode = DNS_RDATASETATTR_NONE; + } else { + UNREACHABLE(); + } + + /* + * "*" should match everything including the root (BIND 8 compat). + * As dns_name_matcheswildcard(".", "*.") returns FALSE add a + * explicit entry for "." when the name is "*". + */ + if (addroot) { + result = dns_order_add(order, dns_rootname, rdtype, rdclass, + mode); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (dns_order_add(order, dns_fixedname_name(&fixed), rdtype, + rdclass, mode)); +} + +static isc_result_t +configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) { + isc_netaddr_t na; + dns_peer_t *peer; + const cfg_obj_t *obj; + const char *str; + isc_result_t result; + unsigned int prefixlen; + + cfg_obj_asnetprefix(cfg_map_getname(cpeer), &na, &prefixlen); + + peer = NULL; + result = dns_peer_newprefix(mctx, &na, prefixlen, &peer); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "bogus", &obj); + if (obj != NULL) { + CHECK(dns_peer_setbogus(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "provide-ixfr", &obj); + if (obj != NULL) { + CHECK(dns_peer_setprovideixfr(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "request-expire", &obj); + if (obj != NULL) { + CHECK(dns_peer_setrequestexpire(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "request-ixfr", &obj); + if (obj != NULL) { + CHECK(dns_peer_setrequestixfr(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "request-nsid", &obj); + if (obj != NULL) { + CHECK(dns_peer_setrequestnsid(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "send-cookie", &obj); + if (obj != NULL) { + CHECK(dns_peer_setsendcookie(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "edns", &obj); + if (obj != NULL) { + CHECK(dns_peer_setsupportedns(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "edns-udp-size", &obj); + if (obj != NULL) { + uint32_t udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512U) { + udpsize = 512U; + } + if (udpsize > 4096U) { + udpsize = 4096U; + } + CHECK(dns_peer_setudpsize(peer, (uint16_t)udpsize)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "edns-version", &obj); + if (obj != NULL) { + uint32_t ednsversion = cfg_obj_asuint32(obj); + if (ednsversion > 255U) { + ednsversion = 255U; + } + CHECK(dns_peer_setednsversion(peer, (uint8_t)ednsversion)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "max-udp-size", &obj); + if (obj != NULL) { + uint32_t udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512U) { + udpsize = 512U; + } + if (udpsize > 4096U) { + udpsize = 4096U; + } + CHECK(dns_peer_setmaxudp(peer, (uint16_t)udpsize)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "padding", &obj); + if (obj != NULL) { + uint32_t padding = cfg_obj_asuint32(obj); + if (padding > 512U) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "server padding value cannot " + "exceed 512: lowering"); + padding = 512U; + } + CHECK(dns_peer_setpadding(peer, (uint16_t)padding)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "tcp-only", &obj); + if (obj != NULL) { + CHECK(dns_peer_setforcetcp(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "tcp-keepalive", &obj); + if (obj != NULL) { + CHECK(dns_peer_settcpkeepalive(peer, cfg_obj_asboolean(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "transfers", &obj); + if (obj != NULL) { + CHECK(dns_peer_settransfers(peer, cfg_obj_asuint32(obj))); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "transfer-format", &obj); + if (obj != NULL) { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "many-answers") == 0) { + CHECK(dns_peer_settransferformat(peer, + dns_many_answers)); + } else if (strcasecmp(str, "one-answer") == 0) { + CHECK(dns_peer_settransferformat(peer, dns_one_answer)); + } else { + UNREACHABLE(); + } + } + + obj = NULL; + (void)cfg_map_get(cpeer, "keys", &obj); + if (obj != NULL) { + result = dns_peer_setkeybycharp(peer, cfg_obj_asstring(obj)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + obj = NULL; + if (na.family == AF_INET) { + (void)cfg_map_get(cpeer, "transfer-source", &obj); + } else { + (void)cfg_map_get(cpeer, "transfer-source-v6", &obj); + } + if (obj != NULL) { + result = dns_peer_settransfersource(peer, + cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + } + + obj = NULL; + if (na.family == AF_INET) { + (void)cfg_map_get(cpeer, "notify-source", &obj); + } else { + (void)cfg_map_get(cpeer, "notify-source-v6", &obj); + } + if (obj != NULL) { + result = dns_peer_setnotifysource(peer, + cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + } + + obj = NULL; + if (na.family == AF_INET) { + (void)cfg_map_get(cpeer, "query-source", &obj); + } else { + (void)cfg_map_get(cpeer, "query-source-v6", &obj); + } + if (obj != NULL) { + result = dns_peer_setquerysource(peer, cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + } + + *peerp = peer; + return (ISC_R_SUCCESS); + +cleanup: + dns_peer_detach(&peer); + return (result); +} + +static isc_result_t +configure_dyndb(const cfg_obj_t *dyndb, isc_mem_t *mctx, + const dns_dyndbctx_t *dctx) { + isc_result_t result = ISC_R_SUCCESS; + const cfg_obj_t *obj; + const char *name, *library; + + /* Get the name of the dyndb instance and the library path . */ + name = cfg_obj_asstring(cfg_tuple_get(dyndb, "name")); + library = cfg_obj_asstring(cfg_tuple_get(dyndb, "library")); + + obj = cfg_tuple_get(dyndb, "parameters"); + if (obj != NULL) { + result = dns_dyndb_load(library, name, cfg_obj_asstring(obj), + cfg_obj_file(obj), cfg_obj_line(obj), + mctx, dctx); + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dynamic database '%s' configuration failed: %s", + name, isc_result_totext(result)); + } + return (result); +} + +static isc_result_t +disable_algorithms(const cfg_obj_t *disabled, dns_resolver_t *resolver) { + isc_result_t result; + const cfg_obj_t *algorithms; + const cfg_listelt_t *element; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(disabled, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + + algorithms = cfg_tuple_get(disabled, "algorithms"); + for (element = cfg_list_first(algorithms); element != NULL; + element = cfg_list_next(element)) + { + isc_textregion_t r; + dns_secalg_t alg; + + DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); + r.length = strlen(r.base); + + result = dns_secalg_fromtext(&alg, &r); + if (result != ISC_R_SUCCESS) { + uint8_t ui; + result = isc_parse_uint8(&ui, r.base, 10); + alg = ui; + } + if (result != ISC_R_SUCCESS) { + cfg_obj_log(cfg_listelt_value(element), named_g_lctx, + ISC_LOG_ERROR, "invalid algorithm"); + CHECK(result); + } + CHECK(dns_resolver_disable_algorithm(resolver, name, alg)); + } +cleanup: + return (result); +} + +static isc_result_t +disable_ds_digests(const cfg_obj_t *disabled, dns_resolver_t *resolver) { + isc_result_t result; + const cfg_obj_t *digests; + const cfg_listelt_t *element; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(disabled, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + + digests = cfg_tuple_get(disabled, "digests"); + for (element = cfg_list_first(digests); element != NULL; + element = cfg_list_next(element)) + { + isc_textregion_t r; + dns_dsdigest_t digest; + + DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); + r.length = strlen(r.base); + + /* disable_ds_digests handles numeric values. */ + result = dns_dsdigest_fromtext(&digest, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(cfg_listelt_value(element), named_g_lctx, + ISC_LOG_ERROR, "invalid algorithm"); + CHECK(result); + } + CHECK(dns_resolver_disable_ds_digest(resolver, name, digest)); + } +cleanup: + return (result); +} + +static bool +on_disable_list(const cfg_obj_t *disablelist, dns_name_t *zonename) { + const cfg_listelt_t *element; + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + const cfg_obj_t *value; + const char *str; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + + for (element = cfg_list_first(disablelist); element != NULL; + element = cfg_list_next(element)) + { + value = cfg_listelt_value(element); + str = cfg_obj_asstring(value); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (dns_name_equal(name, zonename)) { + return (true); + } + } + return (false); +} + +static isc_result_t +check_dbtype(dns_zone_t *zone, unsigned int dbtypec, const char **dbargv, + isc_mem_t *mctx) { + char **argv = NULL; + unsigned int i; + isc_result_t result = ISC_R_SUCCESS; + + CHECK(dns_zone_getdbtype(zone, &argv, mctx)); + + /* + * Check that all the arguments match. + */ + for (i = 0; i < dbtypec; i++) { + if (argv[i] == NULL || strcmp(argv[i], dbargv[i]) != 0) { + CHECK(ISC_R_FAILURE); + + /* + * Check that there are not extra arguments. + */ + } + } + + /* + * Check that there are not extra arguments. + */ + if (i == dbtypec && argv[i] != NULL) { + result = ISC_R_FAILURE; + } + +cleanup: + isc_mem_free(mctx, argv); + return (result); +} + +static isc_result_t +setquerystats(dns_zone_t *zone, isc_mem_t *mctx, dns_zonestat_level_t level) { + isc_result_t result; + isc_stats_t *zoneqrystats; + + dns_zone_setstatlevel(zone, level); + + zoneqrystats = NULL; + if (level == dns_zonestat_full) { + result = isc_stats_create(mctx, &zoneqrystats, + ns_statscounter_max); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + dns_zone_setrequeststats(zone, zoneqrystats); + if (zoneqrystats != NULL) { + isc_stats_detach(&zoneqrystats); + } + + return (ISC_R_SUCCESS); +} + +static named_cache_t * +cachelist_find(named_cachelist_t *cachelist, const char *cachename, + dns_rdataclass_t rdclass) { + named_cache_t *nsc; + + for (nsc = ISC_LIST_HEAD(*cachelist); nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) + { + if (nsc->rdclass == rdclass && + strcmp(dns_cache_getname(nsc->cache), cachename) == 0) + { + return (nsc); + } + } + + return (NULL); +} + +static bool +cache_reusable(dns_view_t *originview, dns_view_t *view, + bool new_zero_no_soattl) { + if (originview->rdclass != view->rdclass || + originview->checknames != view->checknames || + dns_resolver_getzeronosoattl(originview->resolver) != + new_zero_no_soattl || + originview->acceptexpired != view->acceptexpired || + originview->enablevalidation != view->enablevalidation || + originview->maxcachettl != view->maxcachettl || + originview->maxncachettl != view->maxncachettl) + { + return (false); + } + + return (true); +} + +static bool +cache_sharable(dns_view_t *originview, dns_view_t *view, + bool new_zero_no_soattl, uint64_t new_max_cache_size, + uint32_t new_stale_ttl, uint32_t new_stale_refresh_time) { + /* + * If the cache cannot even reused for the same view, it cannot be + * shared with other views. + */ + if (!cache_reusable(originview, view, new_zero_no_soattl)) { + return (false); + } + + /* + * Check other cache related parameters that must be consistent among + * the sharing views. + */ + if (dns_cache_getservestalettl(originview->cache) != new_stale_ttl || + dns_cache_getservestalerefresh(originview->cache) != + new_stale_refresh_time || + dns_cache_getcachesize(originview->cache) != new_max_cache_size) + { + return (false); + } + + return (true); +} + +/* + * Callback from DLZ configure when the driver sets up a writeable zone + */ +static isc_result_t +dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, dns_zone_t *zone) { + dns_name_t *origin = dns_zone_getorigin(zone); + dns_rdataclass_t zclass = view->rdclass; + isc_result_t result; + + result = dns_zonemgr_managezone(named_g_server->zonemgr, zone); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_zone_setstats(zone, named_g_server->zonestats); + + return (named_zone_configure_writeable_dlz(dlzdb, zone, zclass, + origin)); +} + +static isc_result_t +dns64_reverse(dns_view_t *view, isc_mem_t *mctx, isc_netaddr_t *na, + unsigned int prefixlen, const char *server, const char *contact) { + char reverse[48 + sizeof("ip6.arpa.")] = { 0 }; + char buf[sizeof("x.x.")]; + const char *dns64_dbtype[4] = { "_dns64", "dns64", ".", "." }; + const char *sep = ": view "; + const char *viewname = view->name; + const unsigned char *s6; + dns_fixedname_t fixed; + dns_name_t *name; + dns_zone_t *zone = NULL; + int dns64_dbtypec = 4; + isc_buffer_t b; + isc_result_t result; + + REQUIRE(prefixlen == 32 || prefixlen == 40 || prefixlen == 48 || + prefixlen == 56 || prefixlen == 64 || prefixlen == 96); + + if (!strcmp(viewname, "_default")) { + sep = ""; + viewname = ""; + } + + /* + * Construct the reverse name of the zone. + */ + s6 = na->type.in6.s6_addr; + while (prefixlen > 0) { + prefixlen -= 8; + snprintf(buf, sizeof(buf), "%x.%x.", s6[prefixlen / 8] & 0xf, + (s6[prefixlen / 8] >> 4) & 0xf); + strlcat(reverse, buf, sizeof(reverse)); + } + strlcat(reverse, "ip6.arpa.", sizeof(reverse)); + + /* + * Create the actual zone. + */ + if (server != NULL) { + dns64_dbtype[2] = server; + } + if (contact != NULL) { + dns64_dbtype[3] = contact; + } + name = dns_fixedname_initname(&fixed); + isc_buffer_constinit(&b, reverse, strlen(reverse)); + isc_buffer_add(&b, strlen(reverse)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + CHECK(dns_zone_create(&zone, mctx)); + CHECK(dns_zone_setorigin(zone, name)); + dns_zone_setview(zone, view); + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, zone)); + dns_zone_setclass(zone, view->rdclass); + dns_zone_settype(zone, dns_zone_primary); + dns_zone_setstats(zone, named_g_server->zonestats); + dns_zone_setdbtype(zone, dns64_dbtypec, dns64_dbtype); + if (view->queryacl != NULL) { + dns_zone_setqueryacl(zone, view->queryacl); + } + if (view->queryonacl != NULL) { + dns_zone_setqueryonacl(zone, view->queryonacl); + } + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + CHECK(setquerystats(zone, mctx, dns_zonestat_none)); /* XXXMPA */ + CHECK(dns_view_addzone(view, zone)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dns64 reverse zone%s%s: %s", sep, viewname, reverse); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + return (result); +} + +#ifdef USE_DNSRPS +typedef struct conf_dnsrps_ctx conf_dnsrps_ctx_t; +struct conf_dnsrps_ctx { + isc_result_t result; + char *cstr; + size_t cstr_size; + isc_mem_t *mctx; +}; + +/* + * Add to the DNSRPS configuration string. + */ +static bool +conf_dnsrps_sadd(conf_dnsrps_ctx_t *ctx, const char *p, ...) { + size_t new_len, cur_len, new_cstr_size; + char *new_cstr; + va_list args; + + if (ctx->cstr == NULL) { + ctx->cstr = isc_mem_get(ctx->mctx, 256); + ctx->cstr[0] = '\0'; + ctx->cstr_size = 256; + } + + cur_len = strlen(ctx->cstr); + va_start(args, p); + new_len = vsnprintf(ctx->cstr + cur_len, ctx->cstr_size - cur_len, p, + args) + + 1; + va_end(args); + + if (cur_len + new_len <= ctx->cstr_size) { + return (true); + } + + new_cstr_size = ((cur_len + new_len) / 256 + 1) * 256; + new_cstr = isc_mem_get(ctx->mctx, new_cstr_size); + + memmove(new_cstr, ctx->cstr, cur_len); + isc_mem_put(ctx->mctx, ctx->cstr, ctx->cstr_size); + ctx->cstr_size = new_cstr_size; + ctx->cstr = new_cstr; + + /* cannot use args twice after a single va_start()on some systems */ + va_start(args, p); + vsnprintf(ctx->cstr + cur_len, ctx->cstr_size - cur_len, p, args); + va_end(args); + return (true); +} + +/* + * Get an DNSRPS configuration value using the global and view options + * for the default. Return false upon failure. + */ +static bool +conf_dnsrps_get(const cfg_obj_t **sub_obj, const cfg_obj_t **maps, + const cfg_obj_t *obj, const char *name, + conf_dnsrps_ctx_t *ctx) { + if (ctx != NULL && ctx->result != ISC_R_SUCCESS) { + *sub_obj = NULL; + return (false); + } + + *sub_obj = cfg_tuple_get(obj, name); + if (cfg_obj_isvoid(*sub_obj)) { + *sub_obj = NULL; + if (maps != NULL && + ISC_R_SUCCESS != named_config_get(maps, name, sub_obj)) + { + *sub_obj = NULL; + } + } + return (true); +} + +/* + * Handle a DNSRPS boolean configuration value with the global and view + * options providing the default. + */ +static void +conf_dnsrps_yes_no(const cfg_obj_t *obj, const char *name, + conf_dnsrps_ctx_t *ctx) { + const cfg_obj_t *sub_obj; + + if (!conf_dnsrps_get(&sub_obj, NULL, obj, name, ctx)) { + return; + } + if (sub_obj == NULL) { + return; + } + if (ctx == NULL) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "\"%s\" without \"dnsrps-enable yes\"", name); + return; + } + + conf_dnsrps_sadd(ctx, " %s %s", name, + cfg_obj_asboolean(sub_obj) ? "yes" : "no"); +} + +static void +conf_dnsrps_num(const cfg_obj_t *obj, const char *name, + conf_dnsrps_ctx_t *ctx) { + const cfg_obj_t *sub_obj; + + if (!conf_dnsrps_get(&sub_obj, NULL, obj, name, ctx)) { + return; + } + if (sub_obj == NULL) { + return; + } + if (ctx == NULL) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "\"%s\" without \"dnsrps-enable yes\"", name); + return; + } + + if (cfg_obj_isduration(sub_obj)) { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asduration(sub_obj)); + } else { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asuint32(sub_obj)); + } +} + +/* + * Convert the parsed RPZ configuration statement to a string for + * dns_rpz_new_zones(). + */ +static isc_result_t +conf_dnsrps(dns_view_t *view, const cfg_obj_t **maps, bool nsip_enabled, + bool nsdname_enabled, dns_rpz_zbits_t *nsip_on, + dns_rpz_zbits_t *nsdname_on, char **rps_cstr, size_t *rps_cstr_size, + const cfg_obj_t *rpz_obj, const cfg_listelt_t *zone_element) { + conf_dnsrps_ctx_t ctx; + const cfg_obj_t *zone_obj, *obj; + dns_rpz_num_t rpz_num; + bool on; + const char *s; + + memset(&ctx, 0, sizeof(ctx)); + ctx.result = ISC_R_SUCCESS; + ctx.mctx = view->mctx; + + for (rpz_num = 0; zone_element != NULL && ctx.result == ISC_R_SUCCESS; + ++rpz_num) + { + zone_obj = cfg_listelt_value(zone_element); + + s = cfg_obj_asstring(cfg_tuple_get(zone_obj, "zone name")); + conf_dnsrps_sadd(&ctx, "zone \"%s\"", s); + + obj = cfg_tuple_get(zone_obj, "policy"); + if (!cfg_obj_isvoid(obj)) { + s = cfg_obj_asstring(cfg_tuple_get(obj, "policy name")); + conf_dnsrps_sadd(&ctx, " policy %s", s); + if (strcasecmp(s, "cname") == 0) { + s = cfg_obj_asstring( + cfg_tuple_get(obj, "cname")); + conf_dnsrps_sadd(&ctx, " %s", s); + } + } + + conf_dnsrps_yes_no(zone_obj, "recursive-only", &ctx); + conf_dnsrps_yes_no(zone_obj, "log", &ctx); + conf_dnsrps_num(zone_obj, "max-policy-ttl", &ctx); + obj = cfg_tuple_get(rpz_obj, "nsip-enable"); + if (!cfg_obj_isvoid(obj)) { + if (cfg_obj_asboolean(obj)) { + *nsip_on |= DNS_RPZ_ZBIT(rpz_num); + } else { + *nsip_on &= ~DNS_RPZ_ZBIT(rpz_num); + } + } + on = ((*nsip_on & DNS_RPZ_ZBIT(rpz_num)) != 0); + if (nsip_enabled != on) { + conf_dnsrps_sadd(&ctx, on ? " nsip-enable yes " + : " nsip-enable no "); + } + obj = cfg_tuple_get(rpz_obj, "nsdname-enable"); + if (!cfg_obj_isvoid(obj)) { + if (cfg_obj_asboolean(obj)) { + *nsdname_on |= DNS_RPZ_ZBIT(rpz_num); + } else { + *nsdname_on &= ~DNS_RPZ_ZBIT(rpz_num); + } + } + on = ((*nsdname_on & DNS_RPZ_ZBIT(rpz_num)) != 0); + if (nsdname_enabled != on) { + conf_dnsrps_sadd(&ctx, on ? " nsdname-enable yes " + : " nsdname-enable no "); + } + conf_dnsrps_sadd(&ctx, ";\n"); + zone_element = cfg_list_next(zone_element); + } + + conf_dnsrps_yes_no(rpz_obj, "recursive-only", &ctx); + conf_dnsrps_num(rpz_obj, "max-policy-ttl", &ctx); + conf_dnsrps_num(rpz_obj, "min-ns-dots", &ctx); + conf_dnsrps_yes_no(rpz_obj, "qname-wait-recurse", &ctx); + conf_dnsrps_yes_no(rpz_obj, "break-dnssec", &ctx); + if (!nsip_enabled) { + conf_dnsrps_sadd(&ctx, " nsip-enable no "); + } + if (!nsdname_enabled) { + conf_dnsrps_sadd(&ctx, " nsdname-enable no "); + } + + /* + * Get the general dnsrpzd parameters from the response-policy + * statement in the view and the general options. + */ + if (conf_dnsrps_get(&obj, maps, rpz_obj, "dnsrps-options", &ctx) && + obj != NULL) + { + conf_dnsrps_sadd(&ctx, " %s\n", cfg_obj_asstring(obj)); + } + + if (ctx.result == ISC_R_SUCCESS) { + *rps_cstr = ctx.cstr; + *rps_cstr_size = ctx.cstr_size; + } else { + if (ctx.cstr != NULL) { + isc_mem_put(ctx.mctx, ctx.cstr, ctx.cstr_size); + } + *rps_cstr = NULL; + *rps_cstr_size = 0; + } + return (ctx.result); +} +#endif /* ifdef USE_DNSRPS */ + +static isc_result_t +configure_rpz_name(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name, + const char *str, const char *msg) { + isc_result_t result; + + result = dns_name_fromstring(name, str, DNS_NAME_DOWNCASE, view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid %s '%s'", msg, str); + } + return (result); +} + +static isc_result_t +configure_rpz_name2(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name, + const char *str, const dns_name_t *origin) { + isc_result_t result; + + result = dns_name_fromstring2(name, str, origin, DNS_NAME_DOWNCASE, + view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + } + return (result); +} + +static isc_result_t +configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, + bool recursive_only_default, bool add_soa_default, + dns_ttl_t ttl_default, uint32_t minupdateinterval_default, + const dns_rpz_zone_t *old, bool *old_rpz_okp) { + const cfg_obj_t *rpz_obj, *obj; + const char *str; + dns_rpz_zone_t *zone = NULL; + isc_result_t result; + dns_rpz_num_t rpz_num; + + REQUIRE(old != NULL || !*old_rpz_okp); + + rpz_obj = cfg_listelt_value(element); + + if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) { + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "limit of %d response policy zones exceeded", + DNS_RPZ_MAX_ZONES); + return (ISC_R_FAILURE); + } + + result = dns_rpz_new_zone(view->rpzs, &zone); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "Error creating new RPZ zone : %s", + isc_result_totext(result)); + return (result); + } + + obj = cfg_tuple_get(rpz_obj, "recursive-only"); + if (cfg_obj_isvoid(obj) ? recursive_only_default + : cfg_obj_asboolean(obj)) + { + view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(zone->num); + } else { + view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(zone->num); + } + + obj = cfg_tuple_get(rpz_obj, "log"); + if (!cfg_obj_isvoid(obj) && !cfg_obj_asboolean(obj)) { + view->rpzs->p.no_log |= DNS_RPZ_ZBIT(zone->num); + } else { + view->rpzs->p.no_log &= ~DNS_RPZ_ZBIT(zone->num); + } + + obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isduration(obj)) { + zone->max_policy_ttl = cfg_obj_asduration(obj); + } else { + zone->max_policy_ttl = ttl_default; + } + if (*old_rpz_okp && zone->max_policy_ttl != old->max_policy_ttl) { + *old_rpz_okp = false; + } + + obj = cfg_tuple_get(rpz_obj, "min-update-interval"); + if (cfg_obj_isduration(obj)) { + zone->min_update_interval = cfg_obj_asduration(obj); + } else { + zone->min_update_interval = minupdateinterval_default; + } + if (*old_rpz_okp && + zone->min_update_interval != old->min_update_interval) + { + *old_rpz_okp = false; + } + + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name")); + result = configure_rpz_name(view, rpz_obj, &zone->origin, str, "zone"); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (dns_name_equal(&zone->origin, dns_rootname)) { + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone name '%s'", str); + return (DNS_R_EMPTYLABEL); + } + if (!view->rpzs->p.dnsrps_enabled) { + for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones - 1; + ++rpz_num) + { + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, + &zone->origin)) + { + cfg_obj_log(rpz_obj, named_g_lctx, + DNS_RPZ_ERROR_LEVEL, + "duplicate '%s'", str); + result = DNS_R_DUPLICATE; + return (result); + } + } + } + if (*old_rpz_okp && !dns_name_equal(&old->origin, &zone->origin)) { + *old_rpz_okp = false; + } + + result = configure_rpz_name2(view, rpz_obj, &zone->client_ip, + DNS_RPZ_CLIENT_IP_ZONE, &zone->origin); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name2(view, rpz_obj, &zone->ip, DNS_RPZ_IP_ZONE, + &zone->origin); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name2(view, rpz_obj, &zone->nsdname, + DNS_RPZ_NSDNAME_ZONE, &zone->origin); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name2(view, rpz_obj, &zone->nsip, + DNS_RPZ_NSIP_ZONE, &zone->origin); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name(view, rpz_obj, &zone->passthru, + DNS_RPZ_PASSTHRU_NAME, "name"); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name(view, rpz_obj, &zone->drop, + DNS_RPZ_DROP_NAME, "name"); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = configure_rpz_name(view, rpz_obj, &zone->tcp_only, + DNS_RPZ_TCP_ONLY_NAME, "name"); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = cfg_tuple_get(rpz_obj, "policy"); + if (cfg_obj_isvoid(obj)) { + zone->policy = DNS_RPZ_POLICY_GIVEN; + } else { + str = cfg_obj_asstring(cfg_tuple_get(obj, "policy name")); + zone->policy = dns_rpz_str2policy(str); + INSIST(zone->policy != DNS_RPZ_POLICY_ERROR); + if (zone->policy == DNS_RPZ_POLICY_CNAME) { + str = cfg_obj_asstring(cfg_tuple_get(obj, "cname")); + result = configure_rpz_name(view, rpz_obj, &zone->cname, + str, "cname"); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } + if (*old_rpz_okp && (zone->policy != old->policy || + !dns_name_equal(&old->cname, &zone->cname))) + { + *old_rpz_okp = false; + } + + obj = cfg_tuple_get(rpz_obj, "add-soa"); + if (cfg_obj_isvoid(obj)) { + zone->addsoa = add_soa_default; + } else { + zone->addsoa = cfg_obj_asboolean(obj); + } + if (*old_rpz_okp && zone->addsoa != old->addsoa) { + *old_rpz_okp = false; + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_rpz(dns_view_t *view, dns_view_t *pview, const cfg_obj_t **maps, + const cfg_obj_t *rpz_obj, bool *old_rpz_okp) { + bool dnsrps_enabled; + const cfg_listelt_t *zone_element; + char *rps_cstr; + size_t rps_cstr_size; + const cfg_obj_t *sub_obj; + bool recursive_only_default, add_soa_default; + bool nsip_enabled, nsdname_enabled; + dns_rpz_zbits_t nsip_on, nsdname_on; + dns_ttl_t ttl_default; + uint32_t minupdateinterval_default; + dns_rpz_zones_t *zones; + const dns_rpz_zones_t *old; + bool pview_must_detach = false; + const dns_rpz_zone_t *old_zone; + isc_result_t result; + int i; + + *old_rpz_okp = false; + + zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list")); + if (zone_element == NULL) { + return (ISC_R_SUCCESS); + } + + nsip_enabled = true; + sub_obj = cfg_tuple_get(rpz_obj, "nsip-enable"); + if (!cfg_obj_isvoid(sub_obj)) { + nsip_enabled = cfg_obj_asboolean(sub_obj); + } + nsip_on = nsip_enabled ? DNS_RPZ_ALL_ZBITS : 0; + + nsdname_enabled = true; + sub_obj = cfg_tuple_get(rpz_obj, "nsdname-enable"); + if (!cfg_obj_isvoid(sub_obj)) { + nsdname_enabled = cfg_obj_asboolean(sub_obj); + } + nsdname_on = nsdname_enabled ? DNS_RPZ_ALL_ZBITS : 0; + + /* + * "dnsrps-enable yes|no" can be either a global or response-policy + * clause. + */ + dnsrps_enabled = false; + rps_cstr = NULL; + rps_cstr_size = 0; + sub_obj = NULL; + (void)named_config_get(maps, "dnsrps-enable", &sub_obj); + if (sub_obj != NULL) { + dnsrps_enabled = cfg_obj_asboolean(sub_obj); + } + sub_obj = cfg_tuple_get(rpz_obj, "dnsrps-enable"); + if (!cfg_obj_isvoid(sub_obj)) { + dnsrps_enabled = cfg_obj_asboolean(sub_obj); + } +#ifndef USE_DNSRPS + if (dnsrps_enabled) { + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "\"dnsrps-enable yes\" but" + " without `./configure --enable-dnsrps`"); + return (ISC_R_FAILURE); + } +#else /* ifndef USE_DNSRPS */ + if (dnsrps_enabled) { + if (librpz == NULL) { + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_ERROR_LEVEL, + "\"dnsrps-enable yes\" but %s", + librpz_lib_open_emsg.c); + return (ISC_R_FAILURE); + } + + /* + * Generate the DNS Response Policy Service + * configuration string. + */ + result = conf_dnsrps(view, maps, nsip_enabled, nsdname_enabled, + &nsip_on, &nsdname_on, &rps_cstr, + &rps_cstr_size, rpz_obj, zone_element); + if (result != ISC_R_SUCCESS) { + return (result); + } + } +#endif /* ifndef USE_DNSRPS */ + + result = dns_rpz_new_zones(view->mctx, named_g_taskmgr, + named_g_timermgr, rps_cstr, rps_cstr_size, + &view->rpzs); + if (result != ISC_R_SUCCESS) { + return (result); + } + + zones = view->rpzs; + + zones->p.nsip_on = nsip_on; + zones->p.nsdname_on = nsdname_on; + + sub_obj = cfg_tuple_get(rpz_obj, "recursive-only"); + if (!cfg_obj_isvoid(sub_obj) && !cfg_obj_asboolean(sub_obj)) { + recursive_only_default = false; + } else { + recursive_only_default = true; + } + + sub_obj = cfg_tuple_get(rpz_obj, "add-soa"); + if (!cfg_obj_isvoid(sub_obj) && !cfg_obj_asboolean(sub_obj)) { + add_soa_default = false; + } else { + add_soa_default = true; + } + + sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec"); + if (!cfg_obj_isvoid(sub_obj) && cfg_obj_asboolean(sub_obj)) { + zones->p.break_dnssec = true; + } else { + zones->p.break_dnssec = false; + } + + sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isduration(sub_obj)) { + ttl_default = cfg_obj_asduration(sub_obj); + } else { + ttl_default = DNS_RPZ_MAX_TTL_DEFAULT; + } + + sub_obj = cfg_tuple_get(rpz_obj, "min-update-interval"); + if (cfg_obj_isduration(sub_obj)) { + minupdateinterval_default = cfg_obj_asduration(sub_obj); + } else { + minupdateinterval_default = DNS_RPZ_MINUPDATEINTERVAL_DEFAULT; + } + + sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots"); + if (cfg_obj_isuint32(sub_obj)) { + zones->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1; + } else { + zones->p.min_ns_labels = 2; + } + + sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) { + zones->p.qname_wait_recurse = true; + } else { + zones->p.qname_wait_recurse = false; + } + + sub_obj = cfg_tuple_get(rpz_obj, "nsdname-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) { + zones->p.nsdname_wait_recurse = true; + } else { + zones->p.nsdname_wait_recurse = false; + } + + sub_obj = cfg_tuple_get(rpz_obj, "nsip-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) { + zones->p.nsip_wait_recurse = true; + } else { + zones->p.nsip_wait_recurse = false; + } + + if (pview != NULL) { + old = pview->rpzs; + } else { + result = dns_viewlist_find(&named_g_server->viewlist, + view->name, view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + pview_must_detach = true; + old = pview->rpzs; + } else { + old = NULL; + } + } + + if (old == NULL) { + *old_rpz_okp = false; + } else { + *old_rpz_okp = true; + } + + for (i = 0; zone_element != NULL; + ++i, zone_element = cfg_list_next(zone_element)) + { + INSIST(!*old_rpz_okp || old != NULL); + if (*old_rpz_okp && i < old->p.num_zones) { + old_zone = old->zones[i]; + } else { + *old_rpz_okp = false; + old_zone = NULL; + } + result = configure_rpz_zone( + view, zone_element, recursive_only_default, + add_soa_default, ttl_default, minupdateinterval_default, + old_zone, old_rpz_okp); + if (result != ISC_R_SUCCESS) { + if (pview_must_detach) { + dns_view_detach(&pview); + } + return (result); + } + } + + /* + * If this is a reloading and the parameters and list of policy + * zones are unchanged, then use the same policy data. + * Data for individual zones that must be reloaded will be merged. + */ + if (*old_rpz_okp) { + if (old != NULL && + memcmp(&old->p, &zones->p, sizeof(zones->p)) != 0) + { + *old_rpz_okp = false; + } else if ((old == NULL || old->rps_cstr == NULL) != + (zones->rps_cstr == NULL)) + { + *old_rpz_okp = false; + } else if (old != NULL && zones->rps_cstr != NULL && + strcmp(old->rps_cstr, zones->rps_cstr) != 0) + { + *old_rpz_okp = false; + } + } + + if (*old_rpz_okp) { + dns_rpz_shutdown_rpzs(view->rpzs); + dns_rpz_detach_rpzs(&view->rpzs); + dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs); + dns_rpz_detach_rpzs(&pview->rpzs); + } else if (old != NULL && pview != NULL) { + ++pview->rpzs->rpz_ver; + view->rpzs->rpz_ver = pview->rpzs->rpz_ver; + cfg_obj_log(rpz_obj, named_g_lctx, DNS_RPZ_DEBUG_LEVEL1, + "updated RPZ policy: version %d", + view->rpzs->rpz_ver); + } + + if (pview_must_detach) { + dns_view_detach(&pview); + } + + return (ISC_R_SUCCESS); +} + +static void +catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *)event0; + isc_result_t result; + dns_forwarders_t *dnsforwarders = NULL; + dns_name_t *name = NULL; + isc_buffer_t namebuf; + isc_buffer_t *confbuf; + char nameb[DNS_NAME_FORMATSIZE]; + const cfg_obj_t *zlist = NULL; + cfg_obj_t *zoneconf = NULL; + cfg_obj_t *zoneobj = NULL; + ns_cfgctx_t *cfg; + dns_zone_t *zone = NULL; + + cfg = (ns_cfgctx_t *)ev->view->new_zone_config; + if (cfg == NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: allow-new-zones statement missing from " + "config; cannot add zone from the catalog"); + goto cleanup; + } + + name = dns_catz_entry_getname(ev->entry); + + isc_buffer_init(&namebuf, nameb, DNS_NAME_FORMATSIZE); + dns_name_totext(name, true, &namebuf); + isc_buffer_putuint8(&namebuf, 0); + + result = dns_fwdtable_find(ev->view->fwdtable, name, NULL, + &dnsforwarders); + if (result == ISC_R_SUCCESS && + dnsforwarders->fwdpolicy == dns_fwdpolicy_only) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_addmodzone_taskaction: " + "zone '%s' will not be processed because of the " + "explicitly configured forwarding for that zone", + nameb); + goto cleanup; + } + + result = dns_zt_find(ev->view->zonetable, name, 0, NULL, &zone); + + if (ev->mod) { + dns_catz_zone_t *parentcatz; + + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: error \"%s\" while trying to " + "modify zone '%s'", + isc_result_totext(result), nameb); + goto cleanup; + } + + if (!dns_zone_getadded(zone)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_addmodzone_taskaction: " + "zone '%s' is not a dynamically " + "added zone", + nameb); + goto cleanup; + } + + parentcatz = dns_zone_get_parentcatz(zone); + + if (parentcatz == NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_addmodzone_taskaction: " + "zone '%s' exists and is not added by " + "a catalog zone, so won't be modified", + nameb); + goto cleanup; + } + if (parentcatz != ev->origin) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_addmodzone_taskaction: " + "zone '%s' exists in multiple " + "catalog zones", + nameb); + goto cleanup; + } + + dns_zone_detach(&zone); + } else { + /* Zone shouldn't already exist when adding */ + if (result == ISC_R_SUCCESS) { + if (dns_zone_get_parentcatz(zone) == NULL) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: " + "catz_addmodzone_taskaction: " + "zone '%s' will not be added " + "because it is an explicitly " + "configured zone", + nameb); + } else { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: " + "catz_addmodzone_taskaction: " + "zone '%s' will not be added " + "because another catalog zone " + "already contains an entry with " + "that zone", + nameb); + } + goto cleanup; + } else if (result != ISC_R_NOTFOUND && + result != DNS_R_PARTIALMATCH) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: error \"%s\" while trying to " + "add zone '%s'", + isc_result_totext(result), nameb); + goto cleanup; + } else { /* this can happen in case of DNS_R_PARTIALMATCH */ + if (zone != NULL) { + dns_zone_detach(&zone); + } + } + } + RUNTIME_CHECK(zone == NULL); + /* Create a config for new zone */ + confbuf = NULL; + result = dns_catz_generate_zonecfg(ev->origin, ev->entry, &confbuf); + if (result == ISC_R_SUCCESS) { + cfg_parser_reset(cfg->add_parser); + result = cfg_parse_buffer(cfg->add_parser, confbuf, "catz", 0, + &cfg_type_addzoneconf, 0, &zoneconf); + isc_buffer_free(&confbuf); + } + /* + * Fail if either dns_catz_generate_zonecfg() or cfg_parse_buffer3() + * failed. + */ + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: error \"%s\" while trying to generate " + "config for zone '%s'", + isc_result_totext(result), nameb); + goto cleanup; + } + CHECK(cfg_map_get(zoneconf, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) { + CHECK(ISC_R_FAILURE); + } + + /* For now we only support adding one zone at a time */ + zoneobj = cfg_listelt_value(cfg_list_first(zlist)); + + /* Mark view unfrozen so that zone can be added */ + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_view_thaw(ev->view); + result = configure_zone( + cfg->config, zoneobj, cfg->vconfig, ev->cbd->server->mctx, + ev->view, &ev->cbd->server->viewlist, + &ev->cbd->server->kasplist, cfg->actx, true, false, ev->mod); + dns_view_freeze(ev->view); + isc_task_endexclusive(task); + + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: failed to configure zone '%s' - %d", nameb, + result); + goto cleanup; + } + + /* Is it there yet? */ + CHECK(dns_zt_find(ev->view->zonetable, name, 0, NULL, &zone)); + + /* + * Load the zone from the master file. If this fails, we'll + * need to undo the configuration we've done already. + */ + result = dns_zone_load(zone, true); + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: dns_zone_load() failed " + "with %s; reverting.", + isc_result_totext(result)); + + /* If the zone loaded partially, unload it */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Remove the zone from the zone table */ + dns_zt_unmount(ev->view->zonetable, zone); + goto cleanup; + } + + /* Flag the zone as having been added at runtime */ + dns_zone_setadded(zone, true); + dns_zone_set_parentcatz(zone, ev->origin); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (zoneconf != NULL) { + cfg_obj_destroy(cfg->add_parser, &zoneconf); + } + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_detach_catz(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static void +catz_delzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *)event0; + isc_result_t result; + dns_zone_t *zone = NULL; + dns_db_t *dbp = NULL; + char cname[DNS_NAME_FORMATSIZE]; + const char *file; + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_name_format(dns_catz_entry_getname(ev->entry), cname, + DNS_NAME_FORMATSIZE); + result = dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' not found", + cname); + goto cleanup; + } + + if (!dns_zone_getadded(zone)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' is not a dynamically added zone", + cname); + goto cleanup; + } + + if (dns_zone_get_parentcatz(zone) != ev->origin) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: zone " + "'%s' exists in multiple catalog zones", + cname); + goto cleanup; + } + + /* Stop answering for this zone */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + CHECK(dns_zt_unmount(ev->view->zonetable, zone)); + file = dns_zone_getfile(zone); + if (file != NULL) { + isc_file_remove(file); + file = dns_zone_getjournal(zone); + if (file != NULL) { + isc_file_remove(file); + } + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' deleted", + cname); +cleanup: + isc_task_endexclusive(task); + if (zone != NULL) { + dns_zone_detach(&zone); + } + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_detach_catz(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static isc_result_t +catz_create_chg_task(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata, + isc_eventtype_t type) { + catz_chgzone_event_t *event = NULL; + isc_task_t *task = NULL; + isc_result_t result; + isc_taskaction_t action = NULL; + + result = isc_taskmgr_excltask(taskmgr, &task); + if (result != ISC_R_SUCCESS) { + return (result); + } + + switch (type) { + case DNS_EVENT_CATZADDZONE: + case DNS_EVENT_CATZMODZONE: + action = catz_addmodzone_taskaction; + break; + case DNS_EVENT_CATZDELZONE: + action = catz_delzone_taskaction; + break; + default: + REQUIRE(0); + UNREACHABLE(); + } + + event = (catz_chgzone_event_t *)isc_event_allocate( + view->mctx, origin, type, action, NULL, sizeof(*event)); + + event->cbd = (catz_cb_data_t *)udata; + event->entry = NULL; + event->origin = NULL; + event->view = NULL; + event->mod = (type == DNS_EVENT_CATZMODZONE); + + dns_catz_entry_attach(entry, &event->entry); + dns_catz_attach_catz(origin, &event->origin); + dns_view_attach(view, &event->view); + + isc_task_send(task, ISC_EVENT_PTR(&event)); + isc_task_detach(&task); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_addzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, dns_view_t *view, + isc_taskmgr_t *taskmgr, void *udata) { + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZADDZONE)); +} + +static isc_result_t +catz_delzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, dns_view_t *view, + isc_taskmgr_t *taskmgr, void *udata) { + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZDELZONE)); +} + +static isc_result_t +catz_modzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, dns_view_t *view, + isc_taskmgr_t *taskmgr, void *udata) { + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZMODZONE)); +} + +static isc_result_t +configure_catz_zone(dns_view_t *view, dns_view_t *pview, + const cfg_obj_t *config, const cfg_listelt_t *element) { + const cfg_obj_t *catz_obj, *obj; + dns_catz_zone_t *zone = NULL; + const char *str; + isc_result_t result; + dns_name_t origin; + dns_catz_options_t *opts; + + dns_name_init(&origin, NULL); + catz_obj = cfg_listelt_value(element); + + str = cfg_obj_asstring(cfg_tuple_get(catz_obj, "zone name")); + + result = dns_name_fromstring(&origin, str, DNS_NAME_DOWNCASE, + view->mctx); + if (result == ISC_R_SUCCESS && dns_name_equal(&origin, dns_rootname)) { + result = DNS_R_EMPTYLABEL; + } + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(catz_obj, named_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: invalid zone name '%s'", str); + goto cleanup; + } + + result = dns_catz_add_zone(view->catzs, &origin, &zone); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + cfg_obj_log(catz_obj, named_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: unable to create catalog zone '%s', " + "error %s", + str, isc_result_totext(result)); + goto cleanup; + } + + if (result == ISC_R_EXISTS) { + isc_ht_iter_t *it = NULL; + + RUNTIME_CHECK(pview != NULL); + + /* + * xxxwpk todo: reconfigure the zone!!!! + */ + cfg_obj_log(catz_obj, named_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: catalog zone '%s' will not be reconfigured", + str); + /* + * We have to walk through all the member zones and attach + * them to current view + */ + dns_catz_get_iterator(zone, &it); + + for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(it)) + { + dns_name_t *name = NULL; + dns_zone_t *dnszone = NULL; + dns_catz_entry_t *entry = NULL; + isc_result_t tresult; + + isc_ht_iter_current(it, (void **)&entry); + name = dns_catz_entry_getname(entry); + + tresult = dns_view_findzone(pview, name, &dnszone); + if (tresult != ISC_R_SUCCESS) { + continue; + } + + dns_zone_setview(dnszone, view); + dns_view_addzone(view, dnszone); + + /* + * The dns_view_findzone() call above increments the + * zone's reference count, which we need to decrement + * back. However, as dns_zone_detach() sets the + * supplied pointer to NULL, calling it is deferred + * until the dnszone variable is no longer used. + */ + dns_zone_detach(&dnszone); + } + + isc_ht_iter_destroy(&it); + + result = ISC_R_SUCCESS; + } + + dns_catz_zone_resetdefoptions(zone); + opts = dns_catz_zone_getdefoptions(zone); + + obj = cfg_tuple_get(catz_obj, "default-masters"); + if (obj == NULL || !cfg_obj_istuple(obj)) { + obj = cfg_tuple_get(catz_obj, "default-primaries"); + } + if (obj != NULL && cfg_obj_istuple(obj)) { + result = named_config_getipandkeylist( + config, "primaries", obj, view->mctx, &opts->masters); + } + + obj = cfg_tuple_get(catz_obj, "in-memory"); + if (obj != NULL && cfg_obj_isboolean(obj)) { + opts->in_memory = cfg_obj_asboolean(obj); + } + + obj = cfg_tuple_get(catz_obj, "zone-directory"); + if (!opts->in_memory && obj != NULL && cfg_obj_isstring(obj)) { + opts->zonedir = isc_mem_strdup(view->mctx, + cfg_obj_asstring(obj)); + if (isc_file_isdirectory(opts->zonedir) != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: zone-directory '%s' " + "not found; zone files will not be " + "saved", + opts->zonedir); + opts->in_memory = true; + } + } + + obj = cfg_tuple_get(catz_obj, "min-update-interval"); + if (obj != NULL && cfg_obj_isduration(obj)) { + opts->min_update_interval = cfg_obj_asduration(obj); + } + +cleanup: + dns_name_free(&origin, view->mctx); + + return (result); +} + +static catz_cb_data_t ns_catz_cbdata; +static dns_catz_zonemodmethods_t ns_catz_zonemodmethods = { + catz_addzone, catz_modzone, catz_delzone, &ns_catz_cbdata +}; + +static isc_result_t +configure_catz(dns_view_t *view, dns_view_t *pview, const cfg_obj_t *config, + const cfg_obj_t *catz_obj) { + const cfg_listelt_t *zone_element = NULL; + const dns_catz_zones_t *old = NULL; + bool pview_must_detach = false; + isc_result_t result; + + /* xxxwpk TODO do it cleaner, once, somewhere */ + ns_catz_cbdata.server = named_g_server; + + zone_element = cfg_list_first(cfg_tuple_get(catz_obj, "zone list")); + if (zone_element == NULL) { + return (ISC_R_SUCCESS); + } + + CHECK(dns_catz_new_zones(view->mctx, named_g_taskmgr, named_g_timermgr, + &view->catzs, &ns_catz_zonemodmethods)); + + if (pview != NULL) { + old = pview->catzs; + } else { + result = dns_viewlist_find(&named_g_server->viewlist, + view->name, view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + pview_must_detach = true; + old = pview->catzs; + } + } + + if (old != NULL) { + dns_catz_shutdown_catzs(view->catzs); + dns_catz_detach_catzs(&view->catzs); + dns_catz_attach_catzs(pview->catzs, &view->catzs); + dns_catz_detach_catzs(&pview->catzs); + dns_catz_prereconfig(view->catzs); + } + + while (zone_element != NULL) { + CHECK(configure_catz_zone(view, pview, config, zone_element)); + zone_element = cfg_list_next(zone_element); + } + + if (old != NULL) { + dns_catz_postreconfig(view->catzs); + } + + result = ISC_R_SUCCESS; + +cleanup: + if (pview_must_detach) { + dns_view_detach(&pview); + } + + return (result); +} + +#define CHECK_RRL(cond, pat, val1, val2) \ + do { \ + if (!(cond)) { \ + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, pat, \ + val1, val2); \ + result = ISC_R_RANGE; \ + goto cleanup; \ + } \ + } while (0) + +#define CHECK_RRL_RATE(rate, def, max_rate, name) \ + do { \ + obj = NULL; \ + rrl->rate.str = name; \ + result = cfg_map_get(map, name, &obj); \ + if (result == ISC_R_SUCCESS) { \ + rrl->rate.r = cfg_obj_asuint32(obj); \ + CHECK_RRL(rrl->rate.r <= max_rate, name " %d > %d", \ + rrl->rate.r, max_rate); \ + } else { \ + rrl->rate.r = def; \ + } \ + rrl->rate.scaled = rrl->rate.r; \ + } while (0) + +static isc_result_t +configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) { + const cfg_obj_t *obj; + dns_rrl_t *rrl; + isc_result_t result; + int min_entries, i, j; + + /* + * Most DNS servers have few clients, but intentinally open + * recursive and authoritative servers often have many. + * So start with a small number of entries unless told otherwise + * to reduce cold-start costs. + */ + min_entries = 500; + obj = NULL; + result = cfg_map_get(map, "min-table-size", &obj); + if (result == ISC_R_SUCCESS) { + min_entries = cfg_obj_asuint32(obj); + if (min_entries < 1) { + min_entries = 1; + } + } + result = dns_rrl_init(&rrl, view, min_entries); + if (result != ISC_R_SUCCESS) { + return (result); + } + + i = ISC_MAX(20000, min_entries); + obj = NULL; + result = cfg_map_get(map, "max-table-size", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= min_entries, + "max-table-size %d < min-table-size %d", i, + min_entries); + } + rrl->max_entries = i; + + CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE, + "responses-per-second"); + CHECK_RRL_RATE(referrals_per_second, rrl->responses_per_second.r, + DNS_RRL_MAX_RATE, "referrals-per-second"); + CHECK_RRL_RATE(nodata_per_second, rrl->responses_per_second.r, + DNS_RRL_MAX_RATE, "nodata-per-second"); + CHECK_RRL_RATE(nxdomains_per_second, rrl->responses_per_second.r, + DNS_RRL_MAX_RATE, "nxdomains-per-second"); + CHECK_RRL_RATE(errors_per_second, rrl->responses_per_second.r, + DNS_RRL_MAX_RATE, "errors-per-second"); + + CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE, "all-per-second"); + + CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP, "slip"); + + i = 15; + obj = NULL; + result = cfg_map_get(map, "window", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW, + "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW); + } + rrl->window = i; + + i = 0; + obj = NULL; + result = cfg_map_get(map, "qps-scale", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, ""); + } + rrl->qps_scale = i; + rrl->qps = 1.0; + + i = 24; + obj = NULL; + result = cfg_map_get(map, "ipv4-prefix-length", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 8 && i <= 32, + "invalid 'ipv4-prefix-length %d'%s", i, ""); + } + rrl->ipv4_prefixlen = i; + if (i == 32) { + rrl->ipv4_mask = 0xffffffff; + } else { + rrl->ipv4_mask = htonl(0xffffffff << (32 - i)); + } + + i = 56; + obj = NULL; + result = cfg_map_get(map, "ipv6-prefix-length", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX, + "ipv6-prefix-length %d < 16 or > %d", i, + DNS_RRL_MAX_PREFIX); + } + rrl->ipv6_prefixlen = i; + for (j = 0; j < 4; ++j) { + if (i <= 0) { + rrl->ipv6_mask[j] = 0; + } else if (i < 32) { + rrl->ipv6_mask[j] = htonl(0xffffffff << (32 - i)); + } else { + rrl->ipv6_mask[j] = 0xffffffff; + } + i -= 32; + } + + obj = NULL; + result = cfg_map_get(map, "exempt-clients", &obj); + if (result == ISC_R_SUCCESS) { + result = cfg_acl_fromconfig(obj, config, named_g_lctx, + named_g_aclconfctx, named_g_mctx, 0, + &rrl->exempt); + CHECK_RRL(result == ISC_R_SUCCESS, "invalid %s%s", + "address match list", ""); + } + + obj = NULL; + result = cfg_map_get(map, "log-only", &obj); + if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj)) { + rrl->log_only = true; + } else { + rrl->log_only = false; + } + + return (ISC_R_SUCCESS); + +cleanup: + dns_rrl_view_destroy(view); + return (result); +} + +static isc_result_t +add_soa(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + const dns_name_t *origin, const dns_name_t *contact) { + dns_dbnode_t *node = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + isc_result_t result; + unsigned char buf[DNS_SOA_BUFFERSIZE]; + + CHECK(dns_soa_buildrdata(origin, contact, dns_db_class(db), 0, 28800, + 7200, 604800, 86400, buf, &rdata)); + + dns_rdatalist_init(&rdatalist); + rdatalist.type = rdata.type; + rdatalist.rdclass = rdata.rdclass; + rdatalist.ttl = 86400; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + CHECK(dns_rdatalist_tordataset(&rdatalist, &rdataset)); + CHECK(dns_db_findnode(db, name, true, &node)); + CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL)); + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static isc_result_t +add_ns(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + const dns_name_t *nsname) { + dns_dbnode_t *node = NULL; + dns_rdata_ns_t ns; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + isc_result_t result; + isc_buffer_t b; + unsigned char buf[DNS_NAME_MAXWIRE]; + + isc_buffer_init(&b, buf, sizeof(buf)); + + ns.common.rdtype = dns_rdatatype_ns; + ns.common.rdclass = dns_db_class(db); + ns.mctx = NULL; + dns_name_init(&ns.name, NULL); + dns_name_clone(nsname, &ns.name); + CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), dns_rdatatype_ns, + &ns, &b)); + + dns_rdatalist_init(&rdatalist); + rdatalist.type = rdata.type; + rdatalist.rdclass = rdata.rdclass; + rdatalist.ttl = 86400; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + CHECK(dns_rdatalist_tordataset(&rdatalist, &rdataset)); + CHECK(dns_db_findnode(db, name, true, &node)); + CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL)); + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static isc_result_t +create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, + const cfg_obj_t *zonelist, const char **empty_dbtype, + int empty_dbtypec, dns_zonestat_level_t statlevel) { + char namebuf[DNS_NAME_FORMATSIZE]; + const cfg_listelt_t *element; + const cfg_obj_t *obj; + const cfg_obj_t *zconfig; + const cfg_obj_t *zoptions; + const char *rbt_dbtype[4] = { "rbt" }; + const char *sep = ": view "; + const char *str; + const char *viewname = view->name; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_fixedname_t cfixed; + dns_fixedname_t fixed; + dns_fixedname_t nsfixed; + dns_name_t *contact; + dns_name_t *ns; + dns_name_t *zname; + dns_zone_t *zone = NULL; + int rbt_dbtypec = 1; + isc_result_t result; + dns_namereln_t namereln; + int order; + unsigned int nlabels; + + zname = dns_fixedname_initname(&fixed); + ns = dns_fixedname_initname(&nsfixed); + contact = dns_fixedname_initname(&cfixed); + + /* + * Look for forward "zones" beneath this empty zone and if so + * create a custom db for the empty zone. + */ + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + zconfig = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + CHECK(dns_name_fromstring(zname, str, 0, NULL)); + namereln = dns_name_fullcompare(zname, name, &order, &nlabels); + if (namereln != dns_namereln_subdomain) { + continue; + } + + zoptions = cfg_tuple_get(zconfig, "options"); + + obj = NULL; + (void)cfg_map_get(zoptions, "type", &obj); + if (obj != NULL && + strcasecmp(cfg_obj_asstring(obj), "forward") == 0) + { + obj = NULL; + (void)cfg_map_get(zoptions, "forward", &obj); + if (obj == NULL) { + continue; + } + if (strcasecmp(cfg_obj_asstring(obj), "only") != 0) { + continue; + } + } + if (db == NULL) { + CHECK(dns_db_create(view->mctx, "rbt", name, + dns_dbtype_zone, view->rdclass, 0, + NULL, &db)); + CHECK(dns_db_newversion(db, &version)); + if (strcmp(empty_dbtype[2], "@") == 0) { + dns_name_clone(name, ns); + } else { + CHECK(dns_name_fromstring(ns, empty_dbtype[2], + 0, NULL)); + } + CHECK(dns_name_fromstring(contact, empty_dbtype[3], 0, + NULL)); + CHECK(add_soa(db, version, name, ns, contact)); + CHECK(add_ns(db, version, name, ns)); + } + CHECK(add_ns(db, version, zname, dns_rootname)); + } + + /* + * Is the existing zone the ok to use? + */ + if (pzone != NULL) { + unsigned int typec; + const char **dbargv; + + if (db != NULL) { + typec = rbt_dbtypec; + dbargv = rbt_dbtype; + } else { + typec = empty_dbtypec; + dbargv = empty_dbtype; + } + + result = check_dbtype(pzone, typec, dbargv, view->mctx); + if (result != ISC_R_SUCCESS) { + pzone = NULL; + } + + if (pzone != NULL && + dns_zone_gettype(pzone) != dns_zone_primary) + { + pzone = NULL; + } + if (pzone != NULL && dns_zone_getfile(pzone) != NULL) { + pzone = NULL; + } + if (pzone != NULL) { + dns_zone_getraw(pzone, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + pzone = NULL; + } + } + } + + if (pzone == NULL) { + CHECK(dns_zonemgr_createzone(named_g_server->zonemgr, &zone)); + CHECK(dns_zone_setorigin(zone, name)); + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, zone)); + if (db == NULL) { + dns_zone_setdbtype(zone, empty_dbtypec, empty_dbtype); + } + dns_zone_setclass(zone, view->rdclass); + dns_zone_settype(zone, dns_zone_primary); + dns_zone_setstats(zone, named_g_server->zonestats); + } else { + dns_zone_attach(pzone, &zone); + } + + dns_zone_setoption(zone, ~DNS_ZONEOPT_NOCHECKNS, false); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setautomatic(zone, true); + if (view->queryacl != NULL) { + dns_zone_setqueryacl(zone, view->queryacl); + } else { + dns_zone_clearqueryacl(zone); + } + if (view->queryonacl != NULL) { + dns_zone_setqueryonacl(zone, view->queryonacl); + } else { + dns_zone_clearqueryonacl(zone); + } + dns_zone_clearupdateacl(zone); + if (view->transferacl != NULL) { + dns_zone_setxfracl(zone, view->transferacl); + } else { + dns_zone_clearxfracl(zone); + } + + CHECK(setquerystats(zone, view->mctx, statlevel)); + if (db != NULL) { + dns_db_closeversion(db, &version, true); + CHECK(dns_zone_replacedb(zone, db, false)); + } + dns_zone_setoption(zone, DNS_ZONEOPT_AUTOEMPTY, true); + dns_zone_setview(zone, view); + CHECK(dns_view_addzone(view, zone)); + + if (!strcmp(viewname, "_default")) { + sep = ""; + viewname = ""; + } + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_ZONELOAD, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "automatic empty zone%s%s: %s", sep, viewname, namebuf); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + + INSIST(version == NULL); + + return (result); +} + +static isc_result_t +create_ipv4only_zone(dns_zone_t *pzone, dns_view_t *view, + const dns_name_t *name, const char *type, isc_mem_t *mctx, + const char *server, const char *contact) { + char namebuf[DNS_NAME_FORMATSIZE]; + const char *dbtype[4] = { "_builtin", NULL, "@", "." }; + const char *sep = ": view "; + const char *viewname = view->name; + dns_zone_t *zone = NULL; + int dbtypec = 4; + isc_result_t result; + + REQUIRE(type != NULL); + + if (!strcmp(viewname, "_default")) { + sep = ""; + viewname = ""; + } + + dbtype[1] = type; + if (server != NULL) { + dbtype[2] = server; + } + if (contact != NULL) { + dbtype[3] = contact; + } + + if (pzone != NULL) { + result = check_dbtype(pzone, dbtypec, dbtype, view->mctx); + if (result != ISC_R_SUCCESS) { + pzone = NULL; + } + } + + if (pzone == NULL) { + /* + * Create the actual zone. + */ + CHECK(dns_zone_create(&zone, mctx)); + CHECK(dns_zone_setorigin(zone, name)); + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, zone)); + dns_zone_setclass(zone, view->rdclass); + dns_zone_settype(zone, dns_zone_primary); + dns_zone_setstats(zone, named_g_server->zonestats); + dns_zone_setdbtype(zone, dbtypec, dbtype); + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setautomatic(zone, true); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + } else { + dns_zone_attach(pzone, &zone); + } + if (view->queryacl != NULL) { + dns_zone_setqueryacl(zone, view->queryacl); + } else { + dns_zone_clearqueryacl(zone); + } + if (view->queryonacl != NULL) { + dns_zone_setqueryonacl(zone, view->queryonacl); + } else { + dns_zone_clearqueryonacl(zone); + } + dns_zone_setview(zone, view); + CHECK(dns_view_addzone(view, zone)); + + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "automatic ipv4only zone%s%s: %s", sep, viewname, + namebuf); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + return (result); +} + +#ifdef HAVE_DNSTAP +static isc_result_t +configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) { + isc_result_t result; + const cfg_obj_t *obj, *obj2; + const cfg_listelt_t *element; + const char *dpath; + const cfg_obj_t *dlist = NULL; + dns_dtmsgtype_t dttypes = 0; + unsigned int i; + struct fstrm_iothr_options *fopt = NULL; + + result = named_config_get(maps, "dnstap", &dlist); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + for (element = cfg_list_first(dlist); element != NULL; + element = cfg_list_next(element)) + { + const char *str; + dns_dtmsgtype_t dt = 0; + + obj = cfg_listelt_value(element); + obj2 = cfg_tuple_get(obj, "type"); + str = cfg_obj_asstring(obj2); + if (strcasecmp(str, "client") == 0) { + dt |= DNS_DTTYPE_CQ | DNS_DTTYPE_CR; + } else if (strcasecmp(str, "auth") == 0) { + dt |= DNS_DTTYPE_AQ | DNS_DTTYPE_AR; + } else if (strcasecmp(str, "resolver") == 0) { + dt |= DNS_DTTYPE_RQ | DNS_DTTYPE_RR; + } else if (strcasecmp(str, "forwarder") == 0) { + dt |= DNS_DTTYPE_FQ | DNS_DTTYPE_FR; + } else if (strcasecmp(str, "update") == 0) { + dt |= DNS_DTTYPE_UQ | DNS_DTTYPE_UR; + } else if (strcasecmp(str, "all") == 0) { + dt |= DNS_DTTYPE_CQ | DNS_DTTYPE_CR | DNS_DTTYPE_AQ | + DNS_DTTYPE_AR | DNS_DTTYPE_RQ | DNS_DTTYPE_RR | + DNS_DTTYPE_FQ | DNS_DTTYPE_FR | DNS_DTTYPE_UQ | + DNS_DTTYPE_UR; + } + + obj2 = cfg_tuple_get(obj, "mode"); + if (obj2 == NULL || cfg_obj_isvoid(obj2)) { + dttypes |= dt; + continue; + } + + str = cfg_obj_asstring(obj2); + if (strcasecmp(str, "query") == 0) { + dt &= ~DNS_DTTYPE_RESPONSE; + } else if (strcasecmp(str, "response") == 0) { + dt &= ~DNS_DTTYPE_QUERY; + } + + dttypes |= dt; + } + + if (named_g_server->dtenv == NULL && dttypes != 0) { + dns_dtmode_t dmode; + uint64_t max_size = 0; + uint32_t rolls = 0; + isc_log_rollsuffix_t suffix = isc_log_rollsuffix_increment; + + obj = NULL; + CHECKM(named_config_get(maps, "dnstap-output", &obj), + "'dnstap-output' must be set if 'dnstap' is set"); + + obj2 = cfg_tuple_get(obj, "mode"); + if (obj2 == NULL) { + CHECKM(ISC_R_FAILURE, "dnstap-output mode not found"); + } + if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) { + dmode = dns_dtmode_file; + } else { + dmode = dns_dtmode_unix; + } + + obj2 = cfg_tuple_get(obj, "path"); + if (obj2 == NULL) { + CHECKM(ISC_R_FAILURE, "dnstap-output path not found"); + } + + dpath = cfg_obj_asstring(obj2); + + obj2 = cfg_tuple_get(obj, "size"); + if (obj2 != NULL && cfg_obj_isuint64(obj2)) { + max_size = cfg_obj_asuint64(obj2); + if (max_size > SIZE_MAX) { + cfg_obj_log(obj2, named_g_lctx, ISC_LOG_WARNING, + "'dnstap-output size " + "%" PRIu64 "' " + "is too large for this " + "system; reducing to %lu", + max_size, (unsigned long)SIZE_MAX); + max_size = SIZE_MAX; + } + } + + obj2 = cfg_tuple_get(obj, "versions"); + if (obj2 != NULL && cfg_obj_isuint32(obj2)) { + rolls = cfg_obj_asuint32(obj2); + } else { + rolls = ISC_LOG_ROLLINFINITE; + } + + obj2 = cfg_tuple_get(obj, "suffix"); + if (obj2 != NULL && cfg_obj_isstring(obj2) && + strcasecmp(cfg_obj_asstring(obj2), "timestamp") == 0) + { + suffix = isc_log_rollsuffix_timestamp; + } + + fopt = fstrm_iothr_options_init(); + /* + * Both network threads and worker threads may log dnstap data. + */ + fstrm_iothr_options_set_num_input_queues(fopt, + 2 * named_g_cpus); + fstrm_iothr_options_set_queue_model( + fopt, FSTRM_IOTHR_QUEUE_MODEL_MPSC); + + obj = NULL; + result = named_config_get(maps, "fstrm-set-buffer-hint", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_buffer_hint(fopt, i); + } + + obj = NULL; + result = named_config_get(maps, "fstrm-set-flush-timeout", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_flush_timeout(fopt, i); + } + + obj = NULL; + result = named_config_get(maps, "fstrm-set-input-queue-size", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_input_queue_size(fopt, i); + } + + obj = NULL; + result = named_config_get( + maps, "fstrm-set-output-notify-threshold", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_queue_notify_threshold(fopt, i); + } + + obj = NULL; + result = named_config_get(maps, "fstrm-set-output-queue-model", + &obj); + if (result == ISC_R_SUCCESS) { + if (strcasecmp(cfg_obj_asstring(obj), "spsc") == 0) { + i = FSTRM_IOTHR_QUEUE_MODEL_SPSC; + } else { + i = FSTRM_IOTHR_QUEUE_MODEL_MPSC; + } + fstrm_iothr_options_set_queue_model(fopt, i); + } + + obj = NULL; + result = named_config_get(maps, "fstrm-set-output-queue-size", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_output_queue_size(fopt, i); + } + + obj = NULL; + result = named_config_get(maps, "fstrm-set-reopen-interval", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asduration(obj); + fstrm_iothr_options_set_reopen_interval(fopt, i); + } + + CHECKM(dns_dt_create(named_g_mctx, dmode, dpath, &fopt, + named_g_server->task, + &named_g_server->dtenv), + "unable to create dnstap environment"); + + CHECKM(dns_dt_setupfile(named_g_server->dtenv, max_size, rolls, + suffix), + "unable to set up dnstap logfile"); + } + + if (named_g_server->dtenv == NULL) { + return (ISC_R_SUCCESS); + } + + obj = NULL; + result = named_config_get(maps, "dnstap-version", &obj); + if (result != ISC_R_SUCCESS) { + /* not specified; use the product and version */ + dns_dt_setversion(named_g_server->dtenv, PACKAGE_STRING); + } else if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + /* Quoted string */ + dns_dt_setversion(named_g_server->dtenv, cfg_obj_asstring(obj)); + } + + obj = NULL; + result = named_config_get(maps, "dnstap-identity", &obj); + if (result == ISC_R_SUCCESS && cfg_obj_isboolean(obj)) { + /* "hostname" is interpreted as boolean true */ + char buf[256]; + if (gethostname(buf, sizeof(buf)) == 0) { + dns_dt_setidentity(named_g_server->dtenv, buf); + } + } else if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + /* Quoted string */ + dns_dt_setidentity(named_g_server->dtenv, + cfg_obj_asstring(obj)); + } + + dns_dt_attach(named_g_server->dtenv, &view->dtenv); + view->dttypes = dttypes; + + result = ISC_R_SUCCESS; + +cleanup: + if (fopt != NULL) { + fstrm_iothr_options_destroy(&fopt); + } + + return (result); +} +#endif /* HAVE_DNSTAP */ + +static isc_result_t +create_mapped_acl(void) { + isc_result_t result; + dns_acl_t *acl = NULL; + struct in6_addr in6 = IN6ADDR_V4MAPPED_INIT; + isc_netaddr_t addr; + + isc_netaddr_fromin6(&addr, &in6); + + result = dns_acl_create(named_g_mctx, 1, &acl); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_iptable_addprefix(acl->iptable, &addr, 96, true); + if (result == ISC_R_SUCCESS) { + dns_acl_attach(acl, &named_g_mapped); + } + dns_acl_detach(&acl); + return (result); +} + +/*% + * A callback for the cfg_pluginlist_foreach() call in configure_view() below. + * If registering any plugin fails, registering subsequent ones is not + * attempted. + */ +static isc_result_t +register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj, + const char *plugin_path, const char *parameters, + void *callback_data) { + dns_view_t *view = callback_data; + char full_path[PATH_MAX]; + isc_result_t result; + + result = ns_plugin_expandpath(plugin_path, full_path, + sizeof(full_path)); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "%s: plugin configuration failed: " + "unable to get full plugin path: %s", + plugin_path, isc_result_totext(result)); + return (result); + } + + result = ns_plugin_register(full_path, parameters, config, + cfg_obj_file(obj), cfg_obj_line(obj), + named_g_mctx, named_g_lctx, + named_g_aclconfctx, view); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "%s: plugin configuration failed: %s", full_path, + isc_result_totext(result)); + } + + return (result); +} + +/* + * Determine if a minimal-sized cache can be used for a given view, according + * to 'maps' (implicit defaults, global options, view options) and 'optionmaps' + * (global options, view options). This is only allowed for views which have + * recursion disabled and do not have "max-cache-size" set explicitly. Using + * minimal-sized caches prevents a situation in which all explicitly configured + * and built-in views inherit the default "max-cache-size 90%;" setting, which + * could lead to memory exhaustion with multiple views configured. + */ +static bool +minimal_cache_allowed(const cfg_obj_t *maps[4], + const cfg_obj_t *optionmaps[3]) { + const cfg_obj_t *obj; + + /* + * Do not use a minimal-sized cache for a view with recursion enabled. + */ + obj = NULL; + (void)named_config_get(maps, "recursion", &obj); + INSIST(obj != NULL); + if (cfg_obj_asboolean(obj)) { + return (false); + } + + /* + * Do not use a minimal-sized cache if a specific size was requested. + */ + obj = NULL; + (void)named_config_get(optionmaps, "max-cache-size", &obj); + if (obj != NULL) { + return (false); + } + + return (true); +} + +static const char *const response_synonyms[] = { "response", NULL }; + +static const dns_name_t * +algorithm_name(unsigned int alg) { + switch (alg) { + case DST_ALG_HMACMD5: + return (dns_tsig_hmacmd5_name); + case DST_ALG_HMACSHA1: + return (dns_tsig_hmacsha1_name); + case DST_ALG_HMACSHA224: + return (dns_tsig_hmacsha224_name); + case DST_ALG_HMACSHA256: + return (dns_tsig_hmacsha256_name); + case DST_ALG_HMACSHA384: + return (dns_tsig_hmacsha384_name); + case DST_ALG_HMACSHA512: + return (dns_tsig_hmacsha512_name); + case DST_ALG_GSSAPI: + return (dns_tsig_gssapi_name); + default: + UNREACHABLE(); + } +} + +/* + * Configure 'view' according to 'vconfig', taking defaults from + * 'config' where values are missing in 'vconfig'. + * + * When configuring the default view, 'vconfig' will be NULL and the + * global defaults in 'config' used exclusively. + */ +static isc_result_t +configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, + cfg_obj_t *vconfig, named_cachelist_t *cachelist, + dns_kasplist_t *kasplist, const cfg_obj_t *bindkeys, + isc_mem_t *mctx, cfg_aclconfctx_t *actx, bool need_hints) { + const cfg_obj_t *maps[4]; + const cfg_obj_t *cfgmaps[3]; + const cfg_obj_t *optionmaps[3]; + const cfg_obj_t *options = NULL; + const cfg_obj_t *voptions = NULL; + const cfg_obj_t *forwardtype; + const cfg_obj_t *forwarders; + const cfg_obj_t *alternates; + const cfg_obj_t *zonelist; + const cfg_obj_t *dlzlist; + const cfg_obj_t *dlz; + const cfg_obj_t *prefetch_trigger; + const cfg_obj_t *prefetch_eligible; + unsigned int dlzargc; + char **dlzargv; + const cfg_obj_t *dyndb_list, *plugin_list; + const cfg_obj_t *disabled; + const cfg_obj_t *obj, *obj2; + const cfg_listelt_t *element = NULL; + const cfg_listelt_t *zone_element_latest = NULL; + in_port_t port; + dns_cache_t *cache = NULL; + isc_result_t result; + size_t max_cache_size; + uint32_t max_cache_size_percent = 0; + size_t max_adb_size; + uint32_t lame_ttl, fail_ttl; + uint32_t max_stale_ttl = 0; + uint32_t stale_refresh_time = 0; + dns_tsig_keyring_t *ring = NULL; + dns_transport_list_t *transports = NULL; + dns_view_t *pview = NULL; /* Production view */ + isc_mem_t *cmctx = NULL, *hmctx = NULL; + dns_dispatch_t *dispatch4 = NULL; + dns_dispatch_t *dispatch6 = NULL; + bool rpz_configured = false; + bool catz_configured = false; + bool shared_cache = false; + int i = 0, j = 0, k = 0; + const char *str; + const char *cachename = NULL; + dns_order_t *order = NULL; + uint32_t udpsize; + uint32_t maxbits; + unsigned int resopts = 0; + dns_zone_t *zone = NULL; + uint32_t max_clients_per_query; + bool empty_zones_enable; + const cfg_obj_t *disablelist = NULL; + isc_stats_t *resstats = NULL; + dns_stats_t *resquerystats = NULL; + bool auto_root = false; + named_cache_t *nsc; + bool zero_no_soattl; + dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL; + unsigned int query_timeout, ndisp; + bool old_rpz_ok = false; + dns_dyndbctx_t *dctx = NULL; + unsigned int resolver_param; + dns_ntatable_t *ntatable = NULL; + const char *qminmode = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (config != NULL) { + (void)cfg_map_get(config, "options", &options); + } + + /* + * maps: view options, options, defaults + * cfgmaps: view options, config + * optionmaps: view options, options + */ + if (vconfig != NULL) { + voptions = cfg_tuple_get(vconfig, "options"); + maps[i++] = voptions; + optionmaps[j++] = voptions; + cfgmaps[k++] = voptions; + } + if (options != NULL) { + maps[i++] = options; + optionmaps[j++] = options; + } + + maps[i++] = named_g_defaults; + maps[i] = NULL; + optionmaps[j] = NULL; + if (config != NULL) { + cfgmaps[k++] = config; + } + cfgmaps[k] = NULL; + + /* + * Set the view's port number for outgoing queries. + */ + CHECKM(named_config_getport(config, "port", &port), "port"); + dns_view_setdstport(view, port); + + /* + * Make the list of response policy zone names for a view that + * is used for real lookups and so cares about hints. + */ + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + named_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) + { + CHECK(configure_rpz(view, NULL, maps, obj, &old_rpz_ok)); + rpz_configured = true; + } + + obj = NULL; + if (view->rdclass != dns_rdataclass_in && need_hints && + named_config_get(maps, "catalog-zones", &obj) == ISC_R_SUCCESS) + { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "'catalog-zones' option is only supported " + "for views with class IN"); + } + + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + named_config_get(maps, "catalog-zones", &obj) == ISC_R_SUCCESS) + { + CHECK(configure_catz(view, NULL, config, obj)); + catz_configured = true; + } + + /* + * Configure the zones. + */ + zonelist = NULL; + if (voptions != NULL) { + (void)cfg_map_get(voptions, "zone", &zonelist); + } else { + (void)cfg_map_get(config, "zone", &zonelist); + } + + /* + * Load zone configuration + */ + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(element); + CHECK(configure_zone(config, zconfig, vconfig, mctx, view, + viewlist, kasplist, actx, false, + old_rpz_ok, false)); + zone_element_latest = element; + } + + /* + * Check that a primary or secondary zone was found for each + * zone named in the response policy statement, unless we are + * using RPZ service interface. + */ + if (view->rpzs != NULL && !view->rpzs->p.dnsrps_enabled) { + dns_rpz_num_t n; + + for (n = 0; n < view->rpzs->p.num_zones; ++n) { + if ((view->rpzs->defined & DNS_RPZ_ZBIT(n)) == 0) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&view->rpzs->zones[n]->origin, + namebuf, sizeof(namebuf)); + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + DNS_RPZ_ERROR_LEVEL, + "rpz '%s' is not a primary or a " + "secondary zone", + namebuf); + result = ISC_R_NOTFOUND; + goto cleanup; + } + } + } + + /* + * If we're allowing added zones, then load zone configuration + * from the newzone file for zones that were added during previous + * runs. + */ + CHECK(configure_newzones(view, config, vconfig, mctx, actx)); + + /* + * Create Dynamically Loadable Zone driver. + */ + dlzlist = NULL; + if (voptions != NULL) { + (void)cfg_map_get(voptions, "dlz", &dlzlist); + } else { + (void)cfg_map_get(config, "dlz", &dlzlist); + } + + for (element = cfg_list_first(dlzlist); element != NULL; + element = cfg_list_next(element)) + { + dlz = cfg_listelt_value(element); + + obj = NULL; + (void)cfg_map_get(dlz, "database", &obj); + if (obj != NULL) { + dns_dlzdb_t *dlzdb = NULL; + const cfg_obj_t *name, *search = NULL; + char *s = isc_mem_strdup(mctx, cfg_obj_asstring(obj)); + + if (s == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = isc_commandline_strtoargv(mctx, s, &dlzargc, + &dlzargv, 0); + if (result != ISC_R_SUCCESS) { + isc_mem_free(mctx, s); + goto cleanup; + } + + name = cfg_map_getname(dlz); + result = dns_dlzcreate(mctx, cfg_obj_asstring(name), + dlzargv[0], dlzargc, dlzargv, + &dlzdb); + isc_mem_free(mctx, s); + isc_mem_put(mctx, dlzargv, dlzargc * sizeof(*dlzargv)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * If the DLZ backend supports configuration, + * and is searchable, then call its configure + * method now. If not searchable, we'll take + * care of it when we process the zone statement. + */ + (void)cfg_map_get(dlz, "search", &search); + if (search == NULL || cfg_obj_asboolean(search)) { + dlzdb->search = true; + result = dns_dlzconfigure( + view, dlzdb, dlzconfigure_callback); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(view->dlz_searched, dlzdb, + link); + } else { + dlzdb->search = false; + ISC_LIST_APPEND(view->dlz_unsearched, dlzdb, + link); + } + } + } + + /* + * Obtain configuration parameters that affect the decision of whether + * we can reuse/share an existing cache. + */ + obj = NULL; + result = named_config_get(maps, "max-cache-size", &obj); + INSIST(result == ISC_R_SUCCESS); + /* + * If "-T maxcachesize=..." is in effect, it overrides any other + * "max-cache-size" setting found in configuration, either implicit or + * explicit. For simplicity, the value passed to that command line + * option is always treated as the number of bytes to set + * "max-cache-size" to. + */ + if (named_g_maxcachesize != 0) { + max_cache_size = named_g_maxcachesize; + } else if (minimal_cache_allowed(maps, optionmaps)) { + /* + * dns_cache_setcachesize() will adjust this to the smallest + * allowed value. + */ + max_cache_size = 1; + } else if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + INSIST(strcasecmp(str, "unlimited") == 0); + max_cache_size = 0; + } else if (cfg_obj_ispercentage(obj)) { + max_cache_size = SIZE_AS_PERCENT; + max_cache_size_percent = cfg_obj_aspercentage(obj); + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > SIZE_MAX) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "'max-cache-size " + "%" PRIu64 "' " + "is too large for this " + "system; reducing to %lu", + value, (unsigned long)SIZE_MAX); + value = SIZE_MAX; + } + max_cache_size = (size_t)value; + } + + if (max_cache_size == SIZE_AS_PERCENT) { + uint64_t totalphys = isc_meminfo_totalphys(); + + max_cache_size = + (size_t)(totalphys * max_cache_size_percent / 100); + if (totalphys == 0) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "Unable to determine amount of physical " + "memory, setting 'max-cache-size' to " + "unlimited"); + } else { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_INFO, + "'max-cache-size %d%%' " + "- setting to %" PRIu64 "MB " + "(out of %" PRIu64 "MB)", + max_cache_size_percent, + (uint64_t)(max_cache_size / (1024 * 1024)), + totalphys / (1024 * 1024)); + } + } + + /* Check-names. */ + obj = NULL; + result = named_checknames_get(maps, response_synonyms, &obj); + INSIST(result == ISC_R_SUCCESS); + + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "fail") == 0) { + resopts |= DNS_RESOLVER_CHECKNAMES | + DNS_RESOLVER_CHECKNAMESFAIL; + view->checknames = true; + } else if (strcasecmp(str, "warn") == 0) { + resopts |= DNS_RESOLVER_CHECKNAMES; + view->checknames = false; + } else if (strcasecmp(str, "ignore") == 0) { + view->checknames = false; + } else { + UNREACHABLE(); + } + + obj = NULL; + result = named_config_get(maps, "zero-no-soa-ttl-cache", &obj); + INSIST(result == ISC_R_SUCCESS); + zero_no_soattl = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "dns64", &obj); + if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") && + strcmp(view->name, "_meta")) + { + isc_netaddr_t na, suffix, *sp; + unsigned int prefixlen; + const char *server, *contact; + const cfg_obj_t *myobj; + + myobj = NULL; + result = named_config_get(maps, "dns64-server", &myobj); + if (result == ISC_R_SUCCESS) { + server = cfg_obj_asstring(myobj); + } else { + server = NULL; + } + + myobj = NULL; + result = named_config_get(maps, "dns64-contact", &myobj); + if (result == ISC_R_SUCCESS) { + contact = cfg_obj_asstring(myobj); + } else { + contact = NULL; + } + + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *map = cfg_listelt_value(element); + dns_dns64_t *dns64 = NULL; + unsigned int dns64options = 0; + + cfg_obj_asnetprefix(cfg_map_getname(map), &na, + &prefixlen); + + obj = NULL; + (void)cfg_map_get(map, "suffix", &obj); + if (obj != NULL) { + sp = &suffix; + isc_netaddr_fromsockaddr( + sp, cfg_obj_assockaddr(obj)); + } else { + sp = NULL; + } + + clients = mapped = excluded = NULL; + obj = NULL; + (void)cfg_map_get(map, "clients", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + named_g_lctx, actx, + mctx, 0, &clients); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + obj = NULL; + (void)cfg_map_get(map, "mapped", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + named_g_lctx, actx, + mctx, 0, &mapped); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + obj = NULL; + (void)cfg_map_get(map, "exclude", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + named_g_lctx, actx, + mctx, 0, &excluded); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + if (named_g_mapped == NULL) { + result = create_mapped_acl(); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + dns_acl_attach(named_g_mapped, &excluded); + } + + obj = NULL; + (void)cfg_map_get(map, "recursive-only", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) { + dns64options |= DNS_DNS64_RECURSIVE_ONLY; + } + + obj = NULL; + (void)cfg_map_get(map, "break-dnssec", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) { + dns64options |= DNS_DNS64_BREAK_DNSSEC; + } + + result = dns_dns64_create(mctx, &na, prefixlen, sp, + clients, mapped, excluded, + dns64options, &dns64); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + dns_dns64_append(&view->dns64, dns64); + view->dns64cnt++; + result = dns64_reverse(view, mctx, &na, prefixlen, + server, contact); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (clients != NULL) { + dns_acl_detach(&clients); + } + if (mapped != NULL) { + dns_acl_detach(&mapped); + } + if (excluded != NULL) { + dns_acl_detach(&excluded); + } + } + } + + obj = NULL; + result = named_config_get(maps, "dnssec-accept-expired", &obj); + INSIST(result == ISC_R_SUCCESS); + view->acceptexpired = cfg_obj_asboolean(obj); + + obj = NULL; + /* 'optionmaps', not 'maps': don't check named_g_defaults yet */ + (void)named_config_get(optionmaps, "dnssec-validation", &obj); + if (obj == NULL) { + /* + * Default to VALIDATION_DEFAULT as set in config.c. + */ + (void)cfg_map_get(named_g_defaults, "dnssec-validation", &obj); + INSIST(obj != NULL); + } + if (obj != NULL) { + if (cfg_obj_isboolean(obj)) { + view->enablevalidation = cfg_obj_asboolean(obj); + } else { + /* + * If dnssec-validation is set but not boolean, + * then it must be "auto" + */ + view->enablevalidation = true; + auto_root = true; + } + } + + obj = NULL; + result = named_config_get(maps, "max-cache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxcachettl = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "max-ncache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxncachettl = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "min-cache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->mincachettl = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "min-ncache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->minncachettl = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "synth-from-dnssec", &obj); + INSIST(result == ISC_R_SUCCESS); + view->synthfromdnssec = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "stale-cache-enable", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_asboolean(obj)) { + obj = NULL; + result = named_config_get(maps, "max-stale-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + max_stale_ttl = ISC_MAX(cfg_obj_asduration(obj), 1); + } + /* + * If 'stale-cache-enable' is false, max_stale_ttl is set to 0, + * meaning keeping stale RRsets in cache is disabled. + */ + + obj = NULL; + result = named_config_get(maps, "stale-answer-enable", &obj); + INSIST(result == ISC_R_SUCCESS); + view->staleanswersenable = cfg_obj_asboolean(obj); + + result = dns_viewlist_find(&named_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + view->staleanswersok = pview->staleanswersok; + dns_view_detach(&pview); + } else { + view->staleanswersok = dns_stale_answer_conf; + } + + obj = NULL; + result = named_config_get(maps, "stale-answer-client-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isstring(obj)) { + /* + * The only string values available for this option + * are "disabled" and "off". + * We use (uint32_t) -1 to represent disabled since + * a value of zero means that stale data can be used + * to promptly answer the query, while an attempt to + * refresh the RRset will still be made in background. + */ + view->staleanswerclienttimeout = (uint32_t)-1; + } else { + view->staleanswerclienttimeout = cfg_obj_asuint32(obj); + } + + obj = NULL; + result = named_config_get(maps, "stale-refresh-time", &obj); + INSIST(result == ISC_R_SUCCESS); + stale_refresh_time = cfg_obj_asduration(obj); + + /* + * Configure the view's cache. + * + * First, check to see if there are any attach-cache options. If yes, + * attempt to lookup an existing cache at attach it to the view. If + * there is not one, then try to reuse an existing cache if possible; + * otherwise create a new cache. + * + * Note that the ADB is not preserved or shared in either case. + * + * When a matching view is found, the associated statistics are also + * retrieved and reused. + * + * XXX Determining when it is safe to reuse or share a cache is tricky. + * When the view's configuration changes, the cached data may become + * invalid because it reflects our old view of the world. We check + * some of the configuration parameters that could invalidate the cache + * or otherwise make it unshareable, but there are other configuration + * options that should be checked. For example, if a view uses a + * forwarder, changes in the forwarder configuration may invalidate + * the cache. At the moment, it's the administrator's responsibility to + * ensure these configuration options don't invalidate reusing/sharing. + */ + obj = NULL; + result = named_config_get(maps, "attach-cache", &obj); + if (result == ISC_R_SUCCESS) { + cachename = cfg_obj_asstring(obj); + } else { + cachename = view->name; + } + cache = NULL; + nsc = cachelist_find(cachelist, cachename, view->rdclass); + if (nsc != NULL) { + if (!cache_sharable(nsc->primaryview, view, zero_no_soattl, + max_cache_size, max_stale_ttl, + stale_refresh_time)) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "views %s and %s can't share the cache " + "due to configuration parameter mismatch", + nsc->primaryview->name, view->name); + result = ISC_R_FAILURE; + goto cleanup; + } + dns_cache_attach(nsc->cache, &cache); + shared_cache = true; + } else { + if (strcmp(cachename, view->name) == 0) { + result = dns_viewlist_find(&named_g_server->viewlist, + cachename, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + { + goto cleanup; + } + if (pview != NULL) { + if (!cache_reusable(pview, view, + zero_no_soattl)) + { + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + ISC_LOG_DEBUG(1), + "cache cannot be reused " + "for view %s due to " + "configuration parameter " + "mismatch", + view->name); + } else { + INSIST(pview->cache != NULL); + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + ISC_LOG_DEBUG(3), + "reusing existing cache"); + dns_cache_attach(pview->cache, &cache); + } + dns_view_getresstats(pview, &resstats); + dns_view_getresquerystats(pview, + &resquerystats); + dns_view_detach(&pview); + } + } + if (cache == NULL) { + /* + * Create a cache with the desired name. This normally + * equals the view name, but may also be a forward + * reference to a view that share the cache with this + * view but is not yet configured. If it is not the + * view name but not a forward reference either, then it + * is simply a named cache that is not shared. + * + * We use two separate memory contexts for the + * cache, for the main cache memory and the heap + * memory. + */ + isc_mem_create(&cmctx); + isc_mem_setname(cmctx, "cache"); + isc_mem_create(&hmctx); + isc_mem_setname(hmctx, "cache_heap"); + CHECK(dns_cache_create(cmctx, hmctx, named_g_taskmgr, + named_g_timermgr, view->rdclass, + cachename, "rbt", 0, NULL, + &cache)); + isc_mem_detach(&cmctx); + isc_mem_detach(&hmctx); + } + nsc = isc_mem_get(mctx, sizeof(*nsc)); + nsc->cache = NULL; + dns_cache_attach(cache, &nsc->cache); + nsc->primaryview = view; + nsc->needflush = false; + nsc->adbsizeadjusted = false; + nsc->rdclass = view->rdclass; + ISC_LINK_INIT(nsc, link); + ISC_LIST_APPEND(*cachelist, nsc, link); + } + dns_view_setcache(view, cache, shared_cache); + + dns_cache_setcachesize(cache, max_cache_size); + dns_cache_setservestalettl(cache, max_stale_ttl); + dns_cache_setservestalerefresh(cache, stale_refresh_time); + + dns_cache_detach(&cache); + + obj = NULL; + result = named_config_get(maps, "stale-answer-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->staleanswerttl = ISC_MAX(cfg_obj_asduration(obj), 1); + + /* + * Resolver. + */ + CHECK(get_view_querysource_dispatch( + maps, AF_INET, &dispatch4, + (ISC_LIST_PREV(view, link) == NULL))); + CHECK(get_view_querysource_dispatch( + maps, AF_INET6, &dispatch6, + (ISC_LIST_PREV(view, link) == NULL))); + if (dispatch4 == NULL && dispatch6 == NULL) { + UNEXPECTED_ERROR("unable to obtain either an IPv4 or" + " an IPv6 dispatch"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (resstats == NULL) { + CHECK(isc_stats_create(mctx, &resstats, + dns_resstatscounter_max)); + } + dns_view_setresstats(view, resstats); + if (resquerystats == NULL) { + CHECK(dns_rdatatypestats_create(mctx, &resquerystats)); + } + dns_view_setresquerystats(view, resquerystats); + + ndisp = 4 * ISC_MIN(named_g_udpdisp, MAX_UDP_DISPATCH); + CHECK(dns_view_createresolver( + view, named_g_taskmgr, RESOLVER_NTASKS_PERCPU * named_g_cpus, + ndisp, named_g_netmgr, named_g_timermgr, resopts, + named_g_dispatchmgr, dispatch4, dispatch6)); + + /* + * Set the ADB cache size to 1/8th of the max-cache-size or + * MAX_ADB_SIZE_FOR_CACHESHARE when the cache is shared. + */ + max_adb_size = 0; + if (max_cache_size != 0U) { + max_adb_size = max_cache_size / 8; + if (max_adb_size == 0U) { + max_adb_size = 1; /* Force minimum. */ + } + if (view != nsc->primaryview && + max_adb_size > MAX_ADB_SIZE_FOR_CACHESHARE) + { + max_adb_size = MAX_ADB_SIZE_FOR_CACHESHARE; + if (!nsc->adbsizeadjusted) { + dns_adb_setadbsize(nsc->primaryview->adb, + MAX_ADB_SIZE_FOR_CACHESHARE); + nsc->adbsizeadjusted = true; + } + } + } + dns_adb_setadbsize(view->adb, max_adb_size); + + /* + * Set up ADB quotas + */ + { + uint32_t fps, freq; + double low, high, discount; + + obj = NULL; + result = named_config_get(maps, "fetches-per-server", &obj); + INSIST(result == ISC_R_SUCCESS); + obj2 = cfg_tuple_get(obj, "fetches"); + fps = cfg_obj_asuint32(obj2); + obj2 = cfg_tuple_get(obj, "response"); + if (!cfg_obj_isvoid(obj2)) { + const char *resp = cfg_obj_asstring(obj2); + isc_result_t r = DNS_R_SERVFAIL; + + if (strcasecmp(resp, "drop") == 0) { + r = DNS_R_DROP; + } else if (strcasecmp(resp, "fail") == 0) { + r = DNS_R_SERVFAIL; + } else { + UNREACHABLE(); + } + + dns_resolver_setquotaresponse(view->resolver, + dns_quotatype_server, r); + } + + obj = NULL; + result = named_config_get(maps, "fetch-quota-params", &obj); + INSIST(result == ISC_R_SUCCESS); + + obj2 = cfg_tuple_get(obj, "frequency"); + freq = cfg_obj_asuint32(obj2); + + obj2 = cfg_tuple_get(obj, "low"); + low = (double)cfg_obj_asfixedpoint(obj2) / 100.0; + + obj2 = cfg_tuple_get(obj, "high"); + high = (double)cfg_obj_asfixedpoint(obj2) / 100.0; + + obj2 = cfg_tuple_get(obj, "discount"); + discount = (double)cfg_obj_asfixedpoint(obj2) / 100.0; + + dns_adb_setquota(view->adb, fps, freq, low, high, discount); + } + + /* + * Set resolver's lame-ttl. + */ + obj = NULL; + result = named_config_get(maps, "lame-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + lame_ttl = cfg_obj_asduration(obj); + if (lame_ttl > 0) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "disabling lame cache despite lame-ttl > 0 as it " + "may cause performance issues"); + lame_ttl = 0; + } + dns_resolver_setlamettl(view->resolver, lame_ttl); + + /* + * Set the resolver's query timeout. + */ + obj = NULL; + result = named_config_get(maps, "resolver-query-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + query_timeout = cfg_obj_asuint32(obj); + dns_resolver_settimeout(view->resolver, query_timeout); + + /* + * Adjust stale-answer-client-timeout upper bound + * to be resolver-query-timeout - 1s. + * This assignment is safe as dns_resolver_settimeout() + * ensures that resolver->querytimeout value will be in the + * [MINIMUM_QUERY_TIMEOUT, MAXIMUM_QUERY_TIMEOUT] range and + * MINIMUM_QUERY_TIMEOUT is > 1000 (in ms). + */ + if (view->staleanswerclienttimeout != (uint32_t)-1 && + view->staleanswerclienttimeout > + (dns_resolver_gettimeout(view->resolver) - 1000)) + { + view->staleanswerclienttimeout = + dns_resolver_gettimeout(view->resolver) - 1000; + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "stale-answer-client-timeout adjusted to %" PRIu32, + view->staleanswerclienttimeout); + } + + /* Specify whether to use 0-TTL for negative response for SOA query */ + dns_resolver_setzeronosoattl(view->resolver, zero_no_soattl); + + /* + * Set the resolver's EDNS UDP size. + */ + obj = NULL; + result = named_config_get(maps, "edns-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) { + udpsize = 512; + } + if (udpsize > 4096) { + udpsize = 4096; + } + dns_resolver_setudpsize(view->resolver, (uint16_t)udpsize); + + /* + * Set the maximum UDP response size. + */ + obj = NULL; + result = named_config_get(maps, "max-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) { + udpsize = 512; + } + if (udpsize > 4096) { + udpsize = 4096; + } + view->maxudp = udpsize; + + /* + * Set the maximum UDP when a COOKIE is not provided. + */ + obj = NULL; + result = named_config_get(maps, "nocookie-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 128) { + udpsize = 128; + } + if (udpsize > view->maxudp) { + udpsize = view->maxudp; + } + view->nocookieudp = udpsize; + + /* + * Set the maximum rsa exponent bits. + */ + obj = NULL; + result = named_config_get(maps, "max-rsa-exponent-size", &obj); + INSIST(result == ISC_R_SUCCESS); + maxbits = cfg_obj_asuint32(obj); + if (maxbits != 0 && maxbits < 35) { + maxbits = 35; + } + if (maxbits > 4096) { + maxbits = 4096; + } + view->maxbits = maxbits; + + /* + * Set resolver retry parameters. + */ + obj = NULL; + CHECK(named_config_get(maps, "resolver-retry-interval", &obj)); + resolver_param = cfg_obj_asuint32(obj); + if (resolver_param > 0) { + dns_resolver_setretryinterval(view->resolver, resolver_param); + } + + obj = NULL; + CHECK(named_config_get(maps, "resolver-nonbackoff-tries", &obj)); + resolver_param = cfg_obj_asuint32(obj); + if (resolver_param > 0) { + dns_resolver_setnonbackofftries(view->resolver, resolver_param); + } + + /* + * Set supported DNSSEC algorithms. + */ + dns_resolver_reset_algorithms(view->resolver); + disabled = NULL; + (void)named_config_get(maps, "disable-algorithms", &disabled); + if (disabled != NULL) { + for (element = cfg_list_first(disabled); element != NULL; + element = cfg_list_next(element)) + { + CHECK(disable_algorithms(cfg_listelt_value(element), + view->resolver)); + } + } + + /* + * Set supported DS digest types. + */ + dns_resolver_reset_ds_digests(view->resolver); + disabled = NULL; + (void)named_config_get(maps, "disable-ds-digests", &disabled); + if (disabled != NULL) { + for (element = cfg_list_first(disabled); element != NULL; + element = cfg_list_next(element)) + { + CHECK(disable_ds_digests(cfg_listelt_value(element), + view->resolver)); + } + } + + /* + * A global or view "forwarders" option, if present, + * creates an entry for "." in the forwarding table. + */ + forwardtype = NULL; + forwarders = NULL; + (void)named_config_get(maps, "forward", &forwardtype); + (void)named_config_get(maps, "forwarders", &forwarders); + if (forwarders != NULL) { + CHECK(configure_forward(config, view, dns_rootname, forwarders, + forwardtype)); + } + + /* + * Dual Stack Servers. + */ + alternates = NULL; + (void)named_config_get(maps, "dual-stack-servers", &alternates); + if (alternates != NULL) { + CHECK(configure_alternates(config, view, alternates)); + } + + /* + * We have default hints for class IN if we need them. + */ + if (view->rdclass == dns_rdataclass_in && view->hints == NULL) { + dns_view_sethints(view, named_g_server->in_roothints); + } + + /* + * If we still have no hints, this is a non-IN view with no + * "hints zone" configured. Issue a warning, except if this + * is a root server. Root servers never need to consult + * their hints, so it's no point requiring users to configure + * them. + */ + if (view->hints == NULL) { + dns_zone_t *rootzone = NULL; + (void)dns_view_findzone(view, dns_rootname, &rootzone); + if (rootzone != NULL) { + dns_zone_detach(&rootzone); + need_hints = false; + } + if (need_hints) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "no root hints for view '%s'", + view->name); + } + } + + /* + * Configure the view's transports (DoT/DoH) + */ + CHECK(named_transports_fromconfig(config, vconfig, view->mctx, + &transports)); + dns_view_settransports(view, transports); + dns_transport_list_detach(&transports); + + /* + * Configure the view's TSIG keys. + */ + CHECK(named_tsigkeyring_fromconfig(config, vconfig, view->mctx, &ring)); + if (named_g_server->sessionkey != NULL) { + dns_tsigkey_t *tsigkey = NULL; + result = dns_tsigkey_createfromkey( + named_g_server->session_keyname, + algorithm_name(named_g_server->session_keyalg), + named_g_server->sessionkey, false, NULL, 0, 0, mctx, + NULL, &tsigkey); + if (result == ISC_R_SUCCESS) { + result = dns_tsigkeyring_add( + ring, named_g_server->session_keyname, tsigkey); + dns_tsigkey_detach(&tsigkey); + } + CHECK(result); + } + dns_view_setkeyring(view, ring); + dns_tsigkeyring_detach(&ring); + + /* + * See if we can re-use a dynamic key ring. + */ + result = dns_viewlist_find(&named_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + goto cleanup; + } + if (pview != NULL) { + dns_view_getdynamickeyring(pview, &ring); + if (ring != NULL) { + dns_view_setdynamickeyring(view, ring); + } + dns_tsigkeyring_detach(&ring); + dns_view_detach(&pview); + } else { + dns_view_restorekeyring(view); + } + + /* + * Configure the view's peer list. + */ + { + const cfg_obj_t *peers = NULL; + dns_peerlist_t *newpeers = NULL; + + (void)named_config_get(cfgmaps, "server", &peers); + CHECK(dns_peerlist_new(mctx, &newpeers)); + for (element = cfg_list_first(peers); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *cpeer = cfg_listelt_value(element); + dns_peer_t *peer; + + CHECK(configure_peer(cpeer, mctx, &peer)); + dns_peerlist_addpeer(newpeers, peer); + dns_peer_detach(&peer); + } + dns_peerlist_detach(&view->peers); + view->peers = newpeers; /* Transfer ownership. */ + } + + /* + * Configure the views rrset-order. + */ + { + const cfg_obj_t *rrsetorder = NULL; + + (void)named_config_get(maps, "rrset-order", &rrsetorder); + CHECK(dns_order_create(mctx, &order)); + for (element = cfg_list_first(rrsetorder); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *ent = cfg_listelt_value(element); + + CHECK(configure_order(order, ent)); + } + if (view->order != NULL) { + dns_order_detach(&view->order); + } + dns_order_attach(order, &view->order); + dns_order_detach(&order); + } + /* + * Copy the aclenv object. + */ + dns_aclenv_copy(view->aclenv, ns_interfacemgr_getaclenv( + named_g_server->interfacemgr)); + + /* + * Configure the "match-clients" and "match-destinations" ACL. + * (These are only meaningful at the view level, but 'config' + * must be passed so that named ACLs defined at the global level + * can be retrieved.) + */ + CHECK(configure_view_acl(vconfig, config, NULL, "match-clients", NULL, + actx, named_g_mctx, &view->matchclients)); + CHECK(configure_view_acl(vconfig, config, NULL, "match-destinations", + NULL, actx, named_g_mctx, + &view->matchdestinations)); + + /* + * Configure the "match-recursive-only" option. + */ + obj = NULL; + (void)named_config_get(maps, "match-recursive-only", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) { + view->matchrecursiveonly = true; + } else { + view->matchrecursiveonly = false; + } + + /* + * Configure other configurable data. + */ + obj = NULL; + result = named_config_get(maps, "recursion", &obj); + INSIST(result == ISC_R_SUCCESS); + view->recursion = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "qname-minimization", &obj); + INSIST(result == ISC_R_SUCCESS); + qminmode = cfg_obj_asstring(obj); + INSIST(qminmode != NULL); + if (!strcmp(qminmode, "strict")) { + view->qminimization = true; + view->qmin_strict = true; + } else if (!strcmp(qminmode, "relaxed")) { + view->qminimization = true; + view->qmin_strict = false; + } else { /* "disabled" or "off" */ + view->qminimization = false; + view->qmin_strict = false; + } + + obj = NULL; + result = named_config_get(maps, "auth-nxdomain", &obj); + INSIST(result == ISC_R_SUCCESS); + view->auth_nxdomain = cfg_obj_asboolean(obj); + + /* deprecated */ + obj = NULL; + result = named_config_get(maps, "glue-cache", &obj); + INSIST(result == ISC_R_SUCCESS); + view->use_glue_cache = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "minimal-any", &obj); + INSIST(result == ISC_R_SUCCESS); + view->minimal_any = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "minimal-responses", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) { + view->minimalresponses = dns_minimal_yes; + } else { + view->minimalresponses = dns_minimal_no; + } + } else { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "no-auth") == 0) { + view->minimalresponses = dns_minimal_noauth; + } else if (strcasecmp(str, "no-auth-recursive") == 0) { + view->minimalresponses = dns_minimal_noauthrec; + } else { + UNREACHABLE(); + } + } + + obj = NULL; + result = named_config_get(maps, "transfer-format", &obj); + INSIST(result == ISC_R_SUCCESS); + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "many-answers") == 0) { + view->transfer_format = dns_many_answers; + } else if (strcasecmp(str, "one-answer") == 0) { + view->transfer_format = dns_one_answer; + } else { + UNREACHABLE(); + } + + obj = NULL; + result = named_config_get(maps, "trust-anchor-telemetry", &obj); + INSIST(result == ISC_R_SUCCESS); + view->trust_anchor_telemetry = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "root-key-sentinel", &obj); + INSIST(result == ISC_R_SUCCESS); + view->root_key_sentinel = cfg_obj_asboolean(obj); + + /* + * Set the "allow-query", "allow-query-cache", "allow-recursion", + * "allow-recursion-on" and "allow-query-cache-on" ACLs if + * configured in named.conf, but NOT from the global defaults. + * This is done by leaving the third argument to configure_view_acl() + * NULL. + * + * We ignore the global defaults here because these ACLs + * can inherit from each other. If any are still unset after + * applying the inheritance rules, we'll look up the defaults at + * that time. + */ + + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, "allow-query", NULL, + actx, named_g_mctx, &view->queryacl)); + + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, "allow-query-cache", + NULL, actx, named_g_mctx, &view->cacheacl)); + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, "allow-query-cache-on", + NULL, actx, named_g_mctx, &view->cacheonacl)); + + if (strcmp(view->name, "_bind") != 0 && + view->rdclass != dns_rdataclass_chaos) + { + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-recursion", NULL, actx, + named_g_mctx, &view->recursionacl)); + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-recursion-on", NULL, actx, + named_g_mctx, &view->recursiononacl)); + } + + if (view->recursion) { + /* + * "allow-query-cache" inherits from "allow-recursion" if set, + * otherwise from "allow-query" if set. + */ + if (view->cacheacl == NULL) { + if (view->recursionacl != NULL) { + dns_acl_attach(view->recursionacl, + &view->cacheacl); + } else if (view->queryacl != NULL) { + dns_acl_attach(view->queryacl, &view->cacheacl); + } + } + + /* + * "allow-recursion" inherits from "allow-query-cache" if set, + * otherwise from "allow-query" if set. + */ + if (view->recursionacl == NULL) { + if (view->cacheacl != NULL) { + dns_acl_attach(view->cacheacl, + &view->recursionacl); + } else if (view->queryacl != NULL) { + dns_acl_attach(view->queryacl, + &view->recursionacl); + } + } + + /* + * "allow-query-cache-on" inherits from "allow-recursion-on" + * if set. + */ + if (view->cacheonacl == NULL) { + if (view->recursiononacl != NULL) { + dns_acl_attach(view->recursiononacl, + &view->cacheonacl); + } + } + + /* + * "allow-recursion-on" inherits from "allow-query-cache-on" + * if set. + */ + if (view->recursiononacl == NULL) { + if (view->cacheonacl != NULL) { + dns_acl_attach(view->cacheonacl, + &view->recursiononacl); + } + } + + /* + * If any are still unset at this point, we now get default + * values for from the global config. + */ + + if (view->recursionacl == NULL) { + /* global default only */ + CHECK(configure_view_acl( + NULL, NULL, named_g_config, "allow-recursion", + NULL, actx, named_g_mctx, &view->recursionacl)); + } + if (view->recursiononacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, named_g_config, + "allow-recursion-on", NULL, + actx, named_g_mctx, + &view->recursiononacl)); + } + if (view->cacheacl == NULL) { + /* global default only */ + CHECK(configure_view_acl( + NULL, NULL, named_g_config, "allow-query-cache", + NULL, actx, named_g_mctx, &view->cacheacl)); + } + if (view->cacheonacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, named_g_config, + "allow-query-cache-on", NULL, + actx, named_g_mctx, + &view->cacheonacl)); + } + } else { + /* + * We're not recursive; if the query-cache ACLs haven't + * been set at the options/view level, set them to none. + */ + if (view->cacheacl == NULL) { + CHECK(dns_acl_none(mctx, &view->cacheacl)); + } + if (view->cacheonacl == NULL) { + CHECK(dns_acl_none(mctx, &view->cacheonacl)); + } + } + + /* + * Finished setting recursion and query-cache ACLs, so now we + * can get the allow-query default if it wasn't set in named.conf + */ + if (view->queryacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, named_g_config, + "allow-query", NULL, actx, + named_g_mctx, &view->queryacl)); + } + + /* + * Ignore case when compressing responses to the specified + * clients. This causes case not always to be preserved, + * and is needed by some broken clients. + */ + CHECK(configure_view_acl(vconfig, config, named_g_config, + "no-case-compress", NULL, actx, named_g_mctx, + &view->nocasecompress)); + + /* + * Disable name compression completely, this is a tradeoff + * between CPU and network usage. + */ + obj = NULL; + result = named_config_get(maps, "message-compression", &obj); + INSIST(result == ISC_R_SUCCESS); + view->msgcompression = cfg_obj_asboolean(obj); + + /* + * Filter setting on addresses in the answer section. + */ + CHECK(configure_view_acl(vconfig, config, named_g_config, + "deny-answer-addresses", "acl", actx, + named_g_mctx, &view->denyansweracl)); + CHECK(configure_view_nametable(vconfig, config, "deny-answer-addresses", + "except-from", named_g_mctx, + &view->answeracl_exclude)); + + /* + * Filter setting on names (CNAME/DNAME targets) in the answer section. + */ + CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases", + "name", named_g_mctx, + &view->denyanswernames)); + CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases", + "except-from", named_g_mctx, + &view->answernames_exclude)); + + /* + * Configure sortlist, if set + */ + CHECK(configure_view_sortlist(vconfig, config, actx, named_g_mctx, + &view->sortlist)); + + /* + * Configure default allow-update and allow-update-forwarding ACLs, + * so they can be inherited by zones. (XXX: These are not + * read from the options/view level here. However, they may be + * read from there in zoneconf.c:configure_zone_acl() later.) + */ + if (view->updateacl == NULL) { + CHECK(configure_view_acl(NULL, NULL, named_g_config, + "allow-update", NULL, actx, + named_g_mctx, &view->updateacl)); + } + if (view->upfwdacl == NULL) { + CHECK(configure_view_acl(NULL, NULL, named_g_config, + "allow-update-forwarding", NULL, actx, + named_g_mctx, &view->upfwdacl)); + } + + /* + * Configure default allow-transfer and allow-notify ACLs so they + * can be inherited by zones. + */ + if (view->transferacl == NULL) { + CHECK(configure_view_acl(vconfig, config, named_g_config, + "allow-transfer", NULL, actx, + named_g_mctx, &view->transferacl)); + } + if (view->notifyacl == NULL) { + CHECK(configure_view_acl(vconfig, config, named_g_config, + "allow-notify", NULL, actx, + named_g_mctx, &view->notifyacl)); + } + + obj = NULL; + result = named_config_get(maps, "provide-ixfr", &obj); + INSIST(result == ISC_R_SUCCESS); + view->provideixfr = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "request-nsid", &obj); + INSIST(result == ISC_R_SUCCESS); + view->requestnsid = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "send-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + view->sendcookie = cfg_obj_asboolean(obj); + + obj = NULL; + if (view->pad_acl != NULL) { + dns_acl_detach(&view->pad_acl); + } + result = named_config_get(optionmaps, "response-padding", &obj); + if (result == ISC_R_SUCCESS) { + const cfg_obj_t *padobj = cfg_tuple_get(obj, "block-size"); + const cfg_obj_t *aclobj = cfg_tuple_get(obj, "acl"); + uint32_t padding = cfg_obj_asuint32(padobj); + + if (padding > 512U) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "response-padding block-size cannot " + "exceed 512: lowering"); + padding = 512U; + } + view->padding = (uint16_t)padding; + CHECK(cfg_acl_fromconfig(aclobj, config, named_g_lctx, actx, + named_g_mctx, 0, &view->pad_acl)); + } + + obj = NULL; + result = named_config_get(maps, "require-server-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + view->requireservercookie = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "v6-bias", &obj); + INSIST(result == ISC_R_SUCCESS); + view->v6bias = cfg_obj_asuint32(obj) * 1000; + + obj = NULL; + result = named_config_get(maps, "max-clients-per-query", &obj); + INSIST(result == ISC_R_SUCCESS); + max_clients_per_query = cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "clients-per-query", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj), + max_clients_per_query); + + obj = NULL; + result = named_config_get(maps, "max-recursion-depth", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setmaxdepth(view->resolver, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "max-recursion-queries", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "fetches-per-zone", &obj); + INSIST(result == ISC_R_SUCCESS); + obj2 = cfg_tuple_get(obj, "fetches"); + dns_resolver_setfetchesperzone(view->resolver, cfg_obj_asuint32(obj2)); + obj2 = cfg_tuple_get(obj, "response"); + if (!cfg_obj_isvoid(obj2)) { + const char *resp = cfg_obj_asstring(obj2); + isc_result_t r = DNS_R_SERVFAIL; + + if (strcasecmp(resp, "drop") == 0) { + r = DNS_R_DROP; + } else if (strcasecmp(resp, "fail") == 0) { + r = DNS_R_SERVFAIL; + } else { + UNREACHABLE(); + } + + dns_resolver_setquotaresponse(view->resolver, + dns_quotatype_zone, r); + } + + obj = NULL; + result = named_config_get(maps, "prefetch", &obj); + INSIST(result == ISC_R_SUCCESS); + prefetch_trigger = cfg_tuple_get(obj, "trigger"); + view->prefetch_trigger = cfg_obj_asuint32(prefetch_trigger); + if (view->prefetch_trigger > 10) { + view->prefetch_trigger = 10; + } + prefetch_eligible = cfg_tuple_get(obj, "eligible"); + if (cfg_obj_isvoid(prefetch_eligible)) { + int m; + for (m = 1; maps[m] != NULL; m++) { + obj = NULL; + result = named_config_get(&maps[m], "prefetch", &obj); + INSIST(result == ISC_R_SUCCESS); + prefetch_eligible = cfg_tuple_get(obj, "eligible"); + if (cfg_obj_isuint32(prefetch_eligible)) { + break; + } + } + INSIST(cfg_obj_isuint32(prefetch_eligible)); + } + view->prefetch_eligible = cfg_obj_asuint32(prefetch_eligible); + if (view->prefetch_eligible < view->prefetch_trigger + 6) { + view->prefetch_eligible = view->prefetch_trigger + 6; + } + + /* + * For now, there is only one kind of trusted keys, the + * "security roots". + */ + CHECK(configure_view_dnsseckeys(view, vconfig, config, bindkeys, + auto_root, mctx)); + dns_resolver_resetmustbesecure(view->resolver); + obj = NULL; + result = named_config_get(maps, "dnssec-must-be-secure", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(mustbesecure(obj, view->resolver)); + } + + obj = NULL; + result = named_config_get(maps, "nta-recheck", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_recheck = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "nta-lifetime", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_lifetime = cfg_obj_asduration(obj); + + obj = NULL; + result = named_config_get(maps, "preferred-glue", &obj); + if (result == ISC_R_SUCCESS) { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "a") == 0) { + view->preferred_glue = dns_rdatatype_a; + } else if (strcasecmp(str, "aaaa") == 0) { + view->preferred_glue = dns_rdatatype_aaaa; + } else { + view->preferred_glue = 0; + } + } else { + view->preferred_glue = 0; + } + + obj = NULL; + result = named_config_get(maps, "root-delegation-only", &obj); + if (result == ISC_R_SUCCESS) { + dns_view_setrootdelonly(view, true); + } + if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + const cfg_obj_t *exclude; + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + exclude = cfg_listelt_value(element); + CHECK(dns_name_fromstring( + name, cfg_obj_asstring(exclude), 0, NULL)); + dns_view_excludedelegationonly(view, name); + } + } else { + dns_view_setrootdelonly(view, false); + } + + /* + * Load DynDB modules. + */ + dyndb_list = NULL; + if (voptions != NULL) { + (void)cfg_map_get(voptions, "dyndb", &dyndb_list); + } else { + (void)cfg_map_get(config, "dyndb", &dyndb_list); + } + + for (element = cfg_list_first(dyndb_list); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *dyndb = cfg_listelt_value(element); + + if (dctx == NULL) { + const void *hashinit = isc_hash_get_initializer(); + CHECK(dns_dyndb_createctx(mctx, hashinit, named_g_lctx, + view, named_g_server->zonemgr, + named_g_server->task, + named_g_timermgr, &dctx)); + } + + CHECK(configure_dyndb(dyndb, mctx, dctx)); + } + + /* + * Load plugins. + */ + plugin_list = NULL; + if (voptions != NULL) { + (void)cfg_map_get(voptions, "plugin", &plugin_list); + } else { + (void)cfg_map_get(config, "plugin", &plugin_list); + } + + if (plugin_list != NULL) { + INSIST(view->hooktable == NULL); + CHECK(ns_hooktable_create(view->mctx, + (ns_hooktable_t **)&view->hooktable)); + view->hooktable_free = ns_hooktable_free; + + ns_plugins_create(view->mctx, (ns_plugins_t **)&view->plugins); + view->plugins_free = ns_plugins_free; + + CHECK(cfg_pluginlist_foreach(config, plugin_list, named_g_lctx, + register_one_plugin, view)); + } + + /* + * Setup automatic empty zones. If recursion is off then + * they are disabled by default. + */ + obj = NULL; + (void)named_config_get(maps, "empty-zones-enable", &obj); + (void)named_config_get(maps, "disable-empty-zone", &disablelist); + if (obj == NULL && disablelist == NULL && + view->rdclass == dns_rdataclass_in) + { + empty_zones_enable = view->recursion; + } else if (view->rdclass == dns_rdataclass_in) { + if (obj != NULL) { + empty_zones_enable = cfg_obj_asboolean(obj); + } else { + empty_zones_enable = view->recursion; + } + } else { + empty_zones_enable = false; + } + + if (empty_zones_enable) { + const char *empty; + int empty_zone = 0; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t buffer; + char server[DNS_NAME_FORMATSIZE + 1]; + char contact[DNS_NAME_FORMATSIZE + 1]; + const char *empty_dbtype[4] = { "_builtin", "empty", NULL, + NULL }; + int empty_dbtypec = 4; + dns_zonestat_level_t statlevel = dns_zonestat_none; + + name = dns_fixedname_initname(&fixed); + + obj = NULL; + result = named_config_get(maps, "empty-server", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), + 0, NULL)); + isc_buffer_init(&buffer, server, sizeof(server) - 1); + CHECK(dns_name_totext(name, false, &buffer)); + server[isc_buffer_usedlength(&buffer)] = 0; + empty_dbtype[2] = server; + } else { + empty_dbtype[2] = "@"; + } + + obj = NULL; + result = named_config_get(maps, "empty-contact", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), + 0, NULL)); + isc_buffer_init(&buffer, contact, sizeof(contact) - 1); + CHECK(dns_name_totext(name, false, &buffer)); + contact[isc_buffer_usedlength(&buffer)] = 0; + empty_dbtype[3] = contact; + } else { + empty_dbtype[3] = "."; + } + + obj = NULL; + result = named_config_get(maps, "zone-statistics", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) { + statlevel = dns_zonestat_full; + } else { + statlevel = dns_zonestat_none; + } + } else { + const char *levelstr = cfg_obj_asstring(obj); + if (strcasecmp(levelstr, "full") == 0) { + statlevel = dns_zonestat_full; + } else if (strcasecmp(levelstr, "terse") == 0) { + statlevel = dns_zonestat_terse; + } else if (strcasecmp(levelstr, "none") == 0) { + statlevel = dns_zonestat_none; + } else { + UNREACHABLE(); + } + } + + for (empty = empty_zones[empty_zone]; empty != NULL; + empty = empty_zones[++empty_zone]) + { + dns_forwarders_t *dnsforwarders = NULL; + + /* + * Look for zone on drop list. + */ + CHECK(dns_name_fromstring(name, empty, 0, NULL)); + if (disablelist != NULL && + on_disable_list(disablelist, name)) + { + continue; + } + + /* + * This zone already exists. + */ + (void)dns_view_findzone(view, name, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + continue; + } + + /* + * If we would forward this name don't add a + * empty zone for it. + */ + result = dns_fwdtable_find(view->fwdtable, name, NULL, + &dnsforwarders); + if ((result == ISC_R_SUCCESS || + result == DNS_R_PARTIALMATCH) && + dnsforwarders->fwdpolicy == dns_fwdpolicy_only) + { + continue; + } + + /* + * See if we can re-use a existing zone. + */ + result = dns_viewlist_find(&named_g_server->viewlist, + view->name, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + { + goto cleanup; + } + + if (pview != NULL) { + (void)dns_view_findzone(pview, name, &zone); + dns_view_detach(&pview); + } + + CHECK(create_empty_zone(zone, name, view, zonelist, + empty_dbtype, empty_dbtypec, + statlevel)); + if (zone != NULL) { + dns_zone_detach(&zone); + } + } + } + + obj = NULL; + if (view->rdclass == dns_rdataclass_in) { + (void)named_config_get(maps, "ipv4only-enable", &obj); + } + if (view->rdclass == dns_rdataclass_in && (obj != NULL) + ? cfg_obj_asboolean(obj) + : !ISC_LIST_EMPTY(view->dns64)) + { + const char *server, *contact; + dns_fixedname_t fixed; + dns_name_t *name; + struct { + const char *name; + const char *type; + } zones[] = { + { "ipv4only.arpa", "ipv4only" }, + { "170.0.0.192.in-addr.arpa", "ipv4reverse" }, + { "171.0.0.192.in-addr.arpa", "ipv4reverse" }, + }; + size_t ipv4only_zone; + + obj = NULL; + result = named_config_get(maps, "ipv4only-server", &obj); + if (result == ISC_R_SUCCESS) { + server = cfg_obj_asstring(obj); + } else { + server = NULL; + } + + obj = NULL; + result = named_config_get(maps, "ipv4only-contact", &obj); + if (result == ISC_R_SUCCESS) { + contact = cfg_obj_asstring(obj); + } else { + contact = NULL; + } + + name = dns_fixedname_initname(&fixed); + for (ipv4only_zone = 0; ipv4only_zone < ARRAY_SIZE(zones); + ipv4only_zone++) + { + dns_forwarders_t *dnsforwarders = NULL; + + CHECK(dns_name_fromstring( + name, zones[ipv4only_zone].name, 0, NULL)); + + (void)dns_view_findzone(view, name, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + continue; + } + + /* + * If we would forward this name don't add it. + */ + result = dns_fwdtable_find(view->fwdtable, name, NULL, + &dnsforwarders); + if ((result == ISC_R_SUCCESS || + result == DNS_R_PARTIALMATCH) && + dnsforwarders->fwdpolicy == dns_fwdpolicy_only) + { + continue; + } + + /* + * See if we can re-use a existing zone. + */ + result = dns_viewlist_find(&named_g_server->viewlist, + view->name, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + { + goto cleanup; + } + + if (pview != NULL) { + (void)dns_view_findzone(pview, name, &zone); + dns_view_detach(&pview); + } + + CHECK(create_ipv4only_zone(zone, view, name, + zones[ipv4only_zone].type, + mctx, server, contact)); + if (zone != NULL) { + dns_zone_detach(&zone); + } + } + } + + obj = NULL; + result = named_config_get(maps, "rate-limit", &obj); + if (result == ISC_R_SUCCESS) { + result = configure_rrl(view, config, obj); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + /* + * Set the servfail-ttl. + */ + obj = NULL; + result = named_config_get(maps, "servfail-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + fail_ttl = cfg_obj_asduration(obj); + if (fail_ttl > 30) { + fail_ttl = 30; + } + dns_view_setfailttl(view, fail_ttl); + + /* + * Name space to look up redirect information in. + */ + obj = NULL; + result = named_config_get(maps, "nxdomain-redirect", &obj); + if (result == ISC_R_SUCCESS) { + dns_name_t *name = dns_fixedname_name(&view->redirectfixed); + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), 0, + NULL)); + view->redirectzone = name; + } else { + view->redirectzone = NULL; + } + + /* + * Exceptions to DNSSEC validation. + */ + obj = NULL; + result = named_config_get(maps, "validate-except", &obj); + if (result == ISC_R_SUCCESS) { + result = dns_view_getntatable(view, &ntatable); + } + if (result == ISC_R_SUCCESS) { + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + dns_fixedname_t fntaname; + dns_name_t *ntaname; + + ntaname = dns_fixedname_initname(&fntaname); + obj = cfg_listelt_value(element); + CHECK(dns_name_fromstring( + ntaname, cfg_obj_asstring(obj), 0, NULL)); + CHECK(dns_ntatable_add(ntatable, ntaname, true, 0, + 0xffffffffU)); + } + } + +#ifdef HAVE_DNSTAP + /* + * Set up the dnstap environment and configure message + * types to log. + */ + CHECK(configure_dnstap(maps, view)); +#endif /* HAVE_DNSTAP */ + + result = ISC_R_SUCCESS; + +cleanup: + /* + * Revert to the old view if there was an error. + */ + if (result != ISC_R_SUCCESS) { + isc_result_t result2; + + result2 = dns_viewlist_find(&named_g_server->viewlist, + view->name, view->rdclass, &pview); + if (result2 == ISC_R_SUCCESS) { + dns_view_thaw(pview); + + obj = NULL; + if (rpz_configured && + pview->rdclass == dns_rdataclass_in && need_hints && + named_config_get(maps, "response-policy", &obj) == + ISC_R_SUCCESS) + { + /* + * We are swapping the places of the `view` and + * `pview` in the function's parameters list + * because we are reverting the same operation + * done previously in the "correct" order. + */ + result2 = configure_rpz(pview, view, maps, obj, + &old_rpz_ok); + if (result2 != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "rpz configuration " + "revert failed for view " + "'%s'", + pview->name); + } + } + + obj = NULL; + if (catz_configured && + pview->rdclass == dns_rdataclass_in && need_hints && + named_config_get(maps, "catalog-zones", &obj) == + ISC_R_SUCCESS) + { + /* + * We are swapping the places of the `view` and + * `pview` in the function's parameters list + * because we are reverting the same operation + * done previously in the "correct" order. + */ + result2 = configure_catz(pview, view, config, + obj); + if (result2 != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "catz configuration " + "revert failed for view " + "'%s'", + pview->name); + } + } + + dns_view_freeze(pview); + } + + if (pview != NULL) { + dns_view_detach(&pview); + } + + if (zone_element_latest != NULL) { + for (element = cfg_list_first(zonelist); + element != NULL; element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = + cfg_listelt_value(element); + configure_zone_setviewcommit(result, zconfig, + view); + if (element == zone_element_latest) { + /* + * This was the latest element that was + * successfully configured earlier. + */ + break; + } + } + } + } + + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + if (clients != NULL) { + dns_acl_detach(&clients); + } + if (mapped != NULL) { + dns_acl_detach(&mapped); + } + if (excluded != NULL) { + dns_acl_detach(&excluded); + } + if (ring != NULL) { + dns_tsigkeyring_detach(&ring); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (dispatch4 != NULL) { + dns_dispatch_detach(&dispatch4); + } + if (dispatch6 != NULL) { + dns_dispatch_detach(&dispatch6); + } + if (resstats != NULL) { + isc_stats_detach(&resstats); + } + if (resquerystats != NULL) { + dns_stats_detach(&resquerystats); + } + if (order != NULL) { + dns_order_detach(&order); + } + if (cmctx != NULL) { + isc_mem_detach(&cmctx); + } + if (hmctx != NULL) { + isc_mem_detach(&hmctx); + } + if (cache != NULL) { + dns_cache_detach(&cache); + } + if (dctx != NULL) { + dns_dyndb_destroyctx(&dctx); + } + + return (result); +} + +static isc_result_t +configure_hints(dns_view_t *view, const char *filename) { + isc_result_t result; + dns_db_t *db; + + db = NULL; + result = dns_rootns_create(view->mctx, view->rdclass, filename, &db); + if (result == ISC_R_SUCCESS) { + dns_view_sethints(view, db); + dns_db_detach(&db); + } + + return (result); +} + +static isc_result_t +configure_alternates(const cfg_obj_t *config, dns_view_t *view, + const cfg_obj_t *alternates) { + const cfg_obj_t *portobj; + const cfg_obj_t *addresses; + const cfg_listelt_t *element; + isc_result_t result = ISC_R_SUCCESS; + in_port_t port; + + /* + * Determine which port to send requests to. + */ + CHECKM(named_config_getport(config, "port", &port), "port"); + + if (alternates != NULL) { + portobj = cfg_tuple_get(alternates, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, + ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t)val; + } + } + + addresses = NULL; + if (alternates != NULL) { + addresses = cfg_tuple_get(alternates, "addresses"); + } + + for (element = cfg_list_first(addresses); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *alternate = cfg_listelt_value(element); + isc_sockaddr_t sa; + + if (!cfg_obj_issockaddr(alternate)) { + dns_fixedname_t fixed; + dns_name_t *name; + const char *str = cfg_obj_asstring( + cfg_tuple_get(alternate, "name")); + isc_buffer_t buffer; + in_port_t myport = port; + + isc_buffer_constinit(&buffer, str, strlen(str)); + isc_buffer_add(&buffer, strlen(str)); + name = dns_fixedname_initname(&fixed); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); + + portobj = cfg_tuple_get(alternate, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, + ISC_LOG_ERROR, + "port '%u' out of range", + val); + return (ISC_R_RANGE); + } + myport = (in_port_t)val; + } + dns_resolver_addalternate(view->resolver, NULL, name, + myport); + continue; + } + + sa = *cfg_obj_assockaddr(alternate); + if (isc_sockaddr_getport(&sa) == 0) { + isc_sockaddr_setport(&sa, port); + } + dns_resolver_addalternate(view->resolver, &sa, NULL, 0); + } + +cleanup: + return (result); +} + +static isc_result_t +configure_forward(const cfg_obj_t *config, dns_view_t *view, + const dns_name_t *origin, const cfg_obj_t *forwarders, + const cfg_obj_t *forwardtype) { + const cfg_obj_t *portobj = NULL; + const cfg_obj_t *faddresses = NULL; + const cfg_listelt_t *element = NULL; + dns_fwdpolicy_t fwdpolicy = dns_fwdpolicy_none; + dns_forwarderlist_t fwdlist; + dns_forwarder_t *fwd = NULL; + isc_result_t result; + in_port_t port; + + ISC_LIST_INIT(fwdlist); + + /* + * Determine which port to send forwarded requests to. + */ + CHECKM(named_config_getport(config, "port", &port), "port"); + + if (forwarders != NULL) { + portobj = cfg_tuple_get(forwarders, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, named_g_lctx, + ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t)val; + } + } + + faddresses = NULL; + if (forwarders != NULL) { + faddresses = cfg_tuple_get(forwarders, "addresses"); + } + + for (element = cfg_list_first(faddresses); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *forwarder = cfg_listelt_value(element); + fwd = isc_mem_get(view->mctx, sizeof(dns_forwarder_t)); + fwd->addr = *cfg_obj_assockaddr(forwarder); + if (isc_sockaddr_getport(&fwd->addr) == 0) { + isc_sockaddr_setport(&fwd->addr, port); + } + ISC_LINK_INIT(fwd, link); + ISC_LIST_APPEND(fwdlist, fwd, link); + } + + if (ISC_LIST_EMPTY(fwdlist)) { + if (forwardtype != NULL) { + cfg_obj_log(forwardtype, named_g_lctx, ISC_LOG_WARNING, + "no forwarders seen; disabling " + "forwarding"); + } + fwdpolicy = dns_fwdpolicy_none; + } else { + if (forwardtype == NULL) { + fwdpolicy = dns_fwdpolicy_first; + } else { + const char *forwardstr = cfg_obj_asstring(forwardtype); + if (strcasecmp(forwardstr, "first") == 0) { + fwdpolicy = dns_fwdpolicy_first; + } else if (strcasecmp(forwardstr, "only") == 0) { + fwdpolicy = dns_fwdpolicy_only; + } else { + UNREACHABLE(); + } + } + } + + result = dns_fwdtable_addfwd(view->fwdtable, origin, &fwdlist, + fwdpolicy); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + cfg_obj_log(forwarders, named_g_lctx, ISC_LOG_WARNING, + "could not set up forwarding for domain '%s': %s", + namebuf, isc_result_totext(result)); + goto cleanup; + } + + if (fwdpolicy == dns_fwdpolicy_only) { + dns_view_sfd_add(view, origin); + } + + result = ISC_R_SUCCESS; + +cleanup: + + while (!ISC_LIST_EMPTY(fwdlist)) { + fwd = ISC_LIST_HEAD(fwdlist); + ISC_LIST_UNLINK(fwdlist, fwd, link); + isc_mem_put(view->mctx, fwd, sizeof(dns_forwarder_t)); + } + + return (result); +} + +static isc_result_t +get_viewinfo(const cfg_obj_t *vconfig, const char **namep, + dns_rdataclass_t *classp) { + isc_result_t result = ISC_R_SUCCESS; + const char *viewname; + dns_rdataclass_t viewclass; + + REQUIRE(namep != NULL && *namep == NULL); + REQUIRE(classp != NULL); + + if (vconfig != NULL) { + const cfg_obj_t *classobj = NULL; + + viewname = cfg_obj_asstring(cfg_tuple_get(vconfig, "name")); + classobj = cfg_tuple_get(vconfig, "class"); + CHECK(named_config_getclass(classobj, dns_rdataclass_in, + &viewclass)); + if (dns_rdataclass_ismeta(viewclass)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "view '%s': class must not be meta", + viewname); + CHECK(ISC_R_FAILURE); + } + } else { + viewname = "_default"; + viewclass = dns_rdataclass_in; + } + + *namep = viewname; + *classp = viewclass; + +cleanup: + return (result); +} + +/* + * Find a view based on its configuration info and attach to it. + * + * If 'vconfig' is NULL, attach to the default view. + */ +static isc_result_t +find_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, + dns_view_t **viewp) { + isc_result_t result; + const char *viewname = NULL; + dns_rdataclass_t viewclass; + dns_view_t *view = NULL; + + result = get_viewinfo(vconfig, &viewname, &viewclass); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_viewlist_find(viewlist, viewname, viewclass, &view); + if (result != ISC_R_SUCCESS) { + return (result); + } + + *viewp = view; + return (ISC_R_SUCCESS); +} + +/* + * Create a new view and add it to the list. + * + * If 'vconfig' is NULL, create the default view. + * + * The view created is attached to '*viewp'. + */ +static isc_result_t +create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, + dns_view_t **viewp) { + isc_result_t result; + const char *viewname = NULL; + dns_rdataclass_t viewclass; + dns_view_t *view = NULL; + + result = get_viewinfo(vconfig, &viewname, &viewclass); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_viewlist_find(viewlist, viewname, viewclass, &view); + if (result == ISC_R_SUCCESS) { + return (ISC_R_EXISTS); + } + if (result != ISC_R_NOTFOUND) { + return (result); + } + INSIST(view == NULL); + + result = dns_view_create(named_g_mctx, viewclass, viewname, &view); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_nonce_buf(view->secret, sizeof(view->secret)); + + ISC_LIST_APPEND(*viewlist, view, link); + dns_view_attach(view, viewp); + return (ISC_R_SUCCESS); +} + +/* + * Configure or reconfigure a zone. + */ +static isc_result_t +configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, + const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, + bool modify) { + dns_view_t *pview = NULL; /* Production view */ + dns_zone_t *zone = NULL; /* New or reused zone */ + dns_zone_t *raw = NULL; /* New or reused raw zone */ + dns_zone_t *dupzone = NULL; + const cfg_obj_t *options = NULL; + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *typeobj = NULL; + const cfg_obj_t *forwarders = NULL; + const cfg_obj_t *forwardtype = NULL; + const cfg_obj_t *ixfrfromdiffs = NULL; + const cfg_obj_t *only = NULL; + const cfg_obj_t *viewobj = NULL; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + isc_buffer_t buffer; + dns_fixedname_t fixorigin; + dns_name_t *origin; + const char *zname; + dns_rdataclass_t zclass; + const char *ztypestr; + dns_rpz_num_t rpz_num; + bool zone_is_catz = false; + bool zone_maybe_inline = false; + bool inline_signing = false; + bool fullsign = false; + + options = NULL; + (void)cfg_map_get(config, "options", &options); + + zoptions = cfg_tuple_get(zconfig, "options"); + + /* + * Get the zone origin as a dns_name_t. + */ + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + isc_buffer_constinit(&buffer, zname, strlen(zname)); + isc_buffer_add(&buffer, strlen(zname)); + dns_fixedname_init(&fixorigin); + CHECK(dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer, + dns_rootname, 0, NULL)); + origin = dns_fixedname_name(&fixorigin); + + CHECK(named_config_getclass(cfg_tuple_get(zconfig, "class"), + view->rdclass, &zclass)); + if (zclass != view->rdclass) { + const char *vname = NULL; + if (vconfig != NULL) { + vname = cfg_obj_asstring( + cfg_tuple_get(vconfig, "name")); + } else { + vname = "<default view>"; + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': wrong class for view '%s'", zname, + vname); + result = ISC_R_FAILURE; + goto cleanup; + } + + (void)cfg_map_get(zoptions, "in-view", &viewobj); + if (viewobj != NULL) { + const char *inview = cfg_obj_asstring(viewobj); + dns_view_t *otherview = NULL; + + if (viewlist == NULL) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "'in-view' option is not permitted in " + "dynamically added zones"); + result = ISC_R_FAILURE; + goto cleanup; + } + + result = dns_viewlist_find(viewlist, inview, view->rdclass, + &otherview); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "view '%s' is not yet defined.", inview); + result = ISC_R_FAILURE; + goto cleanup; + } + + result = dns_view_findzone(otherview, origin, &zone); + dns_view_detach(&otherview); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "zone '%s' not defined in view '%s'", zname, + inview); + result = ISC_R_FAILURE; + goto cleanup; + } + + CHECK(dns_view_addzone(view, zone)); + dns_zone_detach(&zone); + + /* + * If the zone contains a 'forwarders' statement, configure + * selective forwarding. Note: this is not inherited from the + * other view. + */ + forwarders = NULL; + result = cfg_map_get(zoptions, "forwarders", &forwarders); + if (result == ISC_R_SUCCESS) { + forwardtype = NULL; + (void)cfg_map_get(zoptions, "forward", &forwardtype); + CHECK(configure_forward(config, view, origin, + forwarders, forwardtype)); + } + result = ISC_R_SUCCESS; + goto cleanup; + } + + (void)cfg_map_get(zoptions, "type", &typeobj); + if (typeobj == NULL) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "zone '%s' 'type' not specified", zname); + result = ISC_R_FAILURE; + goto cleanup; + } + ztypestr = cfg_obj_asstring(typeobj); + + /* + * "hints zones" aren't zones. If we've got one, + * configure it and return. + */ + if (strcasecmp(ztypestr, "hint") == 0) { + const cfg_obj_t *fileobj = NULL; + if (cfg_map_get(zoptions, "file", &fileobj) != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': 'file' not specified", zname); + result = ISC_R_FAILURE; + goto cleanup; + } + if (dns_name_equal(origin, dns_rootname)) { + const char *hintsfile = cfg_obj_asstring(fileobj); + + CHECK(configure_hints(view, hintsfile)); + + /* + * Hint zones may also refer to delegation only points. + */ + only = NULL; + tresult = cfg_map_get(zoptions, "delegation-only", + &only); + if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(only)) + { + dns_view_adddelegationonly(view, origin); + } + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "ignoring non-root hint zone '%s'", + zname); + result = ISC_R_SUCCESS; + } + /* Skip ordinary zone processing. */ + goto cleanup; + } + + /* + * "forward zones" aren't zones either. Translate this syntax into + * the appropriate selective forwarding configuration and return. + */ + if (strcasecmp(ztypestr, "forward") == 0) { + forwardtype = NULL; + forwarders = NULL; + + (void)cfg_map_get(zoptions, "forward", &forwardtype); + (void)cfg_map_get(zoptions, "forwarders", &forwarders); + CHECK(configure_forward(config, view, origin, forwarders, + forwardtype)); + + /* + * Forward zones may also set delegation only. + */ + only = NULL; + tresult = cfg_map_get(zoptions, "delegation-only", &only); + if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(only)) { + dns_view_adddelegationonly(view, origin); + } + goto cleanup; + } + + /* + * "delegation-only zones" aren't zones either. + */ + if (strcasecmp(ztypestr, "delegation-only") == 0) { + dns_view_adddelegationonly(view, origin); + goto cleanup; + } + + /* + * Redirect zones only require minimal configuration. + */ + if (strcasecmp(ztypestr, "redirect") == 0) { + if (view->redirect != NULL) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "redirect zone already exists"); + result = ISC_R_EXISTS; + goto cleanup; + } + result = dns_viewlist_find(viewlist, view->name, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + goto cleanup; + } + if (pview != NULL && pview->redirect != NULL) { + dns_zone_attach(pview->redirect, &zone); + dns_zone_setview(zone, view); + } else { + CHECK(dns_zonemgr_createzone(named_g_server->zonemgr, + &zone)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, + zone)); + dns_zone_setstats(zone, named_g_server->zonestats); + } + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, + kasplist, zone, NULL)); + dns_zone_attach(zone, &view->redirect); + goto cleanup; + } + + if (!modify) { + /* + * Check for duplicates in the new zone table. + */ + result = dns_view_findzone(view, origin, &dupzone); + if (result == ISC_R_SUCCESS) { + /* + * We already have this zone! + */ + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "zone '%s' already exists", zname); + dns_zone_detach(&dupzone); + result = ISC_R_EXISTS; + goto cleanup; + } + INSIST(dupzone == NULL); + } + + /* + * Note whether this is a response policy zone and which one if so, + * unless we are using RPZ service interface. In that case, the + * BIND zone database has nothing to do with rpz and so we don't care. + */ + for (rpz_num = 0;; ++rpz_num) { + if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones || + view->rpzs->p.dnsrps_enabled) + { + rpz_num = DNS_RPZ_INVALID_NUM; + break; + } + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin)) + { + break; + } + } + + if (view->catzs != NULL && + dns_catz_get_zone(view->catzs, origin) != NULL) + { + zone_is_catz = true; + } + + /* + * See if we can reuse an existing zone. This is + * only possible if all of these are true: + * - The zone's view exists + * - A zone with the right name exists in the view + * - The zone is compatible with the config + * options (e.g., an existing primary zone cannot + * be reused if the options specify a secondary zone) + * - The zone was not and is still not a response policy zone + * or the zone is a policy zone with an unchanged number + * and we are using the old policy zone summary data. + */ + result = dns_viewlist_find(&named_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + goto cleanup; + } + if (pview != NULL) { + result = dns_view_findzone(pview, origin, &zone); + } + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (zone != NULL && !named_zone_reusable(zone, zconfig)) { + dns_zone_detach(&zone); + fullsign = true; + } + + if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) || + (rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok))) + { + dns_zone_detach(&zone); + } + + if (zone != NULL) { + /* + * We found a reusable zone. Make it use the + * new view. + */ + dns_zone_setview(zone, view); + } else { + /* + * We cannot reuse an existing zone, we have + * to create a new one. + */ + CHECK(dns_zonemgr_createzone(named_g_server->zonemgr, &zone)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, zone)); + dns_zone_setstats(zone, named_g_server->zonestats); + } + if (rpz_num != DNS_RPZ_INVALID_NUM) { + result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': incompatible" + " masterfile-format or database" + " for a response policy zone", + zname); + goto cleanup; + } + } + + if (zone_is_catz) { + dns_zone_catz_enable(zone, view->catzs); + } else if (dns_zone_catz_is_enabled(zone)) { + dns_zone_catz_disable(zone); + } + + /* + * If the zone contains a 'forwarders' statement, configure + * selective forwarding. + */ + forwarders = NULL; + if (cfg_map_get(zoptions, "forwarders", &forwarders) == ISC_R_SUCCESS) { + forwardtype = NULL; + (void)cfg_map_get(zoptions, "forward", &forwardtype); + CHECK(configure_forward(config, view, origin, forwarders, + forwardtype)); + } + + /* + * Stub and forward zones may also refer to delegation only points. + */ + only = NULL; + if (cfg_map_get(zoptions, "delegation-only", &only) == ISC_R_SUCCESS) { + if (cfg_obj_asboolean(only)) { + dns_view_adddelegationonly(view, origin); + } + } + + /* + * Mark whether the zone was originally added at runtime or not + */ + dns_zone_setadded(zone, added); + + /* + * Determine if we need to set up inline signing. + */ + zone_maybe_inline = ((strcasecmp(ztypestr, "primary") == 0 || + strcasecmp(ztypestr, "master") == 0 || + strcasecmp(ztypestr, "secondary") == 0 || + strcasecmp(ztypestr, "slave") == 0)); + + if (zone_maybe_inline) { + inline_signing = named_zone_inlinesigning(zconfig); + } + if (inline_signing) { + dns_zone_getraw(zone, &raw); + if (raw == NULL) { + CHECK(dns_zone_create(&raw, mctx)); + CHECK(dns_zone_setorigin(raw, origin)); + dns_zone_setview(raw, view); + dns_zone_setstats(raw, named_g_server->zonestats); + CHECK(dns_zone_link(zone, raw)); + } + if (cfg_map_get(zoptions, "ixfr-from-differences", + &ixfrfromdiffs) == ISC_R_SUCCESS) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone '%s': 'ixfr-from-differences' is " + "ignored for inline-signed zones", + zname); + } + } + + /* + * Configure the zone. + */ + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, kasplist, + zone, raw)); + + /* + * Add the zone to its view in the new view list. + */ + if (!modify) { + CHECK(dns_view_addzone(view, zone)); + } + + if (zone_is_catz) { + /* + * force catz reload if the zone is loaded; + * if it's not it'll get reloaded on zone load + */ + dns_db_t *db = NULL; + + tresult = dns_zone_getdb(zone, &db); + if (tresult == ISC_R_SUCCESS) { + dns_catz_dbupdate_callback(db, view->catzs); + dns_db_detach(&db); + } + } + + /* + * Ensure that zone keys are reloaded on reconfig + */ + if ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_MAINTAIN) != 0) { + dns_zone_rekey(zone, fullsign); + } + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (raw != NULL) { + dns_zone_detach(&raw); + } + if (pview != NULL) { + dns_view_detach(&pview); + } + + return (result); +} + +/* + * Configure built-in zone for storing managed-key data. + */ +static isc_result_t +add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) { + isc_result_t result; + dns_view_t *pview = NULL; + dns_zone_t *zone = NULL; + dns_acl_t *none = NULL; + char filename[PATH_MAX]; + bool defaultview; + + REQUIRE(view != NULL); + + /* See if we can re-use an existing keydata zone. */ + result = dns_viewlist_find(&named_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + return (result); + } + + if (pview != NULL) { + if (pview->managed_keys != NULL) { + dns_zone_attach(pview->managed_keys, + &view->managed_keys); + dns_zone_setview(pview->managed_keys, view); + dns_zone_setviewcommit(pview->managed_keys); + dns_view_detach(&pview); + dns_zone_synckeyzone(view->managed_keys); + return (ISC_R_SUCCESS); + } + + dns_view_detach(&pview); + } + + /* No existing keydata zone was found; create one */ + CHECK(dns_zonemgr_createzone(named_g_server->zonemgr, &zone)); + CHECK(dns_zone_setorigin(zone, dns_rootname)); + + defaultview = (strcmp(view->name, "_default") == 0); + CHECK(isc_file_sanitize( + directory, defaultview ? "managed-keys" : view->name, + defaultview ? "bind" : "mkeys", filename, sizeof(filename))); + CHECK(dns_zone_setfile(zone, filename, dns_masterformat_text, + &dns_master_style_default)); + + dns_zone_setview(zone, view); + dns_zone_settype(zone, dns_zone_key); + dns_zone_setclass(zone, view->rdclass); + + CHECK(dns_zonemgr_managezone(named_g_server->zonemgr, zone)); + + CHECK(dns_acl_none(mctx, &none)); + dns_zone_setqueryacl(zone, none); + dns_zone_setqueryonacl(zone, none); + dns_acl_detach(&none); + + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + dns_zone_setjournalsize(zone, 0); + + dns_zone_setstats(zone, named_g_server->zonestats); + CHECK(setquerystats(zone, mctx, dns_zonestat_none)); + + if (view->managed_keys != NULL) { + dns_zone_detach(&view->managed_keys); + } + dns_zone_attach(zone, &view->managed_keys); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "set up managed keys zone for view %s, file '%s'", + view->name, filename); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (none != NULL) { + dns_acl_detach(&none); + } + + return (result); +} + +/* + * Configure a single server quota. + */ +static void +configure_server_quota(const cfg_obj_t **maps, const char *name, + isc_quota_t *quota) { + const cfg_obj_t *obj = NULL; + isc_result_t result; + + result = named_config_get(maps, name, &obj); + INSIST(result == ISC_R_SUCCESS); + isc_quota_max(quota, cfg_obj_asuint32(obj)); +} + +/* + * This function is called as soon as the 'directory' statement has been + * parsed. This can be extended to support other options if necessary. + */ +static isc_result_t +directory_callback(const char *clausename, const cfg_obj_t *obj, void *arg) { + isc_result_t result; + const char *directory; + + REQUIRE(strcasecmp("directory", clausename) == 0); + + UNUSED(arg); + UNUSED(clausename); + + /* + * Change directory. + */ + directory = cfg_obj_asstring(obj); + + if (!isc_file_ischdiridempotent(directory)) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "option 'directory' contains relative path '%s'", + directory); + } + + if (!isc_file_isdirwritable(directory)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "directory '%s' is not writable", directory); + return (ISC_R_NOPERM); + } + + result = isc_dir_chdir(directory); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "change directory to '%s' failed: %s", directory, + isc_result_totext(result)); + return (result); + } + + return (ISC_R_SUCCESS); +} + +/* + * This event callback is invoked to do periodic network interface + * scanning. + */ + +static void +interface_timer_tick(isc_task_t *task, isc_event_t *event) { + named_server_t *server = (named_server_t *)event->ev_arg; + INSIST(task == server->task); + UNUSED(task); + + isc_event_free(&event); + ns_interfacemgr_scan(server->interfacemgr, false, false); +} + +static void +heartbeat_timer_tick(isc_task_t *task, isc_event_t *event) { + named_server_t *server = (named_server_t *)event->ev_arg; + dns_view_t *view; + + UNUSED(task); + isc_event_free(&event); + view = ISC_LIST_HEAD(server->viewlist); + while (view != NULL) { + dns_view_dialup(view); + view = ISC_LIST_NEXT(view, link); + } +} + +typedef struct { + isc_mem_t *mctx; + isc_task_t *task; + dns_fetch_t *fetch; + dns_view_t *view; + dns_fixedname_t tatname; + dns_fixedname_t keyname; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; +} ns_tat_t; + +static int +cid(const void *a, const void *b) { + const uint16_t ida = *(const uint16_t *)a; + const uint16_t idb = *(const uint16_t *)b; + if (ida < idb) { + return (-1); + } else if (ida > idb) { + return (1); + } else { + return (0); + } +} + +static void +tat_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent; + ns_tat_t *tat; + + INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE); + INSIST(event->ev_arg != NULL); + + UNUSED(task); + + tat = event->ev_arg; + devent = (dns_fetchevent_t *)event; + + /* Free resources which are not of interest */ + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + isc_event_free(&event); + dns_resolver_destroyfetch(&tat->fetch); + if (dns_rdataset_isassociated(&tat->rdataset)) { + dns_rdataset_disassociate(&tat->rdataset); + } + if (dns_rdataset_isassociated(&tat->sigrdataset)) { + dns_rdataset_disassociate(&tat->sigrdataset); + } + dns_view_detach(&tat->view); + isc_task_detach(&tat->task); + isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat)); +} + +struct dotat_arg { + dns_view_t *view; + isc_task_t *task; +}; + +/*% + * Prepare the QNAME for the TAT query to be sent by processing the trust + * anchors present at 'keynode' of 'keytable'. Store the result in 'dst' and + * the domain name which 'keynode' is associated with in 'origin'. + * + * A maximum of 12 key IDs can be reported in a single TAT query due to the + * 63-octet length limit for any single label in a domain name. If there are + * more than 12 keys configured at 'keynode', only the first 12 will be + * reported in the TAT query. + */ +static isc_result_t +get_tat_qname(dns_name_t *target, dns_name_t *keyname, dns_keynode_t *keynode) { + dns_rdataset_t dsset; + unsigned int i, n = 0; + uint16_t ids[12]; + isc_textregion_t r; + char label[64]; + int m; + + dns_rdataset_init(&dsset); + if (dns_keynode_dsset(keynode, &dsset)) { + isc_result_t result; + + for (result = dns_rdataset_first(&dsset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&dsset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_ds_t ds; + + dns_rdata_reset(&rdata); + dns_rdataset_current(&dsset, &rdata); + result = dns_rdata_tostruct(&rdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (n < (sizeof(ids) / sizeof(ids[0]))) { + ids[n] = ds.key_tag; + n++; + } + } + dns_rdataset_disassociate(&dsset); + } + + if (n == 0) { + return (DNS_R_EMPTYNAME); + } + + if (n > 1) { + qsort(ids, n, sizeof(ids[0]), cid); + } + + /* + * Encoded as "_ta-xxxx\(-xxxx\)*" where xxxx is the hex version of + * of the keyid. + */ + label[0] = 0; + r.base = label; + r.length = sizeof(label); + m = snprintf(r.base, r.length, "_ta"); + if (m < 0 || (unsigned)m > r.length) { + return (ISC_R_FAILURE); + } + isc_textregion_consume(&r, m); + for (i = 0; i < n; i++) { + m = snprintf(r.base, r.length, "-%04x", ids[i]); + if (m < 0 || (unsigned)m > r.length) { + return (ISC_R_FAILURE); + } + isc_textregion_consume(&r, m); + } + + return (dns_name_fromstring2(target, label, keyname, 0, NULL)); +} + +static void +tat_send(isc_task_t *task, isc_event_t *event) { + ns_tat_t *tat; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fdomain; + dns_name_t *domain; + dns_rdataset_t nameservers; + isc_result_t result; + dns_name_t *keyname; + dns_name_t *tatname; + + INSIST(event != NULL && event->ev_type == NAMED_EVENT_TATSEND); + INSIST(event->ev_arg != NULL); + + UNUSED(task); + + tat = event->ev_arg; + + keyname = dns_fixedname_name(&tat->keyname); + tatname = dns_fixedname_name(&tat->tatname); + + dns_name_format(tatname, namebuf, sizeof(namebuf)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s: sending trust-anchor-telemetry query '%s/NULL'", + tat->view->name, namebuf); + + /* + * TAT queries should be sent to the authoritative servers for a given + * zone. If this function is called for a keytable node corresponding + * to a locally served zone, calling dns_resolver_createfetch() with + * NULL 'domain' and 'nameservers' arguments will cause 'tatname' to be + * resolved locally, without sending any TAT queries upstream. + * + * Work around this issue by calling dns_view_findzonecut() first. If + * the zone is served locally, the NS RRset for the given domain name + * will be retrieved from local data; if it is not, the deepest zone + * cut we have for it will be retrieved from cache. In either case, + * passing the results to dns_resolver_createfetch() will prevent it + * from returning NXDOMAIN for 'tatname' while still allowing it to + * chase down any potential delegations returned by upstream servers in + * order to eventually find the destination host to send the TAT query + * to. + * + * After the dns_view_findzonecut() call, 'domain' will hold the + * deepest zone cut we can find for 'keyname' while 'nameservers' will + * hold the NS RRset at that zone cut. + */ + domain = dns_fixedname_initname(&fdomain); + dns_rdataset_init(&nameservers); + result = dns_view_findzonecut(tat->view, keyname, domain, NULL, 0, 0, + true, true, &nameservers, NULL); + if (result == ISC_R_SUCCESS) { + result = dns_resolver_createfetch( + tat->view->resolver, tatname, dns_rdatatype_null, + domain, &nameservers, NULL, NULL, 0, 0, 0, NULL, + tat->task, tat_done, tat, &tat->rdataset, + &tat->sigrdataset, &tat->fetch); + } + + /* + * 'domain' holds the dns_name_t pointer inside a dst_key_t structure. + * dns_resolver_createfetch() creates its own copy of 'domain' if it + * succeeds. Thus, 'domain' is not freed here. + * + * Even if dns_view_findzonecut() returned something else than + * ISC_R_SUCCESS, it still could have associated 'nameservers'. + * dns_resolver_createfetch() creates its own copy of 'nameservers' if + * it succeeds. Thus, we need to check whether 'nameservers' is + * associated and release it if it is. + */ + if (dns_rdataset_isassociated(&nameservers)) { + dns_rdataset_disassociate(&nameservers); + } + + if (result != ISC_R_SUCCESS) { + dns_view_detach(&tat->view); + isc_task_detach(&tat->task); + isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat)); + } + isc_event_free(&event); +} + +static void +dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, dns_name_t *keyname, + void *arg) { + struct dotat_arg *dotat_arg = arg; + isc_result_t result; + dns_view_t *view; + isc_task_t *task; + ns_tat_t *tat; + isc_event_t *event; + + REQUIRE(keytable != NULL); + REQUIRE(keynode != NULL); + REQUIRE(dotat_arg != NULL); + + view = dotat_arg->view; + task = dotat_arg->task; + + tat = isc_mem_get(dotat_arg->view->mctx, sizeof(*tat)); + + tat->fetch = NULL; + tat->mctx = NULL; + tat->task = NULL; + tat->view = NULL; + dns_rdataset_init(&tat->rdataset); + dns_rdataset_init(&tat->sigrdataset); + dns_name_copy(keyname, dns_fixedname_initname(&tat->keyname)); + result = get_tat_qname(dns_fixedname_initname(&tat->tatname), keyname, + keynode); + if (result != ISC_R_SUCCESS) { + isc_mem_put(dotat_arg->view->mctx, tat, sizeof(*tat)); + return; + } + isc_mem_attach(dotat_arg->view->mctx, &tat->mctx); + isc_task_attach(task, &tat->task); + dns_view_attach(view, &tat->view); + + /* + * We don't want to be holding the keytable lock when calling + * dns_view_findzonecut() as it creates a lock order loop so + * call dns_view_findzonecut() in a event handler. + * + * zone->lock (dns_zone_setviewcommit) while holding view->lock + * (dns_view_setviewcommit) + * + * keytable->lock (dns_keytable_find) while holding zone->lock + * (zone_asyncload) + * + * view->lock (dns_view_findzonecut) while holding keytable->lock + * (dns_keytable_forall) + */ + event = isc_event_allocate(tat->mctx, keytable, NAMED_EVENT_TATSEND, + tat_send, tat, sizeof(isc_event_t)); + isc_task_send(task, &event); +} + +static void +tat_timer_tick(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + named_server_t *server = (named_server_t *)event->ev_arg; + struct dotat_arg arg; + dns_view_t *view; + dns_keytable_t *secroots = NULL; + + isc_event_free(&event); + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!view->trust_anchor_telemetry || !view->enablevalidation) { + continue; + } + + result = dns_view_getsecroots(view, &secroots); + if (result != ISC_R_SUCCESS) { + continue; + } + + arg.view = view; + arg.task = task; + (void)dns_keytable_forall(secroots, dotat, &arg); + dns_keytable_detach(&secroots); + } +} + +static void +pps_timer_tick(isc_task_t *task, isc_event_t *event) { + static unsigned int oldrequests = 0; + unsigned int requests = atomic_load_relaxed(&ns_client_requests); + + UNUSED(task); + isc_event_free(&event); + + /* + * Don't worry about wrapping as the overflow result will be right. + */ + dns_pps = (requests - oldrequests) / 1200; + oldrequests = requests; +} + +/* + * Replace the current value of '*field', a dynamically allocated + * string or NULL, with a dynamically allocated copy of the + * null-terminated string pointed to by 'value', or NULL. + */ +static isc_result_t +setstring(named_server_t *server, char **field, const char *value) { + char *copy; + + if (value != NULL) { + copy = isc_mem_strdup(server->mctx, value); + } else { + copy = NULL; + } + + if (*field != NULL) { + isc_mem_free(server->mctx, *field); + } + + *field = copy; + return (ISC_R_SUCCESS); +} + +/* + * Replace the current value of '*field', a dynamically allocated + * string or NULL, with another dynamically allocated string + * or NULL if whether 'obj' is a string or void value, respectively. + */ +static isc_result_t +setoptstring(named_server_t *server, char **field, const cfg_obj_t *obj) { + if (cfg_obj_isvoid(obj)) { + return (setstring(server, field, NULL)); + } else { + return (setstring(server, field, cfg_obj_asstring(obj))); + } +} + +static void +set_limit(const cfg_obj_t **maps, const char *configname, + const char *description, isc_resource_t resourceid, + isc_resourcevalue_t defaultvalue) { + const cfg_obj_t *obj = NULL; + const char *resource; + isc_resourcevalue_t value; + isc_result_t result; + + if (named_config_get(maps, configname, &obj) != ISC_R_SUCCESS) { + return; + } + + if (cfg_obj_isstring(obj)) { + resource = cfg_obj_asstring(obj); + if (strcasecmp(resource, "unlimited") == 0) { + value = ISC_RESOURCE_UNLIMITED; + } else { + INSIST(strcasecmp(resource, "default") == 0); + value = defaultvalue; + } + } else { + value = cfg_obj_asuint64(obj); + } + + result = isc_resource_setlimit(resourceid, value); + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, + result == ISC_R_SUCCESS ? ISC_LOG_DEBUG(3) : ISC_LOG_WARNING, + "set maximum %s to %" PRIu64 ": %s", description, value, + isc_result_totext(result)); +} + +#define SETLIMIT(cfgvar, resource, description) \ + set_limit(maps, cfgvar, description, isc_resource_##resource, \ + named_g_init##resource) + +static void +set_limits(const cfg_obj_t **maps) { + SETLIMIT("stacksize", stacksize, "stack size"); + SETLIMIT("datasize", datasize, "data size"); + SETLIMIT("coresize", coresize, "core size"); + SETLIMIT("files", openfiles, "open files"); +} + +static void +portset_fromconf(isc_portset_t *portset, const cfg_obj_t *ports, + bool positive) { + const cfg_listelt_t *element; + + for (element = cfg_list_first(ports); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *obj = cfg_listelt_value(element); + + if (cfg_obj_isuint32(obj)) { + in_port_t port = (in_port_t)cfg_obj_asuint32(obj); + + if (positive) { + isc_portset_add(portset, port); + } else { + isc_portset_remove(portset, port); + } + } else { + const cfg_obj_t *obj_loport, *obj_hiport; + in_port_t loport, hiport; + + obj_loport = cfg_tuple_get(obj, "loport"); + loport = (in_port_t)cfg_obj_asuint32(obj_loport); + obj_hiport = cfg_tuple_get(obj, "hiport"); + hiport = (in_port_t)cfg_obj_asuint32(obj_hiport); + + if (positive) { + isc_portset_addrange(portset, loport, hiport); + } else { + isc_portset_removerange(portset, loport, + hiport); + } + } + } +} + +static isc_result_t +removed(dns_zone_t *zone, void *uap) { + if (dns_zone_getview(zone) != uap) { + return (ISC_R_SUCCESS); + } + + dns_zone_log(zone, ISC_LOG_INFO, "(%s) removed", + dns_zonetype_name(dns_zone_gettype(zone))); + return (ISC_R_SUCCESS); +} + +static void +cleanup_session_key(named_server_t *server, isc_mem_t *mctx) { + if (server->session_keyfile != NULL) { + isc_file_remove(server->session_keyfile); + isc_mem_free(mctx, server->session_keyfile); + server->session_keyfile = NULL; + } + + if (server->session_keyname != NULL) { + if (dns_name_dynamic(server->session_keyname)) { + dns_name_free(server->session_keyname, mctx); + } + isc_mem_put(mctx, server->session_keyname, sizeof(dns_name_t)); + server->session_keyname = NULL; + } + + if (server->sessionkey != NULL) { + dst_key_free(&server->sessionkey); + } + + server->session_keyalg = DST_ALG_UNKNOWN; + server->session_keybits = 0; +} + +static isc_result_t +generate_session_key(const char *filename, const char *keynamestr, + const dns_name_t *keyname, const char *algstr, + unsigned int algtype, uint16_t bits, isc_mem_t *mctx, + bool first_time, dst_key_t **keyp) { + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *key = NULL; + isc_buffer_t key_txtbuffer; + isc_buffer_t key_rawbuffer; + char key_txtsecret[256]; + char key_rawsecret[64]; + isc_region_t key_rawregion; + FILE *fp = NULL; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "generating session key for dynamic DNS"); + + /* generate key */ + result = dst_key_generate(keyname, algtype, bits, 1, 0, + DNS_KEYPROTO_ANY, dns_rdataclass_in, mctx, + &key, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Dump the key to the buffer for later use. + */ + isc_buffer_init(&key_rawbuffer, &key_rawsecret, sizeof(key_rawsecret)); + CHECK(dst_key_tobuffer(key, &key_rawbuffer)); + + isc_buffer_usedregion(&key_rawbuffer, &key_rawregion); + isc_buffer_init(&key_txtbuffer, &key_txtsecret, sizeof(key_txtsecret)); + CHECK(isc_base64_totext(&key_rawregion, -1, "", &key_txtbuffer)); + + /* Dump the key to the key file. */ + fp = named_os_openfile(filename, S_IRUSR | S_IWUSR, first_time); + if (fp == NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not create %s", filename); + result = ISC_R_NOPERM; + goto cleanup; + } + + fprintf(fp, + "key \"%s\" {\n" + "\talgorithm %s;\n" + "\tsecret \"%.*s\";\n};\n", + keynamestr, algstr, (int)isc_buffer_usedlength(&key_txtbuffer), + (char *)isc_buffer_base(&key_txtbuffer)); + + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + *keyp = key; + return (ISC_R_SUCCESS); + +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed to generate session key " + "for dynamic DNS: %s", + isc_result_totext(result)); + if (fp != NULL) { + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + } + if (key != NULL) { + dst_key_free(&key); + } + + return (result); +} + +static isc_result_t +configure_session_key(const cfg_obj_t **maps, named_server_t *server, + isc_mem_t *mctx, bool first_time) { + const char *keyfile, *keynamestr, *algstr; + unsigned int algtype; + dns_fixedname_t fname; + dns_name_t *keyname; + const dns_name_t *algname; + isc_buffer_t buffer; + uint16_t bits; + const cfg_obj_t *obj; + bool need_deleteold = false; + bool need_createnew = false; + isc_result_t result; + + obj = NULL; + result = named_config_get(maps, "session-keyfile", &obj); + if (result == ISC_R_SUCCESS) { + if (cfg_obj_isvoid(obj)) { + keyfile = NULL; /* disable it */ + } else { + keyfile = cfg_obj_asstring(obj); + } + } else { + keyfile = named_g_defaultsessionkeyfile; + } + + obj = NULL; + result = named_config_get(maps, "session-keyname", &obj); + INSIST(result == ISC_R_SUCCESS); + keynamestr = cfg_obj_asstring(obj); + isc_buffer_constinit(&buffer, keynamestr, strlen(keynamestr)); + isc_buffer_add(&buffer, strlen(keynamestr)); + keyname = dns_fixedname_initname(&fname); + result = dns_name_fromtext(keyname, &buffer, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = NULL; + result = named_config_get(maps, "session-keyalg", &obj); + INSIST(result == ISC_R_SUCCESS); + algstr = cfg_obj_asstring(obj); + algname = NULL; + result = named_config_getkeyalgorithm2(algstr, &algname, &algtype, + &bits); + if (result != ISC_R_SUCCESS) { + const char *s = " (keeping current key)"; + + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "session-keyalg: " + "unsupported or unknown algorithm '%s'%s", + algstr, server->session_keyfile != NULL ? s : ""); + return (result); + } + + /* See if we need to (re)generate a new key. */ + if (keyfile == NULL) { + if (server->session_keyfile != NULL) { + need_deleteold = true; + } + } else if (server->session_keyfile == NULL) { + need_createnew = true; + } else if (strcmp(keyfile, server->session_keyfile) != 0 || + !dns_name_equal(server->session_keyname, keyname) || + server->session_keyalg != algtype || + server->session_keybits != bits) + { + need_deleteold = true; + need_createnew = true; + } + + if (need_deleteold) { + INSIST(server->session_keyfile != NULL); + INSIST(server->session_keyname != NULL); + INSIST(server->sessionkey != NULL); + + cleanup_session_key(server, mctx); + } + + if (need_createnew) { + INSIST(server->sessionkey == NULL); + INSIST(server->session_keyfile == NULL); + INSIST(server->session_keyname == NULL); + INSIST(server->session_keyalg == DST_ALG_UNKNOWN); + INSIST(server->session_keybits == 0); + + server->session_keyname = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(server->session_keyname, NULL); + dns_name_dup(keyname, mctx, server->session_keyname); + + server->session_keyfile = isc_mem_strdup(mctx, keyfile); + + server->session_keyalg = algtype; + server->session_keybits = bits; + + CHECK(generate_session_key(keyfile, keynamestr, keyname, algstr, + algtype, bits, mctx, first_time, + &server->sessionkey)); + } + + return (result); + +cleanup: + cleanup_session_key(server, mctx); + return (result); +} + +#ifndef HAVE_LMDB +static isc_result_t +count_newzones(dns_view_t *view, ns_cfgctx_t *nzcfg, int *num_zonesp) { + isc_result_t result; + + /* The new zone file may not exist. That is OK. */ + if (!isc_file_exists(view->new_zone_file)) { + *num_zonesp = 0; + return (ISC_R_SUCCESS); + } + + /* + * In the case of NZF files, we also parse the configuration in + * the file at this stage. + * + * This may be called in multiple views, so we reset + * the parser each time. + */ + cfg_parser_reset(named_g_addparser); + result = cfg_parse_file(named_g_addparser, view->new_zone_file, + &cfg_type_addzoneconf, &nzcfg->nzf_config); + if (result == ISC_R_SUCCESS) { + int num_zones; + + num_zones = count_zones(nzcfg->nzf_config); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "NZF file '%s' contains %d zones", + view->new_zone_file, num_zones); + if (num_zonesp != NULL) { + *num_zonesp = num_zones; + } + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error parsing NZF file '%s': %s", + view->new_zone_file, isc_result_totext(result)); + } + + return (result); +} + +#else /* HAVE_LMDB */ + +static isc_result_t +count_newzones(dns_view_t *view, ns_cfgctx_t *nzcfg, int *num_zonesp) { + isc_result_t result; + int n; + + UNUSED(nzcfg); + + REQUIRE(num_zonesp != NULL); + + LOCK(&view->new_zone_lock); + + CHECK(migrate_nzf(view)); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading NZD zone count from '%s' " + "for view '%s'", + view->new_zone_db, view->name); + + CHECK(nzd_count(view, &n)); + + *num_zonesp = n; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "NZD database '%s' contains %d zones", view->new_zone_db, + n); + +cleanup: + if (result != ISC_R_SUCCESS) { + *num_zonesp = 0; + } + + UNLOCK(&view->new_zone_lock); + + return (ISC_R_SUCCESS); +} + +#endif /* HAVE_LMDB */ + +static isc_result_t +setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + cfg_parser_t *conf_parser, cfg_aclconfctx_t *actx, + int *num_zones) { + isc_result_t result = ISC_R_SUCCESS; + bool allow = false; + ns_cfgctx_t *nzcfg = NULL; + const cfg_obj_t *maps[4]; + const cfg_obj_t *options = NULL, *voptions = NULL; + const cfg_obj_t *nz = NULL; + const cfg_obj_t *nzdir = NULL; + const char *dir = NULL; + const cfg_obj_t *obj = NULL; + int i = 0; + uint64_t mapsize = 0ULL; + + REQUIRE(config != NULL); + + if (vconfig != NULL) { + voptions = cfg_tuple_get(vconfig, "options"); + } + if (voptions != NULL) { + maps[i++] = voptions; + } + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) { + maps[i++] = options; + } + maps[i++] = named_g_defaults; + maps[i] = NULL; + + result = named_config_get(maps, "allow-new-zones", &nz); + if (result == ISC_R_SUCCESS) { + allow = cfg_obj_asboolean(nz); + } + result = named_config_get(maps, "new-zones-directory", &nzdir); + if (result == ISC_R_SUCCESS) { + dir = cfg_obj_asstring(nzdir); + result = isc_file_isdirectory(dir); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "invalid new-zones-directory %s: %s", dir, + isc_result_totext(result)); + return (result); + } + if (!isc_file_isdirwritable(dir)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "new-zones-directory '%s' " + "is not writable", + dir); + return (ISC_R_NOPERM); + } + + dns_view_setnewzonedir(view, dir); + } + +#ifdef HAVE_LMDB + result = named_config_get(maps, "lmdb-mapsize", &obj); + if (result == ISC_R_SUCCESS && obj != NULL) { + mapsize = cfg_obj_asuint64(obj); + if (mapsize < (1ULL << 20)) { /* 1 megabyte */ + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "'lmdb-mapsize " + "%" PRId64 "' " + "is too small", + mapsize); + return (ISC_R_FAILURE); + } else if (mapsize > (1ULL << 40)) { /* 1 terabyte */ + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "'lmdb-mapsize " + "%" PRId64 "' " + "is too large", + mapsize); + return (ISC_R_FAILURE); + } + } +#else /* ifdef HAVE_LMDB */ + UNUSED(obj); +#endif /* HAVE_LMDB */ + + /* + * A non-empty catalog-zones statement implies allow-new-zones + */ + if (!allow) { + const cfg_obj_t *cz = NULL; + result = named_config_get(maps, "catalog-zones", &cz); + if (result == ISC_R_SUCCESS) { + const cfg_listelt_t *e = + cfg_list_first(cfg_tuple_get(cz, "zone list")); + if (e != NULL) { + allow = true; + } + } + } + + if (!allow) { + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + if (num_zones != NULL) { + *num_zones = 0; + } + return (ISC_R_SUCCESS); + } + + nzcfg = isc_mem_get(view->mctx, sizeof(*nzcfg)); + + /* + * We attach the parser that was used for config as well + * as the one that will be used for added zones, to avoid + * a shutdown race later. + */ + memset(nzcfg, 0, sizeof(*nzcfg)); + cfg_parser_attach(conf_parser, &nzcfg->conf_parser); + cfg_parser_attach(named_g_addparser, &nzcfg->add_parser); + isc_mem_attach(view->mctx, &nzcfg->mctx); + cfg_aclconfctx_attach(actx, &nzcfg->actx); + + result = dns_view_setnewzones(view, true, nzcfg, newzone_cfgctx_destroy, + mapsize); + if (result != ISC_R_SUCCESS) { + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + return (result); + } + + cfg_obj_attach(config, &nzcfg->config); + if (vconfig != NULL) { + cfg_obj_attach(vconfig, &nzcfg->vconfig); + } + + result = count_newzones(view, nzcfg, num_zones); + return (result); +} + +static void +configure_zone_setviewcommit(isc_result_t result, const cfg_obj_t *zconfig, + dns_view_t *view) { + const char *zname; + dns_fixedname_t fixorigin; + dns_name_t *origin; + isc_result_t result2; + dns_view_t *pview = NULL; + dns_zone_t *zone = NULL; + + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + origin = dns_fixedname_initname(&fixorigin); + + result2 = dns_name_fromstring(origin, zname, 0, NULL); + if (result2 != ISC_R_SUCCESS) { + return; + } + + result2 = dns_viewlist_find(&named_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result2 != ISC_R_SUCCESS) { + return; + } + + result2 = dns_view_findzone(pview, origin, &zone); + if (result2 != ISC_R_SUCCESS) { + dns_view_detach(&pview); + return; + } + + if (result == ISC_R_SUCCESS) { + dns_zone_setviewcommit(zone); + } else { + dns_zone_setviewrevert(zone); + } + + dns_zone_detach(&zone); + dns_view_detach(&pview); +} + +#ifndef HAVE_LMDB + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx) { + isc_result_t result; + ns_cfgctx_t *nzctx; + const cfg_obj_t *zonelist; + const cfg_listelt_t *element; + + nzctx = view->new_zone_config; + if (nzctx == NULL || nzctx->nzf_config == NULL) { + return (ISC_R_SUCCESS); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading additional zones for view '%s'", view->name); + + zonelist = NULL; + cfg_map_get(nzctx->nzf_config, "zone", &zonelist); + + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(element); + CHECK(configure_zone(config, zconfig, vconfig, mctx, view, + &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, + false, false)); + } + + result = ISC_R_SUCCESS; + +cleanup: + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(element); + configure_zone_setviewcommit(result, zconfig, view); + } + + return (result); +} + +#else /* HAVE_LMDB */ + +static isc_result_t +data_to_cfg(dns_view_t *view, MDB_val *key, MDB_val *data, isc_buffer_t **text, + cfg_obj_t **zoneconfig) { + isc_result_t result; + const char *zone_name; + size_t zone_name_len; + const char *zone_config; + size_t zone_config_len; + cfg_obj_t *zoneconf = NULL; + char bufname[DNS_NAME_FORMATSIZE]; + + REQUIRE(view != NULL); + REQUIRE(key != NULL); + REQUIRE(data != NULL); + REQUIRE(text != NULL); + REQUIRE(zoneconfig != NULL && *zoneconfig == NULL); + + if (*text == NULL) { + isc_buffer_allocate(view->mctx, text, 256); + } else { + isc_buffer_clear(*text); + } + + zone_name = (const char *)key->mv_data; + zone_name_len = key->mv_size; + INSIST(zone_name != NULL && zone_name_len > 0); + + zone_config = (const char *)data->mv_data; + zone_config_len = data->mv_size; + INSIST(zone_config != NULL && zone_config_len > 0); + + /* zone zonename { config; }; */ + result = isc_buffer_reserve(text, 6 + zone_name_len + 2 + + zone_config_len + 2); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + CHECK(putstr(text, "zone \"")); + CHECK(putmem(text, (const void *)zone_name, zone_name_len)); + CHECK(putstr(text, "\" ")); + CHECK(putmem(text, (const void *)zone_config, zone_config_len)); + CHECK(putstr(text, ";\n")); + + snprintf(bufname, sizeof(bufname), "%.*s", (int)zone_name_len, + zone_name); + + cfg_parser_reset(named_g_addparser); + result = cfg_parse_buffer(named_g_addparser, *text, bufname, 0, + &cfg_type_addzoneconf, 0, &zoneconf); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "parsing config for zone '%.*s' in " + "NZD database '%s' failed", + (int)zone_name_len, zone_name, view->new_zone_db); + goto cleanup; + } + + *zoneconfig = zoneconf; + zoneconf = NULL; + result = ISC_R_SUCCESS; + +cleanup: + if (zoneconf != NULL) { + cfg_obj_destroy(named_g_addparser, &zoneconf); + } + + return (result); +} + +/*% + * Prototype for a callback which can be used with for_all_newzone_cfgs(). + */ +typedef isc_result_t (*newzone_cfg_cb_t)(const cfg_obj_t *zconfig, + cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx); + +/*% + * For each zone found in a NZD opened by the caller, create an object + * representing its configuration and invoke "callback" with the created + * object, "config", "vconfig", "mctx", "view" and "actx" as arguments (all + * these are non-global variables required to invoke configure_zone()). + * Immediately interrupt processing if an error is encountered while + * transforming NZD data into a zone configuration object or if "callback" + * returns an error. + * + * Caller must hold 'view->new_zone_lock'. + */ +static isc_result_t +for_all_newzone_cfgs(newzone_cfg_cb_t callback, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx, MDB_txn *txn, MDB_dbi dbi) { + const cfg_obj_t *zconfig, *zlist; + isc_result_t result = ISC_R_SUCCESS; + cfg_obj_t *zconfigobj = NULL; + isc_buffer_t *text = NULL; + MDB_cursor *cursor = NULL; + MDB_val data, key; + int status; + + status = mdb_cursor_open(txn, dbi, &cursor); + if (status != MDB_SUCCESS) { + return (ISC_R_FAILURE); + } + + for (status = mdb_cursor_get(cursor, &key, &data, MDB_FIRST); + status == MDB_SUCCESS; + status = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) + { + /* + * Create a configuration object from data fetched from NZD. + */ + result = data_to_cfg(view, &key, &data, &text, &zconfigobj); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Extract zone configuration from configuration object. + */ + zlist = NULL; + result = cfg_map_get(zconfigobj, "zone", &zlist); + if (result != ISC_R_SUCCESS) { + break; + } else if (!cfg_obj_islist(zlist)) { + result = ISC_R_FAILURE; + break; + } + zconfig = cfg_listelt_value(cfg_list_first(zlist)); + + /* + * Invoke callback. + */ + result = callback(zconfig, config, vconfig, mctx, view, actx); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Destroy the configuration object created in this iteration. + */ + cfg_obj_destroy(named_g_addparser, &zconfigobj); + } + + if (text != NULL) { + isc_buffer_free(&text); + } + if (zconfigobj != NULL) { + cfg_obj_destroy(named_g_addparser, &zconfigobj); + } + mdb_cursor_close(cursor); + + return (result); +} + +/*% + * Attempt to configure a zone found in NZD and return the result. + */ +static isc_result_t +configure_newzone(const cfg_obj_t *zconfig, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx) { + return (configure_zone( + config, zconfig, vconfig, mctx, view, &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, false, false)); +} + +/*% + * Revert new view assignment for a zone found in NZD. + */ +static isc_result_t +configure_newzone_revert(const cfg_obj_t *zconfig, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx) { + UNUSED(config); + UNUSED(vconfig); + UNUSED(mctx); + UNUSED(actx); + + configure_zone_setviewcommit(ISC_R_FAILURE, zconfig, view); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx) { + isc_result_t result; + MDB_txn *txn = NULL; + MDB_dbi dbi; + + if (view->new_zone_config == NULL) { + return (ISC_R_SUCCESS); + } + + LOCK(&view->new_zone_lock); + + result = nzd_open(view, MDB_RDONLY, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + UNLOCK(&view->new_zone_lock); + return (ISC_R_SUCCESS); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading NZD configs from '%s' " + "for view '%s'", + view->new_zone_db, view->name); + + result = for_all_newzone_cfgs(configure_newzone, config, vconfig, mctx, + view, actx, txn, dbi); + if (result != ISC_R_SUCCESS) { + /* + * An error was encountered while attempting to configure zones + * found in NZD. As this error may have been caused by a + * configure_zone() failure, try restoring a sane configuration + * by reattaching all zones found in NZD to the old view. If + * this also fails, too bad, there is nothing more we can do in + * terms of trying to make things right. + */ + (void)for_all_newzone_cfgs(configure_newzone_revert, config, + vconfig, mctx, view, actx, txn, dbi); + } + + (void)nzd_close(&txn, false); + + UNLOCK(&view->new_zone_lock); + + return (result); +} + +static isc_result_t +get_newzone_config(dns_view_t *view, const char *zonename, + cfg_obj_t **zoneconfig) { + isc_result_t result; + int status; + cfg_obj_t *zoneconf = NULL; + isc_buffer_t *text = NULL; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_val key, data; + char zname[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fname; + dns_name_t *name; + isc_buffer_t b; + + INSIST(zoneconfig != NULL && *zoneconfig == NULL); + + LOCK(&view->new_zone_lock); + + CHECK(nzd_open(view, MDB_RDONLY, &txn, &dbi)); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading NZD config from '%s' " + "for zone '%s'", + view->new_zone_db, zonename); + + /* Normalize zone name */ + isc_buffer_constinit(&b, zonename, strlen(zonename)); + isc_buffer_add(&b, strlen(zonename)); + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(name, &b, dns_rootname, DNS_NAME_DOWNCASE, + NULL)); + dns_name_format(name, zname, sizeof(zname)); + + key.mv_data = zname; + key.mv_size = strlen(zname); + + status = mdb_get(txn, dbi, &key, &data); + if (status != MDB_SUCCESS) { + CHECK(ISC_R_FAILURE); + } + + CHECK(data_to_cfg(view, &key, &data, &text, &zoneconf)); + + *zoneconfig = zoneconf; + zoneconf = NULL; + result = ISC_R_SUCCESS; + +cleanup: + (void)nzd_close(&txn, false); + + UNLOCK(&view->new_zone_lock); + + if (zoneconf != NULL) { + cfg_obj_destroy(named_g_addparser, &zoneconf); + } + if (text != NULL) { + isc_buffer_free(&text); + } + + return (result); +} + +#endif /* HAVE_LMDB */ + +static int +count_zones(const cfg_obj_t *conf) { + const cfg_obj_t *zonelist = NULL; + const cfg_listelt_t *element; + int n = 0; + + REQUIRE(conf != NULL); + + cfg_map_get(conf, "zone", &zonelist); + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + n++; + } + + return (n); +} + +static isc_result_t +check_lockfile(named_server_t *server, const cfg_obj_t *config, + bool first_time) { + isc_result_t result; + const char *filename = NULL; + const cfg_obj_t *maps[3]; + const cfg_obj_t *options; + const cfg_obj_t *obj; + int i; + + i = 0; + options = NULL; + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) { + maps[i++] = options; + } + maps[i++] = named_g_defaults; + maps[i] = NULL; + + obj = NULL; + (void)named_config_get(maps, "lock-file", &obj); + + if (!first_time) { + if (obj != NULL && !cfg_obj_isstring(obj) && + server->lockfile != NULL && + strcmp(cfg_obj_asstring(obj), server->lockfile) != 0) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "changing 'lock-file' " + "has no effect until the " + "server is restarted"); + } + + return (ISC_R_SUCCESS); + } + + if (obj != NULL) { + if (cfg_obj_isvoid(obj)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "skipping lock-file check "); + return (ISC_R_SUCCESS); + } else if (named_g_forcelock) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "'lock-file' has no effect " + "because the server was run with -X"); + server->lockfile = isc_mem_strdup( + server->mctx, named_g_defaultlockfile); + } else { + filename = cfg_obj_asstring(obj); + server->lockfile = isc_mem_strdup(server->mctx, + filename); + } + + if (server->lockfile == NULL) { + return (ISC_R_NOMEMORY); + } + } + + if (named_g_forcelock && named_g_defaultlockfile != NULL) { + INSIST(server->lockfile == NULL); + server->lockfile = isc_mem_strdup(server->mctx, + named_g_defaultlockfile); + } + + if (server->lockfile == NULL) { + return (ISC_R_SUCCESS); + } + + if (named_os_issingleton(server->lockfile)) { + return (ISC_R_SUCCESS); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not lock %s; another named " + "process may be running", + server->lockfile); + return (ISC_R_FAILURE); +} + +static isc_result_t +load_configuration(const char *filename, named_server_t *server, + bool first_time) { + cfg_obj_t *config = NULL, *bindkeys = NULL; + cfg_parser_t *conf_parser = NULL, *bindkeys_parser = NULL; + const cfg_listelt_t *element; + const cfg_obj_t *builtin_views; + const cfg_obj_t *maps[3]; + const cfg_obj_t *obj; + const cfg_obj_t *options; + const cfg_obj_t *usev4ports, *avoidv4ports, *usev6ports, *avoidv6ports; + const cfg_obj_t *kasps; + dns_kasp_t *kasp = NULL; + dns_kasp_t *kasp_next = NULL; + dns_kasp_t *default_kasp = NULL; + dns_kasplist_t tmpkasplist, kasplist; + const cfg_obj_t *views; + dns_view_t *view = NULL; + dns_view_t *view_next = NULL; + dns_viewlist_t tmpviewlist; + dns_viewlist_t viewlist, builtin_viewlist; + in_port_t listen_port, udpport_low, udpport_high; + int i, backlog; + int num_zones = 0; + bool exclusive = false; + isc_interval_t interval; + isc_logconfig_t *logc = NULL; + isc_portset_t *v4portset = NULL; + isc_portset_t *v6portset = NULL; + isc_result_t result, tresult; + uint32_t heartbeat_interval; + uint32_t interface_interval; + uint32_t udpsize; + uint32_t transfer_message_size; + uint32_t recv_tcp_buffer_size; + uint32_t send_tcp_buffer_size; + uint32_t recv_udp_buffer_size; + uint32_t send_udp_buffer_size; + named_cache_t *nsc; + named_cachelist_t cachelist, tmpcachelist; + ns_altsecret_t *altsecret; + ns_altsecretlist_t altsecrets, tmpaltsecrets; + uint32_t softquota = 0; + uint32_t max; + uint64_t initial, idle, keepalive, advertised; + bool loadbalancesockets; + dns_aclenv_t *env = + ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + + ISC_LIST_INIT(kasplist); + ISC_LIST_INIT(viewlist); + ISC_LIST_INIT(builtin_viewlist); + ISC_LIST_INIT(cachelist); + ISC_LIST_INIT(altsecrets); + + /* Create the ACL configuration context */ + if (named_g_aclconfctx != NULL) { + cfg_aclconfctx_detach(&named_g_aclconfctx); + } + CHECK(cfg_aclconfctx_create(named_g_mctx, &named_g_aclconfctx)); + + /* + * Shut down all dyndb instances. + */ + dns_dyndb_cleanup(false); + + /* + * Parse the global default pseudo-config file. + */ + if (first_time) { + result = named_config_parsedefaults(named_g_parser, + &named_g_config); + if (result != ISC_R_SUCCESS) { + named_main_earlyfatal("unable to load " + "internal defaults: %s", + isc_result_totext(result)); + } + RUNTIME_CHECK(cfg_map_get(named_g_config, "options", + &named_g_defaults) == ISC_R_SUCCESS); + } + + /* + * Parse the configuration file using the new config code. + */ + config = NULL; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading configuration from '%s'", filename); + CHECK(cfg_parser_create(named_g_mctx, named_g_lctx, &conf_parser)); + cfg_parser_setcallback(conf_parser, directory_callback, NULL); + result = cfg_parse_file(conf_parser, filename, &cfg_type_namedconf, + &config); + + CHECK(result); + + /* + * Check the validity of the configuration. + * + * (Ignore plugin parameters for now; they will be + * checked later when the modules are actually loaded and + * registered.) + */ + CHECK(bind9_check_namedconf(config, false, false, named_g_lctx, + named_g_mctx)); + + /* Let's recreate the TLS context cache */ + if (server->tlsctx_server_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_server_cache); + } + + isc_tlsctx_cache_create(named_g_mctx, &server->tlsctx_server_cache); + + if (server->tlsctx_client_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_client_cache); + } + + isc_tlsctx_cache_create(named_g_mctx, &server->tlsctx_client_cache); + + dns_zonemgr_set_tlsctx_cache(server->zonemgr, + server->tlsctx_client_cache); + + /* + * Fill in the maps array, used for resolving defaults. + */ + i = 0; + options = NULL; + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) { + maps[i++] = options; + } + maps[i++] = named_g_defaults; + maps[i] = NULL; + +#if HAVE_LIBNGHTTP2 + obj = NULL; + result = named_config_get(maps, "http-port", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_httpport = (in_port_t)cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "https-port", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_httpsport = (in_port_t)cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "http-listener-clients", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_http_listener_clients = cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "http-streams-per-connection", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_http_streams_per_conn = cfg_obj_asuint32(obj); +#endif + + /* + * If bind.keys exists, load it. If "dnssec-validation auto" + * is turned on, the root key found there will be used as a + * default trust anchor. + */ + obj = NULL; + result = named_config_get(maps, "bindkeys-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->bindkeysfile, cfg_obj_asstring(obj)), + "strdup"); + INSIST(server->bindkeysfile != NULL); + + if (access(server->bindkeysfile, R_OK) == 0) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "reading built-in trust anchors " + "from file '%s'", + server->bindkeysfile); + + CHECK(cfg_parser_create(named_g_mctx, named_g_lctx, + &bindkeys_parser)); + + result = cfg_parse_file(bindkeys_parser, server->bindkeysfile, + &cfg_type_bindkeys, &bindkeys); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "unable to parse '%s' error '%s'; using " + "built-in keys instead", + server->bindkeysfile, + isc_result_totext(result)); + } + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "unable to open '%s'; using built-in keys " + "instead", + server->bindkeysfile); + } + + /* Ensure exclusive access to configuration data. */ + if (!exclusive) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + } + + /* + * Set process limits, which (usually) needs to be done as root. + */ + set_limits(maps); + + /* + * Check the process lockfile. + */ + CHECK(check_lockfile(server, config, first_time)); + +#if defined(HAVE_GEOIP2) + /* + * Release any previously opened GeoIP2 databases. + */ + named_geoip_unload(); + + /* + * Initialize GeoIP databases from the configured location. + * This should happen before configuring any ACLs, so that we + * know what databases are available and can reject any GeoIP + * ACLs that can't work. + */ + obj = NULL; + result = named_config_get(maps, "geoip-directory", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isstring(obj)) { + char *dir; + DE_CONST(cfg_obj_asstring(obj), dir); + named_geoip_load(dir); + } + named_g_aclconfctx->geoip = named_g_geoip; +#endif /* HAVE_GEOIP2 */ + + /* + * Configure various server options. + */ + configure_server_quota(maps, "transfers-out", + &server->sctx->xfroutquota); + configure_server_quota(maps, "tcp-clients", &server->sctx->tcpquota); + configure_server_quota(maps, "recursive-clients", + &server->sctx->recursionquota); + configure_server_quota(maps, "update-quota", &server->sctx->updquota); + + max = isc_quota_getmax(&server->sctx->recursionquota); + if (max > 1000) { + unsigned margin = ISC_MAX(100, named_g_cpus + 1); + if (margin + 100 > max) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "'recursive-clients %d' too low when " + "running with %d worker threads", + max, named_g_cpus); + CHECK(ISC_R_RANGE); + } + softquota = max - margin; + } else { + softquota = (max * 90) / 100; + } + + isc_quota_soft(&server->sctx->recursionquota, softquota); + + /* + * Set "blackhole". Only legal at options level; there is + * no default. + */ + CHECK(configure_view_acl(NULL, config, NULL, "blackhole", NULL, + named_g_aclconfctx, named_g_mctx, + &server->sctx->blackholeacl)); + if (server->sctx->blackholeacl != NULL) { + dns_dispatchmgr_setblackhole(named_g_dispatchmgr, + server->sctx->blackholeacl); + } + + /* + * Set "keep-response-order". Only legal at options or + * global defaults level. + */ + CHECK(configure_view_acl(NULL, config, named_g_config, + "keep-response-order", NULL, + named_g_aclconfctx, named_g_mctx, + &server->sctx->keepresporder)); + + obj = NULL; + result = named_config_get(maps, "match-mapped-addresses", &obj); + INSIST(result == ISC_R_SUCCESS); + env->match_mapped = cfg_obj_asboolean(obj); + + CHECKM(named_statschannels_configure(named_g_server, config, + named_g_aclconfctx), + "configuring statistics server(s)"); + + /* + * Configure the network manager + */ + obj = NULL; + result = named_config_get(maps, "tcp-initial-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + initial = cfg_obj_asuint32(obj) * 100; + if (initial > MAX_INITIAL_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-initial-timeout value is out of range: " + "lowering to %" PRIu32, + MAX_INITIAL_TIMEOUT / 100); + initial = MAX_INITIAL_TIMEOUT; + } else if (initial < MIN_INITIAL_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-initial-timeout value is out of range: " + "raising to %" PRIu32, + MIN_INITIAL_TIMEOUT / 100); + initial = MIN_INITIAL_TIMEOUT; + } + + obj = NULL; + result = named_config_get(maps, "tcp-idle-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + idle = cfg_obj_asuint32(obj) * 100; + if (idle > MAX_IDLE_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-idle-timeout value is out of range: " + "lowering to %" PRIu32, + MAX_IDLE_TIMEOUT / 100); + idle = MAX_IDLE_TIMEOUT; + } else if (idle < MIN_IDLE_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-idle-timeout value is out of range: " + "raising to %" PRIu32, + MIN_IDLE_TIMEOUT / 100); + idle = MIN_IDLE_TIMEOUT; + } + + obj = NULL; + result = named_config_get(maps, "tcp-keepalive-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + keepalive = cfg_obj_asuint32(obj) * 100; + if (keepalive > MAX_KEEPALIVE_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-keepalive-timeout value is out of range: " + "lowering to %" PRIu32, + MAX_KEEPALIVE_TIMEOUT / 100); + keepalive = MAX_KEEPALIVE_TIMEOUT; + } else if (keepalive < MIN_KEEPALIVE_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-keepalive-timeout value is out of range: " + "raising to %" PRIu32, + MIN_KEEPALIVE_TIMEOUT / 100); + keepalive = MIN_KEEPALIVE_TIMEOUT; + } + + obj = NULL; + result = named_config_get(maps, "tcp-advertised-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + advertised = cfg_obj_asuint32(obj) * 100; + if (advertised > MAX_ADVERTISED_TIMEOUT) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "tcp-advertized-timeout value is out of range: " + "lowering to %" PRIu32, + MAX_ADVERTISED_TIMEOUT / 100); + advertised = MAX_ADVERTISED_TIMEOUT; + } + + isc_nm_settimeouts(named_g_netmgr, initial, idle, keepalive, + advertised); + +#define CAP_IF_NOT_ZERO(v, min, max) \ + if (v > 0 && v < min) { \ + v = min; \ + } else if (v > max) { \ + v = max; \ + } + + /* Set the kernel send and receive buffer sizes */ + obj = NULL; + result = named_config_get(maps, "tcp-receive-buffer", &obj); + INSIST(result == ISC_R_SUCCESS); + recv_tcp_buffer_size = cfg_obj_asuint32(obj); + CAP_IF_NOT_ZERO(recv_tcp_buffer_size, 4096, INT32_MAX); + + obj = NULL; + result = named_config_get(maps, "tcp-send-buffer", &obj); + INSIST(result == ISC_R_SUCCESS); + send_tcp_buffer_size = cfg_obj_asuint32(obj); + CAP_IF_NOT_ZERO(send_tcp_buffer_size, 4096, INT32_MAX); + + obj = NULL; + result = named_config_get(maps, "udp-receive-buffer", &obj); + INSIST(result == ISC_R_SUCCESS); + recv_udp_buffer_size = cfg_obj_asuint32(obj); + CAP_IF_NOT_ZERO(recv_udp_buffer_size, 4096, INT32_MAX); + + obj = NULL; + result = named_config_get(maps, "udp-send-buffer", &obj); + INSIST(result == ISC_R_SUCCESS); + send_udp_buffer_size = cfg_obj_asuint32(obj); + CAP_IF_NOT_ZERO(send_udp_buffer_size, 4096, INT32_MAX); + + isc_nm_setnetbuffers(named_g_netmgr, recv_tcp_buffer_size, + send_tcp_buffer_size, recv_udp_buffer_size, + send_udp_buffer_size); + +#undef CAP_IF_NOT_ZERO + + /* + * Configure sets of UDP query source ports. + */ + CHECKM(isc_portset_create(named_g_mctx, &v4portset), "creating UDP " + "port set"); + CHECKM(isc_portset_create(named_g_mctx, &v6portset), "creating UDP " + "port set"); + + usev4ports = NULL; + usev6ports = NULL; + avoidv4ports = NULL; + avoidv6ports = NULL; + + (void)named_config_get(maps, "use-v4-udp-ports", &usev4ports); + if (usev4ports != NULL) { + portset_fromconf(v4portset, usev4ports, true); + } else { + CHECKM(isc_net_getudpportrange(AF_INET, &udpport_low, + &udpport_high), + "get the default UDP/IPv4 port range"); + if (udpport_low == udpport_high) { + isc_portset_add(v4portset, udpport_low); + } else { + isc_portset_addrange(v4portset, udpport_low, + udpport_high); + } + if (!ns_server_getoption(server->sctx, NS_SERVER_DISABLE4)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "using default UDP/IPv4 port range: " + "[%d, %d]", + udpport_low, udpport_high); + } + } + (void)named_config_get(maps, "avoid-v4-udp-ports", &avoidv4ports); + if (avoidv4ports != NULL) { + portset_fromconf(v4portset, avoidv4ports, false); + } + + (void)named_config_get(maps, "use-v6-udp-ports", &usev6ports); + if (usev6ports != NULL) { + portset_fromconf(v6portset, usev6ports, true); + } else { + CHECKM(isc_net_getudpportrange(AF_INET6, &udpport_low, + &udpport_high), + "get the default UDP/IPv6 port range"); + if (udpport_low == udpport_high) { + isc_portset_add(v6portset, udpport_low); + } else { + isc_portset_addrange(v6portset, udpport_low, + udpport_high); + } + if (!ns_server_getoption(server->sctx, NS_SERVER_DISABLE6)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "using default UDP/IPv6 port range: " + "[%d, %d]", + udpport_low, udpport_high); + } + } + (void)named_config_get(maps, "avoid-v6-udp-ports", &avoidv6ports); + if (avoidv6ports != NULL) { + portset_fromconf(v6portset, avoidv6ports, false); + } + + dns_dispatchmgr_setavailports(named_g_dispatchmgr, v4portset, + v6portset); + + /* + * Set the EDNS UDP size when we don't match a view. + */ + obj = NULL; + result = named_config_get(maps, "edns-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) { + udpsize = 512; + } + if (udpsize > 4096) { + udpsize = 4096; + } + server->sctx->udpsize = (uint16_t)udpsize; + + /* Set the transfer message size for TCP */ + obj = NULL; + result = named_config_get(maps, "transfer-message-size", &obj); + INSIST(result == ISC_R_SUCCESS); + transfer_message_size = cfg_obj_asuint32(obj); + if (transfer_message_size < 512) { + transfer_message_size = 512; + } else if (transfer_message_size > 65535) { + transfer_message_size = 65535; + } + server->sctx->transfer_tcp_message_size = + (uint16_t)transfer_message_size; + + /* + * Configure the zone manager. + */ + obj = NULL; + result = named_config_get(maps, "transfers-in", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_settransfersin(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "transfers-per-ns", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_settransfersperns(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "notify-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setnotifyrate(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "startup-notify-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setstartupnotifyrate(server->zonemgr, + cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "serial-query-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setserialqueryrate(server->zonemgr, cfg_obj_asuint32(obj)); + + /* + * Determine which port to use for listening for incoming connections. + */ + if (named_g_port != 0) { + listen_port = named_g_port; + } else { + CHECKM(named_config_getport(config, "port", &listen_port), + "port"); + } + + /* + * Find the listen queue depth. + */ + obj = NULL; + result = named_config_get(maps, "tcp-listen-queue", &obj); + INSIST(result == ISC_R_SUCCESS); + backlog = cfg_obj_asuint32(obj); + if ((backlog > 0) && (backlog < 10)) { + backlog = 10; + } + ns_interfacemgr_setbacklog(server->interfacemgr, backlog); + + obj = NULL; + result = named_config_get(maps, "reuseport", &obj); + INSIST(result == ISC_R_SUCCESS); + loadbalancesockets = cfg_obj_asboolean(obj); +#if HAVE_SO_REUSEPORT_LB + if (first_time) { + isc_nm_setloadbalancesockets(named_g_netmgr, + cfg_obj_asboolean(obj)); + } else if (loadbalancesockets != + isc_nm_getloadbalancesockets(named_g_netmgr)) + { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "changing reuseport value requires server restart"); + } +#else + if (loadbalancesockets) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING, + "reuseport has no effect on this system"); + } +#endif + + /* + * Configure the interface manager according to the "listen-on" + * statement. + */ + { + const cfg_obj_t *clistenon = NULL; + ns_listenlist_t *listenon = NULL; + + clistenon = NULL; + /* + * Even though listen-on is present in the default + * configuration, this way is easier. + */ + if (options != NULL) { + (void)cfg_map_get(options, "listen-on", &clistenon); + } + if (clistenon != NULL) { + CHECK(listenlist_fromconfig( + clistenon, config, named_g_aclconfctx, + named_g_mctx, AF_INET, + server->tlsctx_server_cache, &listenon)); + } else { + /* + * Not specified, use default. + */ + CHECK(ns_listenlist_default(named_g_mctx, listen_port, + true, AF_INET, &listenon)); + } + if (listenon != NULL) { + ns_interfacemgr_setlistenon4(server->interfacemgr, + listenon); + ns_listenlist_detach(&listenon); + } + } + /* + * Ditto for IPv6. + */ + { + const cfg_obj_t *clistenon = NULL; + ns_listenlist_t *listenon = NULL; + + if (options != NULL) { + (void)cfg_map_get(options, "listen-on-v6", &clistenon); + } + if (clistenon != NULL) { + CHECK(listenlist_fromconfig( + clistenon, config, named_g_aclconfctx, + named_g_mctx, AF_INET6, + server->tlsctx_server_cache, &listenon)); + } else { + /* + * Not specified, use default. + */ + CHECK(ns_listenlist_default(named_g_mctx, listen_port, + true, AF_INET6, &listenon)); + } + if (listenon != NULL) { + ns_interfacemgr_setlistenon6(server->interfacemgr, + listenon); + ns_listenlist_detach(&listenon); + } + } + + /* + * Rescan the interface list to pick up changes in the + * listen-on option. It's important that we do this before we try + * to configure the query source, since the dispatcher we use might + * be shared with an interface. + */ + result = ns_interfacemgr_scan(server->interfacemgr, true, true); + + /* + * Check that named is able to TCP listen on at least one + * interface. Otherwise, another named process could be running + * and we should fail. + */ + if (first_time && (result == ISC_R_ADDRINUSE)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to listen on any configured interfaces"); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Arrange for further interface scanning to occur periodically + * as specified by the "interface-interval" option. + */ + obj = NULL; + result = named_config_get(maps, "interface-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + interface_interval = cfg_obj_asduration(obj); + if (interface_interval == 0) { + CHECK(isc_timer_reset(server->interface_timer, + isc_timertype_inactive, NULL, NULL, + true)); + } else if (server->interface_interval != interface_interval) { + isc_interval_set(&interval, interface_interval, 0); + CHECK(isc_timer_reset(server->interface_timer, + isc_timertype_ticker, NULL, &interval, + false)); + } + server->interface_interval = interface_interval; + + /* + * Enable automatic interface scans. + */ + obj = NULL; + result = named_config_get(maps, "automatic-interface-scan", &obj); + INSIST(result == ISC_R_SUCCESS); + server->sctx->interface_auto = cfg_obj_asboolean(obj); + + /* + * Configure the dialup heartbeat timer. + */ + obj = NULL; + result = named_config_get(maps, "heartbeat-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + heartbeat_interval = cfg_obj_asuint32(obj) * 60; + if (heartbeat_interval == 0) { + CHECK(isc_timer_reset(server->heartbeat_timer, + isc_timertype_inactive, NULL, NULL, + true)); + } else if (server->heartbeat_interval != heartbeat_interval) { + isc_interval_set(&interval, heartbeat_interval, 0); + CHECK(isc_timer_reset(server->heartbeat_timer, + isc_timertype_ticker, NULL, &interval, + false)); + } + server->heartbeat_interval = heartbeat_interval; + + isc_interval_set(&interval, 1200, 0); + CHECK(isc_timer_reset(server->pps_timer, isc_timertype_ticker, NULL, + &interval, false)); + + isc_interval_set(&interval, named_g_tat_interval, 0); + CHECK(isc_timer_reset(server->tat_timer, isc_timertype_ticker, NULL, + &interval, false)); + + /* + * Write the PID file. + */ + obj = NULL; + if (named_config_get(maps, "pid-file", &obj) == ISC_R_SUCCESS) { + if (cfg_obj_isvoid(obj)) { + named_os_writepidfile(NULL, first_time); + } else { + named_os_writepidfile(cfg_obj_asstring(obj), + first_time); + } + } else { + named_os_writepidfile(named_g_defaultpidfile, first_time); + } + + /* + * Configure the server-wide session key. This must be done before + * configure views because zone configuration may need to know + * session-keyname. + * + * Failure of session key generation isn't fatal at this time; if it + * turns out that a session key is really needed but doesn't exist, + * we'll treat it as a fatal error then. + */ + (void)configure_session_key(maps, server, named_g_mctx, first_time); + + /* + * Create the built-in kasp policies ("default", "insecure"). + */ + kasps = NULL; + (void)cfg_map_get(named_g_config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + + kasp = NULL; + CHECK(cfg_kasp_fromconfig(kconfig, default_kasp, named_g_mctx, + named_g_lctx, &kasplist, &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + + /* Insist that the first built-in policy is the default one. */ + if (default_kasp == NULL) { + INSIST(strcmp(dns_kasp_getname(kasp), "default") == 0); + dns_kasp_attach(kasp, &default_kasp); + } + + dns_kasp_detach(&kasp); + } + INSIST(default_kasp != NULL); + + /* + * Create the DNSSEC key and signing policies (KASP). + */ + kasps = NULL; + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + CHECK(cfg_kasp_fromconfig(kconfig, default_kasp, named_g_mctx, + named_g_lctx, &kasplist, &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + dns_kasp_detach(&kasp); + } + + dns_kasp_detach(&default_kasp); + tmpkasplist = server->kasplist; + server->kasplist = kasplist; + kasplist = tmpkasplist; + + /* + * Configure the views. + */ + views = NULL; + (void)cfg_map_get(config, "view", &views); + + /* + * Create the views and count all the configured zones in + * order to correctly size the zone manager's task table. + * (We only count zones for configured views; the built-in + * "bind" view can be ignored as it only adds a negligible + * number of zones.) + * + * If we're allowing new zones, we need to be able to find the + * new zone file and count those as well. So we setup the new + * zone configuration context, but otherwise view configuration + * waits until after the zone manager's task list has been sized. + */ + for (element = cfg_list_first(views); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + const cfg_obj_t *voptions = cfg_tuple_get(vconfig, "options"); + int nzf_num_zones; + + view = NULL; + + CHECK(create_view(vconfig, &viewlist, &view)); + INSIST(view != NULL); + + num_zones += count_zones(voptions); + + CHECK(setup_newzones(view, config, vconfig, conf_parser, + named_g_aclconfctx, &nzf_num_zones)); + num_zones += nzf_num_zones; + + dns_view_detach(&view); + } + + /* + * If there were no explicit views then we do the default + * view here. + */ + if (views == NULL) { + int nzf_num_zones; + + CHECK(create_view(NULL, &viewlist, &view)); + INSIST(view != NULL); + + num_zones = count_zones(config); + + CHECK(setup_newzones(view, config, NULL, conf_parser, + named_g_aclconfctx, &nzf_num_zones)); + num_zones += nzf_num_zones; + + dns_view_detach(&view); + } + + /* + * Zones have been counted; set the zone manager task pool size. + */ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "sizing zone task pool based on %d zones", num_zones); + CHECK(dns_zonemgr_setsize(named_g_server->zonemgr, num_zones)); + + /* + * Configure and freeze all explicit views. Explicit + * views that have zones were already created at parsing + * time, but views with no zones must be created here. + */ + for (element = cfg_list_first(views); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + + view = NULL; + CHECK(find_view(vconfig, &viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, vconfig, + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, true)); + dns_view_freeze(view); + dns_view_detach(&view); + } + + /* + * Make sure we have a default view if and only if there + * were no explicit views. + */ + if (views == NULL) { + view = NULL; + CHECK(find_view(NULL, &viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, NULL, &cachelist, + &server->kasplist, bindkeys, named_g_mctx, + named_g_aclconfctx, true)); + dns_view_freeze(view); + dns_view_detach(&view); + } + + /* + * Create (or recreate) the built-in views. + */ + builtin_views = NULL; + RUNTIME_CHECK(cfg_map_get(named_g_config, "view", &builtin_views) == + ISC_R_SUCCESS); + for (element = cfg_list_first(builtin_views); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + + CHECK(create_view(vconfig, &builtin_viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, vconfig, + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, false)); + dns_view_freeze(view); + dns_view_detach(&view); + view = NULL; + } + + /* Now combine the two viewlists into one */ + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); + + /* + * Commit any dns_zone_setview() calls on all zones in the new + * view. + */ + for (view = ISC_LIST_HEAD(viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + dns_view_setviewcommit(view); + } + + /* Swap our new view list with the production one. */ + tmpviewlist = server->viewlist; + server->viewlist = viewlist; + viewlist = tmpviewlist; + + /* Make the view list available to each of the views */ + view = ISC_LIST_HEAD(server->viewlist); + while (view != NULL) { + view->viewlist = &server->viewlist; + view = ISC_LIST_NEXT(view, link); + } + + /* Swap our new cache list with the production one. */ + tmpcachelist = server->cachelist; + server->cachelist = cachelist; + cachelist = tmpcachelist; + + /* Load the TKEY information from the configuration. */ + if (options != NULL) { + dns_tkeyctx_t *t = NULL; + CHECKM(named_tkeyctx_fromconfig(options, named_g_mctx, &t), + "configuring TKEY"); + if (server->sctx->tkeyctx != NULL) { + dns_tkeyctx_destroy(&server->sctx->tkeyctx); + } + server->sctx->tkeyctx = t; + } + + /* + * Bind the control port(s). + */ + CHECKM(named_controls_configure(named_g_server->controls, config, + named_g_aclconfctx), + "binding control channel(s)"); + +#ifdef HAVE_LMDB + /* + * If we're using LMDB, we may have created newzones databases + * as root, making it impossible to reopen them later after + * switching to a new userid. We close them now, and reopen + * after relinquishing privileges them. + */ + if (first_time) { + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + nzd_env_close(view); + } + } +#endif /* HAVE_LMDB */ + + /* + * Relinquish root privileges. + */ + if (first_time) { + named_os_changeuser(); + } + + /* + * Check that the working directory is writable. + */ + if (!isc_file_isdirwritable(".")) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "the working directory is not writable"); + result = ISC_R_NOPERM; + goto cleanup; + } + +#ifdef HAVE_LMDB + /* + * Reopen NZD databases. + */ + if (first_time) { + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + nzd_env_reopen(view); + } + } +#endif /* HAVE_LMDB */ + + /* + * Configure the logging system. + * + * Do this after changing UID to make sure that any log + * files specified in named.conf get created by the + * unprivileged user, not root. + */ + if (named_g_logstderr) { + const cfg_obj_t *logobj = NULL; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "not using config file logging " + "statement for logging due to " + "-g option"); + + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) { + result = named_logconfig(NULL, logobj); + if (result != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "checking logging configuration " + "failed: %s", + isc_result_totext(result)); + goto cleanup; + } + } + } else { + const cfg_obj_t *logobj = NULL; + + isc_logconfig_create(named_g_lctx, &logc); + + logobj = NULL; + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) { + CHECKM(named_logconfig(logc, logobj), + "configuring logging"); + } else { + named_log_setdefaultchannels(logc); + named_log_setdefaultsslkeylogfile(logc); + CHECKM(named_log_setunmatchedcategory(logc), + "setting up default 'category unmatched'"); + CHECKM(named_log_setdefaultcategory(logc), + "setting up default 'category default'"); + } + + isc_logconfig_use(named_g_lctx, logc); + logc = NULL; + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "now using logging configuration from " + "config file"); + } + + /* + * Set the default value of the query logging flag depending + * whether a "queries" category has been defined. This is + * a disgusting hack, but we need to do this for BIND 8 + * compatibility. + */ + if (first_time) { + const cfg_obj_t *logobj = NULL; + const cfg_obj_t *categories = NULL; + + obj = NULL; + if (named_config_get(maps, "querylog", &obj) == ISC_R_SUCCESS) { + ns_server_setoption(server->sctx, NS_SERVER_LOGQUERIES, + cfg_obj_asboolean(obj)); + } else { + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) { + (void)cfg_map_get(logobj, "category", + &categories); + } + if (categories != NULL) { + for (element = cfg_list_first(categories); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *catobj; + const char *str; + + obj = cfg_listelt_value(element); + catobj = cfg_tuple_get(obj, "name"); + str = cfg_obj_asstring(catobj); + if (strcasecmp(str, "queries") == 0) { + ns_server_setoption( + server->sctx, + NS_SERVER_LOGQUERIES, + true); + } + } + } + } + } + + obj = NULL; + if (options != NULL && + cfg_map_get(options, "memstatistics", &obj) == ISC_R_SUCCESS) + { + named_g_memstatistics = cfg_obj_asboolean(obj); + } else { + named_g_memstatistics = + ((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0); + } + + obj = NULL; + if (named_config_get(maps, "memstatistics-file", &obj) == ISC_R_SUCCESS) + { + named_main_setmemstats(cfg_obj_asstring(obj)); + } else if (named_g_memstatistics) { + named_main_setmemstats("named.memstats"); + } else { + named_main_setmemstats(NULL); + } + + obj = NULL; + result = named_config_get(maps, "statistics-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->statsfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; + result = named_config_get(maps, "dump-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->dumpfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; + result = named_config_get(maps, "secroots-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->secrootsfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; + result = named_config_get(maps, "recursing-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->recfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; + result = named_config_get(maps, "version", &obj); + if (result == ISC_R_SUCCESS) { + CHECKM(setoptstring(server, &server->version, obj), "strdup"); + server->version_set = true; + } else { + server->version_set = false; + } + + obj = NULL; + result = named_config_get(maps, "hostname", &obj); + if (result == ISC_R_SUCCESS) { + CHECKM(setoptstring(server, &server->hostname, obj), "strdup"); + server->hostname_set = true; + } else { + server->hostname_set = false; + } + + obj = NULL; + result = named_config_get(maps, "server-id", &obj); + server->sctx->usehostname = false; + if (result == ISC_R_SUCCESS && cfg_obj_isboolean(obj)) { + /* The parser translates "hostname" to true */ + server->sctx->usehostname = true; + result = ns_server_setserverid(server->sctx, NULL); + } else if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + /* Found a quoted string */ + result = ns_server_setserverid(server->sctx, + cfg_obj_asstring(obj)); + } else { + result = ns_server_setserverid(server->sctx, NULL); + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + obj = NULL; + result = named_config_get(maps, "flush-zones-on-shutdown", &obj); + if (result == ISC_R_SUCCESS) { + server->flushonshutdown = cfg_obj_asboolean(obj); + } else { + server->flushonshutdown = false; + } + + obj = NULL; + result = named_config_get(maps, "answer-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + server->sctx->answercookie = cfg_obj_asboolean(obj); + + obj = NULL; + result = named_config_get(maps, "cookie-algorithm", &obj); + INSIST(result == ISC_R_SUCCESS); + if (strcasecmp(cfg_obj_asstring(obj), "siphash24") == 0) { + server->sctx->cookiealg = ns_cookiealg_siphash24; + } else if (strcasecmp(cfg_obj_asstring(obj), "aes") == 0) { + server->sctx->cookiealg = ns_cookiealg_aes; + } else { + UNREACHABLE(); + } + + obj = NULL; + result = named_config_get(maps, "cookie-secret", &obj); + if (result == ISC_R_SUCCESS) { + const char *str; + bool first = true; + isc_buffer_t b; + unsigned int usedlength; + unsigned int expectedlength; + + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + + if (first) { + memset(server->sctx->secret, 0, + sizeof(server->sctx->secret)); + isc_buffer_init(&b, server->sctx->secret, + sizeof(server->sctx->secret)); + result = isc_hex_decodestring(str, &b); + if (result != ISC_R_SUCCESS && + result != ISC_R_NOSPACE) + { + goto cleanup; + } + first = false; + } else { + altsecret = isc_mem_get(server->sctx->mctx, + sizeof(*altsecret)); + isc_buffer_init(&b, altsecret->secret, + sizeof(altsecret->secret)); + result = isc_hex_decodestring(str, &b); + if (result != ISC_R_SUCCESS && + result != ISC_R_NOSPACE) + { + isc_mem_put(server->sctx->mctx, + altsecret, + sizeof(*altsecret)); + goto cleanup; + } + ISC_LIST_INITANDAPPEND(altsecrets, altsecret, + link); + } + + usedlength = isc_buffer_usedlength(&b); + switch (server->sctx->cookiealg) { + case ns_cookiealg_siphash24: + expectedlength = ISC_SIPHASH24_KEY_LENGTH; + if (usedlength != expectedlength) { + CHECKM(ISC_R_RANGE, "SipHash-2-4 " + "cookie-secret " + "must be 128 bits"); + } + break; + case ns_cookiealg_aes: + expectedlength = ISC_AES128_KEYLENGTH; + if (usedlength != expectedlength) { + CHECKM(ISC_R_RANGE, "AES cookie-secret " + "must be 128 bits"); + } + break; + } + } + } else { + isc_nonce_buf(server->sctx->secret, + sizeof(server->sctx->secret)); + } + + /* + * Swap altsecrets lists. + */ + tmpaltsecrets = server->sctx->altsecrets; + server->sctx->altsecrets = altsecrets; + altsecrets = tmpaltsecrets; + + (void)named_server_loadnta(server); + +#ifdef USE_DNSRPS + /* + * Start and connect to the DNS Response Policy Service + * daemon, dnsrpzd, for each view that uses DNSRPS. + */ + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + result = dns_dnsrps_connect(view->rpzs); + if (result != ISC_R_SUCCESS) { + view = NULL; + goto cleanup; + } + } +#endif /* ifdef USE_DNSRPS */ + + result = ISC_R_SUCCESS; + +cleanup: + if (logc != NULL) { + isc_logconfig_destroy(&logc); + } + + if (v4portset != NULL) { + isc_portset_destroy(named_g_mctx, &v4portset); + } + + if (v6portset != NULL) { + isc_portset_destroy(named_g_mctx, &v6portset); + } + + if (conf_parser != NULL) { + if (config != NULL) { + cfg_obj_destroy(conf_parser, &config); + } + cfg_parser_destroy(&conf_parser); + } + + if (bindkeys_parser != NULL) { + if (bindkeys != NULL) { + cfg_obj_destroy(bindkeys_parser, &bindkeys); + } + cfg_parser_destroy(&bindkeys_parser); + } + + if (view != NULL) { + dns_view_detach(&view); + } + + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); + + /* + * This cleans up either the old production view list + * or our temporary list depending on whether they + * were swapped above or not. + */ + for (view = ISC_LIST_HEAD(viewlist); view != NULL; view = view_next) { + view_next = ISC_LIST_NEXT(view, link); + ISC_LIST_UNLINK(viewlist, view, link); + if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") != 0) + { + dns_view_setviewrevert(view); + (void)dns_zt_apply(view->zonetable, isc_rwlocktype_read, + false, NULL, removed, view); + } + dns_view_detach(&view); + } + + /* + * Same cleanup for kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + + /* Same cleanup for cache list. */ + while ((nsc = ISC_LIST_HEAD(cachelist)) != NULL) { + ISC_LIST_UNLINK(cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + + /* Cleanup for altsecrets list. */ + while ((altsecret = ISC_LIST_HEAD(altsecrets)) != NULL) { + ISC_LIST_UNLINK(altsecrets, altsecret, link); + isc_mem_put(server->sctx->mctx, altsecret, sizeof(*altsecret)); + } + + /* + * Record the time of most recent configuration + */ + tresult = isc_time_now(&named_g_configtime); + if (tresult != ISC_R_SUCCESS) { + named_main_earlyfatal("isc_time_now() failed: %s", + isc_result_totext(result)); + } + + /* Relinquish exclusive access to configuration data. */ + if (exclusive) { + isc_task_endexclusive(server->task); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "load_configuration: %s", isc_result_totext(result)); + + return (result); +} + +static isc_result_t +view_loaded(void *arg) { + isc_result_t result; + ns_zoneload_t *zl = (ns_zoneload_t *)arg; + + /* + * Force zone maintenance. Do this after loading + * so that we know when we need to force AXFR of + * secondary zones whose master files are missing. + * + * We use the zoneload reference counter to let us + * know when all views are finished. + */ + if (isc_refcount_decrement(&zl->refs) == 1) { + named_server_t *server = zl->server; + bool reconfig = zl->reconfig; + dns_view_t *view = NULL; + + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof(*zl)); + + /* + * To maintain compatibility with log parsing tools that might + * be looking for this string after "rndc reconfig", we keep it + * as it is + */ + if (reconfig) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "any newly configured zones are now " + "loaded"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "all zones loaded"); + } + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->managed_keys != NULL) { + result = dns_zone_synckeyzone( + view->managed_keys); + if (result != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, + DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_ERROR, + "failed to initialize " + "managed-keys for view %s " + "(%s): DNSSEC validation is " + "at risk", + view->name, + isc_result_totext(result)); + } + } + } + + CHECKFATAL(dns_zonemgr_forcemaint(server->zonemgr), + "forcing zone maintenance"); + + named_os_started(); + +#ifdef HAVE_FIPS_MODE + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "FIPS mode is %s", + FIPS_mode() ? "enabled" : "disabled"); +#endif /* ifdef HAVE_FIPS_MODE */ + atomic_store(&server->reload_status, NAMED_RELOAD_DONE); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "running"); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_zones(named_server_t *server, bool init, bool reconfig) { + isc_result_t result; + isc_taskmgr_t *taskmgr = dns_zonemgr_gettaskmgr(server->zonemgr); + ns_zoneload_t *zl = NULL; + dns_view_t *view = NULL; + + zl = isc_mem_get(server->mctx, sizeof(*zl)); + zl->server = server; + zl->reconfig = reconfig; + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_refcount_init(&zl->refs, 1); + + /* + * Schedule zones to be loaded from disk. + */ + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->managed_keys != NULL) { + result = dns_zone_load(view->managed_keys, false); + if (result != ISC_R_SUCCESS && + result != DNS_R_UPTODATE && + result != DNS_R_CONTINUE) + { + goto cleanup; + } + } + if (view->redirect != NULL) { + result = dns_zone_load(view->redirect, false); + if (result != ISC_R_SUCCESS && + result != DNS_R_UPTODATE && + result != DNS_R_CONTINUE) + { + goto cleanup; + } + } + + /* + * 'dns_view_asyncload' calls view_loaded if there are no + * zones. + */ + isc_refcount_increment(&zl->refs); + result = dns_view_asyncload(view, reconfig, view_loaded, zl); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement1(&zl->refs); + goto cleanup; + } + } + +cleanup: + if (isc_refcount_decrement(&zl->refs) == 1) { + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof(*zl)); + } + + if (init) { + /* + * If we're setting up the server for the first time, set + * the task manager into privileged mode; this ensures + * that no other tasks will begin to run until after zone + * loading is complete. We won't return from exclusive mode + * until the loading is finished; we can then drop out of + * privileged mode. + * + * We do *not* want to do this in the case of reload or + * reconfig, as loading a large zone could cause the server + * to be inactive for too long a time. + */ + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged); + isc_task_endexclusive(server->task); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_normal); + } else { + isc_task_endexclusive(server->task); + } + + return (result); +} + +static void +run_server(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + named_server_t *server = (named_server_t *)event->ev_arg; + dns_geoip_databases_t *geoip; + + INSIST(task == server->task); + + isc_event_free(&event); + + CHECKFATAL(dns_dispatchmgr_create(named_g_mctx, named_g_netmgr, + &named_g_dispatchmgr), + "creating dispatch manager"); + + dns_dispatchmgr_setstats(named_g_dispatchmgr, server->resolverstats); + +#if defined(HAVE_GEOIP2) + geoip = named_g_geoip; +#else /* if defined(HAVE_GEOIP2) */ + geoip = NULL; +#endif /* if defined(HAVE_GEOIP2) */ + + CHECKFATAL(ns_interfacemgr_create(named_g_mctx, server->sctx, + named_g_taskmgr, named_g_timermgr, + named_g_netmgr, named_g_dispatchmgr, + server->task, geoip, named_g_cpus, + true, &server->interfacemgr), + "creating interface manager"); + + CHECKFATAL(isc_timer_create(named_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, + interface_timer_tick, server, + &server->interface_timer), + "creating interface timer"); + + CHECKFATAL(isc_timer_create(named_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, + heartbeat_timer_tick, server, + &server->heartbeat_timer), + "creating heartbeat timer"); + + CHECKFATAL(isc_timer_create(named_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, tat_timer_tick, + server, &server->tat_timer), + "creating trust anchor telemetry timer"); + + CHECKFATAL(isc_timer_create(named_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, pps_timer_tick, + server, &server->pps_timer), + "creating pps timer"); + + CHECKFATAL( + cfg_parser_create(named_g_mctx, named_g_lctx, &named_g_parser), + "creating default configuration parser"); + + CHECKFATAL(cfg_parser_create(named_g_mctx, named_g_lctx, + &named_g_addparser), + "creating additional configuration parser"); + + CHECKFATAL(load_configuration(named_g_conffile, server, true), + "loading configuration"); + + CHECKFATAL(load_zones(server, true, false), "loading zones"); +#ifdef ENABLE_AFL + named_g_run_done = true; +#endif /* ifdef ENABLE_AFL */ +} + +void +named_server_flushonshutdown(named_server_t *server, bool flush) { + REQUIRE(NAMED_SERVER_VALID(server)); + + server->flushonshutdown = flush; +} + +static void +shutdown_server(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_view_t *view, *view_next = NULL; + dns_kasp_t *kasp, *kasp_next = NULL; + named_server_t *server = (named_server_t *)event->ev_arg; + bool flush = server->flushonshutdown; + named_cache_t *nsc; + + UNUSED(task); + INSIST(task == server->task); + + /* + * We need to shutdown the interface before going + * exclusive (which would pause the netmgr). + */ + ns_interfacemgr_shutdown(server->interfacemgr); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, "shutting down%s", + flush ? ": flushing changes" : ""); + + named_statschannels_shutdown(server); + named_controls_shutdown(server->controls); + end_reserved_dispatches(server, true); + cleanup_session_key(server, server->mctx); + + if (named_g_aclconfctx != NULL) { + cfg_aclconfctx_detach(&named_g_aclconfctx); + } + + cfg_obj_destroy(named_g_parser, &named_g_config); + cfg_parser_destroy(&named_g_parser); + cfg_parser_destroy(&named_g_addparser); + + (void)named_server_saventa(server); + + for (kasp = ISC_LIST_HEAD(server->kasplist); kasp != NULL; + kasp = kasp_next) + { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(server->kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = view_next) + { + view_next = ISC_LIST_NEXT(view, link); + ISC_LIST_UNLINK(server->viewlist, view, link); + if (flush) { + dns_view_flushanddetach(&view); + } else { + dns_view_detach(&view); + } + } + + /* + * Shut down all dyndb instances. + */ + dns_dyndb_cleanup(true); + + while ((nsc = ISC_LIST_HEAD(server->cachelist)) != NULL) { + ISC_LIST_UNLINK(server->cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + + isc_timer_destroy(&server->interface_timer); + isc_timer_destroy(&server->heartbeat_timer); + isc_timer_destroy(&server->pps_timer); + isc_timer_destroy(&server->tat_timer); + + ns_interfacemgr_detach(&server->interfacemgr); + + dns_dispatchmgr_detach(&named_g_dispatchmgr); + + dns_zonemgr_shutdown(server->zonemgr); + + if (named_g_sessionkey != NULL) { + dns_tsigkey_detach(&named_g_sessionkey); + dns_name_free(&named_g_sessionkeyname, server->mctx); + } +#if defined(HAVE_GEOIP2) + named_geoip_shutdown(); +#endif /* HAVE_GEOIP2 */ + + dns_db_detach(&server->in_roothints); + + isc_task_endexclusive(server->task); + + isc_task_detach(&server->task); + + isc_event_free(&event); +} + +/*% + * Find a view that matches the source and destination addresses of a query. + */ +static isc_result_t +get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, + dns_message_t *message, dns_aclenv_t *env, + isc_result_t *sigresult, dns_view_t **viewp) { + dns_view_t *view; + + REQUIRE(message != NULL); + REQUIRE(sigresult != NULL); + REQUIRE(viewp != NULL && *viewp == NULL); + + for (view = ISC_LIST_HEAD(named_g_server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (message->rdclass == view->rdclass || + message->rdclass == dns_rdataclass_any) + { + const dns_name_t *tsig = NULL; + + *sigresult = dns_message_rechecksig(message, view); + if (*sigresult == ISC_R_SUCCESS) { + dns_tsigkey_t *tsigkey; + + tsigkey = message->tsigkey; + tsig = dns_tsigkey_identity(tsigkey); + } + + if (dns_acl_allowed(srcaddr, tsig, view->matchclients, + env) && + dns_acl_allowed(destaddr, tsig, + view->matchdestinations, env) && + !(view->matchrecursiveonly && + (message->flags & DNS_MESSAGEFLAG_RD) == 0)) + { + dns_view_attach(view, viewp); + return (ISC_R_SUCCESS); + } + } + } + + return (ISC_R_NOTFOUND); +} + +void +named_server_create(isc_mem_t *mctx, named_server_t **serverp) { + isc_result_t result; + named_server_t *server = isc_mem_get(mctx, sizeof(*server)); + + *server = (named_server_t){ + .mctx = mctx, + .statsfile = isc_mem_strdup(mctx, "named.stats"), + .bindkeysfile = isc_mem_strdup(mctx, named_g_defaultbindkeys), + .dumpfile = isc_mem_strdup(mctx, "named_dump.db"), + .secrootsfile = isc_mem_strdup(mctx, "named.secroots"), + .recfile = isc_mem_strdup(mctx, "named.recursing"), + }; + +#ifdef USE_DNSRPS + CHECKFATAL(dns_dnsrps_server_create(), "initializing RPZ service " + "interface"); +#endif /* ifdef USE_DNSRPS */ + + /* Initialize server data structures. */ + ISC_LIST_INIT(server->kasplist); + ISC_LIST_INIT(server->viewlist); + + /* Must be first. */ + CHECKFATAL(dst_lib_init(named_g_mctx, named_g_engine), "initializing " + "DST"); + + CHECKFATAL(dns_rootns_create(mctx, dns_rdataclass_in, NULL, + &server->in_roothints), + "setting up root hints"); + + atomic_init(&server->reload_status, NAMED_RELOAD_IN_PROGRESS); + + /* + * Setup the server task, which is responsible for coordinating + * startup and shutdown of the server, as well as all exclusive + * tasks. + */ + CHECKFATAL(isc_task_create_bound(named_g_taskmgr, 0, &server->task, 0), + "creating server task"); + isc_task_setname(server->task, "server", server); + isc_taskmgr_setexcltask(named_g_taskmgr, server->task); + + CHECKFATAL(ns_server_create(mctx, get_matching_view, &server->sctx), + "creating server context"); + +#if defined(HAVE_GEOIP2) + /* + * GeoIP must be initialized before the interface + * manager (which includes the ACL environment) + * is created. + */ + named_geoip_init(); +#endif /* HAVE_GEOIP2 */ + +#ifdef ENABLE_AFL + server->sctx->fuzztype = named_g_fuzz_type; + server->sctx->fuzznotify = named_fuzz_notify; +#endif /* ifdef ENABLE_AFL */ + + CHECKFATAL(isc_task_onshutdown(server->task, shutdown_server, server), + "isc_task_onshutdown"); + CHECKFATAL( + isc_app_onrun(named_g_mctx, server->task, run_server, server), + "isc_app_onrun"); + + CHECKFATAL(dns_zonemgr_create(named_g_mctx, named_g_taskmgr, + named_g_timermgr, named_g_netmgr, + &server->zonemgr), + "dns_zonemgr_create"); + CHECKFATAL(dns_zonemgr_setsize(server->zonemgr, 1000), "dns_zonemgr_" + "setsize"); + + CHECKFATAL(isc_stats_create(server->mctx, &server->sockstats, + isc_sockstatscounter_max), + "isc_stats_create"); + isc_nm_setstats(named_g_netmgr, server->sockstats); + + CHECKFATAL(isc_stats_create(named_g_mctx, &server->zonestats, + dns_zonestatscounter_max), + "dns_stats_create (zone)"); + + CHECKFATAL(isc_stats_create(named_g_mctx, &server->resolverstats, + dns_resstatscounter_max), + "dns_stats_create (resolver)"); + + CHECKFATAL(named_controls_create(server, &server->controls), + "named_controls_create"); + + ISC_LIST_INIT(server->dispatches); + + ISC_LIST_INIT(server->statschannels); + + ISC_LIST_INIT(server->cachelist); + + server->magic = NAMED_SERVER_MAGIC; + + *serverp = server; +} + +void +named_server_destroy(named_server_t **serverp) { + named_server_t *server = *serverp; + REQUIRE(NAMED_SERVER_VALID(server)); + +#ifdef HAVE_DNSTAP + if (server->dtenv != NULL) { + dns_dt_detach(&server->dtenv); + } +#endif /* HAVE_DNSTAP */ + +#ifdef USE_DNSRPS + dns_dnsrps_server_destroy(); +#endif /* ifdef USE_DNSRPS */ + + named_controls_destroy(&server->controls); + + isc_stats_detach(&server->zonestats); + isc_stats_detach(&server->sockstats); + isc_stats_detach(&server->resolverstats); + + if (server->sctx != NULL) { + ns_server_detach(&server->sctx); + } + + isc_mem_free(server->mctx, server->statsfile); + isc_mem_free(server->mctx, server->bindkeysfile); + isc_mem_free(server->mctx, server->dumpfile); + isc_mem_free(server->mctx, server->secrootsfile); + isc_mem_free(server->mctx, server->recfile); + + if (server->version != NULL) { + isc_mem_free(server->mctx, server->version); + } + if (server->hostname != NULL) { + isc_mem_free(server->mctx, server->hostname); + } + if (server->lockfile != NULL) { + isc_mem_free(server->mctx, server->lockfile); + } + + if (server->zonemgr != NULL) { + dns_zonemgr_detach(&server->zonemgr); + } + + dst_lib_destroy(); + + INSIST(ISC_LIST_EMPTY(server->kasplist)); + INSIST(ISC_LIST_EMPTY(server->viewlist)); + INSIST(ISC_LIST_EMPTY(server->cachelist)); + + if (server->tlsctx_server_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_server_cache); + } + + if (server->tlsctx_client_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_client_cache); + } + + server->magic = 0; + isc_mem_put(server->mctx, server, sizeof(*server)); + *serverp = NULL; +} + +static void +fatal(named_server_t *server, const char *msg, isc_result_t result) { + if (server != NULL && server->task != NULL) { + /* + * Prevent races between the OpenSSL on_exit registered + * function and any other OpenSSL calls from other tasks + * by requesting exclusive access to the task manager. + */ + (void)isc_task_beginexclusive(server->task); + } + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_CRITICAL, "%s: %s", msg, + isc_result_totext(result)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_CRITICAL, + "exiting (due to fatal error)"); + named_os_shutdown(); + exit(1); +} + +static void +start_reserved_dispatches(named_server_t *server) { + REQUIRE(NAMED_SERVER_VALID(server)); + + server->dispatchgen++; +} + +static void +end_reserved_dispatches(named_server_t *server, bool all) { + named_dispatch_t *dispatch, *nextdispatch; + + REQUIRE(NAMED_SERVER_VALID(server)); + + for (dispatch = ISC_LIST_HEAD(server->dispatches); dispatch != NULL; + dispatch = nextdispatch) + { + nextdispatch = ISC_LIST_NEXT(dispatch, link); + if (!all && server->dispatchgen == dispatch->dispatchgen) { + continue; + } + ISC_LIST_UNLINK(server->dispatches, dispatch, link); + dns_dispatch_detach(&dispatch->dispatch); + isc_mem_put(server->mctx, dispatch, sizeof(*dispatch)); + } +} + +void +named_add_reserved_dispatch(named_server_t *server, + const isc_sockaddr_t *addr) { + named_dispatch_t *dispatch; + in_port_t port; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_result_t result; + + REQUIRE(NAMED_SERVER_VALID(server)); + + port = isc_sockaddr_getport(addr); + if (port == 0 || port >= 1024) { + return; + } + + for (dispatch = ISC_LIST_HEAD(server->dispatches); dispatch != NULL; + dispatch = ISC_LIST_NEXT(dispatch, link)) + { + if (isc_sockaddr_equal(&dispatch->addr, addr)) { + break; + } + } + if (dispatch != NULL) { + dispatch->dispatchgen = server->dispatchgen; + return; + } + + dispatch = isc_mem_get(server->mctx, sizeof(*dispatch)); + + dispatch->addr = *addr; + dispatch->dispatchgen = server->dispatchgen; + dispatch->dispatch = NULL; + + result = dns_dispatch_createudp(named_g_dispatchmgr, &dispatch->addr, + &dispatch->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + ISC_LIST_INITANDPREPEND(server->dispatches, dispatch, link); + + return; + +cleanup: + isc_mem_put(server->mctx, dispatch, sizeof(*dispatch)); + isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "unable to create dispatch for reserved port %s: %s", + addrbuf, isc_result_totext(result)); +} + +static isc_result_t +loadconfig(named_server_t *server) { + isc_result_t result; + start_reserved_dispatches(server); + result = load_configuration(named_g_conffile, server, false); + if (result == ISC_R_SUCCESS) { + end_reserved_dispatches(server, false); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "reloading configuration succeeded"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "reloading configuration failed: %s", + isc_result_totext(result)); + atomic_store(&server->reload_status, NAMED_RELOAD_FAILED); + } + + return (result); +} + +static isc_result_t +reload(named_server_t *server) { + isc_result_t result; + + atomic_store(&server->reload_status, NAMED_RELOAD_IN_PROGRESS); + + CHECK(loadconfig(server)); + + result = load_zones(server, false, false); + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "reloading zones succeeded"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "reloading zones failed: %s", + isc_result_totext(result)); + atomic_store(&server->reload_status, NAMED_RELOAD_FAILED); + } +cleanup: + return (result); +} + +/* + * Handle a reload event (from SIGHUP). + */ +static void +named_server_reload(isc_task_t *task, isc_event_t *event) { + named_server_t *server = (named_server_t *)event->ev_sender; + + INSIST(task == server->task); + UNUSED(task); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "received SIGHUP signal to reload zones"); + (void)reload(server); + + isc_event_free(&event); +} + +void +named_server_reloadwanted(named_server_t *server) { + isc_event_t *event = isc_event_allocate( + named_g_mctx, server, NAMED_EVENT_RELOAD, named_server_reload, + NULL, sizeof(isc_event_t)); + isc_task_send(server->task, &event); +} + +void +named_server_scan_interfaces(named_server_t *server) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "automatic interface rescan"); + + ns_interfacemgr_scan(server->interfacemgr, true, false); +} + +/* + * Get the next token from lexer 'lex'. + * + * NOTE: the token value for string tokens always uses the same pointer + * value. Multiple calls to this function on the same lexer will always + * return either that value (lex->data) or NULL. It is necessary to copy + * the token into local storage if it needs to be referenced after the next + * call to next_token(). + */ +static char * +next_token(isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + isc_token_t token; + + token.type = isc_tokentype_unknown; + result = isc_lex_gettoken(lex, ISC_LEXOPT_EOF | ISC_LEXOPT_QSTRING, + &token); + + switch (result) { + case ISC_R_NOMORE: + (void)isc_lex_close(lex); + break; + case ISC_R_SUCCESS: + if (token.type == isc_tokentype_eof) { + (void)isc_lex_close(lex); + } + break; + case ISC_R_NOSPACE: + if (text != NULL) { + (void)putstr(text, "token too large"); + (void)putnull(text); + } + return (NULL); + default: + if (text != NULL) { + (void)putstr(text, isc_result_totext(result)); + (void)putnull(text); + } + return (NULL); + } + + if (token.type == isc_tokentype_string || + token.type == isc_tokentype_qstring) + { + return (token.value.as_textregion.base); + } + + return (NULL); +} + +/* + * Find the zone specified in the control channel command, if any. + * If a zone is specified, point '*zonep' at it, otherwise + * set '*zonep' to NULL, and f 'zonename' is not NULL, copy + * the zone name into it (N.B. 'zonename' must have space to hold + * a full DNS name). + * + * If 'zonetxt' is set, the caller has already pulled a token + * off the command line that is to be used as the zone name. (This + * is sometimes done when it's necessary to check for an optional + * argument before the zone name, as in "rndc sync [-clean] zone".) + */ +static isc_result_t +zone_from_args(named_server_t *server, isc_lex_t *lex, const char *zonetxt, + dns_zone_t **zonep, char *zonename, isc_buffer_t **text, + bool skip) { + char *ptr; + char *classtxt; + const char *viewtxt = NULL; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + dns_view_t *view = NULL; + dns_rdataclass_t rdclass; + char problem[DNS_NAME_FORMATSIZE + 500] = ""; + char zonebuf[DNS_NAME_FORMATSIZE]; + bool redirect = false; + + REQUIRE(zonep != NULL && *zonep == NULL); + + if (skip) { + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + } + + /* Look for the zone name. */ + if (zonetxt == NULL) { + zonetxt = next_token(lex, text); + } + if (zonetxt == NULL) { + return (ISC_R_SUCCESS); + } + + /* Copy zonetxt because it'll be overwritten by next_token() */ + /* To locate a zone named "-redirect" use "-redirect." */ + if (strcmp(zonetxt, "-redirect") == 0) { + redirect = true; + strlcpy(zonebuf, ".", DNS_NAME_FORMATSIZE); + } else { + strlcpy(zonebuf, zonetxt, DNS_NAME_FORMATSIZE); + } + if (zonename != NULL) { + strlcpy(zonename, redirect ? "." : zonetxt, + DNS_NAME_FORMATSIZE); + } + + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromstring(name, zonebuf, 0, NULL)); + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + isc_textregion_t r; + r.base = classtxt; + r.length = strlen(classtxt); + CHECK(dns_rdataclass_fromtext(&rdclass, &r)); + + /* Look for the optional view name. */ + viewtxt = next_token(lex, text); + } else { + rdclass = dns_rdataclass_in; + } + + if (viewtxt == NULL) { + if (redirect) { + result = dns_viewlist_find(&server->viewlist, + "_default", + dns_rdataclass_in, &view); + if (result != ISC_R_SUCCESS || view->redirect == NULL) { + result = ISC_R_NOTFOUND; + snprintf(problem, sizeof(problem), + "redirect zone not found in " + "_default view"); + } else { + dns_zone_attach(view->redirect, zonep); + result = ISC_R_SUCCESS; + } + } else { + result = dns_viewlist_findzone(&server->viewlist, name, + (classtxt == NULL), + rdclass, zonep); + if (result == ISC_R_NOTFOUND) { + snprintf(problem, sizeof(problem), + "no matching zone '%s' in any view", + zonebuf); + } else if (result == ISC_R_MULTIPLE) { + snprintf(problem, sizeof(problem), + "zone '%s' was found in multiple " + "views", + zonebuf); + } + } + } else { + result = dns_viewlist_find(&server->viewlist, viewtxt, rdclass, + &view); + if (result != ISC_R_SUCCESS) { + snprintf(problem, sizeof(problem), + "no matching view '%s'", viewtxt); + goto report; + } + + if (redirect) { + if (view->redirect != NULL) { + dns_zone_attach(view->redirect, zonep); + result = ISC_R_SUCCESS; + } else { + result = ISC_R_NOTFOUND; + } + } else { + result = dns_zt_find(view->zonetable, name, 0, NULL, + zonep); + } + if (result != ISC_R_SUCCESS) { + snprintf(problem, sizeof(problem), + "no matching zone '%s' in view '%s'", zonebuf, + viewtxt); + } + } + + /* Partial match? */ + if (result != ISC_R_SUCCESS && *zonep != NULL) { + dns_zone_detach(zonep); + } + if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } +report: + if (result != ISC_R_SUCCESS) { + isc_result_t tresult; + + tresult = putstr(text, problem); + if (tresult == ISC_R_SUCCESS) { + (void)putnull(text); + } + } + +cleanup: + if (view != NULL) { + dns_view_detach(&view); + } + + return (result); +} + +/* + * Act on a "retransfer" command from the command channel. + */ +isc_result_t +named_server_retransfercommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zone_t *raw = NULL; + dns_zonetype_t type; + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &zone, NULL, text, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (zone == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + dns_zone_detach(&zone); + dns_zone_attach(raw, &zone); + dns_zone_detach(&raw); + } + type = dns_zone_gettype(zone); + if (type == dns_zone_secondary || type == dns_zone_mirror || + type == dns_zone_stub || + (type == dns_zone_redirect && + dns_zone_getredirecttype(zone) == dns_zone_secondary)) + { + dns_zone_forcereload(zone); + } else { + (void)putstr(text, "retransfer: inappropriate zone type: "); + (void)putstr(text, dns_zonetype_name(type)); + if (type == dns_zone_redirect) { + type = dns_zone_getredirecttype(zone); + (void)putstr(text, "("); + (void)putstr(text, dns_zonetype_name(type)); + (void)putstr(text, ")"); + } + (void)putnull(text); + result = ISC_R_FAILURE; + } + dns_zone_detach(&zone); + return (result); +} + +/* + * Act on a "reload" command from the command channel. + */ +isc_result_t +named_server_reloadcommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zonetype_t type; + const char *msg = NULL; + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &zone, NULL, text, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (zone == NULL) { + result = reload(server); + if (result == ISC_R_SUCCESS) { + msg = "server reload successful"; + } + } else { + type = dns_zone_gettype(zone); + if (type == dns_zone_secondary || type == dns_zone_mirror || + type == dns_zone_stub) + { + dns_zone_refresh(zone); + dns_zone_detach(&zone); + msg = "zone refresh queued"; + } else { + result = dns_zone_load(zone, false); + dns_zone_detach(&zone); + switch (result) { + case ISC_R_SUCCESS: + msg = "zone reload successful"; + break; + case DNS_R_CONTINUE: + msg = "zone reload queued"; + result = ISC_R_SUCCESS; + break; + case DNS_R_UPTODATE: + msg = "zone reload up-to-date"; + result = ISC_R_SUCCESS; + break; + default: + /* failure message will be generated by rndc */ + break; + } + } + } + if (msg != NULL) { + (void)putstr(text, msg); + (void)putnull(text); + } + return (result); +} + +/* + * Act on a "reconfig" command from the command channel. + */ +isc_result_t +named_server_reconfigcommand(named_server_t *server) { + isc_result_t result; + atomic_store(&server->reload_status, NAMED_RELOAD_IN_PROGRESS); + + CHECK(loadconfig(server)); + + result = load_zones(server, false, true); + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "scheduled loading new zones"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "loading new zones failed: %s", + isc_result_totext(result)); + atomic_store(&server->reload_status, NAMED_RELOAD_FAILED); + } +cleanup: + return (result); +} + +/* + * Act on a "notify" command from the command channel. + */ +isc_result_t +named_server_notifycommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL; + const char msg[] = "zone notify queued"; + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &zone, NULL, text, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (zone == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + dns_zone_notify(zone); + dns_zone_detach(&zone); + (void)putstr(text, msg); + (void)putnull(text); + + return (ISC_R_SUCCESS); +} + +/* + * Act on a "refresh" command from the command channel. + */ +isc_result_t +named_server_refreshcommand(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL, *raw = NULL; + const char msg1[] = "zone refresh queued"; + const char msg2[] = "not a secondary, mirror, or stub zone"; + dns_zonetype_t type; + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &zone, NULL, text, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (zone == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + dns_zone_detach(&zone); + dns_zone_attach(raw, &zone); + dns_zone_detach(&raw); + } + + type = dns_zone_gettype(zone); + if (type == dns_zone_secondary || type == dns_zone_mirror || + type == dns_zone_stub) + { + dns_zone_refresh(zone); + dns_zone_detach(&zone); + (void)putstr(text, msg1); + (void)putnull(text); + return (ISC_R_SUCCESS); + } + + dns_zone_detach(&zone); + (void)putstr(text, msg2); + (void)putnull(text); + return (ISC_R_FAILURE); +} + +isc_result_t +named_server_togglequerylog(named_server_t *server, isc_lex_t *lex) { + bool prev, value; + char *ptr; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + prev = ns_server_getoption(server->sctx, NS_SERVER_LOGQUERIES); + + ptr = next_token(lex, NULL); + if (ptr == NULL) { + value = !prev; + } else if (!strcasecmp(ptr, "on") || !strcasecmp(ptr, "yes") || + !strcasecmp(ptr, "enable") || !strcasecmp(ptr, "true")) + { + value = true; + } else if (!strcasecmp(ptr, "off") || !strcasecmp(ptr, "no") || + !strcasecmp(ptr, "disable") || !strcasecmp(ptr, "false")) + { + value = false; + } else { + return (DNS_R_SYNTAX); + } + + if (value == prev) { + return (ISC_R_SUCCESS); + } + + ns_server_setoption(server->sctx, NS_SERVER_LOGQUERIES, value); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "query logging is now %s", value ? "on" : "off"); + return (ISC_R_SUCCESS); +} + +static isc_result_t +listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, + ns_listenlist_t **target) { + isc_result_t result; + const cfg_listelt_t *element; + ns_listenlist_t *dlist = NULL; + + REQUIRE(target != NULL && *target == NULL); + + result = ns_listenlist_create(mctx, &dlist); + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (element = cfg_list_first(listenlist); element != NULL; + element = cfg_list_next(element)) + { + ns_listenelt_t *delt = NULL; + const cfg_obj_t *listener = cfg_listelt_value(element); + result = listenelt_fromconfig(listener, config, actx, mctx, + family, tlsctx_cache, &delt); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(dlist->elts, delt, link); + } + *target = dlist; + return (ISC_R_SUCCESS); + +cleanup: + ns_listenlist_detach(&dlist); + return (result); +} + +static const cfg_obj_t * +find_maplist(const cfg_obj_t *config, const char *listname, const char *name) { + isc_result_t result; + const cfg_obj_t *maplist = NULL; + const cfg_listelt_t *elt = NULL; + + REQUIRE(config != NULL); + REQUIRE(name != NULL); + + result = cfg_map_get(config, listname, &maplist); + if (result != ISC_R_SUCCESS) { + return (NULL); + } + + for (elt = cfg_list_first(maplist); elt != NULL; + elt = cfg_list_next(elt)) + { + const cfg_obj_t *map = cfg_listelt_value(elt); + if (strcasecmp(cfg_obj_asstring(cfg_map_getname(map)), name) == + 0) + { + return (map); + } + } + + return (NULL); +} + +/* + * Create a listen list from the corresponding configuration + * data structure. + */ +static isc_result_t +listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, + ns_listenelt_t **target) { + isc_result_t result; + const cfg_obj_t *ltup = NULL; + const cfg_obj_t *tlsobj = NULL, *httpobj = NULL; + const cfg_obj_t *portobj = NULL; + const cfg_obj_t *http_server = NULL; + in_port_t port = 0; + const char *key = NULL, *cert = NULL, *ca_file = NULL, + *dhparam_file = NULL, *ciphers = NULL; + bool tls_prefer_server_ciphers = false, + tls_prefer_server_ciphers_set = false; + bool tls_session_tickets = false, tls_session_tickets_set = false; + bool do_tls = false, no_tls = false, http = false; + ns_listenelt_t *delt = NULL; + uint32_t tls_protos = 0; + ns_listen_tls_params_t tls_params = { 0 }; + const char *tlsname = NULL; + + REQUIRE(target != NULL && *target == NULL); + + ltup = cfg_tuple_get(listener, "tuple"); + RUNTIME_CHECK(ltup != NULL); + + tlsobj = cfg_tuple_get(ltup, "tls"); + if (tlsobj != NULL && cfg_obj_isstring(tlsobj)) { + tlsname = cfg_obj_asstring(tlsobj); + + if (strcasecmp(tlsname, "none") == 0) { + no_tls = true; + } else if (strcasecmp(tlsname, "ephemeral") == 0) { + do_tls = true; + } else { + const cfg_obj_t *keyobj = NULL, *certobj = NULL, + *ca_obj = NULL, *dhparam_obj = NULL; + const cfg_obj_t *tlsmap = NULL; + const cfg_obj_t *tls_proto_list = NULL; + const cfg_obj_t *ciphers_obj = NULL; + const cfg_obj_t *prefer_server_ciphers_obj = NULL; + const cfg_obj_t *session_tickets_obj = NULL; + + do_tls = true; + + tlsmap = find_maplist(config, "tls", tlsname); + if (tlsmap == NULL) { + cfg_obj_log(tlsobj, named_g_lctx, ISC_LOG_ERROR, + "tls '%s' is not defined", + cfg_obj_asstring(tlsobj)); + return (ISC_R_FAILURE); + } + + CHECK(cfg_map_get(tlsmap, "key-file", &keyobj)); + key = cfg_obj_asstring(keyobj); + + CHECK(cfg_map_get(tlsmap, "cert-file", &certobj)); + cert = cfg_obj_asstring(certobj); + + if (cfg_map_get(tlsmap, "ca-file", &ca_obj) == + ISC_R_SUCCESS) + { + ca_file = cfg_obj_asstring(ca_obj); + } + + if (cfg_map_get(tlsmap, "protocols", &tls_proto_list) == + ISC_R_SUCCESS) + { + const cfg_listelt_t *proto = NULL; + INSIST(tls_proto_list != NULL); + for (proto = cfg_list_first(tls_proto_list); + proto != 0; proto = cfg_list_next(proto)) + { + const cfg_obj_t *tls_proto_obj = + cfg_listelt_value(proto); + const char *tls_sver = + cfg_obj_asstring(tls_proto_obj); + const isc_tls_protocol_version_t ver = + isc_tls_protocol_name_to_version( + tls_sver); + + INSIST(ver != + ISC_TLS_PROTO_VER_UNDEFINED); + INSIST(isc_tls_protocol_supported(ver)); + tls_protos |= ver; + } + } + + if (cfg_map_get(tlsmap, "dhparam-file", &dhparam_obj) == + ISC_R_SUCCESS) + { + dhparam_file = cfg_obj_asstring(dhparam_obj); + } + + if (cfg_map_get(tlsmap, "ciphers", &ciphers_obj) == + ISC_R_SUCCESS) + { + ciphers = cfg_obj_asstring(ciphers_obj); + } + + if (cfg_map_get(tlsmap, "prefer-server-ciphers", + &prefer_server_ciphers_obj) == + ISC_R_SUCCESS) + { + tls_prefer_server_ciphers = cfg_obj_asboolean( + prefer_server_ciphers_obj); + tls_prefer_server_ciphers_set = true; + } + + if (cfg_map_get(tlsmap, "session-tickets", + &session_tickets_obj) == ISC_R_SUCCESS) + { + tls_session_tickets = + cfg_obj_asboolean(session_tickets_obj); + tls_session_tickets_set = true; + } + } + } + + tls_params = (ns_listen_tls_params_t){ + .name = tlsname, + .key = key, + .cert = cert, + .ca_file = ca_file, + .protocols = tls_protos, + .dhparam_file = dhparam_file, + .ciphers = ciphers, + .prefer_server_ciphers = tls_prefer_server_ciphers, + .prefer_server_ciphers_set = tls_prefer_server_ciphers_set, + .session_tickets = tls_session_tickets, + .session_tickets_set = tls_session_tickets_set + }; + + httpobj = cfg_tuple_get(ltup, "http"); + if (httpobj != NULL && cfg_obj_isstring(httpobj)) { + const char *httpname = cfg_obj_asstring(httpobj); + + if (!do_tls && !no_tls) { + return (ISC_R_FAILURE); + } + + http_server = find_maplist(config, "http", httpname); + if (http_server == NULL && strcasecmp(httpname, "default") != 0) + { + cfg_obj_log(httpobj, named_g_lctx, ISC_LOG_ERROR, + "http '%s' is not defined", + cfg_obj_asstring(httpobj)); + return (ISC_R_FAILURE); + } + + http = true; + } + + portobj = cfg_tuple_get(ltup, "port"); + if (!cfg_obj_isuint32(portobj)) { + if (http && do_tls) { + if (named_g_httpsport != 0) { + port = named_g_httpsport; + } else { + result = named_config_getport( + config, "https-port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else if (http && !do_tls) { + if (named_g_httpport != 0) { + port = named_g_httpport; + } else { + result = named_config_getport( + config, "http-port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else if (do_tls) { + if (named_g_tlsport != 0) { + port = named_g_tlsport; + } else { + result = named_config_getport( + config, "tls-port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else { + if (named_g_port != 0) { + port = named_g_port; + } else { + result = named_config_getport(config, "port", + &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } + } else { + if (cfg_obj_asuint32(portobj) >= UINT16_MAX) { + return (ISC_R_RANGE); + } + port = (in_port_t)cfg_obj_asuint32(portobj); + } + +#ifdef HAVE_LIBNGHTTP2 + if (http) { + CHECK(listenelt_http(http_server, family, do_tls, &tls_params, + tlsctx_cache, port, mctx, &delt)); + } +#endif /* HAVE_LIBNGHTTP2 */ + + if (!http) { + CHECK(ns_listenelt_create(mctx, port, NULL, family, do_tls, + &tls_params, tlsctx_cache, &delt)); + } + + result = cfg_acl_fromconfig2(cfg_tuple_get(listener, "acl"), config, + named_g_lctx, actx, mctx, 0, family, + &delt->acl); + if (result != ISC_R_SUCCESS) { + ns_listenelt_destroy(delt); + return (result); + } + *target = delt; + +cleanup: + return (result); +} + +#ifdef HAVE_LIBNGHTTP2 +static isc_result_t +listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, + const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, + isc_mem_t *mctx, ns_listenelt_t **target) { + isc_result_t result = ISC_R_SUCCESS; + ns_listenelt_t *delt = NULL; + char **endpoints = NULL; + const cfg_obj_t *eplist = NULL; + const cfg_listelt_t *elt = NULL; + size_t len = 1, i = 0; + uint32_t max_clients = named_g_http_listener_clients; + uint32_t max_streams = named_g_http_streams_per_conn; + + REQUIRE(target != NULL && *target == NULL); + + if (tls) { + INSIST(tls_params != NULL); + INSIST((tls_params->key == NULL) == (tls_params->cert == NULL)); + } + + if (port == 0) { + port = tls ? named_g_httpsport : named_g_httpport; + } + + /* + * If "default" was used, we set up the default endpoint + * of "/dns-query". + */ + if (http != NULL) { + const cfg_obj_t *cfg_max_clients = NULL; + const cfg_obj_t *cfg_max_streams = NULL; + + if (cfg_map_get(http, "endpoints", &eplist) == ISC_R_SUCCESS) { + INSIST(eplist != NULL); + len = cfg_list_length(eplist, false); + } + + if (cfg_map_get(http, "listener-clients", &cfg_max_clients) == + ISC_R_SUCCESS) + { + INSIST(cfg_max_clients != NULL); + max_clients = cfg_obj_asuint32(cfg_max_clients); + } + + if (cfg_map_get(http, "streams-per-connection", + &cfg_max_streams) == ISC_R_SUCCESS) + { + INSIST(cfg_max_streams != NULL); + max_streams = cfg_obj_asuint32(cfg_max_streams); + } + } + + endpoints = isc_mem_allocate(mctx, sizeof(endpoints[0]) * len); + + if (http != NULL && eplist != NULL) { + for (elt = cfg_list_first(eplist); elt != NULL; + elt = cfg_list_next(elt)) + { + const cfg_obj_t *ep = cfg_listelt_value(elt); + const char *path = cfg_obj_asstring(ep); + endpoints[i++] = isc_mem_strdup(mctx, path); + } + } else { + endpoints[i++] = isc_mem_strdup(mctx, ISC_NM_HTTP_DEFAULT_PATH); + } + + INSIST(i == len); + + result = ns_listenelt_create_http(mctx, port, NULL, family, tls, + tls_params, tlsctx_cache, endpoints, + len, max_clients, max_streams, &delt); + if (result != ISC_R_SUCCESS) { + goto error; + } + + *target = delt; + + return (result); +error: + if (delt != NULL) { + ns_listenelt_destroy(delt); + } + return (result); +} +#endif /* HAVE_LIBNGHTTP2 */ + +isc_result_t +named_server_dumpstats(named_server_t *server) { + isc_result_t result; + FILE *fp = NULL; + + CHECKMF(isc_stdio_open(server->statsfile, "a", &fp), + "could not open statistics dump file", server->statsfile); + + result = named_stats_dump(server, fp); + +cleanup: + if (fp != NULL) { + (void)isc_stdio_close(fp); + } + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpstats complete"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpstats failed: %s", + isc_result_totext(result)); + } + return (result); +} + +static isc_result_t +add_zone_tolist(dns_zone_t *zone, void *uap) { + struct dumpcontext *dctx = uap; + struct zonelistentry *zle; + + zle = isc_mem_get(dctx->mctx, sizeof *zle); + zle->zone = NULL; + dns_zone_attach(zone, &zle->zone); + ISC_LINK_INIT(zle, link); + ISC_LIST_APPEND(ISC_LIST_TAIL(dctx->viewlist)->zonelist, zle, link); + return (ISC_R_SUCCESS); +} + +static isc_result_t +add_view_tolist(struct dumpcontext *dctx, dns_view_t *view) { + struct viewlistentry *vle; + isc_result_t result = ISC_R_SUCCESS; + + /* + * Prevent duplicate views. + */ + for (vle = ISC_LIST_HEAD(dctx->viewlist); vle != NULL; + vle = ISC_LIST_NEXT(vle, link)) + { + if (vle->view == view) { + return (ISC_R_SUCCESS); + } + } + + vle = isc_mem_get(dctx->mctx, sizeof *vle); + vle->view = NULL; + dns_view_attach(view, &vle->view); + ISC_LINK_INIT(vle, link); + ISC_LIST_INIT(vle->zonelist); + ISC_LIST_APPEND(dctx->viewlist, vle, link); + if (dctx->dumpzones) { + result = dns_zt_apply(view->zonetable, isc_rwlocktype_read, + true, NULL, add_zone_tolist, dctx); + } + return (result); +} + +static void +dumpcontext_destroy(struct dumpcontext *dctx) { + struct viewlistentry *vle; + struct zonelistentry *zle; + + vle = ISC_LIST_HEAD(dctx->viewlist); + while (vle != NULL) { + ISC_LIST_UNLINK(dctx->viewlist, vle, link); + zle = ISC_LIST_HEAD(vle->zonelist); + while (zle != NULL) { + ISC_LIST_UNLINK(vle->zonelist, zle, link); + dns_zone_detach(&zle->zone); + isc_mem_put(dctx->mctx, zle, sizeof *zle); + zle = ISC_LIST_HEAD(vle->zonelist); + } + dns_view_detach(&vle->view); + isc_mem_put(dctx->mctx, vle, sizeof *vle); + vle = ISC_LIST_HEAD(dctx->viewlist); + } + if (dctx->version != NULL) { + dns_db_closeversion(dctx->db, &dctx->version, false); + } + if (dctx->db != NULL) { + dns_db_detach(&dctx->db); + } + if (dctx->cache != NULL) { + dns_db_detach(&dctx->cache); + } + if (dctx->task != NULL) { + isc_task_detach(&dctx->task); + } + if (dctx->fp != NULL) { + (void)isc_stdio_close(dctx->fp); + } + if (dctx->mdctx != NULL) { + dns_dumpctx_detach(&dctx->mdctx); + } + isc_mem_put(dctx->mctx, dctx, sizeof *dctx); +} + +static void +dumpdone(void *arg, isc_result_t result) { + struct dumpcontext *dctx = arg; + char buf[1024 + 32]; + const dns_master_style_t *style; + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (dctx->mdctx != NULL) { + dns_dumpctx_detach(&dctx->mdctx); + } + if (dctx->view == NULL) { + dctx->view = ISC_LIST_HEAD(dctx->viewlist); + if (dctx->view == NULL) { + goto done; + } + INSIST(dctx->zone == NULL); + } else { + goto resume; + } +nextview: + fprintf(dctx->fp, ";\n; Start view %s\n;\n", dctx->view->view->name); +resume: + if (dctx->dumpcache && dns_view_iscacheshared(dctx->view->view)) { + fprintf(dctx->fp, ";\n; Cache of view '%s' is shared as '%s'\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); + } else if (dctx->zone == NULL && dctx->cache == NULL && dctx->dumpcache) + { + if (dctx->dumpexpired) { + style = &dns_master_style_cache_with_expired; + } else { + style = &dns_master_style_cache; + } + /* start cache dump */ + if (dctx->view->view->cachedb != NULL) { + dns_db_attach(dctx->view->view->cachedb, &dctx->cache); + } + if (dctx->cache != NULL) { + fprintf(dctx->fp, + ";\n; Cache dump of view '%s' (cache %s)\n;\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); + result = dns_master_dumptostreamasync( + dctx->mctx, dctx->cache, NULL, style, dctx->fp, + dctx->task, dumpdone, dctx, &dctx->mdctx); + if (result == DNS_R_CONTINUE) { + return; + } + if (result == ISC_R_NOTIMPLEMENTED) { + fprintf(dctx->fp, "; %s\n", + isc_result_totext(result)); + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + } + + if ((dctx->dumpadb || dctx->dumpbad || dctx->dumpfail) && + dctx->cache == NULL && dctx->view->view->cachedb != NULL) + { + dns_db_attach(dctx->view->view->cachedb, &dctx->cache); + } + + if (dctx->cache != NULL) { + if (dctx->dumpadb) { + dns_adb_dump(dctx->view->view->adb, dctx->fp); + } + if (dctx->dumpbad) { + dns_resolver_printbadcache(dctx->view->view->resolver, + dctx->fp); + } + if (dctx->dumpfail) { + dns_badcache_print(dctx->view->view->failcache, + "SERVFAIL cache", dctx->fp); + } + dns_db_detach(&dctx->cache); + } + if (dctx->dumpzones) { + style = &dns_master_style_full; + nextzone: + if (dctx->version != NULL) { + dns_db_closeversion(dctx->db, &dctx->version, false); + } + if (dctx->db != NULL) { + dns_db_detach(&dctx->db); + } + if (dctx->zone == NULL) { + dctx->zone = ISC_LIST_HEAD(dctx->view->zonelist); + } else { + dctx->zone = ISC_LIST_NEXT(dctx->zone, link); + } + if (dctx->zone != NULL) { + /* start zone dump */ + dns_zone_name(dctx->zone->zone, buf, sizeof(buf)); + fprintf(dctx->fp, ";\n; Zone dump of '%s'\n;\n", buf); + result = dns_zone_getdb(dctx->zone->zone, &dctx->db); + if (result != ISC_R_SUCCESS) { + fprintf(dctx->fp, "; %s\n", + isc_result_totext(result)); + goto nextzone; + } + dns_db_currentversion(dctx->db, &dctx->version); + result = dns_master_dumptostreamasync( + dctx->mctx, dctx->db, dctx->version, style, + dctx->fp, dctx->task, dumpdone, dctx, + &dctx->mdctx); + if (result == DNS_R_CONTINUE) { + return; + } + if (result == ISC_R_NOTIMPLEMENTED) { + fprintf(dctx->fp, "; %s\n", + isc_result_totext(result)); + result = ISC_R_SUCCESS; + POST(result); + goto nextzone; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + } + if (dctx->view != NULL) { + dctx->view = ISC_LIST_NEXT(dctx->view, link); + if (dctx->view != NULL) { + goto nextview; + } + } +done: + fprintf(dctx->fp, "; Dump complete\n"); + result = isc_stdio_flush(dctx->fp); + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpdb complete"); + } +cleanup: + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpdb failed: %s", isc_result_totext(result)); + } + dumpcontext_destroy(dctx); +} + +isc_result_t +named_server_dumpdb(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + struct dumpcontext *dctx = NULL; + dns_view_t *view; + isc_result_t result; + char *ptr; + const char *sep; + bool found; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + dctx = isc_mem_get(server->mctx, sizeof(*dctx)); + + dctx->mctx = server->mctx; + dctx->dumpcache = true; + dctx->dumpadb = true; + dctx->dumpbad = true; + dctx->dumpexpired = false; + dctx->dumpfail = true; + dctx->dumpzones = false; + dctx->fp = NULL; + ISC_LIST_INIT(dctx->viewlist); + dctx->view = NULL; + dctx->zone = NULL; + dctx->cache = NULL; + dctx->mdctx = NULL; + dctx->db = NULL; + dctx->cache = NULL; + dctx->task = NULL; + dctx->version = NULL; + isc_task_attach(server->task, &dctx->task); + + CHECKMF(isc_stdio_open(server->dumpfile, "w", &dctx->fp), + "could not open dump file", server->dumpfile); + + ptr = next_token(lex, NULL); + sep = (ptr == NULL) ? "" : ": "; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpdb started%s%s", sep, (ptr != NULL) ? ptr : ""); + + if (ptr != NULL && strcmp(ptr, "-all") == 0) { + /* also dump zones */ + dctx->dumpzones = true; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-cache") == 0) { + /* this is the default */ + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-expired") == 0) { + /* this is the same as -cache but includes expired data */ + dctx->dumpexpired = true; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-zones") == 0) { + /* only dump zones, suppress caches */ + dctx->dumpadb = false; + dctx->dumpbad = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + dctx->dumpzones = true; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-adb") == 0) { + /* only dump adb, suppress other caches */ + dctx->dumpbad = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-bad") == 0) { + /* only dump badcache, suppress other caches */ + dctx->dumpadb = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-fail") == 0) { + /* only dump servfail cache, suppress other caches */ + dctx->dumpadb = false; + dctx->dumpbad = false; + dctx->dumpcache = false; + ptr = next_token(lex, NULL); + } + +nextview: + found = false; + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ptr != NULL && strcmp(view->name, ptr) != 0) { + continue; + } + found = true; + CHECK(add_view_tolist(dctx, view)); + } + if (ptr != NULL) { + if (!found) { + CHECK(putstr(text, "view '")); + CHECK(putstr(text, ptr)); + CHECK(putstr(text, "' not found")); + CHECK(putnull(text)); + result = ISC_R_NOTFOUND; + dumpdone(dctx, result); + return (result); + } + ptr = next_token(lex, NULL); + if (ptr != NULL) { + goto nextview; + } + } + dumpdone(dctx, ISC_R_SUCCESS); + return (ISC_R_SUCCESS); + +cleanup: + dumpcontext_destroy(dctx); + return (result); +} + +isc_result_t +named_server_dumpsecroots(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + dns_view_t *view; + dns_keytable_t *secroots = NULL; + dns_ntatable_t *ntatable = NULL; + isc_result_t result; + char *ptr; + FILE *fp = NULL; + isc_time_t now; + char tbuf[64]; + unsigned int used = isc_buffer_usedlength(*text); + bool first = true; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* "-" here means print the output instead of dumping to file */ + ptr = next_token(lex, text); + if (ptr != NULL && strcmp(ptr, "-") == 0) { + ptr = next_token(lex, text); + } else { + result = isc_stdio_open(server->secrootsfile, "w", &fp); + if (result != ISC_R_SUCCESS) { + (void)putstr(text, "could not open "); + (void)putstr(text, server->secrootsfile); + CHECKMF(result, "could not open secroots dump file", + server->secrootsfile); + } + } + + TIME_NOW(&now); + isc_time_formattimestamp(&now, tbuf, sizeof(tbuf)); + CHECK(putstr(text, "secure roots as of ")); + CHECK(putstr(text, tbuf)); + CHECK(putstr(text, ":\n")); + used = isc_buffer_usedlength(*text); + + do { + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ptr != NULL && strcmp(view->name, ptr) != 0) { + continue; + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + result = dns_view_getsecroots(view, &secroots); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + if (first || used != isc_buffer_usedlength(*text)) { + CHECK(putstr(text, "\n")); + first = false; + } + CHECK(putstr(text, " Start view ")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, "\n Secure roots:\n\n")); + used = isc_buffer_usedlength(*text); + CHECK(dns_keytable_totext(secroots, text)); + + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + if (used != isc_buffer_usedlength(*text)) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, " Negative trust anchors:\n\n")); + used = isc_buffer_usedlength(*text); + CHECK(dns_ntatable_totext(ntatable, NULL, text)); + } + + if (ptr != NULL) { + ptr = next_token(lex, text); + } + } while (ptr != NULL); + +cleanup: + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + + if (fp != NULL) { + if (used != isc_buffer_usedlength(*text)) { + (void)putstr(text, "\n"); + } + fprintf(fp, "%.*s", (int)isc_buffer_usedlength(*text), + (char *)isc_buffer_base(*text)); + isc_buffer_clear(*text); + (void)isc_stdio_close(fp); + } else if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpsecroots complete"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpsecroots failed: %s", + isc_result_totext(result)); + } + return (result); +} + +isc_result_t +named_server_dumprecursing(named_server_t *server) { + FILE *fp = NULL; + dns_view_t *view; + isc_result_t result; + + CHECKMF(isc_stdio_open(server->recfile, "w", &fp), + "could not open dump file", server->recfile); + fprintf(fp, ";\n; Recursing Queries\n;\n"); + ns_interfacemgr_dumprecursing(fp, server->interfacemgr); + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + fprintf(fp, ";\n; Active fetch domains [view: %s]\n;\n", + view->name); + dns_resolver_dumpfetches(view->resolver, isc_statsformat_file, + fp); + } + + fprintf(fp, "; Dump complete\n"); + +cleanup: + if (fp != NULL) { + result = isc_stdio_close(fp); + } + if (result == ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumprecursing complete"); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumprecursing failed: %s", + isc_result_totext(result)); + } + return (result); +} + +isc_result_t +named_server_setdebuglevel(named_server_t *server, isc_lex_t *lex) { + char *ptr; + char *endp; + long newlevel; + + UNUSED(server); + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Look for the new level name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + if (named_g_debuglevel < 99) { + named_g_debuglevel++; + } + } else { + newlevel = strtol(ptr, &endp, 10); + if (*endp != '\0' || newlevel < 0 || newlevel > 99) { + return (ISC_R_RANGE); + } + named_g_debuglevel = (unsigned int)newlevel; + } + isc_log_setdebuglevel(named_g_lctx, named_g_debuglevel); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "debug level is now %u", named_g_debuglevel); + return (ISC_R_SUCCESS); +} + +isc_result_t +named_server_validation(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + char *ptr; + dns_view_t *view; + bool changed = false; + isc_result_t result; + bool enable = true, set = true, first = true; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (!strcasecmp(ptr, "on") || !strcasecmp(ptr, "yes") || + !strcasecmp(ptr, "enable") || !strcasecmp(ptr, "true")) + { + enable = true; + } else if (!strcasecmp(ptr, "off") || !strcasecmp(ptr, "no") || + !strcasecmp(ptr, "disable") || !strcasecmp(ptr, "false")) + { + enable = false; + } else if (!strcasecmp(ptr, "check") || !strcasecmp(ptr, "status")) { + set = false; + } else { + return (DNS_R_SYNTAX); + } + + /* Look for the view name. */ + ptr = next_token(lex, text); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if ((ptr != NULL && strcasecmp(ptr, view->name) != 0) || + strcasecmp("_bind", view->name) == 0) + { + continue; + } + + if (set) { + CHECK(dns_view_flushcache(view, false)); + view->enablevalidation = enable; + changed = true; + } else { + if (!first) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, "DNSSEC validation is ")); + CHECK(putstr(text, view->enablevalidation + ? "enabled" + : "disabled")); + CHECK(putstr(text, " (view ")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ")")); + first = false; + } + } + CHECK(putnull(text)); + + if (!set) { + result = ISC_R_SUCCESS; + } else if (changed) { + result = ISC_R_SUCCESS; + } else { + result = ISC_R_FAILURE; + } +cleanup: + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +named_server_flushcache(named_server_t *server, isc_lex_t *lex) { + char *ptr; + dns_view_t *view; + bool flushed; + bool found; + isc_result_t result; + named_cache_t *nsc; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Look for the view name. */ + ptr = next_token(lex, NULL); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + flushed = true; + found = false; + + /* + * Flushing a cache is tricky when caches are shared by multiple views. + * We first identify which caches should be flushed in the local cache + * list, flush these caches, and then update other views that refer to + * the flushed cache DB. + */ + if (ptr != NULL) { + /* + * Mark caches that need to be flushed. This is an O(#view^2) + * operation in the very worst case, but should be normally + * much more lightweight because only a few (most typically just + * one) views will match. + */ + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (strcasecmp(ptr, view->name) != 0) { + continue; + } + found = true; + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; nsc = ISC_LIST_NEXT(nsc, link)) + { + if (nsc->cache == view->cache) { + break; + } + } + INSIST(nsc != NULL); + nsc->needflush = true; + } + } else { + found = true; + } + + /* Perform flush */ + for (nsc = ISC_LIST_HEAD(server->cachelist); nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) + { + if (ptr != NULL && !nsc->needflush) { + continue; + } + nsc->needflush = true; + result = dns_view_flushcache(nsc->primaryview, false); + if (result != ISC_R_SUCCESS) { + flushed = false; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing cache in view '%s' failed: %s", + nsc->primaryview->name, + isc_result_totext(result)); + } + } + + /* + * Fix up views that share a flushed cache: let the views update the + * cache DB they're referring to. This could also be an expensive + * operation, but should typically be marginal: the inner loop is only + * necessary for views that share a cache, and if there are many such + * views the number of shared cache should normally be small. + * A worst case is that we have n views and n/2 caches, each shared by + * two views. Then this will be a O(n^2/4) operation. + */ + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!dns_view_iscacheshared(view)) { + continue; + } + for (nsc = ISC_LIST_HEAD(server->cachelist); nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) + { + if (!nsc->needflush || nsc->cache != view->cache) { + continue; + } + result = dns_view_flushcache(view, true); + if (result != ISC_R_SUCCESS) { + flushed = false; + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "fixing cache in view '%s' " + "failed: %s", + view->name, isc_result_totext(result)); + } + } + } + + /* Cleanup the cache list. */ + for (nsc = ISC_LIST_HEAD(server->cachelist); nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) + { + nsc->needflush = false; + } + + if (flushed && found) { + if (ptr != NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing cache in view '%s' succeeded", + ptr); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing caches in all views succeeded"); + } + result = ISC_R_SUCCESS; + } else { + if (!found) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing cache in view '%s' failed: " + "view not found", + ptr); + result = ISC_R_NOTFOUND; + } else { + result = ISC_R_FAILURE; + } + } + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +named_server_flushnode(named_server_t *server, isc_lex_t *lex, bool tree) { + char *ptr, *viewname; + char target[DNS_NAME_FORMATSIZE]; + dns_view_t *view; + bool flushed; + bool found; + isc_result_t result; + isc_buffer_t b; + dns_fixedname_t fixed; + dns_name_t *name; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Find the domain name to flush. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + strlcpy(target, ptr, DNS_NAME_FORMATSIZE); + isc_buffer_constinit(&b, target, strlen(target)); + isc_buffer_add(&b, strlen(target)); + name = dns_fixedname_initname(&fixed); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* Look for the view name. */ + viewname = next_token(lex, NULL); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + flushed = true; + found = false; + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewname != NULL && strcasecmp(viewname, view->name) != 0) { + continue; + } + found = true; + /* + * It's a little inefficient to try flushing name for all views + * if some of the views share a single cache. But since the + * operation is lightweight we prefer simplicity here. + */ + result = dns_view_flushnode(view, name, tree); + if (result != ISC_R_SUCCESS) { + flushed = false; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing %s '%s' in cache view '%s' " + "failed: %s", + tree ? "tree" : "name", target, + view->name, isc_result_totext(result)); + } + } + if (flushed && found) { + if (viewname != NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing %s '%s' in cache view '%s' " + "succeeded", + tree ? "tree" : "name", target, viewname); + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing %s '%s' in all cache views " + "succeeded", + tree ? "tree" : "name", target); + } + result = ISC_R_SUCCESS; + } else { + if (!found) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing %s '%s' in cache view '%s' " + "failed: view not found", + tree ? "tree" : "name", target, viewname); + } + result = ISC_R_FAILURE; + } + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +named_server_status(named_server_t *server, isc_buffer_t **text) { + isc_result_t result; + unsigned int zonecount, xferrunning, xferdeferred, soaqueries; + unsigned int automatic; + const char *ob = "", *cb = "", *alt = ""; + char boottime[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char configtime[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char line[1024], hostname[256]; + named_reload_t reload_status; + + REQUIRE(text != NULL); + + if (named_g_server->version_set) { + ob = " ("; + cb = ")"; + if (named_g_server->version == NULL) { + alt = "version.bind/txt/ch disabled"; + } else { + alt = named_g_server->version; + } + } + zonecount = dns_zonemgr_getcount(server->zonemgr, DNS_ZONESTATE_ANY); + xferrunning = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_XFERRUNNING); + xferdeferred = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_XFERDEFERRED); + soaqueries = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_SOAQUERY); + automatic = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_AUTOMATIC); + + isc_time_formathttptimestamp(&named_g_boottime, boottime, + sizeof(boottime)); + isc_time_formathttptimestamp(&named_g_configtime, configtime, + sizeof(configtime)); + + snprintf(line, sizeof(line), "version: %s%s <id:%s>%s%s%s\n", + PACKAGE_STRING, PACKAGE_DESCRIPTION, PACKAGE_SRCID, ob, alt, + cb); + CHECK(putstr(text, line)); + + if (gethostname(hostname, sizeof(hostname)) == 0) { + strlcpy(hostname, "localhost", sizeof(hostname)); + } + snprintf(line, sizeof(line), "running on %s: %s\n", hostname, + named_os_uname()); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "boot time: %s\n", boottime); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "last configured: %s\n", configtime); + CHECK(putstr(text, line)); + + if (named_g_chrootdir != NULL) { + snprintf(line, sizeof(line), "configuration file: %s (%s%s)\n", + named_g_conffile, named_g_chrootdir, named_g_conffile); + } else { + snprintf(line, sizeof(line), "configuration file: %s\n", + named_g_conffile); + } + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "CPUs found: %u\n", named_g_cpus_detected); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "worker threads: %u\n", named_g_cpus); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "UDP listeners per interface: %u\n", + named_g_udpdisp); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "number of zones: %u (%u automatic)\n", + zonecount, automatic); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "debug level: %u\n", named_g_debuglevel); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "xfers running: %u\n", xferrunning); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "xfers deferred: %u\n", xferdeferred); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "soa queries in progress: %u\n", + soaqueries); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "query logging is %s\n", + ns_server_getoption(server->sctx, NS_SERVER_LOGQUERIES) + ? "ON" + : "OFF"); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "recursive clients: %u/%u/%u\n", + isc_quota_getused(&server->sctx->recursionquota), + isc_quota_getsoft(&server->sctx->recursionquota), + isc_quota_getmax(&server->sctx->recursionquota)); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "tcp clients: %u/%u\n", + isc_quota_getused(&server->sctx->tcpquota), + isc_quota_getmax(&server->sctx->tcpquota)); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "TCP high-water: %u\n", + (unsigned)ns_stats_get_counter(server->sctx->nsstats, + ns_statscounter_tcphighwater)); + CHECK(putstr(text, line)); + + reload_status = atomic_load(&server->reload_status); + if (reload_status != NAMED_RELOAD_DONE) { + snprintf(line, sizeof(line), "reload/reconfig %s\n", + (reload_status == NAMED_RELOAD_FAILED + ? "failed" + : "in progress")); + CHECK(putstr(text, line)); + } + + CHECK(putstr(text, "server is up and running")); + CHECK(putnull(text)); + + return (ISC_R_SUCCESS); +cleanup: + return (result); +} + +isc_result_t +named_server_testgen(isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + char *ptr; + unsigned long count; + unsigned long i; + const unsigned char chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + ptr = next_token(lex, text); + if (ptr == NULL) { + count = 26; + } else { + count = strtoul(ptr, NULL, 10); + } + + CHECK(isc_buffer_reserve(text, count)); + for (i = 0; i < count; i++) { + CHECK(putuint8(text, chars[i % (sizeof(chars) - 1)])); + } + + CHECK(putnull(text)); + +cleanup: + return (result); +} + +static isc_result_t +delete_keynames(dns_tsig_keyring_t *ring, char *target, + unsigned int *foundkeys) { + char namestr[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + +again: + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin); + if (result == ISC_R_NOTFOUND) { + dns_rbtnodechain_invalidate(&chain); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + + if (tkey != NULL) { + if (!tkey->generated) { + goto nextkey; + } + + dns_name_format(&tkey->name, namestr, sizeof(namestr)); + if (strcmp(namestr, target) == 0) { + (*foundkeys)++; + dns_rbtnodechain_invalidate(&chain); + (void)dns_rbt_deletename(ring->keys, + &tkey->name, false); + goto again; + } + } + + nextkey: + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result == ISC_R_NOMORE) { + break; + } + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +named_server_tsigdelete(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_view_t *view; + unsigned int foundkeys = 0; + char *ptr, *viewname; + char target[DNS_NAME_FORMATSIZE]; + char fbuf[16]; + + REQUIRE(text != NULL); + + (void)next_token(lex, text); /* skip command name */ + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + strlcpy(target, ptr, DNS_NAME_FORMATSIZE); + + viewname = next_token(lex, text); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewname == NULL || strcmp(view->name, viewname) == 0) { + RWLOCK(&view->dynamickeys->lock, isc_rwlocktype_write); + result = delete_keynames(view->dynamickeys, target, + &foundkeys); + RWUNLOCK(&view->dynamickeys->lock, + isc_rwlocktype_write); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + return (result); + } + } + } + isc_task_endexclusive(server->task); + + snprintf(fbuf, sizeof(fbuf), "%u", foundkeys); + + CHECK(putstr(text, fbuf)); + CHECK(putstr(text, " tsig keys deleted.")); + CHECK(putnull(text)); + +cleanup: + return (result); +} + +static isc_result_t +list_keynames(dns_view_t *view, dns_tsig_keyring_t *ring, isc_buffer_t **text, + unsigned int *foundkeys) { + char namestr[DNS_NAME_FORMATSIZE]; + char creatorstr[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + const char *viewname; + + if (view != NULL) { + viewname = view->name; + } else { + viewname = "(global)"; + } + + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin); + if (result == ISC_R_NOTFOUND) { + dns_rbtnodechain_invalidate(&chain); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + + if (tkey != NULL) { + dns_name_format(&tkey->name, namestr, sizeof(namestr)); + if (tkey->generated) { + dns_name_format(tkey->creator, creatorstr, + sizeof(creatorstr)); + if (*foundkeys != 0) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, "view \"")); + CHECK(putstr(text, viewname)); + CHECK(putstr(text, "\"; type \"dynamic\"; key " + "\"")); + CHECK(putstr(text, namestr)); + CHECK(putstr(text, "\"; creator \"")); + CHECK(putstr(text, creatorstr)); + CHECK(putstr(text, "\";")); + } else { + if (*foundkeys != 0) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, "view \"")); + CHECK(putstr(text, viewname)); + CHECK(putstr(text, "\"; type \"static\"; key " + "\"")); + CHECK(putstr(text, namestr)); + CHECK(putstr(text, "\";")); + } + (*foundkeys)++; + } + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result == ISC_R_NOMORE || result == DNS_R_NEWORIGIN) { + break; + } + } + + return (ISC_R_SUCCESS); +cleanup: + dns_rbtnodechain_invalidate(&chain); + return (result); +} + +isc_result_t +named_server_tsiglist(named_server_t *server, isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_view_t *view; + unsigned int foundkeys = 0; + + REQUIRE(text != NULL); + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + RWLOCK(&view->statickeys->lock, isc_rwlocktype_read); + result = list_keynames(view, view->statickeys, text, + &foundkeys); + RWUNLOCK(&view->statickeys->lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS) { + return (result); + } + RWLOCK(&view->dynamickeys->lock, isc_rwlocktype_read); + result = list_keynames(view, view->dynamickeys, text, + &foundkeys); + RWUNLOCK(&view->dynamickeys->lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (foundkeys == 0) { + CHECK(putstr(text, "no tsig keys found.")); + } + + if (isc_buffer_usedlength(*text) > 0) { + CHECK(putnull(text)); + } + +cleanup: + return (result); +} + +/* + * Act on a "sign" or "loadkeys" command from the command channel. + */ +isc_result_t +named_server_rekey(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zonetype_t type; + uint16_t keyopts; + bool fullsign = false; + char *ptr; + + REQUIRE(text != NULL); + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcasecmp(ptr, NAMED_COMMAND_SIGN) == 0) { + fullsign = true; + } + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &zone, NULL, text, false); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (zone == NULL) { + return (ISC_R_UNEXPECTEDEND); /* XXX: or do all zones? */ + } + + type = dns_zone_gettype(zone); + if (type != dns_zone_primary) { + dns_zone_detach(&zone); + return (DNS_R_NOTPRIMARY); + } + + keyopts = dns_zone_getkeyopts(zone); + + /* + * "rndc loadkeys" requires "auto-dnssec maintain" + * or a "dnssec-policy". + */ + if ((keyopts & DNS_ZONEKEY_ALLOW) == 0) { + result = ISC_R_NOPERM; + } else if ((keyopts & DNS_ZONEKEY_MAINTAIN) == 0 && !fullsign) { + result = ISC_R_NOPERM; + } else { + dns_zone_rekey(zone, fullsign); + } + + dns_zone_detach(&zone); + return (result); +} + +/* + * Act on a "sync" command from the command channel. + */ +static isc_result_t +synczone(dns_zone_t *zone, void *uap) { + bool cleanup = *(bool *)uap; + isc_result_t result; + dns_zone_t *raw = NULL; + char *journal; + + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + synczone(raw, uap); + dns_zone_detach(&raw); + } + + result = dns_zone_flush(zone); + if (result != ISC_R_SUCCESS) { + cleanup = false; + } + if (cleanup) { + journal = dns_zone_getjournal(zone); + if (journal != NULL) { + (void)isc_file_remove(journal); + } + } + + return (result); +} + +isc_result_t +named_server_sync(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result, tresult; + dns_view_t *view; + dns_zone_t *zone = NULL; + char classstr[DNS_RDATACLASS_FORMATSIZE]; + char zonename[DNS_NAME_FORMATSIZE]; + const char *vname, *sep, *arg; + bool cleanup = false; + + REQUIRE(text != NULL); + + (void)next_token(lex, text); + + arg = next_token(lex, text); + if (arg != NULL && + (strcmp(arg, "-clean") == 0 || strcmp(arg, "-clear") == 0)) + { + cleanup = true; + arg = next_token(lex, text); + } + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, arg, &zone, NULL, text, false); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (zone == NULL) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tresult = ISC_R_SUCCESS; + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + result = dns_zt_apply(view->zonetable, + isc_rwlocktype_none, false, NULL, + synczone, &cleanup); + if (result != ISC_R_SUCCESS && tresult == ISC_R_SUCCESS) + { + tresult = result; + } + } + isc_task_endexclusive(server->task); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumping all zones%s: %s", + cleanup ? ", removing journal files" : "", + isc_result_totext(result)); + return (tresult); + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = synczone(zone, &cleanup); + isc_task_endexclusive(server->task); + + view = dns_zone_getview(zone); + if (strcmp(view->name, "_default") == 0 || + strcmp(view->name, "_bind") == 0) + { + vname = ""; + sep = ""; + } else { + vname = view->name; + sep = " "; + } + dns_rdataclass_format(dns_zone_getclass(zone), classstr, + sizeof(classstr)); + dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename)); + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, + ISC_LOG_INFO, "sync: dumping zone '%s/%s'%s%s%s: %s", zonename, + classstr, sep, vname, cleanup ? ", removing journal file" : "", + isc_result_totext(result)); + dns_zone_detach(&zone); + return (result); +} + +/* + * Act on a "freeze" or "thaw" command from the command channel. + */ +isc_result_t +named_server_freeze(named_server_t *server, bool freeze, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result, tresult; + dns_zone_t *mayberaw = NULL, *raw = NULL; + dns_zonetype_t type; + char classstr[DNS_RDATACLASS_FORMATSIZE]; + char zonename[DNS_NAME_FORMATSIZE]; + dns_view_t *view; + const char *vname, *sep; + bool frozen; + const char *msg = NULL; + + REQUIRE(text != NULL); + + result = zone_from_args(server, lex, NULL, &mayberaw, NULL, text, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (mayberaw == NULL) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tresult = ISC_R_SUCCESS; + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + result = dns_view_freezezones(view, freeze); + if (result != ISC_R_SUCCESS && tresult == ISC_R_SUCCESS) + { + tresult = result; + } + } + isc_task_endexclusive(server->task); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s all zones: %s", + freeze ? "freezing" : "thawing", + isc_result_totext(tresult)); + return (tresult); + } + dns_zone_getraw(mayberaw, &raw); + if (raw != NULL) { + dns_zone_detach(&mayberaw); + dns_zone_attach(raw, &mayberaw); + dns_zone_detach(&raw); + } + type = dns_zone_gettype(mayberaw); + if (type != dns_zone_primary) { + dns_zone_detach(&mayberaw); + return (DNS_R_NOTPRIMARY); + } + + if (freeze && !dns_zone_isdynamic(mayberaw, true)) { + dns_zone_detach(&mayberaw); + return (DNS_R_NOTDYNAMIC); + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + frozen = dns_zone_getupdatedisabled(mayberaw); + if (freeze) { + if (frozen) { + msg = "WARNING: The zone was already frozen.\n" + "Someone else may be editing it or " + "it may still be re-loading."; + result = DNS_R_FROZEN; + } + if (result == ISC_R_SUCCESS) { + result = dns_zone_flush(mayberaw); + if (result != ISC_R_SUCCESS) { + msg = "Flushing the zone updates to " + "disk failed."; + } + } + if (result == ISC_R_SUCCESS) { + dns_zone_setupdatedisabled(mayberaw, freeze); + } + } else { + if (frozen) { + result = dns_zone_loadandthaw(mayberaw); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UPTODATE: + msg = "The zone reload and thaw was " + "successful."; + result = ISC_R_SUCCESS; + break; + case DNS_R_CONTINUE: + msg = "A zone reload and thaw was started.\n" + "Check the logs to see the result."; + result = ISC_R_SUCCESS; + break; + default: + break; + } + } + } + isc_task_endexclusive(server->task); + + if (msg != NULL) { + (void)putstr(text, msg); + (void)putnull(text); + } + + view = dns_zone_getview(mayberaw); + if (strcmp(view->name, "_default") == 0 || + strcmp(view->name, "_bind") == 0) + { + vname = ""; + sep = ""; + } else { + vname = view->name; + sep = " "; + } + dns_rdataclass_format(dns_zone_getclass(mayberaw), classstr, + sizeof(classstr)); + dns_name_format(dns_zone_getorigin(mayberaw), zonename, + sizeof(zonename)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s zone '%s/%s'%s%s: %s", + freeze ? "freezing" : "thawing", zonename, classstr, sep, + vname, isc_result_totext(result)); + dns_zone_detach(&mayberaw); + return (result); +} + +#ifdef HAVE_LIBSCF +/* + * This function adds a message for rndc to echo if named + * is managed by smf and is also running chroot. + */ +isc_result_t +named_smf_add_message(isc_buffer_t **text) { + REQUIRE(text != NULL); + + return (putstr(text, "use svcadm(1M) to manage named")); +} +#endif /* HAVE_LIBSCF */ + +#ifndef HAVE_LMDB + +/* + * Emit a comment at the top of the nzf file containing the viewname + * Expects the fp to already be open for writing + */ +#define HEADER1 "# New zone file for view: " +#define HEADER2 \ + "\n# This file contains configuration for zones added by\n" \ + "# the 'rndc addzone' command. DO NOT EDIT BY HAND.\n" +static isc_result_t +add_comment(FILE *fp, const char *viewname) { + isc_result_t result; + CHECK(isc_stdio_write(HEADER1, sizeof(HEADER1) - 1, 1, fp, NULL)); + CHECK(isc_stdio_write(viewname, strlen(viewname), 1, fp, NULL)); + CHECK(isc_stdio_write(HEADER2, sizeof(HEADER2) - 1, 1, fp, NULL)); +cleanup: + return (result); +} + +static void +dumpzone(void *arg, const char *buf, int len) { + FILE *fp = arg; + + (void)isc_stdio_write(buf, len, 1, fp, NULL); +} + +static isc_result_t +nzf_append(dns_view_t *view, const cfg_obj_t *zconfig) { + isc_result_t result; + off_t offset; + FILE *fp = NULL; + bool offsetok = false; + + LOCK(&view->new_zone_lock); + + CHECK(isc_stdio_open(view->new_zone_file, "a", &fp)); + CHECK(isc_stdio_seek(fp, 0, SEEK_END)); + + CHECK(isc_stdio_tell(fp, &offset)); + offsetok = true; + if (offset == 0) { + CHECK(add_comment(fp, view->name)); + } + + CHECK(isc_stdio_write("zone ", 5, 1, fp, NULL)); + cfg_printx(zconfig, CFG_PRINTER_ONELINE, dumpzone, fp); + CHECK(isc_stdio_write(";\n", 2, 1, fp, NULL)); + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + +cleanup: + if (fp != NULL) { + (void)isc_stdio_close(fp); + if (offsetok) { + isc_result_t result2; + + result2 = isc_file_truncate(view->new_zone_file, + offset); + if (result2 != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error truncating NZF file '%s' " + "during rollback from append: " + "%s", + view->new_zone_file, + isc_result_totext(result2)); + } + } + } + UNLOCK(&view->new_zone_lock); + return (result); +} + +static isc_result_t +nzf_writeconf(const cfg_obj_t *config, dns_view_t *view) { + const cfg_obj_t *zl = NULL; + cfg_list_t *list; + const cfg_listelt_t *elt; + + FILE *fp = NULL; + char tmp[1024]; + isc_result_t result; + + result = isc_file_template(view->new_zone_file, "nzf-XXXXXXXX", tmp, + sizeof(tmp)); + if (result == ISC_R_SUCCESS) { + result = isc_file_openunique(tmp, &fp); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + cfg_map_get(config, "zone", &zl); + if (!cfg_obj_islist(zl)) { + CHECK(ISC_R_FAILURE); + } + + DE_CONST(&zl->value.list, list); + + CHECK(add_comment(fp, view->name)); /* force a comment */ + + for (elt = ISC_LIST_HEAD(*list); elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(elt); + + CHECK(isc_stdio_write("zone ", 5, 1, fp, NULL)); + cfg_printx(zconfig, CFG_PRINTER_ONELINE, dumpzone, fp); + CHECK(isc_stdio_write(";\n", 2, 1, fp, NULL)); + } + + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + CHECK(isc_file_rename(tmp, view->new_zone_file)); + return (result); + +cleanup: + if (fp != NULL) { + (void)isc_stdio_close(fp); + } + (void)isc_file_remove(tmp); + return (result); +} + +#else /* HAVE_LMDB */ + +static void +nzd_setkey(MDB_val *key, dns_name_t *name, char *namebuf, size_t buflen) { + dns_fixedname_t fixed; + + dns_fixedname_init(&fixed); + dns_name_downcase(name, dns_fixedname_name(&fixed), NULL); + dns_name_format(dns_fixedname_name(&fixed), namebuf, buflen); + + key->mv_data = namebuf; + key->mv_size = strlen(namebuf); +} + +static void +dumpzone(void *arg, const char *buf, int len) { + ns_dzarg_t *dzarg = arg; + isc_result_t result; + + REQUIRE(dzarg != NULL && ISC_MAGIC_VALID(dzarg, DZARG_MAGIC)); + + result = putmem(dzarg->text, buf, len); + if (result != ISC_R_SUCCESS && dzarg->result == ISC_R_SUCCESS) { + dzarg->result = result; + } +} + +static isc_result_t +nzd_save(MDB_txn **txnp, MDB_dbi dbi, dns_zone_t *zone, + const cfg_obj_t *zconfig) { + isc_result_t result; + int status; + dns_view_t *view; + bool commit = false; + isc_buffer_t *text = NULL; + char namebuf[1024]; + MDB_val key, data; + ns_dzarg_t dzarg; + + view = dns_zone_getview(zone); + + nzd_setkey(&key, dns_zone_getorigin(zone), namebuf, sizeof(namebuf)); + + if (zconfig == NULL) { + /* We're deleting the zone from the database */ + status = mdb_del(*txnp, dbi, &key, NULL); + if (status != MDB_SUCCESS && status != MDB_NOTFOUND) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error deleting zone %s " + "from NZD database: %s", + namebuf, mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } else if (status != MDB_NOTFOUND) { + commit = true; + } + } else { + /* We're creating or overwriting the zone */ + const cfg_obj_t *zoptions; + + isc_buffer_allocate(view->mctx, &text, 256); + + zoptions = cfg_tuple_get(zconfig, "options"); + if (zoptions == NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Unable to get options from config in " + "nzd_save()"); + result = ISC_R_FAILURE; + goto cleanup; + } + + dzarg.magic = DZARG_MAGIC; + dzarg.text = &text; + dzarg.result = ISC_R_SUCCESS; + cfg_printx(zoptions, CFG_PRINTER_ONELINE, dumpzone, &dzarg); + if (dzarg.result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error writing zone config to " + "buffer in nzd_save(): %s", + isc_result_totext(dzarg.result)); + result = dzarg.result; + goto cleanup; + } + + data.mv_data = isc_buffer_base(text); + data.mv_size = isc_buffer_usedlength(text); + + status = mdb_put(*txnp, dbi, &key, &data, 0); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error inserting zone in " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + commit = true; + } + + result = ISC_R_SUCCESS; + +cleanup: + if (!commit || result != ISC_R_SUCCESS) { + (void)mdb_txn_abort(*txnp); + } else { + status = mdb_txn_commit(*txnp); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error committing " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + } + } + *txnp = NULL; + + if (text != NULL) { + isc_buffer_free(&text); + } + + return (result); +} + +/* + * Check whether the new zone database for 'view' can be opened for writing. + * + * Caller must hold 'view->new_zone_lock'. + */ +static isc_result_t +nzd_writable(dns_view_t *view) { + isc_result_t result = ISC_R_SUCCESS; + int status; + MDB_dbi dbi; + MDB_txn *txn = NULL; + + REQUIRE(view != NULL); + + status = mdb_txn_begin((MDB_env *)view->new_zone_dbenv, 0, 0, &txn); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "mdb_txn_begin: %s", mdb_strerror(status)); + return (ISC_R_FAILURE); + } + + status = mdb_dbi_open(txn, NULL, 0, &dbi); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "mdb_dbi_open: %s", mdb_strerror(status)); + result = ISC_R_FAILURE; + } + + mdb_txn_abort(txn); + return (result); +} + +/* + * Open the new zone database for 'view' and start a transaction for it. + * + * Caller must hold 'view->new_zone_lock'. + */ +static isc_result_t +nzd_open(dns_view_t *view, unsigned int flags, MDB_txn **txnp, MDB_dbi *dbi) { + int status; + MDB_txn *txn = NULL; + + REQUIRE(view != NULL); + REQUIRE(txnp != NULL && *txnp == NULL); + REQUIRE(dbi != NULL); + + status = mdb_txn_begin((MDB_env *)view->new_zone_dbenv, 0, flags, &txn); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "mdb_txn_begin: %s", mdb_strerror(status)); + goto cleanup; + } + + status = mdb_dbi_open(txn, NULL, 0, dbi); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "mdb_dbi_open: %s", mdb_strerror(status)); + goto cleanup; + } + + *txnp = txn; + +cleanup: + if (status != MDB_SUCCESS) { + if (txn != NULL) { + mdb_txn_abort(txn); + } + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +/* + * nzd_env_close() and nzd_env_reopen are a kluge to address the + * problem of an NZD file possibly being created before we drop + * root privileges. + */ +static void +nzd_env_close(dns_view_t *view) { + const char *dbpath = NULL; + char dbpath_copy[PATH_MAX]; + char lockpath[PATH_MAX]; + int status, ret; + + if (view->new_zone_dbenv == NULL) { + return; + } + + status = mdb_env_get_path(view->new_zone_dbenv, &dbpath); + INSIST(status == MDB_SUCCESS); + snprintf(lockpath, sizeof(lockpath), "%s-lock", dbpath); + strlcpy(dbpath_copy, dbpath, sizeof(dbpath_copy)); + mdb_env_close((MDB_env *)view->new_zone_dbenv); + + /* + * Database files must be owned by the eventual user, not by root. + */ + ret = chown(dbpath_copy, ns_os_uid(), -1); + UNUSED(ret); + + /* + * Some platforms need the lockfile not to exist when we reopen the + * environment. + */ + (void)isc_file_remove(lockpath); + + view->new_zone_dbenv = NULL; +} + +static isc_result_t +nzd_env_reopen(dns_view_t *view) { + isc_result_t result; + MDB_env *env = NULL; + int status; + + if (view->new_zone_db == NULL) { + return (ISC_R_SUCCESS); + } + + nzd_env_close(view); + + status = mdb_env_create(&env); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_create failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + if (view->new_zone_mapsize != 0ULL) { + status = mdb_env_set_mapsize(env, view->new_zone_mapsize); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_set_mapsize failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + } + + status = mdb_env_open(env, view->new_zone_db, DNS_LMDB_FLAGS, 0600); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_open of '%s' failed: %s", + view->new_zone_db, mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + view->new_zone_dbenv = env; + env = NULL; + result = ISC_R_SUCCESS; + +cleanup: + if (env != NULL) { + mdb_env_close(env); + } + return (result); +} + +/* + * If 'commit' is true, commit the new zone database transaction pointed to by + * 'txnp'; otherwise, abort that transaction. + * + * Caller must hold 'view->new_zone_lock' for the view that the transaction + * pointed to by 'txnp' was started for. + */ +static isc_result_t +nzd_close(MDB_txn **txnp, bool commit) { + isc_result_t result = ISC_R_SUCCESS; + int status; + + REQUIRE(txnp != NULL); + + if (*txnp != NULL) { + if (commit) { + status = mdb_txn_commit(*txnp); + if (status != MDB_SUCCESS) { + result = ISC_R_FAILURE; + } + } else { + mdb_txn_abort(*txnp); + } + *txnp = NULL; + } + + return (result); +} + +/* + * Count the zones configured in the new zone database for 'view' and store the + * result in 'countp'. + * + * Caller must hold 'view->new_zone_lock'. + */ +static isc_result_t +nzd_count(dns_view_t *view, int *countp) { + isc_result_t result; + int status; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_stat statbuf; + + REQUIRE(countp != NULL); + + result = nzd_open(view, MDB_RDONLY, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + status = mdb_stat(txn, dbi, &statbuf); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "mdb_stat: %s", mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + *countp = statbuf.ms_entries; + +cleanup: + (void)nzd_close(&txn, false); + + return (result); +} + +/* + * Migrate zone configuration from an NZF file to an NZD database. + * Caller must hold view->new_zone_lock. + */ +static isc_result_t +migrate_nzf(dns_view_t *view) { + isc_result_t result; + cfg_obj_t *nzf_config = NULL; + int status, n; + isc_buffer_t *text = NULL; + bool commit = false; + const cfg_obj_t *zonelist; + const cfg_listelt_t *element; + char tempname[PATH_MAX]; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_val key, data; + ns_dzarg_t dzarg; + + /* + * If NZF file doesn't exist, or NZD DB exists and already + * has data, return without attempting migration. + */ + if (!isc_file_exists(view->new_zone_file)) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + result = nzd_count(view, &n); + if (result == ISC_R_SUCCESS && n > 0) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "Migrating zones from NZF file '%s' to " + "NZD database '%s'", + view->new_zone_file, view->new_zone_db); + /* + * Instead of blindly copying lines, we parse the NZF file using + * the configuration parser, because it validates it against the + * config type, giving us a guarantee that valid configuration + * will be written to DB. + */ + cfg_parser_reset(named_g_addparser); + result = cfg_parse_file(named_g_addparser, view->new_zone_file, + &cfg_type_addzoneconf, &nzf_config); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error parsing NZF file '%s': %s", + view->new_zone_file, isc_result_totext(result)); + goto cleanup; + } + + zonelist = NULL; + CHECK(cfg_map_get(nzf_config, "zone", &zonelist)); + if (!cfg_obj_islist(zonelist)) { + CHECK(ISC_R_FAILURE); + } + + CHECK(nzd_open(view, 0, &txn, &dbi)); + + isc_buffer_allocate(view->mctx, &text, 256); + + for (element = cfg_list_first(zonelist); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig; + const cfg_obj_t *zoptions; + char zname[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fname; + dns_name_t *name; + const char *origin; + isc_buffer_t b; + + zconfig = cfg_listelt_value(element); + + origin = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + if (origin == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* Normalize zone name */ + isc_buffer_constinit(&b, origin, strlen(origin)); + isc_buffer_add(&b, strlen(origin)); + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(name, &b, dns_rootname, + DNS_NAME_DOWNCASE, NULL)); + dns_name_format(name, zname, sizeof(zname)); + + key.mv_data = zname; + key.mv_size = strlen(zname); + + zoptions = cfg_tuple_get(zconfig, "options"); + if (zoptions == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + isc_buffer_clear(text); + dzarg.magic = DZARG_MAGIC; + dzarg.text = &text; + dzarg.result = ISC_R_SUCCESS; + cfg_printx(zoptions, CFG_PRINTER_ONELINE, dumpzone, &dzarg); + if (dzarg.result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error writing zone config to " + "buffer in migrate_nzf(): %s", + isc_result_totext(result)); + result = dzarg.result; + goto cleanup; + } + + data.mv_data = isc_buffer_base(text); + data.mv_size = isc_buffer_usedlength(text); + + status = mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE); + if (status != MDB_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Error inserting zone in " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + commit = true; + } + + result = ISC_R_SUCCESS; + + /* + * Leaving the NZF file in place is harmless as we won't use it + * if an NZD database is found for the view. But we rename NZF file + * to a backup name here. + */ + strlcpy(tempname, view->new_zone_file, sizeof(tempname)); + if (strlen(tempname) < sizeof(tempname) - 1) { + strlcat(tempname, "~", sizeof(tempname)); + isc_file_rename(view->new_zone_file, tempname); + } + +cleanup: + if (result != ISC_R_SUCCESS) { + (void)nzd_close(&txn, false); + } else { + result = nzd_close(&txn, commit); + } + + if (text != NULL) { + isc_buffer_free(&text); + } + + if (nzf_config != NULL) { + cfg_obj_destroy(named_g_addparser, &nzf_config); + } + + return (result); +} + +#endif /* HAVE_LMDB */ + +static isc_result_t +newzone_parse(named_server_t *server, char *command, dns_view_t **viewp, + cfg_obj_t **zoneconfp, const cfg_obj_t **zoneobjp, + bool *redirectp, isc_buffer_t **text) { + isc_result_t result; + isc_buffer_t argbuf; + bool redirect = false; + cfg_obj_t *zoneconf = NULL; + const cfg_obj_t *zlist = NULL; + const cfg_obj_t *zoneobj = NULL; + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *obj = NULL; + const char *viewname = NULL; + dns_rdataclass_t rdclass; + dns_view_t *view = NULL; + const char *bn = NULL; + + REQUIRE(viewp != NULL && *viewp == NULL); + REQUIRE(zoneobjp != NULL && *zoneobjp == NULL); + REQUIRE(zoneconfp != NULL && *zoneconfp == NULL); + REQUIRE(redirectp != NULL); + + /* Try to parse the argument string */ + isc_buffer_init(&argbuf, command, (unsigned int)strlen(command)); + isc_buffer_add(&argbuf, strlen(command)); + + if (strncasecmp(command, "add", 3) == 0) { + bn = "addzone"; + } else if (strncasecmp(command, "mod", 3) == 0) { + bn = "modzone"; + } else { + UNREACHABLE(); + } + + /* + * Convert the "addzone" or "modzone" to just "zone", for + * the benefit of the parser + */ + isc_buffer_forward(&argbuf, 3); + + cfg_parser_reset(named_g_addparser); + CHECK(cfg_parse_buffer(named_g_addparser, &argbuf, bn, 0, + &cfg_type_addzoneconf, 0, &zoneconf)); + CHECK(cfg_map_get(zoneconf, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) { + CHECK(ISC_R_FAILURE); + } + + /* For now we only support adding one zone at a time */ + zoneobj = cfg_listelt_value(cfg_list_first(zlist)); + + /* Check the zone type for ones that are not supported by addzone. */ + zoptions = cfg_tuple_get(zoneobj, "options"); + + obj = NULL; + (void)cfg_map_get(zoptions, "type", &obj); + if (obj == NULL) { + (void)cfg_map_get(zoptions, "in-view", &obj); + if (obj != NULL) { + (void)putstr(text, "'in-view' zones not supported by "); + (void)putstr(text, bn); + } else { + (void)putstr(text, "zone type not specified"); + } + CHECK(ISC_R_FAILURE); + } + + if (strcasecmp(cfg_obj_asstring(obj), "hint") == 0 || + strcasecmp(cfg_obj_asstring(obj), "forward") == 0 || + strcasecmp(cfg_obj_asstring(obj), "delegation-only") == 0) + { + (void)putstr(text, "'"); + (void)putstr(text, cfg_obj_asstring(obj)); + (void)putstr(text, "' zones not supported by "); + (void)putstr(text, bn); + CHECK(ISC_R_FAILURE); + } + + if (strcasecmp(cfg_obj_asstring(obj), "redirect") == 0) { + redirect = true; + } + + /* Make sense of optional class argument */ + obj = cfg_tuple_get(zoneobj, "class"); + CHECK(named_config_getclass(obj, dns_rdataclass_in, &rdclass)); + + /* Make sense of optional view argument */ + obj = cfg_tuple_get(zoneobj, "view"); + if (obj && cfg_obj_isstring(obj)) { + viewname = cfg_obj_asstring(obj); + } + if (viewname == NULL || *viewname == '\0') { + viewname = "_default"; + } + result = dns_viewlist_find(&server->viewlist, viewname, rdclass, &view); + if (result == ISC_R_NOTFOUND) { + (void)putstr(text, "no matching view found for '"); + (void)putstr(text, viewname); + (void)putstr(text, "'"); + goto cleanup; + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + *viewp = view; + *zoneobjp = zoneobj; + *zoneconfp = zoneconf; + *redirectp = redirect; + + return (ISC_R_SUCCESS); + +cleanup: + if (zoneconf != NULL) { + cfg_obj_destroy(named_g_addparser, &zoneconf); + } + if (view != NULL) { + dns_view_detach(&view); + } + + return (result); +} + +static isc_result_t +delete_zoneconf(dns_view_t *view, cfg_parser_t *pctx, const cfg_obj_t *config, + const dns_name_t *zname, nzfwriter_t nzfwriter) { + isc_result_t result = ISC_R_NOTFOUND; + const cfg_listelt_t *elt = NULL; + const cfg_obj_t *zl = NULL; + cfg_list_t *list; + dns_fixedname_t myfixed; + dns_name_t *myname; + + REQUIRE(view != NULL); + REQUIRE(pctx != NULL); + REQUIRE(config != NULL); + REQUIRE(zname != NULL); + + LOCK(&view->new_zone_lock); + + cfg_map_get(config, "zone", &zl); + + if (!cfg_obj_islist(zl)) { + CHECK(ISC_R_FAILURE); + } + + DE_CONST(&zl->value.list, list); + + myname = dns_fixedname_initname(&myfixed); + + for (elt = ISC_LIST_HEAD(*list); elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) + { + const cfg_obj_t *zconf = cfg_listelt_value(elt); + const char *zn; + cfg_listelt_t *e; + + zn = cfg_obj_asstring(cfg_tuple_get(zconf, "name")); + result = dns_name_fromstring(myname, zn, 0, NULL); + if (result != ISC_R_SUCCESS || !dns_name_equal(zname, myname)) { + continue; + } + + DE_CONST(elt, e); + ISC_LIST_UNLINK(*list, e, link); + cfg_obj_destroy(pctx, &e->obj); + isc_mem_put(pctx->mctx, e, sizeof(*e)); + result = ISC_R_SUCCESS; + break; + } + + /* + * Write config to NZF file if appropriate + */ + if (nzfwriter != NULL && view->new_zone_file != NULL) { + result = nzfwriter(config, view); + } + +cleanup: + UNLOCK(&view->new_zone_lock); + return (result); +} + +static isc_result_t +do_addzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, + dns_name_t *name, cfg_obj_t *zoneconf, const cfg_obj_t *zoneobj, + bool redirect, isc_buffer_t **text) { + isc_result_t result, tresult; + dns_zone_t *zone = NULL; +#ifndef HAVE_LMDB + FILE *fp = NULL; + bool cleanup_config = false; +#else /* HAVE_LMDB */ + MDB_txn *txn = NULL; + MDB_dbi dbi; + bool locked = false; + + UNUSED(zoneconf); +#endif + + /* Zone shouldn't already exist */ + if (redirect) { + result = (view->redirect != NULL) ? ISC_R_SUCCESS + : ISC_R_NOTFOUND; + } else { + result = dns_zt_find(view->zonetable, name, 0, NULL, &zone); + } + if (result == ISC_R_SUCCESS) { + result = ISC_R_EXISTS; + goto cleanup; + } else if (result == DNS_R_PARTIALMATCH) { + /* Create our sub-zone anyway */ + dns_zone_detach(&zone); + zone = NULL; + } else if (result != ISC_R_NOTFOUND) { + goto cleanup; + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +#ifndef HAVE_LMDB + /* + * Make sure we can open the configuration save file + */ + result = isc_stdio_open(view->new_zone_file, "a", &fp); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + TCHECK(putstr(text, "unable to create '")); + TCHECK(putstr(text, view->new_zone_file)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + (void)isc_stdio_close(fp); + fp = NULL; +#else /* HAVE_LMDB */ + LOCK(&view->new_zone_lock); + locked = true; + /* Make sure we can open the NZD database */ + result = nzd_writable(view); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + TCHECK(putstr(text, "unable to open NZD database for '")); + TCHECK(putstr(text, view->new_zone_db)); + TCHECK(putstr(text, "'")); + result = ISC_R_FAILURE; + goto cleanup; + } +#endif /* HAVE_LMDB */ + + /* Mark view unfrozen and configure zone */ + dns_view_thaw(view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + server->mctx, view, &server->viewlist, + &server->kasplist, cfg->actx, true, false, + false); + dns_view_freeze(view); + + isc_task_endexclusive(server->task); + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "configure_zone failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + /* Is it there yet? */ + if (redirect) { + if (view->redirect == NULL) { + CHECK(ISC_R_NOTFOUND); + } + dns_zone_attach(view->redirect, &zone); + } else { + result = dns_zt_find(view->zonetable, name, 0, NULL, &zone); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "added new zone was not found: %s", + isc_result_totext(result)); + goto cleanup; + } + } + +#ifndef HAVE_LMDB + /* + * If there wasn't a previous newzone config, just save the one + * we've created. If there was a previous one, merge the new + * zone into it. + */ + if (cfg->nzf_config == NULL) { + cfg_obj_attach(zoneconf, &cfg->nzf_config); + } else { + cfg_obj_t *z; + DE_CONST(zoneobj, z); + CHECK(cfg_parser_mapadd(cfg->add_parser, cfg->nzf_config, z, + "zone")); + } + cleanup_config = true; +#endif /* HAVE_LMDB */ + + /* + * Load the zone from the master file. If this fails, we'll + * need to undo the configuration we've done already. + */ + result = dns_zone_load(zone, true); + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + + TCHECK(putstr(text, "dns_zone_loadnew failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "addzone failed; reverting."); + + /* If the zone loaded partially, unload it */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Remove the zone from the zone table */ + dns_zt_unmount(view->zonetable, zone); + goto cleanup; + } + + /* Flag the zone as having been added at runtime */ + dns_zone_setadded(zone, true); + +#ifdef HAVE_LMDB + /* Save the new zone configuration into the NZD */ + CHECK(nzd_open(view, 0, &txn, &dbi)); + CHECK(nzd_save(&txn, dbi, zone, zoneobj)); +#else /* ifdef HAVE_LMDB */ + /* Append the zone configuration to the NZF */ + result = nzf_append(view, zoneobj); +#endif /* HAVE_LMDB */ + +cleanup: + +#ifndef HAVE_LMDB + if (fp != NULL) { + (void)isc_stdio_close(fp); + } + if (result != ISC_R_SUCCESS && cleanup_config) { + tresult = delete_zoneconf(view, cfg->add_parser, + cfg->nzf_config, name, NULL); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + } +#else /* HAVE_LMDB */ + if (txn != NULL) { + (void)nzd_close(&txn, false); + } + if (locked) { + UNLOCK(&view->new_zone_lock); + } +#endif /* HAVE_LMDB */ + + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +static isc_result_t +do_modzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, + dns_name_t *name, const char *zname, const cfg_obj_t *zoneobj, + bool redirect, isc_buffer_t **text) { + isc_result_t result, tresult; + dns_zone_t *zone = NULL; + bool added; + bool exclusive = false; +#ifndef HAVE_LMDB + FILE *fp = NULL; + cfg_obj_t *z; +#else /* HAVE_LMDB */ + MDB_txn *txn = NULL; + MDB_dbi dbi; + bool locked = false; +#endif /* HAVE_LMDB */ + + /* Zone must already exist */ + if (redirect) { + if (view->redirect != NULL) { + dns_zone_attach(view->redirect, &zone); + result = ISC_R_SUCCESS; + } else { + result = ISC_R_NOTFOUND; + } + } else { + result = dns_zt_find(view->zonetable, name, 0, NULL, &zone); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + added = dns_zone_getadded(zone); + dns_zone_detach(&zone); + +#ifndef HAVE_LMDB + cfg = (ns_cfgctx_t *)view->new_zone_config; + if (cfg == NULL) { + TCHECK(putstr(text, "new zone config is not set")); + CHECK(ISC_R_FAILURE); + } +#endif /* ifndef HAVE_LMDB */ + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + +#ifndef HAVE_LMDB + /* Make sure we can open the configuration save file */ + result = isc_stdio_open(view->new_zone_file, "a", &fp); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to open '")); + TCHECK(putstr(text, view->new_zone_file)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + (void)isc_stdio_close(fp); + fp = NULL; +#else /* HAVE_LMDB */ + LOCK(&view->new_zone_lock); + locked = true; + /* Make sure we can open the NZD database */ + result = nzd_writable(view); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to open NZD database for '")); + TCHECK(putstr(text, view->new_zone_db)); + TCHECK(putstr(text, "'")); + result = ISC_R_FAILURE; + goto cleanup; + } +#endif /* HAVE_LMDB */ + + /* Reconfigure the zone */ + dns_view_thaw(view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + server->mctx, view, &server->viewlist, + &server->kasplist, cfg->actx, true, false, + true); + dns_view_freeze(view); + + exclusive = false; + isc_task_endexclusive(server->task); + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "configure_zone failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + /* Is it there yet? */ + if (redirect) { + if (view->redirect == NULL) { + CHECK(ISC_R_NOTFOUND); + } + dns_zone_attach(view->redirect, &zone); + } else { + CHECK(dns_zt_find(view->zonetable, name, 0, NULL, &zone)); + } + +#ifndef HAVE_LMDB + /* Remove old zone from configuration (and NZF file if applicable) */ + if (added) { + result = delete_zoneconf(view, cfg->add_parser, cfg->nzf_config, + dns_zone_getorigin(zone), + nzf_writeconf); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "former zone configuration " + "not deleted: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + } +#endif /* HAVE_LMDB */ + + if (!added) { + if (cfg->vconfig == NULL) { + result = delete_zoneconf( + view, cfg->conf_parser, cfg->config, + dns_zone_getorigin(zone), NULL); + } else { + const cfg_obj_t *voptions = cfg_tuple_get(cfg->vconfig, + "options"); + result = delete_zoneconf( + view, cfg->conf_parser, voptions, + dns_zone_getorigin(zone), NULL); + } + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "former zone configuration " + "not deleted: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + } + + /* Load the zone from the master file if it needs reloading. */ + result = dns_zone_load(zone, true); + + /* + * Dynamic zones need no reloading, so we can pass this result. + */ + if (result == DNS_R_DYNAMIC) { + result = ISC_R_SUCCESS; + } + + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + + TCHECK(putstr(text, "failed to load zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + TCHECK(putstr(text, "\nThe zone is no longer being served. ")); + TCHECK(putstr(text, "Use 'rndc addzone' to correct\n")); + TCHECK(putstr(text, "the problem and restore service.")); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "modzone failed; removing zone."); + + /* If the zone loaded partially, unload it */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Remove the zone from the zone table */ + dns_zt_unmount(view->zonetable, zone); + goto cleanup; + } + +#ifndef HAVE_LMDB + /* Store the new zone configuration; also in NZF if applicable */ + DE_CONST(zoneobj, z); + CHECK(cfg_parser_mapadd(cfg->add_parser, cfg->nzf_config, z, "zone")); +#endif /* HAVE_LMDB */ + + if (added) { +#ifdef HAVE_LMDB + CHECK(nzd_open(view, 0, &txn, &dbi)); + CHECK(nzd_save(&txn, dbi, zone, zoneobj)); +#else /* ifdef HAVE_LMDB */ + result = nzf_append(view, zoneobj); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "\nNew zone config not saved: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } +#endif /* HAVE_LMDB */ + + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "' reconfigured.")); + } else { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "' must also be reconfigured in\n")); + TCHECK(putstr(text, "named.conf to make changes permanent.")); + } + +cleanup: + if (exclusive) { + isc_task_endexclusive(server->task); + } + +#ifndef HAVE_LMDB + if (fp != NULL) { + (void)isc_stdio_close(fp); + } +#else /* HAVE_LMDB */ + if (txn != NULL) { + (void)nzd_close(&txn, false); + } + if (locked) { + UNLOCK(&view->new_zone_lock); + } +#endif /* HAVE_LMDB */ + + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +/* + * Act on an "addzone" or "modzone" command from the command channel. + */ +isc_result_t +named_server_changezone(named_server_t *server, char *command, + isc_buffer_t **text) { + isc_result_t result; + bool addzone; + bool redirect = false; + ns_cfgctx_t *cfg = NULL; + cfg_obj_t *zoneconf = NULL; + const cfg_obj_t *zoneobj = NULL; + const char *zonename; + dns_view_t *view = NULL; + isc_buffer_t buf; + dns_fixedname_t fname; + dns_name_t *dnsname; + + REQUIRE(text != NULL); + + if (strncasecmp(command, "add", 3) == 0) { + addzone = true; + } else { + INSIST(strncasecmp(command, "mod", 3) == 0); + addzone = false; + } + + CHECK(newzone_parse(server, command, &view, &zoneconf, &zoneobj, + &redirect, text)); + + /* Are we accepting new zones in this view? */ +#ifdef HAVE_LMDB + if (view->new_zone_db == NULL) +#else /* ifdef HAVE_LMDB */ + if (view->new_zone_file == NULL) +#endif /* HAVE_LMDB */ + { + (void)putstr(text, "Not allowing new zones in view '"); + (void)putstr(text, view->name); + (void)putstr(text, "'"); + result = ISC_R_NOPERM; + goto cleanup; + } + + cfg = (ns_cfgctx_t *)view->new_zone_config; + if (cfg == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + zonename = cfg_obj_asstring(cfg_tuple_get(zoneobj, "name")); + isc_buffer_constinit(&buf, zonename, strlen(zonename)); + isc_buffer_add(&buf, strlen(zonename)); + + dnsname = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(dnsname, &buf, dns_rootname, 0, NULL)); + + if (redirect) { + if (!dns_name_equal(dnsname, dns_rootname)) { + (void)putstr(text, "redirect zones must be called " + "\".\""); + CHECK(ISC_R_FAILURE); + } + } + + if (addzone) { + CHECK(do_addzone(server, cfg, view, dnsname, zoneconf, zoneobj, + redirect, text)); + } else { + CHECK(do_modzone(server, cfg, view, dnsname, zonename, zoneobj, + redirect, text)); + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s zone %s in view %s via %s", + addzone ? "added" : "updated", zonename, view->name, + addzone ? NAMED_COMMAND_ADDZONE : NAMED_COMMAND_MODZONE); + + /* Changing a zone counts as reconfiguration */ + CHECK(isc_time_now(&named_g_configtime)); + +cleanup: + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + if (zoneconf != NULL) { + cfg_obj_destroy(named_g_addparser, &zoneconf); + } + if (view != NULL) { + dns_view_detach(&view); + } + + return (result); +} + +static bool +inuse(const char *file, bool first, isc_buffer_t **text) { + if (file != NULL && isc_file_exists(file)) { + if (first) { + (void)putstr(text, "The following files were in use " + "and may now be removed:\n"); + } else { + (void)putstr(text, "\n"); + } + (void)putstr(text, file); + (void)putnull(text); + return (false); + } + return (first); +} + +typedef struct { + dns_zone_t *zone; + bool cleanup; +} ns_dzctx_t; + +/* + * Carry out a zone deletion scheduled by named_server_delzone(). + */ +static void +rmzone(isc_task_t *task, isc_event_t *event) { + ns_dzctx_t *dz = (ns_dzctx_t *)event->ev_arg; + dns_zone_t *zone = NULL, *raw = NULL, *mayberaw = NULL; + dns_catz_zone_t *catz = NULL; + char zonename[DNS_NAME_FORMATSIZE]; + dns_view_t *view = NULL; + ns_cfgctx_t *cfg = NULL; + dns_db_t *dbp = NULL; + bool added; + isc_result_t result; +#ifdef HAVE_LMDB + MDB_txn *txn = NULL; + MDB_dbi dbi; +#endif /* ifdef HAVE_LMDB */ + + REQUIRE(dz != NULL); + + isc_event_free(&event); + + /* Dig out configuration for this zone */ + zone = dz->zone; + view = dns_zone_getview(zone); + cfg = (ns_cfgctx_t *)view->new_zone_config; + dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename)); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "deleting zone %s in view %s via delzone", zonename, + view->name); + + /* + * Remove the zone from configuration (and NZF file if applicable) + * (If this is a catalog zone member then nzf_config can be NULL) + */ + added = dns_zone_getadded(zone); + catz = dns_zone_get_parentcatz(zone); + + if (added && catz == NULL && cfg != NULL) { +#ifdef HAVE_LMDB + /* Make sure we can open the NZD database */ + LOCK(&view->new_zone_lock); + result = nzd_open(view, 0, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to open NZD database for '%s'", + view->new_zone_db); + } else { + result = nzd_save(&txn, dbi, zone, NULL); + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to delete zone configuration: %s", + isc_result_totext(result)); + } + + if (txn != NULL) { + (void)nzd_close(&txn, false); + } + UNLOCK(&view->new_zone_lock); +#else /* ifdef HAVE_LMDB */ + result = delete_zoneconf(view, cfg->add_parser, cfg->nzf_config, + dns_zone_getorigin(zone), + nzf_writeconf); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to delete zone configuration: %s", + isc_result_totext(result)); + } +#endif /* HAVE_LMDB */ + } + + if (!added && cfg != NULL) { + if (cfg->vconfig != NULL) { + const cfg_obj_t *voptions = cfg_tuple_get(cfg->vconfig, + "options"); + result = delete_zoneconf( + view, cfg->conf_parser, voptions, + dns_zone_getorigin(zone), NULL); + } else { + result = delete_zoneconf( + view, cfg->conf_parser, cfg->config, + dns_zone_getorigin(zone), NULL); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to delete zone configuration: %s", + isc_result_totext(result)); + } + } + + /* Unload zone database */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Clean up stub/secondary zone files if requested to do so */ + dns_zone_getraw(zone, &raw); + mayberaw = (raw != NULL) ? raw : zone; + + if (added && dz->cleanup) { + const char *file; + + file = dns_zone_getfile(mayberaw); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + + file = dns_zone_getjournal(mayberaw); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + + if (zone != mayberaw) { + file = dns_zone_getfile(zone); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + + file = dns_zone_getjournal(zone); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + } + } + + if (raw != NULL) { + dns_zone_detach(&raw); + } + dns_zone_detach(&zone); + isc_mem_put(named_g_mctx, dz, sizeof(*dz)); + isc_task_detach(&task); +} + +/* + * Act on a "delzone" command from the command channel. + */ +isc_result_t +named_server_delzone(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result, tresult; + dns_zone_t *zone = NULL; + dns_zone_t *raw = NULL; + dns_zone_t *mayberaw; + dns_view_t *view = NULL; + char zonename[DNS_NAME_FORMATSIZE]; + bool cleanup = false; + const char *ptr; + bool added; + ns_dzctx_t *dz = NULL; + isc_event_t *dzevent = NULL; + isc_task_t *task = NULL; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcmp(ptr, "-clean") == 0 || strcmp(ptr, "-clear") == 0) { + cleanup = true; + ptr = next_token(lex, text); + } + + CHECK(zone_from_args(server, lex, ptr, &zone, zonename, text, false)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + INSIST(zonename != NULL); + + /* Is this a policy zone? */ + if (dns_zone_get_rpz_num(zone) != DNS_RPZ_INVALID_NUM) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, + "' cannot be deleted: response-policy zone.")); + result = ISC_R_FAILURE; + goto cleanup; + } + + view = dns_zone_getview(zone); + if (dns_zone_gettype(zone) == dns_zone_redirect) { + dns_zone_detach(&view->redirect); + } else { + CHECK(dns_zt_unmount(view->zonetable, zone)); + } + + /* Send cleanup event */ + dz = isc_mem_get(named_g_mctx, sizeof(*dz)); + + dz->cleanup = cleanup; + dz->zone = NULL; + dns_zone_attach(zone, &dz->zone); + dzevent = isc_event_allocate(named_g_mctx, server, NAMED_EVENT_DELZONE, + rmzone, dz, sizeof(isc_event_t)); + + dns_zone_gettask(zone, &task); + isc_task_send(task, &dzevent); + dz = NULL; + + /* Inform user about cleaning up stub/secondary zone files */ + dns_zone_getraw(zone, &raw); + mayberaw = (raw != NULL) ? raw : zone; + + added = dns_zone_getadded(zone); + if (!added) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, "' is no longer active and will be " + "deleted.\n")); + TCHECK(putstr(text, "To keep it from returning ")); + TCHECK(putstr(text, "when the server is restarted, it\n")); + TCHECK(putstr(text, "must also be removed from named.conf.")); + } else if (cleanup) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, "' and associated files will be deleted.")); + } else if (dns_zone_gettype(mayberaw) == dns_zone_secondary || + dns_zone_gettype(mayberaw) == dns_zone_mirror || + dns_zone_gettype(mayberaw) == dns_zone_stub) + { + bool first; + const char *file; + + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, "' will be deleted.")); + + file = dns_zone_getfile(mayberaw); + first = inuse(file, true, text); + + file = dns_zone_getjournal(mayberaw); + first = inuse(file, first, text); + + if (zone != mayberaw) { + file = dns_zone_getfile(zone); + first = inuse(file, first, text); + + file = dns_zone_getjournal(zone); + (void)inuse(file, first, text); + } + } + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone %s scheduled for removal via delzone", zonename); + + /* Removing a zone counts as reconfiguration */ + CHECK(isc_time_now(&named_g_configtime)); + + result = ISC_R_SUCCESS; + +cleanup: + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + if (raw != NULL) { + dns_zone_detach(&raw); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +static const cfg_obj_t * +find_name_in_list_from_map(const cfg_obj_t *config, + const char *map_key_for_list, const char *name, + bool redirect) { + const cfg_obj_t *list = NULL; + const cfg_listelt_t *element; + const cfg_obj_t *obj = NULL; + dns_fixedname_t fixed1, fixed2; + dns_name_t *name1 = NULL, *name2 = NULL; + isc_result_t result; + + if (strcmp(map_key_for_list, "zone") == 0) { + name1 = dns_fixedname_initname(&fixed1); + name2 = dns_fixedname_initname(&fixed2); + result = dns_name_fromstring(name1, name, 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + cfg_map_get(config, map_key_for_list, &list); + for (element = cfg_list_first(list); element != NULL; + element = cfg_list_next(element)) + { + const char *vname; + + obj = cfg_listelt_value(element); + INSIST(obj != NULL); + vname = cfg_obj_asstring(cfg_tuple_get(obj, "name")); + if (vname == NULL) { + obj = NULL; + continue; + } + + if (name1 != NULL) { + result = dns_name_fromstring(name2, vname, 0, NULL); + if (result == ISC_R_SUCCESS && + dns_name_equal(name1, name2)) + { + const cfg_obj_t *zoptions; + const cfg_obj_t *typeobj = NULL; + zoptions = cfg_tuple_get(obj, "options"); + + if (zoptions != NULL) { + cfg_map_get(zoptions, "type", &typeobj); + } + if (redirect && typeobj != NULL && + strcasecmp(cfg_obj_asstring(typeobj), + "redirect") == 0) + { + break; + } else if (!redirect) { + break; + } + } + } else if (strcasecmp(vname, name) == 0) { + break; + } + + obj = NULL; + } + + return (obj); +} + +static void +emitzone(void *arg, const char *buf, int len) { + ns_dzarg_t *dzarg = arg; + isc_result_t result; + + REQUIRE(dzarg != NULL && ISC_MAGIC_VALID(dzarg, DZARG_MAGIC)); + result = putmem(dzarg->text, buf, len); + if (result != ISC_R_SUCCESS && dzarg->result == ISC_R_SUCCESS) { + dzarg->result = result; + } +} + +/* + * Act on a "showzone" command from the command channel. + */ +isc_result_t +named_server_showzone(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result; + const cfg_obj_t *vconfig = NULL, *zconfig = NULL; + char zonename[DNS_NAME_FORMATSIZE]; + const cfg_obj_t *map; + dns_view_t *view = NULL; + dns_zone_t *zone = NULL; + ns_cfgctx_t *cfg = NULL; +#ifdef HAVE_LMDB + cfg_obj_t *nzconfig = NULL; +#endif /* HAVE_LMDB */ + bool added, redirect; + ns_dzarg_t dzarg; + + REQUIRE(text != NULL); + + /* Parse parameters */ + CHECK(zone_from_args(server, lex, NULL, &zone, zonename, text, true)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + redirect = dns_zone_gettype(zone) == dns_zone_redirect; + added = dns_zone_getadded(zone); + view = dns_zone_getview(zone); + dns_zone_detach(&zone); + + cfg = (ns_cfgctx_t *)view->new_zone_config; + if (cfg == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + if (!added) { + /* Find the view statement */ + vconfig = find_name_in_list_from_map(cfg->config, "view", + view->name, false); + + /* Find the zone statement */ + if (vconfig != NULL) { + map = cfg_tuple_get(vconfig, "options"); + } else { + map = cfg->config; + } + + zconfig = find_name_in_list_from_map(map, "zone", zonename, + redirect); + } + +#ifndef HAVE_LMDB + if (zconfig == NULL && cfg->nzf_config != NULL) { + zconfig = find_name_in_list_from_map(cfg->nzf_config, "zone", + zonename, redirect); + } +#else /* HAVE_LMDB */ + if (zconfig == NULL) { + const cfg_obj_t *zlist = NULL; + CHECK(get_newzone_config(view, zonename, &nzconfig)); + CHECK(cfg_map_get(nzconfig, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) { + CHECK(ISC_R_FAILURE); + } + + zconfig = cfg_listelt_value(cfg_list_first(zlist)); + } +#endif /* HAVE_LMDB */ + + if (zconfig == NULL) { + CHECK(ISC_R_NOTFOUND); + } + + CHECK(putstr(text, "zone ")); + dzarg.magic = DZARG_MAGIC; + dzarg.text = text; + dzarg.result = ISC_R_SUCCESS; + cfg_printx(zconfig, CFG_PRINTER_ONELINE, emitzone, &dzarg); + CHECK(dzarg.result); + + CHECK(putstr(text, ";")); + + result = ISC_R_SUCCESS; + +cleanup: +#ifdef HAVE_LMDB + if (nzconfig != NULL) { + cfg_obj_destroy(named_g_addparser, &nzconfig); + } +#endif /* HAVE_LMDB */ + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + return (result); +} + +static void +newzone_cfgctx_destroy(void **cfgp) { + ns_cfgctx_t *cfg; + + REQUIRE(cfgp != NULL && *cfgp != NULL); + + cfg = *cfgp; + + if (cfg->conf_parser != NULL) { + if (cfg->config != NULL) { + cfg_obj_destroy(cfg->conf_parser, &cfg->config); + } + if (cfg->vconfig != NULL) { + cfg_obj_destroy(cfg->conf_parser, &cfg->vconfig); + } + cfg_parser_destroy(&cfg->conf_parser); + } + if (cfg->add_parser != NULL) { + if (cfg->nzf_config != NULL) { + cfg_obj_destroy(cfg->add_parser, &cfg->nzf_config); + } + cfg_parser_destroy(&cfg->add_parser); + } + + if (cfg->actx != NULL) { + cfg_aclconfctx_detach(&cfg->actx); + } + + isc_mem_putanddetach(&cfg->mctx, cfg, sizeof(*cfg)); + *cfgp = NULL; +} + +isc_result_t +named_server_signing(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *zone = NULL; + dns_name_t *origin; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdatatype_t privatetype; + dns_rdataset_t privset; + bool first = true; + bool list = false, clear = false; + bool chain = false; + bool setserial = false; + bool resalt = false; + uint32_t serial = 0; + char keystr[DNS_SECALG_FORMATSIZE + 7]; /* <5-digit keyid>/<alg> */ + unsigned short hash = 0, flags = 0, iter = 0, saltlen = 0; + unsigned char salt[255]; + const char *ptr; + size_t n; + + REQUIRE(text != NULL); + + dns_rdataset_init(&privset); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcasecmp(ptr, "-list") == 0) { + list = true; + } else if ((strcasecmp(ptr, "-clear") == 0) || + (strcasecmp(ptr, "-clean") == 0)) + { + clear = true; + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + strlcpy(keystr, ptr, sizeof(keystr)); + } else if (strcasecmp(ptr, "-nsec3param") == 0) { + char hashbuf[64], flagbuf[64], iterbuf[64]; + char nbuf[256]; + + chain = true; + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcasecmp(ptr, "none") == 0) { + hash = 0; + } else { + strlcpy(hashbuf, ptr, sizeof(hashbuf)); + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + strlcpy(flagbuf, ptr, sizeof(flagbuf)); + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + strlcpy(iterbuf, ptr, sizeof(iterbuf)); + n = snprintf(nbuf, sizeof(nbuf), "%s %s %s", hashbuf, + flagbuf, iterbuf); + if (n == sizeof(nbuf)) { + return (ISC_R_NOSPACE); + } + n = sscanf(nbuf, "%hu %hu %hu", &hash, &flags, &iter); + if (n != 3U) { + return (ISC_R_BADNUMBER); + } + + if (hash > 0xffU || flags > 0xffU || + iter > dns_nsec3_maxiterations()) + { + return (ISC_R_RANGE); + } + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } else if (strcasecmp(ptr, "auto") == 0) { + /* Auto-generate a random salt. + * XXXMUKS: This currently uses the + * minimum recommended length by RFC + * 5155 (64 bits). It should be made + * configurable. + */ + saltlen = 8; + resalt = true; + } else if (strcmp(ptr, "-") != 0) { + isc_buffer_t buf; + + isc_buffer_init(&buf, salt, sizeof(salt)); + CHECK(isc_hex_decodestring(ptr, &buf)); + saltlen = isc_buffer_usedlength(&buf); + } + } + } else if (strcasecmp(ptr, "-serial") == 0) { + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + CHECK(isc_parse_uint32(&serial, ptr, 10)); + setserial = true; + } else { + CHECK(DNS_R_SYNTAX); + } + + CHECK(zone_from_args(server, lex, NULL, &zone, NULL, text, false)); + if (zone == NULL) { + CHECK(ISC_R_UNEXPECTEDEND); + } + + if (dns_zone_getkasp(zone) != NULL) { + (void)putstr(text, "zone uses dnssec-policy, use rndc dnssec " + "command instead"); + (void)putnull(text); + goto cleanup; + } + + if (clear) { + CHECK(dns_zone_keydone(zone, keystr)); + (void)putstr(text, "request queued"); + (void)putnull(text); + } else if (chain) { + CHECK(dns_zone_setnsec3param( + zone, (uint8_t)hash, (uint8_t)flags, iter, + (uint8_t)saltlen, salt, true, resalt)); + (void)putstr(text, "nsec3param request queued"); + (void)putnull(text); + } else if (setserial) { + CHECK(dns_zone_setserial(zone, serial)); + (void)putstr(text, "serial request queued"); + (void)putnull(text); + } else if (list) { + privatetype = dns_zone_getprivatetype(zone); + origin = dns_zone_getorigin(zone); + CHECK(dns_zone_getdb(zone, &db)); + CHECK(dns_db_findnode(db, origin, false, &node)); + dns_db_currentversion(db, &version); + + result = dns_db_findrdataset(db, node, version, privatetype, + dns_rdatatype_none, 0, &privset, + NULL); + if (result == ISC_R_NOTFOUND) { + (void)putstr(text, "No signing records found"); + (void)putnull(text); + result = ISC_R_SUCCESS; + goto cleanup; + } + + for (result = dns_rdataset_first(&privset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&privset)) + { + dns_rdata_t priv = DNS_RDATA_INIT; + /* + * In theory, the output buffer could hold a full RDATA + * record which is 16-bit and then some text around + * it + */ + char output[UINT16_MAX + BUFSIZ]; + isc_buffer_t buf; + + dns_rdataset_current(&privset, &priv); + + isc_buffer_init(&buf, output, sizeof(output)); + CHECK(dns_private_totext(&priv, &buf)); + if (!first) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, output)); + first = false; + } + if (!first) { + CHECK(putnull(text)); + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + } + +cleanup: + if (dns_rdataset_isassociated(&privset)) { + dns_rdataset_disassociate(&privset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +static bool +argcheck(char *cmd, const char *full) { + size_t l; + + if (cmd == NULL || cmd[0] != '-') { + return (false); + } + + cmd++; + l = strlen(cmd); + if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) { + return (false); + } + + return (true); +} + +isc_result_t +named_server_dnssec(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *zone = NULL; + dns_kasp_t *kasp = NULL; + dns_dnsseckeylist_t keys; + dns_dnsseckey_t *key; + char *ptr, *zonetext = NULL; + const char *msg = NULL; + /* variables for -checkds */ + bool checkds = false, dspublish = false; + /* variables for -rollover */ + bool rollover = false; + /* variables for -key */ + bool use_keyid = false; + dns_keytag_t keyid = 0; + uint8_t algorithm = 0; + /* variables for -status */ + bool status = false; + char output[4096]; + isc_stdtime_t now, when; + isc_time_t timenow, timewhen; + const char *dir; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Initialize current time and key list. */ + TIME_NOW(&timenow); + now = isc_time_seconds(&timenow); + when = now; + + ISC_LIST_INIT(keys); + + if (strcasecmp(ptr, "-status") == 0) { + status = true; + } else if (strcasecmp(ptr, "-rollover") == 0) { + rollover = true; + } else if (strcasecmp(ptr, "-checkds") == 0) { + checkds = true; + } else { + CHECK(DNS_R_SYNTAX); + } + + if (rollover || checkds) { + /* Check for options */ + for (;;) { + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "Bad format"; + CHECK(ISC_R_UNEXPECTEDEND); + } else if (argcheck(ptr, "alg")) { + isc_consttextregion_t alg; + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No key algorithm specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + alg.base = ptr; + alg.length = strlen(alg.base); + result = dns_secalg_fromtext( + &algorithm, (isc_textregion_t *)&alg); + if (result != ISC_R_SUCCESS) { + msg = "Bad algorithm"; + CHECK(DNS_R_SYNTAX); + } + continue; + } else if (argcheck(ptr, "key")) { + uint16_t id; + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No key identifier specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + CHECK(isc_parse_uint16(&id, ptr, 10)); + keyid = (dns_keytag_t)id; + use_keyid = true; + continue; + } else if (argcheck(ptr, "when")) { + uint32_t tw; + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No time specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + CHECK(dns_time32_fromtext(ptr, &tw)); + when = (isc_stdtime_t)tw; + continue; + } else if (ptr[0] == '-') { + msg = "Unknown option"; + CHECK(DNS_R_SYNTAX); + } else if (checkds) { + /* + * No arguments provided, so we must be + * parsing "published|withdrawn". + */ + if (strcasecmp(ptr, "published") == 0) { + dspublish = true; + } else if (strcasecmp(ptr, "withdrawn") != 0) { + CHECK(DNS_R_SYNTAX); + } + } else if (rollover) { + /* + * No arguments provided, so we must be + * parsing the zone. + */ + zonetext = ptr; + } + break; + } + + if (rollover && !use_keyid) { + msg = "Key id is required when scheduling rollover"; + CHECK(DNS_R_SYNTAX); + } + + if (algorithm > 0 && !use_keyid) { + msg = "Key id is required when setting algorithm"; + CHECK(DNS_R_SYNTAX); + } + } + + /* Get zone. */ + CHECK(zone_from_args(server, lex, zonetext, &zone, NULL, text, false)); + if (zone == NULL) { + msg = "Zone not found"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + /* Trailing garbage? */ + ptr = next_token(lex, text); + if (ptr != NULL) { + msg = "Too many arguments"; + CHECK(DNS_R_SYNTAX); + } + + /* Get dnssec-policy. */ + kasp = dns_zone_getkasp(zone); + if (kasp == NULL) { + msg = "Zone does not have dnssec-policy"; + goto cleanup; + } + + /* Get DNSSEC keys. */ + dir = dns_zone_getkeydirectory(zone); + CHECK(dns_zone_getdb(zone, &db)); + dns_db_currentversion(db, &version); + LOCK(&kasp->lock); + result = dns_zone_getdnsseckeys(zone, db, version, now, &keys); + UNLOCK(&kasp->lock); + if (result != ISC_R_SUCCESS) { + if (result != ISC_R_NOTFOUND) { + goto cleanup; + } + } + + if (status) { + /* + * Output the DNSSEC status of the key and signing policy. + */ + LOCK(&kasp->lock); + dns_keymgr_status(kasp, &keys, now, &output[0], sizeof(output)); + UNLOCK(&kasp->lock); + CHECK(putstr(text, output)); + } else if (checkds) { + /* + * Mark DS record has been seen, so it may move to the + * rumoured state. + */ + char whenbuf[80]; + isc_time_set(&timewhen, when, 0); + isc_time_formattimestamp(&timewhen, whenbuf, sizeof(whenbuf)); + isc_result_t ret; + + LOCK(&kasp->lock); + if (use_keyid) { + result = dns_keymgr_checkds_id(kasp, &keys, dir, now, + when, dspublish, keyid, + (unsigned int)algorithm); + } else { + result = dns_keymgr_checkds(kasp, &keys, dir, now, when, + dspublish); + } + UNLOCK(&kasp->lock); + + switch (result) { + case ISC_R_SUCCESS: + /* + * Rekey after checkds command because the next key + * event may have changed. + */ + dns_zone_rekey(zone, false); + + if (use_keyid) { + char tagbuf[6]; + snprintf(tagbuf, sizeof(tagbuf), "%u", keyid); + CHECK(putstr(text, "KSK ")); + CHECK(putstr(text, tagbuf)); + CHECK(putstr(text, ": ")); + } + CHECK(putstr(text, "Marked DS as ")); + if (dspublish) { + CHECK(putstr(text, "published ")); + } else { + CHECK(putstr(text, "withdrawn ")); + } + CHECK(putstr(text, "since ")); + CHECK(putstr(text, whenbuf)); + break; + case DNS_R_TOOMANYKEYS: + CHECK(putstr(text, + "Error: multiple possible keys found, " + "retry command with -key id")); + break; + default: + ret = result; + CHECK(putstr(text, + "Error executing checkds command: ")); + CHECK(putstr(text, isc_result_totext(ret))); + break; + } + } else if (rollover) { + /* + * Manually rollover a key. + */ + char whenbuf[80]; + isc_time_set(&timewhen, when, 0); + isc_time_formattimestamp(&timewhen, whenbuf, sizeof(whenbuf)); + isc_result_t ret; + + LOCK(&kasp->lock); + result = dns_keymgr_rollover(kasp, &keys, dir, now, when, keyid, + (unsigned int)algorithm); + UNLOCK(&kasp->lock); + + switch (result) { + case ISC_R_SUCCESS: + /* + * Rekey after rollover command because the next key + * event may have changed. + */ + dns_zone_rekey(zone, false); + + if (use_keyid) { + char tagbuf[6]; + snprintf(tagbuf, sizeof(tagbuf), "%u", keyid); + CHECK(putstr(text, "Key ")); + CHECK(putstr(text, tagbuf)); + CHECK(putstr(text, ": ")); + } + CHECK(putstr(text, "Rollover scheduled on ")); + CHECK(putstr(text, whenbuf)); + break; + case DNS_R_TOOMANYKEYS: + CHECK(putstr(text, + "Error: multiple possible keys found, " + "retry command with -alg algorithm")); + break; + default: + ret = result; + CHECK(putstr(text, + "Error executing rollover command: ")); + CHECK(putstr(text, isc_result_totext(ret))); + break; + } + } + CHECK(putnull(text)); + +cleanup: + if (msg != NULL) { + (void)putstr(text, msg); + (void)putnull(text); + } + + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + + while (!ISC_LIST_EMPTY(keys)) { + key = ISC_LIST_HEAD(keys); + ISC_LIST_UNLINK(keys, key, link); + dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key); + } + + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +static isc_result_t +putmem(isc_buffer_t **b, const char *str, size_t len) { + isc_result_t result; + + result = isc_buffer_reserve(b, (unsigned int)len); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem(*b, (const unsigned char *)str, (unsigned int)len); + return (ISC_R_SUCCESS); +} + +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + return (putmem(b, str, strlen(str))); +} + +static isc_result_t +putuint8(isc_buffer_t **b, uint8_t val) { + isc_result_t result; + + result = isc_buffer_reserve(b, 1); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putuint8(*b, val); + return (ISC_R_SUCCESS); +} + +static isc_result_t +putnull(isc_buffer_t **b) { + return (putuint8(b, 0)); +} + +isc_result_t +named_server_zonestatus(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *zone = NULL, *raw = NULL, *mayberaw = NULL; + const char *type, *file; + char zonename[DNS_NAME_FORMATSIZE]; + uint32_t serial, signed_serial, nodes; + char serbuf[16], sserbuf[16], nodebuf[16]; + char resignbuf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 2]; + char lbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char xbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char rbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char kbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char rtbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_t loadtime, expiretime, refreshtime; + isc_time_t refreshkeytime, resigntime; + dns_zonetype_t zonetype; + bool dynamic = false, frozen = false; + bool hasraw = false; + bool secure, maintain, allow; + dns_db_t *db = NULL, *rawdb = NULL; + char **incfiles = NULL; + int nfiles = 0; + + REQUIRE(text != NULL); + + isc_time_settoepoch(&loadtime); + isc_time_settoepoch(&refreshtime); + isc_time_settoepoch(&expiretime); + isc_time_settoepoch(&refreshkeytime); + isc_time_settoepoch(&resigntime); + + CHECK(zone_from_args(server, lex, NULL, &zone, zonename, text, true)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + /* Inline signing? */ + CHECK(dns_zone_getdb(zone, &db)); + dns_zone_getraw(zone, &raw); + hasraw = (raw != NULL); + if (hasraw) { + mayberaw = raw; + zonetype = dns_zone_gettype(raw); + CHECK(dns_zone_getdb(raw, &rawdb)); + } else { + mayberaw = zone; + zonetype = dns_zone_gettype(zone); + } + + type = dns_zonetype_name(zonetype); + + /* Serial number */ + result = dns_zone_getserial(mayberaw, &serial); + + /* This is to mirror old behavior with dns_zone_getserial */ + if (result != ISC_R_SUCCESS) { + serial = 0; + } + + snprintf(serbuf, sizeof(serbuf), "%u", serial); + if (hasraw) { + result = dns_zone_getserial(zone, &signed_serial); + if (result != ISC_R_SUCCESS) { + serial = 0; + } + snprintf(sserbuf, sizeof(sserbuf), "%u", signed_serial); + } + + /* Database node count */ + nodes = dns_db_nodecount(hasraw ? rawdb : db, dns_dbtree_main); + snprintf(nodebuf, sizeof(nodebuf), "%u", nodes); + + /* Security */ + secure = dns_db_issecure(db); + allow = ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_ALLOW) != 0); + maintain = ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_MAINTAIN) != 0); + + /* Master files */ + file = dns_zone_getfile(mayberaw); + nfiles = dns_zone_getincludes(mayberaw, &incfiles); + + /* Load time */ + dns_zone_getloadtime(zone, &loadtime); + isc_time_formathttptimestamp(&loadtime, lbuf, sizeof(lbuf)); + + /* Refresh/expire times */ + if (zonetype == dns_zone_secondary || zonetype == dns_zone_mirror || + zonetype == dns_zone_stub || zonetype == dns_zone_redirect) + { + dns_zone_getexpiretime(mayberaw, &expiretime); + isc_time_formathttptimestamp(&expiretime, xbuf, sizeof(xbuf)); + dns_zone_getrefreshtime(mayberaw, &refreshtime); + isc_time_formathttptimestamp(&refreshtime, rbuf, sizeof(rbuf)); + } + + /* Key refresh time */ + if (zonetype == dns_zone_primary || + (zonetype == dns_zone_secondary && hasraw)) + { + dns_zone_getrefreshkeytime(zone, &refreshkeytime); + isc_time_formathttptimestamp(&refreshkeytime, kbuf, + sizeof(kbuf)); + } + + /* Dynamic? */ + if (zonetype == dns_zone_primary) { + dynamic = dns_zone_isdynamic(mayberaw, true); + frozen = dynamic && !dns_zone_isdynamic(mayberaw, false); + } + + /* Next resign event */ + if (secure && + (zonetype == dns_zone_primary || + (zonetype == dns_zone_secondary && hasraw)) && + ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_NORESIGN) == 0)) + { + dns_name_t *name; + dns_fixedname_t fixed; + dns_rdataset_t next; + + dns_rdataset_init(&next); + name = dns_fixedname_initname(&fixed); + + result = dns_db_getsigningtime(db, &next, name); + if (result == ISC_R_SUCCESS) { + isc_stdtime_t timenow; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + isc_stdtime_get(&timenow); + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(next.covers, typebuf, + sizeof(typebuf)); + snprintf(resignbuf, sizeof(resignbuf), "%s/%s", namebuf, + typebuf); + isc_time_set( + &resigntime, + next.resign - + dns_zone_getsigresigninginterval(zone), + 0); + isc_time_formathttptimestamp(&resigntime, rtbuf, + sizeof(rtbuf)); + dns_rdataset_disassociate(&next); + } + } + + /* Create text */ + CHECK(putstr(text, "name: ")); + CHECK(putstr(text, zonename)); + + CHECK(putstr(text, "\ntype: ")); + CHECK(putstr(text, type)); + + if (file != NULL) { + int i; + CHECK(putstr(text, "\nfiles: ")); + CHECK(putstr(text, file)); + for (i = 0; i < nfiles; i++) { + CHECK(putstr(text, ", ")); + if (incfiles[i] != NULL) { + CHECK(putstr(text, incfiles[i])); + } + } + } + + CHECK(putstr(text, "\nserial: ")); + CHECK(putstr(text, serbuf)); + if (hasraw) { + CHECK(putstr(text, "\nsigned serial: ")); + CHECK(putstr(text, sserbuf)); + } + + CHECK(putstr(text, "\nnodes: ")); + CHECK(putstr(text, nodebuf)); + + if (!isc_time_isepoch(&loadtime)) { + CHECK(putstr(text, "\nlast loaded: ")); + CHECK(putstr(text, lbuf)); + } + + if (!isc_time_isepoch(&refreshtime)) { + CHECK(putstr(text, "\nnext refresh: ")); + CHECK(putstr(text, rbuf)); + } + + if (!isc_time_isepoch(&expiretime)) { + CHECK(putstr(text, "\nexpires: ")); + CHECK(putstr(text, xbuf)); + } + + if (secure) { + CHECK(putstr(text, "\nsecure: yes")); + if (hasraw) { + CHECK(putstr(text, "\ninline signing: yes")); + } else { + CHECK(putstr(text, "\ninline signing: no")); + } + } else { + CHECK(putstr(text, "\nsecure: no")); + } + + if (maintain) { + CHECK(putstr(text, "\nkey maintenance: automatic")); + if (!isc_time_isepoch(&refreshkeytime)) { + CHECK(putstr(text, "\nnext key event: ")); + CHECK(putstr(text, kbuf)); + } + } else if (allow) { + CHECK(putstr(text, "\nkey maintenance: on command")); + } else if (secure || hasraw) { + CHECK(putstr(text, "\nkey maintenance: none")); + } + + if (!isc_time_isepoch(&resigntime)) { + CHECK(putstr(text, "\nnext resign node: ")); + CHECK(putstr(text, resignbuf)); + CHECK(putstr(text, "\nnext resign time: ")); + CHECK(putstr(text, rtbuf)); + } + + if (dynamic) { + CHECK(putstr(text, "\ndynamic: yes")); + if (frozen) { + CHECK(putstr(text, "\nfrozen: yes")); + } else { + CHECK(putstr(text, "\nfrozen: no")); + } + } else { + CHECK(putstr(text, "\ndynamic: no")); + } + + CHECK(putstr(text, "\nreconfigurable via modzone: ")); + CHECK(putstr(text, dns_zone_getadded(zone) ? "yes" : "no")); + +cleanup: + /* Indicate truncated output if possible. */ + if (result == ISC_R_NOSPACE) { + (void)putstr(text, "\n..."); + } + if ((result == ISC_R_SUCCESS || result == ISC_R_NOSPACE)) { + (void)putnull(text); + } + + if (db != NULL) { + dns_db_detach(&db); + } + if (rawdb != NULL) { + dns_db_detach(&rawdb); + } + if (incfiles != NULL && mayberaw != NULL) { + int i; + isc_mem_t *mctx = dns_zone_getmctx(mayberaw); + + for (i = 0; i < nfiles; i++) { + if (incfiles[i] != NULL) { + isc_mem_free(mctx, incfiles[i]); + } + } + isc_mem_free(mctx, incfiles); + } + if (raw != NULL) { + dns_zone_detach(&raw); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + return (result); +} + +isc_result_t +named_server_nta(named_server_t *server, isc_lex_t *lex, bool readonly, + isc_buffer_t **text) { + dns_view_t *view; + dns_ntatable_t *ntatable = NULL; + isc_result_t result = ISC_R_SUCCESS; + char *ptr, *nametext = NULL, *viewname; + char namebuf[DNS_NAME_FORMATSIZE]; + char viewbuf[DNS_NAME_FORMATSIZE]; + isc_stdtime_t now, when; + isc_time_t t; + char tbuf[64]; + const char *msg = NULL; + bool dump = false, force = false; + dns_fixedname_t fn; + const dns_name_t *ntaname; + dns_name_t *fname; + dns_ttl_t ntattl; + bool ttlset = false, excl = false, viewfound = false; + dns_rdataclass_t rdclass = dns_rdataclass_in; + bool first = true; + + REQUIRE(text != NULL); + + UNUSED(force); + + fname = dns_fixedname_initname(&fn); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + for (;;) { + /* Check for options */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcmp(ptr, "--") == 0) { + break; + } else if (argcheck(ptr, "dump")) { + dump = true; + } else if (argcheck(ptr, "remove")) { + ntattl = 0; + ttlset = true; + } else if (argcheck(ptr, "force")) { + force = true; + continue; + } else if (argcheck(ptr, "lifetime")) { + isc_textregion_t tr; + + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No lifetime specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + tr.base = ptr; + tr.length = strlen(ptr); + result = dns_ttl_fromtext(&tr, &ntattl); + if (result != ISC_R_SUCCESS) { + msg = "could not parse NTA lifetime"; + CHECK(result); + } + + if (ntattl > 604800) { + msg = "NTA lifetime cannot exceed one week"; + CHECK(ISC_R_RANGE); + } + + ttlset = true; + continue; + } else if (argcheck(ptr, "class")) { + isc_textregion_t tr; + + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No class specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + tr.base = ptr; + tr.length = strlen(ptr); + CHECK(dns_rdataclass_fromtext(&rdclass, &tr)); + continue; + } else if (ptr[0] == '-') { + msg = "Unknown option"; + CHECK(DNS_R_SYNTAX); + } else { + nametext = ptr; + } + + break; + } + + /* + * If -dump was specified, list NTA's and return + */ + if (dump) { + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + continue; + } + + CHECK(dns_ntatable_totext(ntatable, view->name, text)); + } + CHECK(putnull(text)); + + goto cleanup; + } + + if (readonly) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_CONTROL, ISC_LOG_INFO, + "rejecting restricted control channel " + "NTA command"); + CHECK(ISC_R_FAILURE); + } + + /* Get the NTA name if not found above. */ + if (nametext == NULL) { + nametext = next_token(lex, text); + } + if (nametext == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Copy nametext as it'll be overwritten by next_token() */ + strlcpy(namebuf, nametext, DNS_NAME_FORMATSIZE); + + if (strcmp(namebuf, ".") == 0) { + ntaname = dns_rootname; + } else { + isc_buffer_t b; + isc_buffer_init(&b, namebuf, strlen(namebuf)); + isc_buffer_add(&b, strlen(namebuf)); + CHECK(dns_name_fromtext(fname, &b, dns_rootname, 0, NULL)); + ntaname = fname; + } + + /* Look for the view name. */ + viewname = next_token(lex, text); + if (viewname != NULL) { + strlcpy(viewbuf, viewname, DNS_NAME_FORMATSIZE); + viewname = viewbuf; + } + + if (next_token(lex, text) != NULL) { + CHECK(DNS_R_SYNTAX); + } + + isc_stdtime_get(&now); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + excl = true; + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewname != NULL && strcmp(view->name, viewname) != 0) { + continue; + } + viewfound = true; + + if (view->rdclass != rdclass && rdclass != dns_rdataclass_any) { + continue; + } + + if (view->nta_lifetime == 0) { + continue; + } + + if (!ttlset) { + ntattl = view->nta_lifetime; + } + + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + + result = dns_view_flushnode(view, ntaname, true); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "flush tree '%s' in cache view '%s': %s", namebuf, + view->name, isc_result_totext(result)); + + if (ntattl != 0) { + CHECK(dns_ntatable_add(ntatable, ntaname, force, now, + ntattl)); + + when = now + ntattl; + isc_time_set(&t, when, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + if (!first) { + CHECK(putstr(text, "\n")); + } + first = false; + + CHECK(putstr(text, "Negative trust anchor added: ")); + CHECK(putstr(text, namebuf)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ", expires ")); + CHECK(putstr(text, tbuf)); + + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "added NTA '%s' (%d sec) in view '%s'", + namebuf, ntattl, view->name); + } else { + bool wasremoved; + + result = dns_ntatable_delete(ntatable, ntaname); + if (result == ISC_R_SUCCESS) { + wasremoved = true; + } else if (result == ISC_R_NOTFOUND) { + wasremoved = false; + } else { + goto cleanup; + } + + if (!first) { + CHECK(putstr(text, "\n")); + } + first = false; + + CHECK(putstr(text, "Negative trust anchor ")); + CHECK(putstr(text, + wasremoved ? "removed: " : "not found: ")); + CHECK(putstr(text, namebuf)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + + if (wasremoved) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "removed NTA '%s' in view %s", namebuf, + view->name); + } + } + + result = dns_view_saventa(view); + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + if (!viewfound) { + msg = "No such view"; + CHECK(ISC_R_NOTFOUND); + } + + (void)putnull(text); + +cleanup: + if (msg != NULL) { + (void)putstr(text, msg); + (void)putnull(text); + } + + if (excl) { + isc_task_endexclusive(server->task); + } + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + return (result); +} + +isc_result_t +named_server_saventa(named_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_saventa(view); + + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +named_server_loadnta(named_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_loadnta(view); + + if ((result != ISC_R_SUCCESS) && + (result != ISC_R_FILENOTFOUND) && + (result != ISC_R_NOTFOUND)) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error loading NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +mkey_refresh(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + char msg[DNS_NAME_FORMATSIZE + 500] = ""; + + snprintf(msg, sizeof(msg), "refreshing managed keys for '%s'", + view->name); + CHECK(putstr(text, msg)); + CHECK(dns_zone_synckeyzone(view->managed_keys)); + +cleanup: + return (result); +} + +static isc_result_t +mkey_destroy(named_server_t *server, dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + char msg[DNS_NAME_FORMATSIZE + 500] = ""; + bool exclusive = false; + const char *file = NULL; + dns_db_t *dbp = NULL; + dns_zone_t *mkzone = NULL; + bool removed_a_file = false; + + if (view->managed_keys == NULL) { + CHECK(ISC_R_NOTFOUND); + } + + snprintf(msg, sizeof(msg), "destroying managed-keys database for '%s'", + view->name); + CHECK(putstr(text, msg)); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + + /* Remove and clean up managed keys zone from view */ + mkzone = view->managed_keys; + view->managed_keys = NULL; + (void)dns_zone_flush(mkzone); + + /* Unload zone database */ + if (dns_zone_getdb(mkzone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(mkzone); + } + + /* Delete files */ + file = dns_zone_getfile(mkzone); + result = isc_file_remove(file); + if (result == ISC_R_SUCCESS) { + removed_a_file = true; + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + + file = dns_zone_getjournal(mkzone); + result = isc_file_remove(file); + if (result == ISC_R_SUCCESS) { + removed_a_file = true; + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", file, + isc_result_totext(result)); + } + + if (!removed_a_file) { + CHECK(putstr(text, "error: no files could be removed")); + CHECK(ISC_R_FAILURE); + } + + dns_zone_detach(&mkzone); + result = ISC_R_SUCCESS; + +cleanup: + if (exclusive) { + isc_task_endexclusive(server->task); + } + return (result); +} + +static isc_result_t +mkey_dumpzone(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL; + dns_rriterator_t rrit; + isc_stdtime_t now; + dns_name_t *prevname = NULL; + + isc_stdtime_get(&now); + + CHECK(dns_zone_getdb(view->managed_keys, &db)); + dns_db_currentversion(db, &ver); + dns_rriterator_init(&rrit, db, ver, 0); + for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS; + result = dns_rriterator_nextrrset(&rrit)) + { + char buf[DNS_NAME_FORMATSIZE + 500]; + dns_name_t *name = NULL; + dns_rdataset_t *kdset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t kd; + uint32_t ttl; + + dns_rriterator_current(&rrit, &name, &ttl, &kdset, NULL); + if (kdset == NULL || kdset->type != dns_rdatatype_keydata || + !dns_rdataset_isassociated(kdset)) + { + continue; + } + + if (name != prevname) { + char nbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, nbuf, sizeof(nbuf)); + snprintf(buf, sizeof(buf), "\n\n name: %s", nbuf); + CHECK(putstr(text, buf)); + } + + for (result = dns_rdataset_first(kdset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(kdset)) + { + char alg[DNS_SECALG_FORMATSIZE]; + char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + dns_keytag_t keyid; + isc_region_t r; + isc_time_t t; + bool revoked; + + dns_rdata_reset(&rdata); + dns_rdataset_current(kdset, &rdata); + result = dns_rdata_tostruct(&rdata, &kd, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdata_toregion(&rdata, &r); + isc_region_consume(&r, 12); + keyid = dst_region_computeid(&r); + + snprintf(buf, sizeof(buf), "\n keyid: %u", keyid); + CHECK(putstr(text, buf)); + + dns_secalg_format(kd.algorithm, alg, sizeof(alg)); + snprintf(buf, sizeof(buf), "\n\talgorithm: %s", alg); + CHECK(putstr(text, buf)); + + revoked = ((kd.flags & DNS_KEYFLAG_REVOKE) != 0); + snprintf(buf, sizeof(buf), "\n\tflags:%s%s%s", + revoked ? " REVOKE" : "", + ((kd.flags & DNS_KEYFLAG_KSK) != 0) ? " SEP" + : "", + (kd.flags == 0) ? " (none)" : ""); + CHECK(putstr(text, buf)); + + isc_time_set(&t, kd.refresh, 0); + isc_time_formathttptimestamp(&t, tbuf, sizeof(tbuf)); + snprintf(buf, sizeof(buf), "\n\tnext refresh: %s", + tbuf); + CHECK(putstr(text, buf)); + + if (kd.removehd != 0) { + isc_time_set(&t, kd.removehd, 0); + isc_time_formathttptimestamp(&t, tbuf, + sizeof(tbuf)); + snprintf(buf, sizeof(buf), "\n\tremove at: %s", + tbuf); + CHECK(putstr(text, buf)); + } + + isc_time_set(&t, kd.addhd, 0); + isc_time_formathttptimestamp(&t, tbuf, sizeof(tbuf)); + if (kd.addhd == 0) { + snprintf(buf, sizeof(buf), "\n\tno trust"); + } else if (revoked) { + snprintf(buf, sizeof(buf), "\n\ttrust revoked"); + } else if (kd.addhd <= now) { + snprintf(buf, sizeof(buf), + "\n\ttrusted since: %s", tbuf); + } else if (kd.addhd > now) { + snprintf(buf, sizeof(buf), + "\n\ttrust pending: %s", tbuf); + } + CHECK(putstr(text, buf)); + } + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + if (ver != NULL) { + dns_rriterator_destroy(&rrit); + dns_db_closeversion(db, &ver, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + + return (result); +} + +static isc_result_t +mkey_status(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + char msg[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_t t; + + CHECK(putstr(text, "view: ")); + CHECK(putstr(text, view->name)); + + CHECK(putstr(text, "\nnext scheduled event: ")); + + dns_zone_getrefreshkeytime(view->managed_keys, &t); + if (isc_time_isepoch(&t)) { + CHECK(putstr(text, "never")); + } else { + isc_time_formathttptimestamp(&t, msg, sizeof(msg)); + CHECK(putstr(text, msg)); + } + + CHECK(mkey_dumpzone(view, text)); + +cleanup: + return (result); +} + +isc_result_t +named_server_mkeys(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + char *cmd, *classtxt, *viewtxt = NULL; + isc_result_t result = ISC_R_SUCCESS; + dns_view_t *view = NULL; + dns_rdataclass_t rdclass; + char msg[DNS_NAME_FORMATSIZE + 500] = ""; + enum { NONE, STATUS, REFRESH, SYNC, DESTROY } opt = NONE; + bool found = false; + bool first = true; + + REQUIRE(text != NULL); + + /* Skip rndc command name */ + cmd = next_token(lex, text); + if (cmd == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Get managed-keys subcommand */ + cmd = next_token(lex, text); + if (cmd == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (strcasecmp(cmd, "status") == 0) { + opt = STATUS; + } else if (strcasecmp(cmd, "refresh") == 0) { + opt = REFRESH; + } else if (strcasecmp(cmd, "sync") == 0) { + opt = SYNC; + } else if (strcasecmp(cmd, "destroy") == 0) { + opt = DESTROY; + } else { + snprintf(msg, sizeof(msg), "unknown command '%s'", cmd); + (void)putstr(text, msg); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + isc_textregion_t r; + r.base = classtxt; + r.length = strlen(classtxt); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + snprintf(msg, sizeof(msg), "unknown class '%s'", + classtxt); + (void)putstr(text, msg); + goto cleanup; + } + viewtxt = next_token(lex, text); + } + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewtxt != NULL && (rdclass != view->rdclass || + strcmp(view->name, viewtxt) != 0)) + { + continue; + } + + if (view->managed_keys == NULL) { + if (viewtxt != NULL) { + snprintf(msg, sizeof(msg), + "view '%s': no managed keys", viewtxt); + CHECK(putstr(text, msg)); + goto cleanup; + } else { + continue; + } + } + + found = true; + + switch (opt) { + case REFRESH: + if (!first) { + CHECK(putstr(text, "\n")); + } + CHECK(mkey_refresh(view, text)); + break; + case STATUS: + if (!first) { + CHECK(putstr(text, "\n\n")); + } + CHECK(mkey_status(view, text)); + break; + case SYNC: + CHECK(dns_zone_flush(view->managed_keys)); + break; + case DESTROY: + if (!first) { + CHECK(putstr(text, "\n")); + } + CHECK(mkey_destroy(server, view, text)); + break; + default: + UNREACHABLE(); + } + + if (viewtxt != NULL) { + break; + } + first = false; + } + + if (!found) { + CHECK(putstr(text, "no views with managed keys")); + } + +cleanup: + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + return (result); +} + +isc_result_t +named_server_dnstap(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { +#ifdef HAVE_DNSTAP + char *ptr; + isc_result_t result; + bool reopen = false; + int backups = 0; + + REQUIRE(text != NULL); + + if (server->dtenv == NULL) { + return (ISC_R_NOTFOUND); + } + + /* Check the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* "dnstap-reopen" was used in 9.11.0b1 */ + if (strcasecmp(ptr, "dnstap-reopen") == 0) { + reopen = true; + } else { + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + } + + if (reopen || strcasecmp(ptr, "-reopen") == 0) { + backups = ISC_LOG_ROLLNEVER; + } else if ((strcasecmp(ptr, "-roll") == 0)) { + unsigned int n; + ptr = next_token(lex, text); + if (ptr != NULL) { + unsigned int u; + n = sscanf(ptr, "%u", &u); + if (n != 1U || u > INT_MAX) { + return (ISC_R_BADNUMBER); + } + backups = u; + } else { + backups = ISC_LOG_ROLLINFINITE; + } + } else { + return (DNS_R_SYNTAX); + } + + result = dns_dt_reopen(server->dtenv, backups); + return (result); +#else /* ifdef HAVE_DNSTAP */ + UNUSED(server); + UNUSED(lex); + UNUSED(text); + return (ISC_R_NOTIMPLEMENTED); +#endif /* ifdef HAVE_DNSTAP */ +} + +isc_result_t +named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) { + char *ptr; + isc_result_t result = ISC_R_SUCCESS; + uint32_t initial, idle, keepalive, advertised; + char msg[128]; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_nm_gettimeouts(named_g_netmgr, &initial, &idle, &keepalive, + &advertised); + + /* Look for optional arguments. */ + ptr = next_token(lex, NULL); + if (ptr != NULL) { + CHECK(isc_parse_uint32(&initial, ptr, 10)); + initial *= 100; + if (initial > MAX_INITIAL_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + if (initial < MIN_INITIAL_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + CHECK(isc_parse_uint32(&idle, ptr, 10)); + idle *= 100; + if (idle > MAX_IDLE_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + if (idle < MIN_IDLE_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + CHECK(isc_parse_uint32(&keepalive, ptr, 10)); + keepalive *= 100; + if (keepalive > MAX_KEEPALIVE_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + if (keepalive < MIN_KEEPALIVE_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + CHECK(isc_parse_uint32(&advertised, ptr, 10)); + advertised *= 100; + if (advertised > MAX_ADVERTISED_TIMEOUT) { + CHECK(ISC_R_RANGE); + } + + result = isc_task_beginexclusive(named_g_server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_nm_settimeouts(named_g_netmgr, initial, idle, keepalive, + advertised); + + isc_task_endexclusive(named_g_server->task); + } + + snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial / 100); + CHECK(putstr(text, msg)); + snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle / 100); + CHECK(putstr(text, msg)); + snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n", + keepalive / 100); + CHECK(putstr(text, msg)); + snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u", + advertised / 100); + CHECK(putstr(text, msg)); + +cleanup: + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + return (result); +} + +isc_result_t +named_server_servestale(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + char *ptr, *classtxt, *viewtxt = NULL; + char msg[128]; + dns_rdataclass_t rdclass = dns_rdataclass_in; + dns_view_t *view; + bool found = false; + dns_stale_answer_t staleanswersok = dns_stale_answer_conf; + bool wantstatus = false; + isc_result_t result = ISC_R_SUCCESS; + bool exclusive = false; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + ptr = next_token(lex, NULL); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (!strcasecmp(ptr, "on") || !strcasecmp(ptr, "yes") || + !strcasecmp(ptr, "enable") || !strcasecmp(ptr, "true")) + { + staleanswersok = dns_stale_answer_yes; + } else if (!strcasecmp(ptr, "off") || !strcasecmp(ptr, "no") || + !strcasecmp(ptr, "disable") || !strcasecmp(ptr, "false")) + { + staleanswersok = dns_stale_answer_no; + } else if (strcasecmp(ptr, "reset") == 0) { + staleanswersok = dns_stale_answer_conf; + } else if (!strcasecmp(ptr, "check") || !strcasecmp(ptr, "status")) { + wantstatus = true; + } else { + return (DNS_R_SYNTAX); + } + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + isc_textregion_t r; + + /* Look for the optional view name. */ + viewtxt = next_token(lex, text); + + /* + * If 'classtext' is not a valid class then it us a view name. + */ + r.base = classtxt; + r.length = strlen(classtxt); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + if (viewtxt != NULL) { + snprintf(msg, sizeof(msg), "unknown class '%s'", + classtxt); + (void)putstr(text, msg); + goto cleanup; + } + + viewtxt = classtxt; + classtxt = NULL; + } + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + dns_ttl_t stale_ttl = 0; + uint32_t stale_refresh = 0; + dns_db_t *db = NULL; + + if (classtxt != NULL && rdclass != view->rdclass) { + continue; + } + + if (viewtxt != NULL && strcmp(view->name, viewtxt) != 0) { + continue; + } + + if (!wantstatus) { + view->staleanswersok = staleanswersok; + found = true; + continue; + } + + db = NULL; + dns_db_attach(view->cachedb, &db); + (void)dns_db_getservestalettl(db, &stale_ttl); + (void)dns_db_getservestalerefresh(db, &stale_refresh); + dns_db_detach(&db); + if (found) { + CHECK(putstr(text, "\n")); + } + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ": ")); + switch (view->staleanswersok) { + case dns_stale_answer_yes: + if (stale_ttl > 0) { + CHECK(putstr(text, "stale cache enabled; stale " + "answers enabled")); + } else { + CHECK(putstr(text, + "stale cache disabled; stale " + "answers unavailable")); + } + break; + case dns_stale_answer_no: + if (stale_ttl > 0) { + CHECK(putstr(text, "stale cache enabled; stale " + "answers disabled")); + } else { + CHECK(putstr(text, + "stale cache disabled; stale " + "answers unavailable")); + } + break; + case dns_stale_answer_conf: + if (view->staleanswersenable && stale_ttl > 0) { + CHECK(putstr(text, "stale cache enabled; stale " + "answers enabled")); + } else if (stale_ttl > 0) { + CHECK(putstr(text, "stale cache enabled; stale " + "answers disabled")); + } else { + CHECK(putstr(text, + "stale cache disabled; stale " + "answers unavailable")); + } + break; + } + if (stale_ttl > 0) { + snprintf(msg, sizeof(msg), + " (stale-answer-ttl=%u max-stale-ttl=%u " + "stale-refresh-time=%u)", + view->staleanswerttl, stale_ttl, + stale_refresh); + CHECK(putstr(text, msg)); + } + found = true; + } + + if (!found) { + result = ISC_R_NOTFOUND; + } + +cleanup: + if (exclusive) { + isc_task_endexclusive(named_g_server->task); + } + + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + return (result); +} diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c new file mode 100644 index 0000000..2c4760c --- /dev/null +++ b/bin/named/statschannel.c @@ -0,0 +1,4084 @@ +/* + * 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 <isc/buffer.h> +#include <isc/httpd.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/print.h> +#include <isc/stats.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/cache.h> +#include <dns/db.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/rdataclass.h> +#include <dns/rdatatype.h> +#include <dns/resolver.h> +#include <dns/stats.h> +#include <dns/view.h> +#include <dns/zt.h> + +#include <ns/stats.h> + +#include <named/log.h> +#include <named/server.h> +#include <named/statschannel.h> + +#if HAVE_JSON_C +#include <json_object.h> +#include <linkhash.h> +#endif /* HAVE_JSON_C */ + +#if HAVE_LIBXML2 +#include <libxml/xmlwriter.h> +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +#include "xsl_p.h" + +#define STATS_XML_VERSION_MAJOR "3" +#define STATS_XML_VERSION_MINOR "13" +#define STATS_XML_VERSION STATS_XML_VERSION_MAJOR "." STATS_XML_VERSION_MINOR + +#define STATS_JSON_VERSION_MAJOR "1" +#define STATS_JSON_VERSION_MINOR "7" +#define STATS_JSON_VERSION STATS_JSON_VERSION_MAJOR "." STATS_JSON_VERSION_MINOR + +#define CHECK(m) \ + do { \ + result = (m); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +struct named_statschannel { + /* Unlocked */ + isc_httpdmgr_t *httpdmgr; + isc_sockaddr_t address; + isc_mem_t *mctx; + + /* + * Locked by channel lock: can be referenced and modified by both + * the server task and the channel task. + */ + isc_mutex_t lock; + dns_acl_t *acl; + + /* Locked by server task */ + ISC_LINK(struct named_statschannel) link; +}; + +typedef struct stats_dumparg { + isc_statsformat_t type; + void *arg; /* type dependent argument */ + int ncounters; /* for general statistics */ + int *counterindices; /* for general statistics */ + uint64_t *countervalues; /* for general statistics */ + isc_result_t result; +} stats_dumparg_t; + +static isc_once_t once = ISC_ONCE_INIT; + +#if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) +#define EXTENDED_STATS +#else /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */ +#undef EXTENDED_STATS +#endif /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */ + +#ifdef EXTENDED_STATS +static const char * +user_zonetype(dns_zone_t *zone) { + dns_zonetype_t ztype; + dns_view_t *view; + static const struct zt { + const dns_zonetype_t type; + const char *const string; + } typemap[] = { { dns_zone_none, "none" }, + { dns_zone_primary, "primary" }, + { dns_zone_secondary, "secondary" }, + { dns_zone_mirror, "mirror" }, + { dns_zone_stub, "stub" }, + { dns_zone_staticstub, "static-stub" }, + { dns_zone_key, "key" }, + { dns_zone_dlz, "dlz" }, + { dns_zone_redirect, "redirect" }, + { 0, NULL } }; + const struct zt *tp; + + if ((dns_zone_getoptions(zone) & DNS_ZONEOPT_AUTOEMPTY) != 0) { + return ("builtin"); + } + + view = dns_zone_getview(zone); + if (view != NULL && strcmp(view->name, "_bind") == 0) { + return ("builtin"); + } + + ztype = dns_zone_gettype(zone); + for (tp = typemap; tp->string != NULL && tp->type != ztype; tp++) { + /* empty */ + } + return (tp->string); +} +#endif /* ifdef EXTENDED_STATS */ + +/*% + * Statistics descriptions. These could be statistically initialized at + * compile time, but we configure them run time in the init_desc() function + * below so that they'll be less susceptible to counter name changes. + */ +static const char *nsstats_desc[ns_statscounter_max]; +static const char *resstats_desc[dns_resstatscounter_max]; +static const char *adbstats_desc[dns_adbstats_max]; +static const char *zonestats_desc[dns_zonestatscounter_max]; +static const char *sockstats_desc[isc_sockstatscounter_max]; +static const char *dnssecstats_desc[dns_dnssecstats_max]; +static const char *udpinsizestats_desc[dns_sizecounter_in_max]; +static const char *udpoutsizestats_desc[dns_sizecounter_out_max]; +static const char *tcpinsizestats_desc[dns_sizecounter_in_max]; +static const char *tcpoutsizestats_desc[dns_sizecounter_out_max]; +static const char *dnstapstats_desc[dns_dnstapcounter_max]; +static const char *gluecachestats_desc[dns_gluecachestatscounter_max]; +#if defined(EXTENDED_STATS) +static const char *nsstats_xmldesc[ns_statscounter_max]; +static const char *resstats_xmldesc[dns_resstatscounter_max]; +static const char *adbstats_xmldesc[dns_adbstats_max]; +static const char *zonestats_xmldesc[dns_zonestatscounter_max]; +static const char *sockstats_xmldesc[isc_sockstatscounter_max]; +static const char *dnssecstats_xmldesc[dns_dnssecstats_max]; +static const char *udpinsizestats_xmldesc[dns_sizecounter_in_max]; +static const char *udpoutsizestats_xmldesc[dns_sizecounter_out_max]; +static const char *tcpinsizestats_xmldesc[dns_sizecounter_in_max]; +static const char *tcpoutsizestats_xmldesc[dns_sizecounter_out_max]; +static const char *dnstapstats_xmldesc[dns_dnstapcounter_max]; +static const char *gluecachestats_xmldesc[dns_gluecachestatscounter_max]; +#else /* if defined(EXTENDED_STATS) */ +#define nsstats_xmldesc NULL +#define resstats_xmldesc NULL +#define adbstats_xmldesc NULL +#define zonestats_xmldesc NULL +#define sockstats_xmldesc NULL +#define dnssecstats_xmldesc NULL +#define udpinsizestats_xmldesc NULL +#define udpoutsizestats_xmldesc NULL +#define tcpinsizestats_xmldesc NULL +#define tcpoutsizestats_xmldesc NULL +#define dnstapstats_xmldesc NULL +#define gluecachestats_xmldesc NULL +#endif /* EXTENDED_STATS */ + +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto cleanup; \ + } while (0) + +/*% + * Mapping arrays to represent statistics counters in the order of our + * preference, regardless of the order of counter indices. For example, + * nsstats_desc[nsstats_index[0]] will be the description that is shown first. + */ +static int nsstats_index[ns_statscounter_max]; +static int resstats_index[dns_resstatscounter_max]; +static int adbstats_index[dns_adbstats_max]; +static int zonestats_index[dns_zonestatscounter_max]; +static int sockstats_index[isc_sockstatscounter_max]; +static int dnssecstats_index[dns_dnssecstats_max]; +static int udpinsizestats_index[dns_sizecounter_in_max]; +static int udpoutsizestats_index[dns_sizecounter_out_max]; +static int tcpinsizestats_index[dns_sizecounter_in_max]; +static int tcpoutsizestats_index[dns_sizecounter_out_max]; +static int dnstapstats_index[dns_dnstapcounter_max]; +static int gluecachestats_index[dns_gluecachestatscounter_max]; + +static void +set_desc(int counter, int maxcounter, const char *fdesc, const char **fdescs, + const char *xdesc, const char **xdescs) { + REQUIRE(counter < maxcounter); + REQUIRE(fdescs != NULL && fdescs[counter] == NULL); +#if defined(EXTENDED_STATS) + REQUIRE(xdescs != NULL && xdescs[counter] == NULL); +#endif /* if defined(EXTENDED_STATS) */ + + fdescs[counter] = fdesc; +#if defined(EXTENDED_STATS) + xdescs[counter] = xdesc; +#else /* if defined(EXTENDED_STATS) */ + UNUSED(xdesc); + UNUSED(xdescs); +#endif /* if defined(EXTENDED_STATS) */ +} + +static void +init_desc(void) { + int i; + + /* Initialize name server statistics */ + for (i = 0; i < ns_statscounter_max; i++) { + nsstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < ns_statscounter_max; i++) { + nsstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_NSSTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(ns_statscounter_##counterid, ns_statscounter_max, \ + desc, nsstats_desc, xmldesc, nsstats_xmldesc); \ + nsstats_index[i++] = ns_statscounter_##counterid; \ + } while (0) + + i = 0; + SET_NSSTATDESC(requestv4, "IPv4 requests received", "Requestv4"); + SET_NSSTATDESC(requestv6, "IPv6 requests received", "Requestv6"); + SET_NSSTATDESC(edns0in, "requests with EDNS(0) received", "ReqEdns0"); + SET_NSSTATDESC(badednsver, + "requests with unsupported EDNS version received", + "ReqBadEDNSVer"); + SET_NSSTATDESC(tsigin, "requests with TSIG received", "ReqTSIG"); + SET_NSSTATDESC(sig0in, "requests with SIG(0) received", "ReqSIG0"); + SET_NSSTATDESC(invalidsig, "requests with invalid signature", + "ReqBadSIG"); + SET_NSSTATDESC(requesttcp, "TCP requests received", "ReqTCP"); + SET_NSSTATDESC(tcphighwater, "TCP connection high-water", + "TCPConnHighWater"); + SET_NSSTATDESC(authrej, "auth queries rejected", "AuthQryRej"); + SET_NSSTATDESC(recurserej, "recursive queries rejected", "RecQryRej"); + SET_NSSTATDESC(xfrrej, "transfer requests rejected", "XfrRej"); + SET_NSSTATDESC(updaterej, "update requests rejected", "UpdateRej"); + SET_NSSTATDESC(response, "responses sent", "Response"); + SET_NSSTATDESC(truncatedresp, "truncated responses sent", + "TruncatedResp"); + SET_NSSTATDESC(edns0out, "responses with EDNS(0) sent", "RespEDNS0"); + SET_NSSTATDESC(tsigout, "responses with TSIG sent", "RespTSIG"); + SET_NSSTATDESC(sig0out, "responses with SIG(0) sent", "RespSIG0"); + SET_NSSTATDESC(success, "queries resulted in successful answer", + "QrySuccess"); + SET_NSSTATDESC(authans, "queries resulted in authoritative answer", + "QryAuthAns"); + SET_NSSTATDESC(nonauthans, + "queries resulted in non authoritative answer", + "QryNoauthAns"); + SET_NSSTATDESC(referral, "queries resulted in referral answer", + "QryReferral"); + SET_NSSTATDESC(nxrrset, "queries resulted in nxrrset", "QryNxrrset"); + SET_NSSTATDESC(servfail, "queries resulted in SERVFAIL", "QrySERVFAIL"); + SET_NSSTATDESC(formerr, "queries resulted in FORMERR", "QryFORMERR"); + SET_NSSTATDESC(nxdomain, "queries resulted in NXDOMAIN", "QryNXDOMAIN"); + SET_NSSTATDESC(recursion, "queries caused recursion", "QryRecursion"); + SET_NSSTATDESC(duplicate, "duplicate queries received", "QryDuplicate"); + SET_NSSTATDESC(dropped, "queries dropped", "QryDropped"); + SET_NSSTATDESC(failure, "other query failures", "QryFailure"); + SET_NSSTATDESC(xfrdone, "requested transfers completed", "XfrReqDone"); + SET_NSSTATDESC(updatereqfwd, "update requests forwarded", + "UpdateReqFwd"); + SET_NSSTATDESC(updaterespfwd, "update responses forwarded", + "UpdateRespFwd"); + SET_NSSTATDESC(updatefwdfail, "update forward failed", "UpdateFwdFail"); + SET_NSSTATDESC(updatedone, "updates completed", "UpdateDone"); + SET_NSSTATDESC(updatefail, "updates failed", "UpdateFail"); + SET_NSSTATDESC(updatebadprereq, + "updates rejected due to prerequisite failure", + "UpdateBadPrereq"); + SET_NSSTATDESC(recursclients, "recursing clients", "RecursClients"); + SET_NSSTATDESC(dns64, "queries answered by DNS64", "DNS64"); + SET_NSSTATDESC(ratedropped, "responses dropped for rate limits", + "RateDropped"); + SET_NSSTATDESC(rateslipped, "responses truncated for rate limits", + "RateSlipped"); + SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites", + "RPZRewrites"); + SET_NSSTATDESC(udp, "UDP queries received", "QryUDP"); + SET_NSSTATDESC(tcp, "TCP queries received", "QryTCP"); + SET_NSSTATDESC(nsidopt, "NSID option received", "NSIDOpt"); + SET_NSSTATDESC(expireopt, "Expire option received", "ExpireOpt"); + SET_NSSTATDESC(keepaliveopt, "EDNS TCP keepalive option received", + "KeepAliveOpt"); + SET_NSSTATDESC(padopt, "EDNS padding option received", "PadOpt"); + SET_NSSTATDESC(otheropt, "Other EDNS option received", "OtherOpt"); + SET_NSSTATDESC(cookiein, "COOKIE option received", "CookieIn"); + SET_NSSTATDESC(cookienew, "COOKIE - client only", "CookieNew"); + SET_NSSTATDESC(cookiebadsize, "COOKIE - bad size", "CookieBadSize"); + SET_NSSTATDESC(cookiebadtime, "COOKIE - bad time", "CookieBadTime"); + SET_NSSTATDESC(cookienomatch, "COOKIE - no match", "CookieNoMatch"); + SET_NSSTATDESC(cookiematch, "COOKIE - match", "CookieMatch"); + SET_NSSTATDESC(ecsopt, "EDNS client subnet option received", "ECSOpt"); + SET_NSSTATDESC(nxdomainredirect, + "queries resulted in NXDOMAIN that were redirected", + "QryNXRedir"); + SET_NSSTATDESC(nxdomainredirect_rlookup, + "queries resulted in NXDOMAIN that were redirected and " + "resulted in a successful remote lookup", + "QryNXRedirRLookup"); + SET_NSSTATDESC(badcookie, "sent badcookie response", "QryBADCOOKIE"); + SET_NSSTATDESC(nxdomainsynth, "synthesized a NXDOMAIN response", + "SynthNXDOMAIN"); + SET_NSSTATDESC(nodatasynth, "synthesized a no-data response", + "SynthNODATA"); + SET_NSSTATDESC(wildcardsynth, "synthesized a wildcard response", + "SynthWILDCARD"); + SET_NSSTATDESC(trystale, + "attempts to use stale cache data after lookup failure", + "QryTryStale"); + SET_NSSTATDESC(usedstale, + "successful uses of stale cache data after lookup " + "failure", + "QryUsedStale"); + SET_NSSTATDESC(prefetch, "queries triggered prefetch", "Prefetch"); + SET_NSSTATDESC(keytagopt, "Keytag option received", "KeyTagOpt"); + SET_NSSTATDESC(reclimitdropped, + "queries dropped due to recursive client limit", + "RecLimitDropped"); + SET_NSSTATDESC(updatequota, "Update quota exceeded", "UpdateQuota"); + + INSIST(i == ns_statscounter_max); + + /* Initialize resolver statistics */ + for (i = 0; i < dns_resstatscounter_max; i++) { + resstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < dns_resstatscounter_max; i++) { + resstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_RESSTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(dns_resstatscounter_##counterid, \ + dns_resstatscounter_max, desc, resstats_desc, \ + xmldesc, resstats_xmldesc); \ + resstats_index[i++] = dns_resstatscounter_##counterid; \ + } while (0) + + i = 0; + SET_RESSTATDESC(queryv4, "IPv4 queries sent", "Queryv4"); + SET_RESSTATDESC(queryv6, "IPv6 queries sent", "Queryv6"); + SET_RESSTATDESC(responsev4, "IPv4 responses received", "Responsev4"); + SET_RESSTATDESC(responsev6, "IPv6 responses received", "Responsev6"); + SET_RESSTATDESC(nxdomain, "NXDOMAIN received", "NXDOMAIN"); + SET_RESSTATDESC(servfail, "SERVFAIL received", "SERVFAIL"); + SET_RESSTATDESC(formerr, "FORMERR received", "FORMERR"); + SET_RESSTATDESC(othererror, "other errors received", "OtherError"); + SET_RESSTATDESC(edns0fail, "EDNS(0) query failures", "EDNS0Fail"); + SET_RESSTATDESC(mismatch, "mismatch responses received", "Mismatch"); + SET_RESSTATDESC(truncated, "truncated responses received", "Truncated"); + SET_RESSTATDESC(lame, "lame delegations received", "Lame"); + SET_RESSTATDESC(retry, "query retries", "Retry"); + SET_RESSTATDESC(dispabort, "queries aborted due to quota", + "QueryAbort"); + SET_RESSTATDESC(dispsockfail, "failures in opening query sockets", + "QuerySockFail"); + SET_RESSTATDESC(disprequdp, "UDP queries in progress", "QueryCurUDP"); + SET_RESSTATDESC(dispreqtcp, "TCP queries in progress", "QueryCurTCP"); + SET_RESSTATDESC(querytimeout, "query timeouts", "QueryTimeout"); + SET_RESSTATDESC(gluefetchv4, "IPv4 NS address fetches", "GlueFetchv4"); + SET_RESSTATDESC(gluefetchv6, "IPv6 NS address fetches", "GlueFetchv6"); + SET_RESSTATDESC(gluefetchv4fail, "IPv4 NS address fetch failed", + "GlueFetchv4Fail"); + SET_RESSTATDESC(gluefetchv6fail, "IPv6 NS address fetch failed", + "GlueFetchv6Fail"); + SET_RESSTATDESC(val, "DNSSEC validation attempted", "ValAttempt"); + SET_RESSTATDESC(valsuccess, "DNSSEC validation succeeded", "ValOk"); + SET_RESSTATDESC(valnegsuccess, "DNSSEC NX validation succeeded", + "ValNegOk"); + SET_RESSTATDESC(valfail, "DNSSEC validation failed", "ValFail"); + SET_RESSTATDESC(queryrtt0, + "queries with RTT < " DNS_RESOLVER_QRYRTTCLASS0STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS0STR); + SET_RESSTATDESC(queryrtt1, + "queries with RTT " DNS_RESOLVER_QRYRTTCLASS0STR + "-" DNS_RESOLVER_QRYRTTCLASS1STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS1STR); + SET_RESSTATDESC(queryrtt2, + "queries with RTT " DNS_RESOLVER_QRYRTTCLASS1STR + "-" DNS_RESOLVER_QRYRTTCLASS2STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS2STR); + SET_RESSTATDESC(queryrtt3, + "queries with RTT " DNS_RESOLVER_QRYRTTCLASS2STR + "-" DNS_RESOLVER_QRYRTTCLASS3STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS3STR); + SET_RESSTATDESC(queryrtt4, + "queries with RTT " DNS_RESOLVER_QRYRTTCLASS3STR + "-" DNS_RESOLVER_QRYRTTCLASS4STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS4STR); + SET_RESSTATDESC(queryrtt5, + "queries with RTT > " DNS_RESOLVER_QRYRTTCLASS4STR "ms", + "QryRTT" DNS_RESOLVER_QRYRTTCLASS4STR "+"); + SET_RESSTATDESC(nfetch, "active fetches", "NumFetch"); + SET_RESSTATDESC(buckets, "bucket size", "BucketSize"); + SET_RESSTATDESC(refused, "REFUSED received", "REFUSED"); + SET_RESSTATDESC(cookienew, "COOKIE send with client cookie only", + "ClientCookieOut"); + SET_RESSTATDESC(cookieout, "COOKIE sent with client and server cookie", + "ServerCookieOut"); + SET_RESSTATDESC(cookiein, "COOKIE replies received", "CookieIn"); + SET_RESSTATDESC(cookieok, "COOKIE client ok", "CookieClientOk"); + SET_RESSTATDESC(badvers, "bad EDNS version", "BadEDNSVersion"); + SET_RESSTATDESC(badcookie, "bad cookie rcode", "BadCookieRcode"); + SET_RESSTATDESC(zonequota, "spilled due to zone quota", "ZoneQuota"); + SET_RESSTATDESC(serverquota, "spilled due to server quota", + "ServerQuota"); + SET_RESSTATDESC(clientquota, "spilled due to clients per query quota", + "ClientQuota"); + SET_RESSTATDESC(nextitem, "waited for next item", "NextItem"); + SET_RESSTATDESC(priming, "priming queries", "Priming"); + + INSIST(i == dns_resstatscounter_max); + + /* Initialize adb statistics */ + for (i = 0; i < dns_adbstats_max; i++) { + adbstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < dns_adbstats_max; i++) { + adbstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_ADBSTATDESC(id, desc, xmldesc) \ + do { \ + set_desc(dns_adbstats_##id, dns_adbstats_max, desc, \ + adbstats_desc, xmldesc, adbstats_xmldesc); \ + adbstats_index[i++] = dns_adbstats_##id; \ + } while (0) + i = 0; + SET_ADBSTATDESC(nentries, "Address hash table size", "nentries"); + SET_ADBSTATDESC(entriescnt, "Addresses in hash table", "entriescnt"); + SET_ADBSTATDESC(nnames, "Name hash table size", "nnames"); + SET_ADBSTATDESC(namescnt, "Names in hash table", "namescnt"); + + INSIST(i == dns_adbstats_max); + + /* Initialize zone statistics */ + for (i = 0; i < dns_zonestatscounter_max; i++) { + zonestats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < dns_zonestatscounter_max; i++) { + zonestats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_ZONESTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(dns_zonestatscounter_##counterid, \ + dns_zonestatscounter_max, desc, zonestats_desc, \ + xmldesc, zonestats_xmldesc); \ + zonestats_index[i++] = dns_zonestatscounter_##counterid; \ + } while (0) + + i = 0; + SET_ZONESTATDESC(notifyoutv4, "IPv4 notifies sent", "NotifyOutv4"); + SET_ZONESTATDESC(notifyoutv6, "IPv6 notifies sent", "NotifyOutv6"); + SET_ZONESTATDESC(notifyinv4, "IPv4 notifies received", "NotifyInv4"); + SET_ZONESTATDESC(notifyinv6, "IPv6 notifies received", "NotifyInv6"); + SET_ZONESTATDESC(notifyrej, "notifies rejected", "NotifyRej"); + SET_ZONESTATDESC(soaoutv4, "IPv4 SOA queries sent", "SOAOutv4"); + SET_ZONESTATDESC(soaoutv6, "IPv6 SOA queries sent", "SOAOutv6"); + SET_ZONESTATDESC(axfrreqv4, "IPv4 AXFR requested", "AXFRReqv4"); + SET_ZONESTATDESC(axfrreqv6, "IPv6 AXFR requested", "AXFRReqv6"); + SET_ZONESTATDESC(ixfrreqv4, "IPv4 IXFR requested", "IXFRReqv4"); + SET_ZONESTATDESC(ixfrreqv6, "IPv6 IXFR requested", "IXFRReqv6"); + SET_ZONESTATDESC(xfrsuccess, "transfer requests succeeded", + "XfrSuccess"); + SET_ZONESTATDESC(xfrfail, "transfer requests failed", "XfrFail"); + INSIST(i == dns_zonestatscounter_max); + + /* Initialize socket statistics */ + for (i = 0; i < isc_sockstatscounter_max; i++) { + sockstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < isc_sockstatscounter_max; i++) { + sockstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_SOCKSTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(isc_sockstatscounter_##counterid, \ + isc_sockstatscounter_max, desc, sockstats_desc, \ + xmldesc, sockstats_xmldesc); \ + sockstats_index[i++] = isc_sockstatscounter_##counterid; \ + } while (0) + + i = 0; + SET_SOCKSTATDESC(udp4open, "UDP/IPv4 sockets opened", "UDP4Open"); + SET_SOCKSTATDESC(udp6open, "UDP/IPv6 sockets opened", "UDP6Open"); + SET_SOCKSTATDESC(tcp4open, "TCP/IPv4 sockets opened", "TCP4Open"); + SET_SOCKSTATDESC(tcp6open, "TCP/IPv6 sockets opened", "TCP6Open"); + SET_SOCKSTATDESC(unixopen, "Unix domain sockets opened", "UnixOpen"); + SET_SOCKSTATDESC(rawopen, "Raw sockets opened", "RawOpen"); + SET_SOCKSTATDESC(udp4openfail, "UDP/IPv4 socket open failures", + "UDP4OpenFail"); + SET_SOCKSTATDESC(udp6openfail, "UDP/IPv6 socket open failures", + "UDP6OpenFail"); + SET_SOCKSTATDESC(tcp4openfail, "TCP/IPv4 socket open failures", + "TCP4OpenFail"); + SET_SOCKSTATDESC(tcp6openfail, "TCP/IPv6 socket open failures", + "TCP6OpenFail"); + SET_SOCKSTATDESC(unixopenfail, "Unix domain socket open failures", + "UnixOpenFail"); + SET_SOCKSTATDESC(rawopenfail, "Raw socket open failures", + "RawOpenFail"); + SET_SOCKSTATDESC(udp4close, "UDP/IPv4 sockets closed", "UDP4Close"); + SET_SOCKSTATDESC(udp6close, "UDP/IPv6 sockets closed", "UDP6Close"); + SET_SOCKSTATDESC(tcp4close, "TCP/IPv4 sockets closed", "TCP4Close"); + SET_SOCKSTATDESC(tcp6close, "TCP/IPv6 sockets closed", "TCP6Close"); + SET_SOCKSTATDESC(unixclose, "Unix domain sockets closed", "UnixClose"); + SET_SOCKSTATDESC(fdwatchclose, "FDwatch sockets closed", + "FDWatchClose"); + SET_SOCKSTATDESC(rawclose, "Raw sockets closed", "RawClose"); + SET_SOCKSTATDESC(udp4bindfail, "UDP/IPv4 socket bind failures", + "UDP4BindFail"); + SET_SOCKSTATDESC(udp6bindfail, "UDP/IPv6 socket bind failures", + "UDP6BindFail"); + SET_SOCKSTATDESC(tcp4bindfail, "TCP/IPv4 socket bind failures", + "TCP4BindFail"); + SET_SOCKSTATDESC(tcp6bindfail, "TCP/IPv6 socket bind failures", + "TCP6BindFail"); + SET_SOCKSTATDESC(unixbindfail, "Unix domain socket bind failures", + "UnixBindFail"); + SET_SOCKSTATDESC(fdwatchbindfail, "FDwatch socket bind failures", + "FdwatchBindFail"); + SET_SOCKSTATDESC(udp4connectfail, "UDP/IPv4 socket connect failures", + "UDP4ConnFail"); + SET_SOCKSTATDESC(udp6connectfail, "UDP/IPv6 socket connect failures", + "UDP6ConnFail"); + SET_SOCKSTATDESC(tcp4connectfail, "TCP/IPv4 socket connect failures", + "TCP4ConnFail"); + SET_SOCKSTATDESC(tcp6connectfail, "TCP/IPv6 socket connect failures", + "TCP6ConnFail"); + SET_SOCKSTATDESC(unixconnectfail, "Unix domain socket connect failures", + "UnixConnFail"); + SET_SOCKSTATDESC(fdwatchconnectfail, "FDwatch socket connect failures", + "FDwatchConnFail"); + SET_SOCKSTATDESC(udp4connect, "UDP/IPv4 connections established", + "UDP4Conn"); + SET_SOCKSTATDESC(udp6connect, "UDP/IPv6 connections established", + "UDP6Conn"); + SET_SOCKSTATDESC(tcp4connect, "TCP/IPv4 connections established", + "TCP4Conn"); + SET_SOCKSTATDESC(tcp6connect, "TCP/IPv6 connections established", + "TCP6Conn"); + SET_SOCKSTATDESC(unixconnect, "Unix domain connections established", + "UnixConn"); + SET_SOCKSTATDESC(fdwatchconnect, + "FDwatch domain connections established", + "FDwatchConn"); + SET_SOCKSTATDESC(tcp4acceptfail, "TCP/IPv4 connection accept failures", + "TCP4AcceptFail"); + SET_SOCKSTATDESC(tcp6acceptfail, "TCP/IPv6 connection accept failures", + "TCP6AcceptFail"); + SET_SOCKSTATDESC(unixacceptfail, + "Unix domain connection accept failures", + "UnixAcceptFail"); + SET_SOCKSTATDESC(tcp4accept, "TCP/IPv4 connections accepted", + "TCP4Accept"); + SET_SOCKSTATDESC(tcp6accept, "TCP/IPv6 connections accepted", + "TCP6Accept"); + SET_SOCKSTATDESC(unixaccept, "Unix domain connections accepted", + "UnixAccept"); + SET_SOCKSTATDESC(udp4sendfail, "UDP/IPv4 send errors", "UDP4SendErr"); + SET_SOCKSTATDESC(udp6sendfail, "UDP/IPv6 send errors", "UDP6SendErr"); + SET_SOCKSTATDESC(tcp4sendfail, "TCP/IPv4 send errors", "TCP4SendErr"); + SET_SOCKSTATDESC(tcp6sendfail, "TCP/IPv6 send errors", "TCP6SendErr"); + SET_SOCKSTATDESC(unixsendfail, "Unix domain send errors", + "UnixSendErr"); + SET_SOCKSTATDESC(fdwatchsendfail, "FDwatch send errors", + "FDwatchSendErr"); + SET_SOCKSTATDESC(udp4recvfail, "UDP/IPv4 recv errors", "UDP4RecvErr"); + SET_SOCKSTATDESC(udp6recvfail, "UDP/IPv6 recv errors", "UDP6RecvErr"); + SET_SOCKSTATDESC(tcp4recvfail, "TCP/IPv4 recv errors", "TCP4RecvErr"); + SET_SOCKSTATDESC(tcp6recvfail, "TCP/IPv6 recv errors", "TCP6RecvErr"); + SET_SOCKSTATDESC(unixrecvfail, "Unix domain recv errors", + "UnixRecvErr"); + SET_SOCKSTATDESC(fdwatchrecvfail, "FDwatch recv errors", + "FDwatchRecvErr"); + SET_SOCKSTATDESC(rawrecvfail, "Raw recv errors", "RawRecvErr"); + SET_SOCKSTATDESC(udp4active, "UDP/IPv4 sockets active", "UDP4Active"); + SET_SOCKSTATDESC(udp6active, "UDP/IPv6 sockets active", "UDP6Active"); + SET_SOCKSTATDESC(tcp4active, "TCP/IPv4 sockets active", "TCP4Active"); + SET_SOCKSTATDESC(tcp6active, "TCP/IPv6 sockets active", "TCP6Active"); + SET_SOCKSTATDESC(unixactive, "Unix domain sockets active", + "UnixActive"); + SET_SOCKSTATDESC(rawactive, "Raw sockets active", "RawActive"); + INSIST(i == isc_sockstatscounter_max); + + /* Initialize DNSSEC statistics */ + for (i = 0; i < dns_dnssecstats_max; i++) { + dnssecstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < dns_dnssecstats_max; i++) { + dnssecstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_DNSSECSTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(dns_dnssecstats_##counterid, dns_dnssecstats_max, \ + desc, dnssecstats_desc, xmldesc, \ + dnssecstats_xmldesc); \ + dnssecstats_index[i++] = dns_dnssecstats_##counterid; \ + } while (0) + + i = 0; + SET_DNSSECSTATDESC(asis, + "dnssec validation success with signer " + "\"as is\"", + "DNSSECasis"); + SET_DNSSECSTATDESC(downcase, + "dnssec validation success with signer " + "lower cased", + "DNSSECdowncase"); + SET_DNSSECSTATDESC(wildcard, "dnssec validation of wildcard signature", + "DNSSECwild"); + SET_DNSSECSTATDESC(fail, "dnssec validation failures", "DNSSECfail"); + INSIST(i == dns_dnssecstats_max); + + /* Initialize dnstap statistics */ + for (i = 0; i < dns_dnstapcounter_max; i++) { + dnstapstats_desc[i] = NULL; + } +#if defined(EXTENDED_STATS) + for (i = 0; i < dns_dnstapcounter_max; i++) { + dnstapstats_xmldesc[i] = NULL; + } +#endif /* if defined(EXTENDED_STATS) */ + +#define SET_DNSTAPSTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(dns_dnstapcounter_##counterid, dns_dnstapcounter_max, \ + desc, dnstapstats_desc, xmldesc, \ + dnstapstats_xmldesc); \ + dnstapstats_index[i++] = dns_dnstapcounter_##counterid; \ + } while (0) + i = 0; + SET_DNSTAPSTATDESC(success, "dnstap messages written", "DNSTAPsuccess"); + SET_DNSTAPSTATDESC(drop, "dnstap messages dropped", "DNSTAPdropped"); + INSIST(i == dns_dnstapcounter_max); + +#define SET_GLUECACHESTATDESC(counterid, desc, xmldesc) \ + do { \ + set_desc(dns_gluecachestatscounter_##counterid, \ + dns_gluecachestatscounter_max, desc, \ + gluecachestats_desc, xmldesc, \ + gluecachestats_xmldesc); \ + gluecachestats_index[i++] = \ + dns_gluecachestatscounter_##counterid; \ + } while (0) + i = 0; + SET_GLUECACHESTATDESC(hits_present, "Hits for present glue (cached)", + "GLUECACHEhitspresent"); + SET_GLUECACHESTATDESC(hits_absent, + "Hits for non-existent glue (cached)", + "GLUECACHEhitsabsent"); + SET_GLUECACHESTATDESC(inserts_present, + "Miss-plus-cache-inserts for present glue", + "GLUECACHEinsertspresent"); + SET_GLUECACHESTATDESC(inserts_absent, + "Miss-plus-cache-inserts for non-existent glue", + "GLUECACHEinsertsabsent"); + INSIST(i == dns_gluecachestatscounter_max); + + /* Sanity check */ + for (i = 0; i < ns_statscounter_max; i++) { + INSIST(nsstats_desc[i] != NULL); + } + for (i = 0; i < dns_resstatscounter_max; i++) { + INSIST(resstats_desc[i] != NULL); + } + for (i = 0; i < dns_adbstats_max; i++) { + INSIST(adbstats_desc[i] != NULL); + } + for (i = 0; i < dns_zonestatscounter_max; i++) { + INSIST(zonestats_desc[i] != NULL); + } + for (i = 0; i < isc_sockstatscounter_max; i++) { + INSIST(sockstats_desc[i] != NULL); + } + for (i = 0; i < dns_dnssecstats_max; i++) { + INSIST(dnssecstats_desc[i] != NULL); + } + for (i = 0; i < dns_dnstapcounter_max; i++) { + INSIST(dnstapstats_desc[i] != NULL); + } + for (i = 0; i < dns_gluecachestatscounter_max; i++) { + INSIST(gluecachestats_desc[i] != NULL); + } +#if defined(EXTENDED_STATS) + for (i = 0; i < ns_statscounter_max; i++) { + INSIST(nsstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_resstatscounter_max; i++) { + INSIST(resstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_adbstats_max; i++) { + INSIST(adbstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_zonestatscounter_max; i++) { + INSIST(zonestats_xmldesc[i] != NULL); + } + for (i = 0; i < isc_sockstatscounter_max; i++) { + INSIST(sockstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_dnssecstats_max; i++) { + INSIST(dnssecstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_dnstapcounter_max; i++) { + INSIST(dnstapstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_gluecachestatscounter_max; i++) { + INSIST(gluecachestats_xmldesc[i] != NULL); + } +#endif /* if defined(EXTENDED_STATS) */ + + /* Initialize traffic size statistics */ + for (i = 0; i < dns_sizecounter_in_max; i++) { + udpinsizestats_desc[i] = NULL; + tcpinsizestats_desc[i] = NULL; +#if defined(EXTENDED_STATS) + udpinsizestats_xmldesc[i] = NULL; + tcpinsizestats_xmldesc[i] = NULL; +#endif /* if defined(EXTENDED_STATS) */ + } + for (i = 0; i < dns_sizecounter_out_max; i++) { + udpoutsizestats_desc[i] = NULL; + tcpoutsizestats_desc[i] = NULL; +#if defined(EXTENDED_STATS) + udpoutsizestats_xmldesc[i] = NULL; + tcpoutsizestats_xmldesc[i] = NULL; +#endif /* if defined(EXTENDED_STATS) */ + } + +#define SET_SIZESTATDESC(counterid, desc, xmldesc, inout) \ + do { \ + set_desc(dns_sizecounter_##inout##_##counterid, \ + dns_sizecounter_##inout##_max, desc, \ + udp##inout##sizestats_desc, xmldesc, \ + udp##inout##sizestats_xmldesc); \ + set_desc(dns_sizecounter_##inout##_##counterid, \ + dns_sizecounter_##inout##_max, desc, \ + tcp##inout##sizestats_desc, xmldesc, \ + tcp##inout##sizestats_xmldesc); \ + udp##inout##sizestats_index[i] = \ + dns_sizecounter_##inout##_##counterid; \ + tcp##inout##sizestats_index[i] = \ + dns_sizecounter_##inout##_##counterid; \ + i++; \ + } while (0) + + i = 0; + SET_SIZESTATDESC(0, "requests received 0-15 bytes", "0-15", in); + SET_SIZESTATDESC(16, "requests received 16-31 bytes", "16-31", in); + SET_SIZESTATDESC(32, "requests received 32-47 bytes", "32-47", in); + SET_SIZESTATDESC(48, "requests received 48-63 bytes", "48-63", in); + SET_SIZESTATDESC(64, "requests received 64-79 bytes", "64-79", in); + SET_SIZESTATDESC(80, "requests received 80-95 bytes", "80-95", in); + SET_SIZESTATDESC(96, "requests received 96-111 bytes", "96-111", in); + SET_SIZESTATDESC(112, "requests received 112-127 bytes", "112-127", in); + SET_SIZESTATDESC(128, "requests received 128-143 bytes", "128-143", in); + SET_SIZESTATDESC(144, "requests received 144-159 bytes", "144-159", in); + SET_SIZESTATDESC(160, "requests received 160-175 bytes", "160-175", in); + SET_SIZESTATDESC(176, "requests received 176-191 bytes", "176-191", in); + SET_SIZESTATDESC(192, "requests received 192-207 bytes", "192-207", in); + SET_SIZESTATDESC(208, "requests received 208-223 bytes", "208-223", in); + SET_SIZESTATDESC(224, "requests received 224-239 bytes", "224-239", in); + SET_SIZESTATDESC(240, "requests received 240-255 bytes", "240-255", in); + SET_SIZESTATDESC(256, "requests received 256-271 bytes", "256-271", in); + SET_SIZESTATDESC(272, "requests received 272-287 bytes", "272-287", in); + SET_SIZESTATDESC(288, "requests received 288+ bytes", "288+", in); + INSIST(i == dns_sizecounter_in_max); + + i = 0; + SET_SIZESTATDESC(0, "responses sent 0-15 bytes", "0-15", out); + SET_SIZESTATDESC(16, "responses sent 16-31 bytes", "16-31", out); + SET_SIZESTATDESC(32, "responses sent 32-47 bytes", "32-47", out); + SET_SIZESTATDESC(48, "responses sent 48-63 bytes", "48-63", out); + SET_SIZESTATDESC(64, "responses sent 64-79 bytes", "64-79", out); + SET_SIZESTATDESC(80, "responses sent 80-95 bytes", "80-95", out); + SET_SIZESTATDESC(96, "responses sent 96-111 bytes", "96-111", out); + SET_SIZESTATDESC(112, "responses sent 112-127 bytes", "112-127", out); + SET_SIZESTATDESC(128, "responses sent 128-143 bytes", "128-143", out); + SET_SIZESTATDESC(144, "responses sent 144-159 bytes", "144-159", out); + SET_SIZESTATDESC(160, "responses sent 160-175 bytes", "160-175", out); + SET_SIZESTATDESC(176, "responses sent 176-191 bytes", "176-191", out); + SET_SIZESTATDESC(192, "responses sent 192-207 bytes", "192-207", out); + SET_SIZESTATDESC(208, "responses sent 208-223 bytes", "208-223", out); + SET_SIZESTATDESC(224, "responses sent 224-239 bytes", "224-239", out); + SET_SIZESTATDESC(240, "responses sent 240-255 bytes", "240-255", out); + SET_SIZESTATDESC(256, "responses sent 256-271 bytes", "256-271", out); + SET_SIZESTATDESC(272, "responses sent 272-287 bytes", "272-287", out); + SET_SIZESTATDESC(288, "responses sent 288-303 bytes", "288-303", out); + SET_SIZESTATDESC(304, "responses sent 304-319 bytes", "304-319", out); + SET_SIZESTATDESC(320, "responses sent 320-335 bytes", "320-335", out); + SET_SIZESTATDESC(336, "responses sent 336-351 bytes", "336-351", out); + SET_SIZESTATDESC(352, "responses sent 352-367 bytes", "352-367", out); + SET_SIZESTATDESC(368, "responses sent 368-383 bytes", "368-383", out); + SET_SIZESTATDESC(384, "responses sent 384-399 bytes", "384-399", out); + SET_SIZESTATDESC(400, "responses sent 400-415 bytes", "400-415", out); + SET_SIZESTATDESC(416, "responses sent 416-431 bytes", "416-431", out); + SET_SIZESTATDESC(432, "responses sent 432-447 bytes", "432-447", out); + SET_SIZESTATDESC(448, "responses sent 448-463 bytes", "448-463", out); + SET_SIZESTATDESC(464, "responses sent 464-479 bytes", "464-479", out); + SET_SIZESTATDESC(480, "responses sent 480-495 bytes", "480-495", out); + SET_SIZESTATDESC(496, "responses sent 496-511 bytes", "496-511", out); + SET_SIZESTATDESC(512, "responses sent 512-527 bytes", "512-527", out); + SET_SIZESTATDESC(528, "responses sent 528-543 bytes", "528-543", out); + SET_SIZESTATDESC(544, "responses sent 544-559 bytes", "544-559", out); + SET_SIZESTATDESC(560, "responses sent 560-575 bytes", "560-575", out); + SET_SIZESTATDESC(576, "responses sent 576-591 bytes", "576-591", out); + SET_SIZESTATDESC(592, "responses sent 592-607 bytes", "592-607", out); + SET_SIZESTATDESC(608, "responses sent 608-623 bytes", "608-623", out); + SET_SIZESTATDESC(624, "responses sent 624-639 bytes", "624-639", out); + SET_SIZESTATDESC(640, "responses sent 640-655 bytes", "640-655", out); + SET_SIZESTATDESC(656, "responses sent 656-671 bytes", "656-671", out); + SET_SIZESTATDESC(672, "responses sent 672-687 bytes", "672-687", out); + SET_SIZESTATDESC(688, "responses sent 688-703 bytes", "688-703", out); + SET_SIZESTATDESC(704, "responses sent 704-719 bytes", "704-719", out); + SET_SIZESTATDESC(720, "responses sent 720-735 bytes", "720-735", out); + SET_SIZESTATDESC(736, "responses sent 736-751 bytes", "736-751", out); + SET_SIZESTATDESC(752, "responses sent 752-767 bytes", "752-767", out); + SET_SIZESTATDESC(768, "responses sent 768-783 bytes", "768-783", out); + SET_SIZESTATDESC(784, "responses sent 784-799 bytes", "784-799", out); + SET_SIZESTATDESC(800, "responses sent 800-815 bytes", "800-815", out); + SET_SIZESTATDESC(816, "responses sent 816-831 bytes", "816-831", out); + SET_SIZESTATDESC(832, "responses sent 832-847 bytes", "832-847", out); + SET_SIZESTATDESC(848, "responses sent 848-863 bytes", "848-863", out); + SET_SIZESTATDESC(864, "responses sent 864-879 bytes", "864-879", out); + SET_SIZESTATDESC(880, "responses sent 880-895 bytes", "880-895", out); + SET_SIZESTATDESC(896, "responses sent 896-911 bytes", "896-911", out); + SET_SIZESTATDESC(912, "responses sent 912-927 bytes", "912-927", out); + SET_SIZESTATDESC(928, "responses sent 928-943 bytes", "928-943", out); + SET_SIZESTATDESC(944, "responses sent 944-959 bytes", "944-959", out); + SET_SIZESTATDESC(960, "responses sent 960-975 bytes", "960-975", out); + SET_SIZESTATDESC(976, "responses sent 976-991 bytes", "976-991", out); + SET_SIZESTATDESC(992, "responses sent 992-1007 bytes", "992-1007", out); + SET_SIZESTATDESC(1008, "responses sent 1008-1023 bytes", "1008-1023", + out); + SET_SIZESTATDESC(1024, "responses sent 1024-1039 bytes", "1024-1039", + out); + SET_SIZESTATDESC(1040, "responses sent 1040-1055 bytes", "1040-1055", + out); + SET_SIZESTATDESC(1056, "responses sent 1056-1071 bytes", "1056-1071", + out); + SET_SIZESTATDESC(1072, "responses sent 1072-1087 bytes", "1072-1087", + out); + SET_SIZESTATDESC(1088, "responses sent 1088-1103 bytes", "1088-1103", + out); + SET_SIZESTATDESC(1104, "responses sent 1104-1119 bytes", "1104-1119", + out); + SET_SIZESTATDESC(1120, "responses sent 1120-1135 bytes", "1120-1135", + out); + SET_SIZESTATDESC(1136, "responses sent 1136-1151 bytes", "1136-1151", + out); + SET_SIZESTATDESC(1152, "responses sent 1152-1167 bytes", "1152-1167", + out); + SET_SIZESTATDESC(1168, "responses sent 1168-1183 bytes", "1168-1183", + out); + SET_SIZESTATDESC(1184, "responses sent 1184-1199 bytes", "1184-1199", + out); + SET_SIZESTATDESC(1200, "responses sent 1200-1215 bytes", "1200-1215", + out); + SET_SIZESTATDESC(1216, "responses sent 1216-1231 bytes", "1216-1231", + out); + SET_SIZESTATDESC(1232, "responses sent 1232-1247 bytes", "1232-1247", + out); + SET_SIZESTATDESC(1248, "responses sent 1248-1263 bytes", "1248-1263", + out); + SET_SIZESTATDESC(1264, "responses sent 1264-1279 bytes", "1264-1279", + out); + SET_SIZESTATDESC(1280, "responses sent 1280-1295 bytes", "1280-1295", + out); + SET_SIZESTATDESC(1296, "responses sent 1296-1311 bytes", "1296-1311", + out); + SET_SIZESTATDESC(1312, "responses sent 1312-1327 bytes", "1312-1327", + out); + SET_SIZESTATDESC(1328, "responses sent 1328-1343 bytes", "1328-1343", + out); + SET_SIZESTATDESC(1344, "responses sent 1344-1359 bytes", "1344-1359", + out); + SET_SIZESTATDESC(1360, "responses sent 1360-1375 bytes", "1360-1375", + out); + SET_SIZESTATDESC(1376, "responses sent 1376-1391 bytes", "1376-1391", + out); + SET_SIZESTATDESC(1392, "responses sent 1392-1407 bytes", "1392-1407", + out); + SET_SIZESTATDESC(1408, "responses sent 1408-1423 bytes", "1408-1423", + out); + SET_SIZESTATDESC(1424, "responses sent 1424-1439 bytes", "1424-1439", + out); + SET_SIZESTATDESC(1440, "responses sent 1440-1455 bytes", "1440-1455", + out); + SET_SIZESTATDESC(1456, "responses sent 1456-1471 bytes", "1456-1471", + out); + SET_SIZESTATDESC(1472, "responses sent 1472-1487 bytes", "1472-1487", + out); + SET_SIZESTATDESC(1488, "responses sent 1488-1503 bytes", "1488-1503", + out); + SET_SIZESTATDESC(1504, "responses sent 1504-1519 bytes", "1504-1519", + out); + SET_SIZESTATDESC(1520, "responses sent 1520-1535 bytes", "1520-1535", + out); + SET_SIZESTATDESC(1536, "responses sent 1536-1551 bytes", "1536-1551", + out); + SET_SIZESTATDESC(1552, "responses sent 1552-1567 bytes", "1552-1567", + out); + SET_SIZESTATDESC(1568, "responses sent 1568-1583 bytes", "1568-1583", + out); + SET_SIZESTATDESC(1584, "responses sent 1584-1599 bytes", "1584-1599", + out); + SET_SIZESTATDESC(1600, "responses sent 1600-1615 bytes", "1600-1615", + out); + SET_SIZESTATDESC(1616, "responses sent 1616-1631 bytes", "1616-1631", + out); + SET_SIZESTATDESC(1632, "responses sent 1632-1647 bytes", "1632-1647", + out); + SET_SIZESTATDESC(1648, "responses sent 1648-1663 bytes", "1648-1663", + out); + SET_SIZESTATDESC(1664, "responses sent 1664-1679 bytes", "1664-1679", + out); + SET_SIZESTATDESC(1680, "responses sent 1680-1695 bytes", "1680-1695", + out); + SET_SIZESTATDESC(1696, "responses sent 1696-1711 bytes", "1696-1711", + out); + SET_SIZESTATDESC(1712, "responses sent 1712-1727 bytes", "1712-1727", + out); + SET_SIZESTATDESC(1728, "responses sent 1728-1743 bytes", "1728-1743", + out); + SET_SIZESTATDESC(1744, "responses sent 1744-1759 bytes", "1744-1759", + out); + SET_SIZESTATDESC(1760, "responses sent 1760-1775 bytes", "1760-1775", + out); + SET_SIZESTATDESC(1776, "responses sent 1776-1791 bytes", "1776-1791", + out); + SET_SIZESTATDESC(1792, "responses sent 1792-1807 bytes", "1792-1807", + out); + SET_SIZESTATDESC(1808, "responses sent 1808-1823 bytes", "1808-1823", + out); + SET_SIZESTATDESC(1824, "responses sent 1824-1839 bytes", "1824-1839", + out); + SET_SIZESTATDESC(1840, "responses sent 1840-1855 bytes", "1840-1855", + out); + SET_SIZESTATDESC(1856, "responses sent 1856-1871 bytes", "1856-1871", + out); + SET_SIZESTATDESC(1872, "responses sent 1872-1887 bytes", "1872-1887", + out); + SET_SIZESTATDESC(1888, "responses sent 1888-1903 bytes", "1888-1903", + out); + SET_SIZESTATDESC(1904, "responses sent 1904-1919 bytes", "1904-1919", + out); + SET_SIZESTATDESC(1920, "responses sent 1920-1935 bytes", "1920-1935", + out); + SET_SIZESTATDESC(1936, "responses sent 1936-1951 bytes", "1936-1951", + out); + SET_SIZESTATDESC(1952, "responses sent 1952-1967 bytes", "1952-1967", + out); + SET_SIZESTATDESC(1968, "responses sent 1968-1983 bytes", "1968-1983", + out); + SET_SIZESTATDESC(1984, "responses sent 1984-1999 bytes", "1984-1999", + out); + SET_SIZESTATDESC(2000, "responses sent 2000-2015 bytes", "2000-2015", + out); + SET_SIZESTATDESC(2016, "responses sent 2016-2031 bytes", "2016-2031", + out); + SET_SIZESTATDESC(2032, "responses sent 2032-2047 bytes", "2032-2047", + out); + SET_SIZESTATDESC(2048, "responses sent 2048-2063 bytes", "2048-2063", + out); + SET_SIZESTATDESC(2064, "responses sent 2064-2079 bytes", "2064-2079", + out); + SET_SIZESTATDESC(2080, "responses sent 2080-2095 bytes", "2080-2095", + out); + SET_SIZESTATDESC(2096, "responses sent 2096-2111 bytes", "2096-2111", + out); + SET_SIZESTATDESC(2112, "responses sent 2112-2127 bytes", "2112-2127", + out); + SET_SIZESTATDESC(2128, "responses sent 2128-2143 bytes", "2128-2143", + out); + SET_SIZESTATDESC(2144, "responses sent 2144-2159 bytes", "2144-2159", + out); + SET_SIZESTATDESC(2160, "responses sent 2160-2175 bytes", "2160-2175", + out); + SET_SIZESTATDESC(2176, "responses sent 2176-2191 bytes", "2176-2191", + out); + SET_SIZESTATDESC(2192, "responses sent 2192-2207 bytes", "2192-2207", + out); + SET_SIZESTATDESC(2208, "responses sent 2208-2223 bytes", "2208-2223", + out); + SET_SIZESTATDESC(2224, "responses sent 2224-2239 bytes", "2224-2239", + out); + SET_SIZESTATDESC(2240, "responses sent 2240-2255 bytes", "2240-2255", + out); + SET_SIZESTATDESC(2256, "responses sent 2256-2271 bytes", "2256-2271", + out); + SET_SIZESTATDESC(2272, "responses sent 2272-2287 bytes", "2272-2287", + out); + SET_SIZESTATDESC(2288, "responses sent 2288-2303 bytes", "2288-2303", + out); + SET_SIZESTATDESC(2304, "responses sent 2304-2319 bytes", "2304-2319", + out); + SET_SIZESTATDESC(2320, "responses sent 2320-2335 bytes", "2320-2335", + out); + SET_SIZESTATDESC(2336, "responses sent 2336-2351 bytes", "2336-2351", + out); + SET_SIZESTATDESC(2352, "responses sent 2352-2367 bytes", "2352-2367", + out); + SET_SIZESTATDESC(2368, "responses sent 2368-2383 bytes", "2368-2383", + out); + SET_SIZESTATDESC(2384, "responses sent 2384-2399 bytes", "2384-2399", + out); + SET_SIZESTATDESC(2400, "responses sent 2400-2415 bytes", "2400-2415", + out); + SET_SIZESTATDESC(2416, "responses sent 2416-2431 bytes", "2416-2431", + out); + SET_SIZESTATDESC(2432, "responses sent 2432-2447 bytes", "2432-2447", + out); + SET_SIZESTATDESC(2448, "responses sent 2448-2463 bytes", "2448-2463", + out); + SET_SIZESTATDESC(2464, "responses sent 2464-2479 bytes", "2464-2479", + out); + SET_SIZESTATDESC(2480, "responses sent 2480-2495 bytes", "2480-2495", + out); + SET_SIZESTATDESC(2496, "responses sent 2496-2511 bytes", "2496-2511", + out); + SET_SIZESTATDESC(2512, "responses sent 2512-2527 bytes", "2512-2527", + out); + SET_SIZESTATDESC(2528, "responses sent 2528-2543 bytes", "2528-2543", + out); + SET_SIZESTATDESC(2544, "responses sent 2544-2559 bytes", "2544-2559", + out); + SET_SIZESTATDESC(2560, "responses sent 2560-2575 bytes", "2560-2575", + out); + SET_SIZESTATDESC(2576, "responses sent 2576-2591 bytes", "2576-2591", + out); + SET_SIZESTATDESC(2592, "responses sent 2592-2607 bytes", "2592-2607", + out); + SET_SIZESTATDESC(2608, "responses sent 2608-2623 bytes", "2608-2623", + out); + SET_SIZESTATDESC(2624, "responses sent 2624-2639 bytes", "2624-2639", + out); + SET_SIZESTATDESC(2640, "responses sent 2640-2655 bytes", "2640-2655", + out); + SET_SIZESTATDESC(2656, "responses sent 2656-2671 bytes", "2656-2671", + out); + SET_SIZESTATDESC(2672, "responses sent 2672-2687 bytes", "2672-2687", + out); + SET_SIZESTATDESC(2688, "responses sent 2688-2703 bytes", "2688-2703", + out); + SET_SIZESTATDESC(2704, "responses sent 2704-2719 bytes", "2704-2719", + out); + SET_SIZESTATDESC(2720, "responses sent 2720-2735 bytes", "2720-2735", + out); + SET_SIZESTATDESC(2736, "responses sent 2736-2751 bytes", "2736-2751", + out); + SET_SIZESTATDESC(2752, "responses sent 2752-2767 bytes", "2752-2767", + out); + SET_SIZESTATDESC(2768, "responses sent 2768-2783 bytes", "2768-2783", + out); + SET_SIZESTATDESC(2784, "responses sent 2784-2799 bytes", "2784-2799", + out); + SET_SIZESTATDESC(2800, "responses sent 2800-2815 bytes", "2800-2815", + out); + SET_SIZESTATDESC(2816, "responses sent 2816-2831 bytes", "2816-2831", + out); + SET_SIZESTATDESC(2832, "responses sent 2832-2847 bytes", "2832-2847", + out); + SET_SIZESTATDESC(2848, "responses sent 2848-2863 bytes", "2848-2863", + out); + SET_SIZESTATDESC(2864, "responses sent 2864-2879 bytes", "2864-2879", + out); + SET_SIZESTATDESC(2880, "responses sent 2880-2895 bytes", "2880-2895", + out); + SET_SIZESTATDESC(2896, "responses sent 2896-2911 bytes", "2896-2911", + out); + SET_SIZESTATDESC(2912, "responses sent 2912-2927 bytes", "2912-2927", + out); + SET_SIZESTATDESC(2928, "responses sent 2928-2943 bytes", "2928-2943", + out); + SET_SIZESTATDESC(2944, "responses sent 2944-2959 bytes", "2944-2959", + out); + SET_SIZESTATDESC(2960, "responses sent 2960-2975 bytes", "2960-2975", + out); + SET_SIZESTATDESC(2976, "responses sent 2976-2991 bytes", "2976-2991", + out); + SET_SIZESTATDESC(2992, "responses sent 2992-3007 bytes", "2992-3007", + out); + SET_SIZESTATDESC(3008, "responses sent 3008-3023 bytes", "3008-3023", + out); + SET_SIZESTATDESC(3024, "responses sent 3024-3039 bytes", "3024-3039", + out); + SET_SIZESTATDESC(3040, "responses sent 3040-3055 bytes", "3040-3055", + out); + SET_SIZESTATDESC(3056, "responses sent 3056-3071 bytes", "3056-3071", + out); + SET_SIZESTATDESC(3072, "responses sent 3072-3087 bytes", "3072-3087", + out); + SET_SIZESTATDESC(3088, "responses sent 3088-3103 bytes", "3088-3103", + out); + SET_SIZESTATDESC(3104, "responses sent 3104-3119 bytes", "3104-3119", + out); + SET_SIZESTATDESC(3120, "responses sent 3120-3135 bytes", "3120-3135", + out); + SET_SIZESTATDESC(3136, "responses sent 3136-3151 bytes", "3136-3151", + out); + SET_SIZESTATDESC(3152, "responses sent 3152-3167 bytes", "3152-3167", + out); + SET_SIZESTATDESC(3168, "responses sent 3168-3183 bytes", "3168-3183", + out); + SET_SIZESTATDESC(3184, "responses sent 3184-3199 bytes", "3184-3199", + out); + SET_SIZESTATDESC(3200, "responses sent 3200-3215 bytes", "3200-3215", + out); + SET_SIZESTATDESC(3216, "responses sent 3216-3231 bytes", "3216-3231", + out); + SET_SIZESTATDESC(3232, "responses sent 3232-3247 bytes", "3232-3247", + out); + SET_SIZESTATDESC(3248, "responses sent 3248-3263 bytes", "3248-3263", + out); + SET_SIZESTATDESC(3264, "responses sent 3264-3279 bytes", "3264-3279", + out); + SET_SIZESTATDESC(3280, "responses sent 3280-3295 bytes", "3280-3295", + out); + SET_SIZESTATDESC(3296, "responses sent 3296-3311 bytes", "3296-3311", + out); + SET_SIZESTATDESC(3312, "responses sent 3312-3327 bytes", "3312-3327", + out); + SET_SIZESTATDESC(3328, "responses sent 3328-3343 bytes", "3328-3343", + out); + SET_SIZESTATDESC(3344, "responses sent 3344-3359 bytes", "3344-3359", + out); + SET_SIZESTATDESC(3360, "responses sent 3360-3375 bytes", "3360-3375", + out); + SET_SIZESTATDESC(3376, "responses sent 3376-3391 bytes", "3376-3391", + out); + SET_SIZESTATDESC(3392, "responses sent 3392-3407 bytes", "3392-3407", + out); + SET_SIZESTATDESC(3408, "responses sent 3408-3423 bytes", "3408-3423", + out); + SET_SIZESTATDESC(3424, "responses sent 3424-3439 bytes", "3424-3439", + out); + SET_SIZESTATDESC(3440, "responses sent 3440-3455 bytes", "3440-3455", + out); + SET_SIZESTATDESC(3456, "responses sent 3456-3471 bytes", "3456-3471", + out); + SET_SIZESTATDESC(3472, "responses sent 3472-3487 bytes", "3472-3487", + out); + SET_SIZESTATDESC(3488, "responses sent 3488-3503 bytes", "3488-3503", + out); + SET_SIZESTATDESC(3504, "responses sent 3504-3519 bytes", "3504-3519", + out); + SET_SIZESTATDESC(3520, "responses sent 3520-3535 bytes", "3520-3535", + out); + SET_SIZESTATDESC(3536, "responses sent 3536-3551 bytes", "3536-3551", + out); + SET_SIZESTATDESC(3552, "responses sent 3552-3567 bytes", "3552-3567", + out); + SET_SIZESTATDESC(3568, "responses sent 3568-3583 bytes", "3568-3583", + out); + SET_SIZESTATDESC(3584, "responses sent 3584-3599 bytes", "3584-3599", + out); + SET_SIZESTATDESC(3600, "responses sent 3600-3615 bytes", "3600-3615", + out); + SET_SIZESTATDESC(3616, "responses sent 3616-3631 bytes", "3616-3631", + out); + SET_SIZESTATDESC(3632, "responses sent 3632-3647 bytes", "3632-3647", + out); + SET_SIZESTATDESC(3648, "responses sent 3648-3663 bytes", "3648-3663", + out); + SET_SIZESTATDESC(3664, "responses sent 3664-3679 bytes", "3664-3679", + out); + SET_SIZESTATDESC(3680, "responses sent 3680-3695 bytes", "3680-3695", + out); + SET_SIZESTATDESC(3696, "responses sent 3696-3711 bytes", "3696-3711", + out); + SET_SIZESTATDESC(3712, "responses sent 3712-3727 bytes", "3712-3727", + out); + SET_SIZESTATDESC(3728, "responses sent 3728-3743 bytes", "3728-3743", + out); + SET_SIZESTATDESC(3744, "responses sent 3744-3759 bytes", "3744-3759", + out); + SET_SIZESTATDESC(3760, "responses sent 3760-3775 bytes", "3760-3775", + out); + SET_SIZESTATDESC(3776, "responses sent 3776-3791 bytes", "3776-3791", + out); + SET_SIZESTATDESC(3792, "responses sent 3792-3807 bytes", "3792-3807", + out); + SET_SIZESTATDESC(3808, "responses sent 3808-3823 bytes", "3808-3823", + out); + SET_SIZESTATDESC(3824, "responses sent 3824-3839 bytes", "3824-3839", + out); + SET_SIZESTATDESC(3840, "responses sent 3840-3855 bytes", "3840-3855", + out); + SET_SIZESTATDESC(3856, "responses sent 3856-3871 bytes", "3856-3871", + out); + SET_SIZESTATDESC(3872, "responses sent 3872-3887 bytes", "3872-3887", + out); + SET_SIZESTATDESC(3888, "responses sent 3888-3903 bytes", "3888-3903", + out); + SET_SIZESTATDESC(3904, "responses sent 3904-3919 bytes", "3904-3919", + out); + SET_SIZESTATDESC(3920, "responses sent 3920-3935 bytes", "3920-3935", + out); + SET_SIZESTATDESC(3936, "responses sent 3936-3951 bytes", "3936-3951", + out); + SET_SIZESTATDESC(3952, "responses sent 3952-3967 bytes", "3952-3967", + out); + SET_SIZESTATDESC(3968, "responses sent 3968-3983 bytes", "3968-3983", + out); + SET_SIZESTATDESC(3984, "responses sent 3984-3999 bytes", "3984-3999", + out); + SET_SIZESTATDESC(4000, "responses sent 4000-4015 bytes", "4000-4015", + out); + SET_SIZESTATDESC(4016, "responses sent 4016-4031 bytes", "4016-4031", + out); + SET_SIZESTATDESC(4032, "responses sent 4032-4047 bytes", "4032-4047", + out); + SET_SIZESTATDESC(4048, "responses sent 4048-4063 bytes", "4048-4063", + out); + SET_SIZESTATDESC(4064, "responses sent 4064-4079 bytes", "4064-4079", + out); + SET_SIZESTATDESC(4080, "responses sent 4080-4095 bytes", "4080-4095", + out); + SET_SIZESTATDESC(4096, "responses sent 4096+ bytes", "4096+", out); + INSIST(i == dns_sizecounter_out_max); + + /* Sanity check */ + for (i = 0; i < ns_statscounter_max; i++) { + INSIST(nsstats_desc[i] != NULL); + } + for (i = 0; i < dns_resstatscounter_max; i++) { + INSIST(resstats_desc[i] != NULL); + } + for (i = 0; i < dns_adbstats_max; i++) { + INSIST(adbstats_desc[i] != NULL); + } + for (i = 0; i < dns_zonestatscounter_max; i++) { + INSIST(zonestats_desc[i] != NULL); + } + for (i = 0; i < isc_sockstatscounter_max; i++) { + INSIST(sockstats_desc[i] != NULL); + } + for (i = 0; i < dns_dnssecstats_max; i++) { + INSIST(dnssecstats_desc[i] != NULL); + } + for (i = 0; i < dns_sizecounter_in_max; i++) { + INSIST(udpinsizestats_desc[i] != NULL); + INSIST(tcpinsizestats_desc[i] != NULL); + } + for (i = 0; i < dns_sizecounter_out_max; i++) { + INSIST(udpoutsizestats_desc[i] != NULL); + INSIST(tcpoutsizestats_desc[i] != NULL); + } +#if defined(EXTENDED_STATS) + for (i = 0; i < ns_statscounter_max; i++) { + INSIST(nsstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_resstatscounter_max; i++) { + INSIST(resstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_adbstats_max; i++) { + INSIST(adbstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_zonestatscounter_max; i++) { + INSIST(zonestats_xmldesc[i] != NULL); + } + for (i = 0; i < isc_sockstatscounter_max; i++) { + INSIST(sockstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_dnssecstats_max; i++) { + INSIST(dnssecstats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_sizecounter_in_max; i++) { + INSIST(udpinsizestats_xmldesc[i] != NULL); + INSIST(tcpinsizestats_xmldesc[i] != NULL); + } + for (i = 0; i < dns_sizecounter_out_max; i++) { + INSIST(udpoutsizestats_xmldesc[i] != NULL); + INSIST(tcpoutsizestats_xmldesc[i] != NULL); + } +#endif /* if defined(EXTENDED_STATS) */ +} + +/*% + * Dump callback functions. + */ +static void +generalstat_dump(isc_statscounter_t counter, uint64_t val, void *arg) { + stats_dumparg_t *dumparg = arg; + + REQUIRE(counter < dumparg->ncounters); + dumparg->countervalues[counter] = val; +} + +static isc_result_t +dump_counters(isc_stats_t *stats, isc_statsformat_t type, void *arg, + const char *category, const char **desc, int ncounters, + int *indices, uint64_t *values, int options) { + int i, idx; + uint64_t value; + stats_dumparg_t dumparg; + FILE *fp; +#ifdef HAVE_LIBXML2 + void *writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *job, *cat, *counter; +#endif /* ifdef HAVE_JSON_C */ + +#if !defined(EXTENDED_STATS) + UNUSED(category); +#endif /* if !defined(EXTENDED_STATS) */ + + dumparg.type = type; + dumparg.ncounters = ncounters; + dumparg.counterindices = indices; + dumparg.countervalues = values; + + memset(values, 0, sizeof(values[0]) * ncounters); + isc_stats_dump(stats, generalstat_dump, &dumparg, options); + +#ifdef HAVE_JSON_C + cat = job = (json_object *)arg; + if (ncounters > 0 && type == isc_statsformat_json) { + if (category != NULL) { + cat = json_object_new_object(); + if (cat == NULL) { + return (ISC_R_NOMEMORY); + } + json_object_object_add(job, category, cat); + } + } +#endif /* ifdef HAVE_JSON_C */ + + for (i = 0; i < ncounters; i++) { + idx = indices[i]; + value = values[idx]; + + if (value == 0 && (options & ISC_STATSDUMP_VERBOSE) == 0) { + continue; + } + + switch (dumparg.type) { + case isc_statsformat_file: + fp = arg; + fprintf(fp, "%20" PRIu64 " %s\n", value, desc[idx]); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = arg; + + if (category != NULL) { + /* <NameOfCategory> */ + TRY0(xmlTextWriterStartElement( + writer, ISC_XMLCHAR category)); + + /* <name> inside category */ + TRY0(xmlTextWriterStartElement( + writer, ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteString( + writer, ISC_XMLCHAR desc[idx])); + TRY0(xmlTextWriterEndElement(writer)); + /* </name> */ + + /* <counter> */ + TRY0(xmlTextWriterStartElement( + writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIu64, value)); + + TRY0(xmlTextWriterEndElement(writer)); + /* </counter> */ + TRY0(xmlTextWriterEndElement(writer)); + /* </NameOfCategory> */ + } else { + TRY0(xmlTextWriterStartElement( + writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute( + writer, ISC_XMLCHAR "name", + ISC_XMLCHAR desc[idx])); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIu64, value)); + TRY0(xmlTextWriterEndElement(writer)); + /* counter */ + } + +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + counter = json_object_new_int64(value); + if (counter == NULL) { + return (ISC_R_NOMEMORY); + } + json_object_object_add(cat, desc[idx], counter); +#endif /* ifdef HAVE_JSON_C */ + break; + } + } + return (ISC_R_SUCCESS); +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at dump_counters()"); + return (ISC_R_FAILURE); +#endif /* ifdef HAVE_LIBXML2 */ +} + +static void +rdtypestat_dump(dns_rdatastatstype_t type, uint64_t val, void *arg) { + char typebuf[64]; + const char *typestr; + stats_dumparg_t *dumparg = arg; + FILE *fp; +#ifdef HAVE_LIBXML2 + void *writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *zoneobj, *obj; +#endif /* ifdef HAVE_JSON_C */ + + if ((DNS_RDATASTATSTYPE_ATTR(type) & + DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) == 0) + { + dns_rdatatype_format(DNS_RDATASTATSTYPE_BASE(type), typebuf, + sizeof(typebuf)); + typestr = typebuf; + } else { + typestr = "Others"; + } + + switch (dumparg->type) { + case isc_statsformat_file: + fp = dumparg->arg; + fprintf(fp, "%20" PRIu64 " %s\n", val, typestr); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = dumparg->arg; + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR typestr)); + + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, val)); + + TRY0(xmlTextWriterEndElement(writer)); /* type */ +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + zoneobj = (json_object *)dumparg->arg; + obj = json_object_new_int64(val); + if (obj == NULL) { + return; + } + json_object_object_add(zoneobj, typestr, obj); +#endif /* ifdef HAVE_JSON_C */ + break; + } + return; +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at rdtypestat_dump()"); + dumparg->result = ISC_R_FAILURE; + return; +#endif /* ifdef HAVE_LIBXML2 */ +} + +static bool +rdatastatstype_attr(dns_rdatastatstype_t type, unsigned int attr) { + return ((DNS_RDATASTATSTYPE_ATTR(type) & attr) != 0); +} + +static void +rdatasetstats_dump(dns_rdatastatstype_t type, uint64_t val, void *arg) { + stats_dumparg_t *dumparg = arg; + FILE *fp; + char typebuf[64]; + const char *typestr; + bool nxrrset = false; + bool stale = false; + bool ancient = false; +#ifdef HAVE_LIBXML2 + void *writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *zoneobj, *obj; + char buf[1024]; +#endif /* ifdef HAVE_JSON_C */ + + if ((DNS_RDATASTATSTYPE_ATTR(type) & + DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) != 0) + { + typestr = "NXDOMAIN"; + } else if ((DNS_RDATASTATSTYPE_ATTR(type) & + DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) != 0) + { + typestr = "Others"; + } else { + dns_rdatatype_format(DNS_RDATASTATSTYPE_BASE(type), typebuf, + sizeof(typebuf)); + typestr = typebuf; + } + + nxrrset = rdatastatstype_attr(type, DNS_RDATASTATSTYPE_ATTR_NXRRSET); + stale = rdatastatstype_attr(type, DNS_RDATASTATSTYPE_ATTR_STALE); + ancient = rdatastatstype_attr(type, DNS_RDATASTATSTYPE_ATTR_ANCIENT); + + switch (dumparg->type) { + case isc_statsformat_file: + fp = dumparg->arg; + fprintf(fp, "%20" PRIu64 " %s%s%s%s\n", val, ancient ? "~" : "", + stale ? "#" : "", nxrrset ? "!" : "", typestr); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = dumparg->arg; + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "rrset")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%s%s%s%s", ancient ? "~" : "", + stale ? "#" : "", nxrrset ? "!" : "", typestr)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, val)); + TRY0(xmlTextWriterEndElement(writer)); /* counter */ + + TRY0(xmlTextWriterEndElement(writer)); /* rrset */ +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + zoneobj = (json_object *)dumparg->arg; + snprintf(buf, sizeof(buf), "%s%s%s%s", ancient ? "~" : "", + stale ? "#" : "", nxrrset ? "!" : "", typestr); + obj = json_object_new_int64(val); + if (obj == NULL) { + return; + } + json_object_object_add(zoneobj, buf, obj); +#endif /* ifdef HAVE_JSON_C */ + break; + } + return; +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at rdatasetstats_dump()"); + dumparg->result = ISC_R_FAILURE; +#endif /* ifdef HAVE_LIBXML2 */ +} + +static void +opcodestat_dump(dns_opcode_t code, uint64_t val, void *arg) { + FILE *fp; + isc_buffer_t b; + char codebuf[64]; + stats_dumparg_t *dumparg = arg; +#ifdef HAVE_LIBXML2 + void *writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *zoneobj, *obj; +#endif /* ifdef HAVE_JSON_C */ + + isc_buffer_init(&b, codebuf, sizeof(codebuf) - 1); + dns_opcode_totext(code, &b); + codebuf[isc_buffer_usedlength(&b)] = '\0'; + + switch (dumparg->type) { + case isc_statsformat_file: + fp = dumparg->arg; + fprintf(fp, "%20" PRIu64 " %s\n", val, codebuf); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = dumparg->arg; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR codebuf)); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, val)); + TRY0(xmlTextWriterEndElement(writer)); /* counter */ +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + zoneobj = (json_object *)dumparg->arg; + obj = json_object_new_int64(val); + if (obj == NULL) { + return; + } + json_object_object_add(zoneobj, codebuf, obj); +#endif /* ifdef HAVE_JSON_C */ + break; + } + return; + +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at opcodestat_dump()"); + dumparg->result = ISC_R_FAILURE; + return; +#endif /* ifdef HAVE_LIBXML2 */ +} + +static void +rcodestat_dump(dns_rcode_t code, uint64_t val, void *arg) { + FILE *fp; + isc_buffer_t b; + char codebuf[64]; + stats_dumparg_t *dumparg = arg; +#ifdef HAVE_LIBXML2 + void *writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *zoneobj, *obj; +#endif /* ifdef HAVE_JSON_C */ + + isc_buffer_init(&b, codebuf, sizeof(codebuf) - 1); + dns_rcode_totext(code, &b); + codebuf[isc_buffer_usedlength(&b)] = '\0'; + + switch (dumparg->type) { + case isc_statsformat_file: + fp = dumparg->arg; + fprintf(fp, "%20" PRIu64 " %s\n", val, codebuf); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = dumparg->arg; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR codebuf)); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, val)); + TRY0(xmlTextWriterEndElement(writer)); /* counter */ +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + zoneobj = (json_object *)dumparg->arg; + obj = json_object_new_int64(val); + if (obj == NULL) { + return; + } + json_object_object_add(zoneobj, codebuf, obj); +#endif /* ifdef HAVE_JSON_C */ + break; + } + return; + +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at rcodestat_dump()"); + dumparg->result = ISC_R_FAILURE; + return; +#endif /* ifdef HAVE_LIBXML2 */ +} + +#if defined(EXTENDED_STATS) +static void +dnssecsignstat_dump(dns_keytag_t tag, uint64_t val, void *arg) { + FILE *fp; + char tagbuf[64]; + stats_dumparg_t *dumparg = arg; +#ifdef HAVE_LIBXML2 + xmlTextWriterPtr writer; + int xmlrc; +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + json_object *zoneobj, *obj; +#endif /* ifdef HAVE_JSON_C */ + + snprintf(tagbuf, sizeof(tagbuf), "%u", tag); + + switch (dumparg->type) { + case isc_statsformat_file: + fp = dumparg->arg; + fprintf(fp, "%20" PRIu64 " %s\n", val, tagbuf); + break; + case isc_statsformat_xml: +#ifdef HAVE_LIBXML2 + writer = dumparg->arg; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR tagbuf)); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, val)); + TRY0(xmlTextWriterEndElement(writer)); /* counter */ +#endif /* ifdef HAVE_LIBXML2 */ + break; + case isc_statsformat_json: +#ifdef HAVE_JSON_C + zoneobj = (json_object *)dumparg->arg; + obj = json_object_new_int64(val); + if (obj == NULL) { + return; + } + json_object_object_add(zoneobj, tagbuf, obj); +#endif /* ifdef HAVE_JSON_C */ + break; + } + return; +#ifdef HAVE_LIBXML2 +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at dnssecsignstat_dump()"); + dumparg->result = ISC_R_FAILURE; + return; +#endif /* ifdef HAVE_LIBXML2 */ +} +#endif /* defined(EXTENDED_STATS) */ + +#ifdef HAVE_LIBXML2 +/* + * Which statistics to include when rendering to XML + */ +#define STATS_XML_STATUS 0x00 /* display only common statistics */ +#define STATS_XML_SERVER 0x01 +#define STATS_XML_ZONES 0x02 +#define STATS_XML_TASKS 0x04 +#define STATS_XML_NET 0x08 +#define STATS_XML_MEM 0x10 +#define STATS_XML_TRAFFIC 0x20 +#define STATS_XML_ALL 0xff + +static isc_result_t +zone_xmlrender(dns_zone_t *zone, void *arg) { + isc_result_t result; + char buf[1024 + 32]; /* sufficiently large for zone name and class */ + dns_rdataclass_t rdclass; + uint32_t serial; + xmlTextWriterPtr writer = arg; + dns_zonestat_level_t statlevel; + int xmlrc; + stats_dumparg_t dumparg; + const char *ztype; + isc_time_t timestamp; + + statlevel = dns_zone_getstatlevel(zone); + if (statlevel == dns_zonestat_none) { + return (ISC_R_SUCCESS); + } + + dumparg.type = isc_statsformat_xml; + dumparg.arg = writer; + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "zone")); + + dns_zone_nameonly(zone, buf, sizeof(buf)); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR buf)); + + rdclass = dns_zone_getclass(zone); + dns_rdataclass_format(rdclass, buf, sizeof(buf)); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "rdataclass", + ISC_XMLCHAR buf)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "type")); + ztype = user_zonetype(zone); + if (ztype != NULL) { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR ztype)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "unknown")); + } + TRY0(xmlTextWriterEndElement(writer)); /* type */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "serial")); + if (dns_zone_getserial(zone, &serial) == ISC_R_SUCCESS) { + TRY0(xmlTextWriterWriteFormatString(writer, "%u", serial)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); /* serial */ + + /* + * Export zone timers to the statistics channel in XML format. For + * primary zones, only include the loaded time. For secondary zones, + * also include the expire and refresh times. + */ + CHECK(dns_zone_getloadtime(zone, ×tamp)); + + isc_time_formatISO8601(×tamp, buf, 64); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "loaded")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR buf)); + TRY0(xmlTextWriterEndElement(writer)); + + if (dns_zone_gettype(zone) == dns_zone_secondary) { + CHECK(dns_zone_getexpiretime(zone, ×tamp)); + isc_time_formatISO8601(×tamp, buf, 64); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "expires")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR buf)); + TRY0(xmlTextWriterEndElement(writer)); + + CHECK(dns_zone_getrefreshtime(zone, ×tamp)); + isc_time_formatISO8601(×tamp, buf, 64); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "refresh")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR buf)); + TRY0(xmlTextWriterEndElement(writer)); + } + + if (statlevel == dns_zonestat_full) { + isc_stats_t *zonestats; + isc_stats_t *gluecachestats; + dns_stats_t *rcvquerystats; + dns_stats_t *dnssecsignstats; + uint64_t nsstat_values[ns_statscounter_max]; + uint64_t gluecachestats_values[dns_gluecachestatscounter_max]; + + zonestats = dns_zone_getrequeststats(zone); + if (zonestats != NULL) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, + ISC_XMLCHAR "type", + ISC_XMLCHAR "rcode")); + + CHECK(dump_counters(zonestats, isc_statsformat_xml, + writer, NULL, nsstats_xmldesc, + ns_statscounter_max, nsstats_index, + nsstat_values, + ISC_STATSDUMP_VERBOSE)); + /* counters type="rcode"*/ + TRY0(xmlTextWriterEndElement(writer)); + } + + gluecachestats = dns_zone_getgluecachestats(zone); + if (gluecachestats != NULL) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute( + writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "gluecache")); + + CHECK(dump_counters( + gluecachestats, isc_statsformat_xml, writer, + NULL, gluecachestats_xmldesc, + dns_gluecachestatscounter_max, + gluecachestats_index, gluecachestats_values, + ISC_STATSDUMP_VERBOSE)); + /* counters type="rcode"*/ + TRY0(xmlTextWriterEndElement(writer)); + } + + rcvquerystats = dns_zone_getrcvquerystats(zone); + if (rcvquerystats != NULL) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, + ISC_XMLCHAR "type", + ISC_XMLCHAR "qtype")); + + dumparg.result = ISC_R_SUCCESS; + dns_rdatatypestats_dump(rcvquerystats, rdtypestat_dump, + &dumparg, 0); + CHECK(dumparg.result); + + /* counters type="qtype"*/ + TRY0(xmlTextWriterEndElement(writer)); + } + + dnssecsignstats = dns_zone_getdnssecsignstats(zone); + if (dnssecsignstats != NULL) { + /* counters type="dnssec-sign"*/ + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute( + writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "dnssec-sign")); + + dumparg.result = ISC_R_SUCCESS; + dns_dnssecsignstats_dump( + dnssecsignstats, dns_dnssecsignstats_sign, + dnssecsignstat_dump, &dumparg, 0); + CHECK(dumparg.result); + + /* counters type="dnssec-sign"*/ + TRY0(xmlTextWriterEndElement(writer)); + + /* counters type="dnssec-refresh"*/ + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute( + writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "dnssec-refresh")); + + dumparg.result = ISC_R_SUCCESS; + dns_dnssecsignstats_dump( + dnssecsignstats, dns_dnssecsignstats_refresh, + dnssecsignstat_dump, &dumparg, 0); + CHECK(dumparg.result); + + /* counters type="dnssec-refresh"*/ + TRY0(xmlTextWriterEndElement(writer)); + } + } + + TRY0(xmlTextWriterEndElement(writer)); /* zone */ + + return (ISC_R_SUCCESS); +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Failed at zone_xmlrender()"); + return (ISC_R_FAILURE); +} + +static isc_result_t +generatexml(named_server_t *server, uint32_t flags, int *buflen, + xmlChar **buf) { + char boottime[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + char configtime[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + char nowstr[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + isc_time_t now; + xmlTextWriterPtr writer = NULL; + xmlDocPtr doc = NULL; + int xmlrc; + dns_view_t *view; + stats_dumparg_t dumparg; + dns_stats_t *cacherrstats; + uint64_t nsstat_values[ns_statscounter_max]; + uint64_t resstat_values[dns_resstatscounter_max]; + uint64_t adbstat_values[dns_adbstats_max]; + uint64_t zonestat_values[dns_zonestatscounter_max]; + uint64_t sockstat_values[isc_sockstatscounter_max]; + uint64_t udpinsizestat_values[dns_sizecounter_in_max]; + uint64_t udpoutsizestat_values[dns_sizecounter_out_max]; + uint64_t tcpinsizestat_values[dns_sizecounter_in_max]; + uint64_t tcpoutsizestat_values[dns_sizecounter_out_max]; +#ifdef HAVE_DNSTAP + uint64_t dnstapstat_values[dns_dnstapcounter_max]; +#endif /* ifdef HAVE_DNSTAP */ + isc_result_t result; + + isc_time_now(&now); + isc_time_formatISO8601ms(&named_g_boottime, boottime, sizeof boottime); + isc_time_formatISO8601ms(&named_g_configtime, configtime, + sizeof configtime); + isc_time_formatISO8601ms(&now, nowstr, sizeof nowstr); + + writer = xmlNewTextWriterDoc(&doc, 0); + if (writer == NULL) { + goto cleanup; + } + TRY0(xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL)); + TRY0(xmlTextWriterWritePI(writer, ISC_XMLCHAR "xml-stylesheet", + ISC_XMLCHAR "type=\"text/xsl\" " + "href=\"/bind9.xsl\"")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "statistics")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "version", + ISC_XMLCHAR STATS_XML_VERSION)); + + /* Set common fields for statistics dump */ + dumparg.type = isc_statsformat_xml; + dumparg.arg = writer; + + /* Render server information */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "server")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "boot-time")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR boottime)); + TRY0(xmlTextWriterEndElement(writer)); /* boot-time */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "config-time")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR configtime)); + TRY0(xmlTextWriterEndElement(writer)); /* config-time */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "current-time")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR nowstr)); + TRY0(xmlTextWriterEndElement(writer)); /* current-time */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "version")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR PACKAGE_VERSION)); + TRY0(xmlTextWriterEndElement(writer)); /* version */ + + if ((flags & STATS_XML_SERVER) != 0) { + dumparg.result = ISC_R_SUCCESS; + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "opcode")); + + dns_opcodestats_dump(server->sctx->opcodestats, opcodestat_dump, + &dumparg, ISC_STATSDUMP_VERBOSE); + CHECK(dumparg.result); + + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "rcode")); + + dns_rcodestats_dump(server->sctx->rcodestats, rcodestat_dump, + &dumparg, ISC_STATSDUMP_VERBOSE); + CHECK(dumparg.result); + + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "qtype")); + + dumparg.result = ISC_R_SUCCESS; + dns_rdatatypestats_dump(server->sctx->rcvquerystats, + rdtypestat_dump, &dumparg, 0); + CHECK(dumparg.result); + + TRY0(xmlTextWriterEndElement(writer)); /* counters */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "nsstat")); + + CHECK(dump_counters(ns_stats_get(server->sctx->nsstats), + isc_statsformat_xml, writer, NULL, + nsstats_xmldesc, ns_statscounter_max, + nsstats_index, nsstat_values, + ISC_STATSDUMP_VERBOSE)); + + TRY0(xmlTextWriterEndElement(writer)); /* /nsstat */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "zonestat")); + + CHECK(dump_counters(server->zonestats, isc_statsformat_xml, + writer, NULL, zonestats_xmldesc, + dns_zonestatscounter_max, zonestats_index, + zonestat_values, ISC_STATSDUMP_VERBOSE)); + + TRY0(xmlTextWriterEndElement(writer)); /* /zonestat */ + + /* + * Most of the common resolver statistics entries are 0, so + * we don't use the verbose dump here. + */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "resstat")); + CHECK(dump_counters(server->resolverstats, isc_statsformat_xml, + writer, NULL, resstats_xmldesc, + dns_resstatscounter_max, resstats_index, + resstat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* resstat */ + +#ifdef HAVE_DNSTAP + if (server->dtenv != NULL) { + isc_stats_t *dnstapstats = NULL; + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, + ISC_XMLCHAR "type", + ISC_XMLCHAR "dnstap")); + dns_dt_getstats(named_g_server->dtenv, &dnstapstats); + result = dump_counters( + dnstapstats, isc_statsformat_xml, writer, NULL, + dnstapstats_xmldesc, dns_dnstapcounter_max, + dnstapstats_index, dnstapstat_values, 0); + isc_stats_detach(&dnstapstats); + CHECK(result); + + TRY0(xmlTextWriterEndElement(writer)); /* dnstap */ + } +#endif /* ifdef HAVE_DNSTAP */ + } + + if ((flags & STATS_XML_NET) != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "sockstat")); + + CHECK(dump_counters(server->sockstats, isc_statsformat_xml, + writer, NULL, sockstats_xmldesc, + isc_sockstatscounter_max, sockstats_index, + sockstat_values, ISC_STATSDUMP_VERBOSE)); + + TRY0(xmlTextWriterEndElement(writer)); /* /sockstat */ + } + TRY0(xmlTextWriterEndElement(writer)); /* /server */ + + if ((flags & STATS_XML_TRAFFIC) != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "traffic")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ipv4")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "udp")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "request-size")); + + CHECK(dump_counters( + server->sctx->udpinstats4, isc_statsformat_xml, writer, + NULL, udpinsizestats_xmldesc, dns_sizecounter_in_max, + udpinsizestats_index, udpinsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "response-size")); + + CHECK(dump_counters( + server->sctx->udpoutstats4, isc_statsformat_xml, writer, + NULL, udpoutsizestats_xmldesc, dns_sizecounter_out_max, + udpoutsizestats_index, udpoutsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + TRY0(xmlTextWriterEndElement(writer)); /* </udp> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "tcp")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "request-size")); + + CHECK(dump_counters( + server->sctx->tcpinstats4, isc_statsformat_xml, writer, + NULL, tcpinsizestats_xmldesc, dns_sizecounter_in_max, + tcpinsizestats_index, tcpinsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "response-size")); + + CHECK(dump_counters( + server->sctx->tcpoutstats4, isc_statsformat_xml, writer, + NULL, tcpoutsizestats_xmldesc, dns_sizecounter_out_max, + tcpoutsizestats_index, tcpoutsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + TRY0(xmlTextWriterEndElement(writer)); /* </tcp> */ + TRY0(xmlTextWriterEndElement(writer)); /* </ipv4> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ipv6")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "udp")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "request-size")); + + CHECK(dump_counters( + server->sctx->udpinstats6, isc_statsformat_xml, writer, + NULL, udpinsizestats_xmldesc, dns_sizecounter_in_max, + udpinsizestats_index, udpinsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "response-size")); + + CHECK(dump_counters( + server->sctx->udpoutstats6, isc_statsformat_xml, writer, + NULL, udpoutsizestats_xmldesc, dns_sizecounter_out_max, + udpoutsizestats_index, udpoutsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + TRY0(xmlTextWriterEndElement(writer)); /* </udp> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "tcp")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "request-size")); + + CHECK(dump_counters( + server->sctx->tcpinstats6, isc_statsformat_xml, writer, + NULL, tcpinsizestats_xmldesc, dns_sizecounter_in_max, + tcpinsizestats_index, tcpinsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "response-size")); + + CHECK(dump_counters( + server->sctx->tcpoutstats6, isc_statsformat_xml, writer, + NULL, tcpoutsizestats_xmldesc, dns_sizecounter_out_max, + tcpoutsizestats_index, tcpoutsizestat_values, 0)); + + TRY0(xmlTextWriterEndElement(writer)); /* </counters> */ + TRY0(xmlTextWriterEndElement(writer)); /* </tcp> */ + TRY0(xmlTextWriterEndElement(writer)); /* </ipv6> */ + TRY0(xmlTextWriterEndElement(writer)); /* </traffic> */ + } + + /* + * Render views. For each view we know of, call its + * rendering function. + */ + view = ISC_LIST_HEAD(server->viewlist); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "views")); + while (view != NULL && + ((flags & (STATS_XML_SERVER | STATS_XML_ZONES)) != 0)) + { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "view")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR view->name)); + + if ((flags & STATS_XML_ZONES) != 0) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "zones")); + CHECK(dns_zt_apply(view->zonetable, isc_rwlocktype_read, + true, NULL, zone_xmlrender, writer)); + TRY0(xmlTextWriterEndElement(writer)); /* /zones */ + } + + if ((flags & STATS_XML_SERVER) == 0) { + TRY0(xmlTextWriterEndElement(writer)); /* /view */ + view = ISC_LIST_NEXT(view, link); + continue; + } + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "resqtype")); + + if (view->resquerystats != NULL) { + dumparg.result = ISC_R_SUCCESS; + dns_rdatatypestats_dump(view->resquerystats, + rdtypestat_dump, &dumparg, 0); + CHECK(dumparg.result); + } + TRY0(xmlTextWriterEndElement(writer)); + + /* <resstats> */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "resstats")); + if (view->resstats != NULL) { + CHECK(dump_counters(view->resstats, isc_statsformat_xml, + writer, NULL, resstats_xmldesc, + dns_resstatscounter_max, + resstats_index, resstat_values, + ISC_STATSDUMP_VERBOSE)); + } + TRY0(xmlTextWriterEndElement(writer)); /* </resstats> */ + + cacherrstats = dns_db_getrrsetstats(view->cachedb); + if (cacherrstats != NULL) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "cache")); + TRY0(xmlTextWriterWriteAttribute( + writer, ISC_XMLCHAR "name", + ISC_XMLCHAR dns_cache_getname(view->cache))); + dumparg.result = ISC_R_SUCCESS; + dns_rdatasetstats_dump(cacherrstats, rdatasetstats_dump, + &dumparg, 0); + CHECK(dumparg.result); + TRY0(xmlTextWriterEndElement(writer)); /* cache */ + } + + /* <adbstats> */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "adbstat")); + if (view->adbstats != NULL) { + CHECK(dump_counters(view->adbstats, isc_statsformat_xml, + writer, NULL, adbstats_xmldesc, + dns_adbstats_max, adbstats_index, + adbstat_values, + ISC_STATSDUMP_VERBOSE)); + } + TRY0(xmlTextWriterEndElement(writer)); /* </adbstats> */ + + /* <cachestats> */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "cachestats")); + TRY0(dns_cache_renderxml(view->cache, writer)); + TRY0(xmlTextWriterEndElement(writer)); /* </cachestats> */ + + TRY0(xmlTextWriterEndElement(writer)); /* view */ + + view = ISC_LIST_NEXT(view, link); + } + TRY0(xmlTextWriterEndElement(writer)); /* /views */ + + if ((flags & STATS_XML_TASKS) != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "taskmgr")); + TRY0(isc_taskmgr_renderxml(named_g_taskmgr, writer)); + TRY0(xmlTextWriterEndElement(writer)); /* /taskmgr */ + } + + if ((flags & STATS_XML_MEM) != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "memory")); + TRY0(isc_mem_renderxml(writer)); + TRY0(xmlTextWriterEndElement(writer)); /* /memory */ + } + + TRY0(xmlTextWriterEndElement(writer)); /* /statistics */ + TRY0(xmlTextWriterEndDocument(writer)); + + xmlDocDumpFormatMemoryEnc(doc, buf, buflen, "UTF-8", 0); + if (*buf == NULL) { + goto cleanup; + } + + xmlFreeTextWriter(writer); + xmlFreeDoc(doc); + return (ISC_R_SUCCESS); + +cleanup: + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed generating XML response"); + if (writer != NULL) { + xmlFreeTextWriter(writer); + } + if (doc != NULL) { + xmlFreeDoc(doc); + } + return (ISC_R_FAILURE); +} + +static void +wrap_xmlfree(isc_buffer_t *buffer, void *arg) { + UNUSED(arg); + + xmlFree(isc_buffer_base(buffer)); +} + +static isc_result_t +render_xml(uint32_t flags, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + unsigned char *msg = NULL; + int msglen; + named_server_t *server = arg; + isc_result_t result; + + result = generatexml(server, flags, &msglen, &msg); + + if (result == ISC_R_SUCCESS) { + *retcode = 200; + *retmsg = "OK"; + *mimetype = "text/xml"; + isc_buffer_reinit(b, msg, msglen); + isc_buffer_add(b, msglen); + *freecb = wrap_xmlfree; + *freecb_args = NULL; + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at rendering XML()"); + } + + return (result); +} + +static isc_result_t +render_xml_all(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, + void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_ALL, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_status(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_STATUS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_server(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_SERVER, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_ZONES, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_net(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, + void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_NET, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_tasks(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_TASKS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_mem(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, + void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_MEM, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_xml_traffic(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_TRAFFIC, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +/* + * Which statistics to include when rendering to JSON + */ +#define STATS_JSON_STATUS 0x00 /* display only common statistics */ +#define STATS_JSON_SERVER 0x01 +#define STATS_JSON_ZONES 0x02 +#define STATS_JSON_TASKS 0x04 +#define STATS_JSON_NET 0x08 +#define STATS_JSON_MEM 0x10 +#define STATS_JSON_TRAFFIC 0x20 +#define STATS_JSON_ALL 0xff + +#define CHECKMEM(m) \ + do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto cleanup; \ + } \ + } while (0) + +static void +wrap_jsonfree(isc_buffer_t *buffer, void *arg) { + json_object_put(isc_buffer_base(buffer)); + if (arg != NULL) { + json_object_put((json_object *)arg); + } +} + +static json_object * +addzone(char *name, char *classname, const char *ztype, uint32_t serial, + bool add_serial) { + json_object *node = json_object_new_object(); + + if (node == NULL) { + return (NULL); + } + + json_object_object_add(node, "name", json_object_new_string(name)); + json_object_object_add(node, "class", + json_object_new_string(classname)); + if (add_serial) { + json_object_object_add(node, "serial", + json_object_new_int64(serial)); + } + if (ztype != NULL) { + json_object_object_add(node, "type", + json_object_new_string(ztype)); + } + return (node); +} + +static isc_result_t +zone_jsonrender(dns_zone_t *zone, void *arg) { + isc_result_t result = ISC_R_SUCCESS; + char buf[1024 + 32]; /* sufficiently large for zone name and class */ + char classbuf[64]; /* sufficiently large for class */ + char *zone_name_only = NULL; + char *class_only = NULL; + dns_rdataclass_t rdclass; + uint32_t serial; + json_object *zonearray = (json_object *)arg; + json_object *zoneobj = NULL; + dns_zonestat_level_t statlevel; + isc_time_t timestamp; + + statlevel = dns_zone_getstatlevel(zone); + if (statlevel == dns_zonestat_none) { + return (ISC_R_SUCCESS); + } + + dns_zone_nameonly(zone, buf, sizeof(buf)); + zone_name_only = buf; + + rdclass = dns_zone_getclass(zone); + dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf)); + class_only = classbuf; + + if (dns_zone_getserial(zone, &serial) != ISC_R_SUCCESS) { + zoneobj = addzone(zone_name_only, class_only, + user_zonetype(zone), 0, false); + } else { + zoneobj = addzone(zone_name_only, class_only, + user_zonetype(zone), serial, true); + } + + if (zoneobj == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * Export zone timers to the statistics channel in JSON format. + * For primary zones, only include the loaded time. For secondary + * zones, also include the expire and refresh times. + */ + + CHECK(dns_zone_getloadtime(zone, ×tamp)); + + isc_time_formatISO8601(×tamp, buf, 64); + json_object_object_add(zoneobj, "loaded", json_object_new_string(buf)); + + if (dns_zone_gettype(zone) == dns_zone_secondary) { + CHECK(dns_zone_getexpiretime(zone, ×tamp)); + isc_time_formatISO8601(×tamp, buf, 64); + json_object_object_add(zoneobj, "expires", + json_object_new_string(buf)); + + CHECK(dns_zone_getrefreshtime(zone, ×tamp)); + isc_time_formatISO8601(×tamp, buf, 64); + json_object_object_add(zoneobj, "refresh", + json_object_new_string(buf)); + } + + if (statlevel == dns_zonestat_full) { + isc_stats_t *zonestats; + isc_stats_t *gluecachestats; + dns_stats_t *rcvquerystats; + dns_stats_t *dnssecsignstats; + uint64_t nsstat_values[ns_statscounter_max]; + uint64_t gluecachestats_values[dns_gluecachestatscounter_max]; + + zonestats = dns_zone_getrequeststats(zone); + if (zonestats != NULL) { + json_object *counters = json_object_new_object(); + if (counters == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = dump_counters(zonestats, isc_statsformat_json, + counters, NULL, nsstats_xmldesc, + ns_statscounter_max, + nsstats_index, nsstat_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(zoneobj, "rcodes", + counters); + } else { + json_object_put(counters); + } + } + + gluecachestats = dns_zone_getgluecachestats(zone); + if (gluecachestats != NULL) { + json_object *counters = json_object_new_object(); + if (counters == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = dump_counters( + gluecachestats, isc_statsformat_json, counters, + NULL, gluecachestats_xmldesc, + dns_gluecachestatscounter_max, + gluecachestats_index, gluecachestats_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(zoneobj, "gluecache", + counters); + } else { + json_object_put(counters); + } + } + + rcvquerystats = dns_zone_getrcvquerystats(zone); + if (rcvquerystats != NULL) { + stats_dumparg_t dumparg; + json_object *counters = json_object_new_object(); + CHECKMEM(counters); + + dumparg.type = isc_statsformat_json; + dumparg.arg = counters; + dumparg.result = ISC_R_SUCCESS; + dns_rdatatypestats_dump(rcvquerystats, rdtypestat_dump, + &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(zoneobj, "qtypes", + counters); + } else { + json_object_put(counters); + } + } + + dnssecsignstats = dns_zone_getdnssecsignstats(zone); + if (dnssecsignstats != NULL) { + stats_dumparg_t dumparg; + json_object *sign_counters = json_object_new_object(); + CHECKMEM(sign_counters); + + dumparg.type = isc_statsformat_json; + dumparg.arg = sign_counters; + dumparg.result = ISC_R_SUCCESS; + dns_dnssecsignstats_dump( + dnssecsignstats, dns_dnssecsignstats_sign, + dnssecsignstat_dump, &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(sign_counters); + goto cleanup; + } + + if (json_object_get_object(sign_counters)->count != 0) { + json_object_object_add(zoneobj, "dnssec-sign", + sign_counters); + } else { + json_object_put(sign_counters); + } + + json_object *refresh_counters = + json_object_new_object(); + CHECKMEM(refresh_counters); + + dumparg.type = isc_statsformat_json; + dumparg.arg = refresh_counters; + dumparg.result = ISC_R_SUCCESS; + dns_dnssecsignstats_dump( + dnssecsignstats, dns_dnssecsignstats_refresh, + dnssecsignstat_dump, &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(refresh_counters); + goto cleanup; + } + + if (json_object_get_object(refresh_counters)->count != + 0) + { + json_object_object_add(zoneobj, + "dnssec-refresh", + refresh_counters); + } else { + json_object_put(refresh_counters); + } + } + } + + json_object_array_add(zonearray, zoneobj); + zoneobj = NULL; + result = ISC_R_SUCCESS; + +cleanup: + if (zoneobj != NULL) { + json_object_put(zoneobj); + } + return (result); +} + +static isc_result_t +generatejson(named_server_t *server, size_t *msglen, const char **msg, + json_object **rootp, uint32_t flags) { + dns_view_t *view; + isc_result_t result = ISC_R_SUCCESS; + json_object *bindstats, *viewlist, *counters, *obj; + json_object *traffic = NULL; + json_object *udpreq4 = NULL, *udpresp4 = NULL; + json_object *tcpreq4 = NULL, *tcpresp4 = NULL; + json_object *udpreq6 = NULL, *udpresp6 = NULL; + json_object *tcpreq6 = NULL, *tcpresp6 = NULL; + uint64_t nsstat_values[ns_statscounter_max]; + uint64_t resstat_values[dns_resstatscounter_max]; + uint64_t adbstat_values[dns_adbstats_max]; + uint64_t zonestat_values[dns_zonestatscounter_max]; + uint64_t sockstat_values[isc_sockstatscounter_max]; + uint64_t udpinsizestat_values[dns_sizecounter_in_max]; + uint64_t udpoutsizestat_values[dns_sizecounter_out_max]; + uint64_t tcpinsizestat_values[dns_sizecounter_in_max]; + uint64_t tcpoutsizestat_values[dns_sizecounter_out_max]; +#ifdef HAVE_DNSTAP + uint64_t dnstapstat_values[dns_dnstapcounter_max]; +#endif /* ifdef HAVE_DNSTAP */ + stats_dumparg_t dumparg; + char boottime[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + char configtime[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + char nowstr[sizeof "yyyy-mm-ddThh:mm:ss.sssZ"]; + isc_time_t now; + + REQUIRE(msglen != NULL); + REQUIRE(msg != NULL && *msg == NULL); + REQUIRE(rootp == NULL || *rootp == NULL); + + bindstats = json_object_new_object(); + if (bindstats == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * These statistics are included no matter which URL we use. + */ + obj = json_object_new_string(STATS_JSON_VERSION); + CHECKMEM(obj); + json_object_object_add(bindstats, "json-stats-version", obj); + + isc_time_now(&now); + isc_time_formatISO8601ms(&named_g_boottime, boottime, sizeof(boottime)); + isc_time_formatISO8601ms(&named_g_configtime, configtime, + sizeof configtime); + isc_time_formatISO8601ms(&now, nowstr, sizeof(nowstr)); + + obj = json_object_new_string(boottime); + CHECKMEM(obj); + json_object_object_add(bindstats, "boot-time", obj); + + obj = json_object_new_string(configtime); + CHECKMEM(obj); + json_object_object_add(bindstats, "config-time", obj); + + obj = json_object_new_string(nowstr); + CHECKMEM(obj); + json_object_object_add(bindstats, "current-time", obj); + obj = json_object_new_string(PACKAGE_VERSION); + CHECKMEM(obj); + json_object_object_add(bindstats, "version", obj); + + if ((flags & STATS_JSON_SERVER) != 0) { + /* OPCODE counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.type = isc_statsformat_json; + dumparg.arg = counters; + + dns_opcodestats_dump(server->sctx->opcodestats, opcodestat_dump, + &dumparg, ISC_STATSDUMP_VERBOSE); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "opcodes", counters); + } else { + json_object_put(counters); + } + + /* OPCODE counters */ + counters = json_object_new_object(); + + dumparg.type = isc_statsformat_json; + dumparg.arg = counters; + + dns_rcodestats_dump(server->sctx->rcodestats, rcodestat_dump, + &dumparg, ISC_STATSDUMP_VERBOSE); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "rcodes", counters); + } else { + json_object_put(counters); + } + + /* QTYPE counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + + dns_rdatatypestats_dump(server->sctx->rcvquerystats, + rdtypestat_dump, &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "qtypes", counters); + } else { + json_object_put(counters); + } + + /* server stat counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + + result = dump_counters(ns_stats_get(server->sctx->nsstats), + isc_statsformat_json, counters, NULL, + nsstats_xmldesc, ns_statscounter_max, + nsstats_index, nsstat_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "nsstats", counters); + } else { + json_object_put(counters); + } + + /* zone stat counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + + result = dump_counters(server->zonestats, isc_statsformat_json, + counters, NULL, zonestats_xmldesc, + dns_zonestatscounter_max, + zonestats_index, zonestat_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "zonestats", + counters); + } else { + json_object_put(counters); + } + + /* resolver stat counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + + result = dump_counters( + server->resolverstats, isc_statsformat_json, counters, + NULL, resstats_xmldesc, dns_resstatscounter_max, + resstats_index, resstat_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "resstats", counters); + } else { + json_object_put(counters); + } + +#ifdef HAVE_DNSTAP + /* dnstap stat counters */ + if (named_g_server->dtenv != NULL) { + isc_stats_t *dnstapstats = NULL; + dns_dt_getstats(named_g_server->dtenv, &dnstapstats); + counters = json_object_new_object(); + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + result = dump_counters( + dnstapstats, isc_statsformat_json, counters, + NULL, dnstapstats_xmldesc, + dns_dnstapcounter_max, dnstapstats_index, + dnstapstat_values, 0); + isc_stats_detach(&dnstapstats); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "dnstapstats", + counters); + } else { + json_object_put(counters); + } + } +#endif /* ifdef HAVE_DNSTAP */ + } + + if ((flags & (STATS_JSON_ZONES | STATS_JSON_SERVER)) != 0) { + viewlist = json_object_new_object(); + CHECKMEM(viewlist); + + json_object_object_add(bindstats, "views", viewlist); + + view = ISC_LIST_HEAD(server->viewlist); + while (view != NULL) { + json_object *za, *v = json_object_new_object(); + + CHECKMEM(v); + json_object_object_add(viewlist, view->name, v); + + za = json_object_new_array(); + CHECKMEM(za); + + if ((flags & STATS_JSON_ZONES) != 0) { + CHECK(dns_zt_apply(view->zonetable, + isc_rwlocktype_read, true, + NULL, zone_jsonrender, za)); + } + + if (json_object_array_length(za) != 0) { + json_object_object_add(v, "zones", za); + } else { + json_object_put(za); + } + + if ((flags & STATS_JSON_SERVER) != 0) { + json_object *res; + dns_stats_t *dstats; + isc_stats_t *istats; + + res = json_object_new_object(); + CHECKMEM(res); + json_object_object_add(v, "resolver", res); + + istats = view->resstats; + if (istats != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + result = dump_counters( + istats, isc_statsformat_json, + counters, NULL, + resstats_xmldesc, + dns_resstatscounter_max, + resstats_index, resstat_values, + 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + result = dumparg.result; + goto cleanup; + } + + json_object_object_add(res, "stats", + counters); + } + + dstats = view->resquerystats; + if (dstats != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + dumparg.arg = counters; + dumparg.result = ISC_R_SUCCESS; + dns_rdatatypestats_dump(dstats, + rdtypestat_dump, + &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + result = dumparg.result; + goto cleanup; + } + + json_object_object_add(res, "qtypes", + counters); + } + + dstats = dns_db_getrrsetstats(view->cachedb); + if (dstats != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + dumparg.arg = counters; + dumparg.result = ISC_R_SUCCESS; + dns_rdatasetstats_dump( + dstats, rdatasetstats_dump, + &dumparg, 0); + if (dumparg.result != ISC_R_SUCCESS) { + json_object_put(counters); + result = dumparg.result; + goto cleanup; + } + + json_object_object_add(res, "cache", + counters); + } + + counters = json_object_new_object(); + CHECKMEM(counters); + + result = dns_cache_renderjson(view->cache, + counters); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + json_object_object_add(res, "cachestats", + counters); + + istats = view->adbstats; + if (istats != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + result = dump_counters( + istats, isc_statsformat_json, + counters, NULL, + adbstats_xmldesc, + dns_adbstats_max, + adbstats_index, adbstat_values, + 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + result = dumparg.result; + goto cleanup; + } + + json_object_object_add(res, "adb", + counters); + } + } + + view = ISC_LIST_NEXT(view, link); + } + } + + if ((flags & STATS_JSON_NET) != 0) { + /* socket stat counters */ + counters = json_object_new_object(); + + dumparg.result = ISC_R_SUCCESS; + dumparg.arg = counters; + + result = dump_counters(server->sockstats, isc_statsformat_json, + counters, NULL, sockstats_xmldesc, + isc_sockstatscounter_max, + sockstats_index, sockstat_values, 0); + if (result != ISC_R_SUCCESS) { + json_object_put(counters); + goto cleanup; + } + + if (json_object_get_object(counters)->count != 0) { + json_object_object_add(bindstats, "sockstats", + counters); + } else { + json_object_put(counters); + } + } + + if ((flags & STATS_JSON_TASKS) != 0) { + json_object *tasks = json_object_new_object(); + CHECKMEM(tasks); + + result = isc_taskmgr_renderjson(named_g_taskmgr, tasks); + if (result != ISC_R_SUCCESS) { + json_object_put(tasks); + goto cleanup; + } + + json_object_object_add(bindstats, "taskmgr", tasks); + } + + if ((flags & STATS_JSON_MEM) != 0) { + json_object *memory = json_object_new_object(); + CHECKMEM(memory); + + result = isc_mem_renderjson(memory); + if (result != ISC_R_SUCCESS) { + json_object_put(memory); + goto cleanup; + } + + json_object_object_add(bindstats, "memory", memory); + } + + if ((flags & STATS_JSON_TRAFFIC) != 0) { + traffic = json_object_new_object(); + CHECKMEM(traffic); + + udpreq4 = json_object_new_object(); + CHECKMEM(udpreq4); + + udpresp4 = json_object_new_object(); + CHECKMEM(udpresp4); + + tcpreq4 = json_object_new_object(); + CHECKMEM(tcpreq4); + + tcpresp4 = json_object_new_object(); + CHECKMEM(tcpresp4); + + udpreq6 = json_object_new_object(); + CHECKMEM(udpreq6); + + udpresp6 = json_object_new_object(); + CHECKMEM(udpresp6); + + tcpreq6 = json_object_new_object(); + CHECKMEM(tcpreq6); + + tcpresp6 = json_object_new_object(); + CHECKMEM(tcpresp6); + + CHECK(dump_counters( + server->sctx->udpinstats4, isc_statsformat_json, + udpreq4, NULL, udpinsizestats_xmldesc, + dns_sizecounter_in_max, udpinsizestats_index, + udpinsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->udpoutstats4, isc_statsformat_json, + udpresp4, NULL, udpoutsizestats_xmldesc, + dns_sizecounter_out_max, udpoutsizestats_index, + udpoutsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->tcpinstats4, isc_statsformat_json, + tcpreq4, NULL, tcpinsizestats_xmldesc, + dns_sizecounter_in_max, tcpinsizestats_index, + tcpinsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->tcpoutstats4, isc_statsformat_json, + tcpresp4, NULL, tcpoutsizestats_xmldesc, + dns_sizecounter_out_max, tcpoutsizestats_index, + tcpoutsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->udpinstats6, isc_statsformat_json, + udpreq6, NULL, udpinsizestats_xmldesc, + dns_sizecounter_in_max, udpinsizestats_index, + udpinsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->udpoutstats6, isc_statsformat_json, + udpresp6, NULL, udpoutsizestats_xmldesc, + dns_sizecounter_out_max, udpoutsizestats_index, + udpoutsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->tcpinstats6, isc_statsformat_json, + tcpreq6, NULL, tcpinsizestats_xmldesc, + dns_sizecounter_in_max, tcpinsizestats_index, + tcpinsizestat_values, 0)); + + CHECK(dump_counters( + server->sctx->tcpoutstats6, isc_statsformat_json, + tcpresp6, NULL, tcpoutsizestats_xmldesc, + dns_sizecounter_out_max, tcpoutsizestats_index, + tcpoutsizestat_values, 0)); + + json_object_object_add(traffic, + "dns-udp-requests-sizes-received-ipv4", + udpreq4); + json_object_object_add( + traffic, "dns-udp-responses-sizes-sent-ipv4", udpresp4); + json_object_object_add(traffic, + "dns-tcp-requests-sizes-received-ipv4", + tcpreq4); + json_object_object_add( + traffic, "dns-tcp-responses-sizes-sent-ipv4", tcpresp4); + json_object_object_add(traffic, + "dns-udp-requests-sizes-received-ipv6", + udpreq6); + json_object_object_add( + traffic, "dns-udp-responses-sizes-sent-ipv6", udpresp6); + json_object_object_add(traffic, + "dns-tcp-requests-sizes-received-ipv6", + tcpreq6); + json_object_object_add( + traffic, "dns-tcp-responses-sizes-sent-ipv6", tcpresp6); + json_object_object_add(bindstats, "traffic", traffic); + udpreq4 = NULL; + udpresp4 = NULL; + tcpreq4 = NULL; + tcpresp4 = NULL; + udpreq6 = NULL; + udpresp6 = NULL; + tcpreq6 = NULL; + tcpresp6 = NULL; + traffic = NULL; + } + + *msg = json_object_to_json_string_ext(bindstats, + JSON_C_TO_STRING_PRETTY); + *msglen = strlen(*msg); + + if (rootp != NULL) { + *rootp = bindstats; + bindstats = NULL; + } + + result = ISC_R_SUCCESS; + +cleanup: + if (udpreq4 != NULL) { + json_object_put(udpreq4); + } + if (udpresp4 != NULL) { + json_object_put(udpresp4); + } + if (tcpreq4 != NULL) { + json_object_put(tcpreq4); + } + if (tcpresp4 != NULL) { + json_object_put(tcpresp4); + } + if (udpreq6 != NULL) { + json_object_put(udpreq6); + } + if (udpresp6 != NULL) { + json_object_put(udpresp6); + } + if (tcpreq6 != NULL) { + json_object_put(tcpreq6); + } + if (tcpresp6 != NULL) { + json_object_put(tcpresp6); + } + if (traffic != NULL) { + json_object_put(traffic); + } + if (bindstats != NULL) { + json_object_put(bindstats); + } + + return (result); +} + +static isc_result_t +render_json(uint32_t flags, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + isc_result_t result; + json_object *bindstats = NULL; + named_server_t *server = arg; + const char *msg = NULL; + size_t msglen = 0; + char *p; + + result = generatejson(server, &msglen, &msg, &bindstats, flags); + if (result == ISC_R_SUCCESS) { + *retcode = 200; + *retmsg = "OK"; + *mimetype = "application/json"; + DE_CONST(msg, p); + isc_buffer_reinit(b, p, msglen); + isc_buffer_add(b, msglen); + *freecb = wrap_jsonfree; + *freecb_args = bindstats; + } else { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed at rendering JSON()"); + } + + return (result); +} + +static isc_result_t +render_json_all(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_ALL, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_json_status(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_STATUS, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); +} + +static isc_result_t +render_json_server(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_SERVER, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); +} + +static isc_result_t +render_json_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_ZONES, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_json_mem(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_MEM, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_json_tasks(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_TASKS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_json_net(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_NET, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + +static isc_result_t +render_json_traffic(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_TRAFFIC, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); +} + +#endif /* HAVE_JSON_C */ + +static isc_result_t +render_xsl(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *args, + unsigned int *retcode, const char **retmsg, const char **mimetype, + isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { + isc_result_t result; + char *p = NULL; + + UNUSED(httpd); + UNUSED(args); + + *freecb = NULL; + *freecb_args = NULL; + *mimetype = "text/xslt+xml"; + + if (isc_httpdurl_isstatic(urlinfo)) { + time_t t1, t2; + const isc_time_t *when; + const isc_time_t *loadtime; + + when = isc_httpd_if_modified_since(httpd); + + if (isc_time_isepoch(when)) { + goto send; + } + + result = isc_time_secondsastimet(when, &t1); + if (result != ISC_R_SUCCESS) { + goto send; + } + + loadtime = isc_httpdurl_loadtime(urlinfo); + + result = isc_time_secondsastimet(loadtime, &t2); + if (result != ISC_R_SUCCESS) { + goto send; + } + + if (t1 < t2) { + goto send; + } + + *retcode = 304; + *retmsg = "Not modified"; + goto end; + } + +send: + *retcode = 200; + *retmsg = "OK"; + DE_CONST(xslmsg, p); + isc_buffer_reinit(b, p, strlen(xslmsg)); + isc_buffer_add(b, strlen(xslmsg)); +end: + return (ISC_R_SUCCESS); +} + +static void +shutdown_listener(named_statschannel_t *listener) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&listener->address, socktext, sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "stopping statistics channel on %s", socktext); + + isc_httpdmgr_shutdown(&listener->httpdmgr); +} + +static bool +client_ok(const isc_sockaddr_t *fromaddr, void *arg) { + named_statschannel_t *listener = arg; + dns_aclenv_t *env = + ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + isc_netaddr_t netaddr; + char socktext[ISC_SOCKADDR_FORMATSIZE]; + int match; + + REQUIRE(listener != NULL); + + isc_netaddr_fromsockaddr(&netaddr, fromaddr); + + LOCK(&listener->lock); + if ((dns_acl_match(&netaddr, NULL, listener->acl, env, &match, NULL) == + ISC_R_SUCCESS) && + match > 0) + { + UNLOCK(&listener->lock); + return (true); + } + UNLOCK(&listener->lock); + + isc_sockaddr_format(fromaddr, socktext, sizeof(socktext)); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "rejected statistics connection from %s", socktext); + + return (false); +} + +static void +destroy_listener(void *arg) { + named_statschannel_t *listener = (named_statschannel_t *)arg; + + REQUIRE(listener != NULL); + REQUIRE(!ISC_LINK_LINKED(listener, link)); + + /* We don't have to acquire the lock here since it's already unlinked */ + dns_acl_detach(&listener->acl); + + isc_mutex_destroy(&listener->lock); + isc_mem_putanddetach(&listener->mctx, listener, sizeof(*listener)); +} + +static isc_result_t +add_listener(named_server_t *server, named_statschannel_t **listenerp, + const cfg_obj_t *listen_params, const cfg_obj_t *config, + isc_sockaddr_t *addr, cfg_aclconfctx_t *aclconfctx, + const char *socktext) { + isc_result_t result; + named_statschannel_t *listener = NULL; + const cfg_obj_t *allow = NULL; + dns_acl_t *new_acl = NULL; + int pf; + + listener = isc_mem_get(server->mctx, sizeof(*listener)); + *listener = (named_statschannel_t){ .address = *addr }; + ISC_LINK_INIT(listener, link); + isc_mutex_init(&listener->lock); + isc_mem_attach(server->mctx, &listener->mctx); + + allow = cfg_tuple_get(listen_params, "allow"); + if (allow != NULL && cfg_obj_islist(allow)) { + result = cfg_acl_fromconfig(allow, config, named_g_lctx, + aclconfctx, listener->mctx, 0, + &new_acl); + } else { + result = dns_acl_any(listener->mctx, &new_acl); + } + CHECK(result); + + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + + pf = isc_sockaddr_pf(&listener->address); + if ((pf == AF_INET && isc_net_probeipv4() != ISC_R_SUCCESS) || + (pf == AF_INET6 && isc_net_probeipv6() != ISC_R_SUCCESS)) + { + CHECK(ISC_R_FAMILYNOSUPPORT); + } + + CHECK(isc_httpdmgr_create(named_g_netmgr, server->mctx, addr, client_ok, + destroy_listener, listener, + &listener->httpdmgr)); + +#ifdef HAVE_LIBXML2 + isc_httpdmgr_addurl(listener->httpdmgr, "/", false, render_xml_all, + server); + isc_httpdmgr_addurl(listener->httpdmgr, "/xml", false, render_xml_all, + server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR, false, + render_xml_all, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/status", false, + render_xml_status, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/server", false, + render_xml_server, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/zones", false, + render_xml_zones, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/net", false, + render_xml_net, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/tasks", false, + render_xml_tasks, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/mem", false, + render_xml_mem, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/traffic", false, + render_xml_traffic, server); +#endif /* ifdef HAVE_LIBXML2 */ +#ifdef HAVE_JSON_C + isc_httpdmgr_addurl(listener->httpdmgr, "/json", false, render_json_all, + server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR, false, + render_json_all, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/status", false, + render_json_status, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/server", false, + render_json_server, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/zones", false, + render_json_zones, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/tasks", false, + render_json_tasks, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/net", false, + render_json_net, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/mem", false, + render_json_mem, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/traffic", + false, render_json_traffic, server); +#endif /* ifdef HAVE_JSON_C */ + isc_httpdmgr_addurl(listener->httpdmgr, "/bind9.xsl", true, render_xsl, + server); + + *listenerp = listener; + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "statistics channel listening on %s", socktext); + + return (ISC_R_SUCCESS); + +cleanup: + if (listener->acl != NULL) { + dns_acl_detach(&listener->acl); + } + isc_mutex_destroy(&listener->lock); + isc_mem_putanddetach(&listener->mctx, listener, sizeof(*listener)); + + return (result); +} + +static void +update_listener(named_server_t *server, named_statschannel_t **listenerp, + const cfg_obj_t *listen_params, const cfg_obj_t *config, + isc_sockaddr_t *addr, cfg_aclconfctx_t *aclconfctx, + const char *socktext) { + named_statschannel_t *listener; + const cfg_obj_t *allow = NULL; + dns_acl_t *new_acl = NULL; + isc_result_t result = ISC_R_SUCCESS; + + for (listener = ISC_LIST_HEAD(server->statschannels); listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + if (isc_sockaddr_equal(addr, &listener->address)) { + break; + } + } + + if (listener == NULL) { + *listenerp = NULL; + return; + } + + /* + * Now, keep the old access list unless a new one can be made. + */ + allow = cfg_tuple_get(listen_params, "allow"); + if (allow != NULL && cfg_obj_islist(allow)) { + result = cfg_acl_fromconfig(allow, config, named_g_lctx, + aclconfctx, listener->mctx, 0, + &new_acl); + } else { + result = dns_acl_any(listener->mctx, &new_acl); + } + + if (result == ISC_R_SUCCESS) { + LOCK(&listener->lock); + + dns_acl_detach(&listener->acl); + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + + UNLOCK(&listener->lock); + } else { + cfg_obj_log(listen_params, named_g_lctx, ISC_LOG_WARNING, + "couldn't install new acl for " + "statistics channel %s: %s", + socktext, isc_result_totext(result)); + } + + *listenerp = listener; +} + +isc_result_t +named_statschannels_configure(named_server_t *server, const cfg_obj_t *config, + cfg_aclconfctx_t *aclconfctx) { + named_statschannel_t *listener, *listener_next; + named_statschannellist_t new_listeners; + const cfg_obj_t *statschannellist = NULL; + const cfg_listelt_t *element, *element2; + char socktext[ISC_SOCKADDR_FORMATSIZE]; + + RUNTIME_CHECK(isc_once_do(&once, init_desc) == ISC_R_SUCCESS); + + ISC_LIST_INIT(new_listeners); + + /* + * Get the list of named.conf 'statistics-channels' statements. + */ + (void)cfg_map_get(config, "statistics-channels", &statschannellist); + + /* + * Run through the new address/port list, noting sockets that are + * already being listened on and moving them to the new list. + * + * Identifying duplicate addr/port combinations is left to either + * the underlying config code, or to the bind attempt getting an + * address-in-use error. + */ + if (statschannellist != NULL) { +#ifndef EXTENDED_STATS + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "statistics-channels specified but not effective " + "due to missing XML and/or JSON library"); +#else /* EXTENDED_STATS */ +#ifndef HAVE_LIBXML2 + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "statistics-channels: XML library missing, " + "only JSON stats will be available"); +#endif /* !HAVE_LIBXML2 */ +#ifndef HAVE_JSON_C + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "statistics-channels: JSON library missing, " + "only XML stats will be available"); +#endif /* !HAVE_JSON_C */ +#endif /* EXTENDED_STATS */ + + for (element = cfg_list_first(statschannellist); + element != NULL; element = cfg_list_next(element)) + { + const cfg_obj_t *statschannel; + const cfg_obj_t *listenercfg = NULL; + + statschannel = cfg_listelt_value(element); + (void)cfg_map_get(statschannel, "inet", &listenercfg); + if (listenercfg == NULL) { + continue; + } + + for (element2 = cfg_list_first(listenercfg); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + const cfg_obj_t *listen_params; + const cfg_obj_t *obj; + isc_sockaddr_t addr; + + listen_params = cfg_listelt_value(element2); + + obj = cfg_tuple_get(listen_params, "address"); + addr = *cfg_obj_assockaddr(obj); + if (isc_sockaddr_getport(&addr) == 0) { + isc_sockaddr_setport( + &addr, + NAMED_STATSCHANNEL_HTTPPORT); + } + + isc_sockaddr_format(&addr, socktext, + sizeof(socktext)); + + isc_log_write(named_g_lctx, + NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, + ISC_LOG_DEBUG(9), + "processing statistics " + "channel %s", + socktext); + + update_listener(server, &listener, + listen_params, config, &addr, + aclconfctx, socktext); + + if (listener != NULL) { + /* + * Remove the listener from the old + * list, so it won't be shut down. + */ + ISC_LIST_UNLINK(server->statschannels, + listener, link); + } else { + /* + * This is a new listener. + */ + isc_result_t r; + + r = add_listener(server, &listener, + listen_params, config, + &addr, aclconfctx, + socktext); + if (r != ISC_R_SUCCESS) { + cfg_obj_log( + listen_params, + named_g_lctx, + ISC_LOG_WARNING, + "couldn't allocate " + "statistics channel" + " %s: %s", + socktext, + isc_result_totext(r)); + } + } + + if (listener != NULL) { + ISC_LIST_APPEND(new_listeners, listener, + link); + } + } + } + } + + for (listener = ISC_LIST_HEAD(server->statschannels); listener != NULL; + listener = listener_next) + { + listener_next = ISC_LIST_NEXT(listener, link); + ISC_LIST_UNLINK(server->statschannels, listener, link); + shutdown_listener(listener); + } + + ISC_LIST_APPENDLIST(server->statschannels, new_listeners, link); + return (ISC_R_SUCCESS); +} + +void +named_statschannels_shutdown(named_server_t *server) { + named_statschannel_t *listener; + + while ((listener = ISC_LIST_HEAD(server->statschannels)) != NULL) { + ISC_LIST_UNLINK(server->statschannels, listener, link); + shutdown_listener(listener); + } +} + +isc_result_t +named_stats_dump(named_server_t *server, FILE *fp) { + isc_stdtime_t now; + isc_result_t result; + dns_view_t *view; + dns_zone_t *zone, *next; + stats_dumparg_t dumparg; + uint64_t nsstat_values[ns_statscounter_max]; + uint64_t resstat_values[dns_resstatscounter_max]; + uint64_t adbstat_values[dns_adbstats_max]; + uint64_t zonestat_values[dns_zonestatscounter_max]; + uint64_t sockstat_values[isc_sockstatscounter_max]; + uint64_t gluecachestats_values[dns_gluecachestatscounter_max]; + + RUNTIME_CHECK(isc_once_do(&once, init_desc) == ISC_R_SUCCESS); + + /* Set common fields */ + dumparg.type = isc_statsformat_file; + dumparg.arg = fp; + + isc_stdtime_get(&now); + fprintf(fp, "+++ Statistics Dump +++ (%lu)\n", (unsigned long)now); + + fprintf(fp, "++ Incoming Requests ++\n"); + dns_opcodestats_dump(server->sctx->opcodestats, opcodestat_dump, + &dumparg, 0); + + fprintf(fp, "++ Incoming Queries ++\n"); + dns_rdatatypestats_dump(server->sctx->rcvquerystats, rdtypestat_dump, + &dumparg, 0); + + fprintf(fp, "++ Outgoing Rcodes ++\n"); + dns_rcodestats_dump(server->sctx->rcodestats, rcodestat_dump, &dumparg, + 0); + + fprintf(fp, "++ Outgoing Queries ++\n"); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->resquerystats == NULL) { + continue; + } + if (strcmp(view->name, "_default") == 0) { + fprintf(fp, "[View: default]\n"); + } else { + fprintf(fp, "[View: %s]\n", view->name); + } + dns_rdatatypestats_dump(view->resquerystats, rdtypestat_dump, + &dumparg, 0); + } + + fprintf(fp, "++ Name Server Statistics ++\n"); + (void)dump_counters(ns_stats_get(server->sctx->nsstats), + isc_statsformat_file, fp, NULL, nsstats_desc, + ns_statscounter_max, nsstats_index, nsstat_values, + 0); + + fprintf(fp, "++ Zone Maintenance Statistics ++\n"); + (void)dump_counters(server->zonestats, isc_statsformat_file, fp, NULL, + zonestats_desc, dns_zonestatscounter_max, + zonestats_index, zonestat_values, 0); + + fprintf(fp, "++ Resolver Statistics ++\n"); + fprintf(fp, "[Common]\n"); + (void)dump_counters(server->resolverstats, isc_statsformat_file, fp, + NULL, resstats_desc, dns_resstatscounter_max, + resstats_index, resstat_values, 0); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->resstats == NULL) { + continue; + } + if (strcmp(view->name, "_default") == 0) { + fprintf(fp, "[View: default]\n"); + } else { + fprintf(fp, "[View: %s]\n", view->name); + } + (void)dump_counters(view->resstats, isc_statsformat_file, fp, + NULL, resstats_desc, + dns_resstatscounter_max, resstats_index, + resstat_values, 0); + } + + fprintf(fp, "++ Cache Statistics ++\n"); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (strcmp(view->name, "_default") == 0) { + fprintf(fp, "[View: default]\n"); + } else { + fprintf(fp, "[View: %s (Cache: %s)]\n", view->name, + dns_cache_getname(view->cache)); + } + /* + * Avoid dumping redundant statistics when the cache is shared. + */ + if (dns_view_iscacheshared(view)) { + continue; + } + dns_cache_dumpstats(view->cache, fp); + } + + fprintf(fp, "++ Cache DB RRsets ++\n"); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + dns_stats_t *cacherrstats; + + cacherrstats = dns_db_getrrsetstats(view->cachedb); + if (cacherrstats == NULL) { + continue; + } + if (strcmp(view->name, "_default") == 0) { + fprintf(fp, "[View: default]\n"); + } else { + fprintf(fp, "[View: %s (Cache: %s)]\n", view->name, + dns_cache_getname(view->cache)); + } + if (dns_view_iscacheshared(view)) { + /* + * Avoid dumping redundant statistics when the cache is + * shared. + */ + continue; + } + dns_rdatasetstats_dump(cacherrstats, rdatasetstats_dump, + &dumparg, 0); + } + + fprintf(fp, "++ ADB stats ++\n"); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->adbstats == NULL) { + continue; + } + if (strcmp(view->name, "_default") == 0) { + fprintf(fp, "[View: default]\n"); + } else { + fprintf(fp, "[View: %s]\n", view->name); + } + (void)dump_counters(view->adbstats, isc_statsformat_file, fp, + NULL, adbstats_desc, dns_adbstats_max, + adbstats_index, adbstat_values, 0); + } + + fprintf(fp, "++ Socket I/O Statistics ++\n"); + (void)dump_counters(server->sockstats, isc_statsformat_file, fp, NULL, + sockstats_desc, isc_sockstatscounter_max, + sockstats_index, sockstat_values, 0); + + fprintf(fp, "++ Per Zone Query Statistics ++\n"); + zone = NULL; + for (result = dns_zone_first(server->zonemgr, &zone); + result == ISC_R_SUCCESS; + next = NULL, result = dns_zone_next(zone, &next), zone = next) + { + isc_stats_t *zonestats = dns_zone_getrequeststats(zone); + if (zonestats != NULL) { + char zonename[DNS_NAME_FORMATSIZE]; + + view = dns_zone_getview(zone); + if (view == NULL) { + continue; + } + + dns_name_format(dns_zone_getorigin(zone), zonename, + sizeof(zonename)); + fprintf(fp, "[%s", zonename); + if (strcmp(view->name, "_default") != 0) { + fprintf(fp, " (view: %s)", view->name); + } + fprintf(fp, "]\n"); + + (void)dump_counters(zonestats, isc_statsformat_file, fp, + NULL, nsstats_desc, + ns_statscounter_max, nsstats_index, + nsstat_values, 0); + } + } + + fprintf(fp, "++ Per Zone Glue Cache Statistics ++\n"); + zone = NULL; + for (result = dns_zone_first(server->zonemgr, &zone); + result == ISC_R_SUCCESS; + next = NULL, result = dns_zone_next(zone, &next), zone = next) + { + isc_stats_t *gluecachestats = dns_zone_getgluecachestats(zone); + if (gluecachestats != NULL) { + char zonename[DNS_NAME_FORMATSIZE]; + + view = dns_zone_getview(zone); + if (view == NULL) { + continue; + } + + dns_name_format(dns_zone_getorigin(zone), zonename, + sizeof(zonename)); + fprintf(fp, "[%s", zonename); + if (strcmp(view->name, "_default") != 0) { + fprintf(fp, " (view: %s)", view->name); + } + fprintf(fp, "]\n"); + + (void)dump_counters( + gluecachestats, isc_statsformat_file, fp, NULL, + gluecachestats_desc, + dns_gluecachestatscounter_max, + gluecachestats_index, gluecachestats_values, 0); + } + } + + fprintf(fp, "--- Statistics Dump --- (%lu)\n", (unsigned long)now); + + return (ISC_R_SUCCESS); /* this function currently always succeeds */ +} diff --git a/bin/named/tkeyconf.c b/bin/named/tkeyconf.c new file mode 100644 index 0000000..03b198b --- /dev/null +++ b/bin/named/tkeyconf.c @@ -0,0 +1,115 @@ +/* + * 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 <isc/buffer.h> +#include <isc/mem.h> +#include <isc/string.h> /* Required for HP/UX (and others?) */ + +#include <dns/fixedname.h> +#include <dns/keyvalues.h> +#include <dns/name.h> +#include <dns/tkey.h> + +#include <dst/gssapi.h> + +#include <isccfg/cfg.h> + +#include <named/tkeyconf.h> + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#include <named/log.h> +#define LOG(msg) \ + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, \ + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, "%s", msg) + +isc_result_t +named_tkeyctx_fromconfig(const cfg_obj_t *options, isc_mem_t *mctx, + dns_tkeyctx_t **tctxp) { + isc_result_t result; + dns_tkeyctx_t *tctx = NULL; + const char *s; + uint32_t n; + dns_fixedname_t fname; + dns_name_t *name; + isc_buffer_t b; + const cfg_obj_t *obj; + int type; + + result = dns_tkeyctx_create(mctx, &tctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + obj = NULL; + result = cfg_map_get(options, "tkey-dhkey", &obj); + if (result == ISC_R_SUCCESS) { + s = cfg_obj_asstring(cfg_tuple_get(obj, "name")); + n = cfg_obj_asuint32(cfg_tuple_get(obj, "keyid")); + isc_buffer_constinit(&b, s, strlen(s)); + isc_buffer_add(&b, strlen(s)); + name = dns_fixedname_initname(&fname); + RETERR(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + type = DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY; + RETERR(dst_key_fromfile(name, (dns_keytag_t)n, DNS_KEYALG_DH, + type, NULL, mctx, &tctx->dhkey)); + } + + obj = NULL; + result = cfg_map_get(options, "tkey-domain", &obj); + if (result == ISC_R_SUCCESS) { + s = cfg_obj_asstring(obj); + isc_buffer_constinit(&b, s, strlen(s)); + isc_buffer_add(&b, strlen(s)); + name = dns_fixedname_initname(&fname); + RETERR(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + tctx->domain = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(tctx->domain, NULL); + dns_name_dup(name, mctx, tctx->domain); + } + + obj = NULL; + result = cfg_map_get(options, "tkey-gssapi-credential", &obj); + if (result == ISC_R_SUCCESS) { + s = cfg_obj_asstring(obj); + + isc_buffer_constinit(&b, s, strlen(s)); + isc_buffer_add(&b, strlen(s)); + name = dns_fixedname_initname(&fname); + RETERR(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + RETERR(dst_gssapi_acquirecred(name, false, &tctx->gsscred)); + } + + obj = NULL; + result = cfg_map_get(options, "tkey-gssapi-keytab", &obj); + if (result == ISC_R_SUCCESS) { + s = cfg_obj_asstring(obj); + tctx->gssapi_keytab = isc_mem_strdup(mctx, s); + } + + *tctxp = tctx; + return (ISC_R_SUCCESS); + +failure: + dns_tkeyctx_destroy(&tctx); + return (result); +} diff --git a/bin/named/transportconf.c b/bin/named/transportconf.c new file mode 100644 index 0000000..f24aab1 --- /dev/null +++ b/bin/named/transportconf.c @@ -0,0 +1,263 @@ +/* + * 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 <isc/buffer.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/name.h> +#include <dns/transport.h> + +#include <isccfg/cfg.h> + +#include <named/log.h> +#include <named/transportconf.h> + +#define create_name(id, name) \ + isc_buffer_t namesrc, namebuf; \ + char namedata[DNS_NAME_FORMATSIZE + 1]; \ + dns_name_init(name, NULL); \ + isc_buffer_constinit(&namesrc, id, strlen(id)); \ + isc_buffer_add(&namesrc, strlen(id)); \ + isc_buffer_init(&namebuf, namedata, sizeof(namedata)); \ + result = (dns_name_fromtext(name, &namesrc, dns_rootname, \ + DNS_NAME_DOWNCASE, &namebuf)); \ + if (result != ISC_R_SUCCESS) { \ + goto failure; \ + } + +#define parse_transport_option(map, transport, name, setter) \ + { \ + const cfg_obj_t *obj = NULL; \ + cfg_map_get(map, name, &obj); \ + if (obj != NULL) { \ + setter(transport, cfg_obj_asstring(obj)); \ + } \ + } + +#define parse_transport_tls_versions(map, transport, name, setter) \ + { \ + const cfg_obj_t *obj = NULL; \ + cfg_map_get(map, name, &obj); \ + if (obj != NULL) { \ + { \ + uint32_t tls_protos = 0; \ + const cfg_listelt_t *proto = NULL; \ + INSIST(obj != NULL); \ + for (proto = cfg_list_first(obj); proto != 0; \ + proto = cfg_list_next(proto)) \ + { \ + const cfg_obj_t *tls_proto_obj = \ + cfg_listelt_value(proto); \ + const char *tls_sver = \ + cfg_obj_asstring( \ + tls_proto_obj); \ + const isc_tls_protocol_version_t ver = \ + isc_tls_protocol_name_to_version( \ + tls_sver); \ + INSIST(ver != \ + ISC_TLS_PROTO_VER_UNDEFINED); \ + INSIST(isc_tls_protocol_supported( \ + ver)); \ + tls_protos |= ver; \ + } \ + if (tls_protos != 0) { \ + setter(transport, tls_protos); \ + } \ + } \ + } \ + } + +#define parse_transport_bool_option(map, transport, name, setter) \ + { \ + const cfg_obj_t *obj = NULL; \ + cfg_map_get(map, name, &obj); \ + if (obj != NULL) { \ + setter(transport, cfg_obj_asboolean(obj)); \ + } \ + } + +static isc_result_t +add_doh_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) { + const cfg_obj_t *doh = NULL; + const char *dohid = NULL; + isc_result_t result; + + for (const cfg_listelt_t *element = cfg_list_first(transportlist); + element != NULL; element = cfg_list_next(element)) + { + dns_name_t dohname; + dns_transport_t *transport; + + doh = cfg_listelt_value(element); + dohid = cfg_obj_asstring(cfg_map_getname(doh)); + + create_name(dohid, &dohname); + + transport = dns_transport_new(&dohname, DNS_TRANSPORT_HTTP, + list); + + dns_transport_set_tlsname(transport, dohid); + parse_transport_option(doh, transport, "key-file", + dns_transport_set_keyfile); + parse_transport_option(doh, transport, "cert-file", + dns_transport_set_certfile); + parse_transport_tls_versions(doh, transport, "protocols", + dns_transport_set_tls_versions); + parse_transport_option(doh, transport, "ciphers", + dns_transport_set_ciphers); + parse_transport_bool_option( + doh, transport, "prefer-server-ciphers", + dns_transport_set_prefer_server_ciphers) + parse_transport_option(doh, transport, "ca-file", + dns_transport_set_cafile); + parse_transport_option(doh, transport, "remote-hostname", + dns_transport_set_remote_hostname); + } + + return (ISC_R_SUCCESS); +failure: + cfg_obj_log(doh, named_g_lctx, ISC_LOG_ERROR, + "configuring DoH '%s': %s", dohid, + isc_result_totext(result)); + + return (result); +} + +static isc_result_t +add_tls_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) { + const cfg_obj_t *tls = NULL; + const char *tlsid = NULL; + isc_result_t result; + + for (const cfg_listelt_t *element = cfg_list_first(transportlist); + element != NULL; element = cfg_list_next(element)) + { + dns_name_t tlsname; + dns_transport_t *transport; + + tls = cfg_listelt_value(element); + tlsid = cfg_obj_asstring(cfg_map_getname(tls)); + + if (!strcmp(tlsid, "ephemeral")) { + result = ISC_R_UNEXPECTEDTOKEN; + goto failure; + } + + create_name(tlsid, &tlsname); + + transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, + list); + + dns_transport_set_tlsname(transport, tlsid); + parse_transport_option(tls, transport, "key-file", + dns_transport_set_keyfile); + parse_transport_option(tls, transport, "cert-file", + dns_transport_set_certfile); + parse_transport_tls_versions(tls, transport, "protocols", + dns_transport_set_tls_versions); + parse_transport_option(tls, transport, "ciphers", + dns_transport_set_ciphers); + parse_transport_bool_option( + tls, transport, "prefer-server-ciphers", + dns_transport_set_prefer_server_ciphers) + parse_transport_option(tls, transport, "ca-file", + dns_transport_set_cafile); + parse_transport_option(tls, transport, "remote-hostname", + dns_transport_set_remote_hostname); + } + + return (ISC_R_SUCCESS); +failure: + cfg_obj_log(tls, named_g_lctx, ISC_LOG_ERROR, + "configuring tls '%s': %s", tlsid, + isc_result_totext(result)); + + return (result); +} + +#define CHECK(f) \ + if ((result = f) != ISC_R_SUCCESS) { \ + goto failure; \ + } + +static isc_result_t +transport_list_fromconfig(const cfg_obj_t *config, dns_transport_list_t *list) { + const cfg_obj_t *obj = NULL; + isc_result_t result = ISC_R_SUCCESS; + + if (result == ISC_R_SUCCESS && + cfg_map_get(config, "tls", &obj) == ISC_R_SUCCESS) + { + result = add_tls_transports(obj, list); + obj = NULL; + } + + if (result == ISC_R_SUCCESS && + cfg_map_get(config, "doh", &obj) == ISC_R_SUCCESS) + { + result = add_doh_transports(obj, list); + obj = NULL; + } + + return (result); +} + +static void +transport_list_add_ephemeral(dns_transport_list_t *list) { + isc_result_t result; + dns_name_t tlsname; + dns_transport_t *transport; + + create_name("ephemeral", &tlsname); + + transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list); + dns_transport_set_tlsname(transport, "ephemeral"); + + return; +failure: + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +isc_result_t +named_transports_fromconfig(const cfg_obj_t *config, const cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_transport_list_t **listp) { + isc_result_t result; + dns_transport_list_t *list = dns_transport_list_new(mctx); + + REQUIRE(listp != NULL && *listp == NULL); + + transport_list_add_ephemeral(list); + + if (config != NULL) { + result = transport_list_fromconfig(config, list); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + if (vconfig != NULL) { + config = cfg_tuple_get(vconfig, "options"); + transport_list_fromconfig(config, list); + } + + *listp = list; + return (ISC_R_SUCCESS); +failure: + dns_transport_list_detach(&list); + return (result); +} diff --git a/bin/named/tsigconf.c b/bin/named/tsigconf.c new file mode 100644 index 0000000..6d596ab --- /dev/null +++ b/bin/named/tsigconf.c @@ -0,0 +1,181 @@ +/* + * 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 <isc/base64.h> +#include <isc/buffer.h> +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/tsig.h> + +#include <isccfg/cfg.h> + +#include <named/config.h> +#include <named/log.h> +#include <named/tsigconf.h> + +static isc_result_t +add_initial_keys(const cfg_obj_t *list, dns_tsig_keyring_t *ring, + isc_mem_t *mctx) { + dns_tsigkey_t *tsigkey = NULL; + const cfg_listelt_t *element; + const cfg_obj_t *key = NULL; + const char *keyid = NULL; + unsigned char *secret = NULL; + int secretalloc = 0; + int secretlen = 0; + isc_result_t ret; + isc_stdtime_t now; + uint16_t bits; + + for (element = cfg_list_first(list); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *algobj = NULL; + const cfg_obj_t *secretobj = NULL; + dns_name_t keyname; + const dns_name_t *alg; + const char *algstr; + char keynamedata[1024]; + isc_buffer_t keynamesrc, keynamebuf; + const char *secretstr; + isc_buffer_t secretbuf; + + key = cfg_listelt_value(element); + keyid = cfg_obj_asstring(cfg_map_getname(key)); + + algobj = NULL; + secretobj = NULL; + (void)cfg_map_get(key, "algorithm", &algobj); + (void)cfg_map_get(key, "secret", &secretobj); + INSIST(algobj != NULL && secretobj != NULL); + + /* + * Create the key name. + */ + dns_name_init(&keyname, NULL); + isc_buffer_constinit(&keynamesrc, keyid, strlen(keyid)); + isc_buffer_add(&keynamesrc, strlen(keyid)); + isc_buffer_init(&keynamebuf, keynamedata, sizeof(keynamedata)); + ret = dns_name_fromtext(&keyname, &keynamesrc, dns_rootname, + DNS_NAME_DOWNCASE, &keynamebuf); + if (ret != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Create the algorithm. + */ + algstr = cfg_obj_asstring(algobj); + if (named_config_getkeyalgorithm(algstr, &alg, &bits) != + ISC_R_SUCCESS) + { + cfg_obj_log(algobj, named_g_lctx, ISC_LOG_ERROR, + "key '%s': has a " + "unsupported algorithm '%s'", + keyid, algstr); + ret = DNS_R_BADALG; + goto failure; + } + + secretstr = cfg_obj_asstring(secretobj); + secretalloc = secretlen = strlen(secretstr) * 3 / 4; + secret = isc_mem_get(mctx, secretlen); + isc_buffer_init(&secretbuf, secret, secretlen); + ret = isc_base64_decodestring(secretstr, &secretbuf); + if (ret != ISC_R_SUCCESS) { + goto failure; + } + secretlen = isc_buffer_usedlength(&secretbuf); + + isc_stdtime_get(&now); + ret = dns_tsigkey_create(&keyname, alg, secret, secretlen, + false, NULL, now, now, mctx, ring, + &tsigkey); + isc_mem_put(mctx, secret, secretalloc); + secret = NULL; + if (ret != ISC_R_SUCCESS) { + goto failure; + } + /* + * Set digest bits. + */ + dst_key_setbits(tsigkey->key, bits); + dns_tsigkey_detach(&tsigkey); + } + + return (ISC_R_SUCCESS); + +failure: + cfg_obj_log(key, named_g_lctx, ISC_LOG_ERROR, + "configuring key '%s': %s", keyid, isc_result_totext(ret)); + + if (secret != NULL) { + isc_mem_put(mctx, secret, secretalloc); + } + return (ret); +} + +isc_result_t +named_tsigkeyring_fromconfig(const cfg_obj_t *config, const cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_tsig_keyring_t **ringp) { + const cfg_obj_t *maps[3]; + const cfg_obj_t *keylist; + dns_tsig_keyring_t *ring = NULL; + isc_result_t result; + int i; + + REQUIRE(ringp != NULL && *ringp == NULL); + + i = 0; + if (config != NULL) { + maps[i++] = config; + } + if (vconfig != NULL) { + maps[i++] = cfg_tuple_get(vconfig, "options"); + } + maps[i] = NULL; + + result = dns_tsigkeyring_create(mctx, &ring); + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (i = 0;; i++) { + if (maps[i] == NULL) { + break; + } + keylist = NULL; + result = cfg_map_get(maps[i], "key", &keylist); + if (result != ISC_R_SUCCESS) { + continue; + } + result = add_initial_keys(keylist, ring, mctx); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + *ringp = ring; + return (ISC_R_SUCCESS); + +failure: + dns_tsigkeyring_detach(&ring); + return (result); +} diff --git a/bin/named/xsl_p.h b/bin/named/xsl_p.h new file mode 100644 index 0000000..5623534 --- /dev/null +++ b/bin/named/xsl_p.h @@ -0,0 +1,16 @@ +/* + * 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 + +extern const char xslmsg[]; diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c new file mode 100644 index 0000000..44c2242 --- /dev/null +++ b/bin/named/zoneconf.c @@ -0,0 +1,2114 @@ +/* + * 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 <isc/buffer.h> +#include <isc/file.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/stats.h> +#include <isc/string.h> /* Required for HP/UX (and others?) */ +#include <isc/util.h> + +#include <dns/acl.h> +#include <dns/db.h> +#include <dns/fixedname.h> +#include <dns/ipkeylist.h> +#include <dns/journal.h> +#include <dns/kasp.h> +#include <dns/log.h> +#include <dns/masterdump.h> +#include <dns/name.h> +#include <dns/nsec3.h> +#include <dns/rdata.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatatype.h> +#include <dns/sdlz.h> +#include <dns/ssu.h> +#include <dns/stats.h> +#include <dns/tsig.h> +#include <dns/view.h> +#include <dns/zone.h> + +#include <ns/client.h> + +#include <named/config.h> +#include <named/globals.h> +#include <named/log.h> +#include <named/server.h> +#include <named/zoneconf.h> + +/* ACLs associated with zone */ +typedef enum { + allow_notify, + allow_query, + allow_query_on, + allow_transfer, + allow_update, + allow_update_forwarding +} acl_type_t; + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +/*% + * Convenience function for configuring a single zone ACL. + */ +static isc_result_t +configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, + const cfg_obj_t *config, acl_type_t acltype, + cfg_aclconfctx_t *actx, dns_zone_t *zone, + void (*setzacl)(dns_zone_t *, dns_acl_t *), + void (*clearzacl)(dns_zone_t *)) { + isc_result_t result; + const cfg_obj_t *maps[5] = { NULL, NULL, NULL, NULL, NULL }; + const cfg_obj_t *aclobj = NULL; + int i = 0; + dns_acl_t **aclp = NULL, *acl = NULL; + const char *aclname; + dns_view_t *view; + + view = dns_zone_getview(zone); + + switch (acltype) { + case allow_notify: + if (view != NULL) { + aclp = &view->notifyacl; + } + aclname = "allow-notify"; + break; + case allow_query: + if (view != NULL) { + aclp = &view->queryacl; + } + aclname = "allow-query"; + break; + case allow_query_on: + if (view != NULL) { + aclp = &view->queryonacl; + } + aclname = "allow-query-on"; + break; + case allow_transfer: + if (view != NULL) { + aclp = &view->transferacl; + } + aclname = "allow-transfer"; + break; + case allow_update: + if (view != NULL) { + aclp = &view->updateacl; + } + aclname = "allow-update"; + break; + case allow_update_forwarding: + if (view != NULL) { + aclp = &view->upfwdacl; + } + aclname = "allow-update-forwarding"; + break; + default: + UNREACHABLE(); + } + + /* First check to see if ACL is defined within the zone */ + if (zconfig != NULL) { + maps[0] = cfg_tuple_get(zconfig, "options"); + (void)named_config_get(maps, aclname, &aclobj); + if (aclobj != NULL) { + aclp = NULL; + goto parse_acl; + } + } + + /* Failing that, see if there's a default ACL already in the view */ + if (aclp != NULL && *aclp != NULL) { + (*setzacl)(zone, *aclp); + return (ISC_R_SUCCESS); + } + + /* Check for default ACLs that haven't been parsed yet */ + if (vconfig != NULL) { + const cfg_obj_t *options = cfg_tuple_get(vconfig, "options"); + if (options != NULL) { + maps[i++] = options; + } + } + if (config != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + maps[i++] = named_g_defaults; + maps[i] = NULL; + + (void)named_config_get(maps, aclname, &aclobj); + if (aclobj == NULL) { + (*clearzacl)(zone); + return (ISC_R_SUCCESS); + } + +parse_acl: + result = cfg_acl_fromconfig(aclobj, config, named_g_lctx, actx, + named_g_mctx, 0, &acl); + if (result != ISC_R_SUCCESS) { + return (result); + } + (*setzacl)(zone, acl); + + /* Set the view default now */ + if (aclp != NULL) { + dns_acl_attach(acl, aclp); + } + + dns_acl_detach(&acl); + return (ISC_R_SUCCESS); +} + +/*% + * Parse the zone update-policy statement. + */ +static isc_result_t +configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, + const char *zname) { + const cfg_obj_t *updatepolicy = NULL; + const cfg_listelt_t *element, *element2; + dns_ssutable_t *table = NULL; + isc_mem_t *mctx = dns_zone_getmctx(zone); + bool autoddns = false; + isc_result_t result = ISC_R_SUCCESS; + + (void)cfg_map_get(zconfig, "update-policy", &updatepolicy); + + if (updatepolicy == NULL) { + dns_zone_setssutable(zone, NULL); + return (ISC_R_SUCCESS); + } + + if (cfg_obj_isstring(updatepolicy) && + strcmp("local", cfg_obj_asstring(updatepolicy)) == 0) + { + autoddns = true; + updatepolicy = NULL; + } + + dns_ssutable_create(mctx, &table); + + for (element = cfg_list_first(updatepolicy); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *stmt = cfg_listelt_value(element); + const cfg_obj_t *mode = cfg_tuple_get(stmt, "mode"); + const cfg_obj_t *identity = cfg_tuple_get(stmt, "identity"); + const cfg_obj_t *matchtype = cfg_tuple_get(stmt, "matchtype"); + const cfg_obj_t *dname = cfg_tuple_get(stmt, "name"); + const cfg_obj_t *typelist = cfg_tuple_get(stmt, "types"); + const char *str; + bool grant = false; + bool usezone = false; + dns_ssumatchtype_t mtype = dns_ssumatchtype_name; + dns_fixedname_t fname, fident; + isc_buffer_t b; + dns_ssuruletype_t *types; + unsigned int i, n; + + str = cfg_obj_asstring(mode); + if (strcasecmp(str, "grant") == 0) { + grant = true; + } else if (strcasecmp(str, "deny") == 0) { + grant = false; + } else { + UNREACHABLE(); + } + + str = cfg_obj_asstring(matchtype); + CHECK(dns_ssu_mtypefromstring(str, &mtype)); + if (mtype == dns_ssumatchtype_subdomain && + strcasecmp(str, "zonesub") == 0) + { + usezone = true; + } + + dns_fixedname_init(&fident); + str = cfg_obj_asstring(identity); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(dns_fixedname_name(&fident), &b, + dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, named_g_lctx, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + goto cleanup; + } + + dns_fixedname_init(&fname); + if (usezone) { + dns_name_copy(dns_zone_getorigin(zone), + dns_fixedname_name(&fname)); + } else { + str = cfg_obj_asstring(dname); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(dns_fixedname_name(&fname), + &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, named_g_lctx, + ISC_LOG_ERROR, + "'%s' is not a valid name", str); + goto cleanup; + } + } + + n = named_config_listcount(typelist); + if (n == 0) { + types = NULL; + } else { + types = isc_mem_get(mctx, n * sizeof(*types)); + } + + i = 0; + for (element2 = cfg_list_first(typelist); element2 != NULL; + element2 = cfg_list_next(element2)) + { + const cfg_obj_t *typeobj; + const char *bracket; + isc_textregion_t r; + unsigned long max = 0; + + INSIST(i < n); + + typeobj = cfg_listelt_value(element2); + str = cfg_obj_asstring(typeobj); + DE_CONST(str, r.base); + + bracket = strchr(str, '(' /*)*/); + if (bracket != NULL) { + char *end = NULL; + r.length = bracket - str; + max = strtoul(bracket + 1, &end, 10); + if (max > 0xffff || end[0] != /*(*/ ')' || + end[1] != 0) + { + cfg_obj_log(identity, named_g_lctx, + ISC_LOG_ERROR, + "'%s' is not a valid count", + bracket); + isc_mem_put(mctx, types, + n * sizeof(*types)); + goto cleanup; + } + } else { + r.length = strlen(str); + } + types[i].max = max; + + result = dns_rdatatype_fromtext(&types[i++].type, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, named_g_lctx, + ISC_LOG_ERROR, + "'%.*s' is not a valid type", + (int)r.length, str); + isc_mem_put(mctx, types, n * sizeof(*types)); + goto cleanup; + } + } + INSIST(i == n); + + dns_ssutable_addrule(table, grant, dns_fixedname_name(&fident), + mtype, dns_fixedname_name(&fname), n, + types); + if (types != NULL) { + isc_mem_put(mctx, types, n * sizeof(*types)); + } + } + + /* + * If "update-policy local;" and a session key exists, + * then use the default policy, which is equivalent to: + * update-policy { grant <session-keyname> zonesub any; }; + */ + if (autoddns) { + dns_ssuruletype_t any = { dns_rdatatype_any, 0 }; + + if (named_g_server->session_keyname == NULL) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed to enable auto DDNS policy " + "for zone %s: session key not found", + zname); + result = ISC_R_NOTFOUND; + goto cleanup; + } + + dns_ssutable_addrule(table, true, + named_g_server->session_keyname, + dns_ssumatchtype_local, + dns_zone_getorigin(zone), 1, &any); + } + + dns_zone_setssutable(zone, table); + +cleanup: + dns_ssutable_detach(&table); + return (result); +} + +/* + * This is the TTL used for internally generated RRsets for static-stub zones. + * The value doesn't matter because the mapping is static, but needs to be + * defined for the sake of implementation. + */ +#define STATICSTUB_SERVER_TTL 86400 + +/*% + * Configure an apex NS with glues for a static-stub zone. + * For example, for the zone named "example.com", the following RRs will be + * added to the zone DB: + * example.com. NS example.com. + * example.com. A 192.0.2.1 + * example.com. AAAA 2001:db8::1 + */ +static isc_result_t +configure_staticstub_serveraddrs(const cfg_obj_t *zconfig, dns_zone_t *zone, + dns_rdatalist_t *rdatalist_ns, + dns_rdatalist_t *rdatalist_a, + dns_rdatalist_t *rdatalist_aaaa) { + const cfg_listelt_t *element; + isc_mem_t *mctx = dns_zone_getmctx(zone); + isc_region_t region, sregion; + dns_rdata_t *rdata; + isc_result_t result = ISC_R_SUCCESS; + + for (element = cfg_list_first(zconfig); element != NULL; + element = cfg_list_next(element)) + { + const isc_sockaddr_t *sa; + isc_netaddr_t na; + const cfg_obj_t *address = cfg_listelt_value(element); + dns_rdatalist_t *rdatalist; + + sa = cfg_obj_assockaddr(address); + if (isc_sockaddr_getport(sa) != 0) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "port is not configurable for " + "static stub server-addresses"); + return (ISC_R_FAILURE); + } + isc_netaddr_fromsockaddr(&na, sa); + if (isc_netaddr_getzone(&na) != 0) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "scoped address is not allowed " + "for static stub " + "server-addresses"); + return (ISC_R_FAILURE); + } + + switch (na.family) { + case AF_INET: + region.length = sizeof(na.type.in); + rdatalist = rdatalist_a; + break; + default: + INSIST(na.family == AF_INET6); + region.length = sizeof(na.type.in6); + rdatalist = rdatalist_aaaa; + break; + } + + rdata = isc_mem_get(mctx, sizeof(*rdata) + region.length); + region.base = (unsigned char *)(rdata + 1); + memmove(region.base, &na.type, region.length); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, dns_zone_getclass(zone), + rdatalist->type, ®ion); + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + } + + /* + * If no address is specified (unlikely in this context, but possible), + * there's nothing to do anymore. + */ + if (ISC_LIST_EMPTY(rdatalist_a->rdata) && + ISC_LIST_EMPTY(rdatalist_aaaa->rdata)) + { + return (ISC_R_SUCCESS); + } + + /* Add to the list an apex NS with the ns name being the origin name */ + dns_name_toregion(dns_zone_getorigin(zone), &sregion); + rdata = isc_mem_get(mctx, sizeof(*rdata) + sregion.length); + region.length = sregion.length; + region.base = (unsigned char *)(rdata + 1); + memmove(region.base, sregion.base, region.length); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, dns_zone_getclass(zone), dns_rdatatype_ns, + ®ion); + ISC_LIST_APPEND(rdatalist_ns->rdata, rdata, link); + + return (result); +} + +/*% + * Configure an apex NS with an out-of-zone NS names for a static-stub zone. + * For example, for the zone named "example.com", something like the following + * RRs will be added to the zone DB: + * example.com. NS ns.example.net. + */ +static isc_result_t +configure_staticstub_servernames(const cfg_obj_t *zconfig, dns_zone_t *zone, + dns_rdatalist_t *rdatalist, + const char *zname) { + const cfg_listelt_t *element; + isc_mem_t *mctx = dns_zone_getmctx(zone); + dns_rdata_t *rdata; + isc_region_t sregion, region; + isc_result_t result = ISC_R_SUCCESS; + + for (element = cfg_list_first(zconfig); element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *obj; + const char *str; + dns_fixedname_t fixed_name; + dns_name_t *nsname; + isc_buffer_t b; + + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + + nsname = dns_fixedname_initname(&fixed_name); + + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(nsname, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "server-name '%s' is not a valid " + "name", + str); + return (result); + } + if (dns_name_issubdomain(nsname, dns_zone_getorigin(zone))) { + cfg_obj_log(zconfig, named_g_lctx, ISC_LOG_ERROR, + "server-name '%s' must not be a " + "subdomain of zone name '%s'", + str, zname); + return (ISC_R_FAILURE); + } + + dns_name_toregion(nsname, &sregion); + rdata = isc_mem_get(mctx, sizeof(*rdata) + sregion.length); + region.length = sregion.length; + region.base = (unsigned char *)(rdata + 1); + memmove(region.base, sregion.base, region.length); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, dns_zone_getclass(zone), + dns_rdatatype_ns, ®ion); + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + } + + return (result); +} + +/*% + * Configure static-stub zone. + */ +static isc_result_t +configure_staticstub(const cfg_obj_t *zconfig, dns_zone_t *zone, + const char *zname, const char *dbtype) { + int i = 0; + const cfg_obj_t *obj; + isc_mem_t *mctx = dns_zone_getmctx(zone); + dns_db_t *db = NULL; + dns_dbversion_t *dbversion = NULL; + dns_dbnode_t *apexnode = NULL; + dns_name_t apexname; + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist_ns, rdatalist_a, rdatalist_aaaa; + dns_rdatalist_t *rdatalists[] = { &rdatalist_ns, &rdatalist_a, + &rdatalist_aaaa, NULL }; + dns_rdata_t *rdata; + isc_region_t region; + + /* Create the DB beforehand */ + RETERR(dns_db_create(mctx, dbtype, dns_zone_getorigin(zone), + dns_dbtype_stub, dns_zone_getclass(zone), 0, NULL, + &db)); + + dns_rdataset_init(&rdataset); + + dns_rdatalist_init(&rdatalist_ns); + rdatalist_ns.rdclass = dns_zone_getclass(zone); + rdatalist_ns.type = dns_rdatatype_ns; + rdatalist_ns.ttl = STATICSTUB_SERVER_TTL; + + dns_rdatalist_init(&rdatalist_a); + rdatalist_a.rdclass = dns_zone_getclass(zone); + rdatalist_a.type = dns_rdatatype_a; + rdatalist_a.ttl = STATICSTUB_SERVER_TTL; + + dns_rdatalist_init(&rdatalist_aaaa); + rdatalist_aaaa.rdclass = dns_zone_getclass(zone); + rdatalist_aaaa.type = dns_rdatatype_aaaa; + rdatalist_aaaa.ttl = STATICSTUB_SERVER_TTL; + + /* Prepare zone RRs from the configuration */ + obj = NULL; + result = cfg_map_get(zconfig, "server-addresses", &obj); + if (result == ISC_R_SUCCESS) { + INSIST(obj != NULL); + CHECK(configure_staticstub_serveraddrs(obj, zone, &rdatalist_ns, + &rdatalist_a, + &rdatalist_aaaa)); + } + + obj = NULL; + result = cfg_map_get(zconfig, "server-names", &obj); + if (result == ISC_R_SUCCESS) { + INSIST(obj != NULL); + CHECK(configure_staticstub_servernames(obj, zone, &rdatalist_ns, + zname)); + } + + /* + * Sanity check: there should be at least one NS RR at the zone apex + * to trigger delegation. + */ + if (ISC_LIST_EMPTY(rdatalist_ns.rdata)) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "No NS record is configured for a " + "static-stub zone '%s'", + zname); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Now add NS and glue A/AAAA RRsets to the zone DB. + * First open a new version for the add operation and get a pointer + * to the apex node (all RRs are of the apex name). + */ + CHECK(dns_db_newversion(db, &dbversion)); + + dns_name_init(&apexname, NULL); + dns_name_clone(dns_zone_getorigin(zone), &apexname); + CHECK(dns_db_findnode(db, &apexname, false, &apexnode)); + + /* Add NS RRset */ + RUNTIME_CHECK(dns_rdatalist_tordataset(&rdatalist_ns, &rdataset) == + ISC_R_SUCCESS); + CHECK(dns_db_addrdataset(db, apexnode, dbversion, 0, &rdataset, 0, + NULL)); + dns_rdataset_disassociate(&rdataset); + + /* Add glue A RRset, if any */ + if (!ISC_LIST_EMPTY(rdatalist_a.rdata)) { + RUNTIME_CHECK( + dns_rdatalist_tordataset(&rdatalist_a, &rdataset) == + ISC_R_SUCCESS); + CHECK(dns_db_addrdataset(db, apexnode, dbversion, 0, &rdataset, + 0, NULL)); + dns_rdataset_disassociate(&rdataset); + } + + /* Add glue AAAA RRset, if any */ + if (!ISC_LIST_EMPTY(rdatalist_aaaa.rdata)) { + RUNTIME_CHECK( + dns_rdatalist_tordataset(&rdatalist_aaaa, &rdataset) == + ISC_R_SUCCESS); + CHECK(dns_db_addrdataset(db, apexnode, dbversion, 0, &rdataset, + 0, NULL)); + dns_rdataset_disassociate(&rdataset); + } + + dns_db_closeversion(db, &dbversion, true); + dns_zone_setdb(zone, db); + + result = ISC_R_SUCCESS; + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (apexnode != NULL) { + dns_db_detachnode(db, &apexnode); + } + if (dbversion != NULL) { + dns_db_closeversion(db, &dbversion, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + for (i = 0; rdatalists[i] != NULL; i++) { + while ((rdata = ISC_LIST_HEAD(rdatalists[i]->rdata)) != NULL) { + ISC_LIST_UNLINK(rdatalists[i]->rdata, rdata, link); + dns_rdata_toregion(rdata, ®ion); + isc_mem_put(mctx, rdata, + sizeof(*rdata) + region.length); + } + } + + INSIST(dbversion == NULL); + + return (result); +} + +/*% + * Convert a config file zone type into a server zone type. + */ +static dns_zonetype_t +zonetype_fromconfig(const cfg_obj_t *map) { + const cfg_obj_t *obj = NULL; + isc_result_t result; + + result = cfg_map_get(map, "type", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + return (named_config_getzonetype(obj)); +} + +/*% + * Helper function for strtoargv(). Pardon the gratuitous recursion. + */ +static isc_result_t +strtoargvsub(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp, + unsigned int n) { + isc_result_t result; + + /* Discard leading whitespace. */ + while (*s == ' ' || *s == '\t') { + s++; + } + + if (*s == '\0') { + /* We have reached the end of the string. */ + *argcp = n; + *argvp = isc_mem_get(mctx, n * sizeof(char *)); + } else { + char *p = s; + while (*p != ' ' && *p != '\t' && *p != '\0') { + p++; + } + if (*p != '\0') { + *p++ = '\0'; + } + + result = strtoargvsub(mctx, p, argcp, argvp, n + 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + (*argvp)[n] = s; + } + return (ISC_R_SUCCESS); +} + +/*% + * Tokenize the string "s" into whitespace-separated words, + * return the number of words in '*argcp' and an array + * of pointers to the words in '*argvp'. The caller + * must free the array using isc_mem_put(). The string + * is modified in-place. + */ +static isc_result_t +strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) { + return (strtoargvsub(mctx, s, argcp, argvp, 0)); +} + +static const char *const primary_synonyms[] = { "primary", "master", NULL }; + +static const char *const secondary_synonyms[] = { "secondary", "slave", NULL }; + +static void +checknames(dns_zonetype_t ztype, const cfg_obj_t **maps, + const cfg_obj_t **objp) { + isc_result_t result; + + switch (ztype) { + case dns_zone_secondary: + case dns_zone_mirror: + result = named_checknames_get(maps, secondary_synonyms, objp); + break; + case dns_zone_primary: + result = named_checknames_get(maps, primary_synonyms, objp); + break; + default: + UNREACHABLE(); + } + + INSIST(result == ISC_R_SUCCESS && objp != NULL && *objp != NULL); +} + +/* + * Callback to see if a non-recursive query coming from 'srcaddr' to + * 'destaddr', with optional key 'mykey' for class 'rdclass' would be + * delivered to 'myview'. + * + * We run this unlocked as both the view list and the interface list + * are updated when the appropriate task has exclusivity. + */ +static bool +isself(dns_view_t *myview, dns_tsigkey_t *mykey, const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *dstaddr, dns_rdataclass_t rdclass, void *arg) { + dns_aclenv_t *env = NULL; + dns_view_t *view = NULL; + dns_tsigkey_t *key = NULL; + isc_netaddr_t netsrc; + isc_netaddr_t netdst; + + UNUSED(arg); + + /* interfacemgr can be destroyed only in exclusive mode. */ + if (named_g_server->interfacemgr == NULL) { + return (true); + } + + if (!ns_interfacemgr_listeningon(named_g_server->interfacemgr, dstaddr)) + { + return (false); + } + + isc_netaddr_fromsockaddr(&netsrc, srcaddr); + isc_netaddr_fromsockaddr(&netdst, dstaddr); + env = ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + + for (view = ISC_LIST_HEAD(named_g_server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + const dns_name_t *tsig = NULL; + + if (view->matchrecursiveonly) { + continue; + } + + if (rdclass != view->rdclass) { + continue; + } + + if (mykey != NULL) { + bool match; + isc_result_t result; + + result = dns_view_gettsig(view, &mykey->name, &key); + if (result != ISC_R_SUCCESS) { + continue; + } + match = dst_key_compare(mykey->key, key->key); + dns_tsigkey_detach(&key); + if (!match) { + continue; + } + tsig = dns_tsigkey_identity(mykey); + } + + if (dns_acl_allowed(&netsrc, tsig, view->matchclients, env) && + dns_acl_allowed(&netdst, tsig, view->matchdestinations, + env)) + { + break; + } + } + return (view == myview); +} + +/*% + * For mirror zones, change "notify yes;" to "notify explicit;", informing the + * user only if "notify" was explicitly configured rather than inherited from + * default configuration. + */ +static dns_notifytype_t +process_notifytype(dns_notifytype_t ntype, dns_zonetype_t ztype, + const char *zname, const cfg_obj_t **maps) { + const cfg_obj_t *obj = NULL; + + /* + * Return the original setting if this is not a mirror zone or if the + * zone is configured with something else than "notify yes;". + */ + if (ztype != dns_zone_mirror || ntype != dns_notifytype_yes) { + return (ntype); + } + + /* + * Only log a message if "notify" was set in the configuration + * hierarchy supplied in 'maps'. + */ + if (named_config_get(maps, "notify", &obj) == ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_INFO, + "'notify explicit;' will be used for mirror zone " + "'%s'", + zname); + } + + return (dns_notifytype_explicit); +} + +isc_result_t +named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, + const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, + dns_kasplist_t *kasplist, dns_zone_t *zone, + dns_zone_t *raw) { + isc_result_t result; + const char *zname; + dns_rdataclass_t zclass; + dns_rdataclass_t vclass; + const cfg_obj_t *maps[5]; + const cfg_obj_t *nodefault[4]; + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *options = NULL; + const cfg_obj_t *obj; + const char *filename = NULL; + const char *kaspname = NULL; + const char *dupcheck; + dns_notifytype_t notifytype = dns_notifytype_yes; + uint32_t count; + unsigned int dbargc; + char **dbargv; + static char default_dbtype[] = "rbt"; + static char dlz_dbtype[] = "dlz"; + char *cpval = default_dbtype; + isc_mem_t *mctx = dns_zone_getmctx(zone); + dns_dialuptype_t dialup = dns_dialuptype_no; + dns_zonetype_t ztype; + int i; + int32_t journal_size; + bool multi; + bool alt; + dns_view_t *view = NULL; + dns_kasp_t *kasp = NULL; + bool check = false, fail = false; + bool warn = false, ignore = false; + bool ixfrdiff; + bool use_kasp = false; + dns_masterformat_t masterformat; + const dns_master_style_t *masterstyle = &dns_master_style_default; + isc_stats_t *zoneqrystats; + dns_stats_t *rcvquerystats; + dns_stats_t *dnssecsignstats; + dns_zonestat_level_t statlevel = dns_zonestat_none; + int seconds; + dns_ttl_t maxttl = 0; /* unlimited */ + dns_zone_t *mayberaw = (raw != NULL) ? raw : zone; + bool transferinsecs = ns_server_getoption(named_g_server->sctx, + NS_SERVER_TRANSFERINSECS); + + i = 0; + if (zconfig != NULL) { + zoptions = cfg_tuple_get(zconfig, "options"); + nodefault[i] = maps[i] = zoptions; + i++; + } + if (vconfig != NULL) { + nodefault[i] = maps[i] = cfg_tuple_get(vconfig, "options"); + i++; + } + if (config != NULL) { + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + nodefault[i] = maps[i] = options; + i++; + } + } + nodefault[i] = NULL; + maps[i++] = named_g_defaults; + maps[i] = NULL; + + if (vconfig != NULL) { + CHECK(named_config_getclass(cfg_tuple_get(vconfig, "class"), + dns_rdataclass_in, &vclass)); + } else { + vclass = dns_rdataclass_in; + } + + /* + * Configure values common to all zone types. + */ + + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + + CHECK(named_config_getclass(cfg_tuple_get(zconfig, "class"), vclass, + &zclass)); + dns_zone_setclass(zone, zclass); + if (raw != NULL) { + dns_zone_setclass(raw, zclass); + } + + ztype = zonetype_fromconfig(zoptions); + if (raw != NULL) { + dns_zone_settype(raw, ztype); + dns_zone_settype(zone, dns_zone_primary); + } else { + dns_zone_settype(zone, ztype); + } + + obj = NULL; + result = cfg_map_get(zoptions, "database", &obj); + if (result == ISC_R_SUCCESS) { + cpval = isc_mem_strdup(mctx, cfg_obj_asstring(obj)); + } + if (cpval == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + obj = NULL; + result = cfg_map_get(zoptions, "dlz", &obj); + if (result == ISC_R_SUCCESS) { + const char *dlzname = cfg_obj_asstring(obj); + size_t len; + + if (cpval != default_dbtype) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': both 'database' and 'dlz' " + "specified", + zname); + CHECK(ISC_R_FAILURE); + } + + len = strlen(dlzname) + 5; + cpval = isc_mem_allocate(mctx, len); + snprintf(cpval, len, "dlz %s", dlzname); + } + + result = strtoargv(mctx, cpval, &dbargc, &dbargv); + if (result != ISC_R_SUCCESS && cpval != default_dbtype) { + isc_mem_free(mctx, cpval); + CHECK(result); + } + + /* + * ANSI C is strange here. There is no logical reason why (char **) + * cannot be promoted automatically to (const char * const *) by the + * compiler w/o generating a warning. + */ + dns_zone_setdbtype(zone, dbargc, (const char *const *)dbargv); + isc_mem_put(mctx, dbargv, dbargc * sizeof(*dbargv)); + if (cpval != default_dbtype && cpval != dlz_dbtype) { + isc_mem_free(mctx, cpval); + } + + obj = NULL; + result = cfg_map_get(zoptions, "file", &obj); + if (result == ISC_R_SUCCESS) { + filename = cfg_obj_asstring(obj); + } + + /* + * Unless we're using some alternative database, a primary zone + * will be needing a master file. + */ + if (ztype == dns_zone_primary && cpval == default_dbtype && + filename == NULL) + { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': 'file' not specified", zname); + CHECK(ISC_R_FAILURE); + } + + if (ztype == dns_zone_secondary || ztype == dns_zone_mirror) { + masterformat = dns_masterformat_raw; + } else { + masterformat = dns_masterformat_text; + } + obj = NULL; + result = named_config_get(maps, "masterfile-format", &obj); + if (result == ISC_R_SUCCESS) { + const char *masterformatstr = cfg_obj_asstring(obj); + + if (strcasecmp(masterformatstr, "text") == 0) { + masterformat = dns_masterformat_text; + } else if (strcasecmp(masterformatstr, "raw") == 0) { + masterformat = dns_masterformat_raw; + } else { + UNREACHABLE(); + } + } + + obj = NULL; + result = named_config_get(maps, "masterfile-style", &obj); + if (result == ISC_R_SUCCESS) { + const char *masterstylestr = cfg_obj_asstring(obj); + + if (masterformat != dns_masterformat_text) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "zone '%s': 'masterfile-style' " + "can only be used with " + "'masterfile-format text'", + zname); + CHECK(ISC_R_FAILURE); + } + + if (strcasecmp(masterstylestr, "full") == 0) { + masterstyle = &dns_master_style_full; + } else if (strcasecmp(masterstylestr, "relative") == 0) { + masterstyle = &dns_master_style_default; + } else { + UNREACHABLE(); + } + } + + obj = NULL; + result = named_config_get(maps, "max-records", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxrecords(mayberaw, cfg_obj_asuint32(obj)); + if (zone != mayberaw) { + dns_zone_setmaxrecords(zone, 0); + } + + if (raw != NULL && filename != NULL) { +#define SIGNED ".signed" + size_t signedlen = strlen(filename) + sizeof(SIGNED); + char *signedname; + + CHECK(dns_zone_setfile(raw, filename, masterformat, + masterstyle)); + signedname = isc_mem_get(mctx, signedlen); + + (void)snprintf(signedname, signedlen, "%s" SIGNED, filename); + result = dns_zone_setfile(zone, signedname, + dns_masterformat_raw, NULL); + isc_mem_put(mctx, signedname, signedlen); + CHECK(result); + } else { + CHECK(dns_zone_setfile(zone, filename, masterformat, + masterstyle)); + } + + obj = NULL; + result = cfg_map_get(zoptions, "journal", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(dns_zone_setjournal(mayberaw, cfg_obj_asstring(obj))); + } + + /* + * Notify messages are processed by the raw zone if it exists. + */ + if (ztype == dns_zone_secondary || ztype == dns_zone_mirror) { + CHECK(configure_zone_acl(zconfig, vconfig, config, allow_notify, + ac, mayberaw, dns_zone_setnotifyacl, + dns_zone_clearnotifyacl)); + } + + /* + * XXXAG This probably does not make sense for stubs. + */ + CHECK(configure_zone_acl(zconfig, vconfig, config, allow_query, ac, + zone, dns_zone_setqueryacl, + dns_zone_clearqueryacl)); + + CHECK(configure_zone_acl(zconfig, vconfig, config, allow_query_on, ac, + zone, dns_zone_setqueryonacl, + dns_zone_clearqueryonacl)); + + obj = NULL; + result = named_config_get(maps, "dialup", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) { + dialup = dns_dialuptype_yes; + } else { + dialup = dns_dialuptype_no; + } + } else { + const char *dialupstr = cfg_obj_asstring(obj); + if (strcasecmp(dialupstr, "notify") == 0) { + dialup = dns_dialuptype_notify; + } else if (strcasecmp(dialupstr, "notify-passive") == 0) { + dialup = dns_dialuptype_notifypassive; + } else if (strcasecmp(dialupstr, "refresh") == 0) { + dialup = dns_dialuptype_refresh; + } else if (strcasecmp(dialupstr, "passive") == 0) { + dialup = dns_dialuptype_passive; + } else { + UNREACHABLE(); + } + } + if (raw != NULL) { + dns_zone_setdialup(raw, dialup); + } + dns_zone_setdialup(zone, dialup); + + obj = NULL; + result = named_config_get(maps, "zone-statistics", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) { + statlevel = dns_zonestat_full; + } else { + statlevel = dns_zonestat_none; + } + } else { + const char *levelstr = cfg_obj_asstring(obj); + if (strcasecmp(levelstr, "full") == 0) { + statlevel = dns_zonestat_full; + } else if (strcasecmp(levelstr, "terse") == 0) { + statlevel = dns_zonestat_terse; + } else if (strcasecmp(levelstr, "none") == 0) { + statlevel = dns_zonestat_none; + } else { + UNREACHABLE(); + } + } + dns_zone_setstatlevel(zone, statlevel); + + zoneqrystats = NULL; + rcvquerystats = NULL; + dnssecsignstats = NULL; + if (statlevel == dns_zonestat_full) { + CHECK(isc_stats_create(mctx, &zoneqrystats, + ns_statscounter_max)); + CHECK(dns_rdatatypestats_create(mctx, &rcvquerystats)); + CHECK(dns_dnssecsignstats_create(mctx, &dnssecsignstats)); + } + dns_zone_setrequeststats(zone, zoneqrystats); + dns_zone_setrcvquerystats(zone, rcvquerystats); + dns_zone_setdnssecsignstats(zone, dnssecsignstats); + + if (zoneqrystats != NULL) { + isc_stats_detach(&zoneqrystats); + } + + if (rcvquerystats != NULL) { + dns_stats_detach(&rcvquerystats); + } + + if (dnssecsignstats != NULL) { + dns_stats_detach(&dnssecsignstats); + } + + /* + * Configure authoritative zone functionality. This applies + * to primary servers (type "primary") and secondaries + * acting as primaries (type "secondary"), but not to stubs. + */ + if (ztype != dns_zone_stub && ztype != dns_zone_staticstub && + ztype != dns_zone_redirect) + { + obj = NULL; + result = named_config_get(maps, "dnssec-policy", &obj); + if (result == ISC_R_SUCCESS) { + kaspname = cfg_obj_asstring(obj); + if (strcmp(kaspname, "none") != 0) { + result = dns_kasplist_find(kasplist, kaspname, + &kasp); + if (result != ISC_R_SUCCESS) { + cfg_obj_log( + obj, named_g_lctx, + ISC_LOG_ERROR, + "dnssec-policy '%s' not found ", + kaspname); + CHECK(result); + } + dns_zone_setkasp(zone, kasp); + use_kasp = true; + } + } + if (!use_kasp) { + dns_zone_setkasp(zone, NULL); + } + + obj = NULL; + result = named_config_get(maps, "notify", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) { + notifytype = dns_notifytype_yes; + } else { + notifytype = dns_notifytype_no; + } + } else { + const char *str = cfg_obj_asstring(obj); + if (strcasecmp(str, "explicit") == 0) { + notifytype = dns_notifytype_explicit; + } else if (strcasecmp(str, "master-only") == 0 || + strcasecmp(str, "primary-only") == 0) + { + notifytype = dns_notifytype_masteronly; + } else { + UNREACHABLE(); + } + } + notifytype = process_notifytype(notifytype, ztype, zname, + nodefault); + if (raw != NULL) { + dns_zone_setnotifytype(raw, dns_notifytype_no); + } + dns_zone_setnotifytype(zone, notifytype); + + obj = NULL; + result = named_config_get(maps, "also-notify", &obj); + if (result == ISC_R_SUCCESS && + (notifytype == dns_notifytype_yes || + notifytype == dns_notifytype_explicit || + (notifytype == dns_notifytype_masteronly && + ztype == dns_zone_primary))) + { + dns_ipkeylist_t ipkl; + dns_ipkeylist_init(&ipkl); + + CHECK(named_config_getipandkeylist(config, "primaries", + obj, mctx, &ipkl)); + dns_zone_setalsonotify(zone, ipkl.addrs, ipkl.keys, + ipkl.tlss, ipkl.count); + dns_ipkeylist_clear(mctx, &ipkl); + } else { + dns_zone_setalsonotify(zone, NULL, NULL, NULL, 0); + } + + obj = NULL; + result = named_config_get(maps, "parental-source", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + + CHECK(dns_zone_setparentalsrc4(zone, cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "parental-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + + CHECK(dns_zone_setparentalsrc6(zone, cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "notify-source", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "notify-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "notify-to-soa", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_NOTIFYTOSOA, + cfg_obj_asboolean(obj)); + + dns_zone_setisself(zone, isself, NULL); + + CHECK(configure_zone_acl( + zconfig, vconfig, config, allow_transfer, ac, zone, + dns_zone_setxfracl, dns_zone_clearxfracl)); + + obj = NULL; + result = named_config_get(maps, "max-transfer-time-out", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxxfrout( + zone, transferinsecs ? cfg_obj_asuint32(obj) + : cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = named_config_get(maps, "max-transfer-idle-out", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setidleout(zone, transferinsecs + ? cfg_obj_asuint32(obj) + : cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = named_config_get(maps, "max-journal-size", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (raw != NULL) { + dns_zone_setjournalsize(raw, -1); + } + dns_zone_setjournalsize(zone, -1); + if (cfg_obj_isstring(obj)) { + const char *str = cfg_obj_asstring(obj); + if (strcasecmp(str, "unlimited") == 0) { + journal_size = DNS_JOURNAL_SIZE_MAX; + } else { + INSIST(strcasecmp(str, "default") == 0); + journal_size = -1; + } + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > DNS_JOURNAL_SIZE_MAX) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "'max-journal-size " + "%" PRId64 "' " + "is too large", + value); + CHECK(ISC_R_RANGE); + } + journal_size = (uint32_t)value; + } + if (raw != NULL) { + dns_zone_setjournalsize(raw, journal_size); + } + dns_zone_setjournalsize(zone, journal_size); + + obj = NULL; + result = named_config_get(maps, "ixfr-from-differences", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isboolean(obj)) { + ixfrdiff = cfg_obj_asboolean(obj); + } else if ((strcasecmp(cfg_obj_asstring(obj), "primary") == 0 || + strcasecmp(cfg_obj_asstring(obj), "master") == 0) && + ztype == dns_zone_primary) + { + ixfrdiff = true; + } else if ((strcasecmp(cfg_obj_asstring(obj), "secondary") == + 0 || + strcasecmp(cfg_obj_asstring(obj), "slave") == 0) && + ztype == dns_zone_secondary) + { + ixfrdiff = true; + } else { + ixfrdiff = false; + } + if (raw != NULL) { + dns_zone_setoption(raw, DNS_ZONEOPT_IXFRFROMDIFFS, + true); + dns_zone_setoption(zone, DNS_ZONEOPT_IXFRFROMDIFFS, + false); + } else { + dns_zone_setoption(zone, DNS_ZONEOPT_IXFRFROMDIFFS, + ixfrdiff); + } + + obj = NULL; + result = named_config_get(maps, "max-ixfr-ratio", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isstring(obj)) { + dns_zone_setixfrratio(zone, 0); + } else { + dns_zone_setixfrratio(zone, cfg_obj_aspercentage(obj)); + } + + obj = NULL; + result = named_config_get(maps, "request-expire", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setrequestexpire(zone, cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "request-ixfr", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setrequestixfr(zone, cfg_obj_asboolean(obj)); + + obj = NULL; + checknames(ztype, maps, &obj); + INSIST(obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) { + fail = false; + check = true; + } else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) { + fail = check = true; + } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) { + fail = check = false; + } else { + UNREACHABLE(); + } + if (raw != NULL) { + dns_zone_setoption(raw, DNS_ZONEOPT_CHECKNAMES, check); + dns_zone_setoption(raw, DNS_ZONEOPT_CHECKNAMESFAIL, + fail); + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKNAMES, false); + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKNAMESFAIL, + false); + } else { + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKNAMES, check); + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKNAMESFAIL, + fail); + } + + obj = NULL; + result = named_config_get(maps, "notify-delay", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setnotifydelay(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "check-sibling", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKSIBLING, + cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "check-spf", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) { + check = true; + } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) { + check = false; + } else { + UNREACHABLE(); + } + dns_zone_setoption(zone, DNS_ZONEOPT_CHECKSPF, check); + + obj = NULL; + result = named_config_get(maps, "zero-no-soa-ttl", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setzeronosoattl(zone, cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "nsec3-test-zone", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_NSEC3TESTZONE, + cfg_obj_asboolean(obj)); + } else if (ztype == dns_zone_redirect) { + dns_zone_setnotifytype(zone, dns_notifytype_no); + + obj = NULL; + result = named_config_get(maps, "max-journal-size", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setjournalsize(zone, -1); + if (cfg_obj_isstring(obj)) { + const char *str = cfg_obj_asstring(obj); + if (strcasecmp(str, "unlimited") == 0) { + journal_size = DNS_JOURNAL_SIZE_MAX; + } else { + INSIST(strcasecmp(str, "default") == 0); + journal_size = -1; + } + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > DNS_JOURNAL_SIZE_MAX) { + cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR, + "'max-journal-size " + "%" PRId64 "' " + "is too large", + value); + CHECK(ISC_R_RANGE); + } + journal_size = (uint32_t)value; + } + dns_zone_setjournalsize(zone, journal_size); + } + + if (use_kasp) { + maxttl = dns_kasp_zonemaxttl(dns_zone_getkasp(zone), false); + } else { + obj = NULL; + result = named_config_get(maps, "max-zone-ttl", &obj); + if (result == ISC_R_SUCCESS) { + if (cfg_obj_isduration(obj)) { + maxttl = cfg_obj_asduration(obj); + } + } + } + dns_zone_setmaxttl(zone, maxttl); + if (raw != NULL) { + dns_zone_setmaxttl(raw, maxttl); + } + + /* + * Configure update-related options. These apply to + * primary servers only. + */ + if (ztype == dns_zone_primary) { + dns_acl_t *updateacl; + + CHECK(configure_zone_acl(zconfig, vconfig, config, allow_update, + ac, mayberaw, dns_zone_setupdateacl, + dns_zone_clearupdateacl)); + + updateacl = dns_zone_getupdateacl(mayberaw); + if (updateacl != NULL && dns_acl_isinsecure(updateacl)) { + isc_log_write(named_g_lctx, DNS_LOGCATEGORY_SECURITY, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "zone '%s' allows unsigned updates " + "from remote hosts, which is insecure", + zname); + } + + CHECK(configure_zone_ssutable(zoptions, mayberaw, zname)); + } + + /* + * Configure DNSSEC signing. These apply to primary zones or zones that + * use inline-signing (raw != NULL). + */ + if (ztype == dns_zone_primary || raw != NULL) { + const cfg_obj_t *validity, *resign; + bool allow = false, maint = false; + bool sigvalinsecs; + + if (use_kasp) { + if (dns_kasp_nsec3(kasp)) { + result = dns_zone_setnsec3param( + zone, 1, dns_kasp_nsec3flags(kasp), + dns_kasp_nsec3iter(kasp), + dns_kasp_nsec3saltlen(kasp), NULL, true, + false); + } else { + result = dns_zone_setnsec3param( + zone, 0, 0, 0, 0, NULL, true, false); + } + INSIST(result == ISC_R_SUCCESS); + } + + if (use_kasp) { + seconds = (uint32_t)dns_kasp_sigvalidity_dnskey(kasp); + } else { + obj = NULL; + result = named_config_get(maps, "dnskey-sig-validity", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + seconds = cfg_obj_asuint32(obj) * 86400; + } + dns_zone_setkeyvalidityinterval(zone, seconds); + + if (use_kasp) { + seconds = (uint32_t)dns_kasp_sigvalidity(kasp); + dns_zone_setsigvalidityinterval(zone, seconds); + seconds = (uint32_t)dns_kasp_sigrefresh(kasp); + dns_zone_setsigresigninginterval(zone, seconds); + } else { + obj = NULL; + result = named_config_get(maps, "sig-validity-interval", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + + sigvalinsecs = ns_server_getoption( + named_g_server->sctx, NS_SERVER_SIGVALINSECS); + validity = cfg_tuple_get(obj, "validity"); + seconds = cfg_obj_asuint32(validity); + if (!sigvalinsecs) { + seconds *= 86400; + } + dns_zone_setsigvalidityinterval(zone, seconds); + + resign = cfg_tuple_get(obj, "re-sign"); + if (cfg_obj_isvoid(resign)) { + seconds /= 4; + } else if (!sigvalinsecs) { + uint32_t r = cfg_obj_asuint32(resign); + if (seconds > 7 * 86400) { + seconds = r * 86400; + } else { + seconds = r * 3600; + } + } else { + seconds = cfg_obj_asuint32(resign); + } + dns_zone_setsigresigninginterval(zone, seconds); + } + + obj = NULL; + result = named_config_get(maps, "key-directory", &obj); + if (result == ISC_R_SUCCESS) { + filename = cfg_obj_asstring(obj); + CHECK(dns_zone_setkeydirectory(zone, filename)); + } + + obj = NULL; + result = named_config_get(maps, "sig-signing-signatures", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setsignatures(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "sig-signing-nodes", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setnodes(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "sig-signing-type", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setprivatetype(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "update-check-ksk", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_UPDATECHECKKSK, + cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ + + obj = NULL; + result = named_config_get(maps, "dnssec-dnskey-kskonly", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_DNSKEYKSKONLY, + cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ + + obj = NULL; + result = named_config_get(maps, "dnssec-loadkeys-interval", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setrefreshkeyinterval(zone, + cfg_obj_asuint32(obj))); + + obj = NULL; + result = cfg_map_get(zoptions, "auto-dnssec", &obj); + if (kasp != NULL) { + bool s2i = (strcmp(dns_kasp_getname(kasp), + "insecure") != 0); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, true); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, !s2i); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, true); + } else if (result == ISC_R_SUCCESS) { + const char *arg = cfg_obj_asstring(obj); + if (strcasecmp(arg, "allow") == 0) { + allow = true; + } else if (strcasecmp(arg, "maintain") == 0) { + allow = maint = true; + } else if (strcasecmp(arg, "off") == 0) { + /* Default */ + } else { + UNREACHABLE(); + } + dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, allow); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, false); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, maint); + } + } + + if (ztype == dns_zone_secondary || ztype == dns_zone_mirror) { + CHECK(configure_zone_acl(zconfig, vconfig, config, + allow_update_forwarding, ac, mayberaw, + dns_zone_setforwardacl, + dns_zone_clearforwardacl)); + } + + /*% + * Configure parental agents, applies to primary and secondary zones. + */ + if (ztype == dns_zone_primary || ztype == dns_zone_secondary) { + obj = NULL; + (void)cfg_map_get(zoptions, "parental-agents", &obj); + if (obj != NULL) { + dns_ipkeylist_t ipkl; + dns_ipkeylist_init(&ipkl); + CHECK(named_config_getipandkeylist( + config, "parental-agents", obj, mctx, &ipkl)); + dns_zone_setparentals(zone, ipkl.addrs, ipkl.keys, + ipkl.tlss, ipkl.count); + dns_ipkeylist_clear(mctx, &ipkl); + } else { + dns_zone_setparentals(zone, NULL, NULL, NULL, 0); + } + } + + /*% + * Configure primary zone functionality. + */ + if (ztype == dns_zone_primary) { + obj = NULL; + result = named_config_get(maps, "check-wildcard", &obj); + if (result == ISC_R_SUCCESS) { + check = cfg_obj_asboolean(obj); + } else { + check = false; + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKWILDCARD, check); + + obj = NULL; + result = named_config_get(maps, "check-dup-records", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dupcheck = cfg_obj_asstring(obj); + if (strcasecmp(dupcheck, "warn") == 0) { + fail = false; + check = true; + } else if (strcasecmp(dupcheck, "fail") == 0) { + fail = check = true; + } else if (strcasecmp(dupcheck, "ignore") == 0) { + fail = check = false; + } else { + UNREACHABLE(); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKDUPRR, check); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKDUPRRFAIL, fail); + + obj = NULL; + result = named_config_get(maps, "check-mx", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) { + fail = false; + check = true; + } else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) { + fail = check = true; + } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) { + fail = check = false; + } else { + UNREACHABLE(); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKMX, check); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKMXFAIL, fail); + + obj = NULL; + result = named_config_get(maps, "check-integrity", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_CHECKINTEGRITY, + cfg_obj_asboolean(obj)); + + obj = NULL; + result = named_config_get(maps, "check-mx-cname", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) { + warn = true; + ignore = false; + } else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) { + warn = ignore = false; + } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) { + warn = ignore = true; + } else { + UNREACHABLE(); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_WARNMXCNAME, warn); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_IGNOREMXCNAME, ignore); + + obj = NULL; + result = named_config_get(maps, "check-srv-cname", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) { + warn = true; + ignore = false; + } else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) { + warn = ignore = false; + } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) { + warn = ignore = true; + } else { + UNREACHABLE(); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_WARNSRVCNAME, warn); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_IGNORESRVCNAME, + ignore); + + obj = NULL; + result = named_config_get(maps, "dnssec-secure-to-insecure", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_SECURETOINSECURE, + cfg_obj_asboolean(obj)); + + obj = NULL; + result = cfg_map_get(zoptions, "dnssec-update-mode", &obj); + if (result == ISC_R_SUCCESS) { + const char *arg = cfg_obj_asstring(obj); + if (strcasecmp(arg, "no-resign") == 0) { + dns_zone_setkeyopt(zone, DNS_ZONEKEY_NORESIGN, + true); + } else if (strcasecmp(arg, "maintain") == 0) { + /* Default */ + } else { + UNREACHABLE(); + } + } + + obj = NULL; + result = named_config_get(maps, "serial-update-method", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (strcasecmp(cfg_obj_asstring(obj), "unixtime") == 0) { + dns_zone_setserialupdatemethod( + zone, dns_updatemethod_unixtime); + } else if (strcasecmp(cfg_obj_asstring(obj), "date") == 0) { + dns_zone_setserialupdatemethod(zone, + dns_updatemethod_date); + } else { + dns_zone_setserialupdatemethod( + zone, dns_updatemethod_increment); + } + } + + /* + * Configure secondary zone functionality. + */ + switch (ztype) { + case dns_zone_mirror: + /* + * Disable outgoing zone transfers for mirror zones unless they + * are explicitly enabled by zone configuration. + */ + obj = NULL; + (void)cfg_map_get(zoptions, "allow-transfer", &obj); + if (obj == NULL) { + dns_acl_t *none; + CHECK(dns_acl_none(mctx, &none)); + dns_zone_setxfracl(zone, none); + dns_acl_detach(&none); + } + FALLTHROUGH; + case dns_zone_secondary: + case dns_zone_stub: + case dns_zone_redirect: + count = 0; + obj = NULL; + (void)cfg_map_get(zoptions, "primaries", &obj); + if (obj == NULL) { + (void)cfg_map_get(zoptions, "masters", &obj); + } + + /* + * Use the built-in primary server list if one was not + * explicitly specified and this is a root zone mirror. + */ + if (obj == NULL && ztype == dns_zone_mirror && + dns_name_equal(dns_zone_getorigin(zone), dns_rootname)) + { + result = named_config_getremotesdef( + named_g_config, "primaries", + DEFAULT_IANA_ROOT_ZONE_PRIMARIES, &obj); + CHECK(result); + } + if (obj != NULL) { + dns_ipkeylist_t ipkl; + dns_ipkeylist_init(&ipkl); + + CHECK(named_config_getipandkeylist(config, "primaries", + obj, mctx, &ipkl)); + dns_zone_setprimaries(mayberaw, ipkl.addrs, ipkl.keys, + ipkl.tlss, ipkl.count); + count = ipkl.count; + dns_ipkeylist_clear(mctx, &ipkl); + } else { + dns_zone_setprimaries(mayberaw, NULL, NULL, NULL, 0); + } + + multi = false; + if (count > 1) { + obj = NULL; + result = named_config_get(maps, "multi-master", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + multi = cfg_obj_asboolean(obj); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_MULTIMASTER, multi); + + obj = NULL; + result = named_config_get(maps, "max-transfer-time-in", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxxfrin( + mayberaw, transferinsecs ? cfg_obj_asuint32(obj) + : cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = named_config_get(maps, "max-transfer-idle-in", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setidlein(mayberaw, + transferinsecs ? cfg_obj_asuint32(obj) + : cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = named_config_get(maps, "max-refresh-time", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxrefreshtime(mayberaw, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "min-refresh-time", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setminrefreshtime(mayberaw, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "max-retry-time", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxretrytime(mayberaw, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "min-retry-time", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setminretrytime(mayberaw, cfg_obj_asuint32(obj)); + + obj = NULL; + result = named_config_get(maps, "transfer-source", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setxfrsource4(mayberaw, + cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "transfer-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setxfrsource6(mayberaw, + cfg_obj_assockaddr(obj))); + named_add_reserved_dispatch(named_g_server, + cfg_obj_assockaddr(obj)); + + obj = NULL; + result = named_config_get(maps, "alt-transfer-source", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setaltxfrsource4(mayberaw, + cfg_obj_assockaddr(obj))); + obj = NULL; + result = named_config_get(maps, "alt-transfer-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + CHECK(dns_zone_setaltxfrsource6(mayberaw, + cfg_obj_assockaddr(obj))); + obj = NULL; + (void)named_config_get(maps, "use-alt-transfer-source", &obj); + if (obj == NULL) { + /* + * Default off when views are in use otherwise + * on for BIND 8 compatibility. + */ + view = dns_zone_getview(zone); + if (view != NULL && strcmp(view->name, "_default") == 0) + { + alt = true; + } else { + alt = false; + } + } else { + alt = cfg_obj_asboolean(obj); + } + dns_zone_setoption(mayberaw, DNS_ZONEOPT_USEALTXFRSRC, alt); + + obj = NULL; + (void)named_config_get(maps, "try-tcp-refresh", &obj); + dns_zone_setoption(mayberaw, DNS_ZONEOPT_TRYTCPREFRESH, + cfg_obj_asboolean(obj)); + break; + + case dns_zone_staticstub: + CHECK(configure_staticstub(zoptions, zone, zname, + default_dbtype)); + break; + + default: + break; + } + + result = ISC_R_SUCCESS; + +cleanup: + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + return (result); +} + +/* + * Set up a DLZ zone as writeable + */ +isc_result_t +named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone, + dns_rdataclass_t rdclass, dns_name_t *name) { + dns_db_t *db = NULL; + isc_time_t now; + isc_result_t result; + + TIME_NOW(&now); + + dns_zone_settype(zone, dns_zone_dlz); + result = dns_sdlz_setdb(dlzdatabase, rdclass, name, &db); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_zone_dlzpostload(zone, db); + dns_db_detach(&db); + return (result); +} + +bool +named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig) { + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *obj = NULL; + const char *cfilename; + const char *zfilename; + dns_zone_t *raw = NULL; + bool has_raw, inline_signing; + dns_zonetype_t ztype; + + zoptions = cfg_tuple_get(zconfig, "options"); + + /* + * We always reconfigure a static-stub zone for simplicity, assuming + * the amount of data to be loaded is small. + */ + if (zonetype_fromconfig(zoptions) == dns_zone_staticstub) { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "not reusable: staticstub"); + return (false); + } + + /* If there's a raw zone, use that for filename and type comparison */ + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + zfilename = dns_zone_getfile(raw); + ztype = dns_zone_gettype(raw); + dns_zone_detach(&raw); + has_raw = true; + } else { + zfilename = dns_zone_getfile(zone); + ztype = dns_zone_gettype(zone); + has_raw = false; + } + + inline_signing = named_zone_inlinesigning(zconfig); + if (!inline_signing && has_raw) { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "not reusable: old zone was inline-signing"); + return (false); + } else if (inline_signing && !has_raw) { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "not reusable: old zone was not inline-signing"); + return (false); + } + + if (zonetype_fromconfig(zoptions) != ztype) { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "not reusable: type mismatch"); + return (false); + } + + obj = NULL; + (void)cfg_map_get(zoptions, "file", &obj); + if (obj != NULL) { + cfilename = cfg_obj_asstring(obj); + } else { + cfilename = NULL; + } + if (!((cfilename == NULL && zfilename == NULL) || + (cfilename != NULL && zfilename != NULL && + strcmp(cfilename, zfilename) == 0))) + { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "not reusable: filename mismatch"); + return (false); + } + + return (true); +} + +bool +named_zone_inlinesigning(const cfg_obj_t *zconfig) { + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *signing = NULL; + bool inline_signing = false; + + zoptions = cfg_tuple_get(zconfig, "options"); + inline_signing = (cfg_map_get(zoptions, "inline-signing", &signing) == + ISC_R_SUCCESS && + cfg_obj_asboolean(signing)); + + return (inline_signing); +} |