diff options
Diffstat (limited to 'bin/dig')
-rw-r--r-- | bin/dig/Makefile.am | 39 | ||||
-rw-r--r-- | bin/dig/Makefile.in | 897 | ||||
-rw-r--r-- | bin/dig/dig.c | 3102 | ||||
-rw-r--r-- | bin/dig/dig.rst | 809 | ||||
-rw-r--r-- | bin/dig/dighost.c | 4989 | ||||
-rw-r--r-- | bin/dig/dighost.h | 466 | ||||
-rw-r--r-- | bin/dig/host.c | 931 | ||||
-rw-r--r-- | bin/dig/host.rst | 193 | ||||
-rw-r--r-- | bin/dig/nslookup.c | 977 | ||||
-rw-r--r-- | bin/dig/nslookup.rst | 208 | ||||
-rw-r--r-- | bin/dig/readline.h | 58 |
11 files changed, 12669 insertions, 0 deletions
diff --git a/bin/dig/Makefile.am b/bin/dig/Makefile.am new file mode 100644 index 0000000..32dea5b --- /dev/null +++ b/bin/dig/Makefile.am @@ -0,0 +1,39 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) \ + $(LIBISCCFG_CFLAGS) \ + $(LIBIRS_CFLAGS) \ + $(LIBBIND9_CFLAGS) \ + $(LIBIDN2_CFLAGS) + +LDADD += \ + libdighost.la \ + $(LIBISC_LIBS) \ + $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) \ + $(LIBIRS_LIBS) \ + $(LIBBIND9_LIBS) \ + $(LIBIDN2_LIBS) + +noinst_LTLIBRARIES = libdighost.la + +libdighost_la_SOURCES = \ + dighost.h \ + dighost.c + +bin_PROGRAMS = dig host nslookup + +nslookup_CPPFLAGS = \ + $(AM_CPPFLAGS) + +nslookup_LDADD = \ + $(LDADD) + +if HAVE_READLINE +nslookup_CPPFLAGS += \ + $(READLINE_CFLAGS) +nslookup_LDADD += \ + $(READLINE_LIBS) +endif HAVE_READLINE diff --git a/bin/dig/Makefile.in b/bin/dig/Makefile.in new file mode 100644 index 0000000..e5e09ba --- /dev/null +++ b/bin/dig/Makefile.in @@ -0,0 +1,897 @@ +# 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 + +bin_PROGRAMS = dig$(EXEEXT) host$(EXEEXT) nslookup$(EXEEXT) +@HAVE_READLINE_TRUE@am__append_2 = \ +@HAVE_READLINE_TRUE@ $(READLINE_CFLAGS) + +@HAVE_READLINE_TRUE@am__append_3 = \ +@HAVE_READLINE_TRUE@ $(READLINE_LIBS) + +subdir = bin/dig +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) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libdighost_la_LIBADD = +am_libdighost_la_OBJECTS = dighost.lo +libdighost_la_OBJECTS = $(am_libdighost_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +dig_SOURCES = dig.c +dig_OBJECTS = dig.$(OBJEXT) +dig_LDADD = $(LDADD) +am__DEPENDENCIES_1 = +dig_DEPENDENCIES = libdighost.la $(LIBISC_LIBS) $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) $(LIBIRS_LIBS) $(LIBBIND9_LIBS) \ + $(am__DEPENDENCIES_1) +host_SOURCES = host.c +host_OBJECTS = host.$(OBJEXT) +host_LDADD = $(LDADD) +host_DEPENDENCIES = libdighost.la $(LIBISC_LIBS) $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) $(LIBIRS_LIBS) $(LIBBIND9_LIBS) \ + $(am__DEPENDENCIES_1) +nslookup_SOURCES = nslookup.c +nslookup_OBJECTS = nslookup-nslookup.$(OBJEXT) +am__DEPENDENCIES_2 = libdighost.la $(LIBISC_LIBS) $(LIBDNS_LIBS) \ + $(LIBISCCFG_LIBS) $(LIBIRS_LIBS) $(LIBBIND9_LIBS) \ + $(am__DEPENDENCIES_1) +@HAVE_READLINE_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) +nslookup_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) +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)/dig.Po ./$(DEPDIR)/dighost.Plo \ + ./$(DEPDIR)/host.Po ./$(DEPDIR)/nslookup-nslookup.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 = $(libdighost_la_SOURCES) dig.c host.c nslookup.c +DIST_SOURCES = $(libdighost_la_SOURCES) dig.c host.c nslookup.c +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) \ + $(LIBIDN2_CFLAGS) +AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1) +LDADD = libdighost.la $(LIBISC_LIBS) $(LIBDNS_LIBS) $(LIBISCCFG_LIBS) \ + $(LIBIRS_LIBS) $(LIBBIND9_LIBS) $(LIBIDN2_LIBS) +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 + +noinst_LTLIBRARIES = libdighost.la +libdighost_la_SOURCES = \ + dighost.h \ + dighost.c + +nslookup_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_2) +nslookup_LDADD = $(LDADD) $(am__append_3) +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/dig/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bin/dig/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 + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libdighost.la: $(libdighost_la_OBJECTS) $(libdighost_la_DEPENDENCIES) $(EXTRA_libdighost_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libdighost_la_OBJECTS) $(libdighost_la_LIBADD) $(LIBS) + +dig$(EXEEXT): $(dig_OBJECTS) $(dig_DEPENDENCIES) $(EXTRA_dig_DEPENDENCIES) + @rm -f dig$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(dig_OBJECTS) $(dig_LDADD) $(LIBS) + +host$(EXEEXT): $(host_OBJECTS) $(host_DEPENDENCIES) $(EXTRA_host_DEPENDENCIES) + @rm -f host$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(host_OBJECTS) $(host_LDADD) $(LIBS) + +nslookup$(EXEEXT): $(nslookup_OBJECTS) $(nslookup_DEPENDENCIES) $(EXTRA_nslookup_DEPENDENCIES) + @rm -f nslookup$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(nslookup_OBJECTS) $(nslookup_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dig.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dighost.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/host.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nslookup-nslookup.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 $@ $< + +nslookup-nslookup.o: nslookup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(nslookup_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nslookup-nslookup.o -MD -MP -MF $(DEPDIR)/nslookup-nslookup.Tpo -c -o nslookup-nslookup.o `test -f 'nslookup.c' || echo '$(srcdir)/'`nslookup.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nslookup-nslookup.Tpo $(DEPDIR)/nslookup-nslookup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nslookup.c' object='nslookup-nslookup.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(nslookup_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nslookup-nslookup.o `test -f 'nslookup.c' || echo '$(srcdir)/'`nslookup.c + +nslookup-nslookup.obj: nslookup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(nslookup_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nslookup-nslookup.obj -MD -MP -MF $(DEPDIR)/nslookup-nslookup.Tpo -c -o nslookup-nslookup.obj `if test -f 'nslookup.c'; then $(CYGPATH_W) 'nslookup.c'; else $(CYGPATH_W) '$(srcdir)/nslookup.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nslookup-nslookup.Tpo $(DEPDIR)/nslookup-nslookup.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nslookup.c' object='nslookup-nslookup.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(nslookup_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nslookup-nslookup.obj `if test -f 'nslookup.c'; then $(CYGPATH_W) 'nslookup.c'; else $(CYGPATH_W) '$(srcdir)/nslookup.c'; fi` + +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) $(LTLIBRARIES) +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 \ + clean-noinstLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dig.Po + -rm -f ./$(DEPDIR)/dighost.Plo + -rm -f ./$(DEPDIR)/host.Po + -rm -f ./$(DEPDIR)/nslookup-nslookup.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)/dig.Po + -rm -f ./$(DEPDIR)/dighost.Plo + -rm -f ./$(DEPDIR)/host.Po + -rm -f ./$(DEPDIR)/nslookup-nslookup.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 \ + clean-noinstLTLIBRARIES 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/dig/dig.c b/bin/dig/dig.c new file mode 100644 index 0000000..87552be --- /dev/null +++ b/bin/dig/dig.c @@ -0,0 +1,3102 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <ctype.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> + +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/dir.h> +#include <isc/netaddr.h> +#include <isc/parseint.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/util.h> + +#include <dns/byaddr.h> +#include <dns/dns64.h> +#include <dns/fixedname.h> +#include <dns/masterdump.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rcode.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdataset.h> +#include <dns/rdatatype.h> +#include <dns/tsig.h> + +#include "dighost.h" + +#define ADD_STRING(b, s) \ + { \ + if (strlen(s) >= isc_buffer_availablelength(b)) { \ + return ((((ISC_R_NOSPACE)))); \ + } else { \ + isc_buffer_putstr(b, s); \ + } \ + } + +#define DIG_MAX_ADDRESSES 20 + +dig_lookup_t *default_lookup = NULL; + +static atomic_uintptr_t batchname = 0; +static FILE *batchfp = NULL; +static char *argv0; +static int addresscount = 0; + +static char domainopt[DNS_NAME_MAXTEXT]; +static char hexcookie[81]; + +static bool short_form = false, printcmd = true, plusquest = false, + pluscomm = false, ipv4only = false, ipv6only = false, digrc = true; +static uint32_t splitwidth = 0xffffffff; + +/*% opcode text */ +static const char *const opcodetext[] = { + "QUERY", "IQUERY", "STATUS", "RESERVED3", + "NOTIFY", "UPDATE", "RESERVED6", "RESERVED7", + "RESERVED8", "RESERVED9", "RESERVED10", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15" +}; + +static const char * +rcode_totext(dns_rcode_t rcode) { + static char buf[64]; + isc_buffer_t b; + isc_result_t result; + + memset(buf, 0, sizeof(buf)); + isc_buffer_init(&b, buf + 1, sizeof(buf) - 2); + result = dns_rcode_totext(rcode, &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (strspn(buf + 1, "0123456789") == strlen(buf + 1)) { + buf[0] = '?'; + return (buf); + } + return (buf + 1); +} + +/*% print usage */ +static void +print_usage(FILE *fp) { + fprintf(fp, + "Usage: dig [@global-server] [domain] [q-type] [q-class] " + "{q-opt}\n" + " {global-d-opt} host [@local-server] " + "{local-d-opt}\n" + " [ host [@local-server] {local-d-opt} [...]]\n"); +} + +#if TARGET_OS_IPHONE +static void +usage(void) { + fprintf(stderr, "Press <Help> for complete list of options\n"); +} +#else /* if TARGET_OS_IPHONE */ +noreturn static void +usage(void); + +static void +usage(void) { + print_usage(stderr); + fprintf(stderr, "\nUse \"dig -h\" (or \"dig -h | more\") " + "for complete list of options\n"); + exit(1); +} +#endif /* if TARGET_OS_IPHONE */ + +/*% version */ +static void +version(void) { + fprintf(stderr, "DiG %s\n", PACKAGE_VERSION); +} + +/*% help */ +static void +help(void) { + print_usage(stdout); + printf("Where: domain is in the Domain Name System\n" + " q-class is one of (in,hs,ch,...) [default: in]\n" + " q-type is one of " + "(a,any,mx,ns,soa,hinfo,axfr,txt,...) " + "[default:a]\n" + " (Use ixfr=version for type ixfr)\n" + " q-opt is one of:\n" + " -4 (use IPv4 query transport " + "only)\n" + " -6 (use IPv6 query transport " + "only)\n" + " -b address[#port] (bind to source " + "address/port)\n" + " -c class (specify query class)\n" + " -f filename (batch mode)\n" + " -k keyfile (specify tsig key file)\n" + " -m (enable memory usage " + "debugging)\n" + " -p port (specify port number)\n" + " -q name (specify query name)\n" + " -r (do not read ~/.digrc)\n" + " -t type (specify query type)\n" + " -u (display times in usec " + "instead of msec)\n" + " -x dot-notation (shortcut for reverse " + "lookups)\n" + " -y [hmac:]name:key (specify named base64 " + "tsig " + "key)\n" + " d-opt is of the form +keyword[=value], where " + "keyword " + "is:\n" + " +[no]aaflag (Set AA flag in query " + "(+[no]aaflag))\n" + " +[no]aaonly (Set AA flag in query " + "(+[no]aaflag))\n" + " +[no]additional (Control display of " + "additional section)\n" + " +[no]adflag (Set AD flag in query " + "(default on))\n" + " +[no]all (Set or clear all display " + "flags)\n" + " +[no]answer (Control display of " + "answer " + "section)\n" + " +[no]authority (Control display of " + "authority section)\n" + " +[no]badcookie (Retry BADCOOKIE " + "responses)\n" + " +[no]besteffort (Try to parse even " + "illegal " + "messages)\n" + " +bufsize[=###] (Set EDNS0 Max UDP packet " + "size)\n" + " +[no]cdflag (Set checking disabled " + "flag in query)\n" + " +[no]class (Control display of class " + "in records)\n" + " +[no]cmd (Control display of " + "command line -\n" + " global option)\n" + " +[no]comments (Control display of " + "packet " + "header\n" + " and section name " + "comments)\n" + " +[no]cookie (Add a COOKIE option to " + "the request)\n" + " +[no]crypto (Control display of " + "cryptographic\n" + " fields in records)\n" + " +[no]defname (Use search list " + "(+[no]search))\n" + " +[no]dns64prefix (Get the DNS64 prefixes " + "from ipv4only.arpa)\n" + " +[no]dnssec (Request DNSSEC records)\n" + " +domain=### (Set default domainname)\n" + " +[no]edns[=###] (Set EDNS version) [0]\n" + " +ednsflags=### (Set EDNS flag bits)\n" + " +[no]ednsnegotiation (Set EDNS version " + "negotiation)\n" + " +ednsopt=###[:value] (Send specified EDNS " + "option)\n" + " +noednsopt (Clear list of +ednsopt " + "options)\n" + " +[no]expandaaaa (Expand AAAA records)\n" + " +[no]expire (Request time to expire)\n" + " +[no]fail (Don't try next server on " + "SERVFAIL)\n" + " +[no]header-only (Send query without a " + "question section)\n" + " +[no]https[=###] (DNS-over-HTTPS mode) " + "[/]\n" + " +[no]https-get (Use GET instead of " + "default POST method while using HTTPS)\n" + " +[no]http-plain[=###] (DNS over plain HTTP " + "mode) " + "[/]\n" + " +[no]http-plain-get (Use GET instead of " + "default POST method while using plain HTTP)\n" + " +[no]identify (ID responders in short " + "answers)\n" +#ifdef HAVE_LIBIDN2 + " +[no]idnin (Parse IDN names " + "[default=on on tty])\n" + " +[no]idnout (Convert IDN response " + "[default=on on tty])\n" +#endif /* ifdef HAVE_LIBIDN2 */ + " +[no]ignore (Don't revert to TCP for " + "TC responses.)\n" + " +[no]keepalive (Request EDNS TCP " + "keepalive)\n" + " +[no]keepopen (Keep the TCP socket open " + "between " + "queries)\n" + " +[no]multiline (Print records in an " + "expanded format)\n" + " +ndots=### (Set search NDOTS value)\n" + " +[no]nsid (Request Name Server ID)\n" + " +[no]nssearch (Search all authoritative " + "nameservers)\n" + " +[no]onesoa (AXFR prints only one soa " + "record)\n" + " +[no]opcode=### (Set the opcode of the " + "request)\n" + " +padding=### (Set padding block size " + "[0])\n" + " +qid=### (Specify the query ID to " + "use when sending queries)\n" + " +[no]qr (Print question before " + "sending)\n" + " +[no]question (Control display of " + "question section)\n" + " +[no]raflag (Set RA flag in query " + "(+[no]raflag))\n" + " +[no]rdflag (Recursive mode " + "(+[no]recurse))\n" + " +[no]recurse (Recursive mode " + "(+[no]rdflag))\n" + " +retry=### (Set number of UDP " + "retries) [2]\n" + " +[no]rrcomments (Control display of " + "per-record " + "comments)\n" + " +[no]search (Set whether to use " + "searchlist)\n" + " +[no]short (Display nothing except " + "short\n" + " form of answers - global " + "option)\n" + " +[no]showbadcookie (Show BADCOOKIE message)\n" + " +[no]showsearch (Search with intermediate " + "results)\n" + " +[no]split=## (Split hex/base64 fields " + "into chunks)\n" + " +[no]stats (Control display of " + "statistics)\n" + " +subnet=addr (Set edns-client-subnet " + "option)\n" + " +[no]tcflag (Set TC flag in query " + "(+[no]tcflag))\n" + " +[no]tcp (TCP mode (+[no]vc))\n" + " +timeout=### (Set query timeout) [5]\n" + " +[no]tls (DNS-over-TLS mode)\n" + " +[no]tls-ca[=file] (Enable remote server's " + "TLS certificate validation)\n" + " +[no]tls-hostname=hostname (Explicitly set " + "the expected TLS hostname)\n" + " +[no]tls-certfile=file (Load client TLS " + "certificate chain from file)\n" + " +[no]tls-keyfile=file (Load client TLS " + "private key from file)\n" + " +[no]trace (Trace delegation down " + "from root " + "[+dnssec])\n" + " +tries=### (Set number of UDP " + "attempts) [3]\n" + " +[no]ttlid (Control display of ttls " + "in records)\n" + " +[no]ttlunits (Display TTLs in " + "human-readable units)\n" + " +[no]unknownformat (Print RDATA in RFC 3597 " + "\"unknown\" " + "format)\n" + " +[no]vc (TCP mode (+[no]tcp))\n" + " +[no]yaml (Present the results as " + "YAML)\n" + " +[no]zflag (Set Z flag in query)\n" + " global d-opts and servers (before host name) affect " + "all " + "queries.\n" + " local d-opts and servers (after host name) affect only " + "that lookup.\n" + " -h (print help and exit)\n" + " -v (print version " + "and exit)\n"); +} + +/*% + * Callback from dighost.c to print the received message. + */ +static void +received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) { + uint64_t diff; + time_t tnow; + struct tm tmnow; + char time_str[100]; + char fromtext[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(from, fromtext, sizeof(fromtext)); + + if (short_form || yaml) { + return; + } + + if (query->lookup->stats) { + const char *proto; + diff = isc_time_microdiff(&query->time_recv, &query->time_sent); + if (query->lookup->use_usec) { + printf(";; Query time: %ld usec\n", (long)diff); + } else { + printf(";; Query time: %ld msec\n", (long)diff / 1000); + } + if (dig_lookup_is_tls(query->lookup)) { + proto = "TLS"; + } else if (query->lookup->https_mode) { + if (query->lookup->http_plain) { + proto = query->lookup->https_get ? "HTTP-GET" + : "HTTP"; + } else { + proto = query->lookup->https_get ? "HTTPS-GET" + : "HTTPS"; + } + } else if (query->lookup->tcp_mode) { + proto = "TCP"; + } else { + proto = "UDP"; + } + printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg, + proto); + time(&tnow); + (void)localtime_r(&tnow, &tmnow); + + if (strftime(time_str, sizeof(time_str), + "%a %b %d %H:%M:%S %Z %Y", &tmnow) > 0U) + { + printf(";; WHEN: %s\n", time_str); + } + if (query->lookup->doing_xfr) { + printf(";; XFR size: %u records (messages %u, " + "bytes %" PRIu64 ")\n", + query->rr_count, query->msg_count, + query->byte_count); + } else { + printf(";; MSG SIZE rcvd: %u\n", bytes); + } + if (tsigkey != NULL) { + if (!validated) { + puts(";; WARNING -- Some TSIG could not " + "be validated"); + } + } + if ((tsigkey == NULL) && (keysecret[0] != 0)) { + puts(";; WARNING -- TSIG key was not used."); + } + puts(""); + } else if (query->lookup->identify) { + diff = isc_time_microdiff(&query->time_recv, &query->time_sent); + if (query->lookup->use_usec) { + printf(";; Received %" PRIu64 " bytes " + "from %s(%s) in %ld us\n\n", + query->lookup->doing_xfr ? query->byte_count + : (uint64_t)bytes, + fromtext, query->userarg, (long)diff); + } else { + printf(";; Received %" PRIu64 " bytes " + "from %s(%s) in %ld ms\n\n", + query->lookup->doing_xfr ? query->byte_count + : (uint64_t)bytes, + fromtext, query->userarg, (long)diff / 1000); + } + } +} + +/* + * Callback from dighost.c to print that it is trying a server. + * Not used in dig. + * XXX print_trying + */ +static void +trying(char *frm, dig_lookup_t *lookup) { + UNUSED(frm); + UNUSED(lookup); +} + +/*% + * Internal print routine used to print short form replies. + */ +static isc_result_t +say_message(dns_rdata_t *rdata, dig_query_t *query, isc_buffer_t *buf) { + isc_result_t result; + uint64_t diff; + char store[sizeof(" in 18446744073709551616 us.")]; + unsigned int styleflags = 0; + + if (query->lookup->trace || query->lookup->ns_search_only) { + result = dns_rdatatype_totext(rdata->type, buf); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(buf, " "); + } + + /* Turn on rrcomments if explicitly enabled */ + if (query->lookup->rrcomments > 0) { + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + } + if (query->lookup->nocrypto) { + styleflags |= DNS_STYLEFLAG_NOCRYPTO; + } + if (query->lookup->print_unknown_format) { + styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT; + } + if (query->lookup->expandaaaa) { + styleflags |= DNS_STYLEFLAG_EXPANDAAAA; + } + result = dns_rdata_tofmttext(rdata, NULL, styleflags, 0, splitwidth, + " ", buf); + if (result == ISC_R_NOSPACE) { + return (result); + } + check_result(result, "dns_rdata_totext"); + if (query->lookup->identify) { + diff = isc_time_microdiff(&query->time_recv, &query->time_sent); + ADD_STRING(buf, " from server "); + ADD_STRING(buf, query->servname); + if (query->lookup->use_usec) { + snprintf(store, sizeof(store), " in %" PRIu64 " us.", + diff); + } else { + snprintf(store, sizeof(store), " in %" PRIu64 " ms.", + diff / 1000); + } + ADD_STRING(buf, store); + } + ADD_STRING(buf, "\n"); + return (ISC_R_SUCCESS); +} + +/*% + * short_form message print handler. Calls above say_message() + */ +static isc_result_t +dns64prefix_answer(dns_message_t *msg, isc_buffer_t *buf) { + dns_rdataset_t *rdataset = NULL; + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + isc_netprefix_t prefix[10]; + size_t i, count = 10; + + name = dns_fixedname_initname(&fixed); + result = dns_name_fromstring(name, "ipv4only.arpa", 0, NULL); + check_result(result, "dns_name_fromstring"); + + result = dns_message_findname(msg, DNS_SECTION_ANSWER, name, + dns_rdatatype_aaaa, dns_rdatatype_none, + NULL, &rdataset); + if (result == DNS_R_NXDOMAIN || result == DNS_R_NXRRSET) { + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_dns64_findprefix(rdataset, prefix, &count); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (count > 10) { + count = 10; + } + for (i = 0; i < count; i++) { + result = isc_netaddr_totext(&prefix[i].addr, buf); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = isc_buffer_printf(buf, "/%u\n", prefix[i].prefixlen); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +/*% + * short_form message print handler. Calls above say_message() + */ +static isc_result_t +short_answer(dns_message_t *msg, dns_messagetextflag_t flags, isc_buffer_t *buf, + dig_query_t *query) { + dns_name_t *name; + dns_rdataset_t *rdataset; + isc_result_t result, loopresult; + dns_name_t empty_name; + dns_rdata_t rdata = DNS_RDATA_INIT; + + UNUSED(flags); + + dns_name_init(&empty_name, NULL); + result = dns_message_firstname(msg, DNS_SECTION_ANSWER); + if (result == ISC_R_NOMORE) { + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + for (;;) { + name = NULL; + dns_message_currentname(msg, DNS_SECTION_ANSWER, &name); + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + loopresult = dns_rdataset_first(rdataset); + while (loopresult == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + result = say_message(&rdata, query, buf); + if (result == ISC_R_NOSPACE) { + return (result); + } + check_result(result, "say_message"); + loopresult = dns_rdataset_next(rdataset); + dns_rdata_reset(&rdata); + } + } + result = dns_message_nextname(msg, DNS_SECTION_ANSWER); + if (result == ISC_R_NOMORE) { + break; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +static bool +isdotlocal(dns_message_t *msg) { + isc_result_t result; + static unsigned char local_ndata[] = { "\005local" }; + static unsigned char local_offsets[] = { 0, 6 }; + static dns_name_t local = DNS_NAME_INITABSOLUTE(local_ndata, + local_offsets); + + for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION); + result == ISC_R_SUCCESS; + result = dns_message_nextname(msg, DNS_SECTION_QUESTION)) + { + dns_name_t *name = NULL; + dns_message_currentname(msg, DNS_SECTION_QUESTION, &name); + if (dns_name_issubdomain(name, &local)) { + return (true); + } + } + return (false); +} + +/* + * Callback from dighost.c to print the reply from a server + */ +static isc_result_t +printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg, + bool headers) { + isc_result_t result; + dns_messagetextflag_t flags; + isc_buffer_t *buf = NULL; + unsigned int len = OUTPUTBUF; + dns_master_style_t *style = NULL; + unsigned int styleflags = 0; + bool isquery = (msg == query->lookup->sendmsg); + bool dns64prefix = query->lookup->dns64prefix; + + UNUSED(msgbuf); + + dig_idnsetup(query->lookup, true); + + styleflags |= DNS_STYLEFLAG_REL_OWNER; + if (yaml) { + msg->indent.string = " "; + msg->indent.count = 3; + styleflags |= DNS_STYLEFLAG_YAML; + } else { + if (query->lookup->comments) { + styleflags |= DNS_STYLEFLAG_COMMENT; + } + if (query->lookup->print_unknown_format) { + styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT; + } + /* Turn on rrcomments if explicitly enabled */ + if (query->lookup->rrcomments > 0) { + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + } + if (query->lookup->ttlunits) { + styleflags |= DNS_STYLEFLAG_TTL_UNITS; + } + if (query->lookup->nottl) { + styleflags |= DNS_STYLEFLAG_NO_TTL; + } + if (query->lookup->noclass) { + styleflags |= DNS_STYLEFLAG_NO_CLASS; + } + if (query->lookup->nocrypto) { + styleflags |= DNS_STYLEFLAG_NOCRYPTO; + } + if (query->lookup->expandaaaa) { + styleflags |= DNS_STYLEFLAG_EXPANDAAAA; + } + if (query->lookup->multiline) { + styleflags |= DNS_STYLEFLAG_OMIT_OWNER; + styleflags |= DNS_STYLEFLAG_OMIT_CLASS; + styleflags |= DNS_STYLEFLAG_REL_DATA; + styleflags |= DNS_STYLEFLAG_OMIT_TTL; + styleflags |= DNS_STYLEFLAG_TTL; + styleflags |= DNS_STYLEFLAG_MULTILINE; + /* Turn on rrcomments unless explicitly disabled */ + if (query->lookup->rrcomments >= 0) { + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + } + } + } + if (query->lookup->multiline || + (query->lookup->nottl && query->lookup->noclass)) + { + result = dns_master_stylecreate(&style, styleflags, 24, 24, 24, + 32, 80, 8, splitwidth, mctx); + } else if (query->lookup->nottl || query->lookup->noclass) { + result = dns_master_stylecreate(&style, styleflags, 24, 24, 32, + 40, 80, 8, splitwidth, mctx); + } else { + result = dns_master_stylecreate(&style, styleflags, 24, 32, 40, + 48, 80, 8, splitwidth, mctx); + } + check_result(result, "dns_master_stylecreate"); + + if (query->lookup->cmdline[0] != 0) { + if (!short_form && !dns64prefix && printcmd) { + printf("%s", query->lookup->cmdline); + } + query->lookup->cmdline[0] = '\0'; + } + debug("printmessage(%s %s %s)", headers ? "headers" : "noheaders", + query->lookup->comments ? "comments" : "nocomments", + short_form ? "short_form" + : dns64prefix ? "dns64prefix_form" + : "long_form"); + + flags = 0; + if (!headers) { + flags |= DNS_MESSAGETEXTFLAG_NOHEADERS; + flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS; + } + if (query->lookup->onesoa && + query->lookup->rdtype == dns_rdatatype_axfr) + { + flags |= (query->msg_count == 0) ? DNS_MESSAGETEXTFLAG_ONESOA + : DNS_MESSAGETEXTFLAG_OMITSOA; + } + if (!query->lookup->comments) { + flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS; + } + + isc_buffer_allocate(mctx, &buf, len); + + if (yaml) { + enum { Q = 0x1, R = 0x2 }; /* Q:query; R:ecursive */ + unsigned int tflag = 0; + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + uint16_t sport; + char *hash; + int pf; + + printf("-\n"); + printf(" type: MESSAGE\n"); + printf(" message:\n"); + + if (isquery) { + tflag |= Q; + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) { + tflag |= R; + } + } else if (((msg->flags & DNS_MESSAGEFLAG_RD) != 0) && + ((msg->flags & DNS_MESSAGEFLAG_RA) != 0)) + { + tflag |= R; + } + + if (tflag == (Q | R)) { + printf(" type: RECURSIVE_QUERY\n"); + } else if (tflag == Q) { + printf(" type: AUTH_QUERY\n"); + } else if (tflag == R) { + printf(" type: RECURSIVE_RESPONSE\n"); + } else { + printf(" type: AUTH_RESPONSE\n"); + } + + if (!isc_time_isepoch(&query->time_sent)) { + char tbuf[100]; + if (query->lookup->use_usec) { + isc_time_formatISO8601us(&query->time_sent, + tbuf, sizeof(tbuf)); + } else { + isc_time_formatISO8601ms(&query->time_sent, + tbuf, sizeof(tbuf)); + } + printf(" query_time: !!timestamp %s\n", tbuf); + } + + if (!isquery && !isc_time_isepoch(&query->time_recv)) { + char tbuf[100]; + if (query->lookup->use_usec) { + isc_time_formatISO8601us(&query->time_recv, + tbuf, sizeof(tbuf)); + } else { + isc_time_formatISO8601ms(&query->time_recv, + tbuf, sizeof(tbuf)); + } + printf(" response_time: !!timestamp %s\n", tbuf); + } + + printf(" message_size: %ub\n", + isc_buffer_usedlength(msgbuf)); + + pf = isc_sockaddr_pf(&query->sockaddr); + if (pf == PF_INET || pf == PF_INET6) { + printf(" socket_family: %s\n", + pf == PF_INET ? "INET" : "INET6"); + + printf(" socket_protocol: %s\n", + query->lookup->tcp_mode ? "TCP" : "UDP"); + + sport = isc_sockaddr_getport(&query->sockaddr); + isc_sockaddr_format(&query->sockaddr, sockstr, + sizeof(sockstr)); + hash = strchr(sockstr, '#'); + if (hash != NULL) { + *hash = '\0'; + } + if (strcmp(sockstr, "::") == 0) { + strlcat(sockstr, "0", sizeof(sockstr)); + } + + printf(" response_address: \"%s\"\n", sockstr); + printf(" response_port: %u\n", sport); + } + + if (query->handle != NULL) { + isc_sockaddr_t saddr = + isc_nmhandle_localaddr(query->handle); + sport = isc_sockaddr_getport(&saddr); + isc_sockaddr_format(&saddr, sockstr, sizeof(sockstr)); + hash = strchr(sockstr, '#'); + if (hash != NULL) { + *hash = '\0'; + } + if (strcmp(sockstr, "::") == 0) { + strlcat(sockstr, "0", sizeof(sockstr)); + } + + printf(" query_address: \"%s\"\n", sockstr); + printf(" query_port: %u\n", sport); + } + + printf(" %s:\n", isquery ? "query_message_data" + : "response_message_data"); + result = dns_message_headertotext(msg, style, flags, buf); + } else if (query->lookup->comments && !short_form && !dns64prefix) { + if (query->lookup->cmdline[0] != '\0' && printcmd) { + printf("; %s\n", query->lookup->cmdline); + } + if (msg == query->lookup->sendmsg) { + printf(";; Sending:\n"); + } else { + printf(";; Got answer:\n"); + } + + if (headers) { + if (isdotlocal(msg)) { + printf(";; WARNING: .local is reserved for " + "Multicast DNS\n;; You are currently " + "testing what happens when an mDNS " + "query is leaked to DNS\n"); + } + printf(";; ->>HEADER<<- opcode: %s, status: %s, " + "id: %u\n", + opcodetext[msg->opcode], + rcode_totext(msg->rcode), msg->id); + printf(";; flags:"); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) { + printf(" qr"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) { + printf(" aa"); + } + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + printf(" tc"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) { + printf(" rd"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) { + printf(" ra"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) { + printf(" ad"); + } + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) { + printf(" cd"); + } + if ((msg->flags & 0x0040U) != 0) { + printf("; MBZ: 0x4"); + } + + printf("; QUERY: %u, ANSWER: %u, " + "AUTHORITY: %u, ADDITIONAL: %u\n", + msg->counts[DNS_SECTION_QUESTION], + msg->counts[DNS_SECTION_ANSWER], + msg->counts[DNS_SECTION_AUTHORITY], + msg->counts[DNS_SECTION_ADDITIONAL]); + + if (msg != query->lookup->sendmsg && + (msg->flags & DNS_MESSAGEFLAG_RD) != 0 && + (msg->flags & DNS_MESSAGEFLAG_RA) == 0) + { + printf(";; WARNING: recursion requested " + "but not available\n"); + } + } + if (msg != query->lookup->sendmsg && + query->lookup->edns != -1 && msg->opt == NULL && + (msg->rcode == dns_rcode_formerr || + msg->rcode == dns_rcode_notimp)) + { + printf("\n;; WARNING: EDNS query returned status " + "%s - retry with '%s+noedns'\n", + rcode_totext(msg->rcode), + query->lookup->dnssec ? "+nodnssec " : ""); + } + if (msg != query->lookup->sendmsg && extrabytes != 0U) { + printf(";; WARNING: Message has %u extra byte%s at " + "end\n", + extrabytes, extrabytes != 0 ? "s" : ""); + } + } + +repopulate_buffer: + + if (query->lookup->comments && headers && !short_form && !dns64prefix) { + result = dns_message_pseudosectiontotext( + msg, DNS_PSEUDOSECTION_OPT, style, flags, buf); + if (result == ISC_R_NOSPACE) { + buftoosmall: + len += OUTPUTBUF; + isc_buffer_free(&buf); + isc_buffer_allocate(mctx, &buf, len); + goto repopulate_buffer; + } + check_result(result, "dns_message_pseudosectiontotext"); + } + + if (query->lookup->section_question && headers) { + if (!short_form && !dns64prefix) { + result = dns_message_sectiontotext( + msg, DNS_SECTION_QUESTION, style, flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_sectiontotext"); + } + } + if (query->lookup->section_answer) { + if (!short_form && !dns64prefix) { + result = dns_message_sectiontotext( + msg, DNS_SECTION_ANSWER, style, flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_sectiontotext"); + } else if (dns64prefix) { + result = dns64prefix_answer(msg, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns64prefix_answer"); + } else { + result = short_answer(msg, flags, buf, query); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "short_answer"); + } + } + if (query->lookup->section_authority) { + if (!short_form && !dns64prefix) { + result = dns_message_sectiontotext( + msg, DNS_SECTION_AUTHORITY, style, flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_sectiontotext"); + } + } + if (query->lookup->section_additional) { + if (!short_form && !dns64prefix) { + result = dns_message_sectiontotext( + msg, DNS_SECTION_ADDITIONAL, style, flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_sectiontotext"); + /* + * Only print the signature on the first record. + */ + if (headers) { + result = dns_message_pseudosectiontotext( + msg, DNS_PSEUDOSECTION_TSIG, style, + flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_" + "pseudosectiontotext"); + result = dns_message_pseudosectiontotext( + msg, DNS_PSEUDOSECTION_SIG0, style, + flags, buf); + if (result == ISC_R_NOSPACE) { + goto buftoosmall; + } + check_result(result, "dns_message_" + "pseudosectiontotext"); + } + } + } + + if (headers && query->lookup->comments && !short_form && !yaml) { + printf("\n"); + } + + printf("%.*s", (int)isc_buffer_usedlength(buf), + (char *)isc_buffer_base(buf)); + isc_buffer_free(&buf); + + if (style != NULL) { + dns_master_styledestroy(&style, mctx); + } + + dig_idnsetup(query->lookup, false); + + return (result); +} + +/*% + * print the greeting message when the program first starts up. + */ +static void +printgreeting(int argc, char **argv, dig_lookup_t *lookup) { + int i; + static bool first = true; + char append[MXNAME]; + + if (printcmd) { + snprintf(lookup->cmdline, sizeof(lookup->cmdline), + "%s; <<>> DiG %s <<>>", first ? "\n" : "", + PACKAGE_VERSION); + i = 1; + while (i < argc) { + snprintf(append, sizeof(append), " %s", argv[i++]); + strlcat(lookup->cmdline, append, + sizeof(lookup->cmdline)); + } + strlcat(lookup->cmdline, "\n", sizeof(lookup->cmdline)); + if (first && addresscount != 0) { + snprintf(append, sizeof(append), + "; (%d server%s found)\n", addresscount, + addresscount > 1 ? "s" : ""); + strlcat(lookup->cmdline, append, + sizeof(lookup->cmdline)); + } + if (first) { + snprintf(append, sizeof(append), + ";; global options:%s%s\n", + short_form ? " +short" : "", + printcmd ? " +cmd" : ""); + first = false; + strlcat(lookup->cmdline, append, + sizeof(lookup->cmdline)); + } + } +} + +#define FULLCHECK(A) \ + do { \ + size_t _l = strlen(cmd); \ + if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \ + goto invalid_option; \ + } while (0) +#define FULLCHECK2(A, B) \ + do { \ + size_t _l = strlen(cmd); \ + if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \ + (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0)) \ + goto invalid_option; \ + } while (0) +#define FULLCHECK6(A, B, C, D, E, F) \ + do { \ + size_t _l = strlen(cmd); \ + if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \ + (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0) && \ + (_l >= sizeof(C) || strncasecmp(cmd, C, _l) != 0) && \ + (_l >= sizeof(D) || strncasecmp(cmd, D, _l) != 0) && \ + (_l >= sizeof(E) || strncasecmp(cmd, E, _l) != 0) && \ + (_l >= sizeof(F) || strncasecmp(cmd, F, _l) != 0)) \ + goto invalid_option; \ + } while (0) + +static bool +plus_tls_options(const char *cmd, const char *value, const bool state, + dig_lookup_t *lookup) { + /* + * Using TLS implies "TCP-like" mode. + */ + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = state; + } + switch (cmd[3]) { + case '-': + /* + * Assume that if any of the +tls-* options are set, then we + * need to verify the remote certificate (compatibility with + * kdig). + */ + if (state) { + lookup->tls_ca_set = state; + } + switch (cmd[4]) { + case 'c': + switch (cmd[5]) { + case 'a': + FULLCHECK("tls-ca"); + lookup->tls_ca_set = state; + if (state && value != NULL) { + lookup->tls_ca_file = + isc_mem_strdup(mctx, value); + } + break; + case 'e': + FULLCHECK("tls-certfile"); + lookup->tls_cert_file_set = state; + if (state) { + if (value != NULL && *value != '\0') { + lookup->tls_cert_file = + isc_mem_strdup(mctx, + value); + } else { + fprintf(stderr, + ";; TLS certificate " + "file is " + "not specified\n"); + goto invalid_option; + } + } + break; + default: + goto invalid_option; + } + break; + case 'h': + FULLCHECK("tls-hostname"); + lookup->tls_hostname_set = state; + if (state) { + if (value != NULL && *value != '\0') { + lookup->tls_hostname = + isc_mem_strdup(mctx, value); + } else { + fprintf(stderr, ";; TLS hostname is " + "not specified\n"); + goto invalid_option; + } + } + break; + case 'k': + FULLCHECK("tls-keyfile"); + lookup->tls_key_file_set = state; + if (state) { + if (value != NULL && *value != '\0') { + lookup->tls_key_file = + isc_mem_strdup(mctx, value); + } else { + fprintf(stderr, + ";; TLS private key file is " + "not specified\n"); + goto invalid_option; + } + } + break; + default: + goto invalid_option; + } + break; + case '\0': + FULLCHECK("tls"); + lookup->tls_mode = state; + break; + default: + goto invalid_option; + } + + return true; +invalid_option: + return false; +} + +/*% + * We're not using isc_commandline_parse() here since the command line + * syntax of dig is quite a bit different from that which can be described + * by that routine. + * XXX doc options + */ + +static dig_lookup_t * +plus_option(char *option, bool is_batchfile, bool *need_clone, + dig_lookup_t *lookup) { + isc_result_t result; + char *cmd, *value, *last = NULL, *code, *extra; + uint32_t num; + bool state = true; + size_t n; + + INSIST(option != NULL); + + if ((cmd = strtok_r(option, "=", &last)) == NULL) { + printf(";; Invalid option %s\n", option); + return (lookup); + } + if (strncasecmp(cmd, "no", 2) == 0) { + cmd += 2; + state = false; + } + /* parse the rest of the string */ + value = strtok_r(NULL, "", &last); + + switch (cmd[0]) { + case 'a': + switch (cmd[1]) { + case 'a': /* aaonly / aaflag */ + FULLCHECK2("aaonly", "aaflag"); + lookup->aaonly = state; + break; + case 'd': + switch (cmd[2]) { + case 'd': /* additional */ + FULLCHECK("additional"); + lookup->section_additional = state; + break; + case 'f': /* adflag */ + case '\0': /* +ad is a synonym for +adflag */ + FULLCHECK("adflag"); + lookup->adflag = state; + break; + default: + goto invalid_option; + } + break; + case 'l': /* all */ + FULLCHECK("all"); + lookup->section_question = state; + lookup->section_authority = state; + lookup->section_answer = state; + lookup->section_additional = state; + lookup->comments = state; + lookup->stats = state; + printcmd = state; + break; + case 'n': /* answer */ + FULLCHECK("answer"); + lookup->section_answer = state; + break; + case 'u': /* authority */ + FULLCHECK("authority"); + lookup->section_authority = state; + break; + default: + goto invalid_option; + } + break; + case 'b': + switch (cmd[1]) { + case 'a': /* badcookie */ + FULLCHECK("badcookie"); + lookup->badcookie = state; + break; + case 'e': /* besteffort */ + FULLCHECK("besteffort"); + lookup->besteffort = state; + break; + case 'u': /* bufsize */ + FULLCHECK("bufsize"); + if (!state) { + goto invalid_option; + } + if (value == NULL) { + lookup->udpsize = DEFAULT_EDNS_BUFSIZE; + break; + } + result = parse_uint(&num, value, COMMSIZE, + "buffer size"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse buffer size"); + goto exit_or_usage; + } + lookup->udpsize = num; + break; + default: + goto invalid_option; + } + break; + case 'c': + switch (cmd[1]) { + case 'd': /* cdflag */ + switch (cmd[2]) { + case 'f': /* cdflag */ + case '\0': /* +cd is a synonym for +cdflag */ + FULLCHECK("cdflag"); + lookup->cdflag = state; + break; + default: + goto invalid_option; + } + break; + case 'l': /* class */ + /* keep +cl for backwards compatibility */ + FULLCHECK2("cl", "class"); + lookup->noclass = !state; + break; + case 'm': /* cmd */ + FULLCHECK("cmd"); + printcmd = state; + break; + case 'o': /* comments */ + switch (cmd[2]) { + case 'm': + FULLCHECK("comments"); + lookup->comments = state; + if (lookup == default_lookup) { + pluscomm = state; + } + break; + case 'o': /* cookie */ + FULLCHECK("cookie"); + if (state && lookup->edns == -1) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + lookup->sendcookie = state; + if (value != NULL) { + n = strlcpy(hexcookie, value, + sizeof(hexcookie)); + if (n >= sizeof(hexcookie)) { + warn("COOKIE data too large"); + goto exit_or_usage; + } + lookup->cookie = hexcookie; + } else { + lookup->cookie = NULL; + } + break; + default: + goto invalid_option; + } + break; + case 'r': + FULLCHECK("crypto"); + lookup->nocrypto = !state; + break; + default: + goto invalid_option; + } + break; + case 'd': + switch (cmd[1]) { + case 'e': /* defname */ + FULLCHECK("defname"); + if (!lookup->trace) { + usesearch = state; + } + break; + case 'n': + switch (cmd[2]) { + case 's': + switch (cmd[3]) { + case '6': /* dns64prefix */ + FULLCHECK("dns64prefix"); + if (state) { + if (*need_clone) { + lookup = clone_lookup( + default_lookup, + true); + } + *need_clone = true; + lookup->dns64prefix = state; + strlcpy(lookup->textname, + "ipv4only.arpa", + sizeof(lookup->textname)); + printcmd = false; + lookup->section_additional = + false; + lookup->section_answer = true; + lookup->section_authority = + false; + lookup->section_question = + false; + lookup->comments = false; + lookup->stats = false; + lookup->rrcomments = -1; + lookup->rdtype = + dns_rdatatype_aaaa; + lookup->rdtypeset = true; + ISC_LIST_APPEND(lookup_list, + lookup, link); + } + break; + case 's': /* dnssec */ + FULLCHECK("dnssec"); + dnssec: + if (state && lookup->edns == -1) { + lookup->edns = + DEFAULT_EDNS_VERSION; + } + lookup->dnssec = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'o': /* domain ... but treat "do" as synonym for dnssec */ + if (cmd[2] == '\0') { + goto dnssec; + } + FULLCHECK("domain"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + strlcpy(domainopt, value, sizeof(domainopt)); + break; + case 's': /* dscp */ + /* obsolete */ + FULLCHECK("dscp"); + fprintf(stderr, ";; +dscp option is obsolete " + "and has no effect"); + break; + default: + goto invalid_option; + } + break; + case 'e': + switch (cmd[1]) { + case 'd': + switch (cmd[2]) { + case 'n': + switch (cmd[3]) { + case 's': + switch (cmd[4]) { + case 0: + FULLCHECK("edns"); + if (!state) { + lookup->edns = -1; + break; + } + if (value == NULL) { + lookup->edns = + DEFAULT_EDNS_VERSION; + break; + } + result = parse_uint(&num, value, + 255, + "edns"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse " + "edns"); + goto exit_or_usage; + } + lookup->edns = num; + break; + case 'f': + FULLCHECK("ednsflags"); + if (!state) { + lookup->ednsflags = 0; + break; + } + if (value == NULL) { + lookup->ednsflags = 0; + break; + } + result = parse_xint( + &num, value, 0xffff, + "ednsflags"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse " + "ednsflags"); + goto exit_or_usage; + } + lookup->ednsflags = num; + break; + case 'n': + FULLCHECK("ednsnegotiation"); + lookup->ednsneg = state; + break; + case 'o': + FULLCHECK("ednsopt"); + if (!state) { + lookup->ednsoptscnt = 0; + break; + } + code = NULL; + if (value != NULL) { + code = strtok_r(value, + ":", + &last); + } + if (code == NULL) { + warn("ednsopt no " + "code point " + "specified"); + goto exit_or_usage; + } + extra = strtok_r(NULL, "\0", + &last); + save_opt(lookup, code, extra); + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'x': + switch (cmd[2]) { + case 'p': + switch (cmd[3]) { + case 'a': + FULLCHECK("expandaaaa"); + lookup->expandaaaa = state; + break; + case 'i': + FULLCHECK("expire"); + lookup->expire = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'f': /* fail */ + switch (cmd[1]) { + case 'a': + FULLCHECK("fail"); + lookup->servfail_stops = state; + break; + case 'u': + FULLCHECK("fuzztime"); + lookup->fuzzing = state; + if (lookup->fuzzing) { + if (value == NULL) { + lookup->fuzztime = 0x622acce1; + break; + } + result = parse_uint(&num, value, 0xffffffff, + "fuzztime"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse fuzztime"); + goto exit_or_usage; + } + lookup->fuzztime = num; + } + break; + default: + goto invalid_option; + } + break; + case 'h': + switch (cmd[1]) { + case 'e': /* header-only */ + FULLCHECK("header-only"); + lookup->header_only = state; + break; + case 't': + FULLCHECK6("https", "https-get", "https-post", + "http-plain", "http-plain-get", + "http-plain-post"); +#if HAVE_LIBNGHTTP2 + if (lookup->https_path != NULL) { + isc_mem_free(mctx, lookup->https_path); + lookup->https_path = NULL; + } + if (!state) { + lookup->https_mode = false; + break; + } + lookup->https_mode = true; + if (cmd[4] == '-') { + lookup->http_plain = true; + switch (cmd[10]) { + case '\0': + FULLCHECK("http-plain"); + break; + case '-': + switch (cmd[11]) { + case 'p': + FULLCHECK("http-plain-post"); + break; + case 'g': + FULLCHECK("http-plain-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } else { + switch (cmd[5]) { + case '\0': + FULLCHECK("https"); + break; + case '-': + switch (cmd[6]) { + case 'p': + FULLCHECK("https-post"); + break; + case 'g': + FULLCHECK("https-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = state; + } + if (value == NULL) { + lookup->https_path = isc_mem_strdup( + mctx, ISC_NM_HTTP_DEFAULT_PATH); + } else { + if (!isc_nm_http_path_isvalid(value)) { + fprintf(stderr, + ";; The given HTTP path \"%s\" " + "is not " + "a valid absolute path\n", + value); + goto invalid_option; + } + lookup->https_path = isc_mem_strdup(mctx, + value); + } +#else + fprintf(stderr, ";; DoH support not enabled\n"); +#endif + break; + default: + goto invalid_option; + } + break; + case 'i': + switch (cmd[1]) { + case 'd': /* identify */ + switch (cmd[2]) { + case 'e': + FULLCHECK("identify"); + lookup->identify = state; + break; + case 'n': + switch (cmd[3]) { + case 'i': + FULLCHECK("idnin"); +#ifndef HAVE_LIBIDN2 + fprintf(stderr, ";; IDN input support" + " not enabled\n"); +#else /* ifndef HAVE_LIBIDN2 */ + lookup->idnin = state; +#endif /* ifndef HAVE_LIBIDN2 */ + break; + case 'o': + FULLCHECK("idnout"); +#ifndef HAVE_LIBIDN2 + fprintf(stderr, ";; IDN output support" + " not enabled\n"); +#else /* ifndef HAVE_LIBIDN2 */ + lookup->idnout = state; +#endif /* ifndef HAVE_LIBIDN2 */ + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'g': /* ignore */ + default: /* + * Inherits default for compatibility (+[no]i*). + */ + FULLCHECK("ignore"); + lookup->ignore = state; + } + break; + case 'k': + switch (cmd[1]) { + case 'e': + switch (cmd[2]) { + case 'e': + switch (cmd[3]) { + case 'p': + switch (cmd[4]) { + case 'a': + FULLCHECK("keepalive"); + lookup->tcp_keepalive = state; + break; + case 'o': + FULLCHECK("keepopen"); + keep_open = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'm': /* multiline */ + switch (cmd[1]) { + case 'a': + FULLCHECK("mapped"); + fprintf(stderr, ";; +mapped option is deprecated"); + break; + case 'u': + FULLCHECK("multiline"); + lookup->multiline = state; + break; + default: + goto invalid_option; + } + break; + case 'n': + switch (cmd[1]) { + case 'd': /* ndots */ + FULLCHECK("ndots"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&num, value, MAXNDOTS, "ndots"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse ndots"); + goto exit_or_usage; + } + ndots = num; + break; + case 's': + switch (cmd[2]) { + case 'i': /* nsid */ + FULLCHECK("nsid"); + if (state && lookup->edns == -1) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + lookup->nsid = state; + break; + case 's': /* nssearch */ + FULLCHECK("nssearch"); + lookup->ns_search_only = state; + if (state) { + lookup->trace_root = true; + lookup->recurse = true; + lookup->identify = true; + lookup->stats = false; + lookup->comments = false; + lookup->section_additional = false; + lookup->section_authority = false; + lookup->section_question = false; + lookup->rdtype = dns_rdatatype_ns; + lookup->rdtypeset = true; + short_form = true; + lookup->rrcomments = 0; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'o': + switch (cmd[1]) { + case 'n': + FULLCHECK("onesoa"); + lookup->onesoa = state; + break; + case 'p': + FULLCHECK("opcode"); + if (!state) { + lookup->opcode = 0; /* default - query */ + break; + } + if (value == NULL) { + goto need_value; + } + for (num = 0; + num < sizeof(opcodetext) / sizeof(opcodetext[0]); + num++) + { + if (strcasecmp(opcodetext[num], value) == 0) { + break; + } + } + if (num < 16) { + lookup->opcode = (dns_opcode_t)num; + break; + } + result = parse_uint(&num, value, 15, "opcode"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse opcode"); + goto exit_or_usage; + } + lookup->opcode = (dns_opcode_t)num; + break; + default: + goto invalid_option; + } + break; + case 'p': + FULLCHECK("padding"); + if (state && lookup->edns == -1) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + if (value == NULL) { + goto need_value; + } + result = parse_uint(&num, value, 512, "padding"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse padding"); + goto exit_or_usage; + } + lookup->padding = (uint16_t)num; + break; + case 'q': + switch (cmd[1]) { + case 'i': /* qid */ + FULLCHECK("qid"); + if (!state) { + lookup->setqid = false; + lookup->qid = 0; + break; + } + if (value == NULL) { + goto need_value; + } + result = parse_uint(&num, value, MAXQID, "qid"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse qid"); + goto exit_or_usage; + } + lookup->setqid = true; + lookup->qid = num; + break; + case 'r': /* qr */ + FULLCHECK("qr"); + lookup->qr = state; + break; + case 'u': /* question */ + FULLCHECK("question"); + lookup->section_question = state; + if (lookup == default_lookup) { + plusquest = state; + } + break; + default: + goto invalid_option; + } + break; + case 'r': + switch (cmd[1]) { + case 'a': /* raflag */ + FULLCHECK("raflag"); + lookup->raflag = state; + break; + case 'd': /* rdflag */ + FULLCHECK("rdflag"); + lookup->recurse = state; + break; + case 'e': + switch (cmd[2]) { + case 'c': /* recurse */ + FULLCHECK("recurse"); + lookup->recurse = state; + break; + case 't': /* retry / retries */ + FULLCHECK2("retry", "retries"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&lookup->retries, value, + MAXTRIES - 1, "retries"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse retries"); + goto exit_or_usage; + } + lookup->retries++; + break; + default: + goto invalid_option; + } + break; + case 'r': /* rrcomments */ + FULLCHECK("rrcomments"); + lookup->rrcomments = state ? 1 : -1; + break; + default: + goto invalid_option; + } + break; + case 's': + switch (cmd[1]) { + case 'e': /* search */ + FULLCHECK("search"); + if (!lookup->trace) { + usesearch = state; + } + break; + case 'h': + if (cmd[2] != 'o') { + goto invalid_option; + } + switch (cmd[3]) { + case 'r': /* short */ + FULLCHECK("short"); + short_form = state; + if (state) { + printcmd = false; + lookup->section_additional = false; + lookup->section_answer = true; + lookup->section_authority = false; + lookup->section_question = false; + lookup->comments = false; + lookup->stats = false; + lookup->rrcomments = -1; + } + break; + case 'w': /* showsearch */ + switch (cmd[4]) { + case 'b': + FULLCHECK("showbadcookie"); + lookup->showbadcookie = state; + break; + case 's': + FULLCHECK("showsearch"); + if (!lookup->trace) { + showsearch = state; + usesearch = state; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'i': /* sigchase */ + FULLCHECK("sigchase"); + fprintf(stderr, ";; +sigchase option is deprecated"); + break; + case 'p': /* split */ + FULLCHECK("split"); + if (value != NULL && !state) { + goto invalid_option; + } + if (!state) { + splitwidth = 0; + break; + } else if (value == NULL) { + break; + } + + result = parse_uint(&splitwidth, value, 1023, "split"); + if ((splitwidth % 4) != 0U) { + splitwidth = ((splitwidth + 3) / 4) * 4; + fprintf(stderr, + ";; Warning, split must be " + "a multiple of 4; adjusting " + "to %u\n", + splitwidth); + } + /* + * There is an adjustment done in the + * totext_<rrtype>() functions which causes + * splitwidth to shrink. This is okay when we're + * using the default width but incorrect in this + * case, so we correct for it + */ + if (splitwidth) { + splitwidth += 3; + } + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse split"); + goto exit_or_usage; + } + break; + case 't': /* stats */ + FULLCHECK("stats"); + lookup->stats = state; + break; + case 'u': /* subnet */ + FULLCHECK("subnet"); + if (state && value == NULL) { + goto need_value; + } + if (!state) { + if (lookup->ecs_addr != NULL) { + isc_mem_free(mctx, lookup->ecs_addr); + lookup->ecs_addr = NULL; + } + break; + } + if (lookup->edns == -1) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + if (lookup->ecs_addr != NULL) { + isc_mem_free(mctx, lookup->ecs_addr); + lookup->ecs_addr = NULL; + } + result = parse_netprefix(&lookup->ecs_addr, value); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse client"); + goto exit_or_usage; + } + break; + default: + goto invalid_option; + } + break; + case 't': + switch (cmd[1]) { + case 'c': /* tcp */ + switch (cmd[2]) { + case 'f': + FULLCHECK("tcflag"); + lookup->tcflag = state; + break; + case 'p': + FULLCHECK("tcp"); + if (!is_batchfile) { + lookup->tcp_mode = state; + lookup->tcp_mode_set = true; + } + break; + default: + goto invalid_option; + } + break; + case 'i': /* timeout */ + FULLCHECK("timeout"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&timeout, value, MAXTIMEOUT, + "timeout"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse timeout"); + goto exit_or_usage; + } + if (timeout == 0) { + timeout = 1; + } + break; + case 'l': + switch (cmd[2]) { + case 's': + if (!plus_tls_options(cmd, value, state, + lookup)) + { + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'o': + FULLCHECK("topdown"); + fprintf(stderr, ";; +topdown option is deprecated"); + break; + case 'r': + switch (cmd[2]) { + case 'a': /* trace */ + FULLCHECK("trace"); + lookup->trace = state; + lookup->trace_root = state; + if (state) { + lookup->recurse = true; + lookup->identify = true; + lookup->comments = false; + lookup->rrcomments = 0; + lookup->stats = false; + lookup->section_additional = false; + lookup->section_authority = true; + lookup->section_question = false; + lookup->dnssec = true; + lookup->sendcookie = true; + usesearch = false; + } + break; + case 'i': /* tries */ + FULLCHECK("tries"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&lookup->retries, value, + MAXTRIES, "tries"); + if (result != ISC_R_SUCCESS) { + warn("Couldn't parse tries"); + goto exit_or_usage; + } + if (lookup->retries == 0) { + lookup->retries = 1; + } + break; + case 'u': /* trusted-key */ + FULLCHECK("trusted-key"); + fprintf(stderr, ";; +trusted-key option is " + "deprecated"); + break; + default: + goto invalid_option; + } + break; + case 't': + switch (cmd[2]) { + case 'l': + switch (cmd[3]) { + case 0: + case 'i': /* ttlid */ + FULLCHECK2("ttl", "ttlid"); + lookup->nottl = !state; + break; + case 'u': /* ttlunits */ + FULLCHECK("ttlunits"); + lookup->nottl = false; + lookup->ttlunits = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'u': + switch (cmd[1]) { + case 'n': + switch (cmd[2]) { + case 'e': + FULLCHECK("unexpected"); + fprintf(stderr, ";; +unexpected option " + "is deprecated"); + break; + case 'k': + FULLCHECK("unknownformat"); + lookup->print_unknown_format = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + + break; + case 'v': + FULLCHECK("vc"); + if (!is_batchfile) { + lookup->tcp_mode = state; + lookup->tcp_mode_set = true; + } + break; + case 'y': /* yaml */ + FULLCHECK("yaml"); + yaml = state; + if (state) { + printcmd = false; + lookup->stats = false; + lookup->rrcomments = -1; + } + break; + case 'z': /* zflag */ + FULLCHECK("zflag"); + lookup->zflag = state; + break; + default: + invalid_option: + need_value: +#if TARGET_OS_IPHONE + exit_or_usage: +#endif /* if TARGET_OS_IPHONE */ + fprintf(stderr, "Invalid option: +%s\n", option); + usage(); + } + return (lookup); + +#if !TARGET_OS_IPHONE +exit_or_usage: + cleanup_openssl_refs(); + digexit(); +#endif /* if !TARGET_OS_IPHONE */ +} + +/*% + * #true returned if value was used + */ +static const char *single_dash_opts = "46dhimnruv"; +static const char *dash_opts = "46bcdfhikmnpqrtvyx"; +static bool +dash_option(char *option, char *next, dig_lookup_t **lookup, + bool *open_type_class, bool *need_clone, bool config_only, int argc, + char **argv, bool *firstarg) { + char opt, *value, *ptr, *ptr2, *ptr3, *last; + isc_result_t result; + bool value_from_next; + isc_textregion_t tr; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + char textname[MXNAME]; + struct in_addr in4; + struct in6_addr in6; + in_port_t srcport; + char *hash, *cmd; + uint32_t num; + + while (strpbrk(option, single_dash_opts) == &option[0]) { + /* + * Since the -[46dhimnuv] options do not take an argument, + * account for them (in any number and/or combination) + * if they appear as the first character(s) of a q-opt. + */ + opt = option[0]; + switch (opt) { + case '4': + if (have_ipv4) { + isc_net_disableipv6(); + have_ipv6 = false; + } else { + fatal("can't find IPv4 networking"); + UNREACHABLE(); + return (false); + } + break; + case '6': + if (have_ipv6) { + isc_net_disableipv4(); + have_ipv4 = false; + } else { + fatal("can't find IPv6 networking"); + UNREACHABLE(); + return (false); + } + break; + case 'd': + ptr = strpbrk(&option[1], dash_opts); + if (ptr != &option[1]) { + cmd = option; + FULLCHECK("debug"); + debugging = true; + return (false); + } else { + debugging = true; + } + break; + case 'h': + help(); + exit(0); + break; + case 'i': + /* deprecated */ + break; + case 'm': /* memdebug */ + /* memdebug is handled in preparse_args() */ + break; + case 'n': + /* deprecated */ + break; + case 'r': + debug("digrc (late)"); + digrc = false; + break; + case 'u': + (*lookup)->use_usec = true; + break; + case 'v': + version(); + exit(0); + break; + } + if (strlen(option) > 1U) { + option = &option[1]; + } else { + return (false); + } + } + opt = option[0]; + if (strlen(option) > 1U) { + value_from_next = false; + value = &option[1]; + } else { + value_from_next = true; + value = next; + } + if (value == NULL) { + goto invalid_option; + } + switch (opt) { + case 'b': + hash = strchr(value, '#'); + if (hash != NULL) { + result = parse_uint(&num, hash + 1, MAXPORT, + "port number"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse port number"); + } + srcport = num; + *hash = '\0'; + } else { + srcport = 0; + } + if (have_ipv6 && inet_pton(AF_INET6, value, &in6) == 1) { + isc_sockaddr_fromin6(&localaddr, &in6, srcport); + isc_net_disableipv4(); + } else if (have_ipv4 && inet_pton(AF_INET, value, &in4) == 1) { + isc_sockaddr_fromin(&localaddr, &in4, srcport); + isc_net_disableipv6(); + } else { + if (hash != NULL) { + *hash = '#'; + } + fatal("invalid address %s", value); + } + if (hash != NULL) { + *hash = '#'; + } + specified_source = true; + return (value_from_next); + case 'c': + if ((*lookup)->rdclassset) { + fprintf(stderr, ";; Warning, extra class option\n"); + } + *open_type_class = false; + tr.base = value; + tr.length = (unsigned int)strlen(value); + result = dns_rdataclass_fromtext(&rdclass, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) { + (*lookup)->rdclass = rdclass; + (*lookup)->rdclassset = true; + } else { + fprintf(stderr, + ";; Warning, ignoring " + "invalid class %s\n", + value); + } + return (value_from_next); + case 'f': + atomic_store(&batchname, (uintptr_t)value); + return (value_from_next); + case 'k': + strlcpy(keyfile, value, sizeof(keyfile)); + return (value_from_next); + case 'p': + result = parse_uint(&num, value, MAXPORT, "port number"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse port number"); + } + port = num; + port_set = true; + return (value_from_next); + case 'q': + if (!config_only) { + if (*need_clone) { + (*lookup) = clone_lookup(default_lookup, true); + } + *need_clone = true; + strlcpy((*lookup)->textname, value, + sizeof((*lookup)->textname)); + (*lookup)->trace_root = ((*lookup)->trace || + (*lookup)->ns_search_only); + (*lookup)->new_search = true; + if (*firstarg) { + printgreeting(argc, argv, *lookup); + *firstarg = false; + } + ISC_LIST_APPEND(lookup_list, (*lookup), link); + debug("looking up %s", (*lookup)->textname); + } + return (value_from_next); + case 't': + *open_type_class = false; + if (strncasecmp(value, "ixfr=", 5) == 0) { + rdtype = dns_rdatatype_ixfr; + result = ISC_R_SUCCESS; + } else { + tr.base = value; + tr.length = (unsigned int)strlen(value); + result = dns_rdatatype_fromtext( + &rdtype, (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS && + rdtype == dns_rdatatype_ixfr) + { + result = DNS_R_UNKNOWN; + } + } + if (result == ISC_R_SUCCESS) { + if ((*lookup)->rdtypeset) { + fprintf(stderr, ";; Warning, " + "extra type option\n"); + } + if (rdtype == dns_rdatatype_ixfr) { + uint32_t serial; + (*lookup)->rdtype = dns_rdatatype_ixfr; + (*lookup)->rdtypeset = true; + result = parse_uint(&serial, &value[5], + MAXSERIAL, "serial number"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse serial number"); + } + (*lookup)->ixfr_serial = serial; + (*lookup)->section_question = plusquest; + (*lookup)->comments = pluscomm; + if (!(*lookup)->tcp_mode_set) { + (*lookup)->tcp_mode = true; + } + } else { + (*lookup)->rdtype = rdtype; + if (!config_only) { + (*lookup)->rdtypeset = true; + } + if (rdtype == dns_rdatatype_axfr) { + (*lookup)->section_question = plusquest; + (*lookup)->comments = pluscomm; + } else if (rdtype == dns_rdatatype_any) { + if (!(*lookup)->tcp_mode_set) { + (*lookup)->tcp_mode = true; + } + } + (*lookup)->ixfr_serial = false; + } + } else { + fprintf(stderr, + ";; Warning, ignoring " + "invalid type %s\n", + value); + } + return (value_from_next); + case 'y': + if ((ptr = strtok_r(value, ":", &last)) == NULL) { + usage(); + } + if ((ptr2 = strtok_r(NULL, ":", &last)) == NULL) { /* name or + * secret */ + usage(); + } + if ((ptr3 = strtok_r(NULL, ":", &last)) != NULL) { /* secret or + * NULL */ + parse_hmac(ptr); + ptr = ptr2; + ptr2 = ptr3; + } else { + hmacname = DNS_TSIG_HMACMD5_NAME; + digestbits = 0; + } + /* XXXONDREJ: FIXME */ + strlcpy(keynametext, ptr, sizeof(keynametext)); + strlcpy(keysecret, ptr2, sizeof(keysecret)); + return (value_from_next); + case 'x': + if (*need_clone) { + *lookup = clone_lookup(default_lookup, true); + } + *need_clone = true; + if (get_reverse(textname, sizeof(textname), value, false) == + ISC_R_SUCCESS) + { + strlcpy((*lookup)->textname, textname, + sizeof((*lookup)->textname)); + debug("looking up %s", (*lookup)->textname); + (*lookup)->trace_root = ((*lookup)->trace || + (*lookup)->ns_search_only); + if (!(*lookup)->rdtypeset) { + (*lookup)->rdtype = dns_rdatatype_ptr; + } + if (!(*lookup)->rdclassset) { + (*lookup)->rdclass = dns_rdataclass_in; + } + (*lookup)->new_search = true; + if (*firstarg) { + printgreeting(argc, argv, *lookup); + *firstarg = false; + } + ISC_LIST_APPEND(lookup_list, *lookup, link); + } else { + fprintf(stderr, "Invalid IP address %s\n", value); + exit(1); + } + return (value_from_next); + invalid_option: + default: + fprintf(stderr, "Invalid option: -%s\n", option); + usage(); + } + UNREACHABLE(); + return (false); +} + +/*% + * Because we may be trying to do memory allocation recording, we're going + * to need to parse the arguments for the -m *before* we start the main + * argument parsing routine. + * + * I'd prefer not to have to do this, but I am not quite sure how else to + * fix the problem. Argument parsing in dig involves memory allocation + * by its nature, so it can't be done in the main argument parser. + */ +static void +preparse_args(int argc, char **argv) { + int rc; + char **rv; + char *option; + + rc = argc; + rv = argv; + for (rc--, rv++; rc > 0; rc--, rv++) { + if (rv[0][0] != '-') { + continue; + } + option = &rv[0][1]; + while (strpbrk(option, single_dash_opts) == &option[0]) { + switch (option[0]) { + case 'd': + /* For debugging early startup */ + debugging = true; + break; + case 'm': + memdebugging = true; + isc_mem_debugging = ISC_MEM_DEBUGTRACE | + ISC_MEM_DEBUGRECORD; + break; + case 'r': + /* + * Must be done early, because ~/.digrc + * is read before command line parsing + */ + debug("digrc (early)"); + digrc = false; + 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; + } + option = &option[1]; + } + if (strlen(option) == 0U) { + continue; + } + /* Look for dash value option. */ + if (strpbrk(option, dash_opts) != &option[0]) { + goto invalid_option; + } + if (strlen(option) > 1U) { + /* value in option. */ + continue; + } + /* Dash value is next argument so we need to skip it. */ + rc--, rv++; + /* Handle missing argument */ + if (rc == 0) { + invalid_option: + fprintf(stderr, "Invalid option: -%s\n", option); + usage(); + } + } +} + +static int +split_batchline(char *batchline, char **bargv, int len, const char *msg) { + int bargc; + char *last = NULL; + + REQUIRE(batchline != NULL); + + for (bargc = 1, bargv[bargc] = strtok_r(batchline, " \t\r\n", &last); + bargc < len && bargv[bargc]; + bargv[++bargc] = strtok_r(NULL, " \t\r\n", &last)) + { + debug("%s %d: %s", msg, bargc, bargv[bargc]); + } + return (bargc); +} + +static void +parse_args(bool is_batchfile, bool config_only, int argc, char **argv) { + isc_result_t result; + isc_textregion_t tr; + bool firstarg = true; + dig_lookup_t *lookup = NULL; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + bool open_type_class = true; + char batchline[MXNAME]; + int bargc; + char *bargv[64]; + int rc; + char **rv; +#ifndef NOPOSIX + char *homedir; + char rcfile[PATH_MAX]; +#endif /* ifndef NOPOSIX */ + bool need_clone = true; + + /* + * The semantics for parsing the args is a bit complex; if + * we don't have a host yet, make the arg apply globally, + * otherwise make it apply to the latest host. This is + * a bit different than the previous versions, but should + * form a consistent user interface. + * + * First, create a "default lookup" which won't actually be used + * anywhere, except for cloning into new lookups + */ + + debug("parse_args()"); + if (!is_batchfile) { + debug("making new lookup"); + default_lookup = make_empty_lookup(); + default_lookup->adflag = true; + default_lookup->edns = DEFAULT_EDNS_VERSION; + default_lookup->sendcookie = true; + +#ifndef NOPOSIX + /* + * Treat ${HOME}/.digrc as a special batchfile + */ + INSIST(batchfp == NULL); + homedir = getenv("HOME"); + if (homedir != NULL && digrc) { + unsigned int n; + debug("digrc (open)"); + n = snprintf(rcfile, sizeof(rcfile), "%s/.digrc", + homedir); + if (n < sizeof(rcfile)) { + batchfp = fopen(rcfile, "r"); + } + } + if (batchfp != NULL) { + while (fgets(batchline, sizeof(batchline), batchfp) != + 0) + { + debug("config line %s", batchline); + bargc = split_batchline(batchline, bargv, 62, + ".digrc argv"); + bargv[0] = argv[0]; + argv0 = argv[0]; + parse_args(true, true, bargc, (char **)bargv); + } + fclose(batchfp); + } +#endif /* ifndef NOPOSIX */ + } + + if (is_batchfile && !config_only) { + /* Processing '-f batchfile'. */ + lookup = clone_lookup(default_lookup, true); + need_clone = false; + } else { + lookup = default_lookup; + } + + rc = argc; + rv = argv; + for (rc--, rv++; rc > 0; rc--, rv++) { + debug("main parsing %s", rv[0]); + if (strncmp(rv[0], "%", 1) == 0) { + break; + } + if (rv[0][0] == '@') { + if (is_batchfile && !config_only) { + addresscount = getaddresses(lookup, &rv[0][1], + &result); + if (addresscount == 0) { + fprintf(stderr, + "couldn't get address " + "for '%s': %s: skipping " + "lookup\n", + &rv[0][1], + isc_result_totext(result)); + if (ISC_LINK_LINKED(lookup, link)) { + ISC_LIST_DEQUEUE(lookup_list, + lookup, link); + } + destroy_lookup(lookup); + return; + } + } else { + addresscount = getaddresses(lookup, &rv[0][1], + NULL); + if (addresscount == 0) { + fatal("no valid addresses for '%s'\n", + &rv[0][1]); + } + } + } else if (rv[0][0] == '+') { + lookup = plus_option(&rv[0][1], is_batchfile, + &need_clone, lookup); + } else if (rv[0][0] == '-') { + if (rc <= 1) { + if (dash_option(&rv[0][1], NULL, &lookup, + &open_type_class, &need_clone, + config_only, argc, argv, + &firstarg)) + { + rc--; + rv++; + } + } else { + if (dash_option(&rv[0][1], rv[1], &lookup, + &open_type_class, &need_clone, + config_only, argc, argv, + &firstarg)) + { + rc--; + rv++; + } + } + } else { + /* + * Anything which isn't an option + */ + if (open_type_class) { + if (strncasecmp(rv[0], "ixfr=", 5) == 0) { + rdtype = dns_rdatatype_ixfr; + result = ISC_R_SUCCESS; + } else { + tr.base = rv[0]; + tr.length = (unsigned int)strlen(rv[0]); + result = dns_rdatatype_fromtext( + &rdtype, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS && + rdtype == dns_rdatatype_ixfr) + { + fprintf(stderr, ";; Warning, " + "ixfr requires " + "a " + "serial " + "number\n"); + continue; + } + } + if (result == ISC_R_SUCCESS) { + if (lookup->rdtypeset) { + fprintf(stderr, ";; Warning, " + "extra type " + "option\n"); + } + if (rdtype == dns_rdatatype_ixfr) { + uint32_t serial; + lookup->rdtype = + dns_rdatatype_ixfr; + lookup->rdtypeset = true; + result = parse_uint(&serial, + &rv[0][5], + MAXSERIAL, + "serial " + "number"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse " + "serial number"); + } + lookup->ixfr_serial = serial; + lookup->section_question = + plusquest; + lookup->comments = pluscomm; + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = true; + } + } else { + lookup->rdtype = rdtype; + lookup->rdtypeset = true; + if (rdtype == + dns_rdatatype_axfr) + { + lookup->section_question = + plusquest; + lookup->comments = + pluscomm; + } + if (rdtype == + dns_rdatatype_any && + !lookup->tcp_mode_set) + { + lookup->tcp_mode = true; + } + lookup->ixfr_serial = false; + } + continue; + } + result = dns_rdataclass_fromtext( + &rdclass, (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) { + if (lookup->rdclassset) { + fprintf(stderr, ";; Warning, " + "extra class " + "option\n"); + } + lookup->rdclass = rdclass; + lookup->rdclassset = true; + continue; + } + } + + if (!config_only) { + if (need_clone) { + lookup = clone_lookup(default_lookup, + true); + } + need_clone = true; + strlcpy(lookup->textname, rv[0], + sizeof(lookup->textname)); + lookup->trace_root = (lookup->trace || + lookup->ns_search_only); + lookup->new_search = true; + if (firstarg) { + printgreeting(argc, argv, lookup); + firstarg = false; + } + ISC_LIST_APPEND(lookup_list, lookup, link); + debug("looking up %s", lookup->textname); + } + /* XXX Error message */ + } + } + + /* + * If we have a batchfile, seed the lookup list with the + * first entry, then trust the callback in dighost_shutdown + * to get the rest + */ + char *filename = (char *)atomic_load(&batchname); + if ((filename != NULL) && !(is_batchfile)) { + if (strcmp(filename, "-") == 0) { + batchfp = stdin; + } else { + batchfp = fopen(filename, "r"); + } + if (batchfp == NULL) { + perror(filename); + if (exitcode < 8) { + exitcode = 8; + } + fatal("couldn't open specified batch file"); + } + /* XXX Remove code dup from shutdown code */ + next_line: + if (fgets(batchline, sizeof(batchline), batchfp) != 0) { + debug("batch line %s", batchline); + if (batchline[0] == '\r' || batchline[0] == '\n' || + batchline[0] == '#' || batchline[0] == ';') + { + goto next_line; + } + bargc = split_batchline(batchline, bargv, 14, + "batch argv"); + bargv[0] = argv[0]; + argv0 = argv[0]; + parse_args(true, false, bargc, (char **)bargv); + return; + } + return; + } + /* + * If no lookup specified, search for root + */ + if ((lookup_list.head == NULL) && !config_only) { + if (need_clone) { + lookup = clone_lookup(default_lookup, true); + } + need_clone = true; + lookup->trace_root = (lookup->trace || lookup->ns_search_only); + lookup->new_search = true; + strlcpy(lookup->textname, ".", sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_ns; + lookup->rdtypeset = true; + if (firstarg) { + printgreeting(argc, argv, lookup); + firstarg = false; + } + ISC_LIST_APPEND(lookup_list, lookup, link); + } + if (!need_clone) { + destroy_lookup(lookup); + } +} + +/* + * Callback from dighost.c to allow program-specific shutdown code. + * Here, we're possibly reading from a batch file, then shutting down + * for real if there's nothing in the batch file to read. + */ +static void +query_finished(void) { + char batchline[MXNAME]; + int bargc; + char *bargv[16]; + + if (atomic_load(&batchname) == 0) { + isc_app_shutdown(); + return; + } + + fflush(stdout); + if (feof(batchfp)) { + atomic_store(&batchname, 0); + isc_app_shutdown(); + if (batchfp != stdin) { + fclose(batchfp); + } + return; + } + + if (fgets(batchline, sizeof(batchline), batchfp) != 0) { + debug("batch line %s", batchline); + bargc = split_batchline(batchline, bargv, 14, "batch argv"); + bargv[0] = argv0; + parse_args(true, false, bargc, (char **)bargv); + start_lookup(); + } else { + atomic_store(&batchname, 0); + if (batchfp != stdin) { + fclose(batchfp); + } + isc_app_shutdown(); + return; + } +} + +static void +dig_error(const char *format, ...) { + va_list args; + + if (yaml) { + printf("-\n"); + printf(" type: DIG_ERROR\n"); + + /* + * Print an indent before a literal block quote. + * Note: this will break if used to print more than + * one line of text as only the first line would be + * indented. + */ + printf(" message: |\n"); + printf(" "); + } else { + printf(";; "); + } + + va_start(args, format); + vprintf(format, args); + va_end(args); + + if (!yaml) { + printf("\n"); + } +} + +static void +dig_warning(const char *format, ...) { + va_list args; + + if (!yaml) { + printf(";; "); + + va_start(args, format); + vprintf(format, args); + va_end(args); + + printf("\n"); + } +} + +static void +dig_comments(dig_lookup_t *lookup, const char *format, ...) { + va_list args; + + if (lookup->comments && !yaml) { + printf(";; "); + + va_start(args, format); + vprintf(format, args); + va_end(args); + + printf("\n"); + } +} + +void +dig_setup(int argc, char **argv) { + isc_result_t result; + + ISC_LIST_INIT(lookup_list); + ISC_LIST_INIT(server_list); + ISC_LIST_INIT(search_list); + + debug("dig_setup()"); + + /* setup dighost callbacks */ + dighost_printmessage = printmessage; + dighost_received = received; + dighost_trying = trying; + dighost_shutdown = query_finished; + dighost_error = dig_error; + dighost_warning = dig_warning; + dighost_comments = dig_comments; + + progname = argv[0]; + preparse_args(argc, argv); + + result = isc_app_start(); + check_result(result, "isc_app_start"); + + setup_libs(); + setup_system(ipv4only, ipv6only); +} + +void +dig_query_setup(bool is_batchfile, bool config_only, int argc, char **argv) { + debug("dig_query_setup"); + + parse_args(is_batchfile, config_only, argc, argv); + if (keyfile[0] != 0) { + setup_file_key(); + } else if (keysecret[0] != 0) { + setup_text_key(); + } + if (domainopt[0] != '\0') { + set_search_domain(domainopt); + usesearch = true; + } +} + +void +dig_startup(void) { + isc_result_t result; + + debug("dig_startup()"); + + result = isc_app_onrun(mctx, global_task, onrun_callback, NULL); + check_result(result, "isc_app_onrun"); + isc_app_run(); +} + +void +dig_query_start(void) { + start_lookup(); +} + +void +dig_shutdown(void) { + destroy_lookup(default_lookup); + if (atomic_load(&batchname) != 0) { + if (batchfp != stdin) { + fclose(batchfp); + } + atomic_store(&batchname, 0); + } + cancel_all(); + destroy_libs(); + isc_app_finish(); +} + +/*% Main processing routine for dig */ +int +main(int argc, char **argv) { + dig_setup(argc, argv); + dig_query_setup(false, false, argc, argv); + dig_startup(); + dig_shutdown(); + + return (exitcode); +} diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst new file mode 100644 index 0000000..59ac9f1 --- /dev/null +++ b/bin/dig/dig.rst @@ -0,0 +1,809 @@ +.. Copyright (C) Internet Systems Consortium, Inc. ("ISC") +.. +.. SPDX-License-Identifier: MPL-2.0 +.. +.. This Source Code Form is subject to the terms of the Mozilla Public +.. License, v. 2.0. If a copy of the MPL was not distributed with this +.. file, you can obtain one at https://mozilla.org/MPL/2.0/. +.. +.. See the COPYRIGHT file distributed with this work for additional +.. information regarding copyright ownership. + +.. highlight: console + +.. iscman:: dig +.. program:: dig +.. _man_dig: + +dig - DNS lookup utility +------------------------ + +Synopsis +~~~~~~~~ +:program:`dig` [@server] [**-b** address] [**-c** class] [**-f** filename] [**-k** filename] [**-m**] [**-p** port#] [**-q** name] [**-t** type] [**-v**] [**-x** addr] [**-y** [hmac:]name:key] [ [**-4**] | [**-6**] ] [name] [type] [class] [queryopt...] + +:program:`dig` [**-h**] + +:program:`dig` [global-queryopt...] [query...] + +Description +~~~~~~~~~~~ + +:program:`dig` is a flexible tool for interrogating DNS name servers. It +performs DNS lookups and displays the answers that are returned from the +name server(s) that were queried. Most DNS administrators use :program:`dig` to +troubleshoot DNS problems because of its flexibility, ease of use, and +clarity of output. Other lookup tools tend to have less functionality +than :program:`dig`. + +Although :program:`dig` is normally used with command-line arguments, it also +has a batch mode of operation for reading lookup requests from a file. A +brief summary of its command-line arguments and options is printed when +the :option:`-h` option is given. The BIND 9 +implementation of :program:`dig` allows multiple lookups to be issued from the +command line. + +Unless it is told to query a specific name server, :program:`dig` tries each +of the servers listed in ``/etc/resolv.conf``. If no usable server +addresses are found, :program:`dig` sends the query to the local host. + +When no command-line arguments or options are given, :program:`dig` +performs an NS query for "." (the root). + +It is possible to set per-user defaults for :program:`dig` via +``${HOME}/.digrc``. This file is read and any options in it are applied +before the command-line arguments. The :option:`-r` option disables this +feature, for scripts that need predictable behavior. + +The IN and CH class names overlap with the IN and CH top-level domain +names. Either use the :option:`-t` and :option:`-c` options to specify the type and +class, use the :option:`-q` to specify the domain name, or use "IN." and +"CH." when looking up these top-level domains. + +Simple Usage +~~~~~~~~~~~~ + +A typical invocation of :program:`dig` looks like: + +:: + + dig @server name type + +where: + +.. option:: server + + is the name or IP address of the name server to query. This can be an + IPv4 address in dotted-decimal notation or an IPv6 address in + colon-delimited notation. When the supplied ``server`` argument is a + hostname, :program:`dig` resolves that name before querying that name + server. + + If no ``server`` argument is provided, :program:`dig` consults + ``/etc/resolv.conf``; if an address is found there, it queries the + name server at that address. If either of the :option:`-4` or :option:`-6` + options are in use, then only addresses for the corresponding + transport are tried. If no usable addresses are found, :program:`dig` + sends the query to the local host. The reply from the name server + that responds is displayed. + +.. option:: name + + is the name of the resource record that is to be looked up. + +.. option:: type + + indicates what type of query is required - ANY, A, MX, SIG, etc. + ``type`` can be any valid query type. If no ``type`` argument is + supplied, :program:`dig` performs a lookup for an A record. + +Options +~~~~~~~ + +.. option:: -4 + + This option indicates that only IPv4 should be used. + +.. option:: -6 + + This option indicates that only IPv6 should be used. + +.. option:: -b address[#port] + + This option sets the source IP address of the query. The ``address`` must be a + valid address on one of the host's network interfaces, or "0.0.0.0" + or "::". An optional port may be specified by appending ``#port``. + +.. option:: -c class + + This option sets the query class. The default ``class`` is IN; other classes are + HS for Hesiod records or CH for Chaosnet records. + +.. option:: -f file + + This option sets batch mode, in which :program:`dig` reads a list of lookup requests to process from + the given ``file``. Each line in the file should be organized in the + same way it would be presented as a query to :program:`dig` using the + command-line interface. + +.. option:: -h + + Print a usage summary. + +.. option:: -k keyfile + + This option tells :program:`dig` to sign queries using TSIG or + SIG(0) using a key read from the given file. Key files can be + generated using :iscman:`tsig-keygen`. When using TSIG authentication + with :program:`dig`, the name server that is queried needs to + know the key and algorithm that is being used. In BIND, this is + done by providing appropriate ``key`` and ``server`` statements + in :iscman:`named.conf` for TSIG and by looking up the KEY record + in zone data for SIG(0). + +.. option:: -m + + This option enables memory usage debugging. + +.. option:: -p port + + This option sends the query to a non-standard port on the server, instead of the + default port 53. This option is used to test a name server that + has been configured to listen for queries on a non-standard port + number. + +.. option:: -q name + + This option specifies the domain name to query. This is useful to distinguish the ``name`` + from other arguments. + +.. option:: -r + + This option indicates that options from ``${HOME}/.digrc`` should not be read. This is useful for + scripts that need predictable behavior. + +.. option:: -t type + + This option indicates the resource record type to query, which can be any valid query type. If + it is a resource record type supported in BIND 9, it can be given by + the type mnemonic (such as ``NS`` or ``AAAA``). The default query type is + ``A``, unless the :option:`-x` option is supplied to indicate a reverse + lookup. A zone transfer can be requested by specifying a type of + AXFR. When an incremental zone transfer (IXFR) is required, set the + ``type`` to ``ixfr=N``. The incremental zone transfer contains + all changes made to the zone since the serial number in the zone's + SOA record was ``N``. + + All resource record types can be expressed as ``TYPEnn``, where ``nn`` is + the number of the type. If the resource record type is not supported + in BIND 9, the result is displayed as described in :rfc:`3597`. + +.. option:: -u + + This option indicates that print query times should be provided in microseconds instead of milliseconds. + +.. option:: -v + + This option prints the version number and exits. + +.. option:: -x addr + + This option sets simplified reverse lookups, for mapping addresses to names. The + ``addr`` is an IPv4 address in dotted-decimal notation, or a + colon-delimited IPv6 address. When the :option:`-x` option is used, there is no + need to provide the ``name``, ``class``, and ``type`` arguments. + :program:`dig` automatically performs a lookup for a name like + ``94.2.0.192.in-addr.arpa`` and sets the query type and class to PTR + and IN respectively. IPv6 addresses are looked up using nibble format + under the IP6.ARPA domain. + +.. option:: -y [hmac:]keyname:secret + + This option signs queries using TSIG with the given 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``; if MD5 was disabled, the default is + ``hmac-sha256``. + +.. note:: Only the :option:`-k` option should be used, rather than the :option:`-y` option, + because with :option:`-y` 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. + +Query Options +~~~~~~~~~~~~~ + +:program:`dig` provides a number of query options which affect the way in which +lookups are made and the results displayed. Some of these set or reset +flag bits in the query header, some determine which sections of the +answer get printed, and others determine the timeout and retry +strategies. + +Each query option is identified by a keyword preceded by a plus sign +(``+``). Some keywords set or reset an option; these may be preceded by +the string ``no`` to negate the meaning of that keyword. Other keywords +assign values to options, like the timeout interval. They have the form +``+keyword=value``. Keywords may be abbreviated, provided the +abbreviation is unambiguous; for example, :option:`+cd` is equivalent to +:option:`+cdflag`. The query options are: + +.. option:: +aaflag, +noaaflag + + This option is a synonym for :option:`+aaonly`, :option:`+noaaonly`. + +.. option:: +aaonly, +noaaonly + + This option sets the ``aa`` flag in the query. + +.. option:: +additional, +noadditional + + This option displays [or does not display] the additional section of a reply. The + default is to display it. + +.. option:: +adflag, +noadflag + + This option sets [or does not set] the AD (authentic data) bit in the query. This + requests the server to return whether all of the answer and authority + sections have been validated as secure, according to the security + policy of the server. ``AD=1`` indicates that all records have been + validated as secure and the answer is not from a OPT-OUT range. ``AD=0`` + indicates that some part of the answer was insecure or not validated. + This bit is set by default. + +.. option:: +all, +noall + + This option sets or clears all display flags. + +.. option:: +answer, +noanswer + + This option displays [or does not display] the answer section of a reply. The default + is to display it. + +.. option:: +authority, +noauthority + + This option displays [or does not display] the authority section of a reply. The + default is to display it. + +.. option:: +badcookie, +nobadcookie + + This option retries the lookup with a new server cookie if a BADCOOKIE response is + received. + +.. option:: +besteffort, +nobesteffort + + This option attempts to display the contents of messages which are malformed. The + default is to not display malformed answers. + +.. option:: +bufsize[=B] + + This option sets the UDP message buffer size advertised using EDNS0 to + ``B`` bytes. The maximum and minimum sizes of this buffer are 65535 and + 0, respectively. ``+bufsize`` restores the default buffer size. + +.. option:: +cd, +cdflag, +nocdflag + + This option sets [or does not set] the CD (checking disabled) bit in the query. This + requests the server to not perform DNSSEC validation of responses. + +.. option:: +class, +noclass + + This option displays [or does not display] the CLASS when printing the record. + +.. option:: +cmd, +nocmd + + This option toggles the printing of the initial comment in the output, identifying the + version of :program:`dig` and the query options that have been applied. This option + always has a global effect; it cannot be set globally and then overridden on a + per-lookup basis. The default is to print this comment. + +.. option:: +comments, +nocomments + + This option toggles the display of some comment lines in the output, with + information about the packet header and OPT pseudosection, and the names of + the response section. The default is to print these comments. + + Other types of comments in the output are not affected by this option, but + can be controlled using other command-line switches. These include + :option:`+cmd`, :option:`+question`, :option:`+stats`, and :option:`+rrcomments`. + +.. option:: +cookie=####, +nocookie + + This option sends [or does not send] a COOKIE EDNS option, with an optional value. Replaying a COOKIE + from a previous response allows the server to identify a previous + client. The default is ``+cookie``. + + ``+cookie`` is also set when :option:`+trace` is set to better emulate the + default queries from a nameserver. + +.. option:: +crypto, +nocrypto + + This option toggles the display of cryptographic fields in DNSSEC records. The + contents of these fields are unnecessary for debugging most DNSSEC + validation failures and removing them makes it easier to see the + common failures. The default is to display the fields. When omitted, + they are replaced by the string ``[omitted]`` or, in the DNSKEY case, the + key ID is displayed as the replacement, e.g. ``[ key id = value ]``. + +.. option:: +defname, +nodefname + + This option, which is deprecated, is treated as a synonym for + :option:`+search`, :option:`+nosearch`. + +.. option:: +dns64prefix, +nodns64prefix + + Lookup IPV4ONLY.ARPA AAAA and print any DNS64 prefixes found. + +.. option:: +dnssec, +do, +nodnssec, +nodo + + This option requests that DNSSEC records be sent by setting the DNSSEC OK (DO) bit in + the OPT record in the additional section of the query. + +.. option:: +domain=somename + + This option sets the search list to contain the single domain ``somename``, as if + specified in a ``domain`` directive in ``/etc/resolv.conf``, and + enables search list processing as if the :option:`+search` option were + given. + +.. option:: +dscp=value + + This option formerly set the DSCP value used when sending a query. + It is now obsolete, and has no effect. + +.. option:: +edns[=#], +noedns + + This option specifies the EDNS version to query with. Valid values are 0 to 255. + Setting the EDNS version causes an EDNS query to be sent. + ``+noedns`` clears the remembered EDNS version. EDNS is set to 0 by + default. + +.. option:: +ednsflags[=#], +noednsflags + + This option sets the must-be-zero EDNS flags bits (Z bits) to the specified value. + Decimal, hex, and octal encodings are accepted. Setting a named flag + (e.g., DO) is silently ignored. By default, no Z bits are set. + +.. option:: +ednsnegotiation, +noednsnegotiation + + This option enables/disables EDNS version negotiation. By default, EDNS version + negotiation is enabled. + +.. option:: +ednsopt[=code[:value]], +noednsopt + + This option specifies the EDNS option with code point ``code`` and an optional payload + of ``value`` as a hexadecimal string. ``code`` can be either an EDNS + option name (for example, ``NSID`` or ``ECS``) or an arbitrary + numeric value. ``+noednsopt`` clears the EDNS options to be sent. + +.. option:: +expire, +noexpire + + This option sends an EDNS Expire option. + +.. option:: +fail, +nofail + + This option indicates that :iscman:`named` should try [or not try] the next server if a SERVFAIL is received. The default is + to not try the next server, which is the reverse of normal stub + resolver behavior. + +.. option:: +fuzztime[=value], +nofuzztime + + This option allows the signing time to be specified when generating + signed messages. If a value is specified it is the seconds since + 00:00:00 January 1, 1970 UTC ignoring leap seconds. If no value + is specified 1646972129 (Fri 11 Mar 2022 04:15:29 UTC) is used. + The default is ``+nofuzztime`` and the current time is used. + +.. option:: +header-only, +noheader-only + + This option sends a query with a DNS header without a question section. The + default is to add a question section. The query type and query name + are ignored when this is set. + +.. option:: +https[=value], +nohttps + + This option indicates whether to use DNS over HTTPS (DoH) when querying + name servers. When this option is in use, the port number defaults to 443. + The HTTP POST request mode is used when sending the query. + + If ``value`` is specified, it will be used as the HTTP endpoint in the + query URI; the default is ``/dns-query``. So, for example, ``dig + @example.com +https`` will use the URI ``https://example.com/dns-query``. + +.. option:: +https-get[=value], +nohttps-get + + Similar to :option:`+https`, except that the HTTP GET request mode is used + when sending the query. + +.. option:: +https-post[=value], +nohttps-post + + Same as :option:`+https`. + +.. option:: +http-plain[=value], +nohttp-plain + + Similar to :option:`+https`, except that HTTP queries will be sent over a + non-encrypted channel. When this option is in use, the port number + defaults to 80 and the HTTP request mode is POST. + +.. option:: +http-plain-get[=value], +nohttp-plain-get + + Similar to :option:`+http-plain`, except that the HTTP request mode is GET. + +.. option:: +http-plain-post[=value], +nohttp-plain-post + + Same as :option:`+http-plain`. + +.. option:: +identify, +noidentify + + This option shows [or does not show] the IP address and port number that + supplied the answer, when the :option:`+short` option is enabled. If short + form answers are requested, the default is not to show the source + address and port number of the server that provided the answer. + +.. option:: +idnin, +noidnin + + This option processes [or does not process] IDN domain names on input. This requires + ``IDN SUPPORT`` to have been enabled at compile time. + + The default is to process IDN input when standard output is a tty. + The IDN processing on input is disabled when :program:`dig` output is redirected + to files, pipes, and other non-tty file descriptors. + +.. option:: +idnout, +noidnout + + This option converts [or does not convert] puny code on output. This requires + ``IDN SUPPORT`` to have been enabled at compile time. + + The default is to process puny code on output when standard output is + a tty. The puny code processing on output is disabled when :program:`dig` output + is redirected to files, pipes, and other non-tty file descriptors. + +.. option:: +ignore, +noignore + + This option ignores [or does not ignore] truncation in UDP responses instead of retrying with TCP. By + default, TCP retries are performed. + +.. option:: +keepalive, +nokeepalive + + This option sends [or does not send] an EDNS Keepalive option. + +.. option:: +keepopen, +nokeepopen + + This option keeps [or does not keep] the TCP socket open between queries, and reuses it rather than + creating a new TCP socket for each lookup. The default is + ``+nokeepopen``. + +.. option:: +multiline, +nomultiline + + This option prints [or does not print] records, like the SOA records, in a verbose multi-line format + with human-readable comments. The default is to print each record on + a single line to facilitate machine parsing of the :program:`dig` output. + +.. option:: +ndots=D + + This option sets the number of dots (``D``) that must appear in ``name`` for + it to be considered absolute. The default value is that defined using + the ``ndots`` statement in ``/etc/resolv.conf``, or 1 if no ``ndots`` + statement is present. Names with fewer dots are interpreted as + relative names, and are searched for in the domains listed in the + ``search`` or ``domain`` directive in ``/etc/resolv.conf`` if + :option:`+search` is set. + +.. option:: +nsid, +nonsid + + When enabled, this option includes an EDNS name server ID request when sending a query. + +.. option:: +nssearch, +nonssearch + + When this option is set, :program:`dig` attempts to find the authoritative + name servers for the zone containing the name being looked up, and + display the SOA record that each name server has for the zone. + Addresses of servers that did not respond are also printed. + +.. option:: +onesoa, +noonesoa + + When enabled, this option prints only one (starting) SOA record when performing an AXFR. The + default is to print both the starting and ending SOA records. + +.. option:: +opcode=value, +noopcode + + When enabled, this option sets (restores) the DNS message opcode to the specified value. The + default value is QUERY (0). + +.. option:: +padding=value + + This option pads the size of the query packet using the EDNS Padding option to + blocks of ``value`` bytes. For example, ``+padding=32`` causes a + 48-byte query to be padded to 64 bytes. The default block size is 0, + which disables padding; the maximum is 512. Values are ordinarily + expected to be powers of two, such as 128; however, this is not + mandatory. Responses to padded queries may also be padded, but only + if the query uses TCP or DNS COOKIE. + +.. option:: +qid=value + + This option specifies the query ID to use when sending queries. + +.. option:: +qr, +noqr + + This option toggles the display of the query message as it is sent. By default, the query + is not printed. + +.. option:: +question, +noquestion + + This option toggles the display of the question section of a query when an answer is + returned. The default is to print the question section as a comment. + +.. option:: +raflag, +noraflag + + This option sets [or does not set] the RA (Recursion Available) bit in the query. The + default is ``+noraflag``. This bit is ignored by the server for + QUERY. + +.. option:: +rdflag, +nordflag + + This option is a synonym for :option:`+recurse`, :option:`+norecurse`. + +.. option:: +recurse, +norecurse + + This option toggles the setting of the RD (recursion desired) bit in the query. + This bit is set by default, which means :program:`dig` normally sends + recursive queries. Recursion is automatically disabled when the + :option:`+nssearch` or :option:`+trace` query option is used. + +.. option:: +retry=T + + This option sets the number of times to retry UDP and TCP queries to server to ``T`` + instead of the default, 2. Unlike :option:`+tries`, this does not include + the initial query. + +.. option:: +rrcomments, +norrcomments + + This option toggles the display of per-record comments in the output (for example, + human-readable key information about DNSKEY records). The default is + not to print record comments unless multiline mode is active. + +.. option:: +search, +nosearch + + This option uses [or does not use] the search list defined by the searchlist or domain + directive in ``resolv.conf``, if any. The search list is not used by + default. + + ``ndots`` from ``resolv.conf`` (default 1), which may be overridden by + :option:`+ndots`, determines whether the name is treated as relative + and hence whether a search is eventually performed. + +.. option:: +short, +noshort + + This option toggles whether a terse answer is provided. The default is to print the answer in a verbose + form. This option always has a global effect; it cannot be set globally and + then overridden on a per-lookup basis. + +.. option:: +showbadcookie, +noshowbadcookie + + This option toggles whether to show the message containing the + BADCOOKIE rcode before retrying the request or not. The default + is to not show the messages. + +.. option:: +showsearch, +noshowsearch + + This option performs [or does not perform] a search showing intermediate results. + +.. option:: +sigchase, +nosigchase + + This feature is now obsolete and has been removed; use :iscman:`delv` + instead. + +.. option:: +split=W + + This option splits long hex- or base64-formatted fields in resource records into + chunks of ``W`` characters (where ``W`` is rounded up to the nearest + multiple of 4). ``+nosplit`` or ``+split=0`` causes fields not to be + split at all. The default is 56 characters, or 44 characters when + multiline mode is active. + +.. option:: +stats, +nostats + + This option toggles the printing of statistics: when the query was made, the size of the + reply, etc. The default behavior is to print the query statistics as a + comment after each lookup. + +.. option:: +subnet=addr[/prefix-length], +nosubnet + + This option sends [or does not send] an EDNS CLIENT-SUBNET option with the specified IP + address or network prefix. + + ``dig +subnet=0.0.0.0/0``, or simply ``dig +subnet=0`` for short, + sends an EDNS CLIENT-SUBNET option with an empty address and a source + prefix-length of zero, which signals a resolver that the client's + address information must *not* be used when resolving this query. + +.. option:: +tcflag, +notcflag + + This option sets [or does not set] the TC (TrunCation) bit in the query. The default is + ``+notcflag``. This bit is ignored by the server for QUERY. + +.. option:: +tcp, +notcp + + This option indicates whether to use TCP when querying name + servers. The default behavior is to use UDP unless a type ``any`` + or ``ixfr=N`` query is requested, in which case the default is + TCP. AXFR queries always use TCP. To prevent retry over TCP when + TC=1 is returned from a UDP query, use ``+ignore``. + +.. option:: +timeout=T + + This option sets the timeout for a query to ``T`` seconds. The default timeout is + 5 seconds. An attempt to set ``T`` to less than 1 is silently set to 1. + +.. option:: +tls, +notls + + This option indicates whether to use DNS over TLS (DoT) when querying + name servers. When this option is in use, the port number defaults + to 853. + +.. option:: +tls-ca[=file-name], +notls-ca + + This option enables remote server TLS certificate validation for + DNS transports, relying on TLS. Certificate authorities + certificates are loaded from the specified PEM file + (``file-name``). If the file is not specified, the default + certificates from the global certificates store are used. + +.. option:: +tls-certfile=file-name, +tls-keyfile=file-name, +notls-certfile, +notls-keyfile + + These options set the state of certificate-based client + authentication for DNS transports, relying on TLS. Both certificate + chain file and private key file are expected to be in PEM format. + Both options must be specified at the same time. + +.. option:: +tls-hostname=hostname, +notls-hostname + + This option makes :program:`dig` use the provided hostname during remote + server TLS certificate verification. Otherwise, the DNS server name + is used. This option has no effect if :option:`+tls-ca` is not specified. + +.. option:: +topdown, +notopdown + + This feature is related to :option:`dig +sigchase`, which is obsolete and + has been removed. Use :iscman:`delv` instead. + +.. option:: +trace, +notrace + + This option toggles tracing of the delegation path from the root name servers for + the name being looked up. Tracing is disabled by default. When + tracing is enabled, :program:`dig` makes iterative queries to resolve the + name being looked up. It follows referrals from the root servers, + showing the answer from each server that was used to resolve the + lookup. + + If ``@server`` is also specified, it affects only the initial query for + the root zone name servers. + + :option:`+dnssec` is also set when :option:`+trace` is set, to better emulate the + default queries from a name server. + +.. option:: +tries=T + + This option sets the number of times to try UDP and TCP queries to server to ``T`` + instead of the default, 3. If ``T`` is less than or equal to zero, + the number of tries is silently rounded up to 1. + +.. option:: +trusted-key=#### + + This option formerly specified trusted keys for use with :option:`dig +sigchase`. This + feature is now obsolete and has been removed; use :iscman:`delv` instead. + +.. option:: +ttlid, +nottlid + + This option displays [or does not display] the TTL when printing the record. + +.. option:: +ttlunits, +nottlunits + + This option displays [or does not display] the TTL in friendly human-readable time + units of ``s``, ``m``, ``h``, ``d``, and ``w``, representing seconds, minutes, + hours, days, and weeks. This implies :option:`+ttlid`. + +.. option:: +unknownformat, +nounknownformat + + This option prints all RDATA in unknown RR type presentation format (:rfc:`3597`). + The default is to print RDATA for known types in the type's + presentation format. + +.. option:: +vc, +novc + + This option uses [or does not use] TCP when querying name servers. This alternate + syntax to :option:`+tcp` is provided for backwards compatibility. The + ``vc`` stands for "virtual circuit." + +.. option:: +yaml, +noyaml + + When enabled, this option prints the responses (and, if :option:`+qr` is in use, also the + outgoing queries) in a detailed YAML format. + +.. option:: +zflag, +nozflag + + This option sets [or does not set] the last unassigned DNS header flag in a DNS query. + This flag is off by default. + +Multiple Queries +~~~~~~~~~~~~~~~~ + +The BIND 9 implementation of :program:`dig` supports specifying multiple +queries on the command line (in addition to supporting the :option:`-f` batch +file option). Each of those queries can be supplied with its own set of +flags, options, and query options. + +In this case, each ``query`` argument represents an individual query in +the command-line syntax described above. Each consists of any of the +standard options and flags, the name to be looked up, an optional query +type and class, and any query options that should be applied to that +query. + +A global set of query options, which should be applied to all queries, +can also be supplied. These global query options must precede the first +tuple of name, class, type, options, flags, and query options supplied +on the command line. Any global query options (except :option:`+cmd` and +:option:`+short` options) can be overridden by a query-specific set of +query options. For example: + +:: + + dig +qr www.isc.org any -x 127.0.0.1 isc.org ns +noqr + +shows how :program:`dig` can be used from the command line to make three +lookups: an ANY query for ``www.isc.org``, a reverse lookup of 127.0.0.1, +and a query for the NS records of ``isc.org``. A global query option of +:option:`+qr` is applied, so that :program:`dig` shows the initial query it made for +each lookup. The final query has a local query option of :option:`+noqr` which +means that :program:`dig` does not print the initial query when it looks up the +NS records for ``isc.org``. + +IDN Support +~~~~~~~~~~~ + +If :program:`dig` has been built with IDN (internationalized domain name) +support, it can accept and display non-ASCII domain names. :program:`dig` +appropriately converts character encoding of a domain name before sending +a request to a DNS server or displaying a reply from the server. +To turn off IDN support, use the parameters +:option:`+idnin` and :option:`+idnout`, or define the ``IDN_DISABLE`` environment +variable. + +Return Codes +~~~~~~~~~~~~ + +:program:`dig` return codes are: + +``0`` + DNS response received, including NXDOMAIN status + +``1`` + Usage error + +``8`` + Couldn't open batch file + +``9`` + No reply from server + +``10`` + Internal error + +Files +~~~~~ + +``/etc/resolv.conf`` + +``${HOME}/.digrc`` + +See Also +~~~~~~~~ + +:iscman:`delv(1) <delv>`, :iscman:`host(1) <host>`, :iscman:`named(8) <named>`, :iscman:`dnssec-keygen(8) <dnssec-keygen>`, :rfc:`1035`. + +Bugs +~~~~ + +There are probably too many query options. diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c new file mode 100644 index 0000000..55f7bf2 --- /dev/null +++ b/bin/dig/dighost.c @@ -0,0 +1,4989 @@ +/* + * 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 + * \note + * Notice to programmers: Do not use this code as an example of how to + * use the ISC library to perform DNS lookups. Dig and Host both operate + * on the request level, since they allow fine-tuning of output and are + * intended as debugging tools. As a result, they perform many of the + * functions which could be better handled using the dns_resolver + * functions in most applications. + */ + +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_LIBIDN2 +#include <idn2.h> +#endif /* HAVE_LIBIDN2 */ + +#include <isc/app.h> +#include <isc/base64.h> +#include <isc/file.h> +#include <isc/hex.h> +#include <isc/lang.h> +#include <isc/log.h> +#include <isc/managers.h> +#include <isc/netaddr.h> +#include <isc/netdb.h> +#include <isc/nonce.h> +#include <isc/parseint.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/safe.h> +#include <isc/serial.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/byaddr.h> +#include <dns/fixedname.h> +#include <dns/log.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/opcode.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/tsig.h> + +#include <dst/dst.h> + +#include <isccfg/namedconf.h> + +#include <irs/resconf.h> + +#include <bind9/getaddresses.h> + +#include "dighost.h" + +#define systemlocale(l) (void)setlocale(l, "") +#define resetlocale(l) (void)setlocale(l, "C") + +dig_lookuplist_t lookup_list; +dig_serverlist_t server_list; +dig_searchlistlist_t search_list; + +static atomic_bool cancel_now = false; + +bool check_ra = false, have_ipv4 = false, have_ipv6 = false, + specified_source = false, free_now = false, usesearch = false, + showsearch = false, is_dst_up = false, keep_open = false, verbose = false, + yaml = false; +in_port_t port = 53; +bool port_set = false; +unsigned int timeout = 0; +unsigned int extrabytes; +isc_mem_t *mctx = NULL; +isc_log_t *lctx = NULL; +isc_nm_t *netmgr = NULL; +isc_taskmgr_t *taskmgr = NULL; +isc_task_t *global_task = NULL; +isc_sockaddr_t localaddr; +isc_refcount_t sendcount = 0; +isc_refcount_t recvcount = 0; +int ndots = -1; +int tries = -1; +int lookup_counter = 0; + +static char servercookie[256]; + +#ifdef HAVE_LIBIDN2 +static void +idn_locale_to_ace(const char *src, char *dst, size_t dstlen); +static void +idn_ace_to_locale(const char *src, char **dst); +static isc_result_t +idn_output_filter(isc_buffer_t *buffer, unsigned int used_org); +#endif /* HAVE_LIBIDN2 */ + +isc_nmhandle_t *keep = NULL; +isc_sockaddr_t keepaddr; + +/*% + * Exit Codes: + * + *\li 0 Everything went well, including things like NXDOMAIN + *\li 1 Usage error + *\li 7 Got too many RR's or Names + *\li 8 Couldn't open batch file + *\li 9 No reply from server + *\li 10 Internal error + */ +int exitcode = 0; +int fatalexit = 0; +char keynametext[MXNAME]; +char keyfile[MXNAME] = ""; +char keysecret[MXNAME] = ""; +unsigned char cookie_secret[33]; +unsigned char cookie[8]; +const dns_name_t *hmacname = NULL; +unsigned int digestbits = 0; +isc_buffer_t *namebuf = NULL; +dns_tsigkey_t *tsigkey = NULL; +dst_key_t *sig0key = NULL; +bool validated = true; +bool debugging = false; +bool debugtiming = false; +bool memdebugging = false; +char *progname = NULL; +isc_mutex_t lookup_lock; +dig_lookup_t *current_lookup = NULL; + +#define DIG_MAX_ADDRESSES 20 + +/*% + * Apply and clear locks at the event level in global task. + * Can I get rid of these using shutdown events? XXX + */ +#define LOCK_LOOKUP \ + { \ + debug("lock_lookup %s:%d", __FILE__, __LINE__); \ + check_result(isc_mutex_lock((&lookup_lock)), "isc_mutex_" \ + "lock"); \ + debug("success"); \ + } +#define UNLOCK_LOOKUP \ + { \ + debug("unlock_lookup %s:%d", __FILE__, __LINE__); \ + check_result(isc_mutex_unlock((&lookup_lock)), "isc_mutex_" \ + "unlock"); \ + } + +static void +default_warnerr(const char *format, ...) { + va_list args; + + printf(";; "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +} + +static void +default_comments(dig_lookup_t *lookup, const char *format, ...) { + va_list args; + + if (lookup->comments) { + printf(";; "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + } +} + +/* dynamic callbacks */ + +isc_result_t (*dighost_printmessage)(dig_query_t *query, + const isc_buffer_t *msgbuf, + dns_message_t *msg, bool headers); + +void (*dighost_error)(const char *format, ...) = default_warnerr; + +void (*dighost_warning)(const char *format, ...) = default_warnerr; + +void (*dighost_comments)(dig_lookup_t *lookup, const char *format, + ...) = default_comments; + +void (*dighost_received)(unsigned int bytes, isc_sockaddr_t *from, + dig_query_t *query); + +void (*dighost_trying)(char *frm, dig_lookup_t *lookup); + +void (*dighost_shutdown)(void); + +/* forward declarations */ + +#define cancel_lookup(l) _cancel_lookup(l, __FILE__, __LINE__) +static void +_cancel_lookup(dig_lookup_t *lookup, const char *file, unsigned int line); + +static void +recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg); + +static void +start_udp(dig_query_t *query); + +static void +start_tcp(dig_query_t *query); + +static void +force_next(dig_query_t *query); + +static void +launch_next_query(dig_query_t *query); + +static void +clear_current_lookup(void); + +static bool +next_origin(dig_lookup_t *oldlookup); + +static int +count_dots(char *string) { + char *s; + int i = 0; + + s = string; + while (*s != '\0') { + if (*s == '.') { + i++; + } + s++; + } + return (i); +} + +static void +hex_dump(isc_buffer_t *b) { + unsigned int len, i; + isc_region_t r; + + isc_buffer_usedregion(b, &r); + + printf("%u bytes\n", r.length); + for (len = 0; len < r.length; len++) { + printf("%02x ", r.base[len]); + if (len % 16 == 15) { + printf(" "); + for (i = len - 15; i <= len; i++) { + if (r.base[i] >= '!' && r.base[i] <= '}') { + putchar(r.base[i]); + } else { + putchar('.'); + } + } + printf("\n"); + } + } + if (len % 16 != 0) { + for (i = len; (i % 16) != 0; i++) { + printf(" "); + } + printf(" "); + for (i = ((len >> 4) << 4); i < len; i++) { + if (r.base[i] >= '!' && r.base[i] <= '}') { + putchar(r.base[i]); + } else { + putchar('.'); + } + } + printf("\n"); + } +} + +/*% + * Append 'len' bytes of 'text' at '*p', failing with + * ISC_R_NOSPACE if that would advance p past 'end'. + */ +static isc_result_t +append(const char *text, size_t len, char **p, char *end) { + if (*p + len > end) { + return (ISC_R_NOSPACE); + } + memmove(*p, text, len); + *p += len; + return (ISC_R_SUCCESS); +} + +static isc_result_t +reverse_octets(const char *in, char **p, char *end) { + const char *dot = strchr(in, '.'); + size_t len; + if (dot != NULL) { + isc_result_t result; + result = reverse_octets(dot + 1, p, end); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = append(".", 1, p, end); + if (result != ISC_R_SUCCESS) { + return (result); + } + len = (int)(dot - in); + } else { + len = (int)strlen(in); + } + return (append(in, len, p, end)); +} + +isc_result_t +get_reverse(char *reverse, size_t len, char *value, bool strict) { + int r; + isc_result_t result; + isc_netaddr_t addr; + + addr.family = AF_INET6; + r = inet_pton(AF_INET6, value, &addr.type.in6); + if (r > 0) { + /* This is a valid IPv6 address. */ + dns_fixedname_t fname; + dns_name_t *name; + unsigned int options = 0; + + name = dns_fixedname_initname(&fname); + result = dns_byaddr_createptrname(&addr, options, name); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_name_format(name, reverse, (unsigned int)len); + return (ISC_R_SUCCESS); + } else { + /* + * Not a valid IPv6 address. Assume IPv4. + * If 'strict' is not set, construct the + * in-addr.arpa name by blindly reversing + * octets whether or not they look like integers, + * so that this can be used for RFC2317 names + * and such. + */ + char *p = reverse; + char *end = reverse + len; + if (strict && inet_pton(AF_INET, value, &addr.type.in) != 1) { + return (DNS_R_BADDOTTEDQUAD); + } + result = reverse_octets(value, &p, end); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* Append .in-addr.arpa. and a terminating NUL. */ + result = append(".in-addr.arpa.", 15, &p, end); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (ISC_R_SUCCESS); + } +} + +void (*dighost_pre_exit_hook)(void) = NULL; + +#if TARGET_OS_IPHONE +void +warn(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, ";; Warning: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +} +#else /* if TARGET_OS_IPHONE */ +void +warn(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, "%s: ", progname); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +} +#endif /* if TARGET_OS_IPHONE */ + +void +digexit(void) { + if (exitcode < 10) { + exitcode = 10; + } + if (fatalexit != 0) { + exitcode = fatalexit; + } + if (dighost_pre_exit_hook != NULL) { + dighost_pre_exit_hook(); + } + exit(exitcode); +} + +void +fatal(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, "%s: ", progname); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + digexit(); +} + +void +debug(const char *format, ...) { + va_list args; + isc_time_t t; + + if (debugging) { + fflush(stdout); + if (debugtiming) { + TIME_NOW(&t); + fprintf(stderr, "%u.%06u: ", isc_time_seconds(&t), + isc_time_nanoseconds(&t) / 1000); + } + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + } +} + +void +check_result(isc_result_t result, const char *msg) { + if (result != ISC_R_SUCCESS) { + fatal("%s: %s", msg, isc_result_totext(result)); + } +} + +/*% + * Create a server structure, which is part of the lookup structure. + * This is little more than a linked list of servers to query in hopes + * of finding the answer the user is looking for + */ +dig_server_t * +make_server(const char *servname, const char *userarg) { + dig_server_t *srv; + + REQUIRE(servname != NULL); + + debug("make_server(%s)", servname); + srv = isc_mem_allocate(mctx, sizeof(struct dig_server)); + strlcpy(srv->servername, servname, MXNAME); + strlcpy(srv->userarg, userarg, MXNAME); + ISC_LINK_INIT(srv, link); + return (srv); +} + +/*% + * Create a copy of the server list from the resolver configuration structure. + * The dest list must have already had ISC_LIST_INIT applied. + */ +static void +get_server_list(irs_resconf_t *resconf) { + isc_sockaddrlist_t *servers; + isc_sockaddr_t *sa; + dig_server_t *newsrv; + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") + + sizeof("%4000000000")]; + debug("get_server_list()"); + servers = irs_resconf_getnameservers(resconf); + for (sa = ISC_LIST_HEAD(*servers); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + int pf = isc_sockaddr_pf(sa); + isc_netaddr_t na; + isc_result_t result; + isc_buffer_t b; + + if (pf == AF_INET && !have_ipv4) { + continue; + } + if (pf == AF_INET6 && !have_ipv6) { + continue; + } + + isc_buffer_init(&b, tmp, sizeof(tmp)); + isc_netaddr_fromsockaddr(&na, sa); + result = isc_netaddr_totext(&na, &b); + if (result != ISC_R_SUCCESS) { + continue; + } + isc_buffer_putuint8(&b, 0); + if (pf == AF_INET6 && na.zone != 0) { + char buf[sizeof("%4000000000")]; + snprintf(buf, sizeof(buf), "%%%u", na.zone); + strlcat(tmp, buf, sizeof(tmp)); + } + newsrv = make_server(tmp, tmp); + ISC_LINK_INIT(newsrv, link); + ISC_LIST_APPEND(server_list, newsrv, link); + } +} + +void +flush_server_list(void) { + dig_server_t *s, *ps; + + debug("flush_server_list()"); + s = ISC_LIST_HEAD(server_list); + while (s != NULL) { + ps = s; + s = ISC_LIST_NEXT(s, link); + ISC_LIST_DEQUEUE(server_list, ps, link); + isc_mem_free(mctx, ps); + } +} + +void +set_nameserver(char *opt) { + isc_result_t result; + isc_sockaddr_t sockaddrs[DIG_MAX_ADDRESSES]; + isc_netaddr_t netaddr; + int count, i; + dig_server_t *srv; + char tmp[ISC_NETADDR_FORMATSIZE]; + + if (opt == NULL) { + return; + } + + result = bind9_getaddresses(opt, 0, sockaddrs, DIG_MAX_ADDRESSES, + &count); + if (result != ISC_R_SUCCESS) { + fatal("couldn't get address for '%s': %s", opt, + isc_result_totext(result)); + } + + flush_server_list(); + + for (i = 0; i < count; i++) { + isc_netaddr_fromsockaddr(&netaddr, &sockaddrs[i]); + isc_netaddr_format(&netaddr, tmp, sizeof(tmp)); + srv = make_server(tmp, opt); + if (srv == NULL) { + fatal("memory allocation failure"); + } + ISC_LIST_APPEND(server_list, srv, link); + } +} + +/*% + * Produce a cloned server list. The dest list must have already had + * ISC_LIST_INIT applied. + */ +void +clone_server_list(dig_serverlist_t src, dig_serverlist_t *dest) { + dig_server_t *srv, *newsrv; + + debug("clone_server_list()"); + srv = ISC_LIST_HEAD(src); + while (srv != NULL) { + newsrv = make_server(srv->servername, srv->userarg); + ISC_LINK_INIT(newsrv, link); + ISC_LIST_ENQUEUE(*dest, newsrv, link); + srv = ISC_LIST_NEXT(srv, link); + } +} + +/*% + * Create an empty lookup structure, which holds all the information needed + * to get an answer to a user's question. This structure contains two + * linked lists: the server list (servers to query) and the query list + * (outstanding queries which have been made to the listed servers). + */ +dig_lookup_t * +make_empty_lookup(void) { + dig_lookup_t *looknew; +#ifdef HAVE_LIBIDN2 + bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false; +#endif /* HAVE_LIBIDN2 */ + + debug("make_empty_lookup()"); + + INSIST(!free_now); + + looknew = isc_mem_allocate(mctx, sizeof(*looknew)); + *looknew = (dig_lookup_t){ + .pending = true, + .rdtype = dns_rdatatype_a, + .qrdtype = dns_rdatatype_a, + .rdclass = dns_rdataclass_in, + .servfail_stops = true, + .besteffort = true, + .opcode = dns_opcode_query, + .badcookie = true, +#ifdef HAVE_LIBIDN2 + .idnin = idn_allowed, + .idnout = idn_allowed, +#endif /* HAVE_LIBIDN2 */ + .udpsize = -1, + .edns = -1, + .recurse = true, + .retries = tries, + .comments = true, + .stats = true, + .section_question = true, + .section_answer = true, + .section_authority = true, + .section_additional = true, + .ednsneg = true, + }; + + dns_fixedname_init(&looknew->fdomain); + ISC_LINK_INIT(looknew, link); + ISC_LIST_INIT(looknew->q); + ISC_LIST_INIT(looknew->my_server_list); + + isc_tlsctx_cache_create(mctx, &looknew->tls_ctx_cache); + + isc_refcount_init(&looknew->references, 1); + + looknew->magic = DIG_LOOKUP_MAGIC; + + debug("make_empty_lookup() = %p->references = %" PRIuFAST32, looknew, + isc_refcount_current(&looknew->references)); + + return (looknew); +} + +#define EDNSOPT_OPTIONS 100U + +static void +cloneopts(dig_lookup_t *looknew, dig_lookup_t *lookold) { + size_t len = sizeof(looknew->ednsopts[0]) * EDNSOPT_OPTIONS; + size_t i; + looknew->ednsopts = isc_mem_allocate(mctx, len); + for (i = 0; i < EDNSOPT_OPTIONS; i++) { + looknew->ednsopts[i].code = 0; + looknew->ednsopts[i].length = 0; + looknew->ednsopts[i].value = NULL; + } + looknew->ednsoptscnt = 0; + if (lookold == NULL || lookold->ednsopts == NULL) { + return; + } + + for (i = 0; i < lookold->ednsoptscnt; i++) { + len = lookold->ednsopts[i].length; + if (len != 0) { + INSIST(lookold->ednsopts[i].value != NULL); + looknew->ednsopts[i].value = isc_mem_allocate(mctx, + len); + memmove(looknew->ednsopts[i].value, + lookold->ednsopts[i].value, len); + } + looknew->ednsopts[i].code = lookold->ednsopts[i].code; + looknew->ednsopts[i].length = len; + } + looknew->ednsoptscnt = lookold->ednsoptscnt; +} + +/*% + * Clone a lookup, perhaps copying the server list. This does not clone + * the query list, since it will be regenerated by the setup_lookup() + * function, nor does it queue up the new lookup for processing. + * Caution: If you don't clone the servers, you MUST clone the server + * list separately from somewhere else, or construct it by hand. + */ +dig_lookup_t * +clone_lookup(dig_lookup_t *lookold, bool servers) { + dig_lookup_t *looknew; + + debug("clone_lookup()"); + + INSIST(!free_now); + + looknew = make_empty_lookup(); + strlcpy(looknew->textname, lookold->textname, MXNAME); + strlcpy(looknew->cmdline, lookold->cmdline, MXNAME); + looknew->textname[MXNAME - 1] = 0; + looknew->rdtype = lookold->rdtype; + looknew->qrdtype = lookold->qrdtype; + looknew->rdclass = lookold->rdclass; + looknew->rdtypeset = lookold->rdtypeset; + looknew->rdclassset = lookold->rdclassset; + looknew->doing_xfr = lookold->doing_xfr; + looknew->ixfr_serial = lookold->ixfr_serial; + looknew->trace = lookold->trace; + looknew->trace_root = lookold->trace_root; + looknew->identify = lookold->identify; + looknew->identify_previous_line = lookold->identify_previous_line; + looknew->ignore = lookold->ignore; + looknew->servfail_stops = lookold->servfail_stops; + looknew->besteffort = lookold->besteffort; + looknew->dns64prefix = lookold->dns64prefix; + looknew->dnssec = lookold->dnssec; + looknew->ednsflags = lookold->ednsflags; + looknew->opcode = lookold->opcode; + looknew->expire = lookold->expire; + looknew->nsid = lookold->nsid; + looknew->tcp_keepalive = lookold->tcp_keepalive; + looknew->header_only = lookold->header_only; + looknew->https_mode = lookold->https_mode; + if (lookold->https_path != NULL) { + looknew->https_path = isc_mem_strdup(mctx, lookold->https_path); + } + looknew->https_get = lookold->https_get; + looknew->http_plain = lookold->http_plain; + + looknew->tls_ca_set = lookold->tls_ca_set; + if (lookold->tls_ca_file != NULL) { + looknew->tls_ca_file = isc_mem_strdup(mctx, + lookold->tls_ca_file); + }; + + looknew->tls_hostname_set = lookold->tls_hostname_set; + if (lookold->tls_hostname != NULL) { + looknew->tls_hostname = isc_mem_strdup(mctx, + lookold->tls_hostname); + } + + looknew->tls_key_file_set = lookold->tls_key_file_set; + if (lookold->tls_key_file != NULL) { + looknew->tls_key_file = isc_mem_strdup(mctx, + lookold->tls_key_file); + } + + looknew->tls_cert_file_set = lookold->tls_cert_file_set; + if (lookold->tls_cert_file != NULL) { + looknew->tls_cert_file = isc_mem_strdup(mctx, + lookold->tls_cert_file); + } + + looknew->showbadcookie = lookold->showbadcookie; + looknew->sendcookie = lookold->sendcookie; + looknew->seenbadcookie = lookold->seenbadcookie; + looknew->badcookie = lookold->badcookie; + looknew->cookie = lookold->cookie; + if (lookold->ednsopts != NULL) { + cloneopts(looknew, lookold); + } else { + looknew->ednsopts = NULL; + looknew->ednsoptscnt = 0; + } + looknew->ednsneg = lookold->ednsneg; + looknew->padding = lookold->padding; + looknew->multiline = lookold->multiline; + looknew->nottl = lookold->nottl; + looknew->noclass = lookold->noclass; + looknew->onesoa = lookold->onesoa; + looknew->use_usec = lookold->use_usec; + looknew->nocrypto = lookold->nocrypto; + looknew->ttlunits = lookold->ttlunits; + looknew->expandaaaa = lookold->expandaaaa; + looknew->qr = lookold->qr; + looknew->idnin = lookold->idnin; + looknew->idnout = lookold->idnout; + looknew->udpsize = lookold->udpsize; + looknew->edns = lookold->edns; + looknew->recurse = lookold->recurse; + looknew->aaonly = lookold->aaonly; + looknew->adflag = lookold->adflag; + looknew->cdflag = lookold->cdflag; + looknew->raflag = lookold->raflag; + looknew->tcflag = lookold->tcflag; + looknew->print_unknown_format = lookold->print_unknown_format; + looknew->zflag = lookold->zflag; + looknew->setqid = lookold->setqid; + looknew->qid = lookold->qid; + looknew->ns_search_only = lookold->ns_search_only; + looknew->tcp_mode = lookold->tcp_mode; + looknew->tcp_mode_set = lookold->tcp_mode_set; + looknew->tls_mode = lookold->tls_mode; + looknew->comments = lookold->comments; + looknew->stats = lookold->stats; + looknew->section_question = lookold->section_question; + looknew->section_answer = lookold->section_answer; + looknew->section_authority = lookold->section_authority; + looknew->section_additional = lookold->section_additional; + looknew->origin = lookold->origin; + looknew->retries = lookold->retries; + looknew->tsigctx = NULL; + looknew->need_search = lookold->need_search; + looknew->done_as_is = lookold->done_as_is; + looknew->rrcomments = lookold->rrcomments; + looknew->fuzzing = lookold->fuzzing; + looknew->fuzztime = lookold->fuzztime; + + if (lookold->ecs_addr != NULL) { + size_t len = sizeof(isc_sockaddr_t); + looknew->ecs_addr = isc_mem_allocate(mctx, len); + memmove(looknew->ecs_addr, lookold->ecs_addr, len); + } + + dns_name_copy(dns_fixedname_name(&lookold->fdomain), + dns_fixedname_name(&looknew->fdomain)); + + if (servers) { + if (lookold->tls_ctx_cache != NULL) { + isc_tlsctx_cache_detach(&looknew->tls_ctx_cache); + isc_tlsctx_cache_attach(lookold->tls_ctx_cache, + &looknew->tls_ctx_cache); + } + clone_server_list(lookold->my_server_list, + &looknew->my_server_list); + } + + isc_refcount_init(&looknew->references, 1); + + looknew->magic = DIG_LOOKUP_MAGIC; + + return (looknew); +} + +/*% + * Requeue a lookup for further processing, perhaps copying the server + * list. The new lookup structure is returned to the caller, and is + * queued for processing. If servers are not cloned in the requeue, they + * must be added before allowing the current event to complete, since the + * completion of the event may result in the next entry on the lookup + * queue getting run. + */ +dig_lookup_t * +requeue_lookup(dig_lookup_t *lookold, bool servers) { + dig_lookup_t *looknew = NULL; + + debug("requeue_lookup()"); + + lookup_counter++; + if (lookup_counter > LOOKUP_LIMIT) { + fatal("too many lookups"); + } + + looknew = clone_lookup(lookold, servers); + INSIST(looknew != NULL); + + debug("before insertion, init@%p -> %p, new@%p -> %p", lookold, + lookold->link.next, looknew, looknew->link.next); + ISC_LIST_PREPEND(lookup_list, looknew, link); + debug("after insertion, init -> %p, new = %p, new -> %p", lookold, + looknew, looknew->link.next); + return (looknew); +} + +void +setup_text_key(void) { + isc_result_t result; + dns_name_t keyname; + isc_buffer_t secretbuf; + unsigned int secretsize; + unsigned char *secretstore; + + debug("setup_text_key()"); + isc_buffer_allocate(mctx, &namebuf, MXNAME); + dns_name_init(&keyname, NULL); + isc_buffer_putstr(namebuf, keynametext); + secretsize = (unsigned int)strlen(keysecret) * 3 / 4; + secretstore = isc_mem_allocate(mctx, secretsize); + isc_buffer_init(&secretbuf, secretstore, secretsize); + result = isc_base64_decodestring(keysecret, &secretbuf); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + secretsize = isc_buffer_usedlength(&secretbuf); + + if (hmacname == NULL) { + result = DST_R_UNSUPPORTEDALG; + goto failure; + } + + result = dns_name_fromtext(&keyname, namebuf, dns_rootname, 0, namebuf); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_tsigkey_create(&keyname, hmacname, secretstore, + (int)secretsize, false, NULL, 0, 0, mctx, + NULL, &tsigkey); +failure: + if (result != ISC_R_SUCCESS) { + printf(";; Couldn't create key %s: %s\n", keynametext, + isc_result_totext(result)); + } else { + dst_key_setbits(tsigkey->key, digestbits); + } + + isc_mem_free(mctx, secretstore); + dns_name_invalidate(&keyname); + isc_buffer_free(&namebuf); +} + +static isc_result_t +parse_uint_helper(uint32_t *uip, const char *value, uint32_t max, + const char *desc, int base) { + uint32_t n; + isc_result_t result = isc_parse_uint32(&n, value, base); + if (result == ISC_R_SUCCESS && n > max) { + result = ISC_R_RANGE; + } + if (result != ISC_R_SUCCESS) { + printf("invalid %s '%s': %s\n", desc, value, + isc_result_totext(result)); + return (result); + } + *uip = n; + return (ISC_R_SUCCESS); +} + +isc_result_t +parse_uint(uint32_t *uip, const char *value, uint32_t max, const char *desc) { + return (parse_uint_helper(uip, value, max, desc, 10)); +} + +isc_result_t +parse_xint(uint32_t *uip, const char *value, uint32_t max, const char *desc) { + return (parse_uint_helper(uip, value, max, desc, 0)); +} + +static uint32_t +parse_bits(char *arg, const char *desc, uint32_t max) { + isc_result_t result; + uint32_t tmp; + + result = parse_uint(&tmp, arg, max, desc); + if (result != ISC_R_SUCCESS) { + fatal("couldn't parse digest bits"); + } + tmp = (tmp + 7) & ~0x7U; + return (tmp); +} + +isc_result_t +parse_netprefix(isc_sockaddr_t **sap, const char *value) { + isc_result_t result = ISC_R_SUCCESS; + isc_sockaddr_t *sa = NULL; + struct in_addr in4; + struct in6_addr in6; + uint32_t prefix_length = 0xffffffff; + char *slash = NULL; + bool parsed = false; + bool prefix_parsed = false; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX/128")]; + + REQUIRE(sap != NULL && *sap == NULL); + + if (strlcpy(buf, value, sizeof(buf)) >= sizeof(buf)) { + fatal("invalid prefix '%s'\n", value); + } + + sa = isc_mem_allocate(mctx, sizeof(*sa)); + memset(sa, 0, sizeof(*sa)); + + if (strcmp(buf, "0") == 0) { + sa->type.sa.sa_family = AF_UNSPEC; + prefix_length = 0; + goto done; + } + + slash = strchr(buf, '/'); + if (slash != NULL) { + *slash = '\0'; + result = isc_parse_uint32(&prefix_length, slash + 1, 10); + if (result != ISC_R_SUCCESS) { + fatal("invalid prefix length in '%s': %s\n", value, + isc_result_totext(result)); + } + prefix_parsed = true; + } + + if (inet_pton(AF_INET6, buf, &in6) == 1) { + parsed = true; + isc_sockaddr_fromin6(sa, &in6, 0); + if (prefix_length > 128) { + prefix_length = 128; + } + } else if (inet_pton(AF_INET, buf, &in4) == 1) { + parsed = true; + isc_sockaddr_fromin(sa, &in4, 0); + if (prefix_length > 32) { + prefix_length = 32; + } + } else if (prefix_parsed) { + int i; + + for (i = 0; i < 3 && strlen(buf) < sizeof(buf) - 2; i++) { + strlcat(buf, ".0", sizeof(buf)); + if (inet_pton(AF_INET, buf, &in4) == 1) { + parsed = true; + isc_sockaddr_fromin(sa, &in4, 0); + break; + } + } + + if (prefix_length > 32) { + prefix_length = 32; + } + } + + if (!parsed) { + fatal("invalid address '%s'", value); + } + +done: + sa->length = prefix_length; + *sap = sa; + + return (ISC_R_SUCCESS); +} + +/* + * Parse HMAC algorithm specification + */ +void +parse_hmac(const char *hmac) { + char buf[20]; + size_t len; + + REQUIRE(hmac != NULL); + + len = strlen(hmac); + if (len >= sizeof(buf)) { + fatal("unknown key type '%.*s'", (int)len, hmac); + } + strlcpy(buf, hmac, sizeof(buf)); + + digestbits = 0; + + if (strcasecmp(buf, "hmac-md5") == 0) { + hmacname = DNS_TSIG_HMACMD5_NAME; + } else if (strncasecmp(buf, "hmac-md5-", 9) == 0) { + hmacname = DNS_TSIG_HMACMD5_NAME; + digestbits = parse_bits(&buf[9], "digest-bits [0..128]", 128); + } else if (strcasecmp(buf, "hmac-sha1") == 0) { + hmacname = DNS_TSIG_HMACSHA1_NAME; + digestbits = 0; + } else if (strncasecmp(buf, "hmac-sha1-", 10) == 0) { + hmacname = DNS_TSIG_HMACSHA1_NAME; + digestbits = parse_bits(&buf[10], "digest-bits [0..160]", 160); + } else if (strcasecmp(buf, "hmac-sha224") == 0) { + hmacname = DNS_TSIG_HMACSHA224_NAME; + } else if (strncasecmp(buf, "hmac-sha224-", 12) == 0) { + hmacname = DNS_TSIG_HMACSHA224_NAME; + digestbits = parse_bits(&buf[12], "digest-bits [0..224]", 224); + } else if (strcasecmp(buf, "hmac-sha256") == 0) { + hmacname = DNS_TSIG_HMACSHA256_NAME; + } else if (strncasecmp(buf, "hmac-sha256-", 12) == 0) { + hmacname = DNS_TSIG_HMACSHA256_NAME; + digestbits = parse_bits(&buf[12], "digest-bits [0..256]", 256); + } else if (strcasecmp(buf, "hmac-sha384") == 0) { + hmacname = DNS_TSIG_HMACSHA384_NAME; + } else if (strncasecmp(buf, "hmac-sha384-", 12) == 0) { + hmacname = DNS_TSIG_HMACSHA384_NAME; + digestbits = parse_bits(&buf[12], "digest-bits [0..384]", 384); + } else if (strcasecmp(buf, "hmac-sha512") == 0) { + hmacname = DNS_TSIG_HMACSHA512_NAME; + } else if (strncasecmp(buf, "hmac-sha512-", 12) == 0) { + hmacname = DNS_TSIG_HMACSHA512_NAME; + digestbits = parse_bits(&buf[12], "digest-bits [0..512]", 512); + } else { + fprintf(stderr, + ";; Warning, ignoring " + "invalid TSIG algorithm %s\n", + buf); + } +} + +/* + * Get a key from a named.conf format keyfile + */ +static isc_result_t +read_confkey(void) { + cfg_parser_t *pctx = NULL; + cfg_obj_t *file = NULL; + const cfg_obj_t *keyobj = NULL; + const cfg_obj_t *secretobj = NULL; + const cfg_obj_t *algorithmobj = NULL; + const char *keyname; + const char *secretstr; + const char *algorithm; + isc_result_t result; + + if (!isc_file_exists(keyfile)) { + return (ISC_R_FILENOTFOUND); + } + + result = cfg_parser_create(mctx, NULL, &pctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = cfg_parse_file(pctx, keyfile, &cfg_type_sessionkey, &file); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = cfg_map_get(file, "key", &keyobj); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + (void)cfg_map_get(keyobj, "secret", &secretobj); + (void)cfg_map_get(keyobj, "algorithm", &algorithmobj); + if (secretobj == NULL || algorithmobj == NULL) { + fatal("key must have algorithm and secret"); + } + + keyname = cfg_obj_asstring(cfg_map_getname(keyobj)); + secretstr = cfg_obj_asstring(secretobj); + algorithm = cfg_obj_asstring(algorithmobj); + + strlcpy(keynametext, keyname, sizeof(keynametext)); + strlcpy(keysecret, secretstr, sizeof(keysecret)); + parse_hmac(algorithm); + setup_text_key(); + +cleanup: + if (pctx != NULL) { + if (file != NULL) { + cfg_obj_destroy(pctx, &file); + } + cfg_parser_destroy(&pctx); + } + + return (result); +} + +void +setup_file_key(void) { + isc_result_t result; + dst_key_t *dstkey = NULL; + + debug("setup_file_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_confkey(); + if (result == ISC_R_SUCCESS) { + return; + } + } + + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "Couldn't read key from %s: %s\n", keyfile, + isc_result_totext(result)); + goto failure; + } + + 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); + if (result != ISC_R_SUCCESS) { + printf(";; Couldn't create key %s: %s\n", keynametext, + isc_result_totext(result)); + goto failure; + } + } else { + dst_key_attach(dstkey, &sig0key); + dst_key_free(&dstkey); + } +failure: + if (dstkey != NULL) { + dst_key_free(&dstkey); + } +} + +static dig_searchlist_t * +make_searchlist_entry(char *domain) { + dig_searchlist_t *search; + search = isc_mem_allocate(mctx, sizeof(*search)); + strlcpy(search->origin, domain, MXNAME); + search->origin[MXNAME - 1] = 0; + ISC_LINK_INIT(search, link); + return (search); +} + +static void +clear_searchlist(void) { + dig_searchlist_t *search; + while ((search = ISC_LIST_HEAD(search_list)) != NULL) { + ISC_LIST_UNLINK(search_list, search, link); + isc_mem_free(mctx, search); + } +} + +static void +create_search_list(irs_resconf_t *resconf) { + irs_resconf_searchlist_t *list; + irs_resconf_search_t *entry; + dig_searchlist_t *search; + + debug("create_search_list()"); + clear_searchlist(); + + list = irs_resconf_getsearchlist(resconf); + for (entry = ISC_LIST_HEAD(*list); entry != NULL; + entry = ISC_LIST_NEXT(entry, link)) + { + search = make_searchlist_entry(entry->domain); + ISC_LIST_APPEND(search_list, search, link); + } +} + +/*% + * Append 'addr' to the list of servers to be queried. This function is only + * called when no server addresses are explicitly specified and either libirs + * returns an empty list of servers to use or none of the addresses returned by + * libirs are usable due to the specified address family restrictions. + */ +static void +add_fallback_nameserver(const char *addr) { + dig_server_t *server = make_server(addr, addr); + ISC_LINK_INIT(server, link); + ISC_LIST_APPEND(server_list, server, link); +} + +/*% + * Setup the system as a whole, reading key information and resolv.conf + * settings. + */ +void +setup_system(bool ipv4only, bool ipv6only) { + irs_resconf_t *resconf = NULL; + isc_result_t result; + + debug("setup_system()"); + + if (ipv4only) { + if (have_ipv4) { + isc_net_disableipv6(); + have_ipv6 = false; + } else { + fatal("can't find IPv4 networking"); + } + } + + if (ipv6only) { + if (have_ipv6) { + isc_net_disableipv4(); + have_ipv4 = false; + } else { + fatal("can't find IPv6 networking"); + } + } + + result = irs_resconf_load(mctx, RESOLV_CONF, &resconf); + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + fatal("parse of %s failed", RESOLV_CONF); + } + + create_search_list(resconf); + if (ndots == -1) { + ndots = irs_resconf_getndots(resconf); + debug("ndots is %d.", ndots); + } + if (timeout == 0) { + timeout = irs_resconf_gettimeout(resconf); + debug("timeout is %d.", timeout); + } + if (tries == -1) { + tries = irs_resconf_getattempts(resconf); + if (tries == 0) { + tries = 3; + } + debug("retries is %d.", tries); + } + + /* If user doesn't specify server use nameservers from resolv.conf. */ + if (ISC_LIST_EMPTY(server_list)) { + get_server_list(resconf); + } + + /* If we don't find a nameserver fall back to localhost */ + if (ISC_LIST_EMPTY(server_list)) { + if (have_ipv6) { + add_fallback_nameserver("::1"); + } + if (have_ipv4) { + add_fallback_nameserver("127.0.0.1"); + } + } + + irs_resconf_destroy(&resconf); + + if (keyfile[0] != 0) { + setup_file_key(); + } else if (keysecret[0] != 0) { + setup_text_key(); + } + + isc_nonce_buf(cookie_secret, sizeof(cookie_secret)); +} + +/*% + * Override the search list derived from resolv.conf by 'domain'. + */ +void +set_search_domain(char *domain) { + dig_searchlist_t *search; + + clear_searchlist(); + search = make_searchlist_entry(domain); + ISC_LIST_APPEND(search_list, search, link); +} + +/*% + * Setup the ISC and DNS libraries for use by the system. + */ +void +setup_libs(void) { + isc_result_t result; + isc_logconfig_t *logconfig = NULL; + + debug("setup_libs()"); + + result = isc_net_probeipv4(); + if (result == ISC_R_SUCCESS) { + have_ipv4 = true; + } + + result = isc_net_probeipv6(); + if (result == ISC_R_SUCCESS) { + have_ipv6 = true; + } + if (!have_ipv6 && !have_ipv4) { + fatal("can't find either v4 or v6 networking"); + } + + isc_mem_create(&mctx); + isc_mem_setname(mctx, "dig"); + + isc_log_create(mctx, &lctx, &logconfig); + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + + result = isc_log_usechannel(logconfig, "default_debug", NULL, NULL); + check_result(result, "isc_log_usechannel"); + + isc_log_setdebuglevel(lctx, 0); + + isc_managers_create(mctx, 1, 0, &netmgr, &taskmgr, NULL); + + result = isc_task_create(taskmgr, 0, &global_task); + check_result(result, "isc_task_create"); + isc_task_setname(global_task, "dig", NULL); + + result = dst_lib_init(mctx, NULL); + check_result(result, "dst_lib_init"); + is_dst_up = true; + + isc_mutex_init(&lookup_lock); +} + +typedef struct dig_ednsoptname { + uint32_t code; + const char *name; +} dig_ednsoptname_t; + +dig_ednsoptname_t optnames[] = { + { 1, "LLQ" }, /* draft-sekar-dns-llq */ + { 3, "NSID" }, /* RFC 5001 */ + { 5, "DAU" }, /* RFC 6975 */ + { 6, "DHU" }, /* RFC 6975 */ + { 7, "N3U" }, /* RFC 6975 */ + { 8, "ECS" }, /* RFC 7871 */ + { 9, "EXPIRE" }, /* RFC 7314 */ + { 10, "COOKIE" }, /* RFC 7873 */ + { 11, "KEEPALIVE" }, /* RFC 7828 */ + { 12, "PADDING" }, /* RFC 7830 */ + { 12, "PAD" }, /* shorthand */ + { 13, "CHAIN" }, /* RFC 7901 */ + { 14, "KEY-TAG" }, /* RFC 8145 */ + { 15, "EDE" }, /* ietf-dnsop-extended-error-16 */ + { 16, "CLIENT-TAG" }, /* draft-bellis-dnsop-edns-tags */ + { 17, "SERVER-TAG" }, /* draft-bellis-dnsop-edns-tags */ + { 26946, "DEVICEID" }, /* Brian Hartvigsen */ +}; + +#define N_EDNS_OPTNAMES (sizeof(optnames) / sizeof(optnames[0])) + +void +save_opt(dig_lookup_t *lookup, char *code, char *value) { + isc_result_t result; + uint32_t num = 0; + isc_buffer_t b; + bool found = false; + unsigned int i; + + if (lookup->ednsoptscnt >= EDNSOPT_OPTIONS) { + fatal("too many ednsopts"); + } + + for (i = 0; i < N_EDNS_OPTNAMES; i++) { + if (strcasecmp(code, optnames[i].name) == 0) { + num = optnames[i].code; + found = true; + break; + } + } + + if (!found) { + result = parse_uint(&num, code, 65535, "ednsopt"); + if (result != ISC_R_SUCCESS) { + fatal("bad edns code point: %s", code); + } + } + + if (lookup->ednsopts == NULL) { + cloneopts(lookup, NULL); + } + INSIST(lookup->ednsopts != NULL); + + if (lookup->ednsopts[lookup->ednsoptscnt].value != NULL) { + isc_mem_free(mctx, lookup->ednsopts[lookup->ednsoptscnt].value); + } + + lookup->ednsopts[lookup->ednsoptscnt].code = num; + lookup->ednsopts[lookup->ednsoptscnt].length = 0; + lookup->ednsopts[lookup->ednsoptscnt].value = NULL; + + if (value != NULL) { + char *buf; + buf = isc_mem_allocate(mctx, strlen(value) / 2 + 1); + isc_buffer_init(&b, buf, (unsigned int)strlen(value) / 2 + 1); + result = isc_hex_decodestring(value, &b); + check_result(result, "isc_hex_decodestring"); + lookup->ednsopts[lookup->ednsoptscnt].value = + isc_buffer_base(&b); + lookup->ednsopts[lookup->ednsoptscnt].length = + isc_buffer_usedlength(&b); + } + + lookup->ednsoptscnt++; +} + +/*% + * Add EDNS0 option record to a message. Currently, the only supported + * options are UDP buffer size, the DO bit, and EDNS options + * (e.g., NSID, COOKIE, client-subnet) + */ +static void +add_opt(dns_message_t *msg, uint16_t udpsize, uint16_t edns, unsigned int flags, + dns_ednsopt_t *opts, size_t count) { + dns_rdataset_t *rdataset = NULL; + isc_result_t result; + + debug("add_opt()"); + result = dns_message_buildopt(msg, &rdataset, edns, udpsize, flags, + opts, count); + check_result(result, "dns_message_buildopt"); + result = dns_message_setopt(msg, rdataset); + check_result(result, "dns_message_setopt"); +} + +/*% + * Add a question section to a message, asking for the specified name, + * type, and class. + */ +static void +add_question(dns_message_t *message, dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype) { + dns_rdataset_t *rdataset; + isc_result_t result; + + debug("add_question()"); + rdataset = NULL; + result = dns_message_gettemprdataset(message, &rdataset); + check_result(result, "dns_message_gettemprdataset()"); + dns_rdataset_makequestion(rdataset, rdclass, rdtype); + ISC_LIST_APPEND(name->list, rdataset, link); +} + +/*% + * Check if we're done with all the queued lookups, which is true iff + * all sockets, sends, and recvs are accounted for (counters == 0), + * and the lookup list is empty. + * If we are done, pass control back out to dighost_shutdown() (which is + * part of dig.c, host.c, or nslookup.c) to either shutdown the system as + * a whole or reseed the lookup list. + */ +static void +check_if_done(void) { + dig_lookup_t *lookup = NULL; + + debug("check_if_done()"); + debug("list %s", ISC_LIST_EMPTY(lookup_list) ? "empty" : "full"); + + lookup = ISC_LIST_HEAD(lookup_list); + while (lookup != NULL) { + dig_lookup_t *next = NULL; + debug("pending lookup %p", lookup); + next = ISC_LIST_NEXT(lookup, link); + lookup = next; + } + + if (ISC_LIST_EMPTY(lookup_list) && current_lookup == NULL && + isc_refcount_current(&sendcount) == 0) + { + INSIST(isc_refcount_current(&recvcount) == 0); + debug("shutting down"); + dighost_shutdown(); + } +} + +/*% + * Check if we're done with all the queries in the lookup, except for + * the `except_q` query (can be NULL if no exception is required). + * Expects `l` to be a valid and locked lookup. + */ +static bool +check_if_queries_done(dig_lookup_t *l, dig_query_t *except_q) { + dig_query_t *q = ISC_LIST_HEAD(l->q); + + debug("check_if_queries_done(%p)", l); + + while (q != NULL) { + if (!q->started || isc_refcount_current(&q->references) > 1) { + if (!q->canceled && q != except_q) { + debug("there is a pending query %p", q); + return (false); + } + } + q = ISC_LIST_NEXT(q, link); + } + + return (true); +} + +static void +_destroy_lookup(dig_lookup_t *lookup) { + dig_server_t *s; + void *ptr; + + REQUIRE(lookup != NULL); + REQUIRE(ISC_LIST_EMPTY(lookup->q)); + + debug("destroy_lookup"); + + isc_refcount_destroy(&lookup->references); + + s = ISC_LIST_HEAD(lookup->my_server_list); + while (s != NULL) { + debug("freeing server %p belonging to %p", s, lookup); + ptr = s; + s = ISC_LIST_NEXT(s, link); + ISC_LIST_DEQUEUE(lookup->my_server_list, (dig_server_t *)ptr, + link); + isc_mem_free(mctx, ptr); + } + if (lookup->sendmsg != NULL) { + dns_message_detach(&lookup->sendmsg); + } + if (lookup->querysig != NULL) { + debug("freeing buffer %p", lookup->querysig); + isc_buffer_free(&lookup->querysig); + } + if (lookup->sendspace != NULL) { + isc_mem_put(mctx, lookup->sendspace, COMMSIZE); + } + + if (lookup->tsigctx != NULL) { + dst_context_destroy(&lookup->tsigctx); + } + + if (lookup->ecs_addr != NULL) { + isc_mem_free(mctx, lookup->ecs_addr); + } + + if (lookup->ednsopts != NULL) { + size_t i; + for (i = 0; i < EDNSOPT_OPTIONS; i++) { + if (lookup->ednsopts[i].value != NULL) { + isc_mem_free(mctx, lookup->ednsopts[i].value); + } + } + isc_mem_free(mctx, lookup->ednsopts); + } + + if (lookup->https_path) { + isc_mem_free(mctx, lookup->https_path); + } + + if (lookup->tls_ctx_cache != NULL) { + isc_tlsctx_cache_detach(&lookup->tls_ctx_cache); + } + + if (lookup->tls_ca_file != NULL) { + isc_mem_free(mctx, lookup->tls_ca_file); + } + + if (lookup->tls_hostname != NULL) { + isc_mem_free(mctx, lookup->tls_hostname); + } + + if (lookup->tls_key_file != NULL) { + isc_mem_free(mctx, lookup->tls_key_file); + } + + if (lookup->tls_cert_file != NULL) { + isc_mem_free(mctx, lookup->tls_cert_file); + } + + isc_mem_free(mctx, lookup); +} + +#define lookup_attach(s, t) _lookup_attach(s, t, __FILE__, __LINE__) +static void +_lookup_attach(dig_lookup_t *lookup, dig_lookup_t **lookupp, const char *file, + unsigned int line) { + REQUIRE(DIG_VALID_LOOKUP(lookup)); + REQUIRE(lookupp != NULL && *lookupp == NULL); + + debug("%s:%u:lookup_attach(%p) = %" PRIuFAST32, file, line, lookup, + isc_refcount_current(&lookup->references) + 1); + + (void)isc_refcount_increment(&lookup->references); + + *lookupp = lookup; +} + +#define lookup_detach(l) _lookup_detach(l, __FILE__, __LINE__) +static void +_lookup_detach(dig_lookup_t **lookupp, const char *file, unsigned int line) { + REQUIRE(DIG_VALID_LOOKUP(*lookupp)); + + dig_lookup_t *lookup = *lookupp; + *lookupp = NULL; + + debug("%s:%u:lookup_detach(%p) = %" PRIuFAST32, file, line, lookup, + isc_refcount_current(&lookup->references) - 1); + + if (isc_refcount_decrement(&lookup->references) == 1) { + _destroy_lookup(lookup); + if (lookup == current_lookup) { + current_lookup = NULL; + start_lookup(); + } + } +} + +void +destroy_lookup(dig_lookup_t *lookup) { + REQUIRE(DIG_VALID_LOOKUP(lookup)); + + REQUIRE(isc_refcount_decrement(&lookup->references) == 1); + _destroy_lookup(lookup); +} + +/*% + * Destroy a query when we're done with it. WARNING: This routine + * WILL invalidate the query pointer. + */ +static void +destroy_query(dig_query_t *query, const char *file, unsigned int line) { + debug("%s:%u:destroy_query(%p) = %" PRIuFAST32, file, line, query, + isc_refcount_current(&query->references)); + + isc_refcount_destroy(&query->references); + + lookup_detach(&query->lookup); + + INSIST(query->recvspace != NULL); + + isc_mem_put(mctx, query->recvspace, COMMSIZE); + isc_mem_put(mctx, query->tmpsendspace, COMMSIZE); + + query->magic = 0; + isc_mem_free(mctx, query); +} + +#define query_attach(s, t) _query_attach(s, t, __FILE__, __LINE__) + +static void +_query_attach(dig_query_t *source, dig_query_t **targetp, const char *file, + unsigned int line) { + REQUIRE(DIG_VALID_QUERY(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + debug("%s:%u:query_attach(%p) = %" PRIuFAST32, file, line, source, + isc_refcount_current(&source->references) + 1); + + (void)isc_refcount_increment(&source->references); + + *targetp = source; +} + +#define query_detach(q) _query_detach(q, __FILE__, __LINE__) + +static void +_query_detach(dig_query_t **queryp, const char *file, unsigned int line) { + dig_query_t *query = NULL; + dig_lookup_t *lookup = NULL; + + REQUIRE(DIG_VALID_QUERY(*queryp)); + + query = *queryp; + *queryp = NULL; + + lookup = query->lookup; + + if (lookup->current_query == query) { + query_detach(&lookup->current_query); + } + + debug("%s:%u:query_detach(%p) = %" PRIuFAST32, file, line, query, + isc_refcount_current(&query->references) - 1); + + if (isc_refcount_decrement(&query->references) == 1) { + INSIST(query->readhandle == NULL); + INSIST(query->sendhandle == NULL); + + if (ISC_LINK_LINKED(query, link)) { + ISC_LIST_UNLINK(lookup->q, query, link); + } + destroy_query(query, file, line); + } +} + +/*% + * If we can, start the next lookup in the queue running. + * This assumes that the lookup on the head of the queue hasn't been + * started yet. It also removes the lookup from the head of the queue, + * setting the current_lookup pointer pointing to it. + */ +void +start_lookup(void) { + debug("start_lookup()"); + + if (atomic_load(&cancel_now)) { + return; + } + + /* + * If there's a current lookup running, we really shouldn't get + * here. + */ + INSIST(current_lookup == NULL); + + current_lookup = ISC_LIST_HEAD(lookup_list); + + /* + * Put the current lookup somewhere so cancel_all can find it + */ + if (current_lookup != NULL) { + /* + * Formally, we should attach the lookup to the current_lookup + * and detach it from the lookup_list, but it would be one + * attach and one detach. + */ + ISC_LIST_DEQUEUE(lookup_list, current_lookup, link); + if (setup_lookup(current_lookup)) { + do_lookup(current_lookup); + } else if (next_origin(current_lookup)) { + lookup_detach(¤t_lookup); + start_lookup(); + } + } else { + check_if_done(); + } +} + +/*% + * If we can, clear the current lookup and start the next one running. + * (Note that while the reference count of current_lookup may be + * decremented, current_lookup will not be set to NULL.) + */ +static void +clear_current_lookup(void) { + dig_lookup_t *lookup = current_lookup; + + INSIST(!free_now); + + debug("clear_current_lookup()"); + + if (lookup == NULL) { + debug("current_lookup is already detached"); + return; + } + + if (lookup->cleared) { + debug("current_lookup is already cleared"); + return; + } + + if (ISC_LIST_HEAD(lookup->q) != NULL) { + debug("still have a worker"); + return; + } + + lookup->cleared = true; + debug("lookup cleared"); + + lookup_detach(&lookup); +} + +/*% + * Create and queue a new lookup as a followup to the current lookup, + * based on the supplied message and section. This is used in trace and + * name server search modes to start a new lookup using servers from + * NS records in a reply. Returns the number of followup lookups made. + */ +static int +followup_lookup(dns_message_t *msg, dig_query_t *query, dns_section_t section) { + dig_lookup_t *lookup = NULL; + dig_server_t *srv = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_name_t *name = NULL; + isc_result_t result; + bool success = false; + int numLookups = 0; + int num; + isc_result_t lresult, addresses_result; + char bad_namestr[DNS_NAME_FORMATSIZE]; + dns_name_t *domain; + bool horizontal = false, bad = false; + + INSIST(!free_now); + + debug("following up %s", query->lookup->textname); + + addresses_result = ISC_R_SUCCESS; + bad_namestr[0] = '\0'; + for (result = dns_message_firstname(msg, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(msg, section)) + { + name = NULL; + dns_message_currentname(msg, section, &name); + + if (section == DNS_SECTION_AUTHORITY) { + rdataset = NULL; + result = dns_message_findtype(name, dns_rdatatype_soa, + 0, &rdataset); + if (result == ISC_R_SUCCESS) { + return (0); + } + } + rdataset = NULL; + result = dns_message_findtype(name, dns_rdatatype_ns, 0, + &rdataset); + if (result != ISC_R_SUCCESS) { + continue; + } + + debug("found NS set"); + + if (query->lookup->trace && !query->lookup->trace_root) { + dns_namereln_t namereln; + unsigned int nlabels; + int order; + + domain = dns_fixedname_name(&query->lookup->fdomain); + namereln = dns_name_fullcompare(name, domain, &order, + &nlabels); + if (namereln == dns_namereln_equal) { + if (!horizontal) { + dighost_warning("BAD (HORIZONTAL) " + "REFERRAL"); + } + horizontal = true; + } else if (namereln != dns_namereln_subdomain) { + if (!bad) { + dighost_warning("BAD REFERRAL"); + } + bad = true; + continue; + } + } + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + char namestr[DNS_NAME_FORMATSIZE]; + dns_rdata_ns_t ns; + + if (query->lookup->trace_root && + query->lookup->nsfound >= MXSERV) + { + break; + } + + dns_rdataset_current(rdataset, &rdata); + + query->lookup->nsfound++; + result = dns_rdata_tostruct(&rdata, &ns, NULL); + check_result(result, "dns_rdata_tostruct"); + dns_name_format(&ns.name, namestr, sizeof(namestr)); + dns_rdata_freestruct(&ns); + + /* Initialize lookup if we've not yet */ + debug("found NS %s", namestr); + if (!success) { + success = true; + lookup_counter++; + lookup = requeue_lookup(query->lookup, false); + cancel_lookup(query->lookup); + lookup->doing_xfr = false; + if (!lookup->trace_root && + section == DNS_SECTION_ANSWER) + { + lookup->trace = false; + } else { + lookup->trace = query->lookup->trace; + } + lookup->ns_search_only = + query->lookup->ns_search_only; + lookup->trace_root = false; + if (lookup->ns_search_only) { + lookup->recurse = false; + } + domain = dns_fixedname_name(&lookup->fdomain); + dns_name_copy(name, domain); + } + debug("adding server %s", namestr); + num = getaddresses(lookup, namestr, &lresult); + if (lresult != ISC_R_SUCCESS) { + printf("couldn't get address for '%s': %s\n", + namestr, isc_result_totext(lresult)); + if (addresses_result == ISC_R_SUCCESS) { + addresses_result = lresult; + strlcpy(bad_namestr, namestr, + sizeof(bad_namestr)); + } + } + numLookups += num; + dns_rdata_reset(&rdata); + } + } + if (numLookups == 0 && addresses_result != ISC_R_SUCCESS) { + fatal("couldn't get address for '%s': %s", bad_namestr, + isc_result_totext(result)); + } + + if (lookup == NULL && section == DNS_SECTION_ANSWER && + (query->lookup->trace || query->lookup->ns_search_only)) + { + return (followup_lookup(msg, query, DNS_SECTION_AUTHORITY)); + } + + /* + * Randomize the order the nameserver will be tried. + */ + if (numLookups > 1) { + uint32_t i, j; + dig_serverlist_t my_server_list; + dig_server_t *next; + + ISC_LIST_INIT(my_server_list); + + i = numLookups; + for (srv = ISC_LIST_HEAD(lookup->my_server_list); srv != NULL; + srv = ISC_LIST_HEAD(lookup->my_server_list)) + { + INSIST(i > 0); + j = isc_random_uniform(i); + next = ISC_LIST_NEXT(srv, link); + while (j-- > 0 && next != NULL) { + srv = next; + next = ISC_LIST_NEXT(srv, link); + } + ISC_LIST_DEQUEUE(lookup->my_server_list, srv, link); + ISC_LIST_APPEND(my_server_list, srv, link); + i--; + } + ISC_LIST_APPENDLIST(lookup->my_server_list, my_server_list, + link); + } + + return (numLookups); +} + +/*% + * Create and queue a new lookup using the next origin from the search + * list, read in setup_system(). + * + * Return true iff there was another searchlist entry. + */ +static bool +next_origin(dig_lookup_t *oldlookup) { + dig_lookup_t *newlookup; + dig_searchlist_t *search; + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + + INSIST(!free_now); + + debug("next_origin()"); + debug("following up %s", oldlookup->textname); + + if (!usesearch) { + /* + * We're not using a search list, so don't even think + * about finding the next entry. + */ + return (false); + } + + /* + * Check for a absolute name or ndots being met. + */ + name = dns_fixedname_initname(&fixed); + result = dns_name_fromstring2(name, oldlookup->textname, NULL, 0, NULL); + if (result == ISC_R_SUCCESS && + (dns_name_isabsolute(name) || + (int)dns_name_countlabels(name) > ndots)) + { + return (false); + } + + if (oldlookup->origin == NULL && !oldlookup->need_search) { + /* + * Then we just did rootorg; there's nothing left. + */ + return (false); + } + if (oldlookup->origin == NULL && oldlookup->need_search) { + newlookup = requeue_lookup(oldlookup, true); + newlookup->origin = ISC_LIST_HEAD(search_list); + newlookup->need_search = false; + } else { + search = ISC_LIST_NEXT(oldlookup->origin, link); + if (search == NULL && oldlookup->done_as_is) { + return (false); + } + newlookup = requeue_lookup(oldlookup, true); + newlookup->origin = search; + } + cancel_lookup(oldlookup); + return (true); +} + +/*% + * Insert an SOA record into the sendmessage in a lookup. Used for + * creating IXFR queries. + */ +static void +insert_soa(dig_lookup_t *lookup) { + isc_result_t result; + dns_rdata_soa_t soa; + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdataset_t *rdataset = NULL; + dns_name_t *soaname = NULL; + + debug("insert_soa()"); + soa.mctx = mctx; + soa.serial = lookup->ixfr_serial; + soa.refresh = 0; + soa.retry = 0; + soa.expire = 0; + soa.minimum = 0; + soa.common.rdclass = lookup->rdclass; + soa.common.rdtype = dns_rdatatype_soa; + + dns_name_init(&soa.origin, NULL); + dns_name_init(&soa.contact, NULL); + + dns_name_clone(dns_rootname, &soa.origin); + dns_name_clone(dns_rootname, &soa.contact); + + isc_buffer_init(&lookup->rdatabuf, lookup->rdatastore, + sizeof(lookup->rdatastore)); + + result = dns_message_gettemprdata(lookup->sendmsg, &rdata); + check_result(result, "dns_message_gettemprdata"); + + result = dns_rdata_fromstruct(rdata, lookup->rdclass, dns_rdatatype_soa, + &soa, &lookup->rdatabuf); + check_result(result, "isc_rdata_fromstruct"); + + result = dns_message_gettemprdatalist(lookup->sendmsg, &rdatalist); + check_result(result, "dns_message_gettemprdatalist"); + + result = dns_message_gettemprdataset(lookup->sendmsg, &rdataset); + check_result(result, "dns_message_gettemprdataset"); + + dns_rdatalist_init(rdatalist); + rdatalist->type = dns_rdatatype_soa; + rdatalist->rdclass = lookup->rdclass; + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + + dns_rdatalist_tordataset(rdatalist, rdataset); + + result = dns_message_gettempname(lookup->sendmsg, &soaname); + check_result(result, "dns_message_gettempname"); + dns_name_clone(lookup->name, soaname); + ISC_LIST_INIT(soaname->list); + ISC_LIST_APPEND(soaname->list, rdataset, link); + dns_message_addname(lookup->sendmsg, soaname, DNS_SECTION_AUTHORITY); +} + +static void +compute_cookie(unsigned char *clientcookie, size_t len) { + /* XXXMPA need to fix, should be per server. */ + INSIST(len >= 8U); + memmove(clientcookie, cookie_secret, 8); +} + +#define new_query(l, s, u) _new_query(l, s, u, __FILE__, __LINE__) + +static dig_query_t * +_new_query(dig_lookup_t *lookup, char *servname, char *userarg, + const char *file, unsigned int line) { + dig_query_t *query = NULL; + + query = isc_mem_allocate(mctx, sizeof(dig_query_t)); + debug("create query %p linked to lookup %p", query, lookup); + *query = (dig_query_t){ .sendbuf = lookup->renderbuf, + .servname = servname, + .userarg = userarg, + .warn_id = true, + .recvspace = isc_mem_get(mctx, COMMSIZE), + .tmpsendspace = isc_mem_get(mctx, COMMSIZE) }; + + lookup_attach(lookup, &query->lookup); + + isc_refcount_init(&query->references, 1); + + debug("%s:%u:new_query(%p) = %" PRIuFAST32, file, line, query, + isc_refcount_current(&query->references)); + + if (query->recvspace == NULL) { + fatal("memory allocation failure"); + } + if (query->tmpsendspace == NULL) { + fatal("memory allocation failure"); + } + + isc_time_settoepoch(&query->time_sent); + isc_time_settoepoch(&query->time_recv); + + ISC_LINK_INIT(query, clink); + ISC_LINK_INIT(query, link); + + query->magic = DIG_QUERY_MAGIC; + return (query); +} + +/*% + * Setup the supplied lookup structure, making it ready to start sending + * queries to servers. Create and initialize the message to be sent as + * well as the query structures and buffer space for the replies. If the + * server list is empty, clone it from the system default list. + */ +bool +setup_lookup(dig_lookup_t *lookup) { + isc_result_t result; + unsigned int len; + dig_server_t *serv; + dig_query_t *query; + isc_buffer_t b; + dns_compress_t cctx; + char store[MXNAME]; + char ecsbuf[20]; + char cookiebuf[256]; + char *origin = NULL; + char *textname = NULL; + + REQUIRE(lookup != NULL); + +#ifdef HAVE_LIBIDN2 + char idn_origin[MXNAME], idn_textname[MXNAME]; +#endif /* HAVE_LIBIDN2 */ + + INSIST(!free_now); + + debug("setup_lookup(%p)", lookup); + + dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &lookup->sendmsg); + + if (lookup->new_search) { + debug("resetting lookup counter."); + lookup_counter = 0; + } + + if (ISC_LIST_EMPTY(lookup->my_server_list)) { + debug("cloning server list"); + clone_server_list(server_list, &lookup->my_server_list); + } + result = dns_message_gettempname(lookup->sendmsg, &lookup->name); + check_result(result, "dns_message_gettempname"); + + isc_buffer_init(&lookup->namebuf, lookup->name_space, + sizeof(lookup->name_space)); + isc_buffer_init(&lookup->onamebuf, lookup->oname_space, + sizeof(lookup->oname_space)); + + /* + * We cannot convert `textname' and `origin' separately. + * `textname' doesn't contain TLD, but local mapping needs + * TLD. + */ + textname = lookup->textname; +#ifdef HAVE_LIBIDN2 + if (lookup->idnin) { + idn_locale_to_ace(textname, idn_textname, sizeof(idn_textname)); + debug("idn_textname: %s", idn_textname); + textname = idn_textname; + } +#endif /* HAVE_LIBIDN2 */ + + /* + * If the name has too many dots, force the origin to be NULL + * (which produces an absolute lookup). Otherwise, take the origin + * we have if there's one in the struct already. If it's NULL, + * take the first entry in the searchlist iff either usesearch + * is TRUE or we got a domain line in the resolv.conf file. + */ + if (lookup->new_search) { + if ((count_dots(textname) >= ndots) || !usesearch) { + lookup->origin = NULL; /* Force abs lookup */ + lookup->done_as_is = true; + lookup->need_search = usesearch; + } else if (lookup->origin == NULL && usesearch) { + lookup->origin = ISC_LIST_HEAD(search_list); + lookup->need_search = false; + } + } + + if (lookup->origin != NULL) { + debug("trying origin %s", lookup->origin->origin); + result = dns_message_gettempname(lookup->sendmsg, + &lookup->oname); + check_result(result, "dns_message_gettempname"); + /* XXX Helper funct to conv char* to name? */ + origin = lookup->origin->origin; +#ifdef HAVE_LIBIDN2 + if (lookup->idnin) { + idn_locale_to_ace(origin, idn_origin, + sizeof(idn_origin)); + debug("trying idn origin %s", idn_origin); + origin = idn_origin; + } +#endif /* HAVE_LIBIDN2 */ + len = (unsigned int)strlen(origin); + isc_buffer_init(&b, origin, len); + isc_buffer_add(&b, len); + result = dns_name_fromtext(lookup->oname, &b, dns_rootname, 0, + &lookup->onamebuf); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(lookup->sendmsg, &lookup->name); + dns_message_puttempname(lookup->sendmsg, + &lookup->oname); + fatal("'%s' is not in legal name syntax (%s)", origin, + isc_result_totext(result)); + } + if (lookup->trace && lookup->trace_root) { + dns_name_clone(dns_rootname, lookup->name); + } else { + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + len = (unsigned int)strlen(textname); + isc_buffer_init(&b, textname, len); + isc_buffer_add(&b, len); + result = dns_name_fromtext(name, &b, NULL, 0, NULL); + if (result == ISC_R_SUCCESS) { + if (!dns_name_isabsolute(name)) { + result = dns_name_concatenate( + name, lookup->oname, + lookup->name, &lookup->namebuf); + } else { + dns_name_copy(name, lookup->name); + } + } + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(lookup->sendmsg, + &lookup->name); + dns_message_puttempname(lookup->sendmsg, + &lookup->oname); + if (result == DNS_R_NAMETOOLONG) { + return (false); + } + fatal("'%s' is not in legal name syntax (%s)", + lookup->textname, + isc_result_totext(result)); + } + } + dns_message_puttempname(lookup->sendmsg, &lookup->oname); + } else { + debug("using root origin"); + if (lookup->trace && lookup->trace_root) { + dns_name_clone(dns_rootname, lookup->name); + } else { + len = (unsigned int)strlen(textname); + isc_buffer_init(&b, textname, len); + isc_buffer_add(&b, len); + result = dns_name_fromtext(lookup->name, &b, + dns_rootname, 0, + &lookup->namebuf); + } + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(lookup->sendmsg, &lookup->name); + warn("'%s' is not a legal name " + "(%s)", + lookup->textname, isc_result_totext(result)); +#if TARGET_OS_IPHONE + clear_current_lookup(); + return (false); +#else /* if TARGET_OS_IPHONE */ + cleanup_openssl_refs(); + digexit(); +#endif /* if TARGET_OS_IPHONE */ + } + } + dns_name_format(lookup->name, store, sizeof(store)); + dighost_trying(store, lookup); + INSIST(dns_name_isabsolute(lookup->name)); + + lookup->sendmsg->id = (dns_messageid_t)isc_random16(); + lookup->sendmsg->opcode = lookup->opcode; + lookup->msgcounter = 0; + + /* + * If this is a trace request, completely disallow recursion after + * looking up the root name servers, since it's meaningless for traces. + */ + if ((lookup->trace || lookup->ns_search_only) && !lookup->trace_root) { + lookup->recurse = false; + } + + if (lookup->recurse && lookup->rdtype != dns_rdatatype_axfr && + lookup->rdtype != dns_rdatatype_ixfr) + { + debug("recursive query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_RD; + } + + /* XXX aaflag */ + if (lookup->aaonly) { + debug("AA query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_AA; + } + + if (lookup->adflag) { + debug("AD query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_AD; + } + + if (lookup->cdflag) { + debug("CD query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_CD; + } + + if (lookup->raflag) { + debug("RA query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_RA; + } + + if (lookup->tcflag) { + debug("TC query"); + lookup->sendmsg->flags |= DNS_MESSAGEFLAG_TC; + } + + if (lookup->zflag) { + debug("Z query"); + lookup->sendmsg->flags |= 0x0040U; + } + + if (lookup->setqid) { + debug("set QID"); + lookup->sendmsg->id = lookup->qid; + } + + dns_message_addname(lookup->sendmsg, lookup->name, + DNS_SECTION_QUESTION); + + if (lookup->trace && lookup->trace_root) { + lookup->qrdtype = lookup->rdtype; + lookup->rdtype = dns_rdatatype_ns; + } + + if ((lookup->rdtype == dns_rdatatype_axfr) || + (lookup->rdtype == dns_rdatatype_ixfr)) + { + /* + * Force TCP mode if we're doing an axfr. + */ + if (lookup->rdtype == dns_rdatatype_axfr) { + lookup->doing_xfr = true; + lookup->tcp_mode = true; + } else if (lookup->tcp_mode) { + lookup->doing_xfr = true; + } + } + + if (!lookup->header_only) { + add_question(lookup->sendmsg, lookup->name, lookup->rdclass, + lookup->rdtype); + } + + /* add_soa */ + if (lookup->rdtype == dns_rdatatype_ixfr) { + insert_soa(lookup); + } + + /* XXX Insist this? */ + lookup->tsigctx = NULL; + lookup->querysig = NULL; + if (tsigkey != NULL) { + debug("initializing keys"); + result = dns_message_settsigkey(lookup->sendmsg, tsigkey); + check_result(result, "dns_message_settsigkey"); + } else if (sig0key != NULL) { + debug("initializing keys"); + result = dns_message_setsig0key(lookup->sendmsg, sig0key); + check_result(result, "dns_message_setsig0key"); + } + + if (lookup->fuzzing) { + lookup->sendmsg->fuzzing = true; + lookup->sendmsg->fuzztime = lookup->fuzztime; + } + + lookup->sendspace = isc_mem_get(mctx, COMMSIZE); + + result = dns_compress_init(&cctx, -1, mctx); + check_result(result, "dns_compress_init"); + + debug("starting to render the message"); + isc_buffer_init(&lookup->renderbuf, lookup->sendspace, COMMSIZE); + result = dns_message_renderbegin(lookup->sendmsg, &cctx, + &lookup->renderbuf); + check_result(result, "dns_message_renderbegin"); + if (lookup->udpsize > -1 || lookup->dnssec || lookup->edns > -1 || + lookup->ecs_addr != NULL) + { +#define MAXOPTS (EDNSOPT_OPTIONS + DNS_EDNSOPTIONS) + dns_ednsopt_t opts[MAXOPTS]; + unsigned int flags; + unsigned int i = 0; + + /* + * There can't be more than MAXOPTS options to send: + * a maximum of EDNSOPT_OPTIONS set by +ednsopt + * and DNS_EDNSOPTIONS set by other arguments + * (+nsid, +cookie, etc). + */ + if (lookup->udpsize < 0) { + lookup->udpsize = DEFAULT_EDNS_BUFSIZE; + } + if (lookup->edns < 0) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + + if (lookup->nsid) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_NSID; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + + if (lookup->ecs_addr != NULL) { + uint8_t addr[16]; + uint16_t family = 0; + uint32_t plen; + struct sockaddr *sa; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + size_t addrl; + + sa = &lookup->ecs_addr->type.sa; + plen = lookup->ecs_addr->length; + + /* Round up prefix len to a multiple of 8 */ + addrl = (plen + 7) / 8; + + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_CLIENT_SUBNET; + opts[i].length = (uint16_t)addrl + 4; + check_result(result, "isc_buffer_allocate"); + + /* + * XXXMUKS: According to RFC7871, "If there is + * no ADDRESS set, i.e., SOURCE PREFIX-LENGTH is + * set to 0, then FAMILY SHOULD be set to the + * transport over which the query is sent." + * + * However, at this point we don't know what + * transport(s) we'll be using, so we can't + * set the value now. For now, we're using + * IPv4 as the default the +subnet option + * used an IPv4 prefix, or for +subnet=0, + * and IPv6 if the +subnet option used an + * IPv6 prefix. + * + * (For future work: preserve the offset into + * the buffer where the family field is; + * that way we can update it in start_udp() + * or start_tcp() once we know + * what it outght to be.) + */ + switch (sa->sa_family) { + case AF_UNSPEC: + INSIST(plen == 0); + family = 1; + break; + case AF_INET: + INSIST(plen <= 32); + family = 1; + sin = (struct sockaddr_in *)sa; + memmove(addr, &sin->sin_addr, addrl); + break; + case AF_INET6: + INSIST(plen <= 128); + family = 2; + sin6 = (struct sockaddr_in6 *)sa; + memmove(addr, &sin6->sin6_addr, addrl); + break; + default: + UNREACHABLE(); + } + + isc_buffer_init(&b, ecsbuf, sizeof(ecsbuf)); + /* family */ + isc_buffer_putuint16(&b, family); + /* source prefix-length */ + isc_buffer_putuint8(&b, plen); + /* scope prefix-length */ + isc_buffer_putuint8(&b, 0); + + /* address */ + if (addrl > 0) { + /* Mask off last address byte */ + if ((plen % 8) != 0) { + addr[addrl - 1] &= ~0U + << (8 - (plen % 8)); + } + isc_buffer_putmem(&b, addr, (unsigned)addrl); + } + + opts[i].value = (uint8_t *)ecsbuf; + i++; + } + + if (lookup->sendcookie) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_COOKIE; + if (lookup->cookie != NULL) { + isc_buffer_init(&b, cookiebuf, + sizeof(cookiebuf)); + result = isc_hex_decodestring(lookup->cookie, + &b); + check_result(result, "isc_hex_decodestring"); + opts[i].value = isc_buffer_base(&b); + opts[i].length = isc_buffer_usedlength(&b); + } else { + compute_cookie(cookie, sizeof(cookie)); + opts[i].length = 8; + opts[i].value = cookie; + } + i++; + } + + if (lookup->expire) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_EXPIRE; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + + if (lookup->tcp_keepalive) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_TCP_KEEPALIVE; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + + if (lookup->ednsoptscnt != 0) { + INSIST(i + lookup->ednsoptscnt <= MAXOPTS); + memmove(&opts[i], lookup->ednsopts, + sizeof(dns_ednsopt_t) * lookup->ednsoptscnt); + i += lookup->ednsoptscnt; + } + + if (lookup->padding != 0 && (i >= MAXOPTS)) { + debug("turned off padding because of EDNS overflow"); + lookup->padding = 0; + } + + if (lookup->padding != 0) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_PAD; + opts[i].length = 0; + opts[i].value = NULL; + i++; + dns_message_setpadding(lookup->sendmsg, + lookup->padding); + } + + flags = lookup->ednsflags; + flags &= ~DNS_MESSAGEEXTFLAG_DO; + if (lookup->dnssec) { + flags |= DNS_MESSAGEEXTFLAG_DO; + } + add_opt(lookup->sendmsg, lookup->udpsize, lookup->edns, flags, + opts, i); + } + + result = dns_message_rendersection(lookup->sendmsg, + DNS_SECTION_QUESTION, 0); + check_result(result, "dns_message_rendersection"); + result = dns_message_rendersection(lookup->sendmsg, + DNS_SECTION_AUTHORITY, 0); + check_result(result, "dns_message_rendersection"); + result = dns_message_renderend(lookup->sendmsg); + check_result(result, "dns_message_renderend"); + debug("done rendering"); + + dns_compress_invalidate(&cctx); + + /* + * Force TCP mode if the request is larger than 512 bytes. + */ + if (isc_buffer_usedlength(&lookup->renderbuf) > 512) { + lookup->tcp_mode = true; + } + + lookup->pending = false; + + for (serv = ISC_LIST_HEAD(lookup->my_server_list); serv != NULL; + serv = ISC_LIST_NEXT(serv, link)) + { + query = new_query(lookup, serv->servername, serv->userarg); + ISC_LIST_ENQUEUE(lookup->q, query, link); + } + + return (true); +} + +/*% + * NSSEARCH mode special mode handling function to start the next query in the + * list. The lookup lock must be held by the caller. The function will detach + * both the lookup and the query, and may cancel the lookup and clear the + * current lookup. + */ +static void +nssearch_next(dig_lookup_t *l, dig_query_t *q) { + dig_query_t *next = ISC_LIST_NEXT(q, link); + bool tcp_mode = l->tcp_mode; + + INSIST(l->ns_search_only && !l->trace_root); + INSIST(l == current_lookup); + + if (next == NULL) { + /* + * If this is the last query, and if there was + * not a single successful query in the whole + * lookup, then treat the situation as an error, + * cancel and clear the lookup. + */ + if (check_if_queries_done(l, q) && !l->ns_search_success) { + dighost_error("NS servers could not be reached"); + if (exitcode < 9) { + exitcode = 9; + } + + cancel_lookup(l); + query_detach(&q); + lookup_detach(&l); + clear_current_lookup(); + } else { + query_detach(&q); + lookup_detach(&l); + } + } else { + query_detach(&q); + lookup_detach(&l); + + debug("sending next, since searching"); + if (tcp_mode) { + start_tcp(next); + } else { + start_udp(next); + } + } +} + +/*% + * Event handler for send completion. Track send counter, and clear out + * the query if the send was canceled. + */ +static void +send_done(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + dig_query_t *query = (dig_query_t *)arg; + dig_lookup_t *l = NULL; + + REQUIRE(DIG_VALID_QUERY(query)); + INSIST(query->sendhandle != NULL); + INSIST(handle == query->sendhandle); + + debug("send_done(%p, %s, %p)", handle, isc_result_totext(eresult), arg); + + isc_refcount_decrement0(&sendcount); + debug("sendcount=%" PRIuFAST32, isc_refcount_current(&sendcount)); + + INSIST(!free_now); + + LOCK_LOOKUP; + + isc_nmhandle_detach(&query->sendhandle); + + lookup_attach(query->lookup, &l); + + if (eresult == ISC_R_CANCELED || query->canceled) { + debug("send_done: cancel"); + if (!query->canceled) { + cancel_lookup(l); + } + query_detach(&query); + lookup_detach(&l); + UNLOCK_LOOKUP; + return; + } else if (eresult != ISC_R_SUCCESS) { + debug("send failed: %s", isc_result_totext(eresult)); + } + + if (l->ns_search_only && !l->trace_root) { + nssearch_next(l, query); + } else { + query_detach(&query); + lookup_detach(&l); + } + + check_if_done(); + UNLOCK_LOOKUP; +} + +/*% + * Cancel a lookup, sending canceling reads on all existing sockets. + */ + +static void +_cancel_lookup(dig_lookup_t *lookup, const char *file, unsigned int line) { + dig_query_t *query, *next; + + debug("%s:%u:%s()", file, line, __func__); + query = ISC_LIST_HEAD(lookup->q); + while (query != NULL) { + REQUIRE(DIG_VALID_QUERY(query)); + next = ISC_LIST_NEXT(query, link); + ISC_LIST_DEQUEUE(lookup->q, query, link); + debug("canceling pending query %p, belonging to %p", query, + query->lookup); + query->canceled = true; + if (query->readhandle != NULL && + !isc_nm_is_http_handle(query->readhandle)) + { + isc_nm_cancelread(query->readhandle); + } + query_detach(&query); + query = next; + } + lookup->pending = false; + lookup->retries = 0; + check_if_done(); +} + +static isc_tlsctx_t * +get_create_tls_context(dig_query_t *query, const bool is_https, + isc_tlsctx_client_session_cache_t **psess_cache) { + isc_result_t result; + isc_tlsctx_t *ctx = NULL, *found_ctx = NULL; + isc_tls_cert_store_t *store = NULL, *found_store = NULL; + char tlsctxname[ISC_SOCKADDR_FORMATSIZE]; + const uint16_t family = isc_sockaddr_pf(&query->sockaddr) == PF_INET6 + ? AF_INET6 + : AF_INET; + isc_tlsctx_cache_transport_t transport = + is_https ? isc_tlsctx_cache_https : isc_tlsctx_cache_tls; + const bool hostname_ignore_subject = !is_https; + isc_tlsctx_client_session_cache_t *sess_cache = NULL, + *found_sess_cache = NULL; + + if (query->lookup->tls_key_file_set != query->lookup->tls_cert_file_set) + { + return (NULL); + } + + isc_sockaddr_format(&query->sockaddr, tlsctxname, sizeof(tlsctxname)); + + result = isc_tlsctx_cache_find(query->lookup->tls_ctx_cache, tlsctxname, + transport, family, &found_ctx, + &found_store, &found_sess_cache); + if (result != ISC_R_SUCCESS) { + if (query->lookup->tls_ca_set) { + if (found_store == NULL) { + result = isc_tls_cert_store_create( + query->lookup->tls_ca_file, &store); + + if (result != ISC_R_SUCCESS) { + goto failure; + } + } else { + store = found_store; + } + } + + result = isc_tlsctx_createclient(&ctx); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + if (store != NULL) { + const char *hostname = + query->lookup->tls_hostname_set + ? query->lookup->tls_hostname + : query->userarg; + /* + * According to RFC 8310, Subject field MUST NOT be + * inspected when verifying hostname for DoT. Only + * SubjectAltName must be checked. That is NOT the case + * for HTTPS. + */ + result = isc_tlsctx_enable_peer_verification( + ctx, false, store, hostname, + hostname_ignore_subject); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + if (query->lookup->tls_key_file_set && + query->lookup->tls_cert_file_set) + { + result = isc_tlsctx_load_certificate( + ctx, query->lookup->tls_key_file, + query->lookup->tls_cert_file); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + if (!is_https) { + isc_tlsctx_enable_dot_client_alpn(ctx); + } + +#if HAVE_LIBNGHTTP2 + if (is_https) { + isc_tlsctx_enable_http2client_alpn(ctx); + } +#endif /* HAVE_LIBNGHTTP2 */ + + isc_tlsctx_client_session_cache_create( + mctx, ctx, ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE, + &sess_cache); + + result = isc_tlsctx_cache_add( + query->lookup->tls_ctx_cache, tlsctxname, transport, + family, ctx, store, sess_cache, NULL, NULL, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (psess_cache != NULL) { + INSIST(*psess_cache == NULL); + *psess_cache = sess_cache; + } + return (ctx); + } + + if (psess_cache != NULL) { + INSIST(*psess_cache == NULL); + *psess_cache = found_sess_cache; + } + + INSIST(!query->lookup->tls_ca_set || found_store != NULL); + return (found_ctx); +failure: + if (ctx != NULL) { + isc_tlsctx_free(&ctx); + } + /* + * The 'found_store' is being managed by the TLS context + * cache. Thus, we should keep it as it is, as it will get + * destroyed alongside the cache. As there is one store per + * multiple TLS contexts, we need to handle store deletion in a + * special way. + */ + if (store != NULL && store != found_store) { + isc_tls_cert_store_free(&store); + } + return (NULL); +} + +static void +tcp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg); + +/*% + * Unlike start_udp, this can't be called multiple times with the same + * query. When we retry TCP, we requeue the whole lookup, which should + * start anew. + */ +static void +start_tcp(dig_query_t *query) { + isc_result_t result; + dig_query_t *next = NULL; + dig_query_t *connectquery = NULL; + isc_tlsctx_t *tlsctx = NULL; + bool tls_mode = false; + isc_tlsctx_client_session_cache_t *sess_cache = NULL; + REQUIRE(DIG_VALID_QUERY(query)); + + debug("start_tcp(%p)", query); + + query_attach(query, &query->lookup->current_query); + + tls_mode = dig_lookup_is_tls(query->lookup); + + /* + * For TLS connections, we want to override the default + * port number. + */ + if (!port_set) { + if (tls_mode) { + port = 853; + } else if (query->lookup->https_mode && + !query->lookup->http_plain) + { + port = 443; + } else if (query->lookup->https_mode) { + port = 80; + } else { + port = 53; + } + } + + debug("query->servname = %s\n", query->servname); + + result = get_address(query->servname, port, &query->sockaddr); + if (result != ISC_R_SUCCESS) { + /* + * This servname doesn't have an address. Try the next server + * by triggering an immediate 'timeout' (we lie, but the effect + * is the same). + */ + force_next(query); + return; + } + + if (isc_sockaddr_pf(&query->sockaddr) == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&query->sockaddr.type.sin6.sin6_addr)) + { + isc_netaddr_t netaddr; + char buf[ISC_NETADDR_FORMATSIZE]; + + isc_netaddr_fromsockaddr(&netaddr, &query->sockaddr); + isc_netaddr_format(&netaddr, buf, sizeof(buf)); + dighost_warning("Skipping mapped address '%s'", buf); + + if (ISC_LINK_LINKED(query, link)) { + next = ISC_LIST_NEXT(query, link); + } else { + next = NULL; + } + query_detach(&query); + if (next == NULL) { + dighost_warning("No acceptable nameservers"); + clear_current_lookup(); + } else { + start_tcp(next); + } + return; + } + + INSIST(query->handle == NULL); + + if (keep != NULL && isc_sockaddr_equal(&keepaddr, &query->sockaddr)) { + query->handle = keep; + launch_next_query(query); + query_detach(&query); + return; + } else { + int local_timeout = timeout * 1000; + if (local_timeout == 0) { + local_timeout = TCP_TIMEOUT * 1000; + } + + if (keep != NULL) { + isc_nmhandle_detach(&keep); + } + + if (!specified_source) { + if ((isc_sockaddr_pf(&query->sockaddr) == AF_INET) && + have_ipv4) + { + isc_sockaddr_any(&localaddr); + } else { + isc_sockaddr_any6(&localaddr); + } + } + + REQUIRE(query != NULL); + + query_attach(query, &connectquery); + + if (tls_mode) { + tlsctx = get_create_tls_context(connectquery, false, + &sess_cache); + if (tlsctx == NULL) { + goto failure_tls; + } + isc_nm_tlsdnsconnect(netmgr, &localaddr, + &query->sockaddr, tcp_connected, + connectquery, local_timeout, 0, + tlsctx, sess_cache); +#if HAVE_LIBNGHTTP2 + } else if (query->lookup->https_mode) { + char uri[4096] = { 0 }; + isc_nm_http_makeuri(!query->lookup->http_plain, + &query->sockaddr, query->userarg, + port, query->lookup->https_path, + uri, sizeof(uri)); + + if (!query->lookup->http_plain) { + tlsctx = get_create_tls_context( + connectquery, true, &sess_cache); + if (tlsctx == NULL) { + goto failure_tls; + } + } + + isc_nm_httpconnect(netmgr, &localaddr, &query->sockaddr, + uri, !query->lookup->https_get, + tcp_connected, connectquery, tlsctx, + sess_cache, 0, local_timeout); +#endif + } else { + isc_nm_tcpdnsconnect(netmgr, &localaddr, + &query->sockaddr, tcp_connected, + connectquery, local_timeout, 0); + } + } + return; +failure_tls: + if (query->lookup->tls_key_file_set != query->lookup->tls_cert_file_set) + { + dighost_warning( + "both TLS client certificate and key file must be " + "specified a the same time"); + } else { + dighost_warning("TLS context cannot be created"); + } + + if (ISC_LINK_LINKED(query, link)) { + next = ISC_LIST_NEXT(query, link); + } else { + next = NULL; + } + query_detach(&connectquery); + query_detach(&query); + if (next == NULL) { + clear_current_lookup(); + } else { + start_tcp(next); + } +} + +static void +print_query_size(dig_query_t *query) { + if (!yaml) { + printf(";; QUERY SIZE: %u\n\n", + isc_buffer_usedlength(&query->lookup->renderbuf)); + } +} + +static void +send_udp(dig_query_t *query) { + dig_query_t *sendquery = NULL; + isc_region_t r; + + query_attach(query, &sendquery); + + isc_buffer_usedregion(&query->sendbuf, &r); + debug("sending a request"); + if (query->lookup->use_usec) { + TIME_NOW_HIRES(&query->time_sent); + } else { + TIME_NOW(&query->time_sent); + } + + isc_nmhandle_attach(query->handle, &query->sendhandle); + + isc_nm_send(query->handle, &r, send_done, sendquery); + isc_refcount_increment0(&sendcount); + debug("sendcount=%" PRIuFAST32, isc_refcount_current(&sendcount)); + + /* XXX qrflag, print_query, etc... */ + if (query->lookup->qr) { + extrabytes = 0; + dighost_printmessage(query, &query->lookup->renderbuf, + query->lookup->sendmsg, true); + if (query->lookup->stats) { + print_query_size(query); + } + } +} + +static void +udp_ready(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + dig_query_t *query = (dig_query_t *)arg; + dig_query_t *next = NULL; + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + dig_lookup_t *l = NULL; + dig_query_t *readquery = NULL; + int local_timeout = timeout * 1000; + + REQUIRE(DIG_VALID_QUERY(query)); + REQUIRE(query->handle == NULL); + + debug("udp_ready()"); + + query->started = true; + + if (atomic_load(&cancel_now)) { + query_detach(&query); + return; + } + + INSIST(!free_now); + + debug("udp_ready(%p, %s, %p)", handle, isc_result_totext(eresult), + query); + + LOCK_LOOKUP; + lookup_attach(query->lookup, &l); + + if (eresult == ISC_R_CANCELED || query->canceled) { + debug("in cancel handler"); + if (!query->canceled) { + cancel_lookup(l); + } + query_detach(&query); + lookup_detach(&l); + clear_current_lookup(); + UNLOCK_LOOKUP; + return; + } + + if (eresult != ISC_R_SUCCESS) { + debug("udp setup failed: %s", isc_result_totext(eresult)); + isc_sockaddr_format(&query->sockaddr, sockstr, sizeof(sockstr)); + dighost_warning("UDP setup with %s(%s) for %s failed: %s.", + sockstr, query->servname, l->textname, + isc_result_totext(eresult)); + + /* + * NSSEARCH mode: if the current query failed to start properly, + * then send_done() will not be called, and we want to make sure + * that the next query gets a chance to start in order to not + * break the chain. + */ + if (l->ns_search_only && !l->trace_root) { + nssearch_next(l, query); + + check_if_done(); + UNLOCK_LOOKUP; + return; + } + + if (exitcode < 9) { + exitcode = 9; + } + + if (l->retries > 1) { + l->retries--; + debug("making new UDP request, %d tries left", + l->retries); + requeue_lookup(l, true); + next = NULL; + } else if ((l->current_query != NULL) && + (ISC_LINK_LINKED(l->current_query, link))) + { + next = ISC_LIST_NEXT(l->current_query, link); + } else { + next = NULL; + } + + query_detach(&query); + if (next == NULL) { + cancel_lookup(l); + } + lookup_detach(&l); + + if (next != NULL) { + start_udp(next); + check_if_done(); + } else { + clear_current_lookup(); + } + + UNLOCK_LOOKUP; + return; + } + + exitcode = 0; + + query_attach(query, &readquery); + + debug("recving with lookup=%p, query=%p, handle=%p", query->lookup, + query, handle); + + query->handle = handle; + isc_nmhandle_attach(handle, &query->readhandle); + isc_refcount_increment0(&recvcount); + debug("recvcount=%" PRIuFAST32, isc_refcount_current(&recvcount)); + + if (local_timeout == 0) { + local_timeout = UDP_TIMEOUT * 1000; + } + + debug("have local timeout of %d", local_timeout); + isc_nmhandle_settimeout(handle, local_timeout); + + isc_nm_read(handle, recv_done, readquery); + send_udp(readquery); + + query_detach(&query); + lookup_detach(&l); + UNLOCK_LOOKUP; +} + +/*% + * Send a UDP packet to the remote nameserver, possible starting the + * recv action as well. Also make sure that the timer is running and + * is properly reset. + */ +static void +start_udp(dig_query_t *query) { + isc_result_t result; + dig_query_t *next = NULL; + dig_query_t *connectquery = NULL; + + REQUIRE(DIG_VALID_QUERY(query)); + + debug("start_udp(%p)", query); + + query_attach(query, &query->lookup->current_query); + debug("working on lookup %p, query %p", query->lookup, query); + + if (query->handle != NULL) { + launch_next_query(query); + query_detach(&query); + return; + } + + result = get_address(query->servname, port, &query->sockaddr); + if (result != ISC_R_SUCCESS) { + /* This servname doesn't have an address. */ + force_next(query); + return; + } + + if (isc_sockaddr_pf(&query->sockaddr) == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&query->sockaddr.type.sin6.sin6_addr)) + { + isc_netaddr_t netaddr; + char buf[ISC_NETADDR_FORMATSIZE]; + + isc_netaddr_fromsockaddr(&netaddr, &query->sockaddr); + isc_netaddr_format(&netaddr, buf, sizeof(buf)); + dighost_warning("Skipping mapped address '%s'", buf); + next = ISC_LIST_NEXT(query, link); + query_detach(&query); + if (next == NULL) { + dighost_warning("No acceptable nameservers"); + clear_current_lookup(); + } else { + start_udp(next); + } + return; + } + + if (!specified_source) { + if ((isc_sockaddr_pf(&query->sockaddr) == AF_INET) && have_ipv4) + { + isc_sockaddr_any(&localaddr); + } else { + isc_sockaddr_any6(&localaddr); + } + } + + query_attach(query, &connectquery); + isc_nm_udpconnect(netmgr, &localaddr, &query->sockaddr, udp_ready, + connectquery, + (timeout ? timeout : UDP_TIMEOUT) * 1000, 0); +} + +/*% + * If there are more servers available for querying within 'lookup', initiate a + * TCP or UDP query to the next available server and return true; otherwise, + * return false. + */ +static bool +try_next_server(dig_lookup_t *lookup) { + dig_query_t *current_query, *next_query; + + current_query = lookup->current_query; + if (current_query == NULL || !ISC_LINK_LINKED(current_query, link)) { + return (false); + } + + next_query = ISC_LIST_NEXT(current_query, link); + if (next_query == NULL) { + return (false); + } + + debug("trying next server..."); + + if (lookup->tcp_mode) { + start_tcp(next_query); + } else { + start_udp(next_query); + } + + return (true); +} + +static void +force_next(dig_query_t *query) { + dig_lookup_t *l = NULL; + + REQUIRE(DIG_VALID_QUERY(query)); + + debug("force_next()"); + + LOCK_LOOKUP; + INSIST(!free_now); + + if (atomic_load(&cancel_now)) { + UNLOCK_LOOKUP; + return; + } + + lookup_attach(query->lookup, &l); + + if (try_next_server(l)) { + lookup_detach(&l); + UNLOCK_LOOKUP; + return; + } + + if (l->retries > 1) { + l->retries--; + debug("making new %s request, %d tries left", + l->tcp_mode ? "TCP" : "UDP", l->retries); + requeue_lookup(l, true); + lookup_detach(&l); + isc_refcount_decrement0(&recvcount); + debug("recvcount=%" PRIuFAST32, + isc_refcount_current(&recvcount)); + query_detach(&query); + clear_current_lookup(); + UNLOCK_LOOKUP; + return; + } + + if (query->readhandle != NULL) { + isc_refcount_decrement0(&recvcount); + debug("recvcount=%" PRIuFAST32, + isc_refcount_current(&recvcount)); + } + + if (l->ns_search_only) { + isc_netaddr_t netaddr; + char buf[ISC_NETADDR_FORMATSIZE]; + + isc_netaddr_fromsockaddr(&netaddr, &query->sockaddr); + isc_netaddr_format(&netaddr, buf, sizeof(buf)); + + dighost_error("no response from %s\n", buf); + } else { + printf("%s", l->cmdline); + dighost_error("no servers could be reached\n"); + } + + if (exitcode < 9) { + exitcode = 9; + } + + query_detach(&query); + cancel_lookup(l); + lookup_detach(&l); + clear_current_lookup(); + UNLOCK_LOOKUP; +} + +/*% + * For transfers that involve multiple recvs (XFR's in particular), + * launch the next recv. + */ +static void +launch_next_query(dig_query_t *query) { + dig_query_t *readquery = NULL; + int local_timeout = timeout * 1000; + dig_lookup_t *l = NULL; + isc_region_t r; + bool xfr; + + REQUIRE(DIG_VALID_QUERY(query)); + INSIST(!free_now); + + debug("launch_next_query()"); + + lookup_attach(query->lookup, &l); + + if (!l->pending) { + debug("ignoring launch_next_query because !pending"); + query_detach(&query); + lookup_detach(&l); + clear_current_lookup(); + return; + } + + isc_nmhandle_attach(query->handle, &query->readhandle); + isc_refcount_increment0(&recvcount); + debug("recvcount=%" PRIuFAST32, isc_refcount_current(&recvcount)); + + if (local_timeout == 0) { + local_timeout = TCP_TIMEOUT * 1000; + } + + debug("have local timeout of %d", local_timeout); + isc_nmhandle_settimeout(query->handle, local_timeout); + + xfr = query->lookup->rdtype == dns_rdatatype_ixfr || + query->lookup->rdtype == dns_rdatatype_axfr; + if (xfr && isc_nm_socket_type(query->handle) == isc_nm_tlsdnssocket) { + isc_result_t result = isc_nm_xfr_checkperm(query->handle); + if (result != ISC_R_SUCCESS) { + dighost_error("zone transfers over the established TLS " + "connection are not allowed: %s", + isc_result_totext(result)); + isc_refcount_decrement0(&recvcount); + isc_nmhandle_detach(&query->readhandle); + cancel_lookup(l); + lookup_detach(&l); + clear_current_lookup(); + return; + } + } + + query_attach(query, &readquery); + + isc_nm_read(query->handle, recv_done, readquery); + + if (!query->first_soa_rcvd) { + dig_query_t *sendquery = NULL; + debug("sending a request in launch_next_query"); + if (query->lookup->use_usec) { + TIME_NOW_HIRES(&query->time_sent); + } else { + TIME_NOW(&query->time_sent); + } + + query_attach(query, &sendquery); + isc_buffer_usedregion(&query->sendbuf, &r); + if (keep != NULL) { + query->handle = keep; + } + + isc_nmhandle_attach(query->handle, &query->sendhandle); + isc_nm_send(query->handle, &r, send_done, sendquery); + isc_refcount_increment0(&sendcount); + debug("sendcount=%" PRIuFAST32, + isc_refcount_current(&sendcount)); + + /* XXX qrflag, print_query, etc... */ + if (l->qr) { + extrabytes = 0; + dighost_printmessage(query, &l->renderbuf, l->sendmsg, + true); + if (l->stats) { + print_query_size(query); + } + } + } + + lookup_detach(&l); + return; +} + +/*% + * Event handler for TCP connect complete. Make sure the connection was + * successful, then pass into launch_next_query to actually send the + * question. + */ +static void +tcp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + dig_query_t *query = (dig_query_t *)arg; + dig_query_t *next = NULL; + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + dig_lookup_t *l = NULL; + + REQUIRE(DIG_VALID_QUERY(query)); + REQUIRE(query->handle == NULL); + + debug("tcp_connected()"); + + query->started = true; + + if (atomic_load(&cancel_now)) { + query_detach(&query); + return; + } + + INSIST(!free_now); + + debug("tcp_connected(%p, %s, %p)", handle, isc_result_totext(eresult), + query); + + LOCK_LOOKUP; + lookup_attach(query->lookup, &l); + + if (eresult == ISC_R_CANCELED || eresult == ISC_R_TLSBADPEERCERT || + query->canceled) + { + debug("in cancel handler"); + isc_sockaddr_format(&query->sockaddr, sockstr, sizeof(sockstr)); + if (eresult == ISC_R_TLSBADPEERCERT) { + dighost_warning( + "TLS peer certificate verification for " + "%s failed: %s", + sockstr, + isc_nm_verify_tls_peer_result_string(handle)); + } else if (query->lookup->rdtype == dns_rdatatype_ixfr || + query->lookup->rdtype == dns_rdatatype_axfr) + { + puts("; Transfer failed."); + } + + if (!query->canceled) { + cancel_lookup(l); + } + + query_detach(&query); + lookup_detach(&l); + clear_current_lookup(); + UNLOCK_LOOKUP; + return; + } + + if (eresult != ISC_R_SUCCESS) { + debug("unsuccessful connection: %s", + isc_result_totext(eresult)); + isc_sockaddr_format(&query->sockaddr, sockstr, sizeof(sockstr)); + dighost_warning("Connection to %s(%s) for %s failed: %s.", + sockstr, query->servname, l->textname, + isc_result_totext(eresult)); + + /* + * NSSEARCH mode: if the current query failed to start properly, + * then send_done() will not be called, and we want to make sure + * that the next query gets a chance to start in order to not + * break the chain. + */ + if (l->ns_search_only && !l->trace_root) { + nssearch_next(l, query); + + check_if_done(); + UNLOCK_LOOKUP; + return; + } + + /* XXX Clean up exitcodes */ + if (exitcode < 9) { + exitcode = 9; + } + + if (l->retries > 1) { + l->retries--; + debug("making new TCP request, %d tries left", + l->retries); + requeue_lookup(l, true); + next = NULL; + } else if ((l->current_query != NULL) && + (ISC_LINK_LINKED(l->current_query, link))) + { + next = ISC_LIST_NEXT(l->current_query, link); + } else { + next = NULL; + } + + query_detach(&query); + if (next == NULL) { + cancel_lookup(l); + } + lookup_detach(&l); + + if (next != NULL) { + start_tcp(next); + check_if_done(); + } else { + clear_current_lookup(); + } + + UNLOCK_LOOKUP; + return; + } + + exitcode = 0; + + query->handle = handle; + if (keep_open) { + keepaddr = query->sockaddr; + if (keep != NULL) { + isc_nmhandle_detach(&keep); + } + + isc_nmhandle_attach(handle, &keep); + } + + launch_next_query(query); + query_detach(&query); + lookup_detach(&l); + UNLOCK_LOOKUP; +} + +/*% + * Check if the ongoing XFR needs more data before it's complete, using + * the semantics of IXFR and AXFR protocols. Much of the complexity of + * this routine comes from determining when an IXFR is complete. + * false means more data is on the way, and the recv has been issued. + */ +static bool +check_for_more_data(dig_lookup_t *lookup, dig_query_t *query, + dns_message_t *msg, isc_sockaddr_t *peer, int len) { + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + uint32_t ixfr_serial = lookup->ixfr_serial, serial; + isc_result_t result; + bool ixfr = lookup->rdtype == dns_rdatatype_ixfr; + bool axfr = lookup->rdtype == dns_rdatatype_axfr; + + if (ixfr) { + axfr = query->ixfr_axfr; + } + + debug("check_for_more_data()"); + + /* + * By the time we're in this routine, we know we're doing + * either an AXFR or IXFR. If there's no second_rr_type, + * then we don't yet know which kind of answer we got back + * from the server. Here, we're going to walk through the + * rr's in the message, acting as necessary whenever we hit + * an SOA rr. + */ + + query->msg_count++; + query->byte_count += len; + result = dns_message_firstname(msg, DNS_SECTION_ANSWER); + if (result != ISC_R_SUCCESS) { + puts("; Transfer failed."); + return (true); + } + do { + dns_name_t *name; + name = NULL; + dns_message_currentname(msg, DNS_SECTION_ANSWER, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + continue; + } + do { + query->rr_count++; + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + /* + * If this is the first rr, make sure + * it's an SOA + */ + if ((!query->first_soa_rcvd) && + (rdata.type != dns_rdatatype_soa)) + { + puts("; Transfer failed. " + "Didn't start with SOA answer."); + return (true); + } + if ((!query->second_rr_rcvd) && + (rdata.type != dns_rdatatype_soa)) + { + query->second_rr_rcvd = true; + query->second_rr_serial = 0; + debug("got the second rr as nonsoa"); + axfr = query->ixfr_axfr = true; + goto next_rdata; + } + + /* + * If the record is anything except an SOA + * now, just continue on... + */ + if (rdata.type != dns_rdatatype_soa) { + goto next_rdata; + } + + /* Now we have an SOA. Work with it. */ + debug("got an SOA"); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + check_result(result, "dns_rdata_tostruct"); + serial = soa.serial; + dns_rdata_freestruct(&soa); + if (!query->first_soa_rcvd) { + query->first_soa_rcvd = true; + query->first_rr_serial = serial; + debug("this is the first serial %u", + serial); + if (ixfr && + isc_serial_ge(ixfr_serial, serial)) + { + debug("got up to date " + "response"); + goto doexit; + } + goto next_rdata; + } + if (axfr) { + debug("doing axfr, got second SOA"); + goto doexit; + } + if (!query->second_rr_rcvd) { + if (query->first_rr_serial == serial) { + debug("doing ixfr, got " + "empty zone"); + goto doexit; + } + debug("this is the second serial %u", + serial); + query->second_rr_rcvd = true; + query->second_rr_serial = serial; + goto next_rdata; + } + /* + * If we get to this point, we're doing an + * IXFR and have to start really looking + * at serial numbers. + */ + if (query->first_rr_serial == serial) { + debug("got a match for ixfr"); + if (!query->first_repeat_rcvd) { + query->first_repeat_rcvd = true; + goto next_rdata; + } + debug("done with ixfr"); + goto doexit; + } + debug("meaningless soa %u", serial); + next_rdata: + result = dns_rdataset_next(rdataset); + } while (result == ISC_R_SUCCESS); + } + result = dns_message_nextname(msg, DNS_SECTION_ANSWER); + } while (result == ISC_R_SUCCESS); + isc_nmhandle_detach(&query->readhandle); + launch_next_query(query); + query_detach(&query); + return (false); +doexit: + dighost_received(len, peer, query); + return (true); +} + +static void +process_cookie(dig_lookup_t *l, dns_message_t *msg, isc_buffer_t *optbuf, + size_t optlen) { + char bb[256]; + isc_buffer_t hexbuf; + size_t len; + const unsigned char *sent; + bool copy = true; + isc_result_t result; + + if (l->cookie != NULL) { + isc_buffer_init(&hexbuf, bb, sizeof(bb)); + result = isc_hex_decodestring(l->cookie, &hexbuf); + check_result(result, "isc_hex_decodestring"); + sent = isc_buffer_base(&hexbuf); + len = isc_buffer_usedlength(&hexbuf); + } else { + sent = cookie; + len = sizeof(cookie); + } + + INSIST(msg->cc_ok == 0 && msg->cc_bad == 0); + if (len >= 8 && optlen >= 8U) { + if (isc_safe_memequal(isc_buffer_current(optbuf), sent, 8)) { + msg->cc_ok = 1; + } else { + dighost_warning("Warning: Client COOKIE mismatch"); + msg->cc_bad = 1; + copy = false; + } + } else { + dighost_warning("Warning: COOKIE bad token (too short)"); + msg->cc_bad = 1; + copy = false; + } + if (copy) { + isc_region_t r; + + r.base = isc_buffer_current(optbuf); + r.length = (unsigned int)optlen; + isc_buffer_init(&hexbuf, servercookie, sizeof(servercookie)); + result = isc_hex_totext(&r, 2, "", &hexbuf); + check_result(result, "isc_hex_totext"); + if (isc_buffer_availablelength(&hexbuf) > 0) { + isc_buffer_putuint8(&hexbuf, 0); + l->cookie = servercookie; + } + } + isc_buffer_forward(optbuf, (unsigned int)optlen); +} + +static void +process_opt(dig_lookup_t *l, dns_message_t *msg) { + dns_rdata_t rdata; + isc_result_t result; + isc_buffer_t optbuf; + uint16_t optcode, optlen; + dns_rdataset_t *opt = msg->opt; + bool seen_cookie = false; + + result = dns_rdataset_first(opt); + if (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(opt, &rdata); + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) >= 4) { + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + switch (optcode) { + case DNS_OPT_COOKIE: + /* + * Only process the first cookie option. + */ + if (seen_cookie) { + isc_buffer_forward(&optbuf, optlen); + break; + } + process_cookie(l, msg, &optbuf, optlen); + seen_cookie = true; + break; + default: + isc_buffer_forward(&optbuf, optlen); + break; + } + } + } +} + +static int +ednsvers(dns_rdataset_t *opt) { + return ((opt->ttl >> 16) & 0xff); +} + +/*% + * Event handler for recv complete. Perform whatever actions are necessary, + * based on the specifics of the user's request. + */ +static void +recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg) { + dig_query_t *query = (dig_query_t *)arg; + isc_buffer_t b; + dns_message_t *msg = NULL; + isc_result_t result; + dig_lookup_t *n = NULL; + dig_lookup_t *l = NULL; + bool docancel = false; + bool donext = false; + bool match = true; + bool done_process_opt = false; + unsigned int parseflags; + dns_messageid_t id; + unsigned int msgflags; + int newedns; + isc_sockaddr_t peer; + + REQUIRE(DIG_VALID_QUERY(query)); + REQUIRE(query->readhandle != NULL); + INSIST(!free_now); + + debug("recv_done(%p, %s, %p, %p)", handle, isc_result_totext(eresult), + region, arg); + + LOCK_LOOKUP; + + isc_refcount_decrement0(&recvcount); + debug("recvcount=%" PRIuFAST32, isc_refcount_current(&recvcount)); + + lookup_attach(query->lookup, &l); + + if (eresult == ISC_R_CANCELED || query->canceled) { + debug("recv_done: cancel"); + isc_nmhandle_detach(&query->readhandle); + if (!query->canceled) { + cancel_lookup(l); + } + query_detach(&query); + lookup_detach(&l); + clear_current_lookup(); + UNLOCK_LOOKUP; + return; + } + + if (query->lookup->use_usec) { + TIME_NOW_HIRES(&query->time_recv); + } else { + TIME_NOW(&query->time_recv); + } + + if ((!l->pending && !l->ns_search_only) || atomic_load(&cancel_now)) { + debug("no longer pending. Got %s", isc_result_totext(eresult)); + + goto next_lookup; + } + + /* + * NSSEARCH mode is special, because the queries in the followup lookup + * are independent and they are being started in parallel, so if one of + * them fails there is no need to start the next query in the lookup, + * and this failure can be treated as a soft error (with a warning + * message), because there are usually more than one NS servers in the + * lookup's queries list. However, if there was not a single successful + * query in the followup lookup, then print an error message and exit + * with a non-zero exit code. + */ + if (l->ns_search_only && !l->trace_root) { + if (eresult == ISC_R_SUCCESS) { + l->ns_search_success = true; + } else { + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->sockaddr, sockstr, + sizeof(sockstr)); + + dighost_warning("communications error to %s: %s", + sockstr, isc_result_totext(eresult)); + + /* + * If this is not the last query, then we detach the + * query, but keep the lookup running. + */ + if (!check_if_queries_done(l, query)) { + goto detach_query; + } + + /* + * This is the last query, and if there was not a + * single successful query in the whole lookup, then + * treat the situation as an error. + */ + if (!l->ns_search_success) { + dighost_error( + "NS servers could not be reached"); + if (exitcode < 9) { + exitcode = 9; + } + } + + goto cancel_lookup; + } + } + + if (eresult != ISC_R_SUCCESS) { + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(&query->sockaddr, sockstr, sizeof(sockstr)); + dighost_warning("communications error to %s: %s", sockstr, + isc_result_totext(eresult)); + + if (l->retries > 1 && !l->tcp_mode) { + dig_query_t *newq = NULL; + + /* + * For UDP, insert a copy of the current query just + * after itself in the list, and start it to retry the + * request. + */ + newq = new_query(l, query->servname, query->userarg); + ISC_LIST_INSERTAFTER(l->q, query, newq, link); + if (l->current_query == query) { + query_detach(&l->current_query); + } + if (l->current_query == NULL) { + l->retries--; + debug("making new UDP request, %d tries left", + l->retries); + start_udp(newq); + } + if (check_if_queries_done(l, query)) { + goto cancel_lookup; + } + + goto detach_query; + } else if (l->retries > 1 && l->tcp_mode) { + /* + * For TCP, we have to requeue the whole lookup, see + * the comments above the start_tcp() function. + */ + l->retries--; + debug("making new TCP request, %d tries left", + l->retries); + requeue_lookup(l, true); + + if (keep != NULL) { + isc_nmhandle_detach(&keep); + } + + goto cancel_lookup; + } else { + dig_query_t *next = ISC_LIST_NEXT(query, link); + + /* + * No retries left, go to the next query, if there is + * one. + */ + if (next != NULL) { + if (l->current_query == query) { + query_detach(&l->current_query); + } + if (l->current_query == NULL) { + debug("starting next query %p", next); + if (l->tcp_mode) { + start_tcp(next); + } else { + start_udp(next); + } + } + if (check_if_queries_done(l, query)) { + goto cancel_lookup; + } + + goto detach_query; + } + + /* + * Otherwise, print the cmdline and an error message, + * and cancel the lookup. + */ + printf("%s", l->cmdline); + dighost_error("no servers could be reached\n"); + + if (exitcode < 9) { + exitcode = 9; + } + + if (keep != NULL) { + isc_nmhandle_detach(&keep); + } + + goto cancel_lookup; + } + } + + isc_buffer_init(&b, region->base, region->length); + isc_buffer_add(&b, region->length); + + peer = isc_nmhandle_peeraddr(handle); + + result = dns_message_peekheader(&b, &id, &msgflags); + if (result != ISC_R_SUCCESS || l->sendmsg->id != id) { + match = false; + if (l->tcp_mode) { + bool fail = true; + if (result == ISC_R_SUCCESS) { + if ((!query->first_soa_rcvd || query->warn_id)) + { + dighost_warning("%s: ID mismatch: " + "expected ID %u, got " + "%u", + query->first_soa_rcvd + ? "WARNING" + : "ERROR", + l->sendmsg->id, id); + } + if (query->first_soa_rcvd) { + fail = false; + } + query->warn_id = false; + } else { + dighost_warning("ERROR: short (< header size) " + "message"); + } + if (fail) { + goto cancel_lookup; + } + match = true; + } else if (result == ISC_R_SUCCESS) { + dighost_warning("Warning: ID mismatch: expected ID %u," + " got %u", + l->sendmsg->id, id); + } else { + dighost_warning("Warning: short (< header size) " + "message received"); + } + } + + if (result == ISC_R_SUCCESS && (msgflags & DNS_MESSAGEFLAG_QR) == 0) { + dighost_warning("Warning: query response not set"); + } + + if (!match) { + /* + * We are still attached to query and the query->readhandle is + * also attached + */ + isc_refcount_increment0(&recvcount); + debug("recvcount=%" PRIuFAST32, + isc_refcount_current(&recvcount)); + isc_nm_read(handle, recv_done, query); + goto keep_query; + } + + dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &msg); + + if (tsigkey != NULL) { + if (l->querysig == NULL) { + debug("getting initial querysig"); + result = dns_message_getquerytsig(l->sendmsg, mctx, + &l->querysig); + check_result(result, "dns_message_getquerytsig"); + } + result = dns_message_setquerytsig(msg, l->querysig); + check_result(result, "dns_message_setquerytsig"); + result = dns_message_settsigkey(msg, tsigkey); + check_result(result, "dns_message_settsigkey"); + msg->tsigctx = l->tsigctx; + l->tsigctx = NULL; + if (l->msgcounter != 0) { + msg->tcp_continuation = 1; + } + l->msgcounter++; + } + + debug("before parse starts"); + parseflags = l->dns64prefix ? 0 : DNS_MESSAGEPARSE_PRESERVEORDER; + if (l->besteffort) { + parseflags |= DNS_MESSAGEPARSE_BESTEFFORT; + parseflags |= DNS_MESSAGEPARSE_IGNORETRUNCATION; + } + + result = dns_message_parse(msg, &b, parseflags); + if (result == DNS_R_RECOVERABLE) { + dighost_warning("Warning: Message parser reports malformed " + "message packet."); + } else if (result != ISC_R_SUCCESS) { + if (!yaml) { + printf(";; Got bad packet: %s\n", + isc_result_totext(result)); + hex_dump(&b); + } + goto cancel_lookup; + } + + if (msg->opcode != l->opcode) { + char expect[20] = { 0 }, got[20] = { 0 }; + + isc_buffer_init(&b, &expect, sizeof(expect)); + result = dns_opcode_totext(l->opcode, &b); + check_result(result, "dns_opcode_totext"); + + isc_buffer_init(&b, &got, sizeof(got)); + result = dns_opcode_totext(msg->opcode, &b); + check_result(result, "dns_opcode_totext"); + + dighost_warning("Warning: Opcode mismatch: expected %s, got %s", + expect, got); + + isc_refcount_increment0(&recvcount); + debug("recvcount=%" PRIuFAST32, + isc_refcount_current(&recvcount)); + isc_nm_read(handle, recv_done, query); + goto keep_query; + } + + if (msg->counts[DNS_SECTION_QUESTION] != 0) { + match = true; + for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION); + result == ISC_R_SUCCESS && match; + result = dns_message_nextname(msg, DNS_SECTION_QUESTION)) + { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + + dns_message_currentname(msg, DNS_SECTION_QUESTION, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (l->rdtype != rdataset->type || + l->rdclass != rdataset->rdclass || + !dns_name_equal(l->name, name)) + { + char namestr[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + dns_name_format(name, namestr, + sizeof(namestr)); + dns_rdatatype_format(rdataset->type, + typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdataset->rdclass, + classbuf, + sizeof(classbuf)); + dighost_warning(";; Question section " + "mismatch: got " + "%s/%s/%s", + namestr, typebuf, + classbuf); + match = false; + } + } + } + + if (!match) { + if (l->tcp_mode) { + goto cancel_lookup; + } + + /* + * We are still attached to query and the + * query->readhandle is also attached + */ + isc_refcount_increment0(&recvcount); + debug("recvcount=%" PRIuFAST32, + isc_refcount_current(&recvcount)); + isc_nm_read(handle, recv_done, query); + goto keep_query; + } + } + + if (msg->rcode == dns_rcode_badvers && msg->opt != NULL && + (newedns = ednsvers(msg->opt)) < l->edns && l->ednsneg) + { + /* + * Add minimum EDNS version required checks here if needed. + */ + dighost_comments(l, "BADVERS, retrying with EDNS version %u.", + (unsigned int)newedns); + l->edns = newedns; + n = requeue_lookup(l, true); + if (l->trace && l->trace_root) { + n->rdtype = l->qrdtype; + } + goto cancel_lookup; + } + + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0 && !l->ignore && + !l->tcp_mode) + { + if (l->cookie == NULL && l->sendcookie && msg->opt != NULL) { + process_opt(l, msg); + } + dighost_comments(l, "Truncated, retrying in TCP mode."); + n = requeue_lookup(l, true); + n->tcp_mode = true; + if (l->trace && l->trace_root) { + n->rdtype = l->qrdtype; + } + goto cancel_lookup; + } + + if (msg->rcode == dns_rcode_badcookie && !l->tcp_mode && + l->sendcookie && l->badcookie) + { + process_opt(l, msg); + if (msg->cc_ok) { + if (l->showbadcookie) { + dighost_printmessage(query, &b, msg, true); + dighost_received(isc_buffer_usedlength(&b), + &peer, query); + } + dighost_comments(l, "BADCOOKIE, retrying%s.", + l->seenbadcookie ? " in TCP mode" + : ""); + n = requeue_lookup(l, true); + if (l->seenbadcookie) { + n->tcp_mode = true; + } + n->seenbadcookie = true; + if (l->trace && l->trace_root) { + n->rdtype = l->qrdtype; + } + goto cancel_lookup; + } + done_process_opt = true; + } + + if ((msg->rcode == dns_rcode_servfail && !l->servfail_stops) || + (check_ra && (msg->flags & DNS_MESSAGEFLAG_RA) == 0 && l->recurse)) + { + dig_query_t *next = ISC_LIST_NEXT(query, link); + if (l->current_query == query) { + query_detach(&l->current_query); + } + if (next != NULL) { + debug("sending query %p", next); + if (l->tcp_mode) { + start_tcp(next); + } else { + start_udp(next); + } + dighost_comments(l, + "Got %s from %s, trying next " + "server", + msg->rcode == dns_rcode_servfail + ? "SERVFAIL reply" + : "recursion not available", + query->servname); + if (check_if_queries_done(l, query)) { + goto cancel_lookup; + } + + goto detach_query; + } + } + + if (tsigkey != NULL) { + result = dns_tsig_verify(&b, msg, NULL, NULL); + if (result != ISC_R_SUCCESS) { + dighost_warning("Couldn't verify signature: %s", + isc_result_totext(result)); + validated = false; + } + l->tsigctx = msg->tsigctx; + msg->tsigctx = NULL; + if (l->querysig != NULL) { + debug("freeing querysig buffer %p", l->querysig); + isc_buffer_free(&l->querysig); + } + result = dns_message_getquerytsig(msg, mctx, &l->querysig); + check_result(result, "dns_message_getquerytsig"); + } + + extrabytes = isc_buffer_remaininglength(&b); + + debug("after parse"); + if (l->doing_xfr && l->xfr_q == NULL) { + l->xfr_q = query; + /* + * Once we are in the XFR message, increase + * the timeout to much longer, so brief network + * outages won't cause the XFR to abort + */ + if (timeout != INT_MAX && query->timer != NULL) { + unsigned int local_timeout; + + if (timeout == 0) { + if (l->tcp_mode) { + local_timeout = TCP_TIMEOUT * 4000; + } else { + local_timeout = UDP_TIMEOUT * 4000; + } + } else { + if (timeout < (INT_MAX / 4)) { + local_timeout = timeout * 4000; + } else { + local_timeout = INT_MAX; + } + } + + debug("have local timeout of %d", local_timeout); + isc_nmhandle_settimeout(query->handle, local_timeout); + } + } + + if (!done_process_opt) { + if (l->cookie != NULL) { + if (msg->opt == NULL) { + dighost_warning("expected opt record in " + "response"); + } else { + process_opt(l, msg); + } + } else if (l->sendcookie && msg->opt != NULL) { + process_opt(l, msg); + } + } + + if (!l->doing_xfr || l->xfr_q == query) { + if (msg->rcode == dns_rcode_nxdomain && + (l->origin != NULL || l->need_search)) + { + if (!next_origin(l) || showsearch) { + dighost_printmessage(query, &b, msg, true); + dighost_received(isc_buffer_usedlength(&b), + &peer, query); + } + } else if (!l->trace && !l->ns_search_only) { + dighost_printmessage(query, &b, msg, true); + } else if (l->trace) { + int nl = 0; + int count = msg->counts[DNS_SECTION_ANSWER]; + + debug("in TRACE code"); + if (!l->ns_search_only) { + dighost_printmessage(query, &b, msg, true); + } + + l->rdtype = l->qrdtype; + if (l->trace_root || (l->ns_search_only && count > 0)) { + if (!l->trace_root) { + l->rdtype = dns_rdatatype_soa; + } + nl = followup_lookup(msg, query, + DNS_SECTION_ANSWER); + l->trace_root = false; + } else if (count == 0) { + nl = followup_lookup(msg, query, + DNS_SECTION_AUTHORITY); + } + if (nl == 0) { + docancel = true; + } + } else { + debug("in NSSEARCH code"); + + if (l->trace_root) { + /* + * This is the initial NS query. + */ + int nl; + + l->rdtype = dns_rdatatype_soa; + nl = followup_lookup(msg, query, + DNS_SECTION_ANSWER); + if (nl == 0) { + docancel = true; + } + l->trace_root = false; + usesearch = false; + } else { + /* + * This is a query in the followup lookup + */ + dighost_printmessage(query, &b, msg, true); + + docancel = check_if_queries_done(l, query); + } + } + } + + if (l->pending) { + debug("still pending."); + } + + if (l->doing_xfr) { + if (query != l->xfr_q) { + goto detach_query; + } + if (!docancel) { + docancel = check_for_more_data(l, query, msg, &peer, + region->length); + } + if (docancel) { + goto cancel_lookup; + } + /* + * check_for_more_data() will detach from query->readhandle + * and query on its own, as it needs to reuse the query and + * reattach to the readhandle in launch_next_query(). + */ + goto keep_query; + } else { + if (msg->rcode == dns_rcode_noerror || l->origin == NULL) { + dighost_received(isc_buffer_usedlength(&b), &peer, + query); + } + + if (!l->ns_search_only) { + l->pending = false; + } + if (!l->ns_search_only || l->trace_root || docancel) { + goto cancel_lookup; + } + goto next_lookup; + } +cancel_lookup: + docancel = true; +next_lookup: + donext = true; +detach_query: + isc_nmhandle_detach(&query->readhandle); + query_detach(&query); + if (docancel) { + cancel_lookup(l); + } +keep_query: + if (msg != NULL) { + dns_message_detach(&msg); + } + lookup_detach(&l); + if (donext) { + clear_current_lookup(); + } + UNLOCK_LOOKUP; +} + +/*% + * Turn a name into an address, using system-supplied routines. This is + * used in looking up server names, etc... and needs to use system-supplied + * routines, since they may be using a non-DNS system for these lookups. + */ +isc_result_t +get_address(char *host, in_port_t myport, isc_sockaddr_t *sockaddr) { + int count; + isc_result_t result; + bool is_running; + + is_running = isc_app_isrunning(); + if (is_running) { + isc_app_block(); + } + result = bind9_getaddresses(host, myport, sockaddr, 1, &count); + if (is_running) { + isc_app_unblock(); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(count == 1); + + return (ISC_R_SUCCESS); +} + +int +getaddresses(dig_lookup_t *lookup, const char *host, isc_result_t *resultp) { + isc_result_t result; + isc_sockaddr_t sockaddrs[DIG_MAX_ADDRESSES]; + isc_netaddr_t netaddr; + int count, i; + dig_server_t *srv; + char tmp[ISC_NETADDR_FORMATSIZE]; + + result = bind9_getaddresses(host, 0, sockaddrs, DIG_MAX_ADDRESSES, + &count); + if (resultp != NULL) { + *resultp = result; + } + if (result != ISC_R_SUCCESS) { + if (resultp == NULL) { + fatal("couldn't get address for '%s': %s", host, + isc_result_totext(result)); + } + return (0); + } + + for (i = 0; i < count; i++) { + isc_netaddr_fromsockaddr(&netaddr, &sockaddrs[i]); + isc_netaddr_format(&netaddr, tmp, sizeof(tmp)); + srv = make_server(tmp, host); + ISC_LIST_APPEND(lookup->my_server_list, srv, link); + } + + return (count); +} + +/*% + * Initiate either a TCP or UDP lookup + */ +void +do_lookup(dig_lookup_t *lookup) { + dig_query_t *query; + + REQUIRE(lookup != NULL); + + debug("do_lookup()"); + lookup->pending = true; + query = ISC_LIST_HEAD(lookup->q); + if (query != NULL) { + REQUIRE(DIG_VALID_QUERY(query)); + if (lookup->tcp_mode) { + start_tcp(query); + } else { + start_udp(query); + } + } +} + +/*% + * Start everything in action upon task startup. + */ +void +onrun_callback(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + isc_event_free(&event); + LOCK_LOOKUP; + start_lookup(); + UNLOCK_LOOKUP; +} + +/*% + * Make everything on the lookup queue go away. Mainly used by the + * SIGINT handler. + */ +void +cancel_all(void) { + dig_lookup_t *l, *n; + dig_query_t *q, *nq; + + debug("cancel_all()"); + + LOCK_LOOKUP; + if (free_now) { + UNLOCK_LOOKUP; + return; + } + atomic_store(&cancel_now, true); + while (current_lookup != NULL) { + for (q = ISC_LIST_HEAD(current_lookup->q); q != NULL; q = nq) { + nq = ISC_LIST_NEXT(q, link); + debug("canceling pending query %p, belonging to %p", q, + current_lookup); + q->canceled = true; + if (q->readhandle != NULL && + !isc_nm_is_http_handle(q->readhandle)) + { + isc_nm_cancelread(q->readhandle); + } + query_detach(&q); + } + + /* + * current_lookup could have been detached via query_detach(). + */ + if (current_lookup != NULL) { + lookup_detach(¤t_lookup); + } + } + l = ISC_LIST_HEAD(lookup_list); + while (l != NULL) { + n = ISC_LIST_NEXT(l, link); + ISC_LIST_DEQUEUE(lookup_list, l, link); + lookup_detach(&l); + l = n; + } + UNLOCK_LOOKUP; +} + +void +cleanup_openssl_refs(void) { + if (tsigkey != NULL) { + debug("freeing TSIG key %p", tsigkey); + dns_tsigkey_detach(&tsigkey); + } + + if (sig0key != NULL) { + debug("freeing SIG(0) key %p", sig0key); + dst_key_free(&sig0key); + } + + if (is_dst_up) { + debug("destroy DST lib"); + dst_lib_destroy(); + is_dst_up = false; + } +} + +/*% + * Destroy all of the libs we are using, and get everything ready for a + * clean shutdown. + */ +void +destroy_libs(void) { + if (keep != NULL) { + isc_nmhandle_detach(&keep); + } + debug("destroy_libs()"); + if (global_task != NULL) { + debug("freeing task"); + isc_task_detach(&global_task); + } + + isc_managers_destroy(&netmgr, &taskmgr, NULL); + + LOCK_LOOKUP; + isc_refcount_destroy(&recvcount); + isc_refcount_destroy(&sendcount); + + INSIST(ISC_LIST_HEAD(lookup_list) == NULL); + INSIST(current_lookup == NULL); + INSIST(!free_now); + + free_now = true; + + flush_server_list(); + + clear_searchlist(); + + cleanup_openssl_refs(); + + if (namebuf != NULL) { + debug("freeing key %p", tsigkey); + isc_buffer_free(&namebuf); + } + + UNLOCK_LOOKUP; + isc_mutex_destroy(&lookup_lock); + + debug("Removing log context"); + isc_log_destroy(&lctx); + + debug("Destroy memory"); + if (memdebugging != 0) { + isc_mem_stats(mctx, stderr); + } + if (mctx != NULL) { + isc_mem_destroy(&mctx); + } +} + +#ifdef HAVE_LIBIDN2 +static isc_result_t +idn_output_filter(isc_buffer_t *buffer, unsigned int used_org) { + char src[MXNAME], *dst = NULL; + size_t srclen, dstlen; + isc_result_t result = ISC_R_SUCCESS; + + /* + * Copy name from 'buffer' to 'src' and terminate it with NULL. + */ + srclen = isc_buffer_usedlength(buffer) - used_org; + if (srclen >= sizeof(src)) { + warn("Input name too long to perform IDN conversion"); + goto cleanup; + } + memmove(src, (char *)isc_buffer_base(buffer) + used_org, srclen); + src[srclen] = '\0'; + + systemlocale(LC_ALL); + + /* + * Convert 'src' to the current locale's character encoding. + */ + idn_ace_to_locale(src, &dst); + + resetlocale(LC_ALL); + + /* + * Check whether the converted name will fit back into 'buffer'. + */ + dstlen = strlen(dst); + if (isc_buffer_length(buffer) < used_org + dstlen) { + result = ISC_R_NOSPACE; + goto cleanup; + } + + /* + * Put the converted name back into 'buffer'. + */ + isc_buffer_subtract(buffer, srclen); + memmove(isc_buffer_used(buffer), dst, dstlen); + isc_buffer_add(buffer, dstlen); + + /* + * Clean up. + */ +cleanup: + if (dst != NULL) { + idn2_free(dst); + } + + return (result); +} + +/*% + * Convert 'src', which is a string using the current locale's character + * encoding, into an ACE string suitable for use in the DNS, storing the + * conversion result in 'dst', which is 'dstlen' bytes large. + * + * 'dst' MUST be large enough to hold any valid domain name. + */ +static void +idn_locale_to_ace(const char *src, char *dst, size_t dstlen) { + const char *final_src; + char *ascii_src; + int res; + + systemlocale(LC_ALL); + + /* + * We trust libidn2 to return an error if 'src' is too large to be a + * valid domain name. + */ + res = idn2_to_ascii_lz(src, &ascii_src, IDN2_NONTRANSITIONAL); + if (res == IDN2_DISALLOWED) { + res = idn2_to_ascii_lz(src, &ascii_src, IDN2_TRANSITIONAL); + } + if (res != IDN2_OK) { + fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnin", + src, idn2_strerror(res)); + } + + /* + * idn2_to_ascii_lz() normalizes all strings to lower case, but we + * generally don't want to lowercase all input strings; make sure to + * return the original case if the two strings differ only in case. + */ + final_src = (strcasecmp(src, ascii_src) == 0 ? src : ascii_src); + + (void)strlcpy(dst, final_src, dstlen); + + idn2_free(ascii_src); + + resetlocale(LC_ALL); +} + +/*% + * Convert 'src', which is an ACE string suitable for use in the DNS, into a + * string using the current locale's character encoding, storing the conversion + * result in 'dst'. + * + * The caller MUST subsequently release 'dst' using idn2_free(). + */ +static void +idn_ace_to_locale(const char *src, char **dst) { + char *local_src, *utf8_src; + int res; + + systemlocale(LC_ALL); + + /* + * We need to: + * + * 1) check whether 'src' is a valid IDNA2008 name, + * 2) if it is, output it in the current locale's character encoding. + * + * Unlike idn2_to_ascii_*(), idn2_to_unicode_*() functions are unable + * to perform IDNA2008 validity checks. Thus, we need to decode any + * Punycode in 'src', check if the resulting name is a valid IDNA2008 + * name, and only once we ensure it is, output that name in the current + * locale's character encoding. + * + * We could just use idn2_to_unicode_8zlz() + idn2_to_ascii_lz(), but + * then we would not be able to universally tell invalid names and + * character encoding errors apart (if the current locale uses ASCII + * for character encoding, the former function would fail even for a + * valid IDNA2008 name, as long as it contained any non-ASCII + * character). Thus, we need to take a longer route. + * + * First, convert 'src' to UTF-8, ignoring the current locale. + */ + res = idn2_to_unicode_8z8z(src, &utf8_src, 0); + if (res != IDN2_OK) { + fatal("Bad ACE string '%s' (%s), use +noidnout", src, + idn2_strerror(res)); + } + + /* + * Then, check whether decoded 'src' is a valid IDNA2008 name + * and if disallowed character is found, fallback to IDNA2003. + */ + res = idn2_to_ascii_8z(utf8_src, NULL, IDN2_NONTRANSITIONAL); + if (res == IDN2_DISALLOWED) { + res = idn2_to_ascii_8z(utf8_src, NULL, IDN2_TRANSITIONAL); + } + if (res != IDN2_OK) { + fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnout", + src, idn2_strerror(res)); + } + + /* + * Finally, try converting the decoded 'src' into the current locale's + * character encoding. + */ + res = idn2_to_unicode_8zlz(utf8_src, &local_src, 0); + if (res != IDN2_OK) { + static bool warned = false; + + res = idn2_to_ascii_8z(utf8_src, &local_src, 0); + if (res != IDN2_OK) { + fatal("Cannot represent '%s' " + "in the current locale nor ascii (%s), " + "use +noidnout or a different locale", + src, idn2_strerror(res)); + } else if (!warned) { + fprintf(stderr, + ";; Warning: cannot represent '%s' " + "in the current locale", + local_src); + warned = true; + } + } + + /* + * Free the interim conversion result. + */ + idn2_free(utf8_src); + + *dst = local_src; + + resetlocale(LC_ALL); +} +#endif /* HAVE_LIBIDN2 */ + +void +dig_idnsetup(dig_lookup_t *lookup, bool active) { +#ifdef HAVE_LIBIDN2 + isc_result_t result; + result = dns_name_settotextfilter( + (active && lookup->idnout) ? idn_output_filter : NULL); + check_result(result, "dns_name_settotextfilter"); +#else + UNUSED(lookup); + UNUSED(active); + return; +#endif /* HAVE_LIBIDN2 */ +} + +bool +dig_lookup_is_tls(const dig_lookup_t *lookup) { + if (lookup->tls_mode || (lookup->tls_ca_set && !lookup->https_mode)) { + return (true); + } + + return (false); +} diff --git a/bin/dig/dighost.h b/bin/dig/dighost.h new file mode 100644 index 0000000..227c315 --- /dev/null +++ b/bin/dig/dighost.h @@ -0,0 +1,466 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/attributes.h> +#include <isc/buffer.h> +#include <isc/formatcheck.h> +#include <isc/lang.h> +#include <isc/list.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/sockaddr.h> +#include <isc/time.h> + +#include <dns/rdatalist.h> + +#include <dst/dst.h> + +#ifdef __APPLE__ +#include <TargetConditionals.h> +#endif /* ifdef __APPLE__ */ + +#define MXSERV 20 +#define MXNAME (DNS_NAME_MAXTEXT + 1) +#define MXRD 32 +/*% Buffer Size */ +#define BUFSIZE 512 +#define COMMSIZE 0xffff +#ifndef RESOLV_CONF +/*% location of resolve.conf */ +#define RESOLV_CONF "/etc/resolv.conf" +#endif /* ifndef RESOLV_CONF */ +/*% output buffer */ +#define OUTPUTBUF 32767 +/*% Max RR Limit */ +#define MAXRRLIMIT 0xffffffff +#define MAXTIMEOUT 0xffff +/*% Max number of tries */ +#define MAXTRIES 0xffffffff +/*% Max number of dots */ +#define MAXNDOTS 0xffff +/*% Max number of ports */ +#define MAXPORT 0xffff +/*% Max serial number */ +#define MAXSERIAL 0xffffffff +/*% Max query ID */ +#define MAXQID 0xffff + +/*% Default TCP Timeout */ +#define TCP_TIMEOUT 10 +/*% Default UDP Timeout */ +#define UDP_TIMEOUT 5 + +#define SERVER_TIMEOUT 1 + +#define LOOKUP_LIMIT 64 + +#define DEFAULT_EDNS_VERSION 0 +#define DEFAULT_EDNS_BUFSIZE 1232 + +#define DEFAULT_HTTPS_QUERY "?dns=" + +/*% + * Lookup_limit is just a limiter, keeping too many lookups from being + * created. It's job is mainly to prevent the program from running away + * in a tight loop of constant lookups. It's value is arbitrary. + */ + +ISC_LANG_BEGINDECLS + +typedef struct dig_lookup dig_lookup_t; +typedef struct dig_query dig_query_t; +typedef struct dig_server dig_server_t; +typedef ISC_LIST(dig_server_t) dig_serverlist_t; +typedef struct dig_searchlist dig_searchlist_t; + +#define DIG_LOOKUP_MAGIC ISC_MAGIC('D', 'i', 'g', 'l') + +#define DIG_VALID_LOOKUP(x) ISC_MAGIC_VALID((x), DIG_LOOKUP_MAGIC) + +#define DIG_QUERY_MAGIC ISC_MAGIC('D', 'i', 'g', 'q') + +#define DIG_VALID_QUERY(x) ISC_MAGIC_VALID((x), DIG_QUERY_MAGIC) + +/*% The dig_lookup structure */ +struct dig_lookup { + unsigned int magic; + isc_refcount_t references; + bool aaonly, adflag, badcookie, besteffort, cdflag, cleared, comments, + dns64prefix, dnssec, doing_xfr, done_as_is, ednsneg, expandaaaa, + expire, fuzzing, header_only, identify, /*%< Append an "on + server <foo>" message + */ + identify_previous_line, /*% Prepend a "Nameserver <foo>:" + message, with newline and tab */ + idnin, idnout, ignore, multiline, need_search, new_search, + noclass, nocrypto, nottl, ns_search_only, /*%< dig +nssearch, + host -C */ + ns_search_success, nsid, /*% Name Server ID (RFC 5001) */ + onesoa, pending, /*%< Pending a successful answer */ + print_unknown_format, qr, raflag, recurse, section_additional, + section_answer, section_authority, section_question, + seenbadcookie, sendcookie, servfail_stops, + setqid, /*% use a speciied query ID */ + showbadcookie, stats, tcflag, tcp_keepalive, tcp_mode, + tcp_mode_set, tls_mode, /*% connect using TLS */ + trace, /*% dig +trace */ + trace_root, /*% initial query for either +trace or +nssearch */ + ttlunits, use_usec, waiting_connect, zflag; + char textname[MXNAME]; /*% Name we're going to be looking up */ + char cmdline[MXNAME]; + dns_rdatatype_t rdtype; + dns_rdatatype_t qrdtype; + dns_rdataclass_t rdclass; + bool rdtypeset; + bool rdclassset; + char name_space[BUFSIZE]; + char oname_space[BUFSIZE]; + isc_buffer_t namebuf; + isc_buffer_t onamebuf; + isc_buffer_t renderbuf; + char *sendspace; + dns_name_t *name; + isc_interval_t interval; + dns_message_t *sendmsg; + dns_name_t *oname; + ISC_LINK(dig_lookup_t) link; + ISC_LIST(dig_query_t) q; + ISC_LIST(dig_query_t) connecting; + dig_query_t *current_query; + dig_serverlist_t my_server_list; + dig_searchlist_t *origin; + dig_query_t *xfr_q; + uint32_t retries; + int nsfound; + int16_t udpsize; + int16_t edns; + int16_t padding; + uint32_t ixfr_serial; + isc_buffer_t rdatabuf; + char rdatastore[MXNAME]; + dst_context_t *tsigctx; + isc_buffer_t *querysig; + uint32_t msgcounter; + dns_fixedname_t fdomain; + isc_sockaddr_t *ecs_addr; + char *cookie; + dns_ednsopt_t *ednsopts; + unsigned int ednsoptscnt; + unsigned int ednsflags; + dns_opcode_t opcode; + int rrcomments; + uint16_t qid; + struct { + bool http_plain; + bool https_mode; + bool https_get; + char *https_path; + }; + struct { + bool tls_ca_set; + char *tls_ca_file; + bool tls_hostname_set; + char *tls_hostname; + bool tls_cert_file_set; + char *tls_cert_file; + bool tls_key_file_set; + char *tls_key_file; + isc_tlsctx_cache_t *tls_ctx_cache; + }; + isc_stdtime_t fuzztime; +}; + +/*% The dig_query structure */ +struct dig_query { + unsigned int magic; + dig_lookup_t *lookup; + bool started; + bool first_soa_rcvd; + bool second_rr_rcvd; + bool first_repeat_rcvd; + bool warn_id; + bool canceled; + uint32_t first_rr_serial; + uint32_t second_rr_serial; + uint32_t msg_count; + uint32_t rr_count; + bool ixfr_axfr; + char *servname; + char *userarg; + isc_buffer_t sendbuf; + char *recvspace, *tmpsendspace, lengthspace[4]; + isc_refcount_t references; + isc_nmhandle_t *handle; + isc_nmhandle_t *readhandle; + isc_nmhandle_t *sendhandle; + ISC_LINK(dig_query_t) link; + ISC_LINK(dig_query_t) clink; + isc_sockaddr_t sockaddr; + isc_time_t time_sent; + isc_time_t time_recv; + uint64_t byte_count; + isc_timer_t *timer; +}; + +struct dig_server { + char servername[MXNAME]; + char userarg[MXNAME]; + ISC_LINK(dig_server_t) link; +}; + +struct dig_searchlist { + char origin[MXNAME]; + ISC_LINK(dig_searchlist_t) link; +}; + +typedef ISC_LIST(dig_searchlist_t) dig_searchlistlist_t; +typedef ISC_LIST(dig_lookup_t) dig_lookuplist_t; + +/* + * Externals from dighost.c + */ + +extern dig_lookuplist_t lookup_list; +extern dig_serverlist_t server_list; +extern dig_searchlistlist_t search_list; +extern unsigned int extrabytes; + +extern bool check_ra, have_ipv4, have_ipv6, specified_source, usesearch, + showsearch, yaml; +extern in_port_t port; +extern bool port_set; +extern unsigned int timeout; +extern isc_mem_t *mctx; +extern isc_refcount_t sendcount; +extern int ndots; +extern int lookup_counter; +extern int exitcode; +extern isc_sockaddr_t localaddr; +extern char keynametext[MXNAME]; +extern char keyfile[MXNAME]; +extern char keysecret[MXNAME]; +extern const dns_name_t *hmacname; +extern unsigned int digestbits; +extern dns_tsigkey_t *tsigkey; +extern bool validated; +extern isc_taskmgr_t *taskmgr; +extern isc_task_t *global_task; +extern bool free_now; +extern bool debugging, debugtiming, memdebugging; +extern bool keep_open; + +extern char *progname; +extern int tries; +extern int fatalexit; +extern bool verbose; + +/* + * Routines in dighost.c. + */ +isc_result_t +get_address(char *host, in_port_t myport, isc_sockaddr_t *sockaddr); + +int +getaddresses(dig_lookup_t *lookup, const char *host, isc_result_t *resultp); + +isc_result_t +get_reverse(char *reverse, size_t len, char *value, bool strict); + +noreturn void +fatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +void +warn(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +noreturn void +digexit(void); + +void +cleanup_openssl_refs(void); + +void +debug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2); + +void +check_result(isc_result_t result, const char *msg); + +bool +setup_lookup(dig_lookup_t *lookup); + +void +destroy_lookup(dig_lookup_t *lookup); + +void +do_lookup(dig_lookup_t *lookup); + +void +start_lookup(void); + +void +onrun_callback(isc_task_t *task, isc_event_t *event); + +int +dhmain(int argc, char **argv); + +void +setup_libs(void); + +void +setup_system(bool ipv4only, bool ipv6only); + +isc_result_t +parse_uint(uint32_t *uip, const char *value, uint32_t max, const char *desc); + +isc_result_t +parse_xint(uint32_t *uip, const char *value, uint32_t max, const char *desc); + +isc_result_t +parse_netprefix(isc_sockaddr_t **sap, const char *value); + +void +parse_hmac(const char *hmacstr); + +dig_lookup_t * +requeue_lookup(dig_lookup_t *lookold, bool servers); + +dig_lookup_t * +make_empty_lookup(void); + +dig_lookup_t * +clone_lookup(dig_lookup_t *lookold, bool servers); + +dig_server_t * +make_server(const char *servname, const char *userarg); + +void +flush_server_list(void); + +void +set_nameserver(char *opt); + +void +clone_server_list(dig_serverlist_t src, dig_serverlist_t *dest); + +void +cancel_all(void); + +void +destroy_libs(void); + +void +set_search_domain(char *domain); + +/* + * Routines to be defined in dig.c, host.c, and nslookup.c. and + * then assigned to the appropriate function pointer + */ +extern isc_result_t (*dighost_printmessage)(dig_query_t *query, + const isc_buffer_t *msgbuf, + dns_message_t *msg, bool headers); + +/* + * Print an error message in the appropriate format. + */ +extern void (*dighost_error)(const char *format, ...); + +/* + * Print a warning message in the appropriate format. + */ +extern void (*dighost_warning)(const char *format, ...); + +/* + * Print a comment in the appropriate format. + */ +extern void (*dighost_comments)(dig_lookup_t *lookup, const char *format, ...); + +/*%< + * Print the final result of the lookup. + */ + +extern void (*dighost_received)(unsigned int bytes, isc_sockaddr_t *from, + dig_query_t *query); +/*%< + * Print a message about where and when the response + * was received from, like the final comment in the + * output of "dig". + */ + +extern void (*dighost_trying)(char *frm, dig_lookup_t *lookup); + +extern void (*dighost_shutdown)(void); + +extern void (*dighost_pre_exit_hook)(void); + +void +save_opt(dig_lookup_t *lookup, char *code, char *value); + +void +setup_file_key(void); +void +setup_text_key(void); + +/* + * Routines exported from dig.c for use by dig for iOS + */ + +/*% + * Call once only to set up libraries, parse global + * parameters and initial command line query parameters + */ +void +dig_setup(int argc, char **argv); + +/*% + * Call to supply new parameters for the next lookup + */ +void +dig_query_setup(bool, bool, int argc, char **argv); + +/*% + * set the main application event cycle running + */ +void +dig_startup(void); + +/*% + * Initiates the next lookup cycle + */ +void +dig_query_start(void); + +/*% + * Activate/deactivate IDN filtering of output. + */ +void +dig_idnsetup(dig_lookup_t *lookup, bool active); + +/*% + * Cleans up the application + */ +void +dig_shutdown(void); + +bool +dig_lookup_is_tls(const dig_lookup_t *lookup); + +ISC_LANG_ENDDECLS diff --git a/bin/dig/host.c b/bin/dig/host.c new file mode 100644 index 0000000..011587a --- /dev/null +++ b/bin/dig/host.c @@ -0,0 +1,931 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/commandline.h> +#include <isc/netaddr.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/util.h> + +#include <dns/byaddr.h> +#include <dns/fixedname.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> + +#include "dighost.h" + +static bool short_form = true, listed_server = false; +static bool default_lookups = true; +static int seen_error = -1; +static bool list_addresses = true; +static bool list_almost_all = false; +static dns_rdatatype_t list_type = dns_rdatatype_a; +static bool printed_server = false; +static bool ipv4only = false, ipv6only = false; + +static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS", + "RESERVED3", "NOTIFY", "UPDATE", + "RESERVED6", "RESERVED7", "RESERVED8", + "RESERVED9", "RESERVED10", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", + "RESERVED15" }; + +static const char *rcodetext[] = { "NOERROR", "FORMERR", "SERVFAIL", + "NXDOMAIN", "NOTIMP", "REFUSED", + "YXDOMAIN", "YXRRSET", "NXRRSET", + "NOTAUTH", "NOTZONE", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", + "RESERVED15", "BADVERS" }; + +struct rtype { + unsigned int type; + const char *text; +}; + +struct rtype rtypes[] = { { 1, "has address" }, + { 2, "name server" }, + { 5, "is an alias for" }, + { 11, "has well known services" }, + { 12, "domain name pointer" }, + { 13, "host information" }, + { 15, "mail is handled by" }, + { 16, "descriptive text" }, + { 19, "x25 address" }, + { 20, "ISDN address" }, + { 24, "has signature" }, + { 25, "has key" }, + { 28, "has IPv6 address" }, + { 29, "location" }, + { 0, NULL } }; + +static char * +rcode_totext(dns_rcode_t rcode) { + static char buf[sizeof("?65535")]; + union { + const char *consttext; + char *deconsttext; + } totext; + + if (rcode >= (sizeof(rcodetext) / sizeof(rcodetext[0]))) { + snprintf(buf, sizeof(buf), "?%u", rcode); + totext.deconsttext = buf; + } else { + totext.consttext = rcodetext[rcode]; + } + return (totext.deconsttext); +} + +noreturn static void +show_usage(void); + +static void +show_usage(void) { + fprintf(stderr, + "Usage: host [-aCdilrTvVw] [-c class] [-N ndots] [-t type] [-W " + "time]\n" + " [-R number] [-m flag] [-p port] hostname " + "[server]\n" + " -a is equivalent to -v -t ANY\n" + " -A is like -a but omits RRSIG, NSEC, NSEC3\n" + " -c specifies query class for non-IN data\n" + " -C compares SOA records on authoritative nameservers\n" + " -d is equivalent to -v\n" + " -l lists all hosts in a domain, using AXFR\n" + " -m set memory debugging flag (trace|record|usage)\n" + " -N changes the number of dots allowed before root " + "lookup " + "is done\n" + " -p specifies the port on the server to query\n" + " -r disables recursive processing\n" + " -R specifies number of retries for UDP packets\n" + " -s a SERVFAIL response should stop query\n" + " -t specifies the query type\n" + " -T enables TCP/IP mode\n" + " -U enables UDP mode\n" + " -v enables verbose output\n" + " -V print version number and exit\n" + " -w specifies to wait forever for a reply\n" + " -W specifies how long to wait for a reply\n" + " -4 use IPv4 query transport only\n" + " -6 use IPv6 query transport only\n"); + exit(1); +} + +static void +host_shutdown(void) { + (void)isc_app_shutdown(); +} + +static void +received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) { + isc_time_t now; + int diff; + + if (!short_form) { + char fromtext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(from, fromtext, sizeof(fromtext)); + if (query->lookup->use_usec) { + TIME_NOW_HIRES(&now); + } else { + TIME_NOW(&now); + } + diff = (int)isc_time_microdiff(&now, &query->time_sent); + printf("Received %u bytes from %s in %d ms\n", bytes, fromtext, + diff / 1000); + } +} + +static void +trying(char *frm, dig_lookup_t *lookup) { + UNUSED(lookup); + + if (!short_form) { + printf("Trying \"%s\"\n", frm); + } +} + +static void +say_message(dns_name_t *name, const char *msg, dns_rdata_t *rdata, + dig_query_t *query) { + isc_buffer_t *b = NULL; + char namestr[DNS_NAME_FORMATSIZE]; + isc_region_t r; + isc_result_t result; + unsigned int bufsize = BUFSIZ; + + dns_name_format(name, namestr, sizeof(namestr)); +retry: + isc_buffer_allocate(mctx, &b, bufsize); + result = dns_rdata_totext(rdata, NULL, b); + if (result == ISC_R_NOSPACE) { + isc_buffer_free(&b); + bufsize *= 2; + goto retry; + } + check_result(result, "dns_rdata_totext"); + isc_buffer_usedregion(b, &r); + if (query->lookup->identify_previous_line) { + printf("Nameserver %s:\n\t", query->servname); + } + printf("%s %s %.*s", namestr, msg, (int)r.length, (char *)r.base); + if (query->lookup->identify) { + printf(" on server %s", query->servname); + } + printf("\n"); + isc_buffer_free(&b); +} + +static isc_result_t +printsection(dns_message_t *msg, dns_section_t sectionid, + const char *section_name, bool headers, dig_query_t *query) { + dns_name_t *name, *print_name; + dns_rdataset_t *rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t target; + isc_result_t result, loopresult; + isc_region_t r; + dns_name_t empty_name; + char tbuf[4096] = { 0 }; + bool first; + bool no_rdata = (sectionid == DNS_SECTION_QUESTION); + + if (headers) { + printf(";; %s SECTION:\n", section_name); + } + + dns_name_init(&empty_name, NULL); + + result = dns_message_firstname(msg, sectionid); + if (result == ISC_R_NOMORE) { + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + for (;;) { + name = NULL; + dns_message_currentname(msg, sectionid, &name); + + isc_buffer_init(&target, tbuf, sizeof(tbuf)); + first = true; + print_name = name; + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (query->lookup->rdtype == dns_rdatatype_axfr && + !((!list_addresses && + (list_type == dns_rdatatype_any || + rdataset->type == list_type)) || + (list_addresses && + (rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa || + rdataset->type == dns_rdatatype_ns || + rdataset->type == dns_rdatatype_ptr)))) + { + continue; + } + if (list_almost_all && + (rdataset->type == dns_rdatatype_rrsig || + rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3)) + { + continue; + } + if (!short_form) { + result = dns_rdataset_totext(rdataset, + print_name, false, + no_rdata, &target); + if (result != ISC_R_SUCCESS) { + return (result); + } +#ifdef USEINITALWS + if (first) { + print_name = &empty_name; + first = false; + } +#else /* ifdef USEINITALWS */ + UNUSED(first); /* Shut up compiler. */ +#endif /* ifdef USEINITALWS */ + } else { + loopresult = dns_rdataset_first(rdataset); + while (loopresult == ISC_R_SUCCESS) { + struct rtype *t; + const char *rtt; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char typebuf2[DNS_RDATATYPE_FORMATSIZE + + 20]; + dns_rdataset_current(rdataset, &rdata); + + for (t = rtypes; t->text != NULL; t++) { + if (t->type == rdata.type) { + rtt = t->text; + goto found; + } + } + + dns_rdatatype_format(rdata.type, + typebuf, + sizeof(typebuf)); + snprintf(typebuf2, sizeof(typebuf2), + "has %s record", typebuf); + rtt = typebuf2; + found: + say_message(print_name, rtt, &rdata, + query); + dns_rdata_reset(&rdata); + loopresult = + dns_rdataset_next(rdataset); + } + } + } + if (!short_form) { + isc_buffer_usedregion(&target, &r); + if (no_rdata) { + printf(";%.*s", (int)r.length, (char *)r.base); + } else { + printf("%.*s", (int)r.length, (char *)r.base); + } + } + + result = dns_message_nextname(msg, sectionid); + if (result == ISC_R_NOMORE) { + break; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +printrdata(dns_message_t *msg, dns_rdataset_t *rdataset, + const dns_name_t *owner, const char *set_name, bool headers) { + isc_buffer_t target; + isc_result_t result; + isc_region_t r; + char tbuf[4096]; + + UNUSED(msg); + if (headers) { + printf(";; %s SECTION:\n", set_name); + } + + isc_buffer_init(&target, tbuf, sizeof(tbuf)); + + result = dns_rdataset_totext(rdataset, owner, false, false, &target); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_usedregion(&target, &r); + printf("%.*s", (int)r.length, (char *)r.base); + + return (ISC_R_SUCCESS); +} + +static void +chase_cnamechain(dns_message_t *msg, dns_name_t *qname) { + isc_result_t result; + dns_rdataset_t *rdataset; + dns_rdata_cname_t cname; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int i = msg->counts[DNS_SECTION_ANSWER]; + + while (i-- > 0) { + rdataset = NULL; + result = dns_message_findname(msg, DNS_SECTION_ANSWER, qname, + dns_rdatatype_cname, 0, NULL, + &rdataset); + if (result != ISC_R_SUCCESS) { + return; + } + result = dns_rdataset_first(rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + check_result(result, "dns_rdata_tostruct"); + dns_name_copy(&cname.cname, qname); + dns_rdata_freestruct(&cname); + } +} + +static isc_result_t +printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg, + bool headers) { + bool did_flag = false; + dns_rdataset_t *opt, *tsig = NULL; + const dns_name_t *tsigname; + isc_result_t result = ISC_R_SUCCESS; + int force_error; + + UNUSED(msgbuf); + UNUSED(headers); + + /* + * We get called multiple times. + * Preserve any existing error status. + */ + force_error = (seen_error == 1) ? 1 : 0; + seen_error = 1; + if (listed_server && !printed_server) { + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + + printf("Using domain server:\n"); + printf("Name: %s\n", query->userarg); + isc_sockaddr_format(&query->sockaddr, sockstr, sizeof(sockstr)); + printf("Address: %s\n", sockstr); + printf("Aliases: \n\n"); + printed_server = true; + } + + if (msg->rcode != 0) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(query->lookup->name, namestr, sizeof(namestr)); + + if (query->lookup->identify_previous_line) { + printf("Nameserver %s:\n\t%s not found: %d(%s)\n", + query->servname, + (msg->rcode != dns_rcode_nxdomain) + ? namestr + : query->lookup->textname, + msg->rcode, rcode_totext(msg->rcode)); + } else { + printf("Host %s not found: %d(%s)\n", + (msg->rcode != dns_rcode_nxdomain) + ? namestr + : query->lookup->textname, + msg->rcode, rcode_totext(msg->rcode)); + } + return (ISC_R_SUCCESS); + } + + if (default_lookups && query->lookup->rdtype == dns_rdatatype_a) { + char namestr[DNS_NAME_FORMATSIZE]; + dig_lookup_t *lookup; + dns_fixedname_t fixed; + dns_name_t *name; + + /* Add AAAA and MX lookups. */ + name = dns_fixedname_initname(&fixed); + dns_name_copy(query->lookup->name, name); + chase_cnamechain(msg, name); + dns_name_format(name, namestr, sizeof(namestr)); + lookup = clone_lookup(query->lookup, false); + if (lookup != NULL) { + strlcpy(lookup->textname, namestr, + sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_aaaa; + lookup->rdtypeset = true; + lookup->origin = NULL; + lookup->retries = tries; + ISC_LIST_APPEND(lookup_list, lookup, link); + } + lookup = clone_lookup(query->lookup, false); + if (lookup != NULL) { + strlcpy(lookup->textname, namestr, + sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_mx; + lookup->rdtypeset = true; + lookup->origin = NULL; + lookup->retries = tries; + ISC_LIST_APPEND(lookup_list, lookup, link); + } + } + + if (!short_form) { + printf(";; ->>HEADER<<- opcode: %s, status: %s, id: %u\n", + opcodetext[msg->opcode], rcode_totext(msg->rcode), + msg->id); + printf(";; flags: "); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) { + printf("qr"); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) { + printf("%saa", did_flag ? " " : ""); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + printf("%stc", did_flag ? " " : ""); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) { + printf("%srd", did_flag ? " " : ""); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) { + printf("%sra", did_flag ? " " : ""); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) { + printf("%sad", did_flag ? " " : ""); + did_flag = true; + } + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) { + printf("%scd", did_flag ? " " : ""); + did_flag = true; + POST(did_flag); + } + printf("; QUERY: %u, ANSWER: %u, " + "AUTHORITY: %u, ADDITIONAL: %u\n", + msg->counts[DNS_SECTION_QUESTION], + msg->counts[DNS_SECTION_ANSWER], + msg->counts[DNS_SECTION_AUTHORITY], + msg->counts[DNS_SECTION_ADDITIONAL]); + opt = dns_message_getopt(msg); + if (opt != NULL) { + printf(";; EDNS: version: %u, udp=%u\n", + (unsigned int)((opt->ttl & 0x00ff0000) >> 16), + (unsigned int)opt->rdclass); + } + tsigname = NULL; + tsig = dns_message_gettsig(msg, &tsigname); + if (tsig != NULL) { + printf(";; PSEUDOSECTIONS: TSIG\n"); + } + } + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_QUESTION]) && !short_form) + { + printf("\n"); + result = printsection(msg, DNS_SECTION_QUESTION, "QUESTION", + true, query); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ANSWER])) { + if (!short_form) { + printf("\n"); + } + result = printsection(msg, DNS_SECTION_ANSWER, "ANSWER", + !short_form, query); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_AUTHORITY]) && + !short_form) + { + printf("\n"); + result = printsection(msg, DNS_SECTION_AUTHORITY, "AUTHORITY", + true, query); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ADDITIONAL]) && + !short_form) + { + printf("\n"); + result = printsection(msg, DNS_SECTION_ADDITIONAL, "ADDITIONAL", + true, query); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if ((tsig != NULL) && !short_form) { + printf("\n"); + result = printrdata(msg, tsig, tsigname, "PSEUDOSECTION TSIG", + true); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (!short_form) { + printf("\n"); + } + + if (short_form && !default_lookups && + ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ANSWER])) + { + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + dns_name_format(query->lookup->name, namestr, sizeof(namestr)); + dns_rdatatype_format(query->lookup->rdtype, typestr, + sizeof(typestr)); + printf("%s has no %s record\n", namestr, typestr); + } + seen_error = force_error; + return (result); +} + +static const char *optstring = "46aAc:dilnm:p:rst:vVwCDN:R:TUW:"; + +/*% version */ +static void +version(void) { + fprintf(stderr, "host %s\n", PACKAGE_VERSION); +} + +static void +pre_parse_args(int argc, char **argv) { + int c; + + while ((c = isc_commandline_parse(argc, argv, optstring)) != -1) { + switch (c) { + case 'm': + memdebugging = true; + if (strcasecmp("trace", isc_commandline_argument) == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + } else if (strcasecmp("record", + isc_commandline_argument) == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } else if (strcasecmp("usage", + isc_commandline_argument) == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + } + 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 'a': + break; + case 'A': + break; + case 'c': + break; + case 'C': + break; + case 'd': + break; + case 'D': + if (debugging) { + debugtiming = true; + } + debugging = true; + break; + case 'i': + break; + case 'l': + break; + case 'n': + break; + case 'N': + break; + case 'p': + break; + case 'r': + break; + case 'R': + break; + case 's': + break; + case 't': + break; + case 'T': + break; + case 'U': + break; + case 'v': + break; + case 'V': + version(); + exit(0); + break; + case 'w': + break; + case 'W': + break; + default: + show_usage(); + } + } + isc_commandline_reset = true; + isc_commandline_index = 1; +} + +static void +parse_args(bool is_batchfile, int argc, char **argv) { + char hostname[MXNAME]; + dig_lookup_t *lookup; + int c; + char store[MXNAME]; + isc_textregion_t tr; + isc_result_t result = ISC_R_SUCCESS; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + uint32_t serial = 0; + + UNUSED(is_batchfile); + + lookup = make_empty_lookup(); + + lookup->servfail_stops = false; + lookup->besteffort = false; + lookup->comments = false; + short_form = !verbose; + + while ((c = isc_commandline_parse(argc, argv, optstring)) != -1) { + switch (c) { + case 'l': + lookup->tcp_mode = true; + lookup->rdtype = dns_rdatatype_axfr; + lookup->rdtypeset = true; + fatalexit = 3; + break; + case 'v': + case 'd': + short_form = false; + break; + case 'r': + lookup->recurse = false; + break; + case 't': + if (strncasecmp(isc_commandline_argument, "ixfr=", 5) == + 0) + { + rdtype = dns_rdatatype_ixfr; + /* XXXMPA add error checking */ + serial = strtoul(isc_commandline_argument + 5, + NULL, 10); + result = ISC_R_SUCCESS; + } else { + tr.base = isc_commandline_argument; + tr.length = strlen(isc_commandline_argument); + result = dns_rdatatype_fromtext( + &rdtype, (isc_textregion_t *)&tr); + } + + if (result != ISC_R_SUCCESS) { + fatalexit = 2; + fatal("invalid type: %s\n", + isc_commandline_argument); + } + if (!lookup->rdtypeset || + lookup->rdtype != dns_rdatatype_axfr) + { + lookup->rdtype = rdtype; + } + lookup->rdtypeset = true; + if (rdtype == dns_rdatatype_axfr) { + /* -l -t any -v */ + list_type = dns_rdatatype_any; + short_form = false; + lookup->tcp_mode = true; + } else if (rdtype == dns_rdatatype_ixfr) { + lookup->ixfr_serial = serial; + lookup->tcp_mode = true; + list_type = rdtype; + } else if (rdtype == dns_rdatatype_any) { + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = true; + } + } else { + list_type = rdtype; + } + list_addresses = false; + default_lookups = false; + break; + case 'c': + tr.base = isc_commandline_argument; + tr.length = strlen(isc_commandline_argument); + result = dns_rdataclass_fromtext( + &rdclass, (isc_textregion_t *)&tr); + + if (result != ISC_R_SUCCESS) { + fatalexit = 2; + fatal("invalid class: %s\n", + isc_commandline_argument); + } else { + lookup->rdclass = rdclass; + lookup->rdclassset = true; + } + default_lookups = false; + break; + case 'A': + list_almost_all = true; + FALLTHROUGH; + case 'a': + if (!lookup->rdtypeset || + lookup->rdtype != dns_rdatatype_axfr) + { + lookup->rdtype = dns_rdatatype_any; + } + list_type = dns_rdatatype_any; + list_addresses = false; + lookup->rdtypeset = true; + short_form = false; + default_lookups = false; + break; + case 'i': + /* deprecated */ + break; + case 'n': + /* deprecated */ + break; + case 'm': + /* Handled by pre_parse_args(). */ + break; + case 'w': + /* + * The timer routines are coded such that + * timeout==MAXINT doesn't enable the timer + */ + timeout = INT_MAX; + break; + case 'W': + timeout = atoi(isc_commandline_argument); + if (timeout < 1) { + timeout = 1; + } + break; + case 'R': + tries = atoi(isc_commandline_argument) + 1; + if (tries < 2) { + tries = 2; + } + break; + case 'T': + lookup->tcp_mode = true; + lookup->tcp_mode_set = true; + break; + case 'U': + lookup->tcp_mode = false; + lookup->tcp_mode_set = true; + break; + case 'C': + debug("showing all SOAs"); + lookup->rdtype = dns_rdatatype_ns; + lookup->rdtypeset = true; + lookup->rdclass = dns_rdataclass_in; + lookup->rdclassset = true; + lookup->ns_search_only = true; + lookup->trace_root = true; + lookup->identify_previous_line = true; + default_lookups = false; + break; + case 'N': + debug("setting NDOTS to %s", isc_commandline_argument); + ndots = atoi(isc_commandline_argument); + break; + case 'D': + /* Handled by pre_parse_args(). */ + break; + case '4': + /* Handled by pre_parse_args(). */ + break; + case '6': + /* Handled by pre_parse_args(). */ + break; + case 's': + lookup->servfail_stops = true; + break; + case 'p': + port = atoi(isc_commandline_argument); + port_set = true; + break; + } + } + + lookup->retries = tries; + + if (isc_commandline_index >= argc) { + show_usage(); + } + + strlcpy(hostname, argv[isc_commandline_index], sizeof(hostname)); + + if (argc > isc_commandline_index + 1) { + set_nameserver(argv[isc_commandline_index + 1]); + debug("server is %s", argv[isc_commandline_index + 1]); + listed_server = true; + } else { + check_ra = true; + } + + lookup->pending = false; + if (get_reverse(store, sizeof(store), hostname, true) == ISC_R_SUCCESS) + { + strlcpy(lookup->textname, store, sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_ptr; + lookup->rdtypeset = true; + default_lookups = false; + } else { + strlcpy(lookup->textname, hostname, sizeof(lookup->textname)); + usesearch = true; + } + lookup->new_search = true; + ISC_LIST_APPEND(lookup_list, lookup, link); +} + +int +main(int argc, char **argv) { + isc_result_t result; + + tries = 2; + + ISC_LIST_INIT(lookup_list); + ISC_LIST_INIT(server_list); + ISC_LIST_INIT(search_list); + + fatalexit = 1; + + /* setup dighost callbacks */ + dighost_printmessage = printmessage; + dighost_received = received; + dighost_trying = trying; + dighost_shutdown = host_shutdown; + + debug("main()"); + progname = argv[0]; + pre_parse_args(argc, argv); + result = isc_app_start(); + check_result(result, "isc_app_start"); + setup_libs(); + setup_system(ipv4only, ipv6only); + parse_args(false, argc, argv); + if (keyfile[0] != 0) { + setup_file_key(); + } else if (keysecret[0] != 0) { + setup_text_key(); + } + result = isc_app_onrun(mctx, global_task, onrun_callback, NULL); + check_result(result, "isc_app_onrun"); + isc_app_run(); + cancel_all(); + destroy_libs(); + isc_app_finish(); + return ((seen_error == 0) ? 0 : 1); +} diff --git a/bin/dig/host.rst b/bin/dig/host.rst new file mode 100644 index 0000000..2647d36 --- /dev/null +++ b/bin/dig/host.rst @@ -0,0 +1,193 @@ +.. Copyright (C) Internet Systems Consortium, Inc. ("ISC") +.. +.. SPDX-License-Identifier: MPL-2.0 +.. +.. This Source Code Form is subject to the terms of the Mozilla Public +.. License, v. 2.0. If a copy of the MPL was not distributed with this +.. file, you can obtain one at https://mozilla.org/MPL/2.0/. +.. +.. See the COPYRIGHT file distributed with this work for additional +.. information regarding copyright ownership. + +.. highlight: console + +.. iscman:: host +.. program:: host +.. _man_host: + +host - DNS lookup utility +------------------------- + +Synopsis +~~~~~~~~ + +:program:`host` [**-aACdlnrsTUwv**] [**-c** class] [**-N** ndots] [**-p** port] [**-R** number] [**-t** type] [**-W** wait] [**-m** flag] [ [**-4**] | [**-6**] ] [**-v**] [**-V**] {name} [server] + +Description +~~~~~~~~~~~ + +:program:`host` is a simple utility for performing DNS lookups. It is normally +used to convert names to IP addresses and vice versa. When no arguments +or options are given, :program:`host` prints a short summary of its +command-line arguments and options. + +``name`` is the domain name that is to be looked up. It can also be a +dotted-decimal IPv4 address or a colon-delimited IPv6 address, in which +case :program:`host` by default performs a reverse lookup for that address. +``server`` is an optional argument which is either the name or IP +address of the name server that :program:`host` should query instead of the +server or servers listed in ``/etc/resolv.conf``. + +Options +~~~~~~~ + +.. option:: -4 + + This option specifies that only IPv4 should be used for query transport. See also the :option:`-6` option. + +.. option:: -6 + + This option specifies that only IPv6 should be used for query transport. See also the :option:`-4` option. + +.. option:: -a + + The :option:`-a` ("all") option is normally equivalent to :option:`-v` :option:`-t ANY <-t>`. It + also affects the behavior of the :option:`-l` list zone option. + +.. option:: -A + + The :option:`-A` ("almost all") option is equivalent to :option:`-a`, except that RRSIG, + NSEC, and NSEC3 records are omitted from the output. + +.. option:: -c class + + This option specifies the query class, which can be used to lookup HS (Hesiod) or CH (Chaosnet) + class resource records. The default class is IN (Internet). + +.. option:: -C + + This option indicates that :iscman:`named` should check consistency, meaning that :program:`host` queries the SOA records for zone + ``name`` from all the listed authoritative name servers for that + zone. The list of name servers is defined by the NS records that are + found for the zone. + +.. option:: -d + + This option prints debugging traces, and is equivalent to the :option:`-v` verbose option. + +.. option:: -l + + This option tells :iscman:`named` to list the zone, meaning the :program:`host` command performs a zone transfer of zone + ``name`` and prints out the NS, PTR, and address records (A/AAAA). + + Together, the :option:`-l` :option:`-a` options print all records in the zone. + +.. option:: -N ndots + + This option specifies the number of dots (``ndots``) that have to be in ``name`` for it to be + considered absolute. The default value is that defined using the + ``ndots`` statement in ``/etc/resolv.conf``, or 1 if no ``ndots`` statement + is present. Names with fewer dots are interpreted as relative names, + and are searched for in the domains listed in the ``search`` or + ``domain`` directive in ``/etc/resolv.conf``. + +.. option:: -p port + + This option specifies the port to query on the server. The default is 53. + +.. option:: -r + + This option specifies a non-recursive query; setting this option clears the RD (recursion + desired) bit in the query. This means that the name server + receiving the query does not attempt to resolve ``name``. The :option:`-r` + option enables :program:`host` to mimic the behavior of a name server by + making non-recursive queries, and expecting to receive answers to + those queries that can be referrals to other name servers. + +.. option:: -R number + + This option specifies the number of retries for UDP queries. If ``number`` is negative or zero, + the number of retries is silently set to 1. The default value is 1, or + the value of the ``attempts`` option in ``/etc/resolv.conf``, if set. + +.. option:: -s + + This option tells :iscman:`named` *not* to send the query to the next nameserver if any server responds + with a SERVFAIL response, which is the reverse of normal stub + resolver behavior. + +.. option:: -t type + + This option specifies the query type. The ``type`` argument can be any recognized query type: + CNAME, NS, SOA, TXT, DNSKEY, AXFR, etc. + + When no query type is specified, :program:`host` automatically selects an + appropriate query type. By default, it looks for A, AAAA, and MX + records. If the :option:`-C` option is given, queries are made for SOA + records. If ``name`` is a dotted-decimal IPv4 address or + colon-delimited IPv6 address, :program:`host` queries for PTR records. + + If a query type of IXFR is chosen, the starting serial number can be + specified by appending an equals sign (=), followed by the starting serial + number, e.g., :option:`-t IXFR=12345678 <-t>`. + +.. option:: -T, -U + + This option specifies TCP or UDP. By default, :program:`host` uses UDP when making queries; the + :option:`-T` option makes it use a TCP connection when querying the name + server. TCP is automatically selected for queries that require + it, such as zone transfer (AXFR) requests. Type ``ANY`` queries default + to TCP, but can be forced to use UDP initially via :option:`-U`. + +.. option:: -m flag + + This option sets memory usage debugging: the flag can be ``record``, ``usage``, or + ``trace``. The :option:`-m` option can be specified more than once to set + multiple flags. + +.. option:: -v + + This option sets verbose output, and is equivalent to the :option:`-d` debug option. Verbose output + can also be enabled by setting the ``debug`` option in + ``/etc/resolv.conf``. + +.. option:: -V + + This option prints the version number and exits. + +.. option:: -w + + This option sets "wait forever": the query timeout is set to the maximum possible. See + also the :option:`-W` option. + +.. option:: -W wait + + This options sets the length of the wait timeout, indicating that :iscman:`named` should wait for up to ``wait`` seconds for a reply. If ``wait`` is + less than 1, the wait interval is set to 1 second. + + By default, :program:`host` waits for 5 seconds for UDP responses and 10 + seconds for TCP connections. These defaults can be overridden by the + ``timeout`` option in ``/etc/resolv.conf``. + + See also the :option:`-w` option. + +IDN Support +~~~~~~~~~~~ + +If :program:`host` has been built with IDN (internationalized domain name) +support, it can accept and display non-ASCII domain names. :program:`host` +appropriately converts character encoding of a domain name before sending +a request to a DNS server or displaying a reply from the server. +To turn off IDN support, define the ``IDN_DISABLE`` +environment variable. IDN support is disabled if the variable is set +when :program:`host` runs. + +Files +~~~~~ + +``/etc/resolv.conf`` + +See Also +~~~~~~~~ + +:iscman:`dig(1) <dig>`, :iscman:`named(8) <named>`. diff --git a/bin/dig/nslookup.c b/bin/dig/nslookup.c new file mode 100644 index 0000000..a0f508d --- /dev/null +++ b/bin/dig/nslookup.c @@ -0,0 +1,977 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/attributes.h> +#include <isc/buffer.h> +#include <isc/commandline.h> +#include <isc/event.h> +#include <isc/netaddr.h> +#include <isc/parseint.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/util.h> + +#include <dns/byaddr.h> +#include <dns/fixedname.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> + +#include "dighost.h" +#include "readline.h" + +static bool short_form = true, tcpmode = false, tcpmode_set = false, + identify = false, stats = true, comments = true, + section_question = true, section_answer = true, + section_authority = true, section_additional = true, recurse = true, + aaonly = false, nofail = true, default_lookups = true, + a_noanswer = false; + +static bool interactive; + +static bool in_use = false; +static char defclass[MXRD] = "IN"; +static char deftype[MXRD] = "A"; +static isc_event_t *global_event = NULL; +static int query_error = 1, print_error = 0; + +static char domainopt[DNS_NAME_MAXTEXT]; + +static const char *rcodetext[] = { "NOERROR", "FORMERR", "SERVFAIL", + "NXDOMAIN", "NOTIMP", "REFUSED", + "YXDOMAIN", "YXRRSET", "NXRRSET", + "NOTAUTH", "NOTZONE", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", + "RESERVED15", "BADVERS" }; + +static const char *rtypetext[] = { + "rtype_0 = ", /* 0 */ + "internet address = ", /* 1 */ + "nameserver = ", /* 2 */ + "md = ", /* 3 */ + "mf = ", /* 4 */ + "canonical name = ", /* 5 */ + "soa = ", /* 6 */ + "mb = ", /* 7 */ + "mg = ", /* 8 */ + "mr = ", /* 9 */ + "rtype_10 = ", /* 10 */ + "protocol = ", /* 11 */ + "name = ", /* 12 */ + "hinfo = ", /* 13 */ + "minfo = ", /* 14 */ + "mail exchanger = ", /* 15 */ + "text = ", /* 16 */ + "rp = ", /* 17 */ + "afsdb = ", /* 18 */ + "x25 address = ", /* 19 */ + "isdn address = ", /* 20 */ + "rt = ", /* 21 */ + "nsap = ", /* 22 */ + "nsap_ptr = ", /* 23 */ + "signature = ", /* 24 */ + "key = ", /* 25 */ + "px = ", /* 26 */ + "gpos = ", /* 27 */ + "has AAAA address ", /* 28 */ + "loc = ", /* 29 */ + "next = ", /* 30 */ + "rtype_31 = ", /* 31 */ + "rtype_32 = ", /* 32 */ + "service = ", /* 33 */ + "rtype_34 = ", /* 34 */ + "naptr = ", /* 35 */ + "kx = ", /* 36 */ + "cert = ", /* 37 */ + "v6 address = ", /* 38 */ + "dname = ", /* 39 */ + "rtype_40 = ", /* 40 */ + "optional = " /* 41 */ +}; + +#define N_KNOWN_RRTYPES (sizeof(rtypetext) / sizeof(rtypetext[0])) + +static void +getinput(isc_task_t *task, isc_event_t *event); + +static char * +rcode_totext(dns_rcode_t rcode) { + static char buf[sizeof("?65535")]; + union { + const char *consttext; + char *deconsttext; + } totext; + + if (rcode >= (sizeof(rcodetext) / sizeof(rcodetext[0]))) { + snprintf(buf, sizeof(buf), "?%u", rcode); + totext.deconsttext = buf; + } else { + totext.consttext = rcodetext[rcode]; + } + return (totext.deconsttext); +} + +static void +query_finished(void) { + isc_event_t *event = global_event; + + debug("dighost_shutdown()"); + + if (!in_use) { + isc_app_shutdown(); + return; + } + + isc_task_send(global_task, &event); +} + +static void +printsoa(dns_rdata_t *rdata) { + dns_rdata_soa_t soa; + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + + result = dns_rdata_tostruct(rdata, &soa, NULL); + check_result(result, "dns_rdata_tostruct"); + + dns_name_format(&soa.origin, namebuf, sizeof(namebuf)); + printf("\torigin = %s\n", namebuf); + dns_name_format(&soa.contact, namebuf, sizeof(namebuf)); + printf("\tmail addr = %s\n", namebuf); + printf("\tserial = %u\n", soa.serial); + printf("\trefresh = %u\n", soa.refresh); + printf("\tretry = %u\n", soa.retry); + printf("\texpire = %u\n", soa.expire); + printf("\tminimum = %u\n", soa.minimum); + dns_rdata_freestruct(&soa); +} + +static void +printaddr(dns_rdata_t *rdata) { + isc_result_t result; + char text[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + isc_buffer_t b; + + isc_buffer_init(&b, text, sizeof(text)); + result = dns_rdata_totext(rdata, NULL, &b); + check_result(result, "dns_rdata_totext"); + printf("Address: %.*s\n", (int)isc_buffer_usedlength(&b), + (char *)isc_buffer_base(&b)); +} + +static void +printrdata(dns_rdata_t *rdata) { + isc_result_t result; + isc_buffer_t *b = NULL; + unsigned int size = 1024; + bool done = false; + + if (rdata->type < N_KNOWN_RRTYPES) { + printf("%s", rtypetext[rdata->type]); + } else { + printf("rdata_%d = ", rdata->type); + } + + while (!done) { + isc_buffer_allocate(mctx, &b, size); + result = dns_rdata_totext(rdata, NULL, b); + if (result == ISC_R_SUCCESS) { + printf("%.*s\n", (int)isc_buffer_usedlength(b), + (char *)isc_buffer_base(b)); + done = true; + } else if (result != ISC_R_NOSPACE) { + check_result(result, "dns_rdata_totext"); + } + isc_buffer_free(&b); + size *= 2; + } +} + +static isc_result_t +printsection(dig_query_t *query, dns_message_t *msg, bool headers, + dns_section_t section) { + isc_result_t result, loopresult; + dns_name_t *name; + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + char namebuf[DNS_NAME_FORMATSIZE]; + + UNUSED(query); + UNUSED(headers); + + debug("printsection()"); + + result = dns_message_firstname(msg, section); + if (result == ISC_R_NOMORE) { + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + for (;;) { + name = NULL; + dns_message_currentname(msg, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + loopresult = dns_rdataset_first(rdataset); + while (loopresult == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + switch (rdata.type) { + case dns_rdatatype_a: + case dns_rdatatype_aaaa: + if (section != DNS_SECTION_ANSWER) { + goto def_short_section; + } + dns_name_format(name, namebuf, + sizeof(namebuf)); + printf("Name:\t%s\n", namebuf); + printaddr(&rdata); + break; + case dns_rdatatype_soa: + dns_name_format(name, namebuf, + sizeof(namebuf)); + printf("%s\n", namebuf); + printsoa(&rdata); + break; + default: + def_short_section: + dns_name_format(name, namebuf, + sizeof(namebuf)); + printf("%s\t", namebuf); + printrdata(&rdata); + break; + } + dns_rdata_reset(&rdata); + loopresult = dns_rdataset_next(rdataset); + } + } + result = dns_message_nextname(msg, section); + if (result == ISC_R_NOMORE) { + break; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +detailsection(dig_query_t *query, dns_message_t *msg, bool headers, + dns_section_t section) { + isc_result_t result, loopresult; + dns_name_t *name; + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + char namebuf[DNS_NAME_FORMATSIZE]; + + UNUSED(query); + + debug("detailsection()"); + + if (headers) { + switch (section) { + case DNS_SECTION_QUESTION: + puts(" QUESTIONS:"); + break; + case DNS_SECTION_ANSWER: + puts(" ANSWERS:"); + break; + case DNS_SECTION_AUTHORITY: + puts(" AUTHORITY RECORDS:"); + break; + case DNS_SECTION_ADDITIONAL: + puts(" ADDITIONAL RECORDS:"); + break; + } + } + + result = dns_message_firstname(msg, section); + if (result == ISC_R_NOMORE) { + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + for (;;) { + name = NULL; + dns_message_currentname(msg, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (section == DNS_SECTION_QUESTION) { + dns_name_format(name, namebuf, sizeof(namebuf)); + printf("\t%s, ", namebuf); + dns_rdatatype_format(rdataset->type, namebuf, + sizeof(namebuf)); + printf("type = %s, ", namebuf); + dns_rdataclass_format(rdataset->rdclass, + namebuf, sizeof(namebuf)); + printf("class = %s\n", namebuf); + } + loopresult = dns_rdataset_first(rdataset); + while (loopresult == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + + dns_name_format(name, namebuf, sizeof(namebuf)); + printf(" -> %s\n", namebuf); + + switch (rdata.type) { + case dns_rdatatype_soa: + printsoa(&rdata); + break; + default: + printf("\t"); + printrdata(&rdata); + } + dns_rdata_reset(&rdata); + printf("\tttl = %u\n", rdataset->ttl); + loopresult = dns_rdataset_next(rdataset); + } + } + result = dns_message_nextname(msg, section); + if (result == ISC_R_NOMORE) { + break; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (ISC_R_SUCCESS); +} + +static void +received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) { + UNUSED(bytes); + UNUSED(from); + UNUSED(query); +} + +static void +trying(char *frm, dig_lookup_t *lookup) { + UNUSED(frm); + UNUSED(lookup); +} + +static void +chase_cnamechain(dns_message_t *msg, dns_name_t *qname) { + isc_result_t result; + dns_rdataset_t *rdataset; + dns_rdata_cname_t cname; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int i = msg->counts[DNS_SECTION_ANSWER]; + + while (i-- > 0) { + rdataset = NULL; + result = dns_message_findname(msg, DNS_SECTION_ANSWER, qname, + dns_rdatatype_cname, 0, NULL, + &rdataset); + if (result != ISC_R_SUCCESS) { + return; + } + result = dns_rdataset_first(rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + check_result(result, "dns_rdata_tostruct"); + dns_name_copy(&cname.cname, qname); + dns_rdata_freestruct(&cname); + } +} + +static isc_result_t +printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg, + bool headers) { + char servtext[ISC_SOCKADDR_FORMATSIZE]; + + UNUSED(msgbuf); + + /* I've we've gotten this far, we've reached a server. */ + query_error = 0; + + debug("printmessage()"); + + if (!default_lookups || query->lookup->rdtype == dns_rdatatype_a) { + isc_sockaddr_format(&query->sockaddr, servtext, + sizeof(servtext)); + printf("Server:\t\t%s\n", query->userarg); + printf("Address:\t%s\n", servtext); + + puts(""); + } + + if (!short_form) { + puts("------------"); + /* detailheader(query, msg);*/ + detailsection(query, msg, true, DNS_SECTION_QUESTION); + detailsection(query, msg, true, DNS_SECTION_ANSWER); + detailsection(query, msg, true, DNS_SECTION_AUTHORITY); + detailsection(query, msg, true, DNS_SECTION_ADDITIONAL); + puts("------------"); + } + + if (msg->rcode != 0) { + char nametext[DNS_NAME_FORMATSIZE]; + dns_name_format(query->lookup->name, nametext, + sizeof(nametext)); + printf("** server can't find %s: %s\n", nametext, + rcode_totext(msg->rcode)); + debug("returning with rcode == 0"); + + /* the lookup failed */ + print_error |= 1; + return (ISC_R_SUCCESS); + } + + if (default_lookups && query->lookup->rdtype == dns_rdatatype_a) { + char namestr[DNS_NAME_FORMATSIZE]; + dig_lookup_t *lookup; + dns_fixedname_t fixed; + dns_name_t *name; + + /* Add AAAA lookup. */ + name = dns_fixedname_initname(&fixed); + dns_name_copy(query->lookup->name, name); + chase_cnamechain(msg, name); + dns_name_format(name, namestr, sizeof(namestr)); + lookup = clone_lookup(query->lookup, false); + if (lookup != NULL) { + strlcpy(lookup->textname, namestr, + sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_aaaa; + lookup->rdtypeset = true; + lookup->origin = NULL; + lookup->retries = tries; + ISC_LIST_APPEND(lookup_list, lookup, link); + } + } + + if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0 && + (!default_lookups || query->lookup->rdtype == dns_rdatatype_a)) + { + puts("Non-authoritative answer:"); + } + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ANSWER])) { + printsection(query, msg, headers, DNS_SECTION_ANSWER); + } else { + if (default_lookups && query->lookup->rdtype == dns_rdatatype_a) + { + a_noanswer = true; + } else if (!default_lookups || + (query->lookup->rdtype == dns_rdatatype_aaaa && + a_noanswer)) + { + printf("*** Can't find %s: No answer\n", + query->lookup->textname); + } + } + + if (((msg->flags & DNS_MESSAGEFLAG_AA) == 0) && + (query->lookup->rdtype != dns_rdatatype_a) && + (query->lookup->rdtype != dns_rdatatype_aaaa)) + { + puts("\nAuthoritative answers can be found from:"); + printsection(query, msg, headers, DNS_SECTION_AUTHORITY); + printsection(query, msg, headers, DNS_SECTION_ADDITIONAL); + } + return (ISC_R_SUCCESS); +} + +static void +show_settings(bool full, bool serv_only) { + dig_server_t *srv; + isc_sockaddr_t sockaddr; + dig_searchlist_t *listent; + isc_result_t result; + + srv = ISC_LIST_HEAD(server_list); + + while (srv != NULL) { + char sockstr[ISC_SOCKADDR_FORMATSIZE]; + + result = get_address(srv->servername, port, &sockaddr); + check_result(result, "get_address"); + + isc_sockaddr_format(&sockaddr, sockstr, sizeof(sockstr)); + printf("Default server: %s\nAddress: %s\n", srv->userarg, + sockstr); + if (!full) { + return; + } + srv = ISC_LIST_NEXT(srv, link); + } + if (serv_only) { + return; + } + printf("\nSet options:\n"); + printf(" %s\t\t\t%s\t\t%s\n", tcpmode ? "vc" : "novc", + short_form ? "nodebug" : "debug", debugging ? "d2" : "nod2"); + printf(" %s\t\t%s\n", usesearch ? "search" : "nosearch", + recurse ? "recurse" : "norecurse"); + printf(" timeout = %u\t\tretry = %d\tport = %u\tndots = %d\n", timeout, + tries, port, ndots); + printf(" querytype = %-8s\tclass = %s\n", deftype, defclass); + printf(" srchlist = "); + for (listent = ISC_LIST_HEAD(search_list); listent != NULL; + listent = ISC_LIST_NEXT(listent, link)) + { + printf("%s", listent->origin); + if (ISC_LIST_NEXT(listent, link) != NULL) { + printf("/"); + } + } + printf("\n"); +} + +static bool +testtype(char *typetext) { + isc_result_t result; + isc_textregion_t tr; + dns_rdatatype_t rdtype; + + tr.base = typetext; + tr.length = strlen(typetext); + result = dns_rdatatype_fromtext(&rdtype, &tr); + if (result == ISC_R_SUCCESS) { + return (true); + } else { + printf("unknown query type: %s\n", typetext); + return (false); + } +} + +static bool +testclass(char *typetext) { + isc_result_t result; + isc_textregion_t tr; + dns_rdataclass_t rdclass; + + tr.base = typetext; + tr.length = strlen(typetext); + result = dns_rdataclass_fromtext(&rdclass, &tr); + if (result == ISC_R_SUCCESS) { + return (true); + } else { + printf("unknown query class: %s\n", typetext); + return (false); + } +} + +static void +set_port(const char *value) { + uint32_t n; + isc_result_t result = parse_uint(&n, value, 65535, "port"); + if (result == ISC_R_SUCCESS) { + port = (uint16_t)n; + port_set = true; + } +} + +static void +set_timeout(const char *value) { + uint32_t n; + isc_result_t result = parse_uint(&n, value, UINT_MAX, "timeout"); + if (result == ISC_R_SUCCESS) { + timeout = n; + } +} + +static void +set_tries(const char *value) { + uint32_t n; + isc_result_t result = parse_uint(&n, value, INT_MAX, "tries"); + if (result == ISC_R_SUCCESS) { + tries = n; + } +} + +static void +set_ndots(const char *value) { + uint32_t n; + isc_result_t result = parse_uint(&n, value, 128, "ndots"); + if (result == ISC_R_SUCCESS) { + ndots = n; + } +} + +static void +version(void) { + fprintf(stderr, "nslookup %s\n", PACKAGE_VERSION); +} + +static void +setoption(char *opt) { + size_t l = strlen(opt); + +#define CHECKOPT(A, N) \ + ((l >= N) && (l < sizeof(A)) && (strncasecmp(opt, A, l) == 0)) + + if (CHECKOPT("all", 3)) { + show_settings(true, false); + } else if (strncasecmp(opt, "class=", 6) == 0) { + if (testclass(&opt[6])) { + strlcpy(defclass, &opt[6], sizeof(defclass)); + } + } else if (strncasecmp(opt, "cl=", 3) == 0) { + if (testclass(&opt[3])) { + strlcpy(defclass, &opt[3], sizeof(defclass)); + } + } else if (strncasecmp(opt, "type=", 5) == 0) { + if (testtype(&opt[5])) { + strlcpy(deftype, &opt[5], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "ty=", 3) == 0) { + if (testtype(&opt[3])) { + strlcpy(deftype, &opt[3], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "querytype=", 10) == 0) { + if (testtype(&opt[10])) { + strlcpy(deftype, &opt[10], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "query=", 6) == 0) { + if (testtype(&opt[6])) { + strlcpy(deftype, &opt[6], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "qu=", 3) == 0) { + if (testtype(&opt[3])) { + strlcpy(deftype, &opt[3], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "q=", 2) == 0) { + if (testtype(&opt[2])) { + strlcpy(deftype, &opt[2], sizeof(deftype)); + default_lookups = false; + } + } else if (strncasecmp(opt, "domain=", 7) == 0) { + strlcpy(domainopt, &opt[7], sizeof(domainopt)); + set_search_domain(domainopt); + usesearch = true; + } else if (strncasecmp(opt, "do=", 3) == 0) { + strlcpy(domainopt, &opt[3], sizeof(domainopt)); + set_search_domain(domainopt); + usesearch = true; + } else if (strncasecmp(opt, "port=", 5) == 0) { + set_port(&opt[5]); + } else if (strncasecmp(opt, "po=", 3) == 0) { + set_port(&opt[3]); + } else if (strncasecmp(opt, "timeout=", 8) == 0) { + set_timeout(&opt[8]); + } else if (strncasecmp(opt, "t=", 2) == 0) { + set_timeout(&opt[2]); + } else if (CHECKOPT("recurse", 3)) { + recurse = true; + } else if (CHECKOPT("norecurse", 5)) { + recurse = false; + } else if (strncasecmp(opt, "retry=", 6) == 0) { + set_tries(&opt[6]); + } else if (strncasecmp(opt, "ret=", 4) == 0) { + set_tries(&opt[4]); + } else if (CHECKOPT("defname", 3)) { + usesearch = true; + } else if (CHECKOPT("nodefname", 5)) { + usesearch = false; + } else if (CHECKOPT("vc", 2)) { + tcpmode = true; + tcpmode_set = true; + } else if (CHECKOPT("novc", 4)) { + tcpmode = false; + tcpmode_set = true; + } else if (CHECKOPT("debug", 3)) { + short_form = false; + showsearch = true; + } else if (CHECKOPT("nodebug", 5)) { + short_form = true; + showsearch = false; + } else if (CHECKOPT("d2", 2)) { + debugging = true; + } else if (CHECKOPT("nod2", 4)) { + debugging = false; + } else if (CHECKOPT("search", 3)) { + usesearch = true; + } else if (CHECKOPT("nosearch", 5)) { + usesearch = false; + } else if (CHECKOPT("sil", 3)) { + /* deprecation_msg = false; */ + } else if (CHECKOPT("fail", 3)) { + nofail = false; + } else if (CHECKOPT("nofail", 5)) { + nofail = true; + } else if (strncasecmp(opt, "ndots=", 6) == 0) { + set_ndots(&opt[6]); + } else { + printf("*** Invalid option: %s\n", opt); + } +} + +static void +addlookup(char *opt) { + dig_lookup_t *lookup; + isc_result_t result; + isc_textregion_t tr; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + char store[MXNAME]; + + debug("addlookup()"); + + a_noanswer = false; + + tr.base = deftype; + tr.length = strlen(deftype); + result = dns_rdatatype_fromtext(&rdtype, &tr); + if (result != ISC_R_SUCCESS) { + printf("unknown query type: %s\n", deftype); + rdclass = dns_rdatatype_a; + } + tr.base = defclass; + tr.length = strlen(defclass); + result = dns_rdataclass_fromtext(&rdclass, &tr); + if (result != ISC_R_SUCCESS) { + printf("unknown query class: %s\n", defclass); + rdclass = dns_rdataclass_in; + } + lookup = make_empty_lookup(); + if (get_reverse(store, sizeof(store), opt, true) == ISC_R_SUCCESS) { + strlcpy(lookup->textname, store, sizeof(lookup->textname)); + lookup->rdtype = dns_rdatatype_ptr; + lookup->rdtypeset = true; + } else { + strlcpy(lookup->textname, opt, sizeof(lookup->textname)); + lookup->rdtype = rdtype; + lookup->rdtypeset = true; + } + lookup->rdclass = rdclass; + lookup->rdclassset = true; + lookup->trace = false; + lookup->trace_root = lookup->trace; + lookup->ns_search_only = false; + lookup->identify = identify; + lookup->recurse = recurse; + lookup->aaonly = aaonly; + lookup->retries = tries; + lookup->setqid = false; + lookup->qid = 0; + lookup->comments = comments; + if (lookup->rdtype == dns_rdatatype_any && !tcpmode_set) { + lookup->tcp_mode = true; + } else { + lookup->tcp_mode = tcpmode; + } + lookup->stats = stats; + lookup->section_question = section_question; + lookup->section_answer = section_answer; + lookup->section_authority = section_authority; + lookup->section_additional = section_additional; + lookup->new_search = true; + lookup->besteffort = false; + if (nofail) { + lookup->servfail_stops = false; + } + ISC_LIST_INIT(lookup->q); + ISC_LINK_INIT(lookup, link); + ISC_LIST_APPEND(lookup_list, lookup, link); + lookup->origin = NULL; + ISC_LIST_INIT(lookup->my_server_list); + debug("looking up %s", lookup->textname); +} + +static void +do_next_command(char *input) { + char *ptr, *arg, *last; + + if ((ptr = strtok_r(input, " \t\r\n", &last)) == NULL) { + return; + } + arg = strtok_r(NULL, " \t\r\n", &last); + if ((strcasecmp(ptr, "set") == 0) && (arg != NULL)) { + setoption(arg); + } else if ((strcasecmp(ptr, "server") == 0) || + (strcasecmp(ptr, "lserver") == 0)) + { + isc_app_block(); + set_nameserver(arg); + check_ra = false; + isc_app_unblock(); + show_settings(true, true); + } else if (strcasecmp(ptr, "exit") == 0) { + in_use = false; + } else if (strcasecmp(ptr, "help") == 0 || strcasecmp(ptr, "?") == 0) { + printf("The '%s' command is not yet implemented.\n", ptr); + } else if (strcasecmp(ptr, "finger") == 0 || + strcasecmp(ptr, "root") == 0 || strcasecmp(ptr, "ls") == 0 || + strcasecmp(ptr, "view") == 0) + { + printf("The '%s' command is not implemented.\n", ptr); + } else { + addlookup(ptr); + } +} + +static void +get_next_command(void) { + char cmdlinebuf[COMMSIZE]; + char *cmdline, *ptr = NULL; + + isc_app_block(); + if (interactive) { + cmdline = ptr = readline("> "); + if (ptr != NULL && *ptr != 0) { + add_history(ptr); + } + } else { + cmdline = fgets(cmdlinebuf, COMMSIZE, stdin); + } + isc_app_unblock(); + if (cmdline == NULL) { + in_use = false; + } else { + do_next_command(cmdline); + } + if (ptr != NULL) { + free(ptr); + } +} + +noreturn static void +usage(void); + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " nslookup [-opt ...] # interactive mode " + "using default server\n"); + fprintf(stderr, " nslookup [-opt ...] - server # interactive mode " + "using 'server'\n"); + fprintf(stderr, " nslookup [-opt ...] host # just look up " + "'host' using default server\n"); + fprintf(stderr, " nslookup [-opt ...] host server # just look up " + "'host' using 'server'\n"); + exit(1); +} + +static void +parse_args(int argc, char **argv) { + bool have_lookup = false; + + usesearch = true; + for (argc--, argv++; argc > 0 && argv[0] != NULL; argc--, argv++) { + debug("main parsing %s", argv[0]); + if (argv[0][0] == '-') { + if (strncasecmp(argv[0], "-ver", 4) == 0) { + version(); + exit(0); + } else if (argv[0][1] != 0) { + setoption(&argv[0][1]); + } else { + have_lookup = true; + } + } else { + if (!have_lookup) { + have_lookup = true; + in_use = true; + addlookup(argv[0]); + } else { + if (argv[1] != NULL) { + usage(); + } + set_nameserver(argv[0]); + check_ra = false; + } + } + } +} + +static void +getinput(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + if (global_event == NULL) { + global_event = event; + } + while (in_use) { + get_next_command(); + if (ISC_LIST_HEAD(lookup_list) != NULL) { + start_lookup(); + return; + } + } + isc_app_shutdown(); +} + +int +main(int argc, char **argv) { + isc_result_t result; + + interactive = isatty(0); + + ISC_LIST_INIT(lookup_list); + ISC_LIST_INIT(server_list); + ISC_LIST_INIT(search_list); + + check_ra = true; + + /* setup dighost callbacks */ + dighost_printmessage = printmessage; + dighost_received = received; + dighost_trying = trying; + dighost_shutdown = query_finished; + + result = isc_app_start(); + check_result(result, "isc_app_start"); + + setup_libs(); + progname = argv[0]; + + setup_system(false, false); + parse_args(argc, argv); + if (keyfile[0] != 0) { + setup_file_key(); + } else if (keysecret[0] != 0) { + setup_text_key(); + } + if (domainopt[0] != '\0') { + set_search_domain(domainopt); + } + if (in_use) { + result = isc_app_onrun(mctx, global_task, onrun_callback, NULL); + } else { + result = isc_app_onrun(mctx, global_task, getinput, NULL); + } + check_result(result, "isc_app_onrun"); + in_use = !in_use; + + (void)isc_app_run(); + + puts(""); + debug("done, and starting to shut down"); + if (global_event != NULL) { + isc_event_free(&global_event); + } + cancel_all(); + destroy_libs(); + isc_app_finish(); + + return (query_error | print_error); +} diff --git a/bin/dig/nslookup.rst b/bin/dig/nslookup.rst new file mode 100644 index 0000000..2b92aa7 --- /dev/null +++ b/bin/dig/nslookup.rst @@ -0,0 +1,208 @@ +.. Copyright (C) Internet Systems Consortium, Inc. ("ISC") +.. +.. SPDX-License-Identifier: MPL-2.0 +.. +.. This Source Code Form is subject to the terms of the Mozilla Public +.. License, v. 2.0. If a copy of the MPL was not distributed with this +.. file, you can obtain one at https://mozilla.org/MPL/2.0/. +.. +.. See the COPYRIGHT file distributed with this work for additional +.. information regarding copyright ownership. + +.. highlight: console + +.. iscman:: nslookup +.. program:: nslookup +.. _man_nslookup: + +nslookup - query Internet name servers interactively +---------------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`nslookup` [-option] [name | -] [server] + +Description +~~~~~~~~~~~ + +:program:`nslookup` is a program to query Internet domain name servers. +:program:`nslookup` has two modes: interactive and non-interactive. Interactive +mode allows the user to query name servers for information about various +hosts and domains or to print a list of hosts in a domain. +Non-interactive mode prints just the name and requested +information for a host or domain. + +Arguments +~~~~~~~~~ + +Interactive mode is entered in the following cases: + +a. when no arguments are given (the default name server is used); + +b. when the first argument is a hyphen (-) and the second argument is + the host name or Internet address of a name server. + +Non-interactive mode is used when the name or Internet address of the +host to be looked up is given as the first argument. The optional second +argument specifies the host name or address of a name server. + +Options can also be specified on the command line if they precede the +arguments and are prefixed with a hyphen. For example, to change the +default query type to host information, with an initial timeout of 10 +seconds, type: + +:: + + nslookup -query=hinfo -timeout=10 + +The ``-version`` option causes :program:`nslookup` to print the version number +and immediately exit. + +Interactive Commands +~~~~~~~~~~~~~~~~~~~~ + +``host [server]`` + This command looks up information for :iscman:`host` using the current default server or + using ``server``, if specified. If :iscman:`host` is an Internet address and the + query type is A or PTR, the name of the host is returned. If :iscman:`host` is + a name and does not have a trailing period (``.``), the search list is used + to qualify the name. + + To look up a host not in the current domain, append a period to the + name. + +``server domain`` | ``lserver domain`` + These commands change the default server to ``domain``; ``lserver`` uses the initial + server to look up information about ``domain``, while ``server`` uses the + current default server. If an authoritative answer cannot be found, + the names of servers that might have the answer are returned. + +``root`` + This command is not implemented. + +``finger`` + This command is not implemented. + +``ls`` + This command is not implemented. + +``view`` + This command is not implemented. + +``help`` + This command is not implemented. + +``?`` + This command is not implemented. + +``exit`` + This command exits the program. + +``set keyword[=value]`` + This command is used to change state information that affects the + lookups. Valid keywords are: + + ``all`` + This keyword prints the current values of the frequently used options to + ``set``. Information about the current default server and host is + also printed. + + ``class=value`` + This keyword changes the query class to one of: + + ``IN`` + the Internet class + + ``CH`` + the Chaos class + + ``HS`` + the Hesiod class + + ``ANY`` + wildcard + + The class specifies the protocol group of the information. The default + is ``IN``; the abbreviation for this keyword is ``cl``. + + ``nodebug`` + This keyword turns on or off the display of the full response packet, and any + intermediate response packets, when searching. The default for this keyword is + ``nodebug``; the abbreviation for this keyword is ``[no]deb``. + + ``nod2`` + This keyword turns debugging mode on or off. This displays more about what + nslookup is doing. The default is ``nod2``. + + ``domain=name`` + This keyword sets the search list to ``name``. + + ``nosearch`` + If the lookup request contains at least one period, but does not end + with a trailing period, this keyword appends the domain names in the domain + search list to the request until an answer is received. The default is ``search``. + + ``port=value`` + This keyword changes the default TCP/UDP name server port to ``value`` from + its default, port 53. The abbreviation for this keyword is ``po``. + + ``querytype=value`` | ``type=value`` + This keyword changes the type of the information query to ``value``. The + defaults are A and then AAAA; the abbreviations for these keywords are + ``q`` and ``ty``. + + Please note that it is only possible to specify one query type. Only the default + behavior looks up both when an alternative is not specified. + + ``norecurse`` + This keyword tells the name server to query other servers if it does not have + the information. The default is ``recurse``; the abbreviation for this + keyword is ``[no]rec``. + + ``ndots=number`` + This keyword sets the number of dots (label separators) in a domain that + disables searching. Absolute names always stop searching. + + ``retry=number`` + This keyword sets the number of retries to ``number``. + + ``timeout=number`` + This keyword changes the initial timeout interval to wait for a reply to + ``number``, in seconds. + + ``novc`` + This keyword indicates that a virtual circuit should always be used when sending requests to the server. + ``novc`` is the default. + + ``nofail`` + This keyword tries the next nameserver if a nameserver responds with SERVFAIL or + a referral (nofail), or terminates the query (fail) on such a response. The + default is ``nofail``. + +Return Values +~~~~~~~~~~~~~ + +:program:`nslookup` returns with an exit status of 1 if any query failed, and 0 +otherwise. + +IDN Support +~~~~~~~~~~~ + +If :program:`nslookup` has been built with IDN (internationalized domain name) +support, it can accept and display non-ASCII domain names. :program:`nslookup` +appropriately converts character encoding of a domain name before sending +a request to a DNS server or displaying a reply from the server. +To turn off IDN support, define the ``IDN_DISABLE`` +environment variable. IDN support is disabled if the variable is set +when :program:`nslookup` runs, or when the standard output is not a tty. + +Files +~~~~~ + +``/etc/resolv.conf`` + +See Also +~~~~~~~~ + +:iscman:`dig(1) <dig>`, :iscman:`host(1) <host>`, :iscman:`named(8) <named>`. diff --git a/bin/dig/readline.h b/bin/dig/readline.h new file mode 100644 index 0000000..68a38c3 --- /dev/null +++ b/bin/dig/readline.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* + * A little wrapper around readline(), add_history() and free() to make using + * the readline code simpler. + */ + +#if defined(HAVE_READLINE_LIBEDIT) +#include <editline/readline.h> +#elif defined(HAVE_READLINE_EDITLINE) +#include <editline.h> +#elif defined(HAVE_READLINE_READLINE) +/* Prevent deprecated functions being declared. */ +#define _FUNCTION_DEF 1 +/* Ensure rl_message() gets prototype. */ +#define USE_VARARGS 1 +#define PREFER_STDARG 1 +#include <readline/history.h> +#include <readline/readline.h> +#endif + +#if !defined(HAVE_READLINE_LIBEDIT) && !defined(HAVE_READLINE_EDITLINE) && \ + !defined(HAVE_READLINE_READLINE) + +#include <stdio.h> +#include <stdlib.h> + +#define RL_MAXCMD (128 * 1024) + +static inline char * +readline(const char *prompt) { + char *line, *buf = malloc(RL_MAXCMD); + fprintf(stdout, "%s", prompt); + fflush(stdout); + line = fgets(buf, RL_MAXCMD, stdin); + if (line == NULL) { + free(buf); + return (NULL); + } + return (buf); +}; + +#define add_history(line) + +#endif |