diff options
Diffstat (limited to 'bin/nsupdate')
-rw-r--r-- | bin/nsupdate/Makefile.am | 34 | ||||
-rw-r--r-- | bin/nsupdate/Makefile.in | 828 | ||||
-rw-r--r-- | bin/nsupdate/nsupdate.c | 3463 | ||||
-rw-r--r-- | bin/nsupdate/nsupdate.rst | 391 |
4 files changed, 4716 insertions, 0 deletions
diff --git a/bin/nsupdate/Makefile.am b/bin/nsupdate/Makefile.am new file mode 100644 index 0000000..18c8645 --- /dev/null +++ b/bin/nsupdate/Makefile.am @@ -0,0 +1,34 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) \ + $(LIBISCCFG_CFLAGS) \ + $(LIBIRS_CFLAGS) \ + $(LIBBIND9_CFLAGS) \ + $(GSSAPI_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(READLINE_CFLAGS) + +AM_CPPFLAGS += \ + -DSESSION_KEYFILE=\"${localstatedir}/run/named/session.key\" + +LDADD += \ + $(LIBISC_LIBS) \ + $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) \ + $(LIBIRS_LIBS) \ + $(LIBBIND9_LIBS) \ + $(GSSAPI_LIBS) \ + $(KRB5_LIBS) + +if HAVE_READLINE +LDADD += \ + $(READLINE_LIBS) +endif + +bin_PROGRAMS = nsupdate + +nsupdate_SOURCES = \ + nsupdate.c \ + ../dig/readline.h diff --git a/bin/nsupdate/Makefile.in b/bin/nsupdate/Makefile.in new file mode 100644 index 0000000..ce9064d --- /dev/null +++ b/bin/nsupdate/Makefile.in @@ -0,0 +1,828 @@ +# 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_READLINE_TRUE@am__append_2 = \ +@HAVE_READLINE_TRUE@ $(READLINE_LIBS) + +bin_PROGRAMS = nsupdate$(EXEEXT) +subdir = bin/nsupdate +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)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_nsupdate_OBJECTS = nsupdate.$(OBJEXT) +nsupdate_OBJECTS = $(am_nsupdate_OBJECTS) +nsupdate_LDADD = $(LDADD) +am__DEPENDENCIES_1 = +@HAVE_READLINE_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +nsupdate_DEPENDENCIES = $(LIBISC_LIBS) $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) $(LIBIRS_LIBS) $(LIBBIND9_LIBS) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) +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)/nsupdate.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 = $(nsupdate_SOURCES) +DIST_SOURCES = $(nsupdate_SOURCES) +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 $(LIBISC_CFLAGS) $(LIBDNS_CFLAGS) \ + $(LIBISCCFG_CFLAGS) $(LIBIRS_CFLAGS) $(LIBBIND9_CFLAGS) \ + $(GSSAPI_CFLAGS) $(KRB5_CFLAGS) $(READLINE_CFLAGS) \ + -DSESSION_KEYFILE=\"${localstatedir}/run/named/session.key\" +AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1) +LDADD = $(LIBISC_LIBS) $(LIBDNS_LIBS) $(LIBISCCFG_LIBS) $(LIBIRS_LIBS) \ + $(LIBBIND9_LIBS) $(GSSAPI_LIBS) $(KRB5_LIBS) $(am__append_2) +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 + +nsupdate_SOURCES = \ + nsupdate.c \ + ../dig/readline.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bin/nsupdate/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bin/nsupdate/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-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || 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)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || 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)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_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 + +nsupdate$(EXEEXT): $(nsupdate_OBJECTS) $(nsupdate_DEPENDENCIES) $(EXTRA_nsupdate_DEPENDENCIES) + @rm -f nsupdate$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(nsupdate_OBJECTS) $(nsupdate_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nsupdate.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: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/nsupdate.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-binPROGRAMS + +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)/nsupdate.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-binPROGRAMS + +unit: unit-am + +unit-am: unit-local + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir doc-am doc-local dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS 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-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-binPROGRAMS unit-am unit-local + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c new file mode 100644 index 0000000..ae759be --- /dev/null +++ b/bin/nsupdate/nsupdate.c @@ -0,0 +1,3463 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/base64.h> +#include <isc/buffer.h> +#include <isc/commandline.h> +#include <isc/event.h> +#include <isc/file.h> +#include <isc/hash.h> +#include <isc/lex.h> +#include <isc/log.h> +#include <isc/managers.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/nonce.h> +#include <isc/parseint.h> +#include <isc/portset.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/callbacks.h> +#include <dns/dispatch.h> +#include <dns/dnssec.h> +#include <dns/events.h> +#include <dns/fixedname.h> +#include <dns/log.h> +#include <dns/masterdump.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/nsec3.h> +#include <dns/rcode.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> +#include <dns/request.h> +#include <dns/tkey.h> +#include <dns/tsig.h> + +#include <dst/dst.h> + +#include <isccfg/namedconf.h> + +#include <irs/resconf.h> + +#if HAVE_GSSAPI +#include <dst/gssapi.h> + +#if HAVE_KRB5_KRB5_H +#include <krb5/krb5.h> +#elif HAVE_KRB5_H +#include <krb5.h> +#endif + +#if HAVE_GSSAPI_GSSAPI_H +#include <gssapi/gssapi.h> +#elif HAVE_GSSAPI_H +#include <gssapi.h> +#endif + +#endif /* HAVE_GSSAPI */ + +#include <bind9/getaddresses.h> + +#include "../dig/readline.h" + +#define MAXCMD (128 * 1024) +#define MAXWIRE (64 * 1024) +#define INITTEXT (2 * 1024) +#define MAXTEXT (128 * 1024) +#define TTL_MAX 2147483647U /* Maximum signed 32 bit integer. */ + +#define DNSDEFAULTPORT 53 + +/* Number of addresses to request from bind9_getaddresses() */ +#define MAX_SERVERADDRS 4 + +static uint16_t dnsport = DNSDEFAULTPORT; + +#ifndef RESOLV_CONF +#define RESOLV_CONF "/etc/resolv.conf" +#endif /* ifndef RESOLV_CONF */ + +static bool debugging = false, ddebugging = false; +static bool memdebugging = false; +static bool have_ipv4 = false; +static bool have_ipv6 = false; +static bool is_dst_up = false; +static bool usevc = false; +static bool usegsstsig = false; +static bool use_win2k_gsstsig = false; +static bool tried_other_gsstsig = false; +static bool local_only = false; +static isc_nm_t *netmgr = NULL; +static isc_taskmgr_t *taskmgr = NULL; +static isc_task_t *global_task = NULL; +static isc_event_t *global_event = NULL; +static isc_log_t *glctx = NULL; +static isc_mem_t *gmctx = NULL; +static dns_dispatchmgr_t *dispatchmgr = NULL; +static dns_requestmgr_t *requestmgr = NULL; +static dns_dispatch_t *dispatchv4 = NULL; +static dns_dispatch_t *dispatchv6 = NULL; +static dns_message_t *updatemsg = NULL; +static dns_fixedname_t fuserzone; +static dns_fixedname_t fzname; +static dns_name_t *userzone = NULL; +static dns_name_t *zname = NULL; +static dns_name_t tmpzonename = DNS_NAME_INITEMPTY; +static dns_name_t restart_primary = DNS_NAME_INITEMPTY; +static dns_tsig_keyring_t *gssring = NULL; +static dns_tsigkey_t *tsigkey = NULL; +static dst_key_t *sig0key = NULL; +static isc_sockaddr_t *servers = NULL; +static isc_sockaddr_t *primary_servers = NULL; +static bool default_servers = true; +static int ns_inuse = 0; +static int primary_inuse = 0; +static int ns_total = 0; +static int ns_alloc = 0; +static int primary_total = 0; +static int primary_alloc = 0; +static isc_sockaddr_t *localaddr4 = NULL; +static isc_sockaddr_t *localaddr6 = NULL; +static const char *keyfile = NULL; +static char *keystr = NULL; +static bool shuttingdown = false; +static FILE *input; +static bool interactive = true; +static bool seenerror = false; +static const dns_master_style_t *style; +static int requests = 0; +static unsigned int logdebuglevel = 0; +static unsigned int timeout = 300; +static unsigned int udp_timeout = 3; +static unsigned int udp_retries = 3; +static dns_rdataclass_t defaultclass = dns_rdataclass_in; +static dns_rdataclass_t zoneclass = dns_rdataclass_none; +static isc_mutex_t answer_lock; +static dns_message_t *answer = NULL; +static uint32_t default_ttl = 0; +static bool default_ttl_set = false; +static bool checknames = true; +static const char *resolvconf = RESOLV_CONF; + +typedef struct nsu_requestinfo { + dns_message_t *msg; + isc_sockaddr_t *addr; +} nsu_requestinfo_t; + +static void +sendrequest(isc_sockaddr_t *destaddr, dns_message_t *msg, + dns_request_t **request); +static void +send_update(dns_name_t *zonename, isc_sockaddr_t *primary); + +noreturn static void +fatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +static void +debug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +static void +ddebug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +#if HAVE_GSSAPI +static dns_fixedname_t fkname; +static isc_sockaddr_t *kserver = NULL; +static char *realm = NULL; +static char servicename[DNS_NAME_FORMATSIZE]; +static dns_name_t *keyname; +typedef struct nsu_gssinfo { + dns_message_t *msg; + isc_sockaddr_t *addr; + gss_ctx_id_t context; +} nsu_gssinfo_t; + +static void +failed_gssrequest(void); +static void +start_gssrequest(dns_name_t *primary); +static void +send_gssrequest(isc_sockaddr_t *destaddr, dns_message_t *msg, + dns_request_t **request, gss_ctx_id_t context); +static void +recvgss(isc_task_t *task, isc_event_t *event); +#endif /* HAVE_GSSAPI */ + +static void +error(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +#define STATUS_MORE (uint16_t)0 +#define STATUS_SEND (uint16_t)1 +#define STATUS_QUIT (uint16_t)2 +#define STATUS_SYNTAX (uint16_t)3 + +static void +primary_from_servers(void) { + if (primary_servers != NULL && primary_servers != servers) { + isc_mem_put(gmctx, primary_servers, + primary_alloc * sizeof(isc_sockaddr_t)); + } + primary_servers = servers; + primary_total = ns_total; + primary_alloc = ns_alloc; + primary_inuse = ns_inuse; +} + +static dns_rdataclass_t +getzoneclass(void) { + if (zoneclass == dns_rdataclass_none) { + zoneclass = defaultclass; + } + return (zoneclass); +} + +static bool +setzoneclass(dns_rdataclass_t rdclass) { + if (zoneclass == dns_rdataclass_none || rdclass == dns_rdataclass_none) + { + zoneclass = rdclass; + } + if (zoneclass != rdclass) { + return (false); + } + return (true); +} + +static void +fatal(const char *format, ...) { + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(1); +} + +static void +error(const char *format, ...) { + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +} + +static void +debug(const char *format, ...) { + va_list args; + + if (debugging) { + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + } +} + +static void +ddebug(const char *format, ...) { + va_list args; + + if (ddebugging) { + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + } +} + +static void +check_result(isc_result_t result, const char *msg) { + if (result != ISC_R_SUCCESS) { + fatal("%s: %s", msg, isc_result_totext(result)); + } +} + +static char * +nsu_strsep(char **stringp, const char *delim) { + char *string = *stringp; + *stringp = NULL; + char *s; + const char *d; + char sc, dc; + + if (string == NULL) { + return (NULL); + } + + for (; *string != '\0'; string++) { + sc = *string; + for (d = delim; (dc = *d) != '\0'; d++) { + if (sc == dc) { + break; + } + } + if (dc == 0) { + break; + } + } + + for (s = string; *s != '\0'; s++) { + sc = *s; + for (d = delim; (dc = *d) != '\0'; d++) { + if (sc == dc) { + *s++ = '\0'; + *stringp = s; + return (string); + } + } + } + return (string); +} + +static void +reset_system(void) { + ddebug("reset_system()"); + /* If the update message is still around, destroy it */ + if (updatemsg != NULL) { + dns_message_reset(updatemsg, DNS_MESSAGE_INTENTRENDER); + } else { + dns_message_create(gmctx, DNS_MESSAGE_INTENTRENDER, &updatemsg); + } + updatemsg->opcode = dns_opcode_update; + if (usegsstsig) { + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } + if (gssring != NULL) { + dns_tsigkeyring_detach(&gssring); + } + tried_other_gsstsig = false; + } +} + +static bool +parse_hmac(const dns_name_t **hmac, const char *hmacstr, size_t len, + uint16_t *digestbitsp) { + uint16_t digestbits = 0; + isc_result_t result; + char buf[20]; + + REQUIRE(hmac != NULL && *hmac == NULL); + REQUIRE(hmacstr != NULL); + + if (len >= sizeof(buf)) { + error("unknown key type '%.*s'", (int)(len), hmacstr); + return (false); + } + + /* Copy len bytes and NUL terminate. */ + strlcpy(buf, hmacstr, ISC_MIN(len + 1, sizeof(buf))); + + if (strcasecmp(buf, "hmac-md5") == 0) { + *hmac = DNS_TSIG_HMACMD5_NAME; + } else if (strncasecmp(buf, "hmac-md5-", 9) == 0) { + *hmac = DNS_TSIG_HMACMD5_NAME; + result = isc_parse_uint16(&digestbits, &buf[9], 10); + if (result != ISC_R_SUCCESS || digestbits > 128) { + error("digest-bits out of range [0..128]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else if (strcasecmp(buf, "hmac-sha1") == 0) { + *hmac = DNS_TSIG_HMACSHA1_NAME; + } else if (strncasecmp(buf, "hmac-sha1-", 10) == 0) { + *hmac = DNS_TSIG_HMACSHA1_NAME; + result = isc_parse_uint16(&digestbits, &buf[10], 10); + if (result != ISC_R_SUCCESS || digestbits > 160) { + error("digest-bits out of range [0..160]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else if (strcasecmp(buf, "hmac-sha224") == 0) { + *hmac = DNS_TSIG_HMACSHA224_NAME; + } else if (strncasecmp(buf, "hmac-sha224-", 12) == 0) { + *hmac = DNS_TSIG_HMACSHA224_NAME; + result = isc_parse_uint16(&digestbits, &buf[12], 10); + if (result != ISC_R_SUCCESS || digestbits > 224) { + error("digest-bits out of range [0..224]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else if (strcasecmp(buf, "hmac-sha256") == 0) { + *hmac = DNS_TSIG_HMACSHA256_NAME; + } else if (strncasecmp(buf, "hmac-sha256-", 12) == 0) { + *hmac = DNS_TSIG_HMACSHA256_NAME; + result = isc_parse_uint16(&digestbits, &buf[12], 10); + if (result != ISC_R_SUCCESS || digestbits > 256) { + error("digest-bits out of range [0..256]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else if (strcasecmp(buf, "hmac-sha384") == 0) { + *hmac = DNS_TSIG_HMACSHA384_NAME; + } else if (strncasecmp(buf, "hmac-sha384-", 12) == 0) { + *hmac = DNS_TSIG_HMACSHA384_NAME; + result = isc_parse_uint16(&digestbits, &buf[12], 10); + if (result != ISC_R_SUCCESS || digestbits > 384) { + error("digest-bits out of range [0..384]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else if (strcasecmp(buf, "hmac-sha512") == 0) { + *hmac = DNS_TSIG_HMACSHA512_NAME; + } else if (strncasecmp(buf, "hmac-sha512-", 12) == 0) { + *hmac = DNS_TSIG_HMACSHA512_NAME; + result = isc_parse_uint16(&digestbits, &buf[12], 10); + if (result != ISC_R_SUCCESS || digestbits > 512) { + error("digest-bits out of range [0..512]"); + return (false); + } + *digestbitsp = (digestbits + 7) & ~0x7U; + } else { + error("unknown key type '%s'", buf); + return (false); + } + return (true); +} + +static int +basenamelen(const char *file) { + int len = strlen(file); + + if (len > 1 && file[len - 1] == '.') { + len -= 1; + } else if (len > 8 && strcmp(file + len - 8, ".private") == 0) { + len -= 8; + } else if (len > 4 && strcmp(file + len - 4, ".key") == 0) { + len -= 4; + } + return (len); +} + +static void +setup_keystr(void) { + unsigned char *secret = NULL; + int secretlen; + isc_buffer_t secretbuf; + isc_result_t result; + isc_buffer_t keynamesrc; + char *secretstr; + char *s, *n; + dns_fixedname_t fkeyname; + dns_name_t *mykeyname; + char *name; + const dns_name_t *hmacname = NULL; + uint16_t digestbits = 0; + + mykeyname = dns_fixedname_initname(&fkeyname); + + debug("Creating key..."); + + s = strchr(keystr, ':'); + if (s == NULL || s == keystr || s[1] == 0) { + fatal("key option must specify [hmac:]keyname:secret"); + } + secretstr = s + 1; + n = strchr(secretstr, ':'); + if (n != NULL) { + if (n == secretstr || n[1] == 0) { + fatal("key option must specify [hmac:]keyname:secret"); + } + name = secretstr; + secretstr = n + 1; + if (!parse_hmac(&hmacname, keystr, s - keystr, &digestbits)) { + exit(1); + } + } else { + hmacname = DNS_TSIG_HMACMD5_NAME; + name = keystr; + n = s; + } + + isc_buffer_init(&keynamesrc, name, (unsigned int)(n - name)); + isc_buffer_add(&keynamesrc, (unsigned int)(n - name)); + + debug("namefromtext"); + result = dns_name_fromtext(mykeyname, &keynamesrc, dns_rootname, 0, + NULL); + check_result(result, "dns_name_fromtext"); + + secretlen = strlen(secretstr) * 3 / 4; + secret = isc_mem_allocate(gmctx, secretlen); + + isc_buffer_init(&secretbuf, secret, secretlen); + result = isc_base64_decodestring(secretstr, &secretbuf); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not create key from %s: %s\n", keystr, + isc_result_totext(result)); + goto failure; + } + + secretlen = isc_buffer_usedlength(&secretbuf); + + debug("keycreate"); + result = dns_tsigkey_create(mykeyname, hmacname, secret, secretlen, + false, NULL, 0, 0, gmctx, NULL, &tsigkey); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not create key from %s: %s\n", keystr, + isc_result_totext(result)); + } else { + dst_key_setbits(tsigkey->key, digestbits); + } +failure: + if (secret != NULL) { + isc_mem_free(gmctx, secret); + } +} + +/* + * Get a key from a named.conf format keyfile + */ +static isc_result_t +read_sessionkey(isc_mem_t *mctx, isc_log_t *lctx) { + cfg_parser_t *pctx = NULL; + cfg_obj_t *sessionkey = NULL; + const cfg_obj_t *key = NULL; + const cfg_obj_t *secretobj = NULL; + const cfg_obj_t *algorithmobj = NULL; + const char *mykeyname; + const char *secretstr; + const char *algorithm; + isc_result_t result; + int len; + + if (!isc_file_exists(keyfile)) { + return (ISC_R_FILENOTFOUND); + } + + result = cfg_parser_create(mctx, lctx, &pctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = cfg_parse_file(pctx, keyfile, &cfg_type_sessionkey, + &sessionkey); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = cfg_map_get(sessionkey, "key", &key); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + (void)cfg_map_get(key, "secret", &secretobj); + (void)cfg_map_get(key, "algorithm", &algorithmobj); + if (secretobj == NULL || algorithmobj == NULL) { + fatal("key must have algorithm and secret"); + } + + mykeyname = cfg_obj_asstring(cfg_map_getname(key)); + secretstr = cfg_obj_asstring(secretobj); + algorithm = cfg_obj_asstring(algorithmobj); + + len = strlen(algorithm) + strlen(mykeyname) + strlen(secretstr) + 3; + keystr = isc_mem_allocate(mctx, len); + snprintf(keystr, len, "%s:%s:%s", algorithm, mykeyname, secretstr); + setup_keystr(); + +cleanup: + if (pctx != NULL) { + if (sessionkey != NULL) { + cfg_obj_destroy(pctx, &sessionkey); + } + cfg_parser_destroy(&pctx); + } + + if (keystr != NULL) { + isc_mem_free(mctx, keystr); + } + + return (result); +} + +static void +setup_keyfile(isc_mem_t *mctx, isc_log_t *lctx) { + dst_key_t *dstkey = NULL; + isc_result_t result; + const dns_name_t *hmacname = NULL; + + debug("Creating key..."); + + if (sig0key != NULL) { + dst_key_free(&sig0key); + } + + /* Try reading the key from a K* pair */ + result = dst_key_fromnamedfile( + keyfile, NULL, DST_TYPE_PRIVATE | DST_TYPE_KEY, mctx, &dstkey); + + /* If that didn't work, try reading it as a session.key keyfile */ + if (result != ISC_R_SUCCESS) { + result = read_sessionkey(mctx, lctx); + if (result == ISC_R_SUCCESS) { + return; + } + } + + if (result != ISC_R_SUCCESS) { + fprintf(stderr, + "could not read key from %.*s.{private,key}: " + "%s\n", + basenamelen(keyfile), keyfile, + isc_result_totext(result)); + return; + } + + switch (dst_key_alg(dstkey)) { + case DST_ALG_HMACMD5: + hmacname = DNS_TSIG_HMACMD5_NAME; + break; + case DST_ALG_HMACSHA1: + hmacname = DNS_TSIG_HMACSHA1_NAME; + break; + case DST_ALG_HMACSHA224: + hmacname = DNS_TSIG_HMACSHA224_NAME; + break; + case DST_ALG_HMACSHA256: + hmacname = DNS_TSIG_HMACSHA256_NAME; + break; + case DST_ALG_HMACSHA384: + hmacname = DNS_TSIG_HMACSHA384_NAME; + break; + case DST_ALG_HMACSHA512: + hmacname = DNS_TSIG_HMACSHA512_NAME; + break; + } + if (hmacname != NULL) { + result = dns_tsigkey_createfromkey( + dst_key_name(dstkey), hmacname, dstkey, false, NULL, 0, + 0, mctx, NULL, &tsigkey); + dst_key_free(&dstkey); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not create key from %s: %s\n", + keyfile, isc_result_totext(result)); + return; + } + } else { + dst_key_attach(dstkey, &sig0key); + dst_key_free(&dstkey); + } +} + +static void +doshutdown(void) { + isc_task_detach(&global_task); + + /* + * The isc_mem_put of primary_servers must be before the + * isc_mem_put of servers as it sets the servers pointer + * to NULL. + */ + if (primary_servers != NULL && primary_servers != servers) { + isc_mem_put(gmctx, primary_servers, + primary_alloc * sizeof(isc_sockaddr_t)); + } + + if (servers != NULL) { + isc_mem_put(gmctx, servers, ns_alloc * sizeof(isc_sockaddr_t)); + } + + if (localaddr4 != NULL) { + isc_mem_put(gmctx, localaddr4, sizeof(isc_sockaddr_t)); + } + + if (localaddr6 != NULL) { + isc_mem_put(gmctx, localaddr6, sizeof(isc_sockaddr_t)); + } + + if (tsigkey != NULL) { + ddebug("Freeing TSIG key"); + dns_tsigkey_detach(&tsigkey); + } + + if (sig0key != NULL) { + ddebug("Freeing SIG(0) key"); + dst_key_free(&sig0key); + } + + if (updatemsg != NULL) { + dns_message_detach(&updatemsg); + } + + ddebug("Destroying request manager"); + dns_requestmgr_detach(&requestmgr); + + ddebug("Freeing the dispatchers"); + if (have_ipv4) { + dns_dispatch_detach(&dispatchv4); + } + if (have_ipv6) { + dns_dispatch_detach(&dispatchv6); + } + + ddebug("Shutting down dispatch manager"); + dns_dispatchmgr_detach(&dispatchmgr); +} + +static void +maybeshutdown(void) { + /* when called from getinput, doshutdown might be already finished */ + if (requestmgr == NULL) { + return; + } + + ddebug("Shutting down request manager"); + dns_requestmgr_shutdown(requestmgr); + + if (requests != 0) { + return; + } + + doshutdown(); +} + +static void +shutdown_program(isc_task_t *task, isc_event_t *event) { + REQUIRE(task == global_task); + UNUSED(task); + + ddebug("shutdown_program()"); + isc_event_free(&event); + + shuttingdown = true; + maybeshutdown(); +} + +/* + * Try honoring the operating system's preferred ephemeral port range. + */ +static void +set_source_ports(dns_dispatchmgr_t *manager) { + isc_portset_t *v4portset = NULL, *v6portset = NULL; + in_port_t udpport_low, udpport_high; + isc_result_t result; + + result = isc_portset_create(gmctx, &v4portset); + check_result(result, "isc_portset_create (v4)"); + result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); + check_result(result, "isc_net_getudpportrange (v4)"); + isc_portset_addrange(v4portset, udpport_low, udpport_high); + + result = isc_portset_create(gmctx, &v6portset); + check_result(result, "isc_portset_create (v6)"); + result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); + check_result(result, "isc_net_getudpportrange (v6)"); + isc_portset_addrange(v6portset, udpport_low, udpport_high); + + result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset); + check_result(result, "dns_dispatchmgr_setavailports"); + + isc_portset_destroy(gmctx, &v4portset); + isc_portset_destroy(gmctx, &v6portset); +} + +static void +setup_system(void) { + isc_result_t result; + isc_sockaddr_t bind_any, bind_any6; + isc_sockaddrlist_t *nslist; + isc_logconfig_t *logconfig = NULL; + irs_resconf_t *resconf = NULL; + + ddebug("setup_system()"); + + isc_log_create(gmctx, &glctx, &logconfig); + isc_log_setcontext(glctx); + dns_log_init(glctx); + dns_log_setcontext(glctx); + + result = isc_log_usechannel(logconfig, "default_debug", NULL, NULL); + check_result(result, "isc_log_usechannel"); + + isc_log_setdebuglevel(glctx, logdebuglevel); + + result = irs_resconf_load(gmctx, resolvconf, &resconf); + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + fatal("parse of %s failed", resolvconf); + } + + nslist = irs_resconf_getnameservers(resconf); + + if (servers != NULL) { + if (primary_servers == servers) { + primary_servers = NULL; + } + isc_mem_put(gmctx, servers, ns_alloc * sizeof(isc_sockaddr_t)); + } + + ns_inuse = 0; + if (local_only || ISC_LIST_EMPTY(*nslist)) { + struct in_addr in; + struct in6_addr in6; + + if (local_only && keyfile == NULL) { + keyfile = SESSION_KEYFILE; + } + + default_servers = !local_only; + + ns_total = ns_alloc = (have_ipv4 ? 1 : 0) + (have_ipv6 ? 1 : 0); + servers = isc_mem_get(gmctx, ns_alloc * sizeof(isc_sockaddr_t)); + + if (have_ipv6) { + memset(&in6, 0, sizeof(in6)); + in6.s6_addr[15] = 1; + isc_sockaddr_fromin6(&servers[0], &in6, dnsport); + } + if (have_ipv4) { + in.s_addr = htonl(INADDR_LOOPBACK); + isc_sockaddr_fromin(&servers[(have_ipv6 ? 1 : 0)], &in, + dnsport); + } + } else { + isc_sockaddr_t *sa; + int i; + + /* + * Count the nameservers (skipping any that we can't use + * because of address family restrictions) and allocate + * the servers array. + */ + ns_total = 0; + for (sa = ISC_LIST_HEAD(*nslist); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + switch (sa->type.sa.sa_family) { + case AF_INET: + if (have_ipv4) { + ns_total++; + } + break; + case AF_INET6: + if (have_ipv6) { + ns_total++; + } + break; + default: + fatal("bad family"); + } + } + + ns_alloc = ns_total; + servers = isc_mem_get(gmctx, ns_alloc * sizeof(isc_sockaddr_t)); + + i = 0; + for (sa = ISC_LIST_HEAD(*nslist); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + switch (sa->type.sa.sa_family) { + case AF_INET: + if (have_ipv4) { + sa->type.sin.sin_port = htons(dnsport); + } else { + continue; + } + break; + case AF_INET6: + if (have_ipv6) { + sa->type.sin6.sin6_port = + htons(dnsport); + } else { + continue; + } + break; + default: + fatal("bad family"); + } + INSIST(i < ns_alloc); + servers[i++] = *sa; + } + } + + irs_resconf_destroy(&resconf); + + result = isc_managers_create(gmctx, 1, 0, &netmgr, &taskmgr, NULL); + check_result(result, "isc_managers_create"); + + result = dns_dispatchmgr_create(gmctx, netmgr, &dispatchmgr); + check_result(result, "dns_dispatchmgr_create"); + + result = isc_task_create(taskmgr, 0, &global_task); + check_result(result, "isc_task_create"); + + result = isc_task_onshutdown(global_task, shutdown_program, NULL); + check_result(result, "isc_task_onshutdown"); + + result = dst_lib_init(gmctx, NULL); + check_result(result, "dst_lib_init"); + is_dst_up = true; + + set_source_ports(dispatchmgr); + + if (have_ipv6) { + isc_sockaddr_any6(&bind_any6); + result = dns_dispatch_createudp(dispatchmgr, &bind_any6, + &dispatchv6); + check_result(result, "dns_dispatch_createudp (v6)"); + } + + if (have_ipv4) { + isc_sockaddr_any(&bind_any); + result = dns_dispatch_createudp(dispatchmgr, &bind_any, + &dispatchv4); + check_result(result, "dns_dispatch_createudp (v4)"); + } + + result = dns_requestmgr_create(gmctx, taskmgr, dispatchmgr, dispatchv4, + dispatchv6, &requestmgr); + check_result(result, "dns_requestmgr_create"); + + if (keystr != NULL) { + setup_keystr(); + } else if (local_only) { + result = read_sessionkey(gmctx, glctx); + if (result != ISC_R_SUCCESS) { + fatal("can't read key from %s: %s\n", keyfile, + isc_result_totext(result)); + } + } else if (keyfile != NULL) { + setup_keyfile(gmctx, glctx); + } + + isc_mutex_init(&answer_lock); +} + +static int +get_addresses(char *host, in_port_t port, isc_sockaddr_t *sockaddr, + int naddrs) { + int count = 0; + isc_result_t result; + + isc_app_block(); + result = bind9_getaddresses(host, port, sockaddr, naddrs, &count); + isc_app_unblock(); + if (result != ISC_R_SUCCESS) { + error("couldn't get address for '%s': %s", host, + isc_result_totext(result)); + } + return (count); +} + +static void +version(void) { + fprintf(stderr, "nsupdate %s\n", PACKAGE_VERSION); +} + +#define PARSE_ARGS_FMT "46C:dDghilL:Mok:p:Pr:R:t:Tu:vVy:" + +static void +pre_parse_args(int argc, char **argv) { + dns_rdatatype_t t; + int ch; + char buf[100]; + bool doexit = false; + bool ipv4only = false, ipv6only = false; + + while ((ch = isc_commandline_parse(argc, argv, PARSE_ARGS_FMT)) != -1) { + switch (ch) { + case 'M': /* was -dm */ + debugging = true; + ddebugging = true; + memdebugging = true; + isc_mem_debugging = ISC_MEM_DEBUGTRACE | + ISC_MEM_DEBUGRECORD; + break; + + case '4': + if (ipv6only) { + fatal("only one of -4 and -6 allowed"); + } + ipv4only = true; + break; + + case '6': + if (ipv4only) { + fatal("only one of -4 and -6 allowed"); + } + ipv6only = true; + break; + + case '?': + case 'h': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + argv[0], isc_commandline_option); + } + fprintf(stderr, "usage: nsupdate [-CdDi] [-L level] " + "[-l] [-g | -o | -y keyname:secret " + "| -k keyfile] [-p port] " + "[-v] [-V] [-P] [-T] [-4 | -6] " + "[filename]\n"); + exit(1); + + case 'P': + for (t = 0xff00; t <= 0xfffe; t++) { + if (dns_rdatatype_ismeta(t)) { + continue; + } + dns_rdatatype_format(t, buf, sizeof(buf)); + if (strncmp(buf, "TYPE", 4) != 0) { + fprintf(stdout, "%s\n", buf); + } + } + doexit = true; + break; + + case 'T': + for (t = 1; t <= 0xfeff; t++) { + if (dns_rdatatype_ismeta(t)) { + continue; + } + dns_rdatatype_format(t, buf, sizeof(buf)); + if (strncmp(buf, "TYPE", 4) != 0) { + fprintf(stdout, "%s\n", buf); + } + } + doexit = true; + break; + + case 'V': + version(); + doexit = true; + break; + + default: + break; + } + } + if (doexit) { + exit(0); + } + isc_commandline_reset = true; + isc_commandline_index = 1; +} + +static void +parse_args(int argc, char **argv) { + int ch; + uint32_t i; + isc_result_t result; + bool force_interactive = false; + + debug("parse_args"); + while ((ch = isc_commandline_parse(argc, argv, PARSE_ARGS_FMT)) != -1) { + switch (ch) { + case '4': + if (have_ipv4) { + isc_net_disableipv6(); + have_ipv6 = false; + } else { + fatal("can't find IPv4 networking"); + } + break; + case '6': + if (have_ipv6) { + isc_net_disableipv4(); + have_ipv4 = false; + } else { + fatal("can't find IPv6 networking"); + } + break; + case 'C': + resolvconf = isc_commandline_argument; + break; + case 'd': + debugging = true; + break; + case 'D': /* was -dd */ + debugging = true; + ddebugging = true; + break; + case 'M': + break; + case 'i': + force_interactive = true; + interactive = true; + break; + case 'l': + local_only = true; + break; + case 'L': + result = isc_parse_uint32(&i, isc_commandline_argument, + 10); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, + "bad library debug value " + "'%s'\n", + isc_commandline_argument); + exit(1); + } + logdebuglevel = i; + break; + case 'y': + keystr = isc_commandline_argument; + break; + case 'v': + usevc = true; + break; + case 'k': + keyfile = isc_commandline_argument; + break; + case 'g': + usegsstsig = true; + use_win2k_gsstsig = false; + break; + case 'o': + usegsstsig = true; + use_win2k_gsstsig = true; + break; + case 'p': + result = isc_parse_uint16(&dnsport, + isc_commandline_argument, 10); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, + "bad port number " + "'%s'\n", + isc_commandline_argument); + exit(1); + } + break; + case 't': + result = isc_parse_uint32(&timeout, + isc_commandline_argument, 10); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "bad timeout '%s'\n", + isc_commandline_argument); + exit(1); + } + if (timeout == 0) { + timeout = UINT_MAX; + } + break; + case 'u': + result = isc_parse_uint32(&udp_timeout, + isc_commandline_argument, 10); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "bad udp timeout '%s'\n", + isc_commandline_argument); + exit(1); + } + break; + case 'r': + result = isc_parse_uint32(&udp_retries, + isc_commandline_argument, 10); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "bad udp retries '%s'\n", + isc_commandline_argument); + exit(1); + } + break; + + case 'R': + fatal("The -R option has been deprecated."); + break; + + default: + fprintf(stderr, "%s: unhandled option: %c\n", argv[0], + isc_commandline_option); + exit(1); + } + } + if (keyfile != NULL && keystr != NULL) { + fprintf(stderr, "%s: cannot specify both -k and -y\n", argv[0]); + exit(1); + } + +#if HAVE_GSSAPI + if (usegsstsig && (keyfile != NULL || keystr != NULL)) { + fprintf(stderr, "%s: cannot specify -g with -k or -y\n", + argv[0]); + exit(1); + } +#else /* HAVE_GSSAPI */ + if (usegsstsig) { + fprintf(stderr, + "%s: cannot specify -g or -o, " + "program not linked with GSS API Library\n", + argv[0]); + exit(1); + } +#endif /* HAVE_GSSAPI */ + + if (argv[isc_commandline_index] != NULL) { + if (strcmp(argv[isc_commandline_index], "-") == 0) { + input = stdin; + } else { + result = isc_stdio_open(argv[isc_commandline_index], + "r", &input); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not open '%s': %s\n", + argv[isc_commandline_index], + isc_result_totext(result)); + exit(1); + } + } + if (!force_interactive) { + interactive = false; + } + } +} + +static uint16_t +parse_name(char **cmdlinep, dns_message_t *msg, dns_name_t **namep) { + isc_result_t result; + char *word; + isc_buffer_t source; + + word = nsu_strsep(cmdlinep, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read owner name\n"); + return (STATUS_SYNTAX); + } + + result = dns_message_gettempname(msg, namep); + check_result(result, "dns_message_gettempname"); + isc_buffer_init(&source, word, strlen(word)); + isc_buffer_add(&source, strlen(word)); + result = dns_name_fromtext(*namep, &source, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + error("invalid owner name: %s", isc_result_totext(result)); + isc_buffer_invalidate(&source); + dns_message_puttempname(msg, namep); + return (STATUS_SYNTAX); + } + isc_buffer_invalidate(&source); + return (STATUS_MORE); +} + +static uint16_t +parse_rdata(char **cmdlinep, dns_rdataclass_t rdataclass, + dns_rdatatype_t rdatatype, dns_message_t *msg, dns_rdata_t *rdata) { + char *cmdline = *cmdlinep; + isc_buffer_t source, *buf = NULL, *newbuf = NULL; + isc_region_t r; + isc_lex_t *lex = NULL; + dns_rdatacallbacks_t callbacks; + isc_result_t result; + + if (cmdline == NULL) { + rdata->flags = DNS_RDATA_UPDATE; + return (STATUS_MORE); + } + + while (*cmdline != 0 && isspace((unsigned char)*cmdline)) { + cmdline++; + } + + if (*cmdline != 0) { + dns_rdatacallbacks_init(&callbacks); + result = isc_lex_create(gmctx, strlen(cmdline), &lex); + check_result(result, "isc_lex_create"); + isc_buffer_init(&source, cmdline, strlen(cmdline)); + isc_buffer_add(&source, strlen(cmdline)); + result = isc_lex_openbuffer(lex, &source); + check_result(result, "isc_lex_openbuffer"); + isc_buffer_allocate(gmctx, &buf, MAXWIRE); + result = dns_rdata_fromtext(NULL, rdataclass, rdatatype, lex, + dns_rootname, 0, gmctx, buf, + &callbacks); + isc_lex_destroy(&lex); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(buf, &r); + isc_buffer_allocate(gmctx, &newbuf, r.length); + isc_buffer_putmem(newbuf, r.base, r.length); + isc_buffer_usedregion(newbuf, &r); + dns_rdata_fromregion(rdata, rdataclass, rdatatype, &r); + isc_buffer_free(&buf); + dns_message_takebuffer(msg, &newbuf); + } else { + fprintf(stderr, "invalid rdata format: %s\n", + isc_result_totext(result)); + isc_buffer_free(&buf); + return (STATUS_SYNTAX); + } + } else { + rdata->flags = DNS_RDATA_UPDATE; + } + *cmdlinep = cmdline; + return (STATUS_MORE); +} + +static uint16_t +make_prereq(char *cmdline, bool ispositive, bool isrrset) { + isc_result_t result; + char *word; + dns_name_t *name = NULL; + isc_textregion_t region; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdataclass_t rdataclass; + dns_rdatatype_t rdatatype; + dns_rdata_t *rdata = NULL; + uint16_t retval; + + ddebug("make_prereq()"); + + /* + * Read the owner name + */ + retval = parse_name(&cmdline, updatemsg, &name); + if (retval != STATUS_MORE) { + return (retval); + } + + /* + * If this is an rrset prereq, read the class or type. + */ + if (isrrset) { + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read class or type\n"); + goto failure; + } + region.base = word; + region.length = strlen(word); + result = dns_rdataclass_fromtext(&rdataclass, ®ion); + if (result == ISC_R_SUCCESS) { + if (!setzoneclass(rdataclass)) { + fprintf(stderr, "class mismatch: %s\n", word); + goto failure; + } + /* + * Now read the type. + */ + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read type\n"); + goto failure; + } + region.base = word; + region.length = strlen(word); + result = dns_rdatatype_fromtext(&rdatatype, ®ion); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "invalid type: %s\n", word); + goto failure; + } + } else { + rdataclass = getzoneclass(); + result = dns_rdatatype_fromtext(&rdatatype, ®ion); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "invalid type: %s\n", word); + goto failure; + } + } + } else { + rdatatype = dns_rdatatype_any; + } + + result = dns_message_gettemprdata(updatemsg, &rdata); + check_result(result, "dns_message_gettemprdata"); + + dns_rdata_init(rdata); + + if (isrrset && ispositive) { + retval = parse_rdata(&cmdline, rdataclass, rdatatype, updatemsg, + rdata); + if (retval != STATUS_MORE) { + goto failure; + } + } else { + rdata->flags = DNS_RDATA_UPDATE; + } + + result = dns_message_gettemprdatalist(updatemsg, &rdatalist); + check_result(result, "dns_message_gettemprdatalist"); + result = dns_message_gettemprdataset(updatemsg, &rdataset); + check_result(result, "dns_message_gettemprdataset"); + rdatalist->type = rdatatype; + if (ispositive) { + if (isrrset && rdata->data != NULL) { + rdatalist->rdclass = rdataclass; + } else { + rdatalist->rdclass = dns_rdataclass_any; + } + } else { + rdatalist->rdclass = dns_rdataclass_none; + } + rdata->rdclass = rdatalist->rdclass; + rdata->type = rdatatype; + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + dns_rdatalist_tordataset(rdatalist, rdataset); + ISC_LIST_INIT(name->list); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(updatemsg, name, DNS_SECTION_PREREQUISITE); + return (STATUS_MORE); + +failure: + if (name != NULL) { + dns_message_puttempname(updatemsg, &name); + } + return (STATUS_SYNTAX); +} + +static uint16_t +evaluate_prereq(char *cmdline) { + char *word; + bool ispositive, isrrset; + + ddebug("evaluate_prereq()"); + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read operation code\n"); + return (STATUS_SYNTAX); + } + if (strcasecmp(word, "nxdomain") == 0) { + ispositive = false; + isrrset = false; + } else if (strcasecmp(word, "yxdomain") == 0) { + ispositive = true; + isrrset = false; + } else if (strcasecmp(word, "nxrrset") == 0) { + ispositive = false; + isrrset = true; + } else if (strcasecmp(word, "yxrrset") == 0) { + ispositive = true; + isrrset = true; + } else { + fprintf(stderr, "incorrect operation code: %s\n", word); + return (STATUS_SYNTAX); + } + return (make_prereq(cmdline, ispositive, isrrset)); +} + +static uint16_t +evaluate_server(char *cmdline) { + char *word, *server; + long port; + + if (local_only) { + fprintf(stderr, "cannot reset server in localhost-only mode\n"); + return (STATUS_SYNTAX); + } + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read server name\n"); + return (STATUS_SYNTAX); + } + server = word; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + port = dnsport; + } else { + char *endp; + port = strtol(word, &endp, 10); + if (*endp != 0) { + fprintf(stderr, "port '%s' is not numeric\n", word); + return (STATUS_SYNTAX); + } else if (port < 1 || port > 65535) { + fprintf(stderr, + "port '%s' is out of range " + "(1 to 65535)\n", + word); + return (STATUS_SYNTAX); + } + } + + if (servers != NULL) { + if (primary_servers == servers) { + primary_servers = NULL; + } + isc_mem_put(gmctx, servers, ns_alloc * sizeof(isc_sockaddr_t)); + } + + default_servers = false; + + ns_alloc = MAX_SERVERADDRS; + ns_inuse = 0; + servers = isc_mem_get(gmctx, ns_alloc * sizeof(isc_sockaddr_t)); + + memset(servers, 0, ns_alloc * sizeof(isc_sockaddr_t)); + ns_total = get_addresses(server, (in_port_t)port, servers, ns_alloc); + if (ns_total == 0) { + return (STATUS_SYNTAX); + } + + return (STATUS_MORE); +} + +static uint16_t +evaluate_local(char *cmdline) { + char *word, *local; + long port; + struct in_addr in4; + struct in6_addr in6; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read server name\n"); + return (STATUS_SYNTAX); + } + local = word; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + port = 0; + } else { + char *endp; + port = strtol(word, &endp, 10); + if (*endp != 0) { + fprintf(stderr, "port '%s' is not numeric\n", word); + return (STATUS_SYNTAX); + } else if (port < 1 || port > 65535) { + fprintf(stderr, + "port '%s' is out of range " + "(1 to 65535)\n", + word); + return (STATUS_SYNTAX); + } + } + + if (have_ipv6 && inet_pton(AF_INET6, local, &in6) == 1) { + if (localaddr6 == NULL) { + localaddr6 = isc_mem_get(gmctx, sizeof(isc_sockaddr_t)); + } + isc_sockaddr_fromin6(localaddr6, &in6, (in_port_t)port); + } else if (have_ipv4 && inet_pton(AF_INET, local, &in4) == 1) { + if (localaddr4 == NULL) { + localaddr4 = isc_mem_get(gmctx, sizeof(isc_sockaddr_t)); + } + isc_sockaddr_fromin(localaddr4, &in4, (in_port_t)port); + } else { + fprintf(stderr, "invalid address %s", local); + return (STATUS_SYNTAX); + } + + return (STATUS_MORE); +} + +static uint16_t +evaluate_key(char *cmdline) { + char *namestr; + char *secretstr; + isc_buffer_t b; + isc_result_t result; + dns_fixedname_t fkeyname; + dns_name_t *mykeyname; + int secretlen; + unsigned char *secret = NULL; + isc_buffer_t secretbuf; + const dns_name_t *hmacname = NULL; + uint16_t digestbits = 0; + char *n; + + namestr = nsu_strsep(&cmdline, " \t\r\n"); + if (namestr == NULL || *namestr == 0) { + fprintf(stderr, "could not read key name\n"); + return (STATUS_SYNTAX); + } + + mykeyname = dns_fixedname_initname(&fkeyname); + + n = strchr(namestr, ':'); + if (n != NULL) { + if (!parse_hmac(&hmacname, namestr, n - namestr, &digestbits)) { + return (STATUS_SYNTAX); + } + namestr = n + 1; + } else { + hmacname = DNS_TSIG_HMACMD5_NAME; + } + + isc_buffer_init(&b, namestr, strlen(namestr)); + isc_buffer_add(&b, strlen(namestr)); + result = dns_name_fromtext(mykeyname, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not parse key name\n"); + return (STATUS_SYNTAX); + } + + secretstr = nsu_strsep(&cmdline, "\r\n"); + if (secretstr == NULL || *secretstr == 0) { + fprintf(stderr, "could not read key secret\n"); + return (STATUS_SYNTAX); + } + secretlen = strlen(secretstr) * 3 / 4; + secret = isc_mem_allocate(gmctx, secretlen); + + isc_buffer_init(&secretbuf, secret, secretlen); + result = isc_base64_decodestring(secretstr, &secretbuf); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not create key from %s: %s\n", secretstr, + isc_result_totext(result)); + isc_mem_free(gmctx, secret); + return (STATUS_SYNTAX); + } + secretlen = isc_buffer_usedlength(&secretbuf); + + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } + result = dns_tsigkey_create(mykeyname, hmacname, secret, secretlen, + false, NULL, 0, 0, gmctx, NULL, &tsigkey); + isc_mem_free(gmctx, secret); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not create key from %s %s: %s\n", + namestr, secretstr, isc_result_totext(result)); + return (STATUS_SYNTAX); + } + dst_key_setbits(tsigkey->key, digestbits); + return (STATUS_MORE); +} + +static uint16_t +evaluate_zone(char *cmdline) { + char *word; + isc_buffer_t b; + isc_result_t result; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read zone name\n"); + return (STATUS_SYNTAX); + } + + userzone = dns_fixedname_initname(&fuserzone); + isc_buffer_init(&b, word, strlen(word)); + isc_buffer_add(&b, strlen(word)); + result = dns_name_fromtext(userzone, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + userzone = NULL; /* Lest it point to an invalid name */ + fprintf(stderr, "could not parse zone name\n"); + return (STATUS_SYNTAX); + } + + return (STATUS_MORE); +} + +static uint16_t +evaluate_realm(char *cmdline) { +#if HAVE_GSSAPI + char *word; + char buf[1024]; + int n; + + if (realm != NULL) { + isc_mem_free(gmctx, realm); + realm = NULL; + } + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + return (STATUS_MORE); + } + + n = snprintf(buf, sizeof(buf), "@%s", word); + if (n < 0 || (size_t)n >= sizeof(buf)) { + error("realm is too long"); + return (STATUS_SYNTAX); + } + realm = isc_mem_strdup(gmctx, buf); + return (STATUS_MORE); +#else /* HAVE_GSSAPI */ + UNUSED(cmdline); + return (STATUS_SYNTAX); +#endif /* HAVE_GSSAPI */ +} + +static uint16_t +evaluate_ttl(char *cmdline) { + char *word; + isc_result_t result; + uint32_t ttl; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read ttl\n"); + return (STATUS_SYNTAX); + } + + if (!strcasecmp(word, "none")) { + default_ttl = 0; + default_ttl_set = false; + return (STATUS_MORE); + } + + result = isc_parse_uint32(&ttl, word, 10); + if (result != ISC_R_SUCCESS) { + return (STATUS_SYNTAX); + } + + if (ttl > TTL_MAX) { + fprintf(stderr, "ttl '%s' is out of range (0 to %u)\n", word, + TTL_MAX); + return (STATUS_SYNTAX); + } + default_ttl = ttl; + default_ttl_set = true; + + return (STATUS_MORE); +} + +static uint16_t +evaluate_class(char *cmdline) { + char *word; + isc_textregion_t r; + isc_result_t result; + dns_rdataclass_t rdclass; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read class name\n"); + return (STATUS_SYNTAX); + } + + r.base = word; + r.length = strlen(word); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not parse class name: %s\n", word); + return (STATUS_SYNTAX); + } + switch (rdclass) { + case dns_rdataclass_none: + case dns_rdataclass_any: + case dns_rdataclass_reserved0: + fprintf(stderr, "bad default class: %s\n", word); + return (STATUS_SYNTAX); + default: + defaultclass = rdclass; + } + + return (STATUS_MORE); +} + +static uint16_t +update_addordelete(char *cmdline, bool isdelete) { + isc_result_t result; + dns_name_t *name = NULL; + uint32_t ttl; + char *word; + dns_rdataclass_t rdataclass; + dns_rdatatype_t rdatatype; + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdataset_t *rdataset = NULL; + isc_textregion_t region; + uint16_t retval; + + ddebug("update_addordelete()"); + + /* + * Read the owner name. + */ + retval = parse_name(&cmdline, updatemsg, &name); + if (retval != STATUS_MORE) { + return (retval); + } + + result = dns_message_gettemprdata(updatemsg, &rdata); + check_result(result, "dns_message_gettemprdata"); + + dns_rdata_init(rdata); + + /* + * If this is an add, read the TTL and verify that it's in range. + * If it's a delete, ignore a TTL if present (for compatibility). + */ + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + if (!isdelete) { + fprintf(stderr, "could not read owner ttl\n"); + goto failure; + } else { + ttl = 0; + rdataclass = dns_rdataclass_any; + rdatatype = dns_rdatatype_any; + rdata->flags = DNS_RDATA_UPDATE; + goto doneparsing; + } + } + result = isc_parse_uint32(&ttl, word, 10); + if (result != ISC_R_SUCCESS) { + if (isdelete) { + ttl = 0; + goto parseclass; + } else if (default_ttl_set) { + ttl = default_ttl; + goto parseclass; + } else { + fprintf(stderr, "ttl '%s': %s\n", word, + isc_result_totext(result)); + goto failure; + } + } + + if (isdelete) { + ttl = 0; + } else if (ttl > TTL_MAX) { + fprintf(stderr, "ttl '%s' is out of range (0 to %u)\n", word, + TTL_MAX); + goto failure; + } + + /* + * Read the class or type. + */ + word = nsu_strsep(&cmdline, " \t\r\n"); +parseclass: + if (word == NULL || *word == 0) { + if (isdelete) { + rdataclass = dns_rdataclass_any; + rdatatype = dns_rdatatype_any; + rdata->flags = DNS_RDATA_UPDATE; + goto doneparsing; + } else { + fprintf(stderr, "could not read class or type\n"); + goto failure; + } + } + region.base = word; + region.length = strlen(word); + rdataclass = dns_rdataclass_any; + result = dns_rdataclass_fromtext(&rdataclass, ®ion); + if (result == ISC_R_SUCCESS && rdataclass != dns_rdataclass_any) { + if (!setzoneclass(rdataclass)) { + fprintf(stderr, "class mismatch: %s\n", word); + goto failure; + } + /* + * Now read the type. + */ + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + if (isdelete) { + rdataclass = dns_rdataclass_any; + rdatatype = dns_rdatatype_any; + rdata->flags = DNS_RDATA_UPDATE; + goto doneparsing; + } else { + fprintf(stderr, "could not read type\n"); + goto failure; + } + } + region.base = word; + region.length = strlen(word); + result = dns_rdatatype_fromtext(&rdatatype, ®ion); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "'%s' is not a valid type: %s\n", word, + isc_result_totext(result)); + goto failure; + } + } else { + rdataclass = getzoneclass(); + result = dns_rdatatype_fromtext(&rdatatype, ®ion); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, + "'%s' is not a valid class or type: " + "%s\n", + word, isc_result_totext(result)); + goto failure; + } + } + + retval = parse_rdata(&cmdline, rdataclass, rdatatype, updatemsg, rdata); + if (retval != STATUS_MORE) { + goto failure; + } + + if (isdelete) { + if ((rdata->flags & DNS_RDATA_UPDATE) != 0) { + rdataclass = dns_rdataclass_any; + } else { + rdataclass = dns_rdataclass_none; + } + } else { + if ((rdata->flags & DNS_RDATA_UPDATE) != 0) { + fprintf(stderr, "could not read rdata\n"); + goto failure; + } + } + + if (!isdelete && checknames) { + dns_fixedname_t fixed; + dns_name_t *bad; + + if (!dns_rdata_checkowner(name, rdata->rdclass, rdata->type, + true)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + fprintf(stderr, "check-names failed: bad owner '%s'\n", + namebuf); + goto failure; + } + + bad = dns_fixedname_initname(&fixed); + if (!dns_rdata_checknames(rdata, name, bad)) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(bad, namebuf, sizeof(namebuf)); + fprintf(stderr, "check-names failed: bad name '%s'\n", + namebuf); + goto failure; + } + } + + if (!isdelete && rdata->type == dns_rdatatype_nsec3param) { + dns_rdata_nsec3param_t nsec3param; + + result = dns_rdata_tostruct(rdata, &nsec3param, NULL); + check_result(result, "dns_rdata_tostruct"); + if (nsec3param.iterations > dns_nsec3_maxiterations()) { + fprintf(stderr, + "NSEC3PARAM has excessive iterations (> %u)\n", + dns_nsec3_maxiterations()); + goto failure; + } + } + +doneparsing: + + result = dns_message_gettemprdatalist(updatemsg, &rdatalist); + check_result(result, "dns_message_gettemprdatalist"); + result = dns_message_gettemprdataset(updatemsg, &rdataset); + check_result(result, "dns_message_gettemprdataset"); + rdatalist->type = rdatatype; + rdatalist->rdclass = rdataclass; + rdatalist->covers = rdatatype; + rdatalist->ttl = (dns_ttl_t)ttl; + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + dns_rdatalist_tordataset(rdatalist, rdataset); + ISC_LIST_INIT(name->list); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(updatemsg, name, DNS_SECTION_UPDATE); + return (STATUS_MORE); + +failure: + if (name != NULL) { + dns_message_puttempname(updatemsg, &name); + } + dns_message_puttemprdata(updatemsg, &rdata); + return (STATUS_SYNTAX); +} + +static uint16_t +evaluate_update(char *cmdline) { + char *word; + bool isdelete; + + ddebug("evaluate_update()"); + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read operation code\n"); + return (STATUS_SYNTAX); + } + if (strcasecmp(word, "delete") == 0) { + isdelete = true; + } else if (strcasecmp(word, "del") == 0) { + isdelete = true; + } else if (strcasecmp(word, "add") == 0) { + isdelete = false; + } else { + fprintf(stderr, "incorrect operation code: %s\n", word); + return (STATUS_SYNTAX); + } + return (update_addordelete(cmdline, isdelete)); +} + +static uint16_t +evaluate_checknames(char *cmdline) { + char *word; + + ddebug("evaluate_checknames()"); + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read check-names directive\n"); + return (STATUS_SYNTAX); + } + if (strcasecmp(word, "yes") == 0 || strcasecmp(word, "true") == 0 || + strcasecmp(word, "on") == 0) + { + checknames = true; + } else if (strcasecmp(word, "no") == 0 || + strcasecmp(word, "false") == 0 || + strcasecmp(word, "off") == 0) + { + checknames = false; + } else { + fprintf(stderr, "incorrect check-names directive: %s\n", word); + return (STATUS_SYNTAX); + } + return (STATUS_MORE); +} + +static void +setzone(dns_name_t *zonename) { + isc_result_t result; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + + result = dns_message_firstname(updatemsg, DNS_SECTION_ZONE); + if (result == ISC_R_SUCCESS) { + dns_message_currentname(updatemsg, DNS_SECTION_ZONE, &name); + dns_message_removename(updatemsg, name, DNS_SECTION_ZONE); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_HEAD(name->list)) + { + ISC_LIST_UNLINK(name->list, rdataset, link); + dns_rdataset_disassociate(rdataset); + dns_message_puttemprdataset(updatemsg, &rdataset); + } + dns_message_puttempname(updatemsg, &name); + } + + if (zonename != NULL) { + result = dns_message_gettempname(updatemsg, &name); + check_result(result, "dns_message_gettempname"); + dns_name_clone(zonename, name); + result = dns_message_gettemprdataset(updatemsg, &rdataset); + check_result(result, "dns_message_gettemprdataset"); + dns_rdataset_makequestion(rdataset, getzoneclass(), + dns_rdatatype_soa); + ISC_LIST_INIT(name->list); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(updatemsg, name, DNS_SECTION_ZONE); + } +} + +static void +show_message(FILE *stream, dns_message_t *msg, const char *description) { + isc_result_t result; + isc_buffer_t *buf = NULL; + int bufsz; + + ddebug("show_message()"); + + setzone(userzone); + + bufsz = INITTEXT; + do { + if (bufsz > MAXTEXT) { + fprintf(stderr, "could not allocate large enough " + "buffer to display message\n"); + exit(1); + } + if (buf != NULL) { + isc_buffer_free(&buf); + } + isc_buffer_allocate(gmctx, &buf, bufsz); + result = dns_message_totext(msg, style, 0, buf); + bufsz *= 2; + } while (result == ISC_R_NOSPACE); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "could not convert message to text format.\n"); + isc_buffer_free(&buf); + return; + } + fprintf(stream, "%s\n%.*s", description, + (int)isc_buffer_usedlength(buf), (char *)isc_buffer_base(buf)); + fflush(stream); + isc_buffer_free(&buf); +} + +static uint16_t +do_next_command(char *cmdline) { + char *word; + + ddebug("do_next_command()"); + word = nsu_strsep(&cmdline, " \t\r\n"); + + if (word == NULL || *word == 0) { + return (STATUS_SEND); + } + if (word[0] == ';') { + return (STATUS_MORE); + } + if (strcasecmp(word, "quit") == 0) { + return (STATUS_QUIT); + } + if (strcasecmp(word, "prereq") == 0) { + return (evaluate_prereq(cmdline)); + } + if (strcasecmp(word, "nxdomain") == 0) { + return (make_prereq(cmdline, false, false)); + } + if (strcasecmp(word, "yxdomain") == 0) { + return (make_prereq(cmdline, true, false)); + } + if (strcasecmp(word, "nxrrset") == 0) { + return (make_prereq(cmdline, false, true)); + } + if (strcasecmp(word, "yxrrset") == 0) { + return (make_prereq(cmdline, true, true)); + } + if (strcasecmp(word, "update") == 0) { + return (evaluate_update(cmdline)); + } + if (strcasecmp(word, "delete") == 0) { + return (update_addordelete(cmdline, true)); + } + if (strcasecmp(word, "del") == 0) { + return (update_addordelete(cmdline, true)); + } + if (strcasecmp(word, "add") == 0) { + return (update_addordelete(cmdline, false)); + } + if (strcasecmp(word, "server") == 0) { + return (evaluate_server(cmdline)); + } + if (strcasecmp(word, "local") == 0) { + return (evaluate_local(cmdline)); + } + if (strcasecmp(word, "zone") == 0) { + return (evaluate_zone(cmdline)); + } + if (strcasecmp(word, "class") == 0) { + return (evaluate_class(cmdline)); + } + if (strcasecmp(word, "send") == 0) { + return (STATUS_SEND); + } + if (strcasecmp(word, "debug") == 0) { + if (debugging) { + ddebugging = true; + } else { + debugging = true; + } + return (STATUS_MORE); + } + if (strcasecmp(word, "ttl") == 0) { + return (evaluate_ttl(cmdline)); + } + if (strcasecmp(word, "show") == 0) { + show_message(stdout, updatemsg, "Outgoing update query:"); + return (STATUS_MORE); + } + if (strcasecmp(word, "answer") == 0) { + LOCK(&answer_lock); + if (answer != NULL) { + show_message(stdout, answer, "Answer:"); + } + UNLOCK(&answer_lock); + return (STATUS_MORE); + } + if (strcasecmp(word, "key") == 0) { + usegsstsig = false; + return (evaluate_key(cmdline)); + } + if (strcasecmp(word, "realm") == 0) { + return (evaluate_realm(cmdline)); + } + if (strcasecmp(word, "check-names") == 0 || + strcasecmp(word, "checknames") == 0) + { + return (evaluate_checknames(cmdline)); + } + if (strcasecmp(word, "gsstsig") == 0) { +#if HAVE_GSSAPI + usegsstsig = true; + use_win2k_gsstsig = false; +#else /* HAVE_GSSAPI */ + fprintf(stderr, "gsstsig not supported\n"); +#endif /* HAVE_GSSAPI */ + return (STATUS_MORE); + } + if (strcasecmp(word, "oldgsstsig") == 0) { +#if HAVE_GSSAPI + usegsstsig = true; + use_win2k_gsstsig = true; +#else /* HAVE_GSSAPI */ + fprintf(stderr, "gsstsig not supported\n"); +#endif /* HAVE_GSSAPI */ + return (STATUS_MORE); + } + if (strcasecmp(word, "help") == 0) { + fprintf(stdout, "nsupdate " PACKAGE_VERSION ":\n" + "local address [port] (set local " + "resolver)\n" + "server address [port] (set primary server " + "for zone)\n" + "send (send the update " + "request)\n" + "show (show the update " + "request)\n" + "answer (show the answer to " + "the last request)\n" + "quit (quit, any pending " + "update is not sent)\n" + "help (display this " + "message)\n" + "key [hmac:]keyname secret (use TSIG to sign " + "the request)\n" + "gsstsig (use GSS_TSIG to " + "sign the request)\n" + "oldgsstsig (use Microsoft's " + "GSS_TSIG to sign the request)\n" + "zone name (set the zone to be " + "updated)\n" + "class CLASS (set the zone's DNS " + "class, e.g. IN (default), CH)\n" + "check-names { on | off } (enable / disable " + "check-names)\n" + "[prereq] nxdomain name (require that this " + "name does not exist)\n" + "[prereq] yxdomain name (require that this " + "name exists)\n" + "[prereq] nxrrset .... (require that this " + "RRset does not exist)\n" + "[prereq] yxrrset .... (require that this " + "RRset exists)\n" + "[update] add .... (add the given " + "record to the zone)\n" + "[update] del[ete] .... (remove the given " + "record(s) from the zone)\n"); + return (STATUS_MORE); + } + if (strcasecmp(word, "version") == 0) { + fprintf(stdout, "nsupdate " PACKAGE_VERSION "\n"); + return (STATUS_MORE); + } + fprintf(stderr, "incorrect section name: %s\n", word); + return (STATUS_SYNTAX); +} + +static uint16_t +get_next_command(void) { + uint16_t result = STATUS_QUIT; + char cmdlinebuf[MAXCMD]; + char *cmdline = NULL, *ptr = NULL; + + isc_app_block(); + if (interactive) { + cmdline = ptr = readline("> "); + if (ptr != NULL && *ptr != 0) { + add_history(ptr); + } + } else { + cmdline = fgets(cmdlinebuf, MAXCMD, input); + } + isc_app_unblock(); + + if (cmdline != NULL) { + char *tmp = cmdline; + + /* + * Normalize input by removing any eol as readline() + * removes eol but fgets doesn't. + */ + (void)nsu_strsep(&tmp, "\r\n"); + result = do_next_command(cmdline); + } + if (ptr != NULL) { + free(ptr); + } + + return (result); +} + +static bool +user_interaction(void) { + uint16_t result = STATUS_MORE; + + ddebug("user_interaction()"); + while ((result == STATUS_MORE) || (result == STATUS_SYNTAX)) { + result = get_next_command(); + if (!interactive && result == STATUS_SYNTAX) { + fatal("syntax error"); + } + } + if (result == STATUS_SEND) { + return (true); + } + return (false); +} + +static void +done_update(void) { + isc_event_t *event = global_event; + ddebug("done_update()"); + isc_task_send(global_task, &event); +} + +static void +check_tsig_error(dns_rdataset_t *rdataset, isc_buffer_t *b) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_any_tsig_t tsig; + + result = dns_rdataset_first(rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &tsig, NULL); + check_result(result, "dns_rdata_tostruct"); + if (tsig.error != 0) { + if (isc_buffer_remaininglength(b) < 1) { + check_result(ISC_R_NOSPACE, "isc_buffer_" + "remaininglength"); + } + isc_buffer_putstr(b, "(" /*)*/); + result = dns_tsigrcode_totext(tsig.error, b); + check_result(result, "dns_tsigrcode_totext"); + if (isc_buffer_remaininglength(b) < 1) { + check_result(ISC_R_NOSPACE, "isc_buffer_" + "remaininglength"); + } + isc_buffer_putstr(b, /*(*/ ")"); + } +} + +static bool +next_primary(const char *caller, isc_sockaddr_t *addr, isc_result_t eresult) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf)); + fprintf(stderr, "; Communication with %s failed: %s\n", addrbuf, + isc_result_totext(eresult)); + if (++primary_inuse >= primary_total) { + return (false); + } + ddebug("%s: trying next server", caller); + return (true); +} + +static void +update_completed(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = NULL; + isc_result_t result; + dns_request_t *request; + + UNUSED(task); + + ddebug("update_completed()"); + + requests--; + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + + if (shuttingdown) { + dns_request_destroy(&request); + isc_event_free(&event); + maybeshutdown(); + return; + } + + if (reqev->result != ISC_R_SUCCESS) { + if (!next_primary("update_completed", + &primary_servers[primary_inuse], + reqev->result)) + { + seenerror = true; + goto done; + } + + ddebug("Destroying request [%p]", request); + dns_request_destroy(&request); + dns_message_renderreset(updatemsg); + dns_message_settsigkey(updatemsg, NULL); + send_update(zname, &primary_servers[primary_inuse]); + isc_event_free(&event); + return; + } + + LOCK(&answer_lock); + dns_message_create(gmctx, DNS_MESSAGE_INTENTPARSE, &answer); + result = dns_request_getresponse(request, answer, + DNS_MESSAGEPARSE_PRESERVEORDER); + switch (result) { + case ISC_R_SUCCESS: + if (answer->verify_attempted) { + ddebug("tsig verification successful"); + } + break; + case DNS_R_CLOCKSKEW: + case DNS_R_EXPECTEDTSIG: + case DNS_R_TSIGERRORSET: + case DNS_R_TSIGVERIFYFAILURE: + case DNS_R_UNEXPECTEDTSIG: + case ISC_R_FAILURE: +#if 0 + if (usegsstsig && answer->rcode == dns_rcode_noerror) { + /* + * For MS DNS that violates RFC 2845, section 4.2 + */ + break; + } +#endif /* if 0 */ + fprintf(stderr, "; TSIG error with server: %s\n", + isc_result_totext(result)); + seenerror = true; + break; + default: + check_result(result, "dns_request_getresponse"); + } + + if (answer->opcode != dns_opcode_update) { + fatal("invalid OPCODE in response to UPDATE request"); + } + + if (answer->rcode != dns_rcode_noerror) { + seenerror = true; + if (!debugging) { + char buf[64]; + isc_buffer_t b; + dns_rdataset_t *rds; + + isc_buffer_init(&b, buf, sizeof(buf) - 1); + result = dns_rcode_totext(answer->rcode, &b); + check_result(result, "dns_rcode_totext"); + rds = dns_message_gettsig(answer, NULL); + if (rds != NULL) { + check_tsig_error(rds, &b); + } + fprintf(stderr, "update failed: %.*s\n", + (int)isc_buffer_usedlength(&b), buf); + } + } + if (debugging) { + show_message(stderr, answer, "\nReply from update query:"); + } + UNLOCK(&answer_lock); + +done: + dns_request_destroy(&request); + if (usegsstsig) { + dns_name_free(&tmpzonename, gmctx); + dns_name_free(&restart_primary, gmctx); + dns_name_init(&tmpzonename, 0); + dns_name_init(&restart_primary, 0); + } + isc_event_free(&event); + done_update(); +} + +static void +send_update(dns_name_t *zone, isc_sockaddr_t *primary) { + isc_result_t result; + dns_request_t *request = NULL; + unsigned int options = DNS_REQUESTOPT_CASE; + isc_sockaddr_t *srcaddr; + + ddebug("send_update()"); + + setzone(zone); + + if (usevc) { + options |= DNS_REQUESTOPT_TCP; + } + if (tsigkey == NULL && sig0key != NULL) { + result = dns_message_setsig0key(updatemsg, sig0key); + check_result(result, "dns_message_setsig0key"); + } + if (debugging) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(primary, addrbuf, sizeof(addrbuf)); + fprintf(stderr, "Sending update to %s\n", addrbuf); + } + + if (isc_sockaddr_pf(primary) == AF_INET6) { + srcaddr = localaddr6; + } else { + srcaddr = localaddr4; + } + + /* Windows doesn't like the tsig name to be compressed. */ + if (updatemsg->tsigname) { + updatemsg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + } + + result = dns_request_create(requestmgr, updatemsg, srcaddr, primary, + options, tsigkey, timeout, udp_timeout, + udp_retries, global_task, update_completed, + NULL, &request); + check_result(result, "dns_request_create"); + + if (debugging) { + show_message(stdout, updatemsg, "Outgoing update query:"); + } + + requests++; +} + +static void +next_server(const char *caller, isc_sockaddr_t *addr, isc_result_t eresult) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf)); + fprintf(stderr, "; Communication with %s failed: %s\n", addrbuf, + isc_result_totext(eresult)); + if (++ns_inuse >= ns_total) { + fatal("could not reach any name server"); + } else { + ddebug("%s: trying next server", caller); + } +} + +static void +recvsoa(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = NULL; + dns_request_t *request = NULL; + isc_result_t result, eresult; + dns_message_t *rcvmsg = NULL; + dns_section_t section; + dns_name_t *name = NULL; + dns_rdataset_t *soaset = NULL; + dns_rdata_soa_t soa; + dns_rdata_t soarr = DNS_RDATA_INIT; + int pass = 0; + dns_name_t primary; + nsu_requestinfo_t *reqinfo; + dns_message_t *soaquery = NULL; + isc_sockaddr_t *addr; + isc_sockaddr_t *srcaddr; + bool seencname = false; + dns_name_t tname; + unsigned int nlabels; + + UNUSED(task); + + ddebug("recvsoa()"); + + requests--; + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + eresult = reqev->result; + reqinfo = reqev->ev_arg; + soaquery = reqinfo->msg; + addr = reqinfo->addr; + + if (shuttingdown) { + dns_request_destroy(&request); + dns_message_detach(&soaquery); + isc_mem_put(gmctx, reqinfo, sizeof(nsu_requestinfo_t)); + isc_event_free(&event); + maybeshutdown(); + return; + } + + if (eresult != ISC_R_SUCCESS) { + next_server("recvsoa", addr, eresult); + ddebug("Destroying request [%p]", request); + dns_request_destroy(&request); + dns_message_renderreset(soaquery); + dns_message_settsigkey(soaquery, NULL); + sendrequest(&servers[ns_inuse], soaquery, &request); + isc_mem_put(gmctx, reqinfo, sizeof(nsu_requestinfo_t)); + isc_event_free(&event); + setzoneclass(dns_rdataclass_none); + return; + } + + isc_mem_put(gmctx, reqinfo, sizeof(nsu_requestinfo_t)); + reqinfo = NULL; + isc_event_free(&event); + reqev = NULL; + + ddebug("About to create rcvmsg"); + dns_message_create(gmctx, DNS_MESSAGE_INTENTPARSE, &rcvmsg); + result = dns_request_getresponse(request, rcvmsg, + DNS_MESSAGEPARSE_PRESERVEORDER); + if (result == DNS_R_TSIGERRORSET && servers != NULL) { + unsigned int options = 0; + + dns_message_detach(&rcvmsg); + ddebug("Destroying request [%p]", request); + dns_request_destroy(&request); + reqinfo = isc_mem_get(gmctx, sizeof(nsu_requestinfo_t)); + reqinfo->msg = soaquery; + reqinfo->addr = addr; + dns_message_renderreset(soaquery); + ddebug("retrying soa request without TSIG"); + + if (!default_servers && usevc) { + options |= DNS_REQUESTOPT_TCP; + } + + if (isc_sockaddr_pf(addr) == AF_INET6) { + srcaddr = localaddr6; + } else { + srcaddr = localaddr4; + } + + result = dns_request_create(requestmgr, soaquery, srcaddr, addr, + options, NULL, timeout, udp_timeout, + udp_retries, global_task, recvsoa, + reqinfo, &request); + check_result(result, "dns_request_create"); + requests++; + return; + } + check_result(result, "dns_request_getresponse"); + + if (rcvmsg->rcode == dns_rcode_refused) { + next_server("recvsoa", addr, DNS_R_REFUSED); + dns_message_detach(&rcvmsg); + dns_request_destroy(&request); + dns_message_renderreset(soaquery); + dns_message_settsigkey(soaquery, NULL); + sendrequest(&servers[ns_inuse], soaquery, &request); + return; + } + + section = DNS_SECTION_ANSWER; + POST(section); + if (debugging) { + show_message(stderr, rcvmsg, "Reply from SOA query:"); + } + + if (rcvmsg->opcode != dns_opcode_query) { + fatal("invalid OPCODE in response to SOA query"); + } + + if (rcvmsg->rcode != dns_rcode_noerror && + rcvmsg->rcode != dns_rcode_nxdomain) + { + fatal("response to SOA query was unsuccessful"); + } + + if (userzone != NULL && rcvmsg->rcode == dns_rcode_nxdomain) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(userzone, namebuf, sizeof(namebuf)); + error("specified zone '%s' does not exist (NXDOMAIN)", namebuf); + dns_message_detach(&rcvmsg); + dns_request_destroy(&request); + dns_message_detach(&soaquery); + ddebug("Out of recvsoa"); + seenerror = true; + done_update(); + return; + } + +lookforsoa: + if (pass == 0) { + section = DNS_SECTION_ANSWER; + } else if (pass == 1) { + section = DNS_SECTION_AUTHORITY; + } else { + goto droplabel; + } + + result = dns_message_firstname(rcvmsg, section); + if (result != ISC_R_SUCCESS) { + pass++; + goto lookforsoa; + } + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(rcvmsg, section, &name); + soaset = NULL; + result = dns_message_findtype(name, dns_rdatatype_soa, 0, + &soaset); + if (result == ISC_R_SUCCESS) { + break; + } + if (section == DNS_SECTION_ANSWER) { + dns_rdataset_t *tset = NULL; + if (dns_message_findtype(name, dns_rdatatype_cname, 0, + &tset) == ISC_R_SUCCESS || + dns_message_findtype(name, dns_rdatatype_dname, 0, + &tset) == ISC_R_SUCCESS) + { + seencname = true; + break; + } + } + + result = dns_message_nextname(rcvmsg, section); + } + + if (soaset == NULL && !seencname) { + pass++; + goto lookforsoa; + } + + if (seencname) { + goto droplabel; + } + + if (debugging) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + fprintf(stderr, "Found zone name: %s\n", namestr); + } + + result = dns_rdataset_first(soaset); + check_result(result, "dns_rdataset_first"); + + dns_rdata_init(&soarr); + dns_rdataset_current(soaset, &soarr); + result = dns_rdata_tostruct(&soarr, &soa, NULL); + check_result(result, "dns_rdata_tostruct"); + + dns_name_init(&primary, NULL); + dns_name_clone(&soa.origin, &primary); + + if (userzone != NULL) { + zname = userzone; + } else { + /* + * Save the zone name in case we need to try a second + * address. + */ + zname = dns_fixedname_initname(&fzname); + dns_name_copy(name, zname); + } + + if (debugging) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(&primary, namestr, sizeof(namestr)); + fprintf(stderr, "The primary is: %s\n", namestr); + } + + if (default_servers) { + char serverstr[DNS_NAME_MAXTEXT + 1]; + isc_buffer_t buf; + size_t size; + + isc_buffer_init(&buf, serverstr, sizeof(serverstr)); + result = dns_name_totext(&primary, true, &buf); + check_result(result, "dns_name_totext"); + serverstr[isc_buffer_usedlength(&buf)] = 0; + + if (primary_servers != NULL && primary_servers != servers) { + isc_mem_put(gmctx, primary_servers, + primary_alloc * sizeof(isc_sockaddr_t)); + } + primary_alloc = MAX_SERVERADDRS; + size = primary_alloc * sizeof(isc_sockaddr_t); + primary_servers = isc_mem_get(gmctx, size); + + memset(primary_servers, 0, size); + primary_total = get_addresses(serverstr, dnsport, + primary_servers, primary_alloc); + if (primary_total == 0) { + seenerror = true; + dns_rdata_freestruct(&soa); + dns_message_detach(&soaquery); + dns_request_destroy(&request); + dns_message_detach(&rcvmsg); + ddebug("Out of recvsoa"); + done_update(); + return; + } + primary_inuse = 0; + } else { + primary_from_servers(); + } + dns_rdata_freestruct(&soa); + +#if HAVE_GSSAPI + if (usegsstsig) { + dns_name_init(&tmpzonename, NULL); + dns_name_dup(zname, gmctx, &tmpzonename); + dns_name_init(&restart_primary, NULL); + dns_name_dup(&primary, gmctx, &restart_primary); + start_gssrequest(&primary); + } else { + send_update(zname, &primary_servers[primary_inuse]); + setzoneclass(dns_rdataclass_none); + } +#else /* HAVE_GSSAPI */ + send_update(zname, &primary_servers[primary_inuse]); + setzoneclass(dns_rdataclass_none); +#endif /* HAVE_GSSAPI */ + + dns_message_detach(&soaquery); + dns_request_destroy(&request); + +out: + dns_message_detach(&rcvmsg); + ddebug("Out of recvsoa"); + return; + +droplabel: + result = dns_message_firstname(soaquery, DNS_SECTION_QUESTION); + INSIST(result == ISC_R_SUCCESS); + name = NULL; + dns_message_currentname(soaquery, DNS_SECTION_QUESTION, &name); + nlabels = dns_name_countlabels(name); + if (nlabels == 1) { + fatal("could not find enclosing zone"); + } + dns_name_init(&tname, NULL); + dns_name_getlabelsequence(name, 1, nlabels - 1, &tname); + dns_name_clone(&tname, name); + dns_request_destroy(&request); + dns_message_renderreset(soaquery); + dns_message_settsigkey(soaquery, NULL); + sendrequest(&servers[ns_inuse], soaquery, &request); + goto out; +} + +static void +sendrequest(isc_sockaddr_t *destaddr, dns_message_t *msg, + dns_request_t **request) { + isc_result_t result; + nsu_requestinfo_t *reqinfo; + isc_sockaddr_t *srcaddr; + unsigned int options = 0; + + if (!default_servers && usevc) { + options |= DNS_REQUESTOPT_TCP; + } + + reqinfo = isc_mem_get(gmctx, sizeof(nsu_requestinfo_t)); + reqinfo->msg = msg; + reqinfo->addr = destaddr; + + if (isc_sockaddr_pf(destaddr) == AF_INET6) { + srcaddr = localaddr6; + } else { + srcaddr = localaddr4; + } + + result = dns_request_create(requestmgr, msg, srcaddr, destaddr, options, + default_servers ? NULL : tsigkey, timeout, + udp_timeout, udp_retries, global_task, + recvsoa, reqinfo, request); + check_result(result, "dns_request_create"); + requests++; +} + +#if HAVE_GSSAPI + +/* + * Get the realm from the users kerberos ticket if possible + */ +static void +get_ticket_realm(isc_mem_t *mctx) { + krb5_context ctx; + krb5_error_code rc; + krb5_ccache ccache; + krb5_principal princ; + char *name; + const char *ticket_realm; + + rc = krb5_init_context(&ctx); + if (rc != 0) { + return; + } + + rc = krb5_cc_default(ctx, &ccache); + if (rc != 0) { + krb5_free_context(ctx); + return; + } + + rc = krb5_cc_get_principal(ctx, ccache, &princ); + if (rc != 0) { + krb5_cc_close(ctx, ccache); + krb5_free_context(ctx); + return; + } + + rc = krb5_unparse_name(ctx, princ, &name); + if (rc != 0) { + krb5_free_principal(ctx, princ); + krb5_cc_close(ctx, ccache); + krb5_free_context(ctx); + return; + } + + ticket_realm = strrchr(name, '@'); + if (ticket_realm != NULL) { + realm = isc_mem_strdup(mctx, ticket_realm); + } + + free(name); + krb5_free_principal(ctx, princ); + krb5_cc_close(ctx, ccache); + krb5_free_context(ctx); + if (realm != NULL && debugging) { + fprintf(stderr, "Found realm from ticket: %s\n", realm + 1); + } +} + +static void +failed_gssrequest(void) { + seenerror = true; + + dns_name_free(&tmpzonename, gmctx); + dns_name_free(&restart_primary, gmctx); + dns_name_init(&tmpzonename, NULL); + dns_name_init(&restart_primary, NULL); + + done_update(); +} + +static void +start_gssrequest(dns_name_t *primary) { + dns_gss_ctx_id_t context; + isc_buffer_t buf; + isc_result_t result; + uint32_t val = 0; + dns_message_t *rmsg = NULL; + dns_request_t *request = NULL; + dns_name_t *servname; + dns_fixedname_t fname; + char namestr[DNS_NAME_FORMATSIZE]; + char mykeystr[DNS_NAME_FORMATSIZE]; + char *err_message = NULL; + + debug("start_gssrequest"); + usevc = true; + + if (gssring != NULL) { + dns_tsigkeyring_detach(&gssring); + } + gssring = NULL; + result = dns_tsigkeyring_create(gmctx, &gssring); + + if (result != ISC_R_SUCCESS) { + fatal("dns_tsigkeyring_create failed: %s", + isc_result_totext(result)); + } + + dns_name_format(primary, namestr, sizeof(namestr)); + if (kserver == NULL) { + kserver = isc_mem_get(gmctx, sizeof(isc_sockaddr_t)); + } + + memmove(kserver, &primary_servers[primary_inuse], + sizeof(isc_sockaddr_t)); + + servname = dns_fixedname_initname(&fname); + + if (realm == NULL) { + get_ticket_realm(gmctx); + } + + result = snprintf(servicename, sizeof(servicename), "DNS/%s%s", namestr, + realm ? realm : ""); + RUNTIME_CHECK(result < sizeof(servicename)); + isc_buffer_init(&buf, servicename, strlen(servicename)); + isc_buffer_add(&buf, strlen(servicename)); + result = dns_name_fromtext(servname, &buf, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fatal("dns_name_fromtext(servname) failed: %s", + isc_result_totext(result)); + } + + keyname = dns_fixedname_initname(&fkname); + + isc_nonce_buf(&val, sizeof(val)); + + result = snprintf(mykeystr, sizeof(mykeystr), "%u.sig-%s", val, + namestr); + RUNTIME_CHECK(result <= sizeof(mykeystr)); + + isc_buffer_init(&buf, mykeystr, strlen(mykeystr)); + isc_buffer_add(&buf, strlen(mykeystr)); + + result = dns_name_fromtext(keyname, &buf, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fatal("dns_name_fromtext(keyname) failed: %s", + isc_result_totext(result)); + } + + /* Windows doesn't recognize name compression in the key name. */ + keyname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + + rmsg = NULL; + dns_message_create(gmctx, DNS_MESSAGE_INTENTRENDER, &rmsg); + + /* Build first request. */ + context = GSS_C_NO_CONTEXT; + result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0, + &context, use_win2k_gsstsig, gmctx, + &err_message); + if (result == ISC_R_FAILURE) { + fprintf(stderr, "tkey query failed: %s\n", + err_message != NULL ? err_message : "unknown error"); + goto failure; + } + if (result != ISC_R_SUCCESS) { + fatal("dns_tkey_buildgssquery failed: %s", + isc_result_totext(result)); + } + + send_gssrequest(kserver, rmsg, &request, context); + return; + +failure: + if (rmsg != NULL) { + dns_message_detach(&rmsg); + } + if (err_message != NULL) { + isc_mem_free(gmctx, err_message); + } + failed_gssrequest(); +} + +static void +send_gssrequest(isc_sockaddr_t *destaddr, dns_message_t *msg, + dns_request_t **request, gss_ctx_id_t context) { + isc_result_t result; + nsu_gssinfo_t *reqinfo; + unsigned int options = 0; + isc_sockaddr_t *srcaddr; + + debug("send_gssrequest"); + REQUIRE(destaddr != NULL); + + reqinfo = isc_mem_get(gmctx, sizeof(nsu_gssinfo_t)); + reqinfo->msg = msg; + reqinfo->addr = destaddr; + reqinfo->context = context; + + options |= DNS_REQUESTOPT_TCP; + + if (isc_sockaddr_pf(destaddr) == AF_INET6) { + srcaddr = localaddr6; + } else { + srcaddr = localaddr4; + } + + result = dns_request_create(requestmgr, msg, srcaddr, destaddr, options, + tsigkey, timeout, udp_timeout, udp_retries, + global_task, recvgss, reqinfo, request); + check_result(result, "dns_request_create"); + if (debugging) { + show_message(stdout, msg, "Outgoing update query:"); + } + requests++; +} + +static void +recvgss(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = NULL; + dns_request_t *request = NULL; + isc_result_t result, eresult; + dns_message_t *rcvmsg = NULL; + nsu_gssinfo_t *reqinfo; + dns_message_t *tsigquery = NULL; + isc_sockaddr_t *addr; + dns_gss_ctx_id_t context; + isc_buffer_t buf; + dns_name_t *servname; + dns_fixedname_t fname; + char *err_message = NULL; + + UNUSED(task); + + ddebug("recvgss()"); + + requests--; + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + eresult = reqev->result; + reqinfo = reqev->ev_arg; + tsigquery = reqinfo->msg; + context = reqinfo->context; + addr = reqinfo->addr; + + if (shuttingdown) { + dns_request_destroy(&request); + dns_message_detach(&tsigquery); + isc_mem_put(gmctx, reqinfo, sizeof(nsu_gssinfo_t)); + isc_event_free(&event); + maybeshutdown(); + return; + } + + if (eresult != ISC_R_SUCCESS) { + ddebug("Destroying request [%p]", request); + dns_request_destroy(&request); + if (!next_primary("recvgss", addr, eresult)) { + dns_message_detach(&tsigquery); + failed_gssrequest(); + } else { + dns_message_renderreset(tsigquery); + memmove(kserver, &primary_servers[primary_inuse], + sizeof(isc_sockaddr_t)); + send_gssrequest(kserver, tsigquery, &request, context); + } + isc_mem_put(gmctx, reqinfo, sizeof(nsu_gssinfo_t)); + isc_event_free(&event); + return; + } + isc_mem_put(gmctx, reqinfo, sizeof(nsu_gssinfo_t)); + + isc_event_free(&event); + reqev = NULL; + + ddebug("recvgss creating rcvmsg"); + dns_message_create(gmctx, DNS_MESSAGE_INTENTPARSE, &rcvmsg); + + result = dns_request_getresponse(request, rcvmsg, + DNS_MESSAGEPARSE_PRESERVEORDER); + check_result(result, "dns_request_getresponse"); + + if (debugging) { + show_message(stderr, rcvmsg, + "recvmsg reply from GSS-TSIG query"); + } + + if (rcvmsg->opcode != dns_opcode_query) { + fatal("invalid OPCODE in response to GSS-TSIG query"); + } + + if (rcvmsg->rcode == dns_rcode_formerr && !tried_other_gsstsig) { + ddebug("recvgss trying %s GSS-TSIG", + use_win2k_gsstsig ? "Standard" : "Win2k"); + if (use_win2k_gsstsig) { + use_win2k_gsstsig = false; + } else { + use_win2k_gsstsig = true; + } + tried_other_gsstsig = true; + start_gssrequest(&restart_primary); + goto done; + } + + if (rcvmsg->rcode != dns_rcode_noerror && + rcvmsg->rcode != dns_rcode_nxdomain) + { + char rcode[64]; + isc_buffer_t b; + + isc_buffer_init(&b, rcode, sizeof(rcode) - 1); + result = dns_rcode_totext(rcvmsg->rcode, &b); + check_result(result, "dns_rcode_totext"); + rcode[isc_buffer_usedlength(&b)] = 0; + + fatal("response to GSS-TSIG query was unsuccessful (%s)", + rcode); + } + + servname = dns_fixedname_initname(&fname); + isc_buffer_init(&buf, servicename, strlen(servicename)); + isc_buffer_add(&buf, strlen(servicename)); + result = dns_name_fromtext(servname, &buf, dns_rootname, 0, NULL); + check_result(result, "dns_name_fromtext"); + + tsigkey = NULL; + result = dns_tkey_gssnegotiate(tsigquery, rcvmsg, servname, &context, + &tsigkey, gssring, use_win2k_gsstsig, + &err_message); + switch (result) { + case DNS_R_CONTINUE: + dns_message_detach(&rcvmsg); + dns_request_destroy(&request); + send_gssrequest(kserver, tsigquery, &request, context); + ddebug("Out of recvgss"); + return; + + case ISC_R_SUCCESS: + /* + * XXXSRA Waaay too much fun here. There's no good + * reason why we need a TSIG here (the people who put + * it into the spec admitted at the time that it was + * not a security issue), and Windows clients don't + * seem to work if named complies with the spec and + * includes the gratuitous TSIG. So we're in the + * bizarre situation of having to choose between + * complying with a useless requirement in the spec + * and interoperating. This is nuts. If we can + * confirm this behavior, we should ask the WG to + * consider removing the requirement for the + * gratuitous TSIG here. For the moment, we ignore + * the TSIG -- this too is a spec violation, but it's + * the least insane thing to do. + */ +#if 0 + /* + * Verify the signature. + */ + rcvmsg->state = DNS_SECTION_ANY; + dns_message_setquerytsig(rcvmsg, NULL); + result = dns_message_settsigkey(rcvmsg, tsigkey); + check_result(result, "dns_message_settsigkey"); + result = dns_message_checksig(rcvmsg, NULL); + ddebug("tsig verification: %s", isc_result_totext(result)); + check_result(result, "dns_message_checksig"); +#endif /* 0 */ + + send_update(&tmpzonename, &primary_servers[primary_inuse]); + setzoneclass(dns_rdataclass_none); + break; + + default: + fatal("dns_tkey_gssnegotiate: %s %s", isc_result_totext(result), + err_message != NULL ? err_message : ""); + } + +done: + dns_request_destroy(&request); + dns_message_detach(&tsigquery); + + dns_message_detach(&rcvmsg); + ddebug("Out of recvgss"); +} +#endif /* HAVE_GSSAPI */ + +static void +start_update(void) { + isc_result_t result; + dns_rdataset_t *rdataset = NULL; + dns_name_t *name = NULL; + dns_request_t *request = NULL; + dns_message_t *soaquery = NULL; + dns_name_t *firstname; + dns_section_t section = DNS_SECTION_UPDATE; + + ddebug("start_update()"); + + LOCK(&answer_lock); + if (answer != NULL) { + dns_message_detach(&answer); + } + UNLOCK(&answer_lock); + + /* + * If we have both the zone and the servers we have enough information + * to send the update straight away otherwise we need to discover + * the zone and / or the primary server. + */ + if (userzone != NULL && !default_servers && !usegsstsig) { + primary_from_servers(); + send_update(userzone, &primary_servers[primary_inuse]); + setzoneclass(dns_rdataclass_none); + return; + } + + dns_message_create(gmctx, DNS_MESSAGE_INTENTRENDER, &soaquery); + + if (default_servers) { + soaquery->flags |= DNS_MESSAGEFLAG_RD; + } + + result = dns_message_gettempname(soaquery, &name); + check_result(result, "dns_message_gettempname"); + + result = dns_message_gettemprdataset(soaquery, &rdataset); + check_result(result, "dns_message_gettemprdataset"); + + dns_rdataset_makequestion(rdataset, getzoneclass(), dns_rdatatype_soa); + + if (userzone != NULL) { + dns_name_clone(userzone, name); + } else { + dns_rdataset_t *tmprdataset; + result = dns_message_firstname(updatemsg, section); + if (result == ISC_R_NOMORE) { + section = DNS_SECTION_PREREQUISITE; + result = dns_message_firstname(updatemsg, section); + } + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(soaquery, &name); + dns_rdataset_disassociate(rdataset); + dns_message_puttemprdataset(soaquery, &rdataset); + dns_message_detach(&soaquery); + done_update(); + return; + } + firstname = NULL; + dns_message_currentname(updatemsg, section, &firstname); + dns_name_clone(firstname, name); + /* + * Looks to see if the first name references a DS record + * and if that name is not the root remove a label as DS + * records live in the parent zone so we need to start our + * search one label up. + */ + tmprdataset = ISC_LIST_HEAD(firstname->list); + if (section == DNS_SECTION_UPDATE && + !dns_name_equal(firstname, dns_rootname) && + tmprdataset->type == dns_rdatatype_ds) + { + unsigned int labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, name); + } + } + + ISC_LIST_INIT(name->list); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(soaquery, name, DNS_SECTION_QUESTION); + + ns_inuse = 0; + sendrequest(&servers[ns_inuse], soaquery, &request); +} + +static void +cleanup(void) { + ddebug("cleanup()"); + + LOCK(&answer_lock); + if (answer != NULL) { + dns_message_detach(&answer); + } + UNLOCK(&answer_lock); + + ddebug("Shutting down managers"); + isc_managers_destroy(&netmgr, &taskmgr, NULL); + +#if HAVE_GSSAPI + if (tsigkey != NULL) { + ddebug("detach tsigkey x%p", tsigkey); + dns_tsigkey_detach(&tsigkey); + } + if (gssring != NULL) { + ddebug("Detaching GSS-TSIG keyring"); + dns_tsigkeyring_detach(&gssring); + } +#endif /* ifdef HAVE_GSSAPI */ + + if (sig0key != NULL) { + dst_key_free(&sig0key); + } + + ddebug("Destroying event"); + isc_event_free(&global_event); + +#ifdef HAVE_GSSAPI + /* + * Cleanup GSSAPI resources after taskmgr has been destroyed. + */ + if (kserver != NULL) { + isc_mem_put(gmctx, kserver, sizeof(isc_sockaddr_t)); + kserver = NULL; + } + if (realm != NULL) { + isc_mem_free(gmctx, realm); + realm = NULL; + } + if (dns_name_dynamic(&tmpzonename)) { + dns_name_free(&tmpzonename, gmctx); + } + if (dns_name_dynamic(&restart_primary)) { + dns_name_free(&restart_primary, gmctx); + } +#endif /* ifdef HAVE_GSSAPI */ + + ddebug("Removing log context"); + isc_log_destroy(&glctx); + + ddebug("Destroying memory context"); + if (memdebugging) { + isc_mem_stats(gmctx, stderr); + } + isc_mem_destroy(&gmctx); + + isc_mutex_destroy(&answer_lock); + + if (is_dst_up) { + ddebug("Destroy DST lib"); + dst_lib_destroy(); + is_dst_up = false; + } +} + +static void +getinput(isc_task_t *task, isc_event_t *event) { + bool more; + + UNUSED(task); + + if (shuttingdown) { + maybeshutdown(); + return; + } + + if (global_event == NULL) { + global_event = event; + } + + reset_system(); + more = user_interaction(); + if (!more) { + isc_app_shutdown(); + return; + } + start_update(); + return; +} + +int +main(int argc, char **argv) { + isc_result_t result; + uint32_t timeoutms; + + style = &dns_master_style_debug; + + input = stdin; + + interactive = isatty(0); + + isc_app_start(); + + if (isc_net_probeipv4() == ISC_R_SUCCESS) { + have_ipv4 = true; + } + if (isc_net_probeipv6() == ISC_R_SUCCESS) { + have_ipv6 = true; + } + if (!have_ipv4 && !have_ipv6) { + fatal("could not find either IPv4 or IPv6"); + } + + pre_parse_args(argc, argv); + + isc_mem_create(&gmctx); + + parse_args(argc, argv); + + setup_system(); + + /* Set the network manager timeouts in milliseconds. */ + timeoutms = timeout * 1000; + isc_nm_settimeouts(netmgr, timeoutms, timeoutms, timeoutms, timeoutms); + + result = isc_app_onrun(gmctx, global_task, getinput, NULL); + check_result(result, "isc_app_onrun"); + + (void)isc_app_run(); + + cleanup(); + + isc_app_finish(); + + if (seenerror) { + return (2); + } else { + return (0); + } +} diff --git a/bin/nsupdate/nsupdate.rst b/bin/nsupdate/nsupdate.rst new file mode 100644 index 0000000..81bb481 --- /dev/null +++ b/bin/nsupdate/nsupdate.rst @@ -0,0 +1,391 @@ +.. 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. + +.. iscman:: nsupdate +.. program:: nsupdate +.. _man_nsupdate: + +nsupdate - dynamic DNS update utility +------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`nsupdate` [**-d**] [**-D**] [**-i**] [**-L** level] [ [**-g**] | [**-o**] | [**-l**] | [**-y** [hmac:]keyname:secret] | [**-k** keyfile] ] [**-t** timeout] [**-u** udptimeout] [**-r** udpretries] [**-v**] [**-T**] [**-P**] [**-V**] [ [**-4**] | [**-6**] ] [filename] + +Description +~~~~~~~~~~~ + +:program:`nsupdate` is used to submit Dynamic DNS Update requests, as defined in +:rfc:`2136`, to a name server. This allows resource records to be added or +removed from a zone without manually editing the zone file. A single +update request can contain requests to add or remove more than one +resource record. + +Zones that are under dynamic control via :program:`nsupdate` or a DHCP server +should not be edited by hand. Manual edits could conflict with dynamic +updates and cause data to be lost. + +The resource records that are dynamically added or removed with +:program:`nsupdate` must be in the same zone. Requests are sent to the +zone's primary server, which is identified by the MNAME field of the +zone's SOA record. + +Transaction signatures can be used to authenticate the Dynamic DNS +updates. These use the TSIG resource record type described in :rfc:`2845`, +the SIG(0) record described in :rfc:`2535` and :rfc:`2931`, or GSS-TSIG as +described in :rfc:`3645`. + +TSIG relies on a shared secret that should only be known to :program:`nsupdate` +and the name server. For instance, suitable ``key`` and ``server`` +statements are added to |named_conf| so that the name server +can associate the appropriate secret key and algorithm with the IP +address of the client application that is using TSIG +authentication. :iscman:`ddns-confgen` can generate suitable +configuration fragments. :program:`nsupdate` uses the :option:`-y` or :option:`-k` options +to provide the TSIG shared secret; these options are mutually exclusive. + +SIG(0) uses public key cryptography. To use a SIG(0) key, the public key +must be stored in a KEY record in a zone served by the name server. + +GSS-TSIG uses Kerberos credentials. Standard GSS-TSIG mode is switched +on with the :option:`-g` flag. A non-standards-compliant variant of GSS-TSIG +used by Windows 2000 can be switched on with the :option:`-o` flag. + +Options +~~~~~~~ + +.. option:: -4 + + This option sets use of IPv4 only. + +.. option:: -6 + + This option sets use of IPv6 only. + +.. option:: -C + + Overrides the default `resolv.conf` file. This is only intended for testing. + +.. option:: -d + + This option sets debug mode, which provides tracing information about the update + requests that are made and the replies received from the name server. + +.. option:: -D + + This option sets extra debug mode. + +.. option:: -g + + This option enables standard GSS-TSIG mode. + +.. option:: -i + + This option forces interactive mode, even when standard input is not a terminal. + +.. option:: -k keyfile + + This option indicates the file containing the TSIG authentication key. Keyfiles may be in + two formats: a single file containing a :iscman:`named.conf`-format ``key`` + statement, which may be generated automatically by :iscman:`ddns-confgen`; + or a pair of files whose names are of the format + ``K{name}.+157.+{random}.key`` and + ``K{name}.+157.+{random}.private``, which can be generated by + :iscman:`dnssec-keygen`. The :option:`-k` option can also be used to specify a SIG(0) + key used to authenticate Dynamic DNS update requests. In this case, + the key specified is not an HMAC-MD5 key. + +.. option:: -l + + This option sets local-host only mode, which sets the server address to localhost + (disabling the ``server`` so that the server address cannot be + overridden). Connections to the local server use a TSIG key + found in |session_key|, which is automatically + generated by :iscman:`named` if any local ``primary`` zone has set + ``update-policy`` to ``local``. The location of this key file can be + overridden with the :option:`-k` option. + +.. option:: -L level + + This option sets the logging debug level. If zero, logging is disabled. + +.. option:: -o + + This option enables a non-standards-compliant variant of GSS-TSIG + used by Windows 2000. + +.. option:: -p port + + This option sets the port to use for connections to a name server. The default is + 53. + +.. option:: -P + + This option prints the list of private BIND-specific resource record types whose + format is understood by :program:`nsupdate`. See also the :option:`-T` option. + +.. option:: -r udpretries + + This option sets the number of UDP retries. The default is 3. If zero, only one update + request is made. + +.. option:: -t timeout + + This option sets the maximum time an update request can take before it is aborted. The + default is 300 seconds. If zero, the timeout is disabled for TCP mode. For UDP mode, + the option :option:`-u` takes precedence over this option, unless the option :option:`-u` + is set to zero, in which case the interval is computed from the :option:`-t` timeout interval + and the number of UDP retries. For UDP mode, the timeout can not be disabled, and will + be rounded up to 1 second in case if both :option:`-t` and :option:`-u` are set to zero. + +.. option:: -T + + This option prints the list of IANA standard resource record types whose format is + understood by :program:`nsupdate`. :program:`nsupdate` exits after the lists + are printed. The :option:`-T` option can be combined with the :option:`-P` + option. + + Other types can be entered using ``TYPEXXXXX`` where ``XXXXX`` is the + decimal value of the type with no leading zeros. The rdata, if + present, is parsed using the UNKNOWN rdata format, (<backslash> + <hash> <space> <length> <space> <hexstring>). + +.. option:: -u udptimeout + + This option sets the UDP retry interval. The default is 3 seconds. If zero, the + interval is computed from the timeout interval and number of UDP + retries. + +.. option:: -v + + This option specifies that TCP should be used even for small update requests. By default, :program:`nsupdate` uses + UDP to send update requests to the name server unless they are too + large to fit in a UDP request, in which case TCP is used. TCP may + be preferable when a batch of update requests is made. + +.. option:: -V + + This option prints the version number and exits. + +.. option:: -y [hmac:]keyname:secret + + This option sets the literal TSIG authentication key. ``keyname`` is the name of the key, + and ``secret`` is the base64 encoded shared secret. ``hmac`` is the + name of the key algorithm; valid choices are ``hmac-md5``, + ``hmac-sha1``, ``hmac-sha224``, ``hmac-sha256``, ``hmac-sha384``, or + ``hmac-sha512``. If ``hmac`` is not specified, the default is + ``hmac-md5``, or if MD5 was disabled, ``hmac-sha256``. + + NOTE: Use of the :option:`-y` option is discouraged because the shared + secret is supplied as a command-line argument in clear text. This may + be visible in the output from ps1 or in a history file maintained by + the user's shell. + +Input Format +~~~~~~~~~~~~ + +:program:`nsupdate` reads input from ``filename`` or standard input. Each +command is supplied on exactly one line of input. Some commands are for +administrative purposes; others are either update instructions or +prerequisite checks on the contents of the zone. These checks set +conditions that some name or set of resource records (RRset) either +exists or is absent from the zone. These conditions must be met if the +entire update request is to succeed. Updates are rejected if the +tests for the prerequisite conditions fail. + +Every update request consists of zero or more prerequisites and zero or +more updates. This allows a suitably authenticated update request to +proceed if some specified resource records are either present or missing from +the zone. A blank input line (or the ``send`` command) causes the +accumulated commands to be sent as one Dynamic DNS update request to the +name server. + +The command formats and their meanings are as follows: + +``server servername port`` + This command sends all dynamic update requests to the name server ``servername``. + When no server statement is provided, :program:`nsupdate` sends updates + to the primary server of the correct zone. The MNAME field of that + zone's SOA record identify the primary server for that zone. + ``port`` is the port number on ``servername`` where the dynamic + update requests are sent. If no port number is specified, the default + DNS port number of 53 is used. + + .. note:: This command has no effect when GSS-TSIG is in use. + +``local address port`` + This command sends all dynamic update requests using the local ``address``. When + no local statement is provided, :program:`nsupdate` sends updates using + an address and port chosen by the system. ``port`` can also + be used to force requests to come from a specific port. If no port number + is specified, the system assigns one. + +``zone zonename`` + This command specifies that all updates are to be made to the zone ``zonename``. + If no ``zone`` statement is provided, :program:`nsupdate` attempts to + determine the correct zone to update based on the rest of the input. + +``class classname`` + This command specifies the default class. If no ``class`` is specified, the default + class is ``IN``. + +``ttl seconds`` + This command specifies the default time-to-live, in seconds, for records to be added. The value + ``none`` clears the default TTL. + +``key hmac:keyname secret`` + This command specifies that all updates are to be TSIG-signed using the + ``keyname``-``secret`` pair. If ``hmac`` is specified, it sets + the signing algorithm in use. The default is ``hmac-md5``; if MD5 + was disabled, the default is ``hmac-sha256``. The ``key`` command overrides any key + specified on the command line via :option:`-y` or :option:`-k`. + +``gsstsig`` + This command uses GSS-TSIG to sign the updates. This is equivalent to specifying + :option:`-g` on the command line. + +``oldgsstsig`` + This command uses the Windows 2000 version of GSS-TSIG to sign the updates. This is + equivalent to specifying :option:`-o` on the command line. + +``realm [realm_name]`` + When using GSS-TSIG, this command specifies the use of ``realm_name`` rather than the default realm + in ``krb5.conf``. If no realm is specified, the saved realm is + cleared. + +``check-names [boolean]`` + This command turns on or off check-names processing on records to be added. + Check-names has no effect on prerequisites or records to be deleted. + By default check-names processing is on. If check-names processing + fails, the record is not added to the UPDATE message. + +``prereq nxdomain domain-name`` + This command requires that no resource record of any type exist with the name + ``domain-name``. + +``prereq yxdomain domain-name`` + This command requires that ``domain-name`` exist (as at least one resource + record, of any type). + +``prereq nxrrset domain-name class type`` + This command requires that no resource record exist of the specified ``type``, + ``class``, and ``domain-name``. If ``class`` is omitted, IN (Internet) + is assumed. + +``prereq yxrrset domain-name class type`` + This command requires that a resource record of the specified ``type``, + ``class`` and ``domain-name`` exist. If ``class`` is omitted, IN + (internet) is assumed. + +``prereq yxrrset domain-name class type data`` + With this command, the ``data`` from each set of prerequisites of this form sharing a + common ``type``, ``class``, and ``domain-name`` are combined to form + a set of RRs. This set of RRs must exactly match the set of RRs + existing in the zone at the given ``type``, ``class``, and + ``domain-name``. The ``data`` are written in the standard text + representation of the resource record's RDATA. + +``update delete domain-name ttl class type data`` + This command deletes any resource records named ``domain-name``. If ``type`` and + ``data`` are provided, only matching resource records are removed. + The Internet class is assumed if ``class`` is not supplied. The + ``ttl`` is ignored, and is only allowed for compatibility. + +``update add domain-name ttl class type data`` + This command adds a new resource record with the specified ``ttl``, ``class``, and + ``data``. + +``show`` + This command displays the current message, containing all of the prerequisites and + updates specified since the last send. + +``send`` + This command sends the current message. This is equivalent to entering a blank + line. + +``answer`` + This command displays the answer. + +``debug`` + This command turns on debugging. + +``version`` + This command prints the version number. + +``help`` + This command prints a list of commands. + +Lines beginning with a semicolon (;) are comments and are ignored. + +Examples +~~~~~~~~ + +The examples below show how :program:`nsupdate` can be used to insert and +delete resource records from the ``example.com`` zone. Notice that the +input in each example contains a trailing blank line, so that a group of +commands is sent as one dynamic update request to the primary name +server for ``example.com``. + +:: + + # nsupdate + > update delete oldhost.example.com A + > update add newhost.example.com 86400 A 172.16.1.1 + > send + +Any A records for ``oldhost.example.com`` are deleted, and an A record +for ``newhost.example.com`` with IP address 172.16.1.1 is added. The +newly added record has a TTL of 1 day (86400 seconds). + +:: + + # nsupdate + > prereq nxdomain nickname.example.com + > update add nickname.example.com 86400 CNAME somehost.example.com + > send + +The prerequisite condition tells the name server to verify that there are +no resource records of any type for ``nickname.example.com``. If there +are, the update request fails. If this name does not exist, a CNAME for +it is added. This ensures that when the CNAME is added, it cannot +conflict with the long-standing rule in :rfc:`1034` that a name must not +exist as any other record type if it exists as a CNAME. (The rule has +been updated for DNSSEC in :rfc:`2535` to allow CNAMEs to have RRSIG, +DNSKEY, and NSEC records.) + +Files +~~~~~ + +``/etc/resolv.conf`` + Used to identify the default name server + +|session_key| + Sets the default TSIG key for use in local-only mode + +``K{name}.+157.+{random}.key`` + Base-64 encoding of the HMAC-MD5 key created by :iscman:`dnssec-keygen`. + +``K{name}.+157.+{random}.private`` + Base-64 encoding of the HMAC-MD5 key created by :iscman:`dnssec-keygen`. + +See Also +~~~~~~~~ + +:rfc:`2136`, :rfc:`3007`, :rfc:`2104`, :rfc:`2845`, :rfc:`1034`, :rfc:`2535`, :rfc:`2931`, +:iscman:`named(8) <named>`, :iscman:`dnssec-keygen(8) <dnssec-keygen>`, :iscman:`tsig-keygen(8) <tsig-keygen>`. + +Bugs +~~~~ + +The TSIG key is redundantly stored in two separate files. This is a +consequence of :program:`nsupdate` using the DST library for its cryptographic +operations, and may change in future releases. |