From 0ebc5605a048d6ffde2930ac769215f488125e60 Mon Sep 17 00:00:00 2001 From: webbuilder_pel7ppc64lebuilder0 Date: Sat, 2 Mar 2019 22:43:31 +0100 Subject: [PATCH] bind package update Signed-off-by: webbuilder_pel7ppc64lebuilder0 --- SOURCES/bind-9.5-sdb.patch | 31 +- SOURCES/bind99-CVE-2018-5740.patch | 53 + SOURCES/bind99-fips-tests.patch | 1978 +++++++ SOURCES/bind99-fips.patch | 1396 +++++ SOURCES/bind99-nta.patch | 8290 ++++++++++++++++++++++++++++ SOURCES/bind99-rh1510008-2.patch | 232 + SOURCES/bind99-rh1510008.patch | 6053 ++++++++++++++++++++ SOURCES/bind99-rh1549130-2.patch | 54 + SOURCES/bind99-rh1549130.patch | 183 + SOURCES/bind99-rh1647539.patch | 28 + SOURCES/named.conf.sample | 2 + SPECS/bind.spec | 117 +- 12 files changed, 18389 insertions(+), 28 deletions(-) create mode 100644 SOURCES/bind99-CVE-2018-5740.patch create mode 100644 SOURCES/bind99-fips-tests.patch create mode 100644 SOURCES/bind99-fips.patch create mode 100644 SOURCES/bind99-nta.patch create mode 100644 SOURCES/bind99-rh1510008-2.patch create mode 100644 SOURCES/bind99-rh1510008.patch create mode 100644 SOURCES/bind99-rh1549130-2.patch create mode 100644 SOURCES/bind99-rh1549130.patch create mode 100644 SOURCES/bind99-rh1647539.patch diff --git a/SOURCES/bind-9.5-sdb.patch b/SOURCES/bind-9.5-sdb.patch index 6af9818..3abf457 100644 --- a/SOURCES/bind-9.5-sdb.patch +++ b/SOURCES/bind-9.5-sdb.patch @@ -14,10 +14,10 @@ index 187ec23..e6179e7 100644 @BIND9_MAKE_RULES@ diff --git a/bin/named-sdb/Makefile.in b/bin/named-sdb/Makefile.in -index bc5be2a..71324d9 100644 +index 5ba3f56..1868418 100644 --- a/bin/named-sdb/Makefile.in +++ b/bin/named-sdb/Makefile.in -@@ -34,10 +34,10 @@ top_srcdir = @top_srcdir@ +@@ -32,10 +32,10 @@ top_srcdir = @top_srcdir@ # # Add database drivers here. # @@ -31,7 +31,7 @@ index bc5be2a..71324d9 100644 DLZ_DRIVER_DIR = ${top_srcdir}/contrib/dlz/drivers -@@ -83,7 +83,7 @@ NOSYMLIBS = ${LWRESLIBS} ${DNSLIBS} ${BIND9LIBS} \ +@@ -81,7 +81,7 @@ NOSYMLIBS = ${LWRESLIBS} ${DNSLIBS} ${BIND9LIBS} \ SUBDIRS = unix @@ -40,7 +40,7 @@ index bc5be2a..71324d9 100644 GEOIPLINKOBJS = geoip.@O@ -@@ -146,7 +146,7 @@ config.@O@: config.c bind.keys.h +@@ -145,7 +145,7 @@ config.@O@: config.c bind.keys.h -DNS_SYSCONFDIR=\"${sysconfdir}\" \ -c ${srcdir}/config.c @@ -49,7 +49,7 @@ index bc5be2a..71324d9 100644 export MAKE_SYMTABLE="yes"; \ export BASEOBJS="${OBJS} ${UOBJS}"; \ ${FINALBUILDCMD} -@@ -177,15 +177,9 @@ statschannel.@O@: bind9.xsl.h bind9.ver3.xsl.h +@@ -176,15 +176,9 @@ statschannel.@O@: bind9.xsl.h bind9.ver3.xsl.h installdirs: $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${sbindir} @@ -69,7 +69,7 @@ index bc5be2a..71324d9 100644 @DLZ_DRIVER_RULES@ diff --git a/bin/named-sdb/main.c b/bin/named-sdb/main.c -index a00687f..4fba625 100644 +index d26783f..75691ed 100644 --- a/bin/named-sdb/main.c +++ b/bin/named-sdb/main.c @@ -86,6 +86,9 @@ @@ -163,10 +163,10 @@ index a00687f..4fba625 100644 ISC_LOG_NOTICE, "exiting"); ns_log_shutdown(); diff --git a/bin/named/Makefile.in b/bin/named/Makefile.in -index bc5be2a..3c69c9b 100644 +index 5ba3f56..4beeaf0 100644 --- a/bin/named/Makefile.in +++ b/bin/named/Makefile.in -@@ -51,7 +51,7 @@ CINCLUDES = -I${srcdir}/include -I${srcdir}/unix/include -I. \ +@@ -49,7 +49,7 @@ CINCLUDES = -I${srcdir}/include -I${srcdir}/unix/include -I. \ ${ISCCFG_INCLUDES} ${ISCCC_INCLUDES} ${ISC_INCLUDES} \ ${DLZDRIVER_INCLUDES} ${DBDRIVER_INCLUDES} @DST_OPENSSL_INC@ @@ -175,7 +175,7 @@ index bc5be2a..3c69c9b 100644 CWARNINGS = -@@ -75,11 +75,11 @@ DEPLIBS = ${LWRESDEPLIBS} ${DNSDEPLIBS} ${BIND9DEPLIBS} \ +@@ -73,11 +73,11 @@ DEPLIBS = ${LWRESDEPLIBS} ${DNSDEPLIBS} ${BIND9DEPLIBS} \ LIBS = ${LWRESLIBS} ${DNSLIBS} ${BIND9LIBS} \ ${ISCCFGLIBS} ${ISCCCLIBS} ${ISCLIBS} \ @@ -189,7 +189,7 @@ index bc5be2a..3c69c9b 100644 SUBDIRS = unix -@@ -94,8 +94,7 @@ OBJS = builtin.@O@ client.@O@ config.@O@ control.@O@ \ +@@ -92,8 +92,7 @@ OBJS = builtin.@O@ client.@O@ config.@O@ control.@O@ \ tkeyconf.@O@ tsigconf.@O@ update.@O@ xfrout.@O@ \ zoneconf.@O@ \ lwaddr.@O@ lwresd.@O@ lwdclient.@O@ lwderror.@O@ lwdgabn.@O@ \ @@ -199,7 +199,7 @@ index bc5be2a..3c69c9b 100644 UOBJS = unix/os.@O@ unix/dlz_dlopen_driver.@O@ -@@ -110,8 +109,7 @@ SRCS = builtin.c client.c config.c control.c \ +@@ -108,8 +107,7 @@ SRCS = builtin.c client.c config.c control.c \ tkeyconf.c tsigconf.c update.c xfrout.c \ zoneconf.c \ lwaddr.c lwresd.c lwdclient.c lwderror.c lwdgabn.c \ @@ -209,7 +209,7 @@ index bc5be2a..3c69c9b 100644 MANPAGES = named.8 lwresd.8 named.conf.5 -@@ -187,7 +185,5 @@ install:: named@EXEEXT@ lwresd@EXEEXT@ installdirs +@@ -186,7 +184,5 @@ install:: named@EXEEXT@ lwresd@EXEEXT@ installdirs ${INSTALL_DATA} ${srcdir}/lwresd.8 ${DESTDIR}${mandir}/man8 ${INSTALL_DATA} ${srcdir}/named.conf.5 ${DESTDIR}${mandir}/man5 @@ -218,10 +218,10 @@ index bc5be2a..3c69c9b 100644 named-symtbl.@O@: named-symtbl.c ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c named-symtbl.c diff --git a/configure.in b/configure.in -index 9bb9a2a..d72093f 100644 +index d72f635..1aae803 100644 --- a/configure.in +++ b/configure.in -@@ -4018,12 +4018,15 @@ AC_CONFIG_FILES([ +@@ -4050,6 +4050,8 @@ AC_CONFIG_FILES([ bin/named-pkcs11/Makefile bin/named-pkcs11/unix/Makefile bin/named/unix/Makefile @@ -230,8 +230,9 @@ index 9bb9a2a..d72093f 100644 bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile - bin/python/dnssec-checkds.py +@@ -4060,6 +4062,7 @@ AC_CONFIG_FILES([ bin/python/dnssec-coverage.py + bin/python/dnssec-keymgr.py bin/rndc/Makefile + bin/sdb_tools/Makefile bin/tests/Makefile diff --git a/SOURCES/bind99-CVE-2018-5740.patch b/SOURCES/bind99-CVE-2018-5740.patch new file mode 100644 index 0000000..90e858a --- /dev/null +++ b/SOURCES/bind99-CVE-2018-5740.patch @@ -0,0 +1,53 @@ +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 252a02f..bfffb8a 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -5957,6 +5957,7 @@ is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname, + unsigned int nlabels; + dns_fixedname_t fixed; + dns_name_t prefix; ++ int order; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == dns_rdatatype_cname || +@@ -5979,18 +5980,26 @@ is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname, + tname = &cname.cname; + break; + case dns_rdatatype_dname: ++ if (dns_name_fullcompare(qname, rname, &order, &nlabels) != ++ dns_namereln_subdomain) ++ { ++ return (ISC_TRUE); ++ } + result = dns_rdata_tostruct(&rdata, &dname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_init(&prefix, NULL); + dns_fixedname_init(&fixed); + tname = dns_fixedname_name(&fixed); +- nlabels = dns_name_countlabels(qname) - +- dns_name_countlabels(rname); ++ nlabels = dns_name_countlabels(rname); + dns_name_split(qname, nlabels, &prefix, NULL); + result = dns_name_concatenate(&prefix, &dname.dname, tname, + NULL); +- if (result == DNS_R_NAMETOOLONG) ++ if (result == DNS_R_NAMETOOLONG) { ++ if (chainingp != NULL) { ++ *chainingp = ISC_TRUE; ++ } + return (ISC_TRUE); ++ } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + break; + default: +@@ -6719,7 +6728,9 @@ answer_response(fetchctx_t *fctx) { + } + if ((ardataset->type == dns_rdatatype_cname || + ardataset->type == dns_rdatatype_dname) && +- !is_answertarget_allowed(fctx, qname, aname, ardataset, ++ type != ardataset->type && ++ type != dns_rdatatype_any && ++ !is_answertarget_allowed(fctx, qname, aname, ardataset, + NULL)) + { + return (DNS_R_SERVFAIL); diff --git a/SOURCES/bind99-fips-tests.patch b/SOURCES/bind99-fips-tests.patch new file mode 100644 index 0000000..7a10f23 --- /dev/null +++ b/SOURCES/bind99-fips-tests.patch @@ -0,0 +1,1978 @@ +From 4a1bbbbe8ff1951dba9f5d6a69c42dcf274877d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Fri, 22 Jun 2018 14:05:43 +0200 +Subject: [PATCH 2/2] Squashed commit of the following: +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +commit d1de64d54126a9662b0f709adf1467f1ca3caa50 +Author: Petr Menšík +Date: Wed Jun 20 19:15:31 2018 +0200 + + Fix allow_query tests with hmac-256 keys + +commit 854606588f53ee403364461ad29dc1cfd29525a0 +Author: Petr Menšík +Date: Wed Mar 7 15:54:11 2018 +0100 + + Increase bitsize of DSA key to pass FIPS 140-2 mode. + +commit 98dae21d1f863fa26c125271392288730da52842 +Author: Petr Menšík +Date: Thu Apr 19 18:28:09 2018 +0200 + + Fix nsupdate, tsig and rndc tests. + Do not use md5 by default for rndc, skip gracefully md5 if not available. + + Rename md5 keys to rndc*.conf, to pass util/merge_copyrights change. + Fix dynamic ports merge. + +commit 0ec5e2522aa32931cda5abd07a757035078840ea +Author: Petr Menšík +Date: Wed Jun 20 19:34:20 2018 +0200 + + Use testcrypto for crypto detection. Generate random data per test into test directory. + +commit 0ca3c85fa6450ae8b347fa5585d0134ebe41682c +Author: Petr Menšík +Date: Wed Mar 7 13:21:00 2018 +0100 + + Add md5 availability detection to featuretest + +commit c1b104ccf66a1ec37e941e303a56675c7dcccbaa +Author: Petr Menšík +Date: Mon Jan 22 14:12:37 2018 +0100 + + Update system tests to detect MD5 disabled at runtime + +commit 743d24de87b6f022b99d14d3109958660b9ee07b +Author: Petr Menšík +Date: Fri Feb 23 21:57:11 2018 +0100 + + Make testcrypto FIPS compatible + + (cherry picked from commit 0e15cc7012c537a5d683c35534d33d23fcc4d942) + +commit 325dc1f4f37dc4b7133dd39d7780c10d183e4808 +Author: Evan Hunt +Date: Mon Oct 31 23:01:38 2016 -0700 + + [v9_9] 4496. [func] dig: add +idnout to control whether labels are + display in punycode or not. Requires idn support + to be enabled at compile time. [RT #43398] + + (cherry picked from commit 42470b0b87da24b18e0ff6ce78f3143e89df6d31) + (cherry picked from commit 6552f33198438390724c5823b8dbcf477ec9638c) + (cherry picked from commit 7aec46a5ef4074c3957d525643188257c7575841) + + Skip IDN part and import only feature-test from system tests + + (cherry picked from commit 61a01f48604ff6f5f84b64a5aaee722ebae8fadc) + +commit d435ac7bcf72117e75e534c23fca1852f4140eb8 +Author: Petr Menšík +Date: Wed Mar 7 10:44:23 2018 +0100 + + Use hmac-sha256 instead of default hmac-md5 for allow-query. + Do not use hmac-md5 in tests by default, make it pass with MD5 disabled. + +commit 067ca65156a9fadb191b7c9073904a43f57f1896 +Author: Evan Hunt +Date: Thu Feb 6 19:48:49 2014 -0800 + + [v9_9] add testcrypto.sh + + (cherry picked from commit e9a2673e85173d93be168f561c5c77184d4e839d) + +commit 3fd542379fa381b54381e07d6625ce53f9f9b1f0 +Author: Petr Menšík +Date: Thu Jun 21 12:00:35 2018 +0200 + + Revert "4450. [port] Provide more nuanced HSM support which better matches" + + This reverts commit f3b4d031c1f714ff6e862670663aa5a18650951e. + + Revert PK11_MD5_DISABLED also from remaining files. Keep documentation + changes. + +commit f90934f734796595135cdd7a5008555a615dfe8e +Author: Petr Menšík +Date: Wed Jun 20 19:31:19 2018 +0200 + + Fix rndc-confgen default algorithm, report true algorithm in usage. + +commit dd53212c12c6943a21a3c24d60995edd19e1d9f7 +Author: Petr Menšík +Date: Fri Feb 23 21:21:30 2018 +0100 + + Cleanup only if initialization was successful + +commit f163ea51c46bb22bf264a1ac983e2027e43845fa +Author: Petr Menšík +Date: Mon Feb 5 12:19:28 2018 +0100 + + Ensure dst backend is initialized first even before hmac algorithms. + +commit 58751b60bd39168b7c8f817ede70473842432081 +Author: Petr Menšík +Date: Mon Feb 5 12:17:54 2018 +0100 + + Skip initialization of MD5 based algorithms if not available. + +commit 0572b98430d3c80f4a0b0c592b1e3bf7fde9b768 +Author: Petr Menšík +Date: Mon Feb 5 10:21:27 2018 +0100 + + Change secalgs skipping to be more safe + +commit 994f497a032930fce1370d507a265fbb293c66f4 +Author: Petr Menšík +Date: Wed Jan 31 18:26:11 2018 +0100 + + Skip MD5 algorithm also in case of NULL name + +commit abd82fbd2507c4b8f20e1ade202fd66d224fd646 +Author: Petr Menšík +Date: Wed Jan 31 16:54:29 2018 +0100 + + Revert part of commit 1b5c641416eb6de7fc232fc89d31a40a4d439f3d related + to SHA1. + +commit b3c832d53a14a0779f598869bb99685c8e4b2bc0 +Author: Petr Menšík +Date: Wed Jan 31 11:38:12 2018 +0100 + + Make MD5 behave like unknown algorithm in TSIG. + +commit a64a3d6962ee93d6f8699b29bd6507dba0c244ed +Author: Petr Menšík +Date: Tue Nov 28 20:14:37 2017 +0100 + + Select token with most supported functions, instead of demanding it must support all functions + + Initialize PKCS#11 always until successfully initialized + +commit db118c6368668099ea1b6e75860cc12e178afa3b +Author: Petr Menšík +Date: Mon Jan 22 16:17:44 2018 +0100 + + Handle MD5 unavailability from DST + +commit 8f8824dca2f5b4d5a3a176d31ac3ee612321c4e3 +Author: Petr Menšík +Date: Mon Jan 22 14:11:16 2018 +0100 + + Check runtime flag from library and applications, fail gracefully. + +commit bd431384af7dcde8827e670c8749517ad677a967 +Author: Petr Menšík +Date: Mon Jan 22 08:39:08 2018 +0100 + + Modify libraries to use isc_md5_available() if PK11_MD5_DISABLE is not + defined. + TODO: pk11.c should accept slot without MD5 support. + +commit 160b13979ef3d0e92d2dd52d0987a3ec979be6cf +Author: Petr Menšík +Date: Mon Jan 22 07:21:04 2018 +0100 + + Add runtime detection whether MD5 is useable. + +commit 23b27ce0f2ad496c331ae40349cc1074a1b11804 +Author: Mark Andrews +Date: Fri Aug 19 08:25:54 2016 +1000 + + 4450. [port] Provide more nuanced HSM support which better matches + the specific PKCS11 providers capabilities. [RT #42458] + + (cherry picked from commit 8ee6f289d87851a5b898b24a64587f0e6bc225bc) +--- + bin/tests/system/Makefile.in | 25 +++- + bin/tests/system/acl/ns2/named1.conf | 4 +- + bin/tests/system/acl/ns2/named2.conf | 4 +- + bin/tests/system/acl/ns2/named3.conf | 6 +- + bin/tests/system/acl/ns2/named4.conf | 4 +- + bin/tests/system/acl/ns2/named5.conf | 4 +- + bin/tests/system/acl/tests.sh | 32 +++--- + bin/tests/system/allow_query/ns2/named10.conf | 2 +- + bin/tests/system/allow_query/ns2/named11.conf | 4 +- + bin/tests/system/allow_query/ns2/named12.conf | 2 +- + bin/tests/system/allow_query/ns2/named30.conf | 2 +- + bin/tests/system/allow_query/ns2/named31.conf | 4 +- + bin/tests/system/allow_query/ns2/named32.conf | 2 +- + bin/tests/system/allow_query/ns2/named40.conf | 4 +- + bin/tests/system/allow_query/tests.sh | 18 +-- + bin/tests/system/checkconf/bad-tsig.conf | 2 +- + bin/tests/system/conf.sh.in | 6 +- + bin/tests/system/digdelv/ns2/example.db | 15 ++- + bin/tests/system/digdelv/tests.sh | 4 +- + bin/tests/system/dlv/ns1/sign.sh | 4 +- + bin/tests/system/dlv/ns2/sign.sh | 4 +- + bin/tests/system/dlv/ns3/sign.sh | 68 +++++------ + bin/tests/system/dlv/ns6/sign.sh | 64 +++++------ + bin/tests/system/dnssec/ns2/sign.sh | 8 +- + bin/tests/system/dnssec/prereq.sh | 11 +- + bin/tests/system/feature-test.c | 159 ++++++++++++++++++++++++++ + bin/tests/system/filter-aaaa/ns1/sign.sh | 4 +- + bin/tests/system/filter-aaaa/ns4/sign.sh | 4 +- + bin/tests/system/keymgr/prereq.sh | 15 +-- + bin/tests/system/nsupdate/ns1/named.conf | 2 +- + bin/tests/system/nsupdate/ns2/named.conf | 2 +- + bin/tests/system/nsupdate/setup.sh | 7 +- + bin/tests/system/nsupdate/tests.sh | 11 +- + bin/tests/system/rndc/setup.sh | 4 +- + bin/tests/system/rndc/tests.sh | 22 ++-- + bin/tests/system/testcrypto.sh | 71 ++++++++++++ + bin/tests/system/tkey/keycreate.c | 3 + + bin/tests/system/tkey/keydelete.c | 18 ++- + bin/tests/system/tkey/prereq.sh | 11 +- + bin/tests/system/tsig/clean.sh | 1 + + bin/tests/system/tsig/ns1/named.conf | 12 +- + bin/tests/system/tsig/ns1/rndc5.conf.in | 22 ++++ + bin/tests/system/tsig/setup.sh | 25 ++++ + bin/tests/system/tsig/tests.sh | 75 +++++++----- + bin/tests/system/tsiggss/setup.sh | 2 +- + bin/tests/system/upforwd/ns1/named.conf | 2 +- + bin/tests/system/upforwd/tests.sh | 2 +- + 47 files changed, 547 insertions(+), 230 deletions(-) + create mode 100644 bin/tests/system/feature-test.c + create mode 100644 bin/tests/system/testcrypto.sh + create mode 100644 bin/tests/system/tsig/ns1/rndc5.conf.in + create mode 100644 bin/tests/system/tsig/setup.sh + +diff --git a/bin/tests/system/Makefile.in b/bin/tests/system/Makefile.in +index 0c7fdffd01..afee71b2bb 100644 +--- a/bin/tests/system/Makefile.in ++++ b/bin/tests/system/Makefile.in +@@ -23,10 +23,31 @@ top_srcdir = @top_srcdir@ + + SUBDIRS = dlzexternal dyndb filter-aaaa geoip lwresd rpz rrl \ + rsabigexponent tkey tsiggss +-TARGETS = ++CINCLUDES = ${ISC_INCLUDES} ${DNS_INCLUDES} ++ ++CDEFINES = @USE_GSSAPI@ ++CWARNINGS = ++ ++DNSLIBS = ++ISCLIBS = ../../../lib/isc/libisc.@A@ ++ ++DNSDEPLIBS = ++ISCDEPLIBS = ++ ++DEPLIBS = ++ ++LIBS = @LIBS@ ++ ++OBJS = feature-test.@O@ ++SRCS = feature-test.c ++ ++TARGETS = feature-test@EXEEXT@ + + @BIND9_MAKE_RULES@ + ++feature-test@EXEEXT@: feature-test.@O@ ++ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ feature-test.@O@ ${ISCLIBS} ${LIBS} ++ + # Running the scripts below is bypassed when a separate + # build directory is used. + +@@ -38,6 +59,8 @@ test: subdirs + testclean clean distclean:: + if test -f ./cleanall.sh; then sh ./cleanall.sh; fi + rm -f systests.output ++ rm -f ${TARGETS} ++ rm -f ${OBJS} + + distclean:: + rm -f conf.sh +diff --git a/bin/tests/system/acl/ns2/named1.conf b/bin/tests/system/acl/ns2/named1.conf +index b70d1dd761..9037a15c9d 100644 +--- a/bin/tests/system/acl/ns2/named1.conf ++++ b/bin/tests/system/acl/ns2/named1.conf +@@ -35,12 +35,12 @@ options { + include "../../common/controls.conf"; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/acl/ns2/named2.conf b/bin/tests/system/acl/ns2/named2.conf +index bcd7e0df19..648c5fdbdc 100644 +--- a/bin/tests/system/acl/ns2/named2.conf ++++ b/bin/tests/system/acl/ns2/named2.conf +@@ -35,12 +35,12 @@ options { + include "../../common/controls.conf"; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/acl/ns2/named3.conf b/bin/tests/system/acl/ns2/named3.conf +index ea2cbcb44a..546ecf6af4 100644 +--- a/bin/tests/system/acl/ns2/named3.conf ++++ b/bin/tests/system/acl/ns2/named3.conf +@@ -35,17 +35,17 @@ options { + include "../../common/controls.conf"; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key three { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/acl/ns2/named4.conf b/bin/tests/system/acl/ns2/named4.conf +index 99edf7ebe5..4c84d0f163 100644 +--- a/bin/tests/system/acl/ns2/named4.conf ++++ b/bin/tests/system/acl/ns2/named4.conf +@@ -35,12 +35,12 @@ options { + include "../../common/controls.conf"; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/acl/ns2/named5.conf b/bin/tests/system/acl/ns2/named5.conf +index d17e1cf7b7..52ae56300e 100644 +--- a/bin/tests/system/acl/ns2/named5.conf ++++ b/bin/tests/system/acl/ns2/named5.conf +@@ -36,12 +36,12 @@ options { + include "../../common/controls.conf"; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/acl/tests.sh b/bin/tests/system/acl/tests.sh +index 7207c5a1d3..753f9f6743 100644 +--- a/bin/tests/system/acl/tests.sh ++++ b/bin/tests/system/acl/tests.sh +@@ -28,13 +28,13 @@ echo "I:testing basic ACL processing" + # key "one" should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + # any other key should be fine + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + cp -f ns2/named2.conf ns2/named.conf +@@ -44,18 +44,18 @@ sleep 5 + # prefix 10/8 should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + # any other address should work, as long as it sends key "one" + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 127.0.0.1 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 127.0.0.1 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 127.0.0.1 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 127.0.0.1 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + echo "I:testing nested ACL processing" +@@ -67,31 +67,31 @@ sleep 5 + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.2 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.2 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.2 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.2 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # but only one or the other should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 127.0.0.1 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 127.0.0.1 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + t=`expr $t + 1` +@@ -102,7 +102,7 @@ grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $tt failed" ; status=1; } + # and other values? right out + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 127.0.0.1 axfr -y three:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 127.0.0.1 axfr -y hmac-sha256:three:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + # now we only allow 10.53.0.1 *and* key one, or 10.53.0.2 *and* key two +@@ -113,31 +113,31 @@ sleep 5 + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.2 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.2 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # should succeed + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 && { echo "I:test $t failed" ; status=1; } + + # should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.2 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.2 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + # should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.1 axfr -y two:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.1 axfr -y hmac-sha256:two:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + # should fail + t=`expr $t + 1` + $DIG $DIGOPTS tsigzone. \ +- @10.53.0.2 -b 10.53.0.3 axfr -y one:1234abcd8765 -p 5300 > dig.out ++ @10.53.0.2 -b 10.53.0.3 axfr -y hmac-sha256:one:1234abcd8765 -p 5300 > dig.out + grep "^;" dig.out > /dev/null 2>&1 || { echo "I:test $t failed" ; status=1; } + + echo "I:testing allow-query-on ACL processing" +diff --git a/bin/tests/system/allow_query/ns2/named10.conf b/bin/tests/system/allow_query/ns2/named10.conf +index 17786e6f87..918b185671 100644 +--- a/bin/tests/system/allow_query/ns2/named10.conf ++++ b/bin/tests/system/allow_query/ns2/named10.conf +@@ -20,7 +20,7 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named11.conf b/bin/tests/system/allow_query/ns2/named11.conf +index 3d225bd9a2..2ccd8d4b3f 100644 +--- a/bin/tests/system/allow_query/ns2/named11.conf ++++ b/bin/tests/system/allow_query/ns2/named11.conf +@@ -20,12 +20,12 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234efgh8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named12.conf b/bin/tests/system/allow_query/ns2/named12.conf +index e5e64184c8..fd322bb709 100644 +--- a/bin/tests/system/allow_query/ns2/named12.conf ++++ b/bin/tests/system/allow_query/ns2/named12.conf +@@ -19,7 +19,7 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named30.conf b/bin/tests/system/allow_query/ns2/named30.conf +index 9182f21af3..585436f1d9 100644 +--- a/bin/tests/system/allow_query/ns2/named30.conf ++++ b/bin/tests/system/allow_query/ns2/named30.conf +@@ -20,7 +20,7 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named31.conf b/bin/tests/system/allow_query/ns2/named31.conf +index 19efdf397e..d7f0e80616 100644 +--- a/bin/tests/system/allow_query/ns2/named31.conf ++++ b/bin/tests/system/allow_query/ns2/named31.conf +@@ -20,12 +20,12 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234efgh8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named32.conf b/bin/tests/system/allow_query/ns2/named32.conf +index 3c207f3422..4d66a3812d 100644 +--- a/bin/tests/system/allow_query/ns2/named32.conf ++++ b/bin/tests/system/allow_query/ns2/named32.conf +@@ -19,7 +19,7 @@ + controls { /* empty */ }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/allow_query/ns2/named40.conf b/bin/tests/system/allow_query/ns2/named40.conf +index cb81c79e5d..c581c5eefd 100644 +--- a/bin/tests/system/allow_query/ns2/named40.conf ++++ b/bin/tests/system/allow_query/ns2/named40.conf +@@ -23,12 +23,12 @@ acl accept { 10.53.0.2; }; + acl badaccept { 10.53.0.1; }; + + key one { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234abcd8765"; + }; + + key two { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "1234efgh8765"; + }; + +diff --git a/bin/tests/system/allow_query/tests.sh b/bin/tests/system/allow_query/tests.sh +index 0592c342d4..c5ef867451 100644 +--- a/bin/tests/system/allow_query/tests.sh ++++ b/bin/tests/system/allow_query/tests.sh +@@ -195,7 +195,7 @@ sleep 5 + + echo "I:test $n: key allowed - query allowed" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: NOERROR' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -209,7 +209,7 @@ sleep 5 + + echo "I:test $n: key not allowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y two:1234efgh8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:two:1234efgh8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -223,7 +223,7 @@ sleep 5 + + echo "I:test $n: key disallowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -366,7 +366,7 @@ sleep 5 + + echo "I:test $n: views key allowed - query allowed" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: NOERROR' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -380,7 +380,7 @@ sleep 5 + + echo "I:test $n: views key not allowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y two:1234efgh8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:two:1234efgh8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -394,7 +394,7 @@ sleep 5 + + echo "I:test $n: views key disallowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.normal.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.normal.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -530,7 +530,7 @@ status=`expr $status + $ret` + n=`expr $n + 1` + echo "I:test $n: zone key allowed - query allowed" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.keyallow.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.keyallow.example a > dig.out.ns2.$n || ret=1 + grep 'status: NOERROR' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.keyallow.example' dig.out.ns2.$n > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -540,7 +540,7 @@ status=`expr $status + $ret` + n=`expr $n + 1` + echo "I:test $n: zone key not allowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y two:1234efgh8765 a.keyallow.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:two:1234efgh8765 a.keyallow.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.keyallow.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +@@ -550,7 +550,7 @@ status=`expr $status + $ret` + n=`expr $n + 1` + echo "I:test $n: zone key disallowed - query refused" + ret=0 +-$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y one:1234abcd8765 a.keydisallow.example a > dig.out.ns2.$n || ret=1 ++$DIG $DIGOPTS @10.53.0.2 -b 10.53.0.2 -y hmac-sha256:one:1234abcd8765 a.keydisallow.example a > dig.out.ns2.$n || ret=1 + grep 'status: REFUSED' dig.out.ns2.$n > /dev/null || ret=1 + grep '^a.keydisallow.example' dig.out.ns2.$n > /dev/null && ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi +diff --git a/bin/tests/system/checkconf/bad-tsig.conf b/bin/tests/system/checkconf/bad-tsig.conf +index 8f0ecf7ea0..0e4718994f 100644 +--- a/bin/tests/system/checkconf/bad-tsig.conf ++++ b/bin/tests/system/checkconf/bad-tsig.conf +@@ -18,7 +18,7 @@ + + /* Bad secret */ + key "badtsig" { +- algorithm hmac-md5; ++ algorithm hmac-sha256; + secret "jEdD+BPKg=="; + }; + +diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in +index 930928b429..420320c737 100644 +--- a/bin/tests/system/conf.sh.in ++++ b/bin/tests/system/conf.sh.in +@@ -56,6 +56,10 @@ JOURNALPRINT=$TOP/bin/tools/named-journalprint + VERIFY=$TOP/bin/dnssec/dnssec-verify + ARPANAME=$TOP/bin/tools/arpaname + SAMPLE=$TOP/lib/export/samples/sample ++GENRANDOM=$TOP/bin/tools/genrandom ++FEATURETEST=$TOP/bin/tests/system/feature-test ++ ++RANDFILE=$TOP/bin/tests/system/random.data + + # The "stress" test is not run by default since it creates enough + # load on the machine to make it unusable to other users. +@@ -89,4 +93,4 @@ fi + + export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ + PERL PYTHON SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ +- JOURNALPRINT ARPANAME SAMPLE ++ JOURNALPRINT ARPANAME SAMPLE FEATURETEST +diff --git a/bin/tests/system/digdelv/ns2/example.db b/bin/tests/system/digdelv/ns2/example.db +index 0a1aa5d615..fd3ed3a045 100644 +--- a/bin/tests/system/digdelv/ns2/example.db ++++ b/bin/tests/system/digdelv/ns2/example.db +@@ -41,10 +41,13 @@ foo SSHFP 2 1 123456789abcdef67890123456789abcdef67890 + ;; + ;; we are not testing DNSSEC behavior, so we don't care about the semantics + ;; of the following records. +-dnskey 300 DNSKEY 256 3 1 ( +- AQPTpWyReB/e9Ii6mVGnakS8hX2zkh/iUYAg +- +Ge4noWROpTWOIBvm76zeJPWs4Zfqa1IsswD +- Ix5Mqeg0zwclz59uecKsKyx5w9IhtZ8plc4R +- b9VIE5x7KNHAYTvTO5d4S8M= +- ) ++dnskey 300 DNSKEY 256 3 8 ( ++ AwEAAaWmCoDpj2K59zcpqnmnQM7IC/XbjS6jIP7uTBR4X7p1bdQJzAeo ++ EnMhnpnxPp0j+20eZm4847DB2U+HuHy79Mvqd3aozTmfBJvzjKs9qyba ++ zY/ZHn6BDYxNJiFfjSS/VJ1KuQPDbpCzhm2hbvT5s9nSOaG0WyRk+d+R ++ qEca11E7ZKkmmNiGlyzMAgfmTTBwgxWBAAhvd9nU1GqD6eQ6Z63hpTc/ ++ KDIHnFTo7pOcZ4z5urIKUMCMcFytedETlEoR5CIWGPdQq2eIEEMfn5ld ++ QqdEZRHVErD9og8aluJ2s767HZb8LzjCfYgBFoT9/n48T75oZLEKtSkG ++ /idCeeQlaLU= ++ ) + +diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh +index a19256cde3..bdfacf9fb4 100644 +--- a/bin/tests/system/digdelv/tests.sh ++++ b/bin/tests/system/digdelv/tests.sh +@@ -59,7 +59,7 @@ if [ -x ${DIG} ] ; then + echo "I:checking dig +rrcomments works for DNSKEY($n)" + ret=0 + $DIG $DIGOPTS +tcp @10.53.0.3 +rrcomments DNSKEY dnskey.example > dig.out.test$n || ret=1 +- grep "; ZSK; alg = RSAMD5 *; key id = 30795" < dig.out.test$n > /dev/null || ret=1 ++ grep "; ZSK; alg = RSASHA256 *; key id = 36895$" < dig.out.test$n > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + +@@ -146,7 +146,7 @@ if [ -n "${DELV}" -a -x "${DELV}" ] ; then + echo "I:checking delv +rrcomments works for DNSKEY($n)" + ret=0 + $DELV $DELVOPTS @10.53.0.3 +rrcomments DNSKEY dnskey.example > delv.out.test$n || ret=1 +- grep "; ZSK; alg = RSAMD5 *; key id = 30795" < delv.out.test$n > /dev/null || ret=1 ++ grep "; ZSK; alg = RSASHA256 *; key id = 36895" < dig.out.test$n > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + +diff --git a/bin/tests/system/dlv/ns1/sign.sh b/bin/tests/system/dlv/ns1/sign.sh +index 9854f5b7ce..cf261c136c 100755 +--- a/bin/tests/system/dlv/ns1/sign.sh ++++ b/bin/tests/system/dlv/ns1/sign.sh +@@ -30,8 +30,8 @@ infile=root.db.in + zonefile=root.db + outfile=root.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/dlv/ns2/sign.sh b/bin/tests/system/dlv/ns2/sign.sh +index edcc8f21d4..4e142b00d8 100755 +--- a/bin/tests/system/dlv/ns2/sign.sh ++++ b/bin/tests/system/dlv/ns2/sign.sh +@@ -31,8 +31,8 @@ zonefile=druz.db + outfile=druz.pre + dlvzone=utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/dlv/ns3/sign.sh b/bin/tests/system/dlv/ns3/sign.sh +index 6bdc2f6cc5..64c5846f7d 100755 +--- a/bin/tests/system/dlv/ns3/sign.sh ++++ b/bin/tests/system/dlv/ns3/sign.sh +@@ -34,8 +34,8 @@ zonefile=child1.utld.db + outfile=child1.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -49,8 +49,8 @@ zonefile=child3.utld.db + outfile=child3.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -64,8 +64,8 @@ zonefile=child4.utld.db + outfile=child4.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -79,8 +79,8 @@ zonefile=child5.utld.db + outfile=child5.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -93,8 +93,8 @@ infile=child.db.in + zonefile=child7.utld.db + outfile=child7.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -107,8 +107,8 @@ infile=child.db.in + zonefile=child8.utld.db + outfile=child8.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -122,8 +122,8 @@ zonefile=child9.utld.db + outfile=child9.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -136,8 +136,8 @@ zonefile=child10.utld.db + outfile=child10.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -151,8 +151,8 @@ outfile=child1.druz.signed + dlvsets="$dlvsets dlvset-$zone" + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -167,8 +167,8 @@ outfile=child3.druz.signed + dlvsets="$dlvsets dlvset-$zone" + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -183,8 +183,8 @@ outfile=child4.druz.signed + dlvsets="$dlvsets dlvset-$zone" + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -199,8 +199,8 @@ outfile=child5.druz.signed + dlvsets="$dlvsets dlvset-$zone" + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -214,8 +214,8 @@ zonefile=child7.druz.db + outfile=child7.druz.signed + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key ../ns6/dsset-grand.$zone >$zonefile + +@@ -228,8 +228,8 @@ infile=child.db.in + zonefile=child8.druz.db + outfile=child8.druz.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -243,8 +243,8 @@ zonefile=child9.druz.db + outfile=child9.druz.signed + dlvsets="$dlvsets dlvset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -258,8 +258,8 @@ outfile=child10.druz.signed + dlvsets="$dlvsets dlvset-$zone" + dssets="$dssets dsset-$zone" + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -272,8 +272,8 @@ infile=dlv.db.in + zonefile=dlv.utld.db + outfile=dlv.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $dlvsets $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/dlv/ns6/sign.sh b/bin/tests/system/dlv/ns6/sign.sh +index 2bc133e5d6..227c1cb69f 100755 +--- a/bin/tests/system/dlv/ns6/sign.sh ++++ b/bin/tests/system/dlv/ns6/sign.sh +@@ -28,8 +28,8 @@ infile=child.db.in + zonefile=grand.child1.utld.db + outfile=grand.child1.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -43,8 +43,8 @@ zonefile=grand.child3.utld.db + outfile=grand.child3.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -58,8 +58,8 @@ zonefile=grand.child4.utld.db + outfile=grand.child4.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -73,8 +73,8 @@ zonefile=grand.child5.utld.db + outfile=grand.child5.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -88,8 +88,8 @@ zonefile=grand.child7.utld.db + outfile=grand.child7.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -103,8 +103,8 @@ zonefile=grand.child8.utld.db + outfile=grand.child8.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -118,8 +118,8 @@ zonefile=grand.child9.utld.db + outfile=grand.child9.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -132,8 +132,8 @@ zonefile=grand.child10.utld.db + outfile=grand.child10.signed + dlvzone=dlv.utld. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -145,8 +145,8 @@ infile=child.db.in + zonefile=grand.child1.druz.db + outfile=grand.child1.druz.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -160,8 +160,8 @@ zonefile=grand.child3.druz.db + outfile=grand.child3.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -175,8 +175,8 @@ zonefile=grand.child4.druz.db + outfile=grand.child4.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -190,8 +190,8 @@ zonefile=grand.child5.druz.db + outfile=grand.child5.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -205,8 +205,8 @@ zonefile=grand.child7.druz.db + outfile=grand.child7.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -220,8 +220,8 @@ zonefile=grand.child8.druz.db + outfile=grand.child8.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -235,8 +235,8 @@ zonefile=grand.child9.druz.db + outfile=grand.child9.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -249,8 +249,8 @@ zonefile=grand.child10.druz.db + outfile=grand.child10.druz.signed + dlvzone=dlv.druz. + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh +index 118b8a6d6b..0c4dcb4b19 100644 +--- a/bin/tests/system/dnssec/ns2/sign.sh ++++ b/bin/tests/system/dnssec/ns2/sign.sh +@@ -38,8 +38,8 @@ do + cp ../ns3/dsset-$subdomain.example. . + done + +-keyname1=`$KEYGEN -q -r $RANDFILE -a DSA -b 768 -n zone $zone` +-keyname2=`$KEYGEN -q -r $RANDFILE -a DSA -b 768 -n zone $zone` ++keyname1=`$KEYGEN -q -r $RANDFILE -a DSA -b 1024 -n zone $zone` ++keyname2=`$KEYGEN -q -r $RANDFILE -a DSA -b 1024 -n zone $zone` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +@@ -98,7 +98,7 @@ privzone=private.secure.example. + privinfile=private.secure.example.db.in + privzonefile=private.secure.example.db + +-privkeyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 768 -n zone $privzone` ++privkeyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 1024 -n zone $privzone` + + cat $privinfile $privkeyname.key >$privzonefile + +@@ -111,7 +111,7 @@ dlvzone=dlv. + dlvinfile=dlv.db.in + dlvzonefile=dlv.db + +-dlvkeyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 768 -n zone $dlvzone` ++dlvkeyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 1024 -n zone $dlvzone` + + cat $dlvinfile $dlvkeyname.key dlvset-$privzone > $dlvzonefile + +diff --git a/bin/tests/system/dnssec/prereq.sh b/bin/tests/system/dnssec/prereq.sh +index 113e372c28..84630d8abc 100644 +--- a/bin/tests/system/dnssec/prereq.sh ++++ b/bin/tests/system/dnssec/prereq.sh +@@ -17,13 +17,4 @@ + + # $Id: prereq.sh,v 1.13 2009/10/28 00:27:10 marka Exp $ + +-../../../tools/genrandom 400 random.data +- +-if $KEYGEN -q -a RSAMD5 -b 512 -n zone -r random.data foo > /dev/null 2>&1 +-then +- rm -f Kfoo* +-else +- echo "I:This test requires cryptography" >&2 +- echo "I:--with-openssl, or --with-pkcs11 and --enable-native-pkcs11" >&2 +- exit 1 +-fi ++exec $SHELL ../testcrypto.sh +diff --git a/bin/tests/system/feature-test.c b/bin/tests/system/feature-test.c +new file mode 100644 +index 0000000000..495f46a32a +--- /dev/null ++++ b/bin/tests/system/feature-test.c +@@ -0,0 +1,159 @@ ++/* ++ * Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * 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 http://mozilla.org/MPL/2.0/. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#ifdef WIN32 ++#include ++#endif ++ ++#ifndef MAXHOSTNAMELEN ++#ifdef HOST_NAME_MAX ++#define MAXHOSTNAMELEN HOST_NAME_MAX ++#else ++#define MAXHOSTNAMELEN 256 ++#endif ++#endif ++ ++static void ++usage() { ++ fprintf(stderr, "usage: feature-test \n"); ++ fprintf(stderr, "args:\n"); ++ fprintf(stderr, " --enable-filter-aaaa\n"); ++ fprintf(stderr, " --gethostname\n"); ++ fprintf(stderr, " --gssapi\n"); ++ fprintf(stderr, " --have-dlopen\n"); ++ fprintf(stderr, " --have-geoip\n"); ++ fprintf(stderr, " --have-libxml2\n"); ++ fprintf(stderr, " --md5\n"); ++ fprintf(stderr, " --rpz-nsip\n"); ++ fprintf(stderr, " --rpz-nsdname\n"); ++ fprintf(stderr, " --with-idn\n"); ++} ++ ++int ++main(int argc, char **argv) { ++ if (argc != 2) { ++ usage(); ++ return (1); ++ } ++ ++ if (strcmp(argv[1], "--enable-filter-aaaa") == 0) { ++#ifdef ALLOW_FILTER_AAAA ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--gethostname") == 0) { ++ char hostname[MAXHOSTNAMELEN]; ++ int n; ++#ifdef WIN32 ++ /* From lwres InitSocket() */ ++ WORD wVersionRequested; ++ WSADATA wsaData; ++ int err; ++ ++ wVersionRequested = MAKEWORD(2, 0); ++ err = WSAStartup( wVersionRequested, &wsaData ); ++ if (err != 0) { ++ fprintf(stderr, "WSAStartup() failed: %d\n", err); ++ exit(1); ++ } ++#endif ++ ++ n = gethostname(hostname, sizeof(hostname)); ++ if (n == -1) { ++ perror("gethostname"); ++ return(1); ++ } ++ fprintf(stdout, "%s\n", hostname); ++#ifdef WIN32 ++ WSACleanup(); ++#endif ++ return (0); ++ } ++ ++ if (strcmp(argv[1], "--gssapi") == 0) { ++#if defined(GSSAPI) ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--have-dlopen") == 0) { ++#if defined(HAVE_DLOPEN) && defined(ISC_DLZ_DLOPEN) ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--have-geoip") == 0) { ++#ifdef HAVE_GEOIP ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--have-libxml2") == 0) { ++#ifdef HAVE_LIBXML2 ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--md5") == 0) { ++ if (isc_md5_available()) { ++ return (0); ++ } else { ++ return (1); ++ } ++ } ++ ++ if (strcmp(argv[1], "--rpz-nsip") == 0) { ++#ifdef ENABLE_RPZ_NSIP ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--rpz-nsdname") == 0) { ++#ifdef ENABLE_RPZ_NSDNAME ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ if (strcmp(argv[1], "--with-idn") == 0) { ++#ifdef WITH_IDN ++ return (0); ++#else ++ return (1); ++#endif ++ } ++ ++ fprintf(stderr, "unknown arg: %s\n", argv[1]); ++ usage(); ++ return (1); ++} +diff --git a/bin/tests/system/filter-aaaa/ns1/sign.sh b/bin/tests/system/filter-aaaa/ns1/sign.sh +index 203e37ebfb..e0c696b986 100755 +--- a/bin/tests/system/filter-aaaa/ns1/sign.sh ++++ b/bin/tests/system/filter-aaaa/ns1/sign.sh +@@ -27,8 +27,8 @@ infile=signed.db.in + zonefile=signed.db.signed + outfile=signed.db.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/filter-aaaa/ns4/sign.sh b/bin/tests/system/filter-aaaa/ns4/sign.sh +index ff33b10a19..74d755763a 100755 +--- a/bin/tests/system/filter-aaaa/ns4/sign.sh ++++ b/bin/tests/system/filter-aaaa/ns4/sign.sh +@@ -27,8 +27,8 @@ infile=signed.db.in + zonefile=signed.db.signed + outfile=signed.db.signed + +-keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` +-keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 768 -n zone $zone 2> /dev/null` ++keyname1=`$KEYGEN -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` ++keyname2=`$KEYGEN -f KSK -r $RANDFILE -a DSA -b 1024 -n zone $zone 2> /dev/null` + + cat $infile $keyname1.key $keyname2.key >$zonefile + +diff --git a/bin/tests/system/keymgr/prereq.sh b/bin/tests/system/keymgr/prereq.sh +index be2546ec59..e71cc9f03a 100644 +--- a/bin/tests/system/keymgr/prereq.sh ++++ b/bin/tests/system/keymgr/prereq.sh +@@ -14,17 +14,4 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-SYSTEMTESTTOP=.. +-. $SYSTEMTESTTOP/conf.sh +- +-../../../tools/genrandom 400 random.data +- +-if $KEYGEN -q -a RSAMD5 -b 512 -n zone -r random.data foo > /dev/null 2>&1 +-then +- rm -f Kfoo* +-else +- echo "I:This test requires cryptography" >&2 +- echo "I:--with-openssl, or --with-pkcs11 and --enable-native-pkcs11" >&2 +- exit 1 +-fi +-#exec $SHELL ../testcrypto.sh ++exec $SHELL ../testcrypto.sh +diff --git a/bin/tests/system/nsupdate/ns1/named.conf b/bin/tests/system/nsupdate/ns1/named.conf +index 86fe91d070..c53da11685 100644 +--- a/bin/tests/system/nsupdate/ns1/named.conf ++++ b/bin/tests/system/nsupdate/ns1/named.conf +@@ -42,7 +42,7 @@ controls { + }; + + key altkey { +- algorithm hmac-md5; ++ algorithm hmac-sha512; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/nsupdate/ns2/named.conf b/bin/tests/system/nsupdate/ns2/named.conf +index 6db32202ff..68022656ec 100644 +--- a/bin/tests/system/nsupdate/ns2/named.conf ++++ b/bin/tests/system/nsupdate/ns2/named.conf +@@ -33,7 +33,7 @@ options { + }; + + key altkey { +- algorithm hmac-md5; ++ algorithm hmac-sha512; + secret "1234abcd8765"; + }; + +diff --git a/bin/tests/system/nsupdate/setup.sh b/bin/tests/system/nsupdate/setup.sh +index bb015142da..e97406956a 100644 +--- a/bin/tests/system/nsupdate/setup.sh ++++ b/bin/tests/system/nsupdate/setup.sh +@@ -53,8 +53,13 @@ EOF + + ../../../tools/genrandom 400 random.data + $DDNSCONFGEN -q -r random.data -z example.nil > ns1/ddns.key ++if $FEATURETEST --md5; then ++ $DDNSCONFGEN -q -r random.data -a hmac-md5 -k md5-key -z keytests.nil > ns1/md5.key ++else ++ echo -n > ns1/md5.key ++fi ++ + +-$DDNSCONFGEN -q -r random.data -a hmac-md5 -k md5-key -z keytests.nil > ns1/md5.key + $DDNSCONFGEN -q -r random.data -a hmac-sha1 -k sha1-key -z keytests.nil > ns1/sha1.key + $DDNSCONFGEN -q -r random.data -a hmac-sha224 -k sha224-key -z keytests.nil > ns1/sha224.key + $DDNSCONFGEN -q -r random.data -a hmac-sha256 -k sha256-key -z keytests.nil > ns1/sha256.key +diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh +index b9a1c90536..821d7a65e2 100644 +--- a/bin/tests/system/nsupdate/tests.sh ++++ b/bin/tests/system/nsupdate/tests.sh +@@ -516,7 +516,14 @@ fi + n=`expr $n + 1` + ret=0 + echo "I:check TSIG key algorithms ($n)" +-for alg in md5 sha1 sha224 sha256 sha384 sha512; do ++if $FEATURETEST --md5 ++then ++ ALGS="md5 sha1 sha224 sha256 sha384 sha512" ++else ++ ALGS="sha1 sha224 sha256 sha384 sha512" ++ echo_i "skipping disabled md5 algorithm" ++fi ++for alg in $ALGS; do + $NSUPDATE -k ns1/${alg}.key < /dev/null || ret=1 + server 10.53.0.1 5300 + update add ${alg}.keytests.nil. 600 A 10.10.10.3 +@@ -524,7 +531,7 @@ send + END + done + sleep 2 +-for alg in md5 sha1 sha224 sha256 sha384 sha512; do ++for alg in $ALGS; do + $DIG +short @10.53.0.1 -p 5300 ${alg}.keytests.nil | grep 10.10.10.3 > /dev/null 2>&1 || ret=1 + done + if [ $ret -ne 0 ]; then +diff --git a/bin/tests/system/rndc/setup.sh b/bin/tests/system/rndc/setup.sh +index ce80005faf..a7c66841cc 100644 +--- a/bin/tests/system/rndc/setup.sh ++++ b/bin/tests/system/rndc/setup.sh +@@ -22,7 +22,7 @@ SYSTEMTESTTOP=.. + + sh clean.sh + +-../../../tools/genrandom 400 random.data ++../../../tools/genrandom 800 random.data + + sh ../genzone.sh 2 >ns2/nil.db + sh ../genzone.sh 2 >ns2/other.db +@@ -37,7 +37,7 @@ make_key () { + sed 's/allow { 10.53.0.4/allow { any/' >> ns4/named.conf + } + +-make_key 1 hmac-md5 ++$FEATURETEST --md5 && make_key 1 hmac-md5 + make_key 2 hmac-sha1 + make_key 3 hmac-sha224 + make_key 4 hmac-sha256 +diff --git a/bin/tests/system/rndc/tests.sh b/bin/tests/system/rndc/tests.sh +index 01dbc811ae..20a90850d1 100644 +--- a/bin/tests/system/rndc/tests.sh ++++ b/bin/tests/system/rndc/tests.sh +@@ -246,14 +246,20 @@ if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + + echo "I:testing rndc with hmac-md5" +-ret=0 +-$RNDC -s 10.53.0.4 -p 9951 -c ns4/key1.conf status > /dev/null 2>&1 || ret=1 +-for i in 2 3 4 5 6 +-do +- $RNDC -s 10.53.0.4 -p 9951 -c ns4/key${i}.conf status > /dev/null 2>&1 && ret=1 +-done +-if [ $ret != 0 ]; then echo "I:failed"; fi +-status=`expr $status + $ret` ++if $FEATURETEST --md5 ++then ++ echo "I:testing rndc with hmac-md5" ++ ret=0 ++ $RNDC -s 10.53.0.4 -p 9951 -c ns4/key1.conf status > /dev/null 2>&1 || ret=1 ++ for i in 2 3 4 5 6 ++ do ++ $RNDC -s 10.53.0.4 -p 9951 -c ns4/key${i}.conf status > /dev/null 2>&1 && ret=1 ++ done ++ if [ $ret != 0 ]; then echo_i "failed"; fi ++ status=`expr $status + $ret` ++else ++ echo "W:skipping rndc with hmac-md5" ++fi + + echo "I:testing rndc with hmac-sha1" + ret=0 +diff --git a/bin/tests/system/testcrypto.sh b/bin/tests/system/testcrypto.sh +new file mode 100644 +index 0000000000..e21f18b5f5 +--- /dev/null ++++ b/bin/tests/system/testcrypto.sh +@@ -0,0 +1,71 @@ ++#!/bin/sh ++# ++# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++SYSTEMTESTTOP=${SYSTEMTESTTOP:=..} ++. $SYSTEMTESTTOP/conf.sh ++ ++# Unlike 9.11, keep generated data in current directory ++RANDFILE=random.data ++ ++test -r $RANDFILE || $GENRANDOM 800 $RANDFILE ++ ++prog=$0 ++ ++args="-r $RANDFILE" ++alg="-a RSASHA1 -b 2048" ++quiet=0 ++ ++msg1="cryptography" ++msg2="--with-openssl, or --enable-native-pkcs11 --with-pkcs11" ++while test "$#" -gt 0; do ++ case $1 in ++ -q) ++ args="$args -q" ++ quiet=1 ++ ;; ++ rsa|RSA) ++ alg="" ++ msg1="RSA cryptography" ++ ;; ++ gost|GOST) ++ alg="-a eccgost" ++ msg1="GOST cryptography" ++ msg2="--with-gost" ++ ;; ++ ecdsa|ECDSA) ++ alg="-a ecdsap256sha256" ++ msg1="ECDSA cryptography" ++ msg2="--with-ecdsa" ++ ;; ++ *) ++ echo "${prog}: unknown argument" ++ exit 1 ++ ;; ++ esac ++ shift ++done ++ ++ ++if $KEYGEN $args $alg foo > /dev/null 2>&1 ++then ++ rm -f Kfoo* ++else ++ if test $quiet -eq 0; then ++ echo "I:This test requires support for $msg1" >&2 ++ echo "I:configure with $msg2" >&2 ++ fi ++ exit 255 ++fi +diff --git a/bin/tests/system/tkey/keycreate.c b/bin/tests/system/tkey/keycreate.c +index af17582096..b61b5d0796 100644 +--- a/bin/tests/system/tkey/keycreate.c ++++ b/bin/tests/system/tkey/keycreate.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -143,6 +144,8 @@ sendquery(isc_task_t *task, isc_event_t *event) { + static char keystr[] = "0123456789ab"; + + isc_event_free(&event); ++ if (isc_md5_available() == ISC_FALSE) ++ CHECK("MD5 was disabled", ISC_R_NOTIMPLEMENTED); + + result = ISC_R_FAILURE; + if (inet_pton(AF_INET, "10.53.0.1", &inaddr) != 1) +diff --git a/bin/tests/system/tkey/keydelete.c b/bin/tests/system/tkey/keydelete.c +index 1bb33e85fe..da4b1c3c09 100644 +--- a/bin/tests/system/tkey/keydelete.c ++++ b/bin/tests/system/tkey/keydelete.c +@@ -228,12 +228,18 @@ main(int argc, char **argv) { + type = DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY; + result = dst_key_fromnamedfile(keyname, NULL, type, mctx, &dstkey); + CHECK("dst_key_fromnamedfile", result); +- result = dns_tsigkey_createfromkey(dst_key_name(dstkey), +- DNS_TSIG_HMACMD5_NAME, +- dstkey, ISC_TRUE, NULL, 0, 0, +- mctx, ring, &tsigkey); +- dst_key_free(&dstkey); +- CHECK("dns_tsigkey_createfromkey", result); ++ if (isc_md5_available()) { ++ result = dns_tsigkey_createfromkey(dst_key_name(dstkey), ++ DNS_TSIG_HMACMD5_NAME, ++ dstkey, ISC_TRUE, ++ NULL, 0, 0, ++ mctx, ring, &tsigkey); ++ dst_key_free(&dstkey); ++ CHECK("dns_tsigkey_createfromkey", result); ++ } else { ++ dst_key_free(&dstkey); ++ CHECK("MD5 was disabled", ISC_R_NOTIMPLEMENTED); ++ } + + (void)isc_app_run(); + +diff --git a/bin/tests/system/tkey/prereq.sh b/bin/tests/system/tkey/prereq.sh +index 66295fee90..310849f08e 100644 +--- a/bin/tests/system/tkey/prereq.sh ++++ b/bin/tests/system/tkey/prereq.sh +@@ -17,13 +17,4 @@ + + # $Id: prereq.sh,v 1.12 2009/03/02 23:47:43 tbox Exp $ + +-../../../tools/genrandom 400 random.data +- +-if $KEYGEN -a RSAMD5 -b 512 -n zone -r random.data foo > /dev/null 2>&1 +-then +- rm -f foo* +-else +- echo "I:This test requires cryptography" >&2 +- echo "I:--with-openssl, or --with-pkcs11 and --enable-native-pkcs11" >&2 +- exit 1 +-fi ++exec $SHELL ../testcrypto.sh +diff --git a/bin/tests/system/tsig/clean.sh b/bin/tests/system/tsig/clean.sh +index 0e98b4047b..b11a378006 100644 +--- a/bin/tests/system/tsig/clean.sh ++++ b/bin/tests/system/tsig/clean.sh +@@ -23,3 +23,4 @@ + rm -f dig.out.* + rm -f */named.memstats + rm -f */named.run ++rm -f ns1/rndc5.conf +diff --git a/bin/tests/system/tsig/ns1/named.conf b/bin/tests/system/tsig/ns1/named.conf +index b48de835f4..e7e568acc7 100644 +--- a/bin/tests/system/tsig/ns1/named.conf ++++ b/bin/tests/system/tsig/ns1/named.conf +@@ -30,10 +30,7 @@ options { + notify no; + }; + +-key "md5" { +- secret "97rnFx24Tfna4mHPfgnerA=="; +- algorithm hmac-md5; +-}; ++# md5 key included from rndc5.conf + + key "sha1" { + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +@@ -60,10 +57,7 @@ key "sha512" { + algorithm hmac-sha512; + }; + +-key "md5-trunc" { +- secret "97rnFx24Tfna4mHPfgnerA=="; +- algorithm hmac-md5-80; +-}; ++# md5-trunc key included from rndc5.conf + + key "sha1-trunc" { + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +@@ -94,3 +88,5 @@ zone "example.nil" { + type master; + file "example.db"; + }; ++ ++include "rndc5.conf"; +diff --git a/bin/tests/system/tsig/ns1/rndc5.conf.in b/bin/tests/system/tsig/ns1/rndc5.conf.in +new file mode 100644 +index 0000000000..f9b17d6e8e +--- /dev/null ++++ b/bin/tests/system/tsig/ns1/rndc5.conf.in +@@ -0,0 +1,22 @@ ++/* ++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++ * ++ * 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 http://mozilla.org/MPL/2.0/. ++ * ++ * See the COPYRIGHT file distributed with this work for additional ++ * information regarding copyright ownership. ++ */ ++ ++/* These md5 keys are used only when MD5 is not disabled in build */ ++key "md5" { ++ secret "97rnFx24Tfna4mHPfgnerA=="; ++ algorithm hmac-md5; ++}; ++ ++key "md5-trunc" { ++ secret "97rnFx24Tfna4mHPfgnerA=="; ++ algorithm hmac-md5-80; ++}; ++ +diff --git a/bin/tests/system/tsig/setup.sh b/bin/tests/system/tsig/setup.sh +new file mode 100644 +index 0000000000..7f9049ae76 +--- /dev/null ++++ b/bin/tests/system/tsig/setup.sh +@@ -0,0 +1,25 @@ ++#!/bin/sh ++# ++# Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++# ++# 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 http://mozilla.org/MPL/2.0/. ++# ++# See the COPYRIGHT file distributed with this work for additional ++# information regarding copyright ownership. ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++ ++$SHELL clean.sh ++ ++test -r $RANDFILE || $GENRANDOM 800 $RANDFILE ++ ++if $FEATURETEST --md5 ++then ++ # Include MD5 keys only if it is ++ cp ns1/rndc5.conf.in ns1/rndc5.conf ++else ++ echo "# MD5 disabled" > ns1/rndc5.conf ++fi +diff --git a/bin/tests/system/tsig/tests.sh b/bin/tests/system/tsig/tests.sh +index 50ac8d23e6..bd502dd718 100644 +--- a/bin/tests/system/tsig/tests.sh ++++ b/bin/tests/system/tsig/tests.sh +@@ -31,22 +31,27 @@ sha512="jI/Pa4qRu96t76Pns5Z/Ndxbn3QCkwcxLOgt9vgvnJw5wqTRvNyk3FtD6yIMd1dWVlqZ+Y4f + + status=0 + +-echo "I:fetching using hmac-md5 (old form)" +-ret=0 +-$DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ +- -y "md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.old || ret=1 +-grep -i "md5.*TSIG.*NOERROR" dig.out.md5.old > /dev/null || ret=1 +-if [ $ret -eq 1 ] ; then +- echo "I: failed"; status=1 +-fi +- +-echo "I:fetching using hmac-md5 (new form)" +-ret=0 +-$DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ +- -y "hmac-md5:md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.new || ret=1 +-grep -i "md5.*TSIG.*NOERROR" dig.out.md5.new > /dev/null || ret=1 +-if [ $ret -eq 1 ] ; then +- echo "I: failed"; status=1 ++if $FEATURETEST --md5 ++then ++ echo "I:fetching using hmac-md5 (old form)" ++ ret=0 ++ $DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ ++ -y "md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.old || ret=1 ++ grep -i "md5.*TSIG.*NOERROR" dig.out.md5.old > /dev/null || ret=1 ++ if [ $ret -eq 1 ] ; then ++ echo "I: failed"; status=1 ++ fi ++ ++ echo "I:fetching using hmac-md5 (new form)" ++ ret=0 ++ $DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ ++ -y "hmac-md5:md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.new || ret=1 ++ grep -i "md5.*TSIG.*NOERROR" dig.out.md5.new > /dev/null || ret=1 ++ if [ $ret -eq 1 ] ; then ++ echo_i "failed"; status=1 ++ fi ++else ++ echo_i "skipping using hmac-md5" + fi + + echo "I:fetching using hmac-sha1" +@@ -99,13 +104,19 @@ fi + # Truncated TSIG + # + # ++ ++if $FEATURETEST --md5 ++then + echo "I:fetching using hmac-md5 (trunc)" +-ret=0 +-$DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ +- -y "hmac-md5-80:md5-trunc:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.trunc || ret=1 +-grep -i "md5-trunc.*TSIG.*NOERROR" dig.out.md5.trunc > /dev/null || ret=1 +-if [ $ret -eq 1 ] ; then +- echo "I: failed"; status=1 ++ ret=0 ++ $DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ ++ -y "hmac-md5-80:md5-trunc:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5.trunc || ret=1 ++ grep -i "md5-trunc.*TSIG.*NOERROR" dig.out.md5.trunc > /dev/null || ret=1 ++ if [ $ret -eq 1 ] ; then ++ echo "I: failed"; status=1 ++ fi ++else ++ echo "W:skipping using hmac-md5 (trunc)" + fi + + echo "I:fetching using hmac-sha1 (trunc)" +@@ -159,13 +170,19 @@ fi + # Check for bad truncation. + # + # +-echo "I:fetching using hmac-md5-80 (BADTRUNC)" +-ret=0 +-$DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ +- -y "hmac-md5-80:md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5-80 || ret=1 +-grep -i "md5.*TSIG.*BADTRUNC" dig.out.md5-80 > /dev/null || ret=1 +-if [ $ret -eq 1 ] ; then +- echo "I: failed"; status=1 ++ ++if $FEATURETEST --md5 ++then ++ echo "I:fetching using hmac-md5-80 (BADTRUNC)" ++ ret=0 ++ $DIG +tcp +nosea +nostat +noquest +nocomm +nocmd example.nil.\ ++ -y "hmac-md5-80:md5:$md5" @10.53.0.1 soa -p 5300 > dig.out.md5-80 || ret=1 ++ grep -i "md5.*TSIG.*BADTRUNC" dig.out.md5-80 > /dev/null || ret=1 ++ if [ $ret -eq 1 ] ; then ++ echo "I: failed"; status=1 ++ fi ++else ++ echo "W:skipping using hmac-md5-80 (BADTRUNC)" + fi + + echo "I:fetching using hmac-sha1-80 (BADTRUNC)" +diff --git a/bin/tests/system/tsiggss/setup.sh b/bin/tests/system/tsiggss/setup.sh +index 00222bad05..e795df3bff 100644 +--- a/bin/tests/system/tsiggss/setup.sh ++++ b/bin/tests/system/tsiggss/setup.sh +@@ -26,5 +26,5 @@ rm -f ns1/*.jnl ns1/K*.key ns1/K*.private ns1/_default.tsigkeys + + ../../../tools/genrandom 400 $RANDFILE + +-key=`$KEYGEN -Cq -K ns1 -a DSA -b 512 -r $RANDFILE -n HOST -T KEY key.example.nil.` ++key=`$KEYGEN -Cq -K ns1 -a DSA -b 1024 -r $RANDFILE -n HOST -T KEY key.example.nil.` + cat ns1/example.nil.db.in ns1/${key}.key > ns1/example.nil.db +diff --git a/bin/tests/system/upforwd/ns1/named.conf b/bin/tests/system/upforwd/ns1/named.conf +index 8d9d2fa0d9..c3c0238073 100644 +--- a/bin/tests/system/upforwd/ns1/named.conf ++++ b/bin/tests/system/upforwd/ns1/named.conf +@@ -18,7 +18,7 @@ + /* $Id: named.conf,v 1.11 2007/06/18 23:47:31 tbox Exp $ */ + + key "update.example." { +- algorithm "hmac-md5"; ++ algorithm "hmac-sha256"; + secret "c3Ryb25nIGVub3VnaCBmb3IgYSBtYW4gYnV0IG1hZGUgZm9yIGEgd29tYW4K"; + }; + +diff --git a/bin/tests/system/upforwd/tests.sh b/bin/tests/system/upforwd/tests.sh +index a138649ac3..e14a592db6 100644 +--- a/bin/tests/system/upforwd/tests.sh ++++ b/bin/tests/system/upforwd/tests.sh +@@ -68,7 +68,7 @@ if [ $ret != 0 ] ; then echo "I:failed"; status=`expr $status + $ret`; fi + + echo "I:updating zone (signed)" + ret=0 +-$NSUPDATE -y update.example:c3Ryb25nIGVub3VnaCBmb3IgYSBtYW4gYnV0IG1hZGUgZm9yIGEgd29tYW4K -- - < +Date: Fri, 22 Jun 2018 14:05:18 +0200 +Subject: [PATCH 1/2] Squashed commit of the following: +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +commit 3fd542379fa381b54381e07d6625ce53f9f9b1f0 +Author: Petr Menšík +Date: Thu Jun 21 12:00:35 2018 +0200 + + Revert "4450. [port] Provide more nuanced HSM support which better matches" + + This reverts commit f3b4d031c1f714ff6e862670663aa5a18650951e. + + Revert PK11_MD5_DISABLED also from remaining files. Keep documentation + changes. + +commit f90934f734796595135cdd7a5008555a615dfe8e +Author: Petr Menšík +Date: Wed Jun 20 19:31:19 2018 +0200 + + Fix rndc-confgen default algorithm, report true algorithm in usage. + +commit dd53212c12c6943a21a3c24d60995edd19e1d9f7 +Author: Petr Menšík +Date: Fri Feb 23 21:21:30 2018 +0100 + + Cleanup only if initialization was successful + +commit f163ea51c46bb22bf264a1ac983e2027e43845fa +Author: Petr Menšík +Date: Mon Feb 5 12:19:28 2018 +0100 + + Ensure dst backend is initialized first even before hmac algorithms. + +commit 58751b60bd39168b7c8f817ede70473842432081 +Author: Petr Menšík +Date: Mon Feb 5 12:17:54 2018 +0100 + + Skip initialization of MD5 based algorithms if not available. + +commit 0572b98430d3c80f4a0b0c592b1e3bf7fde9b768 +Author: Petr Menšík +Date: Mon Feb 5 10:21:27 2018 +0100 + + Change secalgs skipping to be more safe + +commit 994f497a032930fce1370d507a265fbb293c66f4 +Author: Petr Menšík +Date: Wed Jan 31 18:26:11 2018 +0100 + + Skip MD5 algorithm also in case of NULL name + +commit abd82fbd2507c4b8f20e1ade202fd66d224fd646 +Author: Petr Menšík +Date: Wed Jan 31 16:54:29 2018 +0100 + + Revert part of commit 1b5c641416eb6de7fc232fc89d31a40a4d439f3d related + to SHA1. + +commit b3c832d53a14a0779f598869bb99685c8e4b2bc0 +Author: Petr Menšík +Date: Wed Jan 31 11:38:12 2018 +0100 + + Make MD5 behave like unknown algorithm in TSIG. + +commit a64a3d6962ee93d6f8699b29bd6507dba0c244ed +Author: Petr Menšík +Date: Tue Nov 28 20:14:37 2017 +0100 + + Select token with most supported functions, instead of demanding it must support all functions + + Initialize PKCS#11 always until successfully initialized + +commit db118c6368668099ea1b6e75860cc12e178afa3b +Author: Petr Menšík +Date: Mon Jan 22 16:17:44 2018 +0100 + + Handle MD5 unavailability from DST + +commit 8f8824dca2f5b4d5a3a176d31ac3ee612321c4e3 +Author: Petr Menšík +Date: Mon Jan 22 14:11:16 2018 +0100 + + Check runtime flag from library and applications, fail gracefully. + +commit bd431384af7dcde8827e670c8749517ad677a967 +Author: Petr Menšík +Date: Mon Jan 22 08:39:08 2018 +0100 + + Modify libraries to use isc_md5_available() if PK11_MD5_DISABLE is not + defined. + TODO: pk11.c should accept slot without MD5 support. + +commit 160b13979ef3d0e92d2dd52d0987a3ec979be6cf +Author: Petr Menšík +Date: Mon Jan 22 07:21:04 2018 +0100 + + Add runtime detection whether MD5 is useable. + +commit 23b27ce0f2ad496c331ae40349cc1074a1b11804 +Author: Mark Andrews +Date: Fri Aug 19 08:25:54 2016 +1000 + + 4450. [port] Provide more nuanced HSM support which better matches + the specific PKCS11 providers capabilities. [RT #42458] + + (cherry picked from commit 8ee6f289d87851a5b898b24a64587f0e6bc225bc) + +Fix compiler warnings +--- + bin/confgen/keygen.c | 10 +++++- + bin/confgen/rndc-confgen.c | 12 ++++--- + bin/confgen/rndc-confgen.docbook | 3 +- + bin/dig/dig.c | 5 ++- + bin/dig/dig.docbook | 6 ++-- + bin/dig/dighost.c | 14 ++++++-- + bin/dnssec/dnssec-keyfromlabel.c | 9 +++++ + bin/dnssec/dnssec-keygen.c | 19 +++++++++-- + bin/named/config.c | 23 +++++++++++-- + bin/nsupdate/nsupdate.c | 20 +++++++---- + bin/nsupdate/nsupdate.docbook | 9 ++++- + bin/rndc/rndc.c | 3 +- + bin/tests/hashes/t_hashes.c | 20 ++++++----- + lib/bind9/check.c | 8 +++++ + lib/dns/dst_api.c | 23 +++++++++---- + lib/dns/dst_internal.h | 3 +- + lib/dns/dst_parse.c | 12 ++++++- + lib/dns/hmac_link.c | 2 +- + lib/dns/opensslrsa_link.c | 4 +++ + lib/dns/pkcs11rsa_link.c | 25 ++++++++++++-- + lib/dns/rcode.c | 19 +++++++++-- + lib/dns/tests/tsig_test.c | 1 + + lib/dns/tkey.c | 9 +++++ + lib/dns/tsec.c | 8 ++++- + lib/dns/tsig.c | 22 +++++------- + lib/isc/include/isc/md5.h | 3 ++ + lib/isc/md5.c | 59 ++++++++++++++++++++++++++++++++ + lib/isc/pk11.c | 72 +++++++++++++++++++++++++++++++--------- + lib/isccc/cc.c | 48 ++++++++++++++++----------- + 29 files changed, 373 insertions(+), 98 deletions(-) + +diff --git a/bin/confgen/keygen.c b/bin/confgen/keygen.c +index d0cdafed36..4eb027e723 100644 +--- a/bin/confgen/keygen.c ++++ b/bin/confgen/keygen.c +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,7 +70,7 @@ alg_totext(dns_secalg_t alg) { + */ + dns_secalg_t + alg_fromtext(const char *name) { +- if (strcmp(name, "hmac-md5") == 0) ++ if (strcmp(name, "hmac-md5") == 0 && isc_md5_available()) + return DST_ALG_HMACMD5; + if (strcmp(name, "hmac-sha1") == 0) + return DST_ALG_HMACSHA1; +@@ -126,6 +127,13 @@ generate_key(isc_mem_t *mctx, const char *randomfile, dns_secalg_t alg, + + switch (alg) { + case DST_ALG_HMACMD5: ++ if (isc_md5_available() == ISC_FALSE) { ++ fatal("unsupported algorithm %d\n", alg); ++ } else if (keysize < 1 || keysize > 512) { ++ fatal("keysize %d out of range (must be 1-512)\n", ++ keysize); ++ } ++ break; + case DST_ALG_HMACSHA1: + case DST_ALG_HMACSHA224: + case DST_ALG_HMACSHA256: +diff --git a/bin/confgen/rndc-confgen.c b/bin/confgen/rndc-confgen.c +index 3fd54fe2bb..c48a38f094 100644 +--- a/bin/confgen/rndc-confgen.c ++++ b/bin/confgen/rndc-confgen.c +@@ -41,6 +41,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -66,7 +67,7 @@ const char *progname; + + isc_boolean_t verbose = ISC_FALSE; + +-const char *keyfile, *keydef; ++const char *keyfile, *keydef, *algdef; + + ISC_PLATFORM_NORETURN_PRE static void + usage(int status) ISC_PLATFORM_NORETURN_POST; +@@ -79,7 +80,7 @@ Usage:\n\ + %s [-a] [-b bits] [-c keyfile] [-k keyname] [-p port] [-r randomfile] \ + [-s addr] [-t chrootdir] [-u user]\n\ + -a: generate just the key clause and write it to keyfile (%s)\n\ +- -A alg: algorithm (default hmac-md5)\n\ ++ -A alg: algorithm (default %s)\n\ + -b bits: from 1 through 512, default 256; total length of the secret\n\ + -c keyfile: specify an alternate key file (requires -a)\n\ + -k keyname: the name as it will be used in named.conf and rndc.conf\n\ +@@ -88,7 +89,7 @@ Usage:\n\ + -s addr: the address to which rndc should connect\n\ + -t chrootdir: write a keyfile in chrootdir as well (requires -a)\n\ + -u user: set the keyfile owner to \"user\" (requires -a)\n", +- progname, keydef); ++ progname, keydef, algdef); + + exit (status); + } +@@ -124,9 +125,12 @@ main(int argc, char **argv) { + progname = program; + + keyname = DEFAULT_KEYNAME; +- alg = DST_ALG_HMACMD5; + serveraddr = DEFAULT_SERVER; + port = DEFAULT_PORT; ++ alg = DST_ALG_HMACSHA256; ++ if (isc_md5_available()) ++ alg = DST_ALG_HMACMD5; ++ algdef = alg_totext(alg); + + isc_commandline_errprint = ISC_FALSE; + +diff --git a/bin/confgen/rndc-confgen.docbook b/bin/confgen/rndc-confgen.docbook +index f367b94aae..add8d7357f 100644 +--- a/bin/confgen/rndc-confgen.docbook ++++ b/bin/confgen/rndc-confgen.docbook +@@ -136,7 +136,8 @@ + + Specifies the algorithm to use for the TSIG key. Available + choices are: hmac-md5, hmac-sha1, hmac-sha224, hmac-sha256, +- hmac-sha384 and hmac-sha512. The default is hmac-md5. ++ hmac-sha384 and hmac-sha512. The default is hmac-md5 or ++ if MD5 was disabled hmac-sha256. + + + +diff --git a/bin/dig/dig.c b/bin/dig/dig.c +index 634ccfbfbf..4db0e6430f 100644 +--- a/bin/dig/dig.c ++++ b/bin/dig/dig.c +@@ -25,6 +25,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -1426,7 +1427,9 @@ dash_option(char *option, char *next, dig_lookup_t **lookup, + ptr = ptr2; + ptr2 = ptr3; + } else { +- hmacname = DNS_TSIG_HMACMD5_NAME; ++ hmacname = DNS_TSIG_HMACSHA256_NAME; ++ if (isc_md5_available()) ++ hmacname = DNS_TSIG_HMACMD5_NAME; + digestbits = 0; + } + strncpy(keynametext, ptr, sizeof(keynametext)); +diff --git a/bin/dig/dig.docbook b/bin/dig/dig.docbook +index 028f0fcd73..6ee5bd90bc 100644 +--- a/bin/dig/dig.docbook ++++ b/bin/dig/dig.docbook +@@ -309,9 +309,11 @@ + responses using transaction signatures (TSIG), specify a TSIG key file + using the option. You can also specify the TSIG + key itself on the command line using the option; +- hmac is the type of the TSIG, default HMAC-MD5, ++ hmac is the type of the TSIG, + name is the name of the TSIG key and +- key is the actual key. The key is a ++ key is the actual key. If hmac ++ is not specified, the default is hmac-md5 ++ or if MD5 was disabled hmac-sha256. The key is a + base-64 + encoded string, typically generated by + +diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c +index 3a066c68c9..3998a81a5d 100644 +--- a/bin/dig/dighost.c ++++ b/bin/dig/dighost.c +@@ -81,6 +81,7 @@ + #include + #include + #include ++#include + #include + #ifdef DIG_SIGCHASE + #include +@@ -1025,9 +1026,10 @@ parse_hmac(const char *hmac) { + + digestbits = 0; + +- if (strcasecmp(buf, "hmac-md5") == 0) { ++ if (strcasecmp(buf, "hmac-md5") == 0 && isc_md5_available()) { + hmacname = DNS_TSIG_HMACMD5_NAME; +- } else if (strncasecmp(buf, "hmac-md5-", 9) == 0) { ++ } else if (strncasecmp(buf, "hmac-md5-", 9) == 0 && ++ isc_md5_available()) { + hmacname = DNS_TSIG_HMACMD5_NAME; + digestbits = parse_bits(&buf[9], "digest-bits [0..128]", 128); + } else if (strcasecmp(buf, "hmac-sha1") == 0) { +@@ -1145,7 +1147,13 @@ setup_file_key(void) { + + switch (dst_key_alg(dstkey)) { + case DST_ALG_HMACMD5: +- hmacname = DNS_TSIG_HMACMD5_NAME; ++ if (isc_md5_available()) { ++ hmacname = DNS_TSIG_HMACMD5_NAME; ++ } else { ++ printf(";; Couldn't create key %s: bad algorithm\n", ++ keynametext); ++ goto failure; ++ } + break; + case DST_ALG_HMACSHA1: + hmacname = DNS_TSIG_HMACSHA1_NAME; +diff --git a/bin/dnssec/dnssec-keyfromlabel.c b/bin/dnssec/dnssec-keyfromlabel.c +index cc212e1ed1..a5854a1f4c 100644 +--- a/bin/dnssec/dnssec-keyfromlabel.c ++++ b/bin/dnssec/dnssec-keyfromlabel.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -379,6 +380,11 @@ main(int argc, char **argv) { + if (freeit != NULL) + free(freeit); + return (1); ++ } else if (strcasecmp(algname, "RSAMD5") == 0 && isc_md5_available() == ISC_FALSE) { ++ fprintf(stderr, "The use of RSAMD5 was disabled\n"); ++ if (freeit != NULL) ++ free(freeit); ++ return (1); + } else { + r.base = algname; + r.length = strlen(algname); +@@ -412,6 +418,9 @@ main(int argc, char **argv) { + fatal("invalid type %s", type); + } + ++ if (alg == DST_ALG_RSAMD5 && isc_md5_available() == ISC_FALSE) ++ fatal("Key uses disabled RSAMD5"); ++ + if (nametype == NULL) { + if ((options & DST_TYPE_KEY) != 0) /* KEY */ + fatal("no nametype specified"); +diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c +index 97b96ee1dc..c147bb4e6c 100644 +--- a/bin/dnssec/dnssec-keygen.c ++++ b/bin/dnssec/dnssec-keygen.c +@@ -42,6 +42,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -548,9 +549,21 @@ main(int argc, char **argv) { + "\"-a RSAMD5\"\n"); + INSIST(freeit == NULL); + return (1); +- } else if (strcasecmp(algname, "HMAC-MD5") == 0) +- alg = DST_ALG_HMACMD5; +- else if (strcasecmp(algname, "HMAC-SHA1") == 0) ++ } else if (strcasecmp(algname, "HMAC-MD5") == 0) { ++ if (isc_md5_available()) { ++ alg = DST_ALG_HMACMD5; ++ } else { ++ fprintf(stderr, ++ "The use of HMAC-MD5 was disabled\n"); ++ INSIST(freeit == NULL); ++ return (1); ++ } ++ } else if (strcasecmp(algname, "RSAMD5") == 0 && ++ isc_md5_available() == ISC_FALSE) { ++ fprintf(stderr, "The use of RSAMD5 was disabled\n"); ++ INSIST(freeit == NULL); ++ return (1); ++ } else if (strcasecmp(algname, "HMAC-SHA1") == 0) + alg = DST_ALG_HMACSHA1; + else if (strcasecmp(algname, "HMAC-SHA224") == 0) + alg = DST_ALG_HMACSHA224; +diff --git a/bin/named/config.c b/bin/named/config.c +index 818ed3797a..22d8a85405 100644 +--- a/bin/named/config.c ++++ b/bin/named/config.c +@@ -25,6 +25,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -828,6 +829,19 @@ ns_config_getkeyalgorithm(const char *str, dns_name_t **name, + return (ns_config_getkeyalgorithm2(str, name, NULL, digestbits)); + } + ++static inline int ++algorithms_start() { ++ if (isc_md5_available() == ISC_FALSE) { ++ int i = 0; ++ while (algorithms[i].str != NULL && ++ algorithms[i].hmac == hmacmd5) { ++ i++; ++ } ++ return i; ++ } ++ return 0; ++} ++ + isc_result_t + ns_config_getkeyalgorithm2(const char *str, dns_name_t **name, + unsigned int *typep, isc_uint16_t *digestbits) +@@ -837,7 +851,7 @@ ns_config_getkeyalgorithm2(const char *str, dns_name_t **name, + isc_uint16_t bits; + isc_result_t result; + +- for (i = 0; algorithms[i].str != NULL; i++) { ++ for (i = algorithms_start(); algorithms[i].str != NULL; i++) { + len = strlen(algorithms[i].str); + if (strncasecmp(algorithms[i].str, str, len) == 0 && + (str[len] == '\0' || +@@ -859,7 +873,12 @@ ns_config_getkeyalgorithm2(const char *str, dns_name_t **name, + + if (name != NULL) { + switch (algorithms[i].hmac) { +- case hmacmd5: *name = dns_tsig_hmacmd5_name; break; ++ case hmacmd5: ++ if (isc_md5_available()) { ++ *name = dns_tsig_hmacmd5_name; break; ++ } else { ++ return (ISC_R_NOTFOUND); ++ } + case hmacsha1: *name = dns_tsig_hmacsha1_name; break; + case hmacsha224: *name = dns_tsig_hmacsha224_name; break; + case hmacsha256: *name = dns_tsig_hmacsha256_name; break; +diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c +index 644e3d9fc0..336597a95d 100644 +--- a/bin/nsupdate/nsupdate.c ++++ b/bin/nsupdate/nsupdate.c +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -440,9 +441,10 @@ parse_hmac(dns_name_t **hmac, const char *hmacstr, size_t len) { + strncpy(buf, hmacstr, len); + buf[len] = 0; + +- if (strcasecmp(buf, "hmac-md5") == 0) { ++ if (strcasecmp(buf, "hmac-md5") == 0 && isc_md5_available()) { + *hmac = DNS_TSIG_HMACMD5_NAME; +- } else if (strncasecmp(buf, "hmac-md5-", 9) == 0) { ++ } else if (strncasecmp(buf, "hmac-md5-", 9) == 0 && ++ isc_md5_available()) { + *hmac = DNS_TSIG_HMACMD5_NAME; + result = isc_parse_uint16(&digestbits, &buf[9], 10); + if (result != ISC_R_SUCCESS || digestbits > 128) +@@ -538,7 +540,9 @@ setup_keystr(void) { + secretstr = n + 1; + digestbits = parse_hmac(&hmacname, keystr, s - keystr); + } else { +- hmacname = DNS_TSIG_HMACMD5_NAME; ++ hmacname = DNS_TSIG_HMACSHA256_NAME; ++ if (isc_md5_available()) ++ hmacname = DNS_TSIG_HMACMD5_NAME; + name = keystr; + n = s; + } +@@ -670,7 +674,8 @@ setup_keyfile(isc_mem_t *mctx, isc_log_t *lctx) { + + switch (dst_key_alg(dstkey)) { + case DST_ALG_HMACMD5: +- hmacname = DNS_TSIG_HMACMD5_NAME; ++ if (isc_md5_available()) ++ hmacname = DNS_TSIG_HMACMD5_NAME; + break; + case DST_ALG_HMACSHA1: + hmacname = DNS_TSIG_HMACSHA1_NAME; +@@ -1462,8 +1467,11 @@ evaluate_key(char *cmdline) { + if (n != NULL) { + digestbits = parse_hmac(&hmacname, namestr, n - namestr); + namestr = n + 1; +- } else +- hmacname = DNS_TSIG_HMACMD5_NAME; ++ } else { ++ hmacname = DNS_TSIG_HMACSHA256_NAME; ++ if (isc_md5_available()) ++ hmacname = DNS_TSIG_HMACMD5_NAME; ++ } + + isc_buffer_init(&b, namestr, strlen(namestr)); + isc_buffer_add(&b, strlen(namestr)); +diff --git a/bin/nsupdate/nsupdate.docbook b/bin/nsupdate/nsupdate.docbook +index bbcc68110f..7d8e78f894 100644 +--- a/bin/nsupdate/nsupdate.docbook ++++ b/bin/nsupdate/nsupdate.docbook +@@ -158,6 +158,9 @@ + hmac:keyname:secret. + keyname is the name of the key, and + secret is the base64 encoded shared secret. ++ If hmac is not specified, ++ the default is hmac-md5 ++ or if MD5 was disabled hmac-sha256. + Use of the option is discouraged because the + shared secret is supplied as a command line argument in clear text. + This may be visible in the output from +@@ -371,13 +374,17 @@ + + + key +- name ++ hmac:name + secret + + + + Specifies that all updates are to be TSIG-signed using the + keyname keysecret pair. ++ If hmac is specified, then it sets the ++ signing algorithm in use; the default is ++ hmac-md5 or if MD5 was disabled ++ hmac-sha256. + The key command + overrides any key specified on the command line via + or . +diff --git a/bin/rndc/rndc.c b/bin/rndc/rndc.c +index 81e629f15c..0fd192d3a5 100644 +--- a/bin/rndc/rndc.c ++++ b/bin/rndc/rndc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -607,7 +608,7 @@ parse_config(isc_mem_t *mctx, isc_log_t *log, const char *keyname, + secretstr = cfg_obj_asstring(secretobj); + algorithmstr = cfg_obj_asstring(algorithmobj); + +- if (strcasecmp(algorithmstr, "hmac-md5") == 0) ++ if (strcasecmp(algorithmstr, "hmac-md5") == 0 && isc_md5_available()) + algorithm = ISCCC_ALG_HMACMD5; + else if (strcasecmp(algorithmstr, "hmac-sha1") == 0) + algorithm = ISCCC_ALG_HMACSHA1; +diff --git a/bin/tests/hashes/t_hashes.c b/bin/tests/hashes/t_hashes.c +index 47d08c572d..965271ad67 100644 +--- a/bin/tests/hashes/t_hashes.c ++++ b/bin/tests/hashes/t_hashes.c +@@ -353,8 +353,10 @@ t_hashes(IN *in, OUT *out_sha1, OUT *out_sha224, OUT *out_md5) + t_hash("SHA1", (HASH_INIT)isc_sha1_init, (UPDATE)isc_sha1_update, + (FINAL)isc_sha1_final, in, out_sha1); + t_sha224(in, out_sha224); +- t_hash("md5", (HASH_INIT)isc_md5_init, (UPDATE)isc_md5_update, +- (FINAL)isc_md5_final, in, out_md5); ++ if (isc_md5_available()) { ++ t_hash("md5", (HASH_INIT)isc_md5_init, (UPDATE)isc_md5_update, ++ (FINAL)isc_md5_final, in, out_md5); ++ } + } + + +@@ -435,12 +437,14 @@ t1(void) + t_hashes(&abc, &abc_sha1, &abc_sha224, &abc_md5); + t_hashes(&abc_blah, &abc_blah_sha1, &abc_blah_sha224, &abc_blah_md5); + +- /* +- * three HMAC-md5 examples from RFC 2104 +- */ +- t_md5hmac(&rfc2104_1, &rfc2104_1_hmac); +- t_md5hmac(&rfc2104_2, &rfc2104_2_hmac); +- t_md5hmac(&rfc2104_3, &rfc2104_3_hmac); ++ if (isc_md5_available()) { ++ /* ++ * three HMAC-md5 examples from RFC 2104 ++ */ ++ t_md5hmac(&rfc2104_1, &rfc2104_1_hmac); ++ t_md5hmac(&rfc2104_2, &rfc2104_2_hmac); ++ t_md5hmac(&rfc2104_3, &rfc2104_3_hmac); ++ } + + /* + * four HMAC-SHA tests from RFC 4634 starting on page 86 +diff --git a/lib/bind9/check.c b/lib/bind9/check.c +index 5131b521f4..00c4b3e509 100644 +--- a/lib/bind9/check.c ++++ b/lib/bind9/check.c +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1895,6 +1896,13 @@ bind9_check_key(const cfg_obj_t *key, isc_log_t *logctx) { + } + + algorithm = cfg_obj_asstring(algobj); ++ /* Skip hmac-md5* algorithms */ ++ if (isc_md5_available() == ISC_FALSE && ++ strncasecmp(algorithm, "hmac-md5", 8) == 0) { ++ cfg_obj_log(algobj, logctx, ISC_LOG_ERROR, ++ "disabled algorithm '%s'", algorithm); ++ return (ISC_R_DISABLED); ++ } + for (i = 0; algorithms[i].name != NULL; i++) { + len = strlen(algorithms[i].name); + if (strncasecmp(algorithms[i].name, algorithm, len) == 0 && +diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c +index e71f2026e5..e6375cf406 100644 +--- a/lib/dns/dst_api.c ++++ b/lib/dns/dst_api.c +@@ -202,6 +202,12 @@ dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, + dst_result_register(); + + memset(dst_t_func, 0, sizeof(dst_t_func)); ++ ++#ifdef OPENSSL ++ RETERR(dst__openssl_init(engine)); ++#elif PKCS11CRYPTO ++ RETERR(dst__pkcs11_init(mctx, engine)); ++#endif + RETERR(dst__hmacmd5_init(&dst_t_func[DST_ALG_HMACMD5])); + RETERR(dst__hmacsha1_init(&dst_t_func[DST_ALG_HMACSHA1])); + RETERR(dst__hmacsha224_init(&dst_t_func[DST_ALG_HMACSHA224])); +@@ -209,7 +215,6 @@ dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, + RETERR(dst__hmacsha384_init(&dst_t_func[DST_ALG_HMACSHA384])); + RETERR(dst__hmacsha512_init(&dst_t_func[DST_ALG_HMACSHA512])); + #ifdef OPENSSL +- RETERR(dst__openssl_init(engine)); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSAMD5], + DST_ALG_RSAMD5)); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA1], +@@ -233,12 +238,16 @@ dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, + RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA384])); + #endif + #elif PKCS11CRYPTO +- RETERR(dst__pkcs11_init(mctx, engine)); +- RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSAMD5])); +- RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA1])); +- RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1])); +- RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA256])); +- RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA512])); ++ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSAMD5], ++ DST_ALG_RSAMD5)); ++ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA1], ++ DST_ALG_RSASHA1)); ++ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1], ++ DST_ALG_NSEC3RSASHA1)); ++ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA256], ++ DST_ALG_RSASHA256)); ++ RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA512], ++ DST_ALG_RSASHA512)); + RETERR(dst__pkcs11dsa_init(&dst_t_func[DST_ALG_DSA])); + RETERR(dst__pkcs11dsa_init(&dst_t_func[DST_ALG_NSEC3DSA])); + RETERR(dst__pkcs11dh_init(&dst_t_func[DST_ALG_DH])); +diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h +index b15135e634..66d322a278 100644 +--- a/lib/dns/dst_internal.h ++++ b/lib/dns/dst_internal.h +@@ -232,7 +232,8 @@ isc_result_t dst__hmacsha384_init(struct dst_func **funcp); + isc_result_t dst__hmacsha512_init(struct dst_func **funcp); + isc_result_t dst__opensslrsa_init(struct dst_func **funcp, + unsigned char algorithm); +-isc_result_t dst__pkcs11rsa_init(struct dst_func **funcp); ++isc_result_t dst__pkcs11rsa_init(struct dst_func **funcp, ++ unsigned char algorithm); + isc_result_t dst__openssldsa_init(struct dst_func **funcp); + isc_result_t dst__pkcs11dsa_init(struct dst_func **funcp); + isc_result_t dst__openssldh_init(struct dst_func **funcp); +diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c +index ec622d9603..0e889cc740 100644 +--- a/lib/dns/dst_parse.c ++++ b/lib/dns/dst_parse.c +@@ -40,6 +40,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -353,6 +354,10 @@ check_data(const dst_private_t *priv, const unsigned int alg, + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (alg) { + case DST_ALG_RSAMD5: ++ if (isc_md5_available()) ++ return (check_rsa(priv, external)); ++ else ++ return (DST_R_UNSUPPORTEDALG); + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: +@@ -369,7 +374,10 @@ check_data(const dst_private_t *priv, const unsigned int alg, + case DST_ALG_ECDSA384: + return (check_ecdsa(priv, external)); + case DST_ALG_HMACMD5: +- return (check_hmac_md5(priv, old)); ++ if (isc_md5_available()) ++ return (check_hmac_md5(priv, old)); ++ else ++ return (DST_R_UNSUPPORTEDALG); + case DST_ALG_HMACSHA1: + return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg)); + case DST_ALG_HMACSHA224: +@@ -587,6 +595,8 @@ dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex, + goto fail; + } + ++ if (isc_md5_available() == ISC_FALSE && alg == DST_ALG_RSA) ++ alg = DST_ALG_RSASHA1; + check = check_data(priv, alg, ISC_TRUE, external); + if (check < 0) { + ret = DST_R_INVALIDPRIVATEKEY; +diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c +index 3ac01a8c20..6b6801af1c 100644 +--- a/lib/dns/hmac_link.c ++++ b/lib/dns/hmac_link.c +@@ -340,7 +340,7 @@ static dst_func_t hmacmd5_functions = { + isc_result_t + dst__hmacmd5_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); +- if (*funcp == NULL) ++ if (*funcp == NULL && isc_md5_available()) + *funcp = &hmacmd5_functions; + return (ISC_R_SUCCESS); + } +diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c +index 53c6d4bdd9..08291af016 100644 +--- a/lib/dns/opensslrsa_link.c ++++ b/lib/dns/opensslrsa_link.c +@@ -1484,6 +1484,10 @@ dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) { + + if (*funcp == NULL) { + switch (algorithm) { ++ case DST_ALG_RSAMD5: ++ if (isc_md5_available()) ++ *funcp = &opensslrsa_functions; ++ break; + case DST_ALG_RSASHA256: + #if defined(HAVE_EVP_SHA256) || !USE_EVP + *funcp = &opensslrsa_functions; +diff --git a/lib/dns/pkcs11rsa_link.c b/lib/dns/pkcs11rsa_link.c +index 010d4b64fc..6b556bcd11 100644 +--- a/lib/dns/pkcs11rsa_link.c ++++ b/lib/dns/pkcs11rsa_link.c +@@ -89,6 +89,9 @@ pkcs11rsa_createctx_sign(dst_key_t *key, dst_context_t *dctx) { + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); + ++ if (key->key_alg == DST_ALG_RSAMD5 && isc_md5_available() == ISC_FALSE) ++ return (ISC_R_FAILURE); ++ + rsa = key->keydata.pkey; + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, +@@ -216,6 +219,8 @@ pkcs11rsa_createctx_sign(dst_key_t *key, dst_context_t *dctx) { + + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: ++ if (isc_md5_available() == ISC_FALSE) ++ return (ISC_R_FAILURE); + mech.mechanism = CKM_MD5_RSA_PKCS; + break; + case DST_ALG_RSASHA1: +@@ -297,6 +302,9 @@ pkcs11rsa_createctx_verify(dst_key_t *key, unsigned int maxbits, + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); + ++ if (key->key_alg == DST_ALG_RSAMD5 && isc_md5_available() == ISC_FALSE) ++ return (ISC_R_FAILURE); ++ + rsa = key->keydata.pkey; + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, +@@ -350,6 +358,8 @@ pkcs11rsa_createctx_verify(dst_key_t *key, unsigned int maxbits, + + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: ++ if (isc_md5_available() == ISC_FALSE) ++ return (ISC_R_FAILURE); + mech.mechanism = CKM_MD5_RSA_PKCS; + break; + case DST_ALG_RSASHA1: +@@ -1565,11 +1575,20 @@ static dst_func_t pkcs11rsa_functions = { + }; + + isc_result_t +-dst__pkcs11rsa_init(dst_func_t **funcp) { ++dst__pkcs11rsa_init(dst_func_t **funcp, unsigned char algorithm) { + REQUIRE(funcp != NULL); + +- if (*funcp == NULL) +- *funcp = &pkcs11rsa_functions; ++ if (*funcp == NULL) { ++ switch (algorithm) { ++ case DST_ALG_RSAMD5: ++ if (isc_md5_available()) ++ *funcp = &pkcs11rsa_functions; ++ break; ++ default: ++ *funcp = &pkcs11rsa_functions; ++ break; ++ } ++ } + return (ISC_R_SUCCESS); + } + +diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c +index 091b3c70fb..c1251225d5 100644 +--- a/lib/dns/rcode.c ++++ b/lib/dns/rcode.c +@@ -21,6 +21,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -310,17 +311,31 @@ dns_cert_totext(dns_cert_t cert, isc_buffer_t *target) { + return (dns_mnemonic_totext(cert, target, certs)); + } + ++static inline struct tbl * ++secalgs_tbl_start() { ++ struct tbl *algs = secalgs; ++ ++ if (isc_md5_available() == ISC_FALSE) { ++ while (algs->name != NULL && ++ algs->value == DNS_KEYALG_RSAMD5) ++ ++algs; ++ } ++ return algs; ++} ++ + isc_result_t + dns_secalg_fromtext(dns_secalg_t *secalgp, isc_textregion_t *source) { + unsigned int value; +- RETERR(dns_mnemonic_fromtext(&value, source, secalgs, 0xff)); ++ ++ RETERR(dns_mnemonic_fromtext(&value, source, ++ secalgs_tbl_start(), 0xff)); + *secalgp = value; + return (ISC_R_SUCCESS); + } + + isc_result_t + dns_secalg_totext(dns_secalg_t secalg, isc_buffer_t *target) { +- return (dns_mnemonic_totext(secalg, target, secalgs)); ++ return (dns_mnemonic_totext(secalg, target, secalgs_tbl_start())); + } + + void +diff --git a/lib/dns/tests/tsig_test.c b/lib/dns/tests/tsig_test.c +index 956e4a0469..c4b2f84837 100644 +--- a/lib/dns/tests/tsig_test.c ++++ b/lib/dns/tests/tsig_test.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + + #include +diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c +index 7343b4c26f..59a78c8dca 100644 +--- a/lib/dns/tkey.c ++++ b/lib/dns/tkey.c +@@ -230,6 +230,9 @@ compute_secret(isc_buffer_t *shared, isc_region_t *queryrandomness, + unsigned char digests[32]; + unsigned int i; + ++ if (isc_md5_available() == ISC_FALSE) ++ return (ISC_R_NOTIMPLEMENTED); ++ + isc_buffer_usedregion(shared, &r); + + /* +@@ -298,6 +301,12 @@ process_dhtkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name, + return (DNS_R_REFUSED); + } + ++ if (isc_md5_available() == ISC_FALSE) { ++ tkey_log("process_dhtkey: MD5 was disabled"); ++ tkeyout->error = dns_tsigerror_badalg; ++ return (ISC_R_SUCCESS); ++ } ++ + if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_HMACMD5_NAME)) { + tkey_log("process_dhtkey: algorithms other than " + "hmac-md5 are not supported"); +diff --git a/lib/dns/tsec.c b/lib/dns/tsec.c +index bfa6195d0d..fad645668c 100644 +--- a/lib/dns/tsec.c ++++ b/lib/dns/tsec.c +@@ -18,6 +18,7 @@ + + #include + ++#include + #include + + #include +@@ -66,7 +67,12 @@ dns_tsec_create(isc_mem_t *mctx, dns_tsectype_t type, dst_key_t *key, + case dns_tsectype_tsig: + switch (dst_key_alg(key)) { + case DST_ALG_HMACMD5: +- algname = dns_tsig_hmacmd5_name; ++ if (isc_md5_available()) { ++ algname = dns_tsig_hmacmd5_name; ++ } else { ++ isc_mem_put(mctx, tsec, sizeof(*tsec)); ++ return (DNS_R_BADALG); ++ } + break; + case DST_ALG_HMACSHA1: + algname = dns_tsig_hmacsha1_name; +diff --git a/lib/dns/tsig.c b/lib/dns/tsig.c +index 325c901f93..804ef821f0 100644 +--- a/lib/dns/tsig.c ++++ b/lib/dns/tsig.c +@@ -24,6 +24,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -316,7 +317,7 @@ dns_tsigkey_createfromkey(dns_name_t *name, dns_name_t *algorithm, + goto cleanup_key; + (void)dns_name_downcase(&tkey->name, &tkey->name, NULL); + +- if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME)) { ++ if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME) && isc_md5_available()) { + tkey->algorithm = DNS_TSIG_HMACMD5_NAME; + if (dstkey != NULL && dst_key_alg(dstkey) != DST_ALG_HMACMD5) { + ret = DNS_R_BADALG; +@@ -539,7 +540,7 @@ destroyring(dns_tsig_keyring_t *ring) { + + static unsigned int + dst_alg_fromname(dns_name_t *algorithm) { +- if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME)) { ++ if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME) && isc_md5_available()) { + return (DST_ALG_HMACMD5); + } else if (dns_name_equal(algorithm, DNS_TSIG_HMACSHA1_NAME)) { + return (DST_ALG_HMACSHA1); +@@ -724,7 +725,7 @@ dns_tsigkey_create(dns_name_t *name, dns_name_t *algorithm, + if (length > 0) + REQUIRE(secret != NULL); + +- if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME)) { ++ if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME) && isc_md5_available()) { + if (secret != NULL) { + isc_buffer_t b; + +@@ -1322,7 +1323,8 @@ dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg, + ret = dst_key_sigsize(key, &siglen); + if (ret != ISC_R_SUCCESS) + return (ret); +- if (alg == DST_ALG_HMACMD5 || alg == DST_ALG_HMACSHA1 || ++ if ((alg == DST_ALG_HMACMD5 && isc_md5_available()) || ++ alg == DST_ALG_HMACSHA1 || + alg == DST_ALG_HMACSHA224 || alg == DST_ALG_HMACSHA256 || + alg == DST_ALG_HMACSHA384 || alg == DST_ALG_HMACSHA512) + { +@@ -1484,9 +1486,7 @@ dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg, + } + + if ( +-#ifndef PK11_MD5_DISABLE +- alg == DST_ALG_HMACMD5 || +-#endif ++ (alg == DST_ALG_HMACMD5 && isc_md5_available()) || + alg == DST_ALG_HMACSHA1 || + alg == DST_ALG_HMACSHA224 || alg == DST_ALG_HMACSHA256 || + alg == DST_ALG_HMACSHA384 || alg == DST_ALG_HMACSHA512) +@@ -1626,9 +1626,7 @@ tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg) { + if (ret != ISC_R_SUCCESS) + goto cleanup_querystruct; + if ( +-#ifndef PK11_MD5_DISABLE +- alg == DST_ALG_HMACMD5 || +-#endif ++ (alg == DST_ALG_HMACMD5 && isc_md5_available()) || + alg == DST_ALG_HMACSHA1 || + alg == DST_ALG_HMACSHA224 || + alg == DST_ALG_HMACSHA256 || +@@ -1801,9 +1799,7 @@ tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg) { + if (ret != ISC_R_SUCCESS) + goto cleanup_context; + if ( +-#ifndef PK11_MD5_DISABLE +- alg == DST_ALG_HMACMD5 || +-#endif ++ (alg == DST_ALG_HMACMD5 && isc_md5_available()) || + alg == DST_ALG_HMACSHA1 || + alg == DST_ALG_HMACSHA224 || + alg == DST_ALG_HMACSHA256 || +diff --git a/lib/isc/include/isc/md5.h b/lib/isc/include/isc/md5.h +index a2e00b382a..d4c2b8ab48 100644 +--- a/lib/isc/include/isc/md5.h ++++ b/lib/isc/include/isc/md5.h +@@ -83,6 +83,9 @@ isc_md5_update(isc_md5_t *ctx, const unsigned char *buf, unsigned int len); + void + isc_md5_final(isc_md5_t *ctx, unsigned char *digest); + ++isc_boolean_t ++isc_md5_available(void); ++ + ISC_LANG_ENDDECLS + + #endif /* ISC_MD5_H */ +diff --git a/lib/isc/md5.c b/lib/isc/md5.c +index 2e3cf9a4a3..a5113df4b8 100644 +--- a/lib/isc/md5.c ++++ b/lib/isc/md5.c +@@ -38,6 +38,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -51,6 +52,9 @@ + + #ifdef ISC_PLATFORM_OPENSSLHASH + ++static isc_once_t available_once = ISC_ONCE_INIT; ++static isc_boolean_t available = ISC_FALSE; ++ + void + isc_md5_init(isc_md5_t *ctx) { + EVP_DigestInit(ctx, EVP_md5()); +@@ -71,8 +75,33 @@ isc_md5_final(isc_md5_t *ctx, unsigned char *digest) { + EVP_DigestFinal(ctx, digest, NULL); + } + ++static void ++do_detect_available() { ++ isc_md5_t local; ++ isc_md5_t *ctx = &local; ++ unsigned char digest[ISC_MD5_DIGESTLENGTH]; ++ ++ ctx->ctx = EVP_MD_CTX_new(); ++ RUNTIME_CHECK(ctx->ctx != NULL); ++ available = ISC_TF(EVP_DigestInit(ctx->ctx, EVP_md5()) == 1); ++ if (available) ++ (void)EVP_DigestFinal(ctx->ctx, digest, NULL); ++ EVP_MD_CTX_free(ctx->ctx); ++ ctx->ctx = NULL; ++} ++ ++isc_boolean_t ++isc_md5_available() { ++ RUNTIME_CHECK(isc_once_do(&available_once, do_detect_available) ++ == ISC_R_SUCCESS); ++ return available; ++} ++ + #elif PKCS11CRYPTO + ++static isc_once_t available_once = ISC_ONCE_INIT; ++static isc_boolean_t available = ISC_FALSE; ++ + void + isc_md5_init(isc_md5_t *ctx) { + CK_RV rv; +@@ -115,6 +144,31 @@ isc_md5_final(isc_md5_t *ctx, unsigned char *digest) { + pk11_return_session(ctx); + } + ++static void ++do_detect_available() { ++ isc_md5_t local; ++ isc_md5_t *ctx = &local; ++ CK_RV rv; ++ CK_MECHANISM mech = { CKM_MD5, NULL, 0 }; ++ ++ if (pk11_get_session(ctx, OP_DIGEST, ISC_TRUE, ISC_FALSE, ++ ISC_FALSE, NULL, 0) == ISC_R_SUCCESS) ++ { ++ rv = pkcs_C_DigestInit(ctx->session, &mech); ++ isc_md5_invalidate(ctx); ++ available = (ISC_TF(rv == CKR_OK)); ++ } else { ++ available = ISC_FALSE; ++ } ++} ++ ++isc_boolean_t ++isc_md5_available() { ++ RUNTIME_CHECK(isc_once_do(&available_once, do_detect_available) ++ == ISC_R_SUCCESS); ++ return available; ++} ++ + #else + + static void +@@ -324,4 +378,9 @@ isc_md5_final(isc_md5_t *ctx, unsigned char *digest) { + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(isc_md5_t)); /* In case it's sensitive */ + } ++ ++isc_boolean_t ++isc_md5_available() { ++ return ISC_TRUE; ++} + #endif +diff --git a/lib/isc/pk11.c b/lib/isc/pk11.c +index de4479b7b0..6cce70f0cf 100644 +--- a/lib/isc/pk11.c ++++ b/lib/isc/pk11.c +@@ -139,6 +139,8 @@ + #define PK11_NO_LOGERR 1 + #endif + ++LIBISC_EXTERNAL_DATA isc_boolean_t pk11_verbose_init = ISC_FALSE; ++ + static isc_once_t once = ISC_ONCE_INIT; + static isc_mem_t *pk11_mctx = NULL; + static isc_int32_t allocsize = 0; +@@ -283,13 +285,12 @@ pk11_initialize(isc_mem_t *mctx, const char *engine) { + LOCK(&alloclock); + if ((mctx != NULL) && (pk11_mctx == NULL) && (allocsize == 0)) + isc_mem_attach(mctx, &pk11_mctx); ++ UNLOCK(&alloclock); ++ ++ LOCK(&sessionlock); + if (initialized) { +- UNLOCK(&alloclock); +- return (ISC_R_SUCCESS); +- } else { +- LOCK(&sessionlock); +- initialized = ISC_TRUE; +- UNLOCK(&alloclock); ++ result = ISC_R_SUCCESS; ++ goto unlock; + } + + ISC_LIST_INIT(tokens); +@@ -327,6 +328,7 @@ pk11_initialize(isc_mem_t *mctx, const char *engine) { + } + #endif + #endif /* PKCS11CRYPTO */ ++ initialized = ISC_TRUE; + result = ISC_R_SUCCESS; + unlock: + UNLOCK(&sessionlock); +@@ -363,9 +365,14 @@ pk11_finalize(void) { + pk11_mem_put(token, sizeof(*token)); + token = next; + } ++ LOCK(&alloclock); + if (pk11_mctx != NULL) + isc_mem_detach(&pk11_mctx); ++ UNLOCK(&alloclock); ++ ++ LOCK(&sessionlock); + initialized = ISC_FALSE; ++ UNLOCK(&sessionlock); + return (ret); + } + +@@ -655,6 +662,15 @@ token_login(pk11_session_t *sp) { + return (ret); + } + ++#define PK11_TRACE(fmt) \ ++ if (pk11_verbose_init) fprintf(stderr, fmt) ++#define PK11_TRACE1(fmt, arg) \ ++ if (pk11_verbose_init) fprintf(stderr, fmt, arg) ++#define PK11_TRACE2(fmt, arg1, arg2) \ ++ if (pk11_verbose_init) fprintf(stderr, fmt, arg1, arg2) ++#define PK11_TRACEM(mech) \ ++ if (pk11_verbose_init) fprintf(stderr, #mech ": 0x%lx\n", rv) ++ + static void + choose_slots(void) { + CK_MECHANISM_INFO mechInfo; +@@ -665,6 +681,8 @@ choose_slots(void) { + CK_ULONG slotCount; + pk11_token_t *token; + unsigned int i; ++ unsigned int best_rsa_algorithms = 0; ++ unsigned int best_digest_algorithms = 0; + + slotCount = 0; + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, NULL_PTR, &slotCount)); +@@ -676,6 +694,8 @@ choose_slots(void) { + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, slotList, &slotCount)); + + for (i = 0; i < slotCount; i++) { ++ unsigned int rsa_algorithms = 0; ++ unsigned int digest_algorithms = 0; + slot = slotList[i]; + + rv = pkcs_C_GetTokenInfo(slot, &tokenInfo); +@@ -708,8 +728,11 @@ choose_slots(void) { + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_SIGN) == 0) || +- ((mechInfo.flags & CKF_VERIFY) == 0)) +- goto try_dsa; ++ ((mechInfo.flags & CKF_VERIFY) == 0)) { ++ PK11_TRACEM(CKM_MD5_RSA_PKCS); ++ } ++ else ++ ++rsa_algorithms; + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA1_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || +@@ -729,8 +752,14 @@ choose_slots(void) { + ((mechInfo.flags & CKF_VERIFY) == 0)) + goto try_dsa; + token->operations |= 1 << OP_RSA; +- if (best_rsa_token == NULL) ++ if (best_rsa_token == NULL) { + best_rsa_token = token; ++ best_rsa_algorithms = rsa_algorithms; ++ } else if (rsa_algorithms > best_rsa_algorithms) { ++ pk11_mem_put(best_rsa_token, sizeof(*best_rsa_token)); ++ best_rsa_token = token; ++ best_rsa_algorithms = rsa_algorithms; ++ } + + try_dsa: + rv = pkcs_C_GetMechanismInfo(slot, CKM_DSA_PARAMETER_GEN, +@@ -773,8 +802,11 @@ choose_slots(void) { + + try_digest: + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5, &mechInfo); +- if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) +- continue; ++ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) { ++ PK11_TRACEM(CKM_MD5); ++ } ++ else ++ ++digest_algorithms; + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA_1, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_DIGEST) == 0)) + continue; +@@ -792,13 +824,15 @@ choose_slots(void) { + continue; + #ifdef PKCS11CRYPTOWITHHMAC + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5_HMAC, &mechInfo); +- if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) +- continue; ++ if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) { ++ PK11_TRACEM(CKM_MD5_HMAC); ++ } ++ else ++ ++digest_algorithms; + #endif + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA_1_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) + continue; +- rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA224_HMAC, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) + continue; + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256_HMAC, &mechInfo); +@@ -811,8 +845,14 @@ choose_slots(void) { + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0)) + continue; + token->operations |= 1 << OP_DIGEST; +- if (digest_token == NULL) +- digest_token = token; ++ if (digest_token == NULL) { ++ digest_token = token; ++ best_digest_algorithms = digest_algorithms; ++ } else if (digest_algorithms > best_digest_algorithms) { ++ pk11_mem_put(digest_token, sizeof(*digest_token)); ++ digest_token = token; ++ best_digest_algorithms = digest_algorithms; ++ } + + /* ECDSA requires digest */ + rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_KEY_PAIR_GEN, +diff --git a/lib/isccc/cc.c b/lib/isccc/cc.c +index 9428374cc1..ca3cff27c9 100644 +--- a/lib/isccc/cc.c ++++ b/lib/isccc/cc.c +@@ -254,11 +254,15 @@ sign(unsigned char *data, unsigned int length, unsigned char *hmac, + + switch (algorithm) { + case ISCCC_ALG_HMACMD5: +- isc_hmacmd5_init(&ctx.hmd5, secret->rstart, +- REGION_SIZE(*secret)); +- isc_hmacmd5_update(&ctx.hmd5, data, length); +- isc_hmacmd5_sign(&ctx.hmd5, digest); +- source.rend = digest + ISC_MD5_DIGESTLENGTH; ++ if (isc_md5_available()) { ++ isc_hmacmd5_init(&ctx.hmd5, secret->rstart, ++ REGION_SIZE(*secret)); ++ isc_hmacmd5_update(&ctx.hmd5, data, length); ++ isc_hmacmd5_sign(&ctx.hmd5, digest); ++ source.rend = digest + ISC_MD5_DIGESTLENGTH; ++ } else { ++ return (ISC_R_FAILURE); ++ } + break; + + case ISCCC_ALG_HMACSHA1: +@@ -329,14 +333,14 @@ isccc_cc_towire(isccc_sexpr_t *alist, isccc_region_t *target, + { + unsigned char *hmac_rstart, *signed_rstart; + isc_result_t result; ++ const isc_boolean_t md5 = ISC_TF(algorithm == ISCCC_ALG_HMACMD5); + +- if (algorithm == ISCCC_ALG_HMACMD5) { +- if (REGION_SIZE(*target) < 4 + sizeof(auth_hmd5)) +- return (ISC_R_NOSPACE); +- } else { +- if (REGION_SIZE(*target) < 4 + sizeof(auth_hsha)) +- return (ISC_R_NOSPACE); +- } ++ if (md5 && isc_md5_available() == ISC_FALSE) ++ return (ISC_R_NOTIMPLEMENTED); ++ if (REGION_SIZE(*target) < 4 + ((md5) ? ++ sizeof(auth_hmd5) : ++ sizeof(auth_hsha))) ++ return (ISC_R_NOSPACE); + + /* + * Emit protocol version. +@@ -348,7 +352,7 @@ isccc_cc_towire(isccc_sexpr_t *alist, isccc_region_t *target, + * We'll replace the zeros with the real signature once + * we know what it is. + */ +- if (algorithm == ISCCC_ALG_HMACMD5) { ++ if (md5) { + hmac_rstart = target->rstart + HMD5_OFFSET; + PUT_MEM(auth_hmd5, sizeof(auth_hmd5), target->rstart); + } else { +@@ -404,7 +408,7 @@ verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length, + _auth = isccc_alist_lookup(alist, "_auth"); + if (!isccc_alist_alistp(_auth)) + return (ISC_R_FAILURE); +- if (algorithm == ISCCC_ALG_HMACMD5) ++ if (algorithm == ISCCC_ALG_HMACMD5 && isc_md5_available()) + hmac = isccc_alist_lookup(_auth, "hmd5"); + else + hmac = isccc_alist_lookup(_auth, "hsha"); +@@ -417,12 +421,16 @@ verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length, + target.rstart = digestb64; + switch (algorithm) { + case ISCCC_ALG_HMACMD5: +- isc_hmacmd5_init(&ctx.hmd5, secret->rstart, +- REGION_SIZE(*secret)); +- isc_hmacmd5_update(&ctx.hmd5, data, length); +- isc_hmacmd5_sign(&ctx.hmd5, digest); +- source.rend = digest + ISC_MD5_DIGESTLENGTH; +- break; ++ if (isc_md5_available()) { ++ isc_hmacmd5_init(&ctx.hmd5, secret->rstart, ++ REGION_SIZE(*secret)); ++ isc_hmacmd5_update(&ctx.hmd5, data, length); ++ isc_hmacmd5_sign(&ctx.hmd5, digest); ++ source.rend = digest + ISC_MD5_DIGESTLENGTH; ++ break; ++ } else { ++ return (ISC_R_FAILURE); ++ } + + case ISCCC_ALG_HMACSHA1: + isc_hmacsha1_init(&ctx.hsha, secret->rstart, +-- +2.14.4 + diff --git a/SOURCES/bind99-nta.patch b/SOURCES/bind99-nta.patch new file mode 100644 index 0000000..4d07104 --- /dev/null +++ b/SOURCES/bind99-nta.patch @@ -0,0 +1,8290 @@ +From 819ae187b1e3229baa33f475b940df01766b2a74 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Thu, 9 Nov 2017 15:21:34 +0100 +Subject: [PATCH] Squashed commit of the following: + +commit a1d088bf6565826029187ff89e58694eaa236877 +Author: Mukund Sivaraman +Date: Mon Jan 12 09:04:16 2015 +0530 + + Add NTA persistence (#37087) + + 4034. [func] When added, negative trust anchors (NTA) are now + saved to files (viewname.nta), in order to + persist across restarts of the named server. + [RT #37087] + + (cherry picked from commit a6f0e9c985220f0e4509777e6528afb64e0ad576) + +commit 326288911b82bc42d3fe2b3de7a07e0060ebc840 +Author: Mark Andrews +Date: Sat Oct 18 10:07:24 2014 +1100 + + 3979. [bug] Negative trust anchor fetches where not properly managed. [RT #37488] + + (cherry picked from commit 48f97c23b7d59c925fc3f4280972e50b8ef67c35) + +commit b9facfeab727ae4d6d64cf9f09998b9d4df183ce +Author: Mark Andrews +Date: Thu Jul 10 10:24:47 2014 +1000 + + hold a nta reference while fetching + + (cherry picked from commit dcc7a2738ff7f68535f9d265df61298e4634055c) + +commit a8cf1c6ac1500f7f292ebe5a4d314285f76e8eae +Author: Mark Andrews +Date: Wed Jul 2 14:12:46 2014 +1000 + + be consistent about expire time + + (cherry picked from commit 7dbd30979997031ddd539f3543c6d2f9234f7978) + +commit 0ebf7e0e4b61113a694e88674865cdf086a4b5c1 +Author: Mark Andrews +Date: Thu Jun 19 10:20:34 2014 +1000 + + add #include + + (cherry picked from commit 3a37159a95e961066b90c493c5a4c92d3e0ec029) + +commit 410c729b2a457334a3d2202777c00a1af1f07f82 +Author: Evan Hunt +Date: Wed Jun 18 16:47:22 2014 -0700 + + complete NTA work + + 3882. [func] By default, negative trust anchors will be tested + periodically to see whether data below them can be + validated, and if so, they will be allowed to + expire early. The "rndc nta -force" option + overrides this behvaior. The default NTA lifetime + and the recheck frequency can be configured by the + "nta-lifetime" and "nta-recheck" options. [RT #36146] + + (cherry picked from commit b8a9632333a92d73a503afe1aaa7990016c8bee9) + +commit 0ef07209d5140c2fe395a4a7db25dbb860161e62 +Author: Mark Andrews +Date: Wed Jun 4 11:45:09 2014 +1000 + + bad size on isc_mem_put + + (cherry picked from commit f4db7287da3a6d9d15141d674732d98d39aa2461) + +commit 1d0a452481b92508cd1a7b4de9ab68e22db69a18 +Author: Evan Hunt +Date: Thu May 29 22:22:53 2014 -0700 + + rndc nta + + 3867. [func] "rndc nta" can now be used to set a temporary + negative trust anchor, which disables DNSSEC + validation below a specified name for a specified + period of time (not exceeding 24 hours). This + can be used when validation for a domain is known + to be failing due to a configuration error on + the part of the domain owner rather than a + spoofing attack. [RT #29358] + + (cherry picked from 0cfb24736841b3e98bb25853229a0efabab88bdd) + +delve + +3741. [func] "delve" (domain entity lookup and validation engine): + A new tool with dig-like semantics for performing DNS + lookups, with internal DNSSEC validation, using the + same resolver and validator logic as named. This + allows easy validation of DNSSEC data in environments + with untrustworthy resolvers, and assists with + troubleshooting of DNSSEC problems. (Note: not yet + available on win32.) [RT #32406] + +Additional fixes to adapt to 9.9 codebase + +fix possible rndc secroots crash + +3765. [bug] Fixed a bug in "rndc secroots" that could crash + named when dumping an empty keynode. [RT #35469] + +3761. [bug] Address dangling reference bug in dns_keytable_add. [RT #35471] + +add "rndc nta" options to "rndc -h" output + +rt38571: handle Time::Piece not being supported by perl + +(cherry-picked from 2b4860c4dc2d700c85f1edd16e6d5c27ccdb4080) + +NTAs did not survive reoad/reconfig + +4251. [bug] NTAs were deleted when the server was reconfigured + or reloaded. [RT #41058] + +Log path of nta file used into log file + +Revert some irrelevant parts + +Return NTA persistent not implemented in export libraries + +Fix name of nta file in tests + +update NTA limit to a week + +3940. [func] "rndc nta" now allows negative trust anchors to be + set for up to one week. [RT #37069] + +complete change #3882 + +Parse arguments to "rndc nta" so they can be either +long or shortened (i.e., both "-dump" and "-d" will work). + +"rndc nta -r" could hang + +3930. [bug] "rndc nta -r" could cause a server hang if the + NTA was not found. [RT #36909] + +allow 1-week nta-lifetime/nta-recheck + +3983. [bug] Change #3940 was incomplete: negative trust anchors + could be set to last up to a week, but the + "nta-lifetime" and "nta-recheck" options were + still limted to one day. [RT #37522] + +Address warnings and uninitialized variables + +checking that default nta's were lifted due to lifetime were not robust + +Reset return codes for separate dnssec tests + +oops, nta lifetime change broke dnssec test + +remove remains of file_sanitize not backported + +Always terminate after NTA entry, even in case all entries would not fit +--- + bin/delve/Makefile.in | 84 ++ + bin/delve/delve.1 | 388 ++++++ + bin/delve/delve.c | 1609 ++++++++++++++++++++++ + bin/delve/delve.docbook | 639 +++++++++ + bin/delve/delve.html | 445 ++++++ + bin/named/config.c | 2 + + bin/named/control.c | 2 + + bin/named/include/named/control.h | 1 + + bin/named/include/named/server.h | 19 + + bin/named/server.c | 343 ++++- + bin/rndc/rndc.c | 12 + + bin/rndc/rndc.docbook | 82 +- + bin/tests/system/checkconf/good.conf | 2 + + bin/tests/system/checkconf/tests.sh | 2 - + bin/tests/system/dlvauto/tests.sh | 2 +- + bin/tests/system/dnssec/clean.sh | 1 + + bin/tests/system/dnssec/ns2/example.db.in | 4 + + bin/tests/system/dnssec/ns2/sign.sh | 9 +- + bin/tests/system/dnssec/ns3/bogus.example.db.in | 1 + + bin/tests/system/dnssec/ns3/named.conf | 6 + + bin/tests/system/dnssec/ns3/secure.example.db.in | 4 + + bin/tests/system/dnssec/ns3/sign.sh | 16 + + bin/tests/system/dnssec/ns4/named1.conf | 3 + + bin/tests/system/dnssec/ntadiff.pl | 13 + + bin/tests/system/dnssec/setup.sh | 2 + + bin/tests/system/dnssec/tests.sh | 396 +++++- + bin/tests/system/rndc/tests.sh | 6 + + configure.in | 3 + + doc/arm/Bv9ARM-book.xml | 74 +- + lib/bind9/check.c | 33 + + lib/dns/Makefile.in | 7 +- + lib/dns/adb.c | 6 +- + lib/dns/client.c | 82 +- + lib/dns/include/dns/client.h | 26 +- + lib/dns/include/dns/keytable.h | 10 +- + lib/dns/include/dns/log.h | 2 + + lib/dns/include/dns/masterdump.h | 9 + + lib/dns/include/dns/message.h | 12 + + lib/dns/include/dns/nta.h | 220 +++ + lib/dns/include/dns/resolver.h | 5 +- + lib/dns/include/dns/result.h | 3 +- + lib/dns/include/dns/types.h | 2 + + lib/dns/include/dns/validator.h | 7 +- + lib/dns/include/dns/view.h | 88 +- + lib/dns/keytable.c | 16 +- + lib/dns/log.c | 2 + + lib/dns/masterdump.c | 26 + + lib/dns/message.c | 48 + + lib/dns/nta.c | 683 +++++++++ + lib/dns/resolver.c | 135 +- + lib/dns/result.c | 3 +- + lib/dns/tests/Makefile.in | 10 +- + lib/dns/tests/dnstest.c | 22 +- + lib/dns/tests/dnstest.h | 3 + + lib/dns/tests/keytable_test.c | 611 ++++++++ + lib/dns/validator.c | 52 +- + lib/dns/view.c | 237 +++- + lib/export/dns/Makefile.in | 4 +- + lib/isc/include/isc/log.h | 6 +- + lib/isc/log.c | 20 +- + lib/isc/unix/app.c | 1 - + lib/isccfg/namedconf.c | 45 + + make/includes.in | 3 + + make/rules.in | 3 +- + 64 files changed, 6453 insertions(+), 159 deletions(-) + create mode 100644 bin/delve/Makefile.in + create mode 100644 bin/delve/delve.1 + create mode 100644 bin/delve/delve.c + create mode 100644 bin/delve/delve.docbook + create mode 100644 bin/delve/delve.html + create mode 100755 bin/tests/system/dnssec/ntadiff.pl + create mode 100644 lib/dns/include/dns/nta.h + create mode 100644 lib/dns/nta.c + create mode 100644 lib/dns/tests/keytable_test.c + +diff --git a/bin/delve/Makefile.in b/bin/delve/Makefile.in +new file mode 100644 +index 0000000000..bfd5adfea6 +--- /dev/null ++++ b/bin/delve/Makefile.in +@@ -0,0 +1,84 @@ ++# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++srcdir = @srcdir@ ++VPATH = @srcdir@ ++top_srcdir = @top_srcdir@ ++ ++@BIND9_VERSION@ ++ ++@BIND9_MAKE_INCLUDES@ ++ ++CINCLUDES = -I${srcdir}/include ${DNS_INCLUDES} ${ISC_INCLUDES} \ ++ ${IRS_INCLUDES} ${ISCCFG_INCLUDES} ++ ++CDEFINES = -DVERSION=\"${VERSION}\" -DSYSCONFDIR=\"${sysconfdir}\" ++CWARNINGS = ++ ++ISCCFGLIBS = ../../lib/export/isccfg/libisccfg-export.@A@ ++DNSLIBS = ../../lib/export/dns/libdns-export.@A@ @DNS_CRYPTO_LIBS@ ++ISCNOSYMLIBS = ../../lib/export/isc/libisc-export.@A@ ++ISCLIBS = ../../lib/export/isc/libisc-export.@A@ ++IRSLIBS = ../../lib/export/irs/libirs-export.@A@ ++ ++ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@ ++DNSDEPLIBS = ../../lib/export/dns/libdns-export.@A@ ++ISCDEPLIBS = ../../lib/export/isc/libisc-export.@A@ ++IRSDEPLIBS = ../../lib/export/irs/libirs-export.@A@ ++ ++DEPLIBS = ${DNSDEPLIBS} ${IRSDEPLIBS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} ++ ++LIBS = ${DNSLIBS} ${IRSLIBS} ${ISCCFGLIBS} ${ISCLIBS} @LIBS@ ++NOSYMLIBS = ${DNSLIBS} ${IRSLIBS} ${ISCCFGLIBS} ${ISCNOSYMLIBS} @LIBS@ ++ ++SUBDIRS = ++ ++TARGETS = delve@EXEEXT@ ++ ++OBJS = delve.@O@ ++ ++SRCS = delve.c ++ ++MANPAGES = delve.1 ++ ++HTMLPAGES = delve.html ++ ++MANOBJS = ${MANPAGES} ${HTMLPAGES} ++ ++@BIND9_MAKE_RULES@ ++ ++delve@EXEEXT@: delve.@O@ ${DEPLIBS} ++ export BASEOBJS="delve.@O@"; \ ++ export LIBS0="${DNSLIBS}"; \ ++ ${FINALBUILDCMD} ++ ++# ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ ++# delve.@O@ ${LIBS} ++ ++installdirs: ++ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${bindir} ++ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man1 ++ ++install:: delve@EXEEXT@ ++ ${LIBTOOL_MODE_INSTALL} ${INSTALL_PROGRAM} \ ++ delve@EXEEXT@ ${DESTDIR}${bindir} ++ ${INSTALL_DATA} ${srcdir}/delve.1 ${DESTDIR}${mandir}/man1 ++ ++doc man:: ${MANOBJS} ++ ++docclean manclean maintainer-clean:: ++ rm -f ${MANOBJS} ++ ++clean distclean maintainer-clean:: ++ rm -f ${TARGETS} +diff --git a/bin/delve/delve.1 b/bin/delve/delve.1 +new file mode 100644 +index 0000000000..54abfcce7e +--- /dev/null ++++ b/bin/delve/delve.1 +@@ -0,0 +1,388 @@ ++.\" Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") ++.\" ++.\" Permission to use, copy, modify, and/or distribute this software for any ++.\" purpose with or without fee is hereby granted, provided that the above ++.\" copyright notice and this permission notice appear in all copies. ++.\" ++.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++.\" AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++.\" OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++.\" PERFORMANCE OF THIS SOFTWARE. ++.\" ++.\" $Id$ ++.\" ++.hy 0 ++.ad l ++.\" Title: delve ++.\" Author: ++.\" Generator: DocBook XSL Stylesheets v1.71.1 ++.\" Date: February 12, 2014 ++.\" Manual: BIND9 ++.\" Source: BIND9 ++.\" ++.TH "DELVE" "1" "February 12, 2014" "BIND9" "BIND9" ++.\" disable hyphenation ++.nh ++.\" disable justification (adjust text to left margin only) ++.ad l ++.SH "NAME" ++delve \- DNS lookup and validation utility ++.SH "SYNOPSIS" ++.HP 6 ++\fBdelve\fR [@server] [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIanchor\-file\fR\fR] [\fB\-b\ \fR\fB\fIaddress\fR\fR] [\fB\-c\ \fR\fB\fIclass\fR\fR] [\fB\-d\ \fR\fB\fIlevel\fR\fR] [\fB\-i\fR] [\fB\-m\fR] [\fB\-p\ \fR\fB\fIport#\fR\fR] [\fB\-q\ \fR\fB\fIname\fR\fR] [\fB\-t\ \fR\fB\fItype\fR\fR] [\fB\-x\ \fR\fB\fIaddr\fR\fR] [name] [type] [class] [queryopt...] ++.HP 6 ++\fBdelve\fR [\fB\-h\fR] ++.HP 6 ++\fBdelve\fR [queryopt...] [query...] ++.SH "DESCRIPTION" ++.PP ++\fBdelve\fR ++(Domain Entity Lookup & Validation Engine) is a tool for sending DNS queries and validating the results, using the the same internal resolver and validator logic as ++\fBnamed\fR. ++.PP ++\fBdelve\fR ++will send to a specified name server all queries needed to fetch and validate the requested data; this includes the original requested query, subsequent queries to follow CNAME or DNAME chains, and queries for DNSKEY, DS and DLV records to establish a chain of trust for DNSSEC validation. It does not perform iterative resolution, but simulates the behavior of a name server configured for DNSSEC validating and forwarding. ++.PP ++By default, responses are validated using built\-in DNSSEC trust anchors for the root zone (".") and for the ISC DNSSEC lookaside validation zone ("dlv.isc.org"). Records returned by ++\fBdelve\fR ++are either fully validated or were not signed. If validation fails, an explanation of the failure is included in the output; the validation process can be traced in detail. Because ++\fBdelve\fR ++does not rely on an external server to carry out validation, it can be used to check the validity of DNS responses in environments where local name servers may not be trustworthy. ++.PP ++Unless it is told to query a specific name server, ++\fBdelve\fR ++will try each of the servers listed in ++\fI/etc/resolv.conf\fR. If no usable server addresses are found, ++\fBdelve\fR ++will send queries to the localhost addresses (127.0.0.1 for IPv4, ::1 for IPv6). ++.PP ++When no command line arguments or options are given, ++\fBdelve\fR ++will perform an NS query for "." (the root zone). ++.SH "SIMPLE USAGE" ++.PP ++A typical invocation of ++\fBdelve\fR ++looks like: ++.sp ++.RS 4 ++.nf ++ delve @server name type ++.fi ++.RE ++.sp ++where: ++.PP ++\fBserver\fR ++.RS 4 ++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 ++\fIserver\fR ++argument is a hostname, ++\fBdelve\fR ++resolves that name before querying that name server (note, however, that this initial lookup is ++\fInot\fR ++validated by DNSSEC). ++.sp ++If no ++\fIserver\fR ++argument is provided, ++\fBdelve\fR ++consults ++\fI/etc/resolv.conf\fR; if an address is found there, it queries the name server at that address. If either of the ++\fB\-4\fR ++or ++\fB\-6\fR ++options are in use, then only addresses for the corresponding transport will be tried. If no usable addresses are found, ++\fBdelve\fR ++will send queries to the localhost addresses (127.0.0.1 for IPv4, ::1 for IPv6). ++.RE ++.PP ++\fBname\fR ++.RS 4 ++is the domain name to be looked up. ++.RE ++.PP ++\fBtype\fR ++.RS 4 ++indicates what type of query is required \(em ANY, A, MX, etc. ++\fItype\fR ++can be any valid query type. If no ++\fItype\fR ++argument is supplied, ++\fBdelve\fR ++will perform a lookup for an A record. ++.RE ++.SH "OPTIONS" ++.PP ++\-a ++.RS 4 ++Specifies a file from which to read DNSSEC trust anchors. The default is ++\fI/etc/bind.keys\fR, which is included with ++BIND ++9 and contains trust anchors for the root zone (".") and for the ISC DNSSEC lookaside validation zone ("dlv.isc.org"). ++.sp ++Keys that do not match the root or DLV trust\-anchor names are ignored; these key names can be overridden using the ++\fB+dlv=NAME\fR ++or ++\fB+root=NAME\fR ++options. ++.sp ++Note: When reading the trust anchor file, ++\fBdelve\fR ++treats ++\fBmanaged\-keys\fR ++statements and ++\fBtrusted\-keys\fR ++statements identically. That is, for a managed key, it is the ++\fIinitial\fR ++key that is trusted; RFC 5011 key management is not supported. ++\fBdelve\fR ++will not consult the managed\-keys database maintained by ++\fBnamed\fR. This means that if either of the keys in ++\fI/etc/bind.keys\fR ++is revoked and rolled over, it will be necessary to update ++\fI/etc/bind.keys\fR ++to use DNSSEC validation in ++\fBdelve\fR. ++.RE ++.PP ++\-b ++.RS 4 ++Sets the source IP address of the query to ++\fIaddress\fR. This must be a valid address on one of the host's network interfaces or "0.0.0.0" or "::". An optional source port may be specified by appending "#" ++.RE ++.PP ++\-c ++.RS 4 ++Sets the query class for the requested data. Currently, only class "IN" is supported in ++\fBdelve\fR ++and any other value is ignored. ++.RE ++.PP ++\-i ++.RS 4 ++Insecure mode. This disables internal DNSSEC validation. (Note, however, this does not set the CD bit on upstream queries. If the server being queried is performing DNSSEC validation, then it will not return invalid data; this can cause ++\fBdelve\fR ++to time out. When it is necessary to examine invalid data to debug a DNSSEC problem, use ++\fBdig +cd\fR.) ++.RE ++.PP ++\-m ++.RS 4 ++Enables memory usage debugging. ++.RE ++.PP ++\-p ++.RS 4 ++Specifies a destination port to use for queries instead of the standard DNS port number 53. This option would be used with a name server that has been configured to listen for queries on a non\-standard port number. ++.RE ++.PP ++\-4 ++.RS 4 ++Forces ++\fBdelve\fR ++to only use IPv4. ++.RE ++.PP ++\-6 ++.RS 4 ++Forces ++\fBdelve\fR ++to only use IPv6. ++.RE ++.PP ++\-q ++.RS 4 ++Sets the query name to ++\fIname\fR. While the query name can be specified without using the ++\fB\-q\fR, it is sometimes necessary to disambiguate names from types or classes (for example, when looking up the name "ns", which could be misinterpreted as the type NS, or "ch", which could be misinterpreted as class CH). ++.RE ++.PP ++\-t ++.RS 4 ++Sets the query type to ++\fItype\fR, which can be any valid query type supported in BIND 9 except for zone transfer types AXFR and IXFR. As with ++\fB\-q\fR, this is useful to distinguish query name type or class when they are ambiguous. it is sometimes necessary to disambiguate names from types. ++.sp ++The default query type is "A", unless the ++\fB\-x\fR ++option is supplied to indicate a reverse lookup, in which case it is "PTR". ++.RE ++.PP ++\-x ++.RS 4 ++Performs a reverse lookup, mapping an addresses to a name. ++\fIaddr\fR ++is an IPv4 address in dotted\-decimal notation, or a colon\-delimited IPv6 address. When ++\fB\-x\fR ++is used, there is no need to provide the ++\fIname\fR ++or ++\fItype\fR ++arguments. ++\fBdelve\fR ++automatically performs a lookup for a name like ++11.12.13.10.in\-addr.arpa ++and sets the query type to PTR. IPv6 addresses are looked up using nibble format under the IP6.ARPA domain. ++.RE ++.SH "QUERY OPTIONS" ++.PP ++\fBdelve\fR ++provides a number of query options which affect the way results are displayed, and in some cases the way lookups are performed. ++.PP ++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 ++\fB+keyword=value\fR. The query options are: ++.PP ++\fB+[no]cdflag\fR ++.RS 4 ++Controls whether to set the CD (checking disabled) bit in queries sent by ++\fBdelve\fR. This may be useful when troubleshooting DNSSEC problems from behind a validating resolver. A validating resolver will block invalid responses, making it difficult to retrieve them for analysis. Setting the CD flag on queries will cause the resolver to return invalid responses, which ++\fBdelve\fR ++can then validate internally and report the errors in detail. ++.RE ++.PP ++\fB+[no]class\fR ++.RS 4 ++Controls whether to display the CLASS when printing a record. The default is to display the CLASS. ++.RE ++.PP ++\fB+[no]ttl\fR ++.RS 4 ++Controls whether to display the TTL when printing a record. The default is to display the TTL. ++.RE ++.PP ++\fB+[no]rtrace\fR ++.RS 4 ++Toggle resolver fetch logging. This reports the name and type of each query sent by ++\fBdelve\fR ++in the process of carrying out the resolution and validation process: this includes including the original query and all subsequent queries to follow CNAMEs and to establish a chain of trust for DNSSEC validation. ++.sp ++This is equivalent to setting the debug level to 1 in the "resolver" logging category. Setting the systemwide debug level to 1 using the ++\fB\-d\fR ++option will product the same output (but will affect other logging categories as well). ++.RE ++.PP ++\fB+[no]mtrace\fR ++.RS 4 ++Toggle message logging. This produces a detailed dump of the responses received by ++\fBdelve\fR ++in the process of carrying out the resolution and validation process. ++.sp ++This is equivalent to setting the debug level to 10 for the the "packets" module of the "resolver" logging category. Setting the systemwide debug level to 10 using the ++\fB\-d\fR ++option will produce the same output (but will affect other logging categories as well). ++.RE ++.PP ++\fB+[no]vtrace\fR ++.RS 4 ++Toggle validation logging. This shows the internal process of the validator as it determines whether an answer is validly signed, unsigned, or invalid. ++.sp ++This is equivalent to setting the debug level to 3 for the the "validator" module of the "dnssec" logging category. Setting the systemwide debug level to 3 using the ++\fB\-d\fR ++option will produce the same output (but will affect other logging categories as well). ++.RE ++.PP ++\fB+[no]short\fR ++.RS 4 ++Provide a terse answer. The default is to print the answer in a verbose form. ++.RE ++.PP ++\fB+[no]comments\fR ++.RS 4 ++Toggle the display of comment lines in the output. The default is to print comments. ++.RE ++.PP ++\fB+[no]rrcomments\fR ++.RS 4 ++Toggle the display of per\-record comments in the output (for example, human\-readable key information about DNSKEY records). The default is to print per\-record comments. ++.RE ++.PP ++\fB+[no]crypto\fR ++.RS 4 ++Toggle the display of cryptographic fields in DNSSEC records. The contents of these field are unnecessary to debug 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 ]". ++.RE ++.PP ++\fB+[no]trust\fR ++.RS 4 ++Controls whether to display the trust level when printing a record. The default is to display the trust level. ++.RE ++.PP ++\fB+[no]split[=W]\fR ++.RS 4 ++Split long hex\- or base64\-formatted fields in resource records into chunks of ++\fIW\fR ++characters (where ++\fIW\fR ++is rounded up to the nearest multiple of 4). ++\fI+nosplit\fR ++or ++\fI+split=0\fR ++causes fields not to be split at all. The default is 56 characters, or 44 characters when multiline mode is active. ++.RE ++.PP ++\fB+[no]all\fR ++.RS 4 ++Set or clear the display options ++\fB+[no]comments\fR, ++\fB+[no]rrcomments\fR, and ++\fB+[no]trust\fR ++as a group. ++.RE ++.PP ++\fB+[no]multiline\fR ++.RS 4 ++Print long records (such as RRSIG, DNSKEY, and 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 ++\fBdelve\fR ++output. ++.RE ++.PP ++\fB+[no]dnssec\fR ++.RS 4 ++Indicates whether to display RRSIG records in the ++\fBdelve\fR ++output. The default is to do so. Note that (unlike in ++\fBdig\fR) this does ++\fInot\fR ++control whether to request DNSSEC records or whether to validate them. DNSSEC records are always requested, and validation will always occur unless suppressed by the use of ++\fB\-i\fR ++or ++\fB+noroot\fR ++and ++\fB+nodlv\fR. ++.RE ++.PP ++\fB+[no]root[=ROOT]\fR ++.RS 4 ++Indicates whether to perform conventional (non\- lookaside) DNSSEC validation, and if so, specifies the name of a trust anchor. The default is to validate using a trust anchor of "." (the root zone), for which there is a built\-in key. If specifying a different trust anchor, then ++\fB\-a\fR ++must be used to specify a file containing the key. ++.RE ++.PP ++\fB+[no]dlv[=DLV]\fR ++.RS 4 ++Indicates whether to perform DNSSEC lookaside validation, and if so, specifies the name of the DLV trust anchor. The default is to perform lookaside validation using a trust anchor of "dlv.isc.org", for which there is a built\-in key. If specifying a different name, then ++\fB\-a\fR ++must be used to specify a file containing the DLV key. ++.RE ++.SH "FILES" ++.PP ++\fI/etc/bind.keys\fR ++.PP ++\fI/etc/resolv.conf\fR ++.SH "SEE ALSO" ++.PP ++\fBdig\fR(1), ++\fBnamed\fR(8), ++RFC4034, ++RFC4035, ++RFC4431, ++RFC5074, ++RFC5155. ++.SH "COPYRIGHT" ++Copyright \(co 2014 Internet Systems Consortium, Inc. ("ISC") ++.br +diff --git a/bin/delve/delve.c b/bin/delve/delve.c +new file mode 100644 +index 0000000000..9816cff1e4 +--- /dev/null ++++ b/bin/delve/delve.c +@@ -0,0 +1,1609 @@ ++/* ++ * Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define CHECK(r) \ ++ do { \ ++ result = (r); \ ++ if (result != ISC_R_SUCCESS) \ ++ goto cleanup; \ ++ } while (0) ++ ++#define MAXNAME (DNS_NAME_MAXTEXT+1) ++ ++/* Variables used internally by delve. */ ++char *progname; ++static isc_mem_t *mctx = NULL; ++static isc_log_t *lctx = NULL; ++ ++/* Configurables */ ++static char *server = NULL; ++static const char *port = "53"; ++static isc_sockaddr_t *srcaddr4 = NULL, *srcaddr6 = NULL; ++static char *curqname = NULL, *qname = NULL; ++static isc_boolean_t classset = ISC_FALSE; ++static dns_rdatatype_t qtype = dns_rdatatype_none; ++static isc_boolean_t typeset = ISC_FALSE; ++ ++static unsigned int styleflags = 0; ++static isc_uint32_t splitwidth = 0xffffffff; ++static isc_boolean_t ++ showcomments = ISC_TRUE, ++ showdnssec = ISC_TRUE, ++ showtrust = ISC_TRUE, ++ rrcomments = ISC_TRUE, ++ noclass = ISC_FALSE, ++ nocrypto = ISC_FALSE, ++ nottl = ISC_FALSE, ++ multiline = ISC_FALSE, ++ short_form = ISC_FALSE; ++ ++static isc_boolean_t ++ resolve_trace = ISC_FALSE, ++ validator_trace = ISC_FALSE, ++ message_trace = ISC_FALSE; ++ ++static isc_boolean_t ++ use_ipv4 = ISC_TRUE, ++ use_ipv6 = ISC_TRUE; ++ ++static isc_boolean_t ++ cdflag = ISC_FALSE, ++ no_sigs = ISC_FALSE, ++ root_validation = ISC_TRUE, ++ dlv_validation = ISC_TRUE; ++ ++static char *anchorfile = NULL; ++static char *trust_anchor = NULL; ++static char *dlv_anchor = NULL; ++static int trusted_keys = 0; ++ ++static dns_fixedname_t afn, dfn; ++static dns_name_t *anchor_name = NULL, *dlv_name = NULL; ++ ++/* Default bind.keys contents */ ++static char anchortext[] = MANAGED_KEYS; ++ ++/* ++ * Static function prototypes ++ */ ++static isc_result_t ++get_reverse(char *reverse, size_t len, char *value, isc_boolean_t strict); ++ ++static isc_result_t ++parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, ++ const char *desc); ++ ++static void ++usage(void) { ++ fputs( ++"Usage: delve [@server] {q-opt} {d-opt} [domain] [q-type] [q-class]\n" ++"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" ++" q-opt is one of:\n" ++" -x dot-notation (shortcut for reverse lookups)\n" ++" -d level (set debugging level)\n" ++" -a anchor-file (specify root and dlv trust anchors)\n" ++" -b address[#port] (bind to source address/port)\n" ++" -p port (specify port number)\n" ++" -q name (specify query name)\n" ++" -t type (specify query type)\n" ++" -c class (specify query class)\n" ++" -4 (use IPv4 query transport only)\n" ++" -6 (use IPv6 query transport only)\n" ++" -i (disable DNSSEC validation)\n" ++" -m (enable memory usage debugging)\n" ++" d-opt is of the form +keyword[=value], where keyword is:\n" ++" +[no]all (Set or clear all display flags)\n" ++" +[no]class (Control display of class)\n" ++" +[no]crypto (Control display of cryptographic\n" ++" fields in records)\n" ++" +[no]multiline (Print records in an expanded format)\n" ++" +[no]comments (Control display of comment lines)\n" ++" +[no]rrcomments (Control display of per-record " ++ "comments)\n" ++" +[no]short (Short form answer)\n" ++" +[no]split=## (Split hex/base64 fields into chunks)\n" ++" +[no]ttl (Control display of ttls in records)\n" ++" +[no]trust (Control display of trust level)\n" ++" +[no]rtrace (Trace resolver fetches)\n" ++" +[no]mtrace (Trace messages received)\n" ++" +[no]vtrace (Trace validation process)\n" ++" +[no]dlv (DNSSEC lookaside validation anchor)\n" ++" +[no]root (DNSSEC validation trust anchor)\n" ++" +[no]dnssec (Display DNSSEC records)\n" ++" -h (print help and exit)\n" ++" -v (print version and exit)\n", ++ stderr); ++ exit(1); ++} ++ ++static 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"); ++ exit(1); ++} ++ ++static void ++warn(const char *format, ...) { ++ va_list args; ++ ++ fflush(stdout); ++ fprintf(stderr, "%s: warning: ", progname); ++ va_start(args, format); ++ vfprintf(stderr, format, args); ++ va_end(args); ++ fprintf(stderr, "\n"); ++} ++ ++static isc_logcategory_t categories[] = { ++ { "delve", 0 }, ++ { NULL, 0 } ++}; ++#define LOGCATEGORY_DEFAULT (&categories[0]) ++#define LOGMODULE_DEFAULT (&modules[0]) ++ ++static isc_logmodule_t modules[] = { ++ { "delve", 0 }, ++ { NULL, 0 } ++}; ++ ++static void ++delve_log(int level, const char *fmt, ...) { ++ va_list ap; ++ char msgbuf[2048]; ++ ++ if (! isc_log_wouldlog(lctx, level)) ++ return; ++ ++ va_start(ap, fmt); ++ ++ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); ++ isc_log_write(lctx, LOGCATEGORY_DEFAULT, LOGMODULE_DEFAULT, ++ level, "%s", msgbuf); ++ va_end(ap); ++} ++ ++static int loglevel = 0; ++ ++static void ++setup_logging(FILE *errout) { ++ isc_result_t result; ++ isc_logdestination_t destination; ++ isc_logconfig_t *logconfig = NULL; ++ ++ result = isc_log_create(mctx, &lctx, &logconfig); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set up logging"); ++ ++ isc_log_registercategories(lctx, categories); ++ isc_log_registermodules(lctx, modules); ++ isc_log_setcontext(lctx); ++ dns_log_init(lctx); ++ dns_log_setcontext(lctx); ++ cfg_log_init(lctx); ++ ++ destination.file.stream = errout; ++ destination.file.name = NULL; ++ destination.file.versions = ISC_LOG_ROLLNEVER; ++ destination.file.maximum_size = 0; ++ ++ result = isc_log_createchannel(logconfig, "stderr", ++ ISC_LOG_TOFILEDESC, ISC_LOG_DYNAMIC, ++ &destination, ISC_LOG_PRINTPREFIX); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set up log channel 'stderr'"); ++ ++ isc_log_setdebuglevel(lctx, loglevel); ++ ++ result = isc_log_settag(logconfig, ";; "); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set log tag"); ++ ++ result = isc_log_usechannel(logconfig, "stderr", ++ ISC_LOGCATEGORY_DEFAULT, NULL); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't attach to log channel 'stderr'"); ++ ++ if (resolve_trace && loglevel < 1) { ++ result = isc_log_createchannel(logconfig, "resolver", ++ ISC_LOG_TOFILEDESC, ++ ISC_LOG_DEBUG(1), ++ &destination, ++ ISC_LOG_PRINTPREFIX); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set up log channel 'resolver'"); ++ ++ result = isc_log_usechannel(logconfig, "resolver", ++ DNS_LOGCATEGORY_RESOLVER, ++ DNS_LOGMODULE_RESOLVER); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't attach to log channel 'resolver'"); ++ } ++ ++ if (validator_trace && loglevel < 3) { ++ result = isc_log_createchannel(logconfig, "validator", ++ ISC_LOG_TOFILEDESC, ++ ISC_LOG_DEBUG(3), ++ &destination, ++ ISC_LOG_PRINTPREFIX); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set up log channel 'validator'"); ++ ++ result = isc_log_usechannel(logconfig, "validator", ++ DNS_LOGCATEGORY_DNSSEC, ++ DNS_LOGMODULE_VALIDATOR); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't attach to log channel 'validator'"); ++ } ++ ++ if (message_trace && loglevel < 10) { ++ result = isc_log_createchannel(logconfig, "messages", ++ ISC_LOG_TOFILEDESC, ++ ISC_LOG_DEBUG(10), ++ &destination, ++ ISC_LOG_PRINTPREFIX); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't set up log channel 'messages'"); ++ ++ result = isc_log_usechannel(logconfig, "messages", ++ DNS_LOGCATEGORY_RESOLVER, ++ DNS_LOGMODULE_PACKETS); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't attach to log channel 'messagse'"); ++ } ++} ++ ++static void ++print_status(dns_rdataset_t *rdataset) { ++ const char *astr = "", *tstr = ""; ++ ++ REQUIRE(rdataset != NULL); ++ ++ if (!showtrust || !dns_rdataset_isassociated(rdataset)) ++ return; ++ ++ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) ++ astr = "negative response, "; ++ ++ switch (rdataset->trust) { ++ case dns_trust_none: ++ tstr = "untrusted"; ++ break; ++ case dns_trust_pending_additional: ++ tstr = "signed additional data, pending validation"; ++ break; ++ case dns_trust_pending_answer: ++ tstr = "signed answer, pending validation"; ++ break; ++ case dns_trust_additional: ++ tstr = "unsigned additional data"; ++ break; ++ case dns_trust_glue: ++ tstr = "glue data"; ++ break; ++ case dns_trust_answer: ++ if (root_validation || dlv_validation) ++ tstr = "unsigned answer"; ++ else ++ tstr = "answer not validated"; ++ break; ++ case dns_trust_authauthority: ++ tstr = "authority data"; ++ break; ++ case dns_trust_authanswer: ++ tstr = "authoritative"; ++ break; ++ case dns_trust_secure: ++ tstr = "fully validated"; ++ break; ++ case dns_trust_ultimate: ++ tstr = "ultimate trust"; ++ break; ++ } ++ ++ printf("; %s%s\n", astr, tstr); ++} ++ ++static isc_result_t ++printdata(dns_rdataset_t *rdataset, dns_name_t *owner, ++ dns_master_style_t *style) ++{ ++ isc_result_t result = ISC_R_SUCCESS; ++ static dns_trust_t trust; ++ static isc_boolean_t first = ISC_TRUE; ++ isc_buffer_t target; ++ isc_region_t r; ++ char *t = NULL; ++ int len = 2048; ++ ++ if (!dns_rdataset_isassociated(rdataset)) { ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ dns_name_format(owner, namebuf, sizeof(namebuf)); ++ delve_log(ISC_LOG_DEBUG(4), ++ "WARN: empty rdataset %s", namebuf); ++ return (ISC_R_SUCCESS); ++ } ++ ++ if (!showdnssec && rdataset->type == dns_rdatatype_rrsig) ++ return (ISC_R_SUCCESS); ++ ++ isc_buffer_init(&target, t, sizeof(t)); ++ ++ if (first || rdataset->trust != trust) { ++ if (!first && showtrust && !short_form) ++ putchar('\n'); ++ print_status(rdataset); ++ trust = rdataset->trust; ++ first = ISC_FALSE; ++ } ++ ++ do { ++ t = isc_mem_get(mctx, len); ++ if (t == NULL) ++ return (ISC_R_NOMEMORY); ++ ++ isc_buffer_init(&target, t, len); ++ if (short_form) { ++ dns_rdata_t rdata = DNS_RDATA_INIT; ++ for (result = dns_rdataset_first(rdataset); ++ result == ISC_R_SUCCESS; ++ result = dns_rdataset_next(rdataset)) ++ { ++ isc_region_t r; ++ ++ if ((rdataset->attributes & ++ DNS_RDATASETATTR_NEGATIVE) != 0) ++ continue; ++ ++ dns_rdataset_current(rdataset, &rdata); ++ result = dns_rdata_tofmttext(&rdata, ++ dns_rootname, ++ styleflags, ++ 0, 60, " ", ++ &target); ++ if (result != ISC_R_SUCCESS) ++ break; ++ ++ isc_buffer_availableregion(&target, &r); ++ if (r.length < 1) { ++ result = ISC_R_NOSPACE; ++ break; ++ } ++ ++ r.base[0] = '\n'; ++ isc_buffer_add(&target, 1); ++ ++ dns_rdata_reset(&rdata); ++ } ++ } else { ++ if ((rdataset->attributes & ++ DNS_RDATASETATTR_NEGATIVE) != 0) ++ isc_buffer_putstr(&target, "; "); ++ ++ result = dns_master_rdatasettotext(owner, rdataset, ++ style, &target); ++ } ++ ++ if (result == ISC_R_NOSPACE) { ++ isc_mem_put(mctx, t, len); ++ len += 1024; ++ } else ++ CHECK(result); ++ } while (result == ISC_R_NOSPACE); ++ ++ isc_buffer_usedregion(&target, &r); ++ printf("%.*s", (int)r.length, (char *)r.base); ++ ++ cleanup: ++ if (t != NULL) ++ isc_mem_put(mctx, t, len); ++ ++ return (ISC_R_SUCCESS); ++} ++ ++static isc_result_t ++setup_style(dns_master_style_t **stylep) { ++ isc_result_t result; ++ dns_master_style_t *style = NULL; ++ ++ REQUIRE(stylep != NULL || *stylep == NULL); ++ ++ styleflags |= DNS_STYLEFLAG_REL_OWNER; ++ if (showcomments) ++ styleflags |= DNS_STYLEFLAG_COMMENT; ++ if (rrcomments) ++ styleflags |= DNS_STYLEFLAG_RRCOMMENT; ++ if (nottl) ++ styleflags |= DNS_STYLEFLAG_NO_TTL; ++ if (noclass) ++ styleflags |= DNS_STYLEFLAG_NO_CLASS; ++#if 0 ++ if (nocrypto) ++ styleflags |= DNS_STYLEFLAG_NOCRYPTO; ++#endif ++ if (multiline) { ++ styleflags |= DNS_STYLEFLAG_MULTILINE; ++ styleflags |= DNS_STYLEFLAG_COMMENT; ++ } ++ ++ if (multiline || (nottl && noclass)) ++ result = dns_master_stylecreate2(&style, styleflags, ++ 24, 24, 24, 32, 80, 8, ++ splitwidth, mctx); ++ else if (nottl || noclass) ++ result = dns_master_stylecreate2(&style, styleflags, ++ 24, 24, 32, 40, 80, 8, ++ splitwidth, mctx); ++ else ++ result = dns_master_stylecreate2(&style, styleflags, ++ 24, 32, 40, 48, 80, 8, ++ splitwidth, mctx); ++ ++ if (result == ISC_R_SUCCESS) ++ *stylep = style; ++ return (result); ++} ++ ++static isc_result_t ++convert_name(dns_fixedname_t *fn, dns_name_t **name, const char *text) { ++ isc_result_t result; ++ isc_buffer_t b; ++ dns_name_t *n; ++ size_t len; ++ ++ REQUIRE(fn != NULL && name != NULL && text != NULL); ++ len = strlen(text); ++ ++ isc_buffer_constinit(&b, text, len); ++ isc_buffer_add(&b, len); ++ dns_fixedname_init(fn); ++ n = dns_fixedname_name(fn); ++ ++ result = dns_name_fromtext(n, &b, dns_rootname, 0, NULL); ++ if (result != ISC_R_SUCCESS) { ++ delve_log(ISC_LOG_ERROR, "failed to convert QNAME %s: %s", ++ text, isc_result_totext(result)); ++ return (result); ++ } ++ ++ *name = n; ++ return (ISC_R_SUCCESS); ++} ++ ++static isc_result_t ++key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { ++ dns_rdata_dnskey_t keystruct; ++ isc_uint32_t flags, proto, alg; ++ const char *keystr, *keynamestr; ++ unsigned char keydata[4096]; ++ isc_buffer_t keydatabuf; ++ unsigned char rrdata[4096]; ++ isc_buffer_t rrdatabuf; ++ isc_region_t r; ++ dns_fixedname_t fkeyname; ++ dns_name_t *keyname; ++ isc_result_t result; ++ isc_boolean_t match_root, match_dlv; ++ ++ keynamestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); ++ CHECK(convert_name(&fkeyname, &keyname, keynamestr)); ++ ++ if (!root_validation && !dlv_validation) ++ return (ISC_R_SUCCESS); ++ ++ match_root = dns_name_equal(keyname, anchor_name); ++ match_dlv = dns_name_equal(keyname, dlv_name); ++ ++ if (!match_root && !match_dlv) ++ return (ISC_R_SUCCESS); ++ if ((!root_validation && match_root) || (!dlv_validation && match_dlv)) ++ return (ISC_R_SUCCESS); ++ ++ if (match_root) ++ delve_log(ISC_LOG_DEBUG(3), "adding trust anchor %s", ++ trust_anchor); ++ if (match_dlv) ++ delve_log(ISC_LOG_DEBUG(3), "adding DLV trust anchor %s", ++ dlv_anchor); ++ ++ flags = cfg_obj_asuint32(cfg_tuple_get(key, "flags")); ++ proto = cfg_obj_asuint32(cfg_tuple_get(key, "protocol")); ++ alg = cfg_obj_asuint32(cfg_tuple_get(key, "algorithm")); ++ ++ keystruct.common.rdclass = dns_rdataclass_in; ++ keystruct.common.rdtype = dns_rdatatype_dnskey; ++ /* ++ * The key data in keystruct is not dynamically allocated. ++ */ ++ keystruct.mctx = NULL; ++ ++ ISC_LINK_INIT(&keystruct.common, link); ++ ++ if (flags > 0xffff) ++ CHECK(ISC_R_RANGE); ++ if (proto > 0xff) ++ CHECK(ISC_R_RANGE); ++ if (alg > 0xff) ++ CHECK(ISC_R_RANGE); ++ ++ keystruct.flags = (isc_uint16_t)flags; ++ keystruct.protocol = (isc_uint8_t)proto; ++ keystruct.algorithm = (isc_uint8_t)alg; ++ ++ isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); ++ isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); ++ ++ keystr = cfg_obj_asstring(cfg_tuple_get(key, "key")); ++ CHECK(isc_base64_decodestring(keystr, &keydatabuf)); ++ isc_buffer_usedregion(&keydatabuf, &r); ++ keystruct.datalen = r.length; ++ keystruct.data = r.base; ++ ++ CHECK(dns_rdata_fromstruct(NULL, ++ keystruct.common.rdclass, ++ keystruct.common.rdtype, ++ &keystruct, &rrdatabuf)); ++ ++ CHECK(dns_client_addtrustedkey(client, dns_rdataclass_in, ++ keyname, &rrdatabuf)); ++ trusted_keys++; ++ ++ cleanup: ++ if (result == DST_R_NOCRYPTO) ++ cfg_obj_log(key, lctx, ISC_LOG_ERROR, "no crypto support"); ++ else if (result == DST_R_UNSUPPORTEDALG) { ++ cfg_obj_log(key, lctx, ISC_LOG_WARNING, ++ "skipping trusted key '%s': %s", ++ keynamestr, isc_result_totext(result)); ++ result = ISC_R_SUCCESS; ++ } else if (result != ISC_R_SUCCESS) { ++ cfg_obj_log(key, lctx, ISC_LOG_ERROR, ++ "failed to add trusted key '%s': %s", ++ keynamestr, isc_result_totext(result)); ++ result = ISC_R_FAILURE; ++ } ++ ++ return (result); ++} ++ ++static isc_result_t ++load_keys(const cfg_obj_t *keys, dns_client_t *client) { ++ const cfg_listelt_t *elt, *elt2; ++ const cfg_obj_t *key, *keylist; ++ isc_result_t result = ISC_R_SUCCESS; ++ ++ for (elt = cfg_list_first(keys); ++ elt != NULL; ++ elt = cfg_list_next(elt)) ++ { ++ keylist = cfg_listelt_value(elt); ++ ++ for (elt2 = cfg_list_first(keylist); ++ elt2 != NULL; ++ elt2 = cfg_list_next(elt2)) ++ { ++ key = cfg_listelt_value(elt2); ++ CHECK(key_fromconfig(key, client)); ++ } ++ } ++ ++ cleanup: ++ if (result == DST_R_NOCRYPTO) ++ result = ISC_R_SUCCESS; ++ return (result); ++} ++ ++static isc_result_t ++setup_dnsseckeys(dns_client_t *client) { ++ isc_result_t result; ++ cfg_parser_t *parser = NULL; ++ const cfg_obj_t *keys = NULL; ++ const cfg_obj_t *managed_keys = NULL; ++ cfg_obj_t *bindkeys = NULL; ++ const char *filename = anchorfile; ++ ++ if (!root_validation && !dlv_validation) ++ return (ISC_R_SUCCESS); ++ ++ if (filename == NULL) ++ filename = SYSCONFDIR "/bind.keys"; ++ ++ if (trust_anchor == NULL) ++ trust_anchor = isc_mem_strdup(mctx, "."); ++ if (dlv_anchor == NULL) ++ dlv_anchor = isc_mem_strdup(mctx, "dlv.isc.org"); ++ ++ CHECK(convert_name(&afn, &anchor_name, trust_anchor)); ++ CHECK(convert_name(&dfn, &dlv_name, dlv_anchor)); ++ ++ CHECK(cfg_parser_create(mctx, dns_lctx, &parser)); ++ ++ if (access(filename, R_OK) != 0) { ++ if (anchorfile != NULL) ++ fatal("Unable to read key file '%s'", anchorfile); ++ } else { ++ result = cfg_parse_file(parser, filename, ++ &cfg_type_bindkeys, &bindkeys); ++ if (result != ISC_R_SUCCESS) ++ if (anchorfile != NULL) ++ fatal("Unable to load keys from '%s'", ++ anchorfile); ++ } ++ ++ if (bindkeys == NULL) { ++ isc_buffer_t b; ++ ++ isc_buffer_init(&b, anchortext, sizeof(anchortext) - 1); ++ isc_buffer_add(&b, sizeof(anchortext) - 1); ++ result = cfg_parse_buffer(parser, &b, &cfg_type_bindkeys, ++ &bindkeys); ++ if (result != ISC_R_SUCCESS) ++ fatal("Unable to parse built-in keys"); ++ } ++ ++ INSIST(bindkeys != NULL); ++ cfg_map_get(bindkeys, "trusted-keys", &keys); ++ cfg_map_get(bindkeys, "managed-keys", &managed_keys); ++ ++ if (keys != NULL) ++ CHECK(load_keys(keys, client)); ++ if (managed_keys != NULL) ++ CHECK(load_keys(managed_keys, client)); ++ result = ISC_R_SUCCESS; ++ ++ if (trusted_keys == 0) ++ fatal("No trusted keys were loaded"); ++ ++ if (dlv_validation) ++ dns_client_setdlv(client, dns_rdataclass_in, dlv_anchor); ++ ++ cleanup: ++ if (result != ISC_R_SUCCESS) ++ delve_log(ISC_LOG_ERROR, "setup_dnsseckeys: %s", ++ isc_result_totext(result)); ++ return (result); ++} ++ ++static isc_result_t ++addserver(dns_client_t *client) { ++ struct addrinfo hints, *res, *cur; ++ int gai_error; ++ struct in_addr in4; ++ struct in6_addr in6; ++ isc_sockaddr_t *sa; ++ isc_sockaddrlist_t servers; ++ isc_uint32_t destport; ++ isc_result_t result; ++ dns_name_t *name = NULL; ++ ++ result = parse_uint(&destport, port, 0xffff, "port"); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't parse port number"); ++ ++ ISC_LIST_INIT(servers); ++ ++ if (use_ipv4 && inet_pton(AF_INET, server, &in4) == 1) { ++ sa = isc_mem_get(mctx, sizeof(*sa)); ++ ISC_LINK_INIT(sa, link); ++ isc_sockaddr_fromin(sa, &in4, destport); ++ ISC_LIST_APPEND(servers, sa, link); ++ } else if (use_ipv6 && inet_pton(AF_INET6, server, &in6) == 1) { ++ sa = isc_mem_get(mctx, sizeof(*sa)); ++ ISC_LINK_INIT(sa, link); ++ isc_sockaddr_fromin6(sa, &in6, destport); ++ ISC_LIST_APPEND(servers, sa, link); ++ } else { ++ memset(&hints, 0, sizeof(hints)); ++ if (!use_ipv6) ++ hints.ai_family = AF_INET; ++ else if (!use_ipv4) ++ hints.ai_family = AF_INET6; ++ else ++ hints.ai_family = AF_UNSPEC; ++ hints.ai_socktype = SOCK_DGRAM; ++ hints.ai_protocol = IPPROTO_UDP; ++ gai_error = getaddrinfo(server, port, &hints, &res); ++ if (gai_error != 0) { ++ delve_log(ISC_LOG_ERROR, ++ "getaddrinfo failed: %s", ++ gai_strerror(gai_error)); ++ return (ISC_R_FAILURE); ++ } ++ ++ for (cur = res; cur != NULL; cur = res->ai_next) { ++ sa = isc_mem_get(mctx, sizeof(*sa)); ++ memset(sa, 0, sizeof(*sa)); ++ ISC_LINK_INIT(sa, link); ++ memmove(&sa->type, res->ai_addr, res->ai_addrlen); ++ sa->length = res->ai_addrlen; ++ ISC_LIST_APPEND(servers, sa, link); ++ freeaddrinfo(res); ++ } ++ } ++ ++ ++ CHECK(dns_client_setservers(client, dns_rdataclass_in, name, &servers)); ++ ++ cleanup: ++ while (!ISC_LIST_EMPTY(servers)) { ++ sa = ISC_LIST_HEAD(servers); ++ ISC_LIST_UNLINK(servers, sa, link); ++ isc_mem_put(mctx, sa, sizeof(*sa)); ++ } ++ ++ if (result != ISC_R_SUCCESS) ++ delve_log(ISC_LOG_ERROR, "addserver: %s", ++ isc_result_totext(result)); ++ ++ return (result); ++} ++ ++static isc_result_t ++findserver(dns_client_t *client) { ++ isc_result_t result; ++ irs_resconf_t *resconf = NULL; ++ isc_sockaddrlist_t *nameservers; ++ isc_sockaddr_t *sa, *next; ++ ++ result = irs_resconf_load(mctx, "/etc/resolv.conf", &resconf); ++ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { ++ delve_log(ISC_LOG_ERROR, "irs_resconf_load: %s", ++ isc_result_totext(result)); ++ goto cleanup; ++ } ++ ++ /* Get nameservers from resolv.conf */ ++ nameservers = irs_resconf_getnameservers(resconf); ++ ++ /* Remove nameservers using incompatible protocol family */ ++ for (sa = ISC_LIST_HEAD(*nameservers); sa != NULL; sa = next) { ++ next = ISC_LIST_NEXT(sa, link); ++ if (!use_ipv4 && sa->type.sa.sa_family == AF_INET) { ++ ISC_LIST_UNLINK(*nameservers, sa, link); ++ isc_mem_put(mctx, sa, sizeof(*sa)); ++ } ++ if (!use_ipv6 && sa->type.sa.sa_family == AF_INET6) { ++ ISC_LIST_UNLINK(*nameservers, sa, link); ++ isc_mem_put(mctx, sa, sizeof(*sa)); ++ } ++ } ++ ++ /* None found, use localhost */ ++ if (ISC_LIST_EMPTY(*nameservers)) { ++ isc_uint32_t destport; ++ result = parse_uint(&destport, port, 0xffff, "port"); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't parse port number"); ++ ++ if (use_ipv4) { ++ struct in_addr localhost; ++ localhost.s_addr = htonl(INADDR_LOOPBACK); ++ sa = isc_mem_get(mctx, sizeof(*sa)); ++ if (sa == NULL) { ++ result = ISC_R_NOMEMORY; ++ goto cleanup; ++ } ++ isc_sockaddr_fromin(sa, &localhost, destport); ++ ++ ISC_LINK_INIT(sa, link); ++ ISC_LIST_APPEND(*nameservers, sa, link); ++ } ++ ++ if (use_ipv6) { ++ sa = isc_mem_get(mctx, sizeof(*sa)); ++ if (sa == NULL) { ++ result = ISC_R_NOMEMORY; ++ goto cleanup; ++ } ++ isc_sockaddr_fromin6(sa, &in6addr_loopback, ++ destport); ++ ++ ISC_LINK_INIT(sa, link); ++ ISC_LIST_APPEND(*nameservers, sa, link); ++ } ++ } ++ ++ result = dns_client_setservers(client, dns_rdataclass_in, NULL, ++ nameservers); ++ if (result != ISC_R_SUCCESS) ++ delve_log(ISC_LOG_ERROR, "dns_client_setservers: %s", ++ isc_result_totext(result)); ++ ++cleanup: ++ if (resconf != NULL) ++ irs_resconf_destroy(&resconf); ++ return (result); ++} ++ ++static char * ++next_token(char **stringp, const char *delim) { ++ char *res; ++ ++ do { ++ res = strsep(stringp, delim); ++ if (res == NULL) ++ break; ++ } while (*res == '\0'); ++ return (res); ++} ++ ++static isc_result_t ++parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, ++ const char *desc) { ++ isc_uint32_t n; ++ isc_result_t result = isc_parse_uint32(&n, value, 10); ++ 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); ++} ++ ++static void ++plus_option(char *option) { ++ isc_result_t result; ++ char option_store[256]; ++ char *cmd, *value, *ptr; ++ isc_boolean_t state = ISC_TRUE; ++ ++ strncpy(option_store, option, sizeof(option_store)); ++ option_store[sizeof(option_store)-1]=0; ++ ptr = option_store; ++ cmd = next_token(&ptr,"="); ++ if (cmd == NULL) { ++ printf(";; Invalid option %s\n", option_store); ++ return; ++ } ++ value = ptr; ++ if (strncasecmp(cmd, "no", 2)==0) { ++ cmd += 2; ++ state = ISC_FALSE; ++ } ++ ++#define FULLCHECK(A) \ ++ do { \ ++ size_t _l = strlen(cmd); \ ++ if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \ ++ goto invalid_option; \ ++ } while (0) ++ ++ switch (cmd[0]) { ++ case 'a': /* all */ ++ FULLCHECK("all"); ++ showcomments = state; ++ rrcomments = state; ++ showtrust = state; ++ break; ++ case 'c': ++ switch (cmd[1]) { ++ case 'd': /* cdflag */ ++ FULLCHECK("cdflag"); ++ cdflag = state; ++ break; ++ case 'l': /* class */ ++ FULLCHECK("class"); ++ noclass = ISC_TF(!state); ++ break; ++ case 'o': /* comments */ ++ FULLCHECK("comments"); ++ showcomments = state; ++ break; ++ case 'r': /* crypto */ ++ FULLCHECK("crypto"); ++ nocrypto = ISC_TF(!state); ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 'd': ++ switch (cmd[1]) { ++ case 'l': /* dlv */ ++ FULLCHECK("dlv"); ++ if (state && no_sigs) ++ break; ++ dlv_validation = state; ++ if (value != NULL) ++ dlv_anchor = isc_mem_strdup(mctx, value); ++ break; ++ case 'n': /* dnssec */ ++ FULLCHECK("dnssec"); ++ showdnssec = state; ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 'm': ++ switch (cmd[1]) { ++ case 't': /* mtrace */ ++ message_trace = state; ++ if (state) ++ resolve_trace = state; ++ break; ++ case 'u': /* multiline */ ++ FULLCHECK("multiline"); ++ multiline = state; ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 'r': ++ switch (cmd[1]) { ++ case 'o': /* root */ ++ FULLCHECK("root"); ++ if (state && no_sigs) ++ break; ++ root_validation = state; ++ if (value != NULL) ++ trust_anchor = isc_mem_strdup(mctx, value); ++ break; ++ case 'r': /* rrcomments */ ++ FULLCHECK("rrcomments"); ++ rrcomments = state; ++ break; ++ case 't': /* rtrace */ ++ FULLCHECK("rtrace"); ++ resolve_trace = state; ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 's': ++ switch (cmd[1]) { ++ case 'h': /* short */ ++ FULLCHECK("short"); ++ short_form = state; ++ if (short_form) { ++ multiline = ISC_FALSE; ++ showcomments = ISC_FALSE; ++ showtrust = ISC_FALSE; ++ showdnssec = ISC_FALSE; ++ } ++ 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 != 0) { ++ splitwidth = ((splitwidth + 3) / 4) * 4; ++ warn("split must be a multiple of 4; " ++ "adjusting to %d", splitwidth); ++ } ++ /* ++ * There is an adjustment done in the ++ * totext_() 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) ++ fatal("Couldn't parse split"); ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 't': ++ switch (cmd[1]) { ++ case 'r': /* trust */ ++ FULLCHECK("trust"); ++ showtrust = state; ++ break; ++ case 't': /* ttl */ ++ FULLCHECK("ttl"); ++ nottl = ISC_TF(!state); ++ break; ++ default: ++ goto invalid_option; ++ } ++ break; ++ case 'v': /* vtrace */ ++ FULLCHECK("vtrace"); ++ validator_trace = state; ++ if (state) ++ resolve_trace = state; ++ break; ++ default: ++ invalid_option: ++ /* ++ * We can also add a "need_value:" case here if we ever ++ * add a plus-option that requires a specified value ++ */ ++ fprintf(stderr, "Invalid option: +%s\n", option); ++ usage(); ++ } ++ return; ++} ++ ++/* ++ * options: "46a:b:c:d:himp:q:t:vx:"; ++ */ ++static const char *single_dash_opts = "46himv"; ++static isc_boolean_t ++dash_option(char *option, char *next, isc_boolean_t *open_type_class) { ++ char opt, *value; ++ isc_result_t result; ++ isc_boolean_t value_from_next; ++ isc_textregion_t tr; ++ dns_rdatatype_t rdtype; ++ dns_rdataclass_t rdclass; ++ char textname[MAXNAME]; ++ struct in_addr in4; ++ struct in6_addr in6; ++ isc_sockaddr_t a4, a6; ++ in_port_t srcport; ++ isc_uint32_t num; ++ char *hash; ++ ++ while (strpbrk(option, single_dash_opts) == &option[0]) { ++ /* ++ * Since the -[46himv] 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 (isc_net_probeipv4() != ISC_R_SUCCESS) ++ fatal("IPv4 networking not available"); ++ if (use_ipv6) { ++ isc_net_disableipv6(); ++ use_ipv6 = ISC_FALSE; ++ } ++ break; ++ case '6': ++ if (isc_net_probeipv6() != ISC_R_SUCCESS) ++ fatal("IPv6 networking not available"); ++ if (use_ipv4) { ++ isc_net_disableipv4(); ++ use_ipv4 = ISC_FALSE; ++ } ++ break; ++ case 'h': ++ usage(); ++ exit(0); ++ /* NOTREACHED */ ++ case 'i': ++ no_sigs = ISC_TRUE; ++ dlv_validation = ISC_FALSE; ++ root_validation = ISC_FALSE; ++ break; ++ case 'm': ++ /* handled in preparse_args() */ ++ break; ++ case 'v': ++ fputs("delve " VERSION "\n", stderr); ++ exit(0); ++ /* NOTREACHED */ ++ default: ++ INSIST(0); ++ } ++ if (strlen(option) > 1U) ++ option = &option[1]; ++ else ++ return (ISC_FALSE); ++ } ++ opt = option[0]; ++ if (strlen(option) > 1U) { ++ value_from_next = ISC_FALSE; ++ value = &option[1]; ++ } else { ++ value_from_next = ISC_TRUE; ++ value = next; ++ } ++ if (value == NULL) ++ goto invalid_option; ++ switch (opt) { ++ case 'a': ++ anchorfile = isc_mem_strdup(mctx, value); ++ return (value_from_next); ++ case 'b': ++ hash = strchr(value, '#'); ++ if (hash != NULL) { ++ result = parse_uint(&num, hash + 1, 0xffff, "port"); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't parse port number"); ++ srcport = num; ++ *hash = '\0'; ++ } else ++ srcport = 0; ++ ++ if (inet_pton(AF_INET, value, &in4) == 1) { ++ if (srcaddr4 != NULL) ++ fatal("Only one local address per family " ++ "can be specified\n"); ++ isc_sockaddr_fromin(&a4, &in4, srcport); ++ srcaddr4 = &a4; ++ } else if (inet_pton(AF_INET6, value, &in6) == 1) { ++ if (srcaddr6 != NULL) ++ fatal("Only one local address per family " ++ "can be specified\n"); ++ isc_sockaddr_fromin6(&a6, &in6, srcport); ++ srcaddr6 = &a6; ++ } else { ++ if (hash != NULL) ++ *hash = '#'; ++ fatal("Invalid address %s", value); ++ } ++ if (hash != NULL) ++ *hash = '#'; ++ return (value_from_next); ++ case 'c': ++ if (classset) ++ warn("extra query class"); ++ ++ *open_type_class = ISC_FALSE; ++ tr.base = value; ++ tr.length = strlen(value); ++ result = dns_rdataclass_fromtext(&rdclass, ++ (isc_textregion_t *)&tr); ++ if (result == ISC_R_SUCCESS) ++ classset = ISC_TRUE; ++ else if (rdclass != dns_rdataclass_in) ++ warn("ignoring non-IN query class"); ++ else ++ warn("ignoring invalid class"); ++ return (value_from_next); ++ case 'd': ++ result = parse_uint(&num, value, 99, "debug level"); ++ if (result != ISC_R_SUCCESS) ++ fatal("Couldn't parse debug level"); ++ loglevel = num; ++ return (value_from_next); ++ case 'p': ++ port = value; ++ return (value_from_next); ++ case 'q': ++ if (qname != NULL) { ++ warn("extra query name"); ++ isc_mem_free(mctx, qname); ++ } ++ curqname = value; ++ return (value_from_next); ++ case 't': ++ *open_type_class = ISC_FALSE; ++ tr.base = value; ++ tr.length = strlen(value); ++ result = dns_rdatatype_fromtext(&rdtype, ++ (isc_textregion_t *)&tr); ++ if (result == ISC_R_SUCCESS) { ++ if (typeset) ++ warn("extra query type"); ++ if (rdtype == dns_rdatatype_ixfr || ++ rdtype == dns_rdatatype_axfr) ++ fatal("Transfer not supported"); ++ qtype = rdtype; ++ typeset = ISC_TRUE; ++ } else ++ warn("ignoring invalid type"); ++ return (value_from_next); ++ case 'x': ++ result = get_reverse(textname, sizeof(textname), value, ++ ISC_FALSE); ++ if (result == ISC_R_SUCCESS) { ++ if (curqname != NULL) ++ warn("extra query name"); ++ curqname = isc_mem_strdup(mctx, textname); ++ if (typeset) ++ warn("extra query type"); ++ qtype = dns_rdatatype_ptr; ++ typeset = ISC_TRUE; ++ } 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(); ++ } ++ /* NOTREACHED */ ++ return (ISC_FALSE); ++} ++ ++/* ++ * Check for -m first to determine whether to enable ++ * memory debugging when setting up the memory context. ++ */ ++static void ++preparse_args(int argc, char **argv) { ++ char *option; ++ ++ for (argc--, argv++; argc > 0; argc--, argv++) { ++ if (argv[0][0] != '-') ++ continue; ++ option = &argv[0][1]; ++ while (strpbrk(option, single_dash_opts) == &option[0]) { ++ if (option[0] == 'm') { ++ isc_mem_debugging = ISC_MEM_DEBUGTRACE | ++ ISC_MEM_DEBUGRECORD; ++ return; ++ } ++ option = &option[1]; ++ } ++ } ++} ++ ++/* ++ * Argument parsing is based on dig, but simplified: only one ++ * QNAME/QCLASS/QTYPE tuple can be specified, and options have ++ * been removed that aren't applicable to delve. The interface ++ * should be familiar to dig users, however. ++ */ ++static void ++parse_args(int argc, char **argv) { ++ isc_result_t result; ++ isc_textregion_t tr; ++ dns_rdatatype_t rdtype; ++ dns_rdataclass_t rdclass; ++ isc_boolean_t open_type_class = ISC_TRUE; ++ ++ for (; argc > 0; argc--, argv++) { ++ if (argv[0][0] == '@') { ++ server = &argv[0][1]; ++ } else if (argv[0][0] == '+') { ++ plus_option(&argv[0][1]); ++ } else if (argv[0][0] == '-') { ++ if (argc <= 1) { ++ if (dash_option(&argv[0][1], NULL, ++ &open_type_class)) ++ { ++ argc--; ++ argv++; ++ } ++ } else { ++ if (dash_option(&argv[0][1], argv[1], ++ &open_type_class)) ++ { ++ argc--; ++ argv++; ++ } ++ } ++ } else { ++ /* ++ * Anything which isn't an option ++ */ ++ if (open_type_class) { ++ tr.base = argv[0]; ++ tr.length = strlen(argv[0]); ++ result = dns_rdatatype_fromtext(&rdtype, ++ (isc_textregion_t *)&tr); ++ if (result == ISC_R_SUCCESS) { ++ if (typeset) ++ warn("extra query type"); ++ if (rdtype == dns_rdatatype_ixfr || ++ rdtype == dns_rdatatype_axfr) ++ fatal("Transfer not supported"); ++ qtype = rdtype; ++ typeset = ISC_TRUE; ++ continue; ++ } ++ result = dns_rdataclass_fromtext(&rdclass, ++ (isc_textregion_t *)&tr); ++ if (result == ISC_R_SUCCESS) { ++ if (classset) ++ warn("extra query class"); ++ else if (rdclass != dns_rdataclass_in) ++ warn("ignoring non-IN " ++ "query class"); ++ continue; ++ } ++ } ++ ++ if (curqname == NULL) ++ curqname = argv[0]; ++ } ++ } ++ ++ /* ++ * If no qname or qtype specified, search for root/NS ++ * If no qtype specified, use A ++ */ ++ if (!typeset) ++ qtype = dns_rdatatype_a; ++ ++ if (curqname == NULL) { ++ qname = isc_mem_strdup(mctx, "."); ++ if (!typeset) ++ qtype = dns_rdatatype_ns; ++ } else ++ qname = isc_mem_strdup(mctx, curqname); ++} ++ ++static isc_result_t ++append_str(const char *text, int len, char **p, char *end) { ++ if (len > end - *p) ++ 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) { ++ char *dot = strchr(in, '.'); ++ int len; ++ if (dot != NULL) { ++ isc_result_t result; ++ result = reverse_octets(dot + 1, p, end); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ result = append_str(".", 1, p, end); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ len = (int)(dot - in); ++ } else ++ len = strlen(in); ++ return (append_str(in, len, p, end)); ++} ++ ++static isc_result_t ++get_reverse(char *reverse, size_t len, char *value, isc_boolean_t 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; ++ ++ dns_fixedname_init(&fname); ++ name = dns_fixedname_name(&fname); ++ result = dns_byaddr_createptrname2(&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); ++ result = append_str(".in-addr.arpa.", 15, &p, end); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ return (ISC_R_SUCCESS); ++ } ++} ++ ++int ++main(int argc, char *argv[]) { ++ dns_client_t *client = NULL; ++ isc_result_t result; ++ dns_fixedname_t qfn; ++ dns_name_t *query_name, *response_name; ++ dns_rdataset_t *rdataset; ++ dns_namelist_t namelist; ++ unsigned int resopt; ++ isc_appctx_t *actx = NULL; ++ isc_taskmgr_t *taskmgr = NULL; ++ isc_socketmgr_t *socketmgr = NULL; ++ isc_timermgr_t *timermgr = NULL; ++ dns_master_style_t *style = NULL; ++ struct sigaction sa; ++ ++ preparse_args(argc, argv); ++ progname = argv[0]; ++ ++ argc -= optind; ++ argv += optind; ++ ++ isc_lib_register(); ++ result = dns_lib_init(); ++ if (result != ISC_R_SUCCESS) ++ fatal("dns_lib_init failed: %d", result); ++ ++ result = isc_mem_create(0, 0, &mctx); ++ if (result != ISC_R_SUCCESS) ++ fatal("failed to create mctx"); ++ ++ CHECK(isc_appctx_create(mctx, &actx)); ++ CHECK(isc_taskmgr_createinctx(mctx, actx, 1, 0, &taskmgr)); ++ CHECK(isc_socketmgr_createinctx(mctx, actx, &socketmgr)); ++ CHECK(isc_timermgr_createinctx(mctx, actx, &timermgr)); ++ ++ parse_args(argc, argv); ++ ++ CHECK(setup_style(&style)); ++ ++ setup_logging(stderr); ++ ++ CHECK(isc_app_ctxstart(actx)); ++ ++ /* Unblock SIGINT if it's been blocked by isc_app_ctxstart() */ ++ memset(&sa, 0, sizeof(sa)); ++ sa.sa_handler = SIG_DFL; ++ if (sigfillset(&sa.sa_mask) != 0 || sigaction(SIGINT, &sa, NULL) < 0) ++ fatal("Couldn't set up signal handler"); ++ ++ /* Create client */ ++ result = dns_client_createx(mctx, actx, taskmgr, socketmgr, timermgr, ++ 0, &client); ++ if (result != ISC_R_SUCCESS) { ++ delve_log(ISC_LOG_ERROR, "dns_client_create: %s", ++ isc_result_totext(result)); ++ goto cleanup; ++ } ++ ++ /* Set the nameserver */ ++ if (server != NULL) ++ addserver(client); ++ else ++ findserver(client); ++ ++ CHECK(setup_dnsseckeys(client)); ++ ++ /* Construct QNAME */ ++ convert_name(&qfn, &query_name, qname); ++ ++ /* Set up resolution options */ ++ resopt = DNS_CLIENTRESOPT_ALLOWRUN | DNS_CLIENTRESOPT_NOCDFLAG; ++ if (no_sigs) ++ resopt |= DNS_CLIENTRESOPT_NODNSSEC; ++ if (!root_validation && !dlv_validation) ++ resopt |= DNS_CLIENTRESOPT_NOVALIDATE; ++ if (cdflag) ++ resopt &= ~DNS_CLIENTRESOPT_NOCDFLAG; ++ ++ /* Perform resolution */ ++ ISC_LIST_INIT(namelist); ++ result = dns_client_resolve(client, query_name, dns_rdataclass_in, ++ qtype, resopt, &namelist); ++ if (result != ISC_R_SUCCESS) ++ delve_log(ISC_LOG_ERROR, "resolution failed: %s", ++ isc_result_totext(result)); ++ ++ for (response_name = ISC_LIST_HEAD(namelist); ++ response_name != NULL; ++ response_name = ISC_LIST_NEXT(response_name, link)) { ++ for (rdataset = ISC_LIST_HEAD(response_name->list); ++ rdataset != NULL; ++ rdataset = ISC_LIST_NEXT(rdataset, link)) { ++ result = printdata(rdataset, response_name, style); ++ if (result != ISC_R_SUCCESS) ++ delve_log(ISC_LOG_ERROR, "print data failed"); ++ } ++ } ++ ++ dns_client_freeresanswer(client, &namelist); ++ ++cleanup: ++ if (dlv_anchor != NULL) ++ isc_mem_free(mctx, dlv_anchor); ++ if (trust_anchor != NULL) ++ isc_mem_free(mctx, trust_anchor); ++ if (anchorfile != NULL) ++ isc_mem_free(mctx, anchorfile); ++ if (qname != NULL) ++ isc_mem_free(mctx, qname); ++ if (style != NULL) ++ dns_master_styledestroy(&style, mctx); ++ if (client != NULL) ++ dns_client_destroy(&client); ++ if (taskmgr != NULL) ++ isc_taskmgr_destroy(&taskmgr); ++ if (timermgr != NULL) ++ isc_timermgr_destroy(&timermgr); ++ if (socketmgr != NULL) ++ isc_socketmgr_destroy(&socketmgr); ++ if (actx != NULL) ++ isc_appctx_destroy(&actx); ++ if (lctx != NULL) ++ isc_log_destroy(&lctx); ++ isc_mem_detach(&mctx); ++ ++ dns_lib_shutdown(); ++ ++ exit(0); ++} +diff --git a/bin/delve/delve.docbook b/bin/delve/delve.docbook +new file mode 100644 +index 0000000000..12ff996ba4 +--- /dev/null ++++ b/bin/delve/delve.docbook +@@ -0,0 +1,639 @@ ++]> ++ ++ ++ ++ ++ ++ February 12, 2014 ++ ++ ++ ++ delve ++ 1 ++ BIND9 ++ ++ ++ ++ delve ++ DNS lookup and validation utility ++ ++ ++ ++ ++ 2014 ++ Internet Systems Consortium, Inc. ("ISC") ++ ++ ++ ++ ++ ++ delve ++ @server ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ name ++ type ++ class ++ queryopt ++ ++ ++ ++ delve ++ ++ ++ ++ ++ delve ++ queryopt ++ query ++ ++ ++ ++ ++ DESCRIPTION ++ delve ++ (Domain Entity Lookup & Validation Engine) is a tool for sending ++ DNS queries and validating the results, using the the same internal ++ resolver and validator logic as named. ++ ++ ++ delve will send to a specified name server all ++ queries needed to fetch and validate the requested data; this ++ includes the original requested query, subsequent queries to follow ++ CNAME or DNAME chains, and queries for DNSKEY, DS and DLV records ++ to establish a chain of trust for DNSSEC validation. ++ It does not perform iterative resolution, but simulates the ++ behavior of a name server configured for DNSSEC validating and ++ forwarding. ++ ++ ++ By default, responses are validated using built-in DNSSEC trust ++ anchors for the root zone (".") and for the ISC DNSSEC lookaside ++ validation zone ("dlv.isc.org"). Records returned by ++ delve are either fully validated or ++ were not signed. If validation fails, an explanation of ++ the failure is included in the output; the validation process ++ can be traced in detail. Because delve does ++ not rely on an external server to carry out validation, it can ++ be used to check the validity of DNS responses in environments ++ where local name servers may not be trustworthy. ++ ++ ++ Unless it is told to query a specific name server, ++ delve will try each of the servers listed in ++ /etc/resolv.conf. If no usable server ++ addresses are found, delve will send ++ queries to the localhost addresses (127.0.0.1 for IPv4, ::1 ++ for IPv6). ++ ++ ++ When no command line arguments or options are given, ++ delve will perform an NS query for "." ++ (the root zone). ++ ++ ++ ++ ++ SIMPLE USAGE ++ ++ ++ A typical invocation of delve looks like: ++ delve @server name type ++ where: ++ ++ ++ ++ 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, ++ delve resolves that name before ++ querying that name server (note, however, that this ++ initial lookup is not validated ++ by DNSSEC). ++ ++ ++ If no server argument is ++ provided, delve consults ++ /etc/resolv.conf; if an ++ address is found there, it queries the name server at ++ that address. If either of the or ++ options are in use, then ++ only addresses for the corresponding transport ++ will be tried. If no usable addresses are found, ++ delve will send queries to ++ the localhost addresses (127.0.0.1 for IPv4, ++ ::1 for IPv6). ++ ++ ++ ++ ++ ++ name ++ ++ ++ is the domain name to be looked up. ++ ++ ++ ++ ++ ++ type ++ ++ ++ indicates what type of query is required — ++ ANY, A, MX, etc. ++ type can be any valid query ++ type. If no ++ type argument is supplied, ++ delve will perform a lookup for an ++ A record. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ OPTIONS ++ ++ ++ -a ++ ++ ++ Specifies a file from which to read DNSSEC trust anchors. ++ The default is /etc/bind.keys, which ++ is included with BIND 9 and contains ++ trust anchors for the root zone (".") and for the ISC ++ DNSSEC lookaside validation zone ("dlv.isc.org"). ++ ++ ++ Keys that do not match the root or DLV trust-anchor ++ names are ignored; these key names can be overridden ++ using the or ++ options. ++ ++ ++ Note: When reading the trust anchor file, ++ delve treats ++ statements and statements ++ identically. That is, for a managed key, it is the ++ initial key that is trusted; RFC 5011 ++ key management is not supported. delve ++ will not consult the managed-keys database maintained by ++ named. This means that if either of the ++ keys in /etc/bind.keys is revoked ++ and rolled over, it will be necessary to update ++ /etc/bind.keys to use DNSSEC ++ validation in delve. ++ ++ ++ ++ ++ ++ -b ++ ++ ++ Sets the source IP address of the query to ++ address. This must be a valid address ++ on one of the host's network interfaces or "0.0.0.0" or "::". ++ An optional source port may be specified by appending ++ "#<port>" ++ ++ ++ ++ ++ ++ -c ++ ++ ++ Sets the query class for the requested data. Currently, ++ only class "IN" is supported in delve ++ and any other value is ignored. ++ ++ ++ ++ ++ ++ -i ++ ++ ++ Insecure mode. This disables internal DNSSEC validation. ++ (Note, however, this does not set the CD bit on upstream ++ queries. If the server being queried is performing DNSSEC ++ validation, then it will not return invalid data; this ++ can cause delve to time out. When it ++ is necessary to examine invalid data to debug a DNSSEC ++ problem, use dig +cd.) ++ ++ ++ ++ ++ ++ -m ++ ++ ++ Enables memory usage debugging. ++ ++ ++ ++ ++ ++ -p ++ ++ ++ Specifies a destination port to use for queries instead of ++ the standard DNS port number 53. This option would be used ++ with a name server that has been configured to listen ++ for queries on a non-standard port number. ++ ++ ++ ++ ++ ++ -4 ++ ++ ++ Forces delve to only use IPv4. ++ ++ ++ ++ ++ ++ -6 ++ ++ ++ Forces delve to only use IPv6. ++ ++ ++ ++ ++ ++ -q ++ ++ ++ Sets the query name to name. ++ While the query name can be specified without using the ++ , it is sometimes necessary to disambiguate ++ names from types or classes (for example, when looking up the ++ name "ns", which could be misinterpreted as the type NS, ++ or "ch", which could be misinterpreted as class CH). ++ ++ ++ ++ ++ ++ -t ++ ++ ++ Sets the query type to type, which ++ can be any valid query type supported in BIND 9 except ++ for zone transfer types AXFR and IXFR. As with ++ , this is useful to distinguish ++ query name type or class when they are ambiguous. ++ it is sometimes necessary to disambiguate names from types. ++ ++ ++ The default query type is "A", unless the ++ option is supplied to indicate a reverse lookup, in which case ++ it is "PTR". ++ ++ ++ ++ ++ ++ -x ++ ++ ++ Performs a reverse lookup, mapping an addresses to ++ a name. addr is an IPv4 address in ++ dotted-decimal notation, or a colon-delimited IPv6 address. ++ When is used, there is no need to provide ++ the name or type ++ arguments. delve automatically performs a ++ lookup for a name like 11.12.13.10.in-addr.arpa ++ and sets the query type to PTR. IPv6 addresses are looked up ++ using nibble format under the IP6.ARPA domain. ++ ++ ++ ++ ++ ++ ++ ++ QUERY OPTIONS ++ ++ delve ++ provides a number of query options which affect the way results are ++ displayed, and in some cases the way lookups are performed. ++ ++ ++ ++ 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 . ++ The query options are: ++ ++ ++ ++ ++ ++ ++ Controls whether to set the CD (checking disabled) bit in ++ queries sent by delve. This may be useful ++ when troubleshooting DNSSEC problems from behind a validating ++ resolver. A validating resolver will block invalid responses, ++ making it difficult to retrieve them for analysis. Setting ++ the CD flag on queries will cause the resolver to return ++ invalid responses, which delve can then ++ validate internally and report the errors in detail. ++ ++ ++ ++ ++ ++ ++ ++ ++ Controls whether to display the CLASS when printing ++ a record. The default is to display the CLASS. ++ ++ ++ ++ ++ ++ ++ ++ ++ Controls whether to display the TTL when printing ++ a record. The default is to display the TTL. ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle resolver fetch logging. This reports the ++ name and type of each query sent by delve ++ in the process of carrying out the resolution and validation ++ process: this includes including the original query and ++ all subsequent queries to follow CNAMEs and to establish a ++ chain of trust for DNSSEC validation. ++ ++ ++ This is equivalent to setting the debug level to 1 in ++ the "resolver" logging category. Setting the systemwide ++ debug level to 1 using the option will ++ product the same output (but will affect other logging ++ categories as well). ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle message logging. This produces a detailed dump of ++ the responses received by delve in the ++ process of carrying out the resolution and validation process. ++ ++ ++ This is equivalent to setting the debug level to 10 ++ for the the "packets" module of the "resolver" logging ++ category. Setting the systemwide debug level to 10 using ++ the option will produce the same output ++ (but will affect other logging categories as well). ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle validation logging. This shows the internal ++ process of the validator as it determines whether an ++ answer is validly signed, unsigned, or invalid. ++ ++ ++ This is equivalent to setting the debug level to 3 ++ for the the "validator" module of the "dnssec" logging ++ category. Setting the systemwide debug level to 3 using ++ the option will produce the same output ++ (but will affect other logging categories as well). ++ ++ ++ ++ ++ ++ ++ ++ ++ Provide a terse answer. The default is to print the answer in a ++ verbose form. ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle the display of comment lines in the output. The default ++ is to print comments. ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle the display of per-record comments in the output (for ++ example, human-readable key information about DNSKEY records). ++ The default is to print per-record comments. ++ ++ ++ ++ ++ ++ ++ ++ ++ Toggle the display of cryptographic fields in DNSSEC records. ++ The contents of these field are unnecessary to debug 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 ]". ++ ++ ++ ++ ++ ++ ++ ++ ++ Controls whether to display the trust level when printing ++ a record. The default is to display the trust level. ++ ++ ++ ++ ++ ++ ++ ++ ++ Split 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. ++ ++ ++ ++ ++ ++ ++ ++ ++ Set or clear the display options ++ , ++ , and ++ as a group. ++ ++ ++ ++ ++ ++ ++ ++ ++ Print long records (such as RRSIG, DNSKEY, and 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 delve ++ output. ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates whether to display RRSIG records in the ++ delve output. The default is to ++ do so. Note that (unlike in dig) ++ this does not control whether to ++ request DNSSEC records or whether to validate them. ++ DNSSEC records are always requested, and validation ++ will always occur unless suppressed by the use of ++ or and ++ . ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates whether to perform conventional (non- ++ lookaside) DNSSEC validation, and if so, specifies the ++ name of a trust anchor. The default is to validate using ++ a trust anchor of "." (the root zone), for which there is ++ a built-in key. If specifying a different trust anchor, ++ then must be used to specify a file ++ containing the key. ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates whether to perform DNSSEC lookaside validation, ++ and if so, specifies the name of the DLV trust anchor. ++ The default is to perform lookaside validation using ++ a trust anchor of "dlv.isc.org", for which there is a ++ built-in key. If specifying a different name, then ++ must be used to specify a file ++ containing the DLV key. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ FILES ++ /etc/bind.keys ++ /etc/resolv.conf ++ ++ ++ ++ SEE ALSO ++ ++ dig1 ++ , ++ ++ named8 ++ , ++ RFC4034, ++ RFC4035, ++ RFC4431, ++ RFC5074, ++ RFC5155. ++ ++ ++ +diff --git a/bin/delve/delve.html b/bin/delve/delve.html +new file mode 100644 +index 0000000000..34c30d2cdd +--- /dev/null ++++ b/bin/delve/delve.html +@@ -0,0 +1,445 @@ ++ ++ ++ ++ ++ ++delve ++ ++ ++
++
++
++

Name

++

delve — DNS lookup and validation utility

++
++
++

Synopsis

++

delve [@server] [-4] [-6] [-a anchor-file] [-b address] [-c class] [-d level] [-i] [-m] [-p port#] [-q name] [-t type] [-x addr] [name] [type] [class] [queryopt...]

++

delve [-h]

++

delve [queryopt...] [query...]

++
++
++

DESCRIPTION

++

delve ++ (Domain Entity Lookup & Validation Engine) is a tool for sending ++ DNS queries and validating the results, using the the same internal ++ resolver and validator logic as named. ++

++

++ delve will send to a specified name server all ++ queries needed to fetch and validate the requested data; this ++ includes the original requested query, subsequent queries to follow ++ CNAME or DNAME chains, and queries for DNSKEY, DS and DLV records ++ to establish a chain of trust for DNSSEC validation. ++ It does not perform iterative resolution, but simulates the ++ behavior of a name server configured for DNSSEC validating and ++ forwarding. ++

++

++ By default, responses are validated using built-in DNSSEC trust ++ anchors for the root zone (".") and for the ISC DNSSEC lookaside ++ validation zone ("dlv.isc.org"). Records returned by ++ delve are either fully validated or ++ were not signed. If validation fails, an explanation of ++ the failure is included in the output; the validation process ++ can be traced in detail. Because delve does ++ not rely on an external server to carry out validation, it can ++ be used to check the validity of DNS responses in environments ++ where local name servers may not be trustworthy. ++

++

++ Unless it is told to query a specific name server, ++ delve will try each of the servers listed in ++ /etc/resolv.conf. If no usable server ++ addresses are found, delve will send ++ queries to the localhost addresses (127.0.0.1 for IPv4, ::1 ++ for IPv6). ++

++

++ When no command line arguments or options are given, ++ delve will perform an NS query for "." ++ (the root zone). ++

++
++
++

SIMPLE USAGE

++

++ A typical invocation of delve looks like: ++

++
 delve @server name type 
++

++ where: ++ ++

++
++
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, ++ delve resolves that name before ++ querying that name server (note, however, that this ++ initial lookup is not validated ++ by DNSSEC). ++

++

++ If no server argument is ++ provided, delve consults ++ /etc/resolv.conf; if an ++ address is found there, it queries the name server at ++ that address. If either of the -4 or ++ -6 options are in use, then ++ only addresses for the corresponding transport ++ will be tried. If no usable addresses are found, ++ delve will send queries to ++ the localhost addresses (127.0.0.1 for IPv4, ++ ::1 for IPv6). ++

++
++
name
++

++ is the domain name to be looked up. ++

++
type
++

++ indicates what type of query is required — ++ ANY, A, MX, etc. ++ type can be any valid query ++ type. If no ++ type argument is supplied, ++ delve will perform a lookup for an ++ A record. ++

++
++

++

++
++
++

OPTIONS

++
++
-a
++
++

++ Specifies a file from which to read DNSSEC trust anchors. ++ The default is /etc/bind.keys, which ++ is included with BIND 9 and contains ++ trust anchors for the root zone (".") and for the ISC ++ DNSSEC lookaside validation zone ("dlv.isc.org"). ++

++

++ Keys that do not match the root or DLV trust-anchor ++ names are ignored; these key names can be overridden ++ using the +dlv=NAME or ++ +root=NAME options. ++

++

++ Note: When reading the trust anchor file, ++ delve treats managed-keys ++ statements and trusted-keys statements ++ identically. That is, for a managed key, it is the ++ initial key that is trusted; RFC 5011 ++ key management is not supported. delve ++ will not consult the managed-keys database maintained by ++ named. This means that if either of the ++ keys in /etc/bind.keys is revoked ++ and rolled over, it will be necessary to update ++ /etc/bind.keys to use DNSSEC ++ validation in delve. ++

++
++
-b
++

++ Sets the source IP address of the query to ++ address. This must be a valid address ++ on one of the host's network interfaces or "0.0.0.0" or "::". ++ An optional source port may be specified by appending ++ "#<port>" ++

++
-c
++

++ Sets the query class for the requested data. Currently, ++ only class "IN" is supported in delve ++ and any other value is ignored. ++

++
-i
++

++ Insecure mode. This disables internal DNSSEC validation. ++ (Note, however, this does not set the CD bit on upstream ++ queries. If the server being queried is performing DNSSEC ++ validation, then it will not return invalid data; this ++ can cause delve to time out. When it ++ is necessary to examine invalid data to debug a DNSSEC ++ problem, use dig +cd.) ++

++
-m
++

++ Enables memory usage debugging. ++

++
-p
++

++ Specifies a destination port to use for queries instead of ++ the standard DNS port number 53. This option would be used ++ with a name server that has been configured to listen ++ for queries on a non-standard port number. ++

++
-4
++

++ Forces delve to only use IPv4. ++

++
-6
++

++ Forces delve to only use IPv6. ++

++
-q
++

++ Sets the query name to name. ++ While the query name can be specified without using the ++ -q, it is sometimes necessary to disambiguate ++ names from types or classes (for example, when looking up the ++ name "ns", which could be misinterpreted as the type NS, ++ or "ch", which could be misinterpreted as class CH). ++

++
-t
++
++

++ Sets the query type to type, which ++ can be any valid query type supported in BIND 9 except ++ for zone transfer types AXFR and IXFR. As with ++ -q, this is useful to distinguish ++ query name type or class when they are ambiguous. ++ it is sometimes necessary to disambiguate names from types. ++

++

++ The default query type is "A", unless the -x ++ option is supplied to indicate a reverse lookup, in which case ++ it is "PTR". ++

++
++
-x
++

++ Performs a reverse lookup, mapping an addresses to ++ a name. addr is an IPv4 address in ++ dotted-decimal notation, or a colon-delimited IPv6 address. ++ When -x is used, there is no need to provide ++ the name or type ++ arguments. delve automatically performs a ++ lookup for a name like 11.12.13.10.in-addr.arpa ++ and sets the query type to PTR. IPv6 addresses are looked up ++ using nibble format under the IP6.ARPA domain. ++

++
++
++
++

QUERY OPTIONS

++

delve ++ provides a number of query options which affect the way results are ++ displayed, and in some cases the way lookups are performed. ++

++

++ 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. ++ The query options are: ++ ++

++
++
+[no]cdflag
++

++ Controls whether to set the CD (checking disabled) bit in ++ queries sent by delve. This may be useful ++ when troubleshooting DNSSEC problems from behind a validating ++ resolver. A validating resolver will block invalid responses, ++ making it difficult to retrieve them for analysis. Setting ++ the CD flag on queries will cause the resolver to return ++ invalid responses, which delve can then ++ validate internally and report the errors in detail. ++

++
+[no]class
++

++ Controls whether to display the CLASS when printing ++ a record. The default is to display the CLASS. ++

++
+[no]ttl
++

++ Controls whether to display the TTL when printing ++ a record. The default is to display the TTL. ++

++
+[no]rtrace
++
++

++ Toggle resolver fetch logging. This reports the ++ name and type of each query sent by delve ++ in the process of carrying out the resolution and validation ++ process: this includes including the original query and ++ all subsequent queries to follow CNAMEs and to establish a ++ chain of trust for DNSSEC validation. ++

++

++ This is equivalent to setting the debug level to 1 in ++ the "resolver" logging category. Setting the systemwide ++ debug level to 1 using the -d option will ++ product the same output (but will affect other logging ++ categories as well). ++

++
++
+[no]mtrace
++
++

++ Toggle message logging. This produces a detailed dump of ++ the responses received by delve in the ++ process of carrying out the resolution and validation process. ++

++

++ This is equivalent to setting the debug level to 10 ++ for the the "packets" module of the "resolver" logging ++ category. Setting the systemwide debug level to 10 using ++ the -d option will produce the same output ++ (but will affect other logging categories as well). ++

++
++
+[no]vtrace
++
++

++ Toggle validation logging. This shows the internal ++ process of the validator as it determines whether an ++ answer is validly signed, unsigned, or invalid. ++

++

++ This is equivalent to setting the debug level to 3 ++ for the the "validator" module of the "dnssec" logging ++ category. Setting the systemwide debug level to 3 using ++ the -d option will produce the same output ++ (but will affect other logging categories as well). ++

++
++
+[no]short
++

++ Provide a terse answer. The default is to print the answer in a ++ verbose form. ++

++
+[no]comments
++

++ Toggle the display of comment lines in the output. The default ++ is to print comments. ++

++
+[no]rrcomments
++

++ Toggle the display of per-record comments in the output (for ++ example, human-readable key information about DNSKEY records). ++ The default is to print per-record comments. ++

++
+[no]crypto
++

++ Toggle the display of cryptographic fields in DNSSEC records. ++ The contents of these field are unnecessary to debug 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 ]". ++

++
+[no]trust
++

++ Controls whether to display the trust level when printing ++ a record. The default is to display the trust level. ++

++
+[no]split[=W]
++

++ Split 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. ++

++
+[no]all
++

++ Set or clear the display options ++ +[no]comments, ++ +[no]rrcomments, and ++ +[no]trust as a group. ++

++
+[no]multiline
++

++ Print long records (such as RRSIG, DNSKEY, and 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 delve ++ output. ++

++
+[no]dnssec
++

++ Indicates whether to display RRSIG records in the ++ delve output. The default is to ++ do so. Note that (unlike in dig) ++ this does not control whether to ++ request DNSSEC records or whether to validate them. ++ DNSSEC records are always requested, and validation ++ will always occur unless suppressed by the use of ++ -i or +noroot and ++ +nodlv. ++

++
+[no]root[=ROOT]
++

++ Indicates whether to perform conventional (non- ++ lookaside) DNSSEC validation, and if so, specifies the ++ name of a trust anchor. The default is to validate using ++ a trust anchor of "." (the root zone), for which there is ++ a built-in key. If specifying a different trust anchor, ++ then -a must be used to specify a file ++ containing the key. ++

++
+[no]dlv[=DLV]
++

++ Indicates whether to perform DNSSEC lookaside validation, ++ and if so, specifies the name of the DLV trust anchor. ++ The default is to perform lookaside validation using ++ a trust anchor of "dlv.isc.org", for which there is a ++ built-in key. If specifying a different name, then ++ -a must be used to specify a file ++ containing the DLV key. ++

++
++

++ ++

++
++
++

FILES

++

/etc/bind.keys

++

/etc/resolv.conf

++
++
++

SEE ALSO

++

dig(1), ++ named(8), ++ RFC4034, ++ RFC4035, ++ RFC4431, ++ RFC5074, ++ RFC5155. ++

++
++
++ +diff --git a/bin/named/config.c b/bin/named/config.c +index b43c0fcb0e..818ed3797a 100644 +--- a/bin/named/config.c ++++ b/bin/named/config.c +@@ -78,6 +78,8 @@ options {\n\ + memstatistics-file \"named.memstats\";\n\ + multiple-cnames no;\n\ + # named-xfer ;\n\ ++ nta-lifetime 3600;\n\ ++ nta-recheck 300;\n\ + # pid-file \"" NS_LOCALSTATEDIR "/run/named/named.pid\"; /* or /lwresd.pid */\n\ + bindkeys-file \"" NS_SYSCONFDIR "/bind.keys\";\n\ + port 53;\n\ +diff --git a/bin/named/control.c b/bin/named/control.c +index 86fa691bd2..7a8e71f19b 100644 +--- a/bin/named/control.c ++++ b/bin/named/control.c +@@ -210,6 +210,8 @@ ns_control_docommand(isccc_sexpr_t *message, isc_buffer_t *text) { + result = ns_server_del_zone(ns_g_server, command); + } else if (command_compare(command, NS_COMMAND_SIGNING)) { + result = ns_server_signing(ns_g_server, command, text); ++ } else if (command_compare(command, NS_COMMAND_NTA)) { ++ result = ns_server_nta(ns_g_server, command, text); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, +diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h +index 52ed583a5a..1c8275477f 100644 +--- a/bin/named/include/named/control.h ++++ b/bin/named/include/named/control.h +@@ -66,6 +66,7 @@ + #define NS_COMMAND_DELZONE "delzone" + #define NS_COMMAND_SYNC "sync" + #define NS_COMMAND_SIGNING "signing" ++#define NS_COMMAND_NTA "nta" + + isc_result_t + ns_controls_create(ns_server_t *server, ns_controls_t **ctrlsp); +diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h +index 83622f406a..0107b51e08 100644 +--- a/bin/named/include/named/server.h ++++ b/bin/named/include/named/server.h +@@ -251,6 +251,18 @@ ns_server_togglequerylog(ns_server_t *server, char *args); + * but can also be used as a toggle for backward comptibility.) + */ + ++/*% ++ * Save the current NTAs for all views to files. ++ */ ++isc_result_t ++ns_server_saventa(ns_server_t *server); ++ ++/*% ++ * Load NTAs for all views from files. ++ */ ++isc_result_t ++ns_server_loadnta(ns_server_t *server); ++ + /*% + * Dump the current statistics to the statistics file. + */ +@@ -365,4 +377,11 @@ ns_server_del_zone(ns_server_t *server, char *args); + */ + isc_result_t + ns_server_signing(ns_server_t *server, char *args, isc_buffer_t *text); ++ ++/*% ++ * Adds a Negative Trust Anchor (NTA) for a specified name and ++ * duration, in a particular view if specified, or in all views. ++ */ ++isc_result_t ++ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text); + #endif /* NAMED_SERVER_H */ +diff --git a/bin/named/server.c b/bin/named/server.c +index a00cf1596a..0c297049ed 100644 +--- a/bin/named/server.c ++++ b/bin/named/server.c +@@ -73,6 +73,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -89,6 +90,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -812,6 +814,14 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, + return (ISC_R_UNEXPECTED); + } + ++ result = dns_view_initntatable(view, ns_g_taskmgr, ns_g_timermgr); ++ if (result != ISC_R_SUCCESS) { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_ERROR, ++ "couldn't create NTA table"); ++ return (ISC_R_UNEXPECTED); ++ } ++ + if (auto_dlv && view->rdclass == dns_rdataclass_in) { + const cfg_obj_t *builtin_keys = NULL; + const cfg_obj_t *builtin_managed_keys = NULL; +@@ -3263,6 +3273,16 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + if (result == ISC_R_SUCCESS) + CHECK(mustbesecure(obj, view->resolver)); + ++ obj = NULL; ++ result = ns_config_get(maps, "nta-recheck", &obj); ++ INSIST(result == ISC_R_SUCCESS); ++ view->nta_recheck = cfg_obj_asuint32(obj); ++ ++ obj = NULL; ++ result = ns_config_get(maps, "nta-lifetime", &obj); ++ INSIST(result == ISC_R_SUCCESS); ++ view->nta_lifetime = cfg_obj_asuint32(obj); ++ + obj = NULL; + result = ns_config_get(maps, "preferred-glue", &obj); + if (result == ISC_R_SUCCESS) { +@@ -3348,6 +3368,7 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + } else { + empty_zones_enable = ISC_FALSE; + } ++ + if (empty_zones_enable && !lwresd_g_useresolvconf) { + const char *empty; + int empty_zone = 0; +@@ -5069,9 +5090,10 @@ load_configuration(const char *filename, ns_server_t *server, + maps[i] = NULL; + + /* +- * If bind.keys exists, load it. If "dnssec-lookaside auto" +- * is turned on, the keys found there will be used as default +- * trust anchors. ++ * If bind.keys exists, load it. If "dnssec-validation auto" ++ * is turned on, the root key found there will be used as a ++ * default trust anchor, and if "dnssec-lookaside auto" is ++ * turned on, then the DLV key found there will too. + */ + obj = NULL; + result = ns_config_get(maps, "bindkeys-file", &obj); +@@ -5852,6 +5874,8 @@ load_configuration(const char *filename, ns_server_t *server, + server->flushonshutdown = ISC_FALSE; + } + ++ (void) ns_server_loadnta(server); ++ + result = ISC_R_SUCCESS; + + cleanup: +@@ -6147,6 +6171,8 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { + cfg_obj_destroy(ns_g_parser, &ns_g_config); + cfg_parser_destroy(&ns_g_parser); + ++ (void) ns_server_saventa(server); ++ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = view_next) { +@@ -7297,6 +7323,7 @@ isc_result_t + ns_server_dumpsecroots(ns_server_t *server, char *args) { + dns_view_t *view; + dns_keytable_t *secroots = NULL; ++ dns_ntatable_t *ntatable = NULL; + isc_result_t result; + char *ptr; + FILE *fp = NULL; +@@ -7330,11 +7357,25 @@ ns_server_dumpsecroots(ns_server_t *server, char *args) { + result = ISC_R_SUCCESS; + continue; + } +- fprintf(fp, "\n Start view %s\n\n", view->name); ++ fprintf(fp, "\n Start view %s\n", view->name); ++ fprintf(fp, " Secure roots:\n\n"); + result = dns_keytable_dump(secroots, fp); + if (result != ISC_R_SUCCESS) + fprintf(fp, " dumpsecroots failed: %s\n", + isc_result_totext(result)); ++ ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ result = dns_view_getntatable(view, &ntatable); ++ if (result == ISC_R_NOTFOUND) { ++ result = ISC_R_SUCCESS; ++ continue; ++ } ++ fprintf(fp, "\n Negative trust anchors:\n\n"); ++ result = dns_ntatable_dump(ntatable, fp); ++ if (result != ISC_R_SUCCESS) ++ fprintf(fp, " dumpntatable failed: %s\n", ++ isc_result_totext(result)); + } + if (ptr != NULL) + ptr = next_token(&args, " \t"); +@@ -7343,6 +7384,8 @@ ns_server_dumpsecroots(ns_server_t *server, char *args) { + cleanup: + if (secroots != NULL) + dns_keytable_detach(&secroots); ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); + if (fp != NULL) + (void)isc_stdio_close(fp); + if (result == ISC_R_SUCCESS) +@@ -8800,3 +8843,295 @@ ns_server_signing(ns_server_t *server, char *args, isc_buffer_t *text) { + + return (result); + } ++ ++static isc_result_t ++putstr(isc_buffer_t *b, const char *str) { ++ unsigned int l = strlen(str); ++ ++ /* ++ * Use >= to leave space for NUL termination. ++ */ ++ if (l >= isc_buffer_availablelength(b)) ++ return (ISC_R_NOSPACE); ++ ++ isc_buffer_putmem(b, (const unsigned char *)str, l); ++ return (ISC_R_SUCCESS); ++} ++ ++static inline isc_boolean_t ++argcheck(char *cmd, const char *full) { ++ size_t l; ++ ++ if (cmd == NULL || cmd[0] != '-') ++ return (ISC_FALSE); ++ ++ cmd++; ++ l = strlen(cmd); ++ if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) ++ return (ISC_FALSE); ++ ++ return (ISC_TRUE); ++} ++ ++isc_result_t ++ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { ++ dns_view_t *view; ++ dns_ntatable_t *ntatable = NULL; ++ isc_result_t result; ++ char *ptr, *nametext = NULL, *viewname; ++ isc_stdtime_t now, when; ++ isc_time_t t; ++ char tbuf[64]; ++ const char *msg = NULL; ++ isc_boolean_t dump = ISC_FALSE, force = ISC_FALSE; ++ dns_fixedname_t fn; ++ dns_name_t *ntaname; ++ dns_ttl_t ntattl; ++ isc_boolean_t ttlset = ISC_FALSE, excl = ISC_FALSE; ++ ++ UNUSED(force); ++ ++ dns_fixedname_init(&fn); ++ ntaname = dns_fixedname_name(&fn); ++ ++ /* Skip the command name. */ ++ ptr = next_token(&args, " \t"); ++ if (ptr == NULL) ++ return (ISC_R_UNEXPECTEDEND); ++ ++ for (;;) { ++ /* Check for options */ ++ ptr = next_token(&args, " \t"); ++ if (ptr == NULL) ++ return (ISC_R_UNEXPECTEDEND); ++ ++ if (argcheck(ptr, "dump")) ++ dump = ISC_TRUE; ++ else if (argcheck(ptr, "remove")) { ++ ntattl = 0; ++ ttlset = ISC_TRUE; ++ } else if (argcheck(ptr, "force")) { ++ force = ISC_TRUE; ++ continue; ++ } else if (argcheck(ptr, "lifetime")) { ++ isc_textregion_t tr; ++ ++ ptr = next_token(&args, " \t"); ++ if (ptr == NULL) { ++ msg = "No lifetime specified"; ++ CHECK(ISC_R_UNEXPECTEDEND); ++ } ++ ++ tr.base = ptr; ++ tr.length = strlen(ptr); ++ result = dns_ttl_fromtext(&tr, &ntattl); ++ if (result != ISC_R_SUCCESS) { ++ msg = "could not parse NTA lifetime"; ++ CHECK(result); ++ } ++ ++ if (ntattl > 604800) { ++ msg = "NTA lifetime cannot exceed one week"; ++ CHECK(ISC_R_RANGE); ++ } ++ ++ ttlset = ISC_TRUE; ++ continue; ++ } else ++ nametext = ptr; ++ ++ break; ++ } ++ ++ /* ++ * If -dump was specified, list NTA's and return ++ */ ++ if (dump) { ++ for (view = ISC_LIST_HEAD(server->viewlist); ++ view != NULL; ++ view = ISC_LIST_NEXT(view, link)) ++ { ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ result = dns_view_getntatable(view, &ntatable); ++ if (result == ISC_R_NOTFOUND) { ++ result = ISC_R_SUCCESS; ++ continue; ++ } ++ CHECK(dns_ntatable_totext(ntatable, text)); ++ } ++ ++ goto cleanup; ++ } ++ ++ /* Get the NTA name. */ ++ if (nametext == NULL) ++ nametext = next_token(&args, " \t"); ++ if (nametext == NULL) ++ return (ISC_R_UNEXPECTEDEND); ++ ++ if (strcmp(nametext, ".") == 0) ++ ntaname = dns_rootname; ++ else { ++ isc_buffer_t b; ++ isc_buffer_init(&b, nametext, strlen(nametext)); ++ isc_buffer_add(&b, strlen(nametext)); ++ CHECK(dns_name_fromtext(ntaname, &b, dns_rootname, 0, NULL)); ++ } ++ ++ /* Look for the view name. */ ++ viewname = next_token(&args, " \t"); ++ ++ isc_stdtime_get(&now); ++ ++ result = isc_task_beginexclusive(server->task); ++ RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ excl = ISC_TRUE; ++ for (view = ISC_LIST_HEAD(server->viewlist); ++ view != NULL; ++ view = ISC_LIST_NEXT(view, link)) ++ { ++ if (viewname != NULL && ++ strcmp(view->name, viewname) != 0) ++ continue; ++ ++ if (view->nta_lifetime == 0) ++ continue; ++ ++ if (!ttlset) ++ ntattl = view->nta_lifetime; ++ ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ ++ result = dns_view_getntatable(view, &ntatable); ++ if (result == ISC_R_NOTFOUND) { ++ result = ISC_R_SUCCESS; ++ continue; ++ } ++ ++ result = dns_view_flushnode(view, ntaname, ISC_TRUE); ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_INFO, ++ "flush tree '%s' in cache view '%s': %s", ++ nametext, view->name, ++ isc_result_totext(result)); ++ ++ if (ntattl != 0) { ++ CHECK(dns_ntatable_add(ntatable, ntaname, ++ force, now, ntattl)); ++ ++ when = now + ntattl; ++ isc_time_set(&t, when, 0); ++ isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); ++ ++ CHECK(putstr(text, "Negative trust anchor added: ")); ++ CHECK(putstr(text, nametext)); ++ CHECK(putstr(text, "/")); ++ CHECK(putstr(text, view->name)); ++ CHECK(putstr(text, ", expires ")); ++ CHECK(putstr(text, tbuf)); ++ ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_INFO, ++ "added NTA '%s' (%d sec) in view '%s'", ++ nametext, ntattl, view->name); ++ } else { ++ CHECK(dns_ntatable_delete(ntatable, ntaname)); ++ ++ CHECK(putstr(text, "Negative trust anchor removed: ")); ++ CHECK(putstr(text, nametext)); ++ CHECK(putstr(text, "/")); ++ CHECK(putstr(text, view->name)); ++ ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_INFO, ++ "removed NTA '%s' in view %s", ++ nametext, view->name); ++ } ++ ++ if (isc_buffer_availablelength(text) == 0) ++ return (ISC_R_NOSPACE); ++ result = dns_view_saventa(view); ++ if (result != ISC_R_SUCCESS) { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_ERROR, ++ "error writing NTA file " ++ "for view '%s': %s", ++ view->name, isc_result_totext(result)); ++ } ++ ++ ++ ++ isc_buffer_putuint8(text, 0); ++ } ++ cleanup: ++ if (msg != NULL && strlen(msg) < isc_buffer_availablelength(text)) ++ isc_buffer_putstr(text, msg); ++ if (excl) ++ isc_task_endexclusive(server->task); ++ ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ return (result); ++} ++ ++isc_result_t ++ns_server_saventa(ns_server_t *server) { ++ dns_view_t *view; ++ ++ for (view = ISC_LIST_HEAD(server->viewlist); ++ view != NULL; ++ view = ISC_LIST_NEXT(view, link)) ++ { ++ isc_result_t result = dns_view_saventa(view); ++ ++ if (result != ISC_R_SUCCESS) { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_ERROR, ++ "error writing NTA file '%s' " ++ "for view '%s': %s", ++ view->nta_file, view->name, ++ isc_result_totext(result)); ++ } else { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), ++ "written NTA file '%s' for view '%s'", ++ view->nta_file, view->name); ++ } ++ ++ } ++ ++ return (ISC_R_SUCCESS); ++} ++ ++isc_result_t ++ns_server_loadnta(ns_server_t *server) { ++ dns_view_t *view; ++ ++ for (view = ISC_LIST_HEAD(server->viewlist); ++ view != NULL; ++ view = ISC_LIST_NEXT(view, link)) ++ { ++ isc_result_t result = dns_view_loadnta(view); ++ ++ if ((result != ISC_R_SUCCESS) && ++ (result != ISC_R_FILENOTFOUND) && ++ (result != ISC_R_NOTFOUND)) ++ { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_ERROR, ++ "error loading NTA file '%s' " ++ "for view '%s': %s", ++ view->nta_file, view->name, ++ isc_result_totext(result)); ++ } else if (result == ISC_R_SUCCESS) { ++ isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, ++ NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), ++ "loaded NTA file '%s' for view '%s'", ++ view->nta_file, view->name); ++ } ++ } ++ ++ return (ISC_R_SUCCESS); ++} +diff --git a/bin/rndc/rndc.c b/bin/rndc/rndc.c +index c67223bbbb..81e629f15c 100644 +--- a/bin/rndc/rndc.c ++++ b/bin/rndc/rndc.c +@@ -157,6 +157,18 @@ command is one of the following:\n\ + Delete a TKEY-negotiated TSIG key.\n\ + validation newstate [view]\n\ + Enable / disable DNSSEC validation.\n\ ++ nta [-lifetime duration] [-force] domain [view]\n\ ++ Set a negative trust anchor, disabling DNSSEC validation\n\ ++ for the given domain.\n\ ++ Using -lifetime specifies the duration of the NTA, up\n\ ++ to one day.\n\ ++ Using -force prevents the NTA from expiring before its\n\ ++ full lifetime, even if the domain can validate sooner.\n\ ++ nta -remove domain [view]\n\ ++ Remove a negative trust anchor, re-enabling validation\n\ ++ for the given domain.\n\ ++ nta -dump\n\ ++ List all negative trust anchors.\n\ + addzone [\"file\"] zone [class [view]] { zone-options }\n\ + Add zone to given view. Requires new-zone-file option.\n\ + delzone [\"file\"] zone [class [view]]\n\ +diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook +index ac2e54d98d..3c2061e26f 100644 +--- a/bin/rndc/rndc.docbook ++++ b/bin/rndc/rndc.docbook +@@ -427,10 +427,9 @@ + secroots view ... + + +- Dump the server's security roots to the secroots +- file for the specified views. If no view is +- specified, security roots for all +- views are dumped. ++ Dump the server's security roots and negative trust anchors ++ to the secroots file for the specified views. If no view is ++ specified, all views are dumped. + + +
+@@ -561,6 +560,81 @@ + + + ++ ++ nta ++ ( -d | -f | -r | -l duration) ++ domain ++ view ++ ++ ++ ++ Sets a DNSSEC negative trust anchor (NTA) ++ for , with a lifetime of ++ . The default lifetime is ++ configured in named.conf via the ++ , and defaults to ++ one hour. The lifetime cannot exceed one week. ++ ++ ++ A negative trust anchor selectively disables ++ DNSSEC validation for zones that known to be ++ failing because of misconfiguration rather than ++ an attack. When data to be validated is ++ at or below an active NTA (and above any other ++ configured trust anchors), named will ++ abort the DNSSEC validation process and treat the data as ++ insecure rather than bogus. This continues until the ++ NTA's lifetime is elapsed. ++ ++ ++ NTAs persist across restarts of the named server. ++ The NTAs for a view are saved in a file called ++ name.nta, ++ where name is the ++ name of the view, or if it contains characters ++ that are incompatible with use as a file name, a ++ cryptographic hash generated from the name ++ of the view. ++ ++ ++ An existing NTA can be removed by using the ++ option. ++ ++ ++ An NTA's lifetime can be specified with the ++ option. TTL-style ++ suffixes can be used to specify the lifetime in ++ seconds, minutes, or hours. If the specified NTA ++ already exists, its lifetime will be updated to the ++ new value. Setting to zero ++ is equivalent to . ++ ++ ++ If is used, any other arguments ++ are ignored, and a list of existing NTAs is printed ++ (note that this may include NTAs that are expired but ++ have not yet been cleaned up). ++ ++ ++ Normally, named will periodically ++ test to see whether data below an NTA can now be ++ validated (see the option ++ in the Administrator Reference Manual for details). ++ If data can be validated, then the NTA is regarded as ++ no longer necessary, and will be allowed to expire ++ early. The overrides this ++ behavior and forces an NTA to persist for its entire ++ lifetime, regardless of whether data could be ++ validated if the NTA were not present. ++ ++ ++ All of these options can be shortened, i.e., to ++ , , , ++ and . ++ ++ ++ ++ + + tsig-list + +diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf +index a6310cd1e2..cf7c745722 100644 +--- a/bin/tests/system/checkconf/good.conf ++++ b/bin/tests/system/checkconf/good.conf +@@ -67,6 +67,8 @@ options { + serial-query-rate 100; + server-id none; + max-cache-size 20000000000000; ++ nta-recheck 604800; ++ nta-lifetime 604800; + zone-statistics none; + }; + view "first" { +diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh +index 64563154df..7ddd9c789f 100644 +--- a/bin/tests/system/checkconf/tests.sh ++++ b/bin/tests/system/checkconf/tests.sh +@@ -12,8 +12,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + SYSTEMTESTTOP=.. + . $SYSTEMTESTTOP/conf.sh + +diff --git a/bin/tests/system/dlvauto/tests.sh b/bin/tests/system/dlvauto/tests.sh +index 25bb8e30d8..138a72b850 100644 +--- a/bin/tests/system/dlvauto/tests.sh ++++ b/bin/tests/system/dlvauto/tests.sh +@@ -51,7 +51,7 @@ linecount=`grep "\./RSAMD5/.* ; managed" ns2/named.secroots | wc -l` + linecount=`grep "dlv.isc.org/RSAMD5/.* ; managed" ns2/named.secroots | wc -l` + [ "$linecount" -eq 2 ] || ret=1 + linecount=`cat ns2/named.secroots | wc -l` +-[ "$linecount" -eq 13 ] || ret=1 ++[ "$linecount" -eq 28 ] || ret=1 + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` +diff --git a/bin/tests/system/dnssec/clean.sh b/bin/tests/system/dnssec/clean.sh +index 0f333409b3..3b91a96818 100644 +--- a/bin/tests/system/dnssec/clean.sh ++++ b/bin/tests/system/dnssec/clean.sh +@@ -69,3 +69,4 @@ rm -f ns6/optout-tld.db + rm -f nosign.before + rm -f signing.out* + rm -f canonical?.* ++rm -f ns*/*.nta +diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in +index 7be1044204..e22999f445 100644 +--- a/bin/tests/system/dnssec/ns2/example.db.in ++++ b/bin/tests/system/dnssec/ns2/example.db.in +@@ -65,6 +65,10 @@ ns.insecure A 10.53.0.3 + bogus NS ns.bogus + ns.bogus A 10.53.0.3 + ++; A subdomain with a corrupt DS ++badds NS ns.badds ++ns.badds A 10.53.0.3 ++ + ; A dynamic secure subdomain + dynamic NS dynamic + dynamic A 10.53.0.3 +diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh +index 5b5d337a5d..118b8a6d6b 100644 +--- a/bin/tests/system/dnssec/ns2/sign.sh ++++ b/bin/tests/system/dnssec/ns2/sign.sh +@@ -30,11 +30,10 @@ zonefile=example.db + + ( cd ../ns3 && sh sign.sh ) + +-for subdomain in secure bogus dynamic keyless nsec3 optout nsec3-unknown \ +- optout-unknown multiple rsasha256 rsasha512 kskonly update-nsec3 \ +- auto-nsec auto-nsec3 secure.below-cname ttlpatch split-dnssec \ +- split-smart expired expiring upper lower +- ++for subdomain in secure badds bogus dynamic keyless nsec3 optout \ ++ nsec3-unknown optout-unknown multiple rsasha256 rsasha512 \ ++ kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \ ++ ttlpatch split-dnssec split-smart expired expiring upper lower + do + cp ../ns3/dsset-$subdomain.example. . + done +diff --git a/bin/tests/system/dnssec/ns3/bogus.example.db.in b/bin/tests/system/dnssec/ns3/bogus.example.db.in +index e83d07bea0..80fb5e3992 100644 +--- a/bin/tests/system/dnssec/ns3/bogus.example.db.in ++++ b/bin/tests/system/dnssec/ns3/bogus.example.db.in +@@ -28,5 +28,6 @@ ns A 10.53.0.3 + + a A 10.0.0.1 + b A 10.0.0.2 ++c A 10.0.0.3 + d A 10.0.0.4 + z A 10.0.0.26 +diff --git a/bin/tests/system/dnssec/ns3/named.conf b/bin/tests/system/dnssec/ns3/named.conf +index e745a7525f..6ef21b39c6 100644 +--- a/bin/tests/system/dnssec/ns3/named.conf ++++ b/bin/tests/system/dnssec/ns3/named.conf +@@ -68,6 +68,12 @@ zone "bogus.example" { + allow-update { any; }; + }; + ++zone "badds.example" { ++ type master; ++ file "badds.example.db.signed"; ++ allow-update { any; }; ++}; ++ + zone "dynamic.example" { + type master; + file "dynamic.example.db.signed"; +diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in +index c9de3e5b6d..816f766fcb 100644 +--- a/bin/tests/system/dnssec/ns3/secure.example.db.in ++++ b/bin/tests/system/dnssec/ns3/secure.example.db.in +@@ -28,7 +28,11 @@ ns A 10.53.0.3 + + a A 10.0.0.1 + b A 10.0.0.2 ++c A 10.0.0.3 + d A 10.0.0.4 ++e A 10.0.0.5 ++f A 10.0.0.6 ++g A 10.0.0.7 + z A 10.0.0.26 + a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 + x CNAME a +diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh +index 24b0fed7dd..4f010168cf 100644 +--- a/bin/tests/system/dnssec/ns3/sign.sh ++++ b/bin/tests/system/dnssec/ns3/sign.sh +@@ -451,3 +451,19 @@ kskname=`$KEYGEN -I $now+90s -q -r $RANDFILE -f KSK $zone` + zskname=`$KEYGEN -q -r $RANDFILE $zone` + cp $infile $zonefile + $SIGNER -S -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1 ++ ++ ++# ++# A zone with a bad DS in the parent ++# (sourced from bogus.example.db.in) ++# ++zone=badds.example. ++infile=bogus.example.db.in ++zonefile=badds.example.db ++ ++keyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 768 -n zone $zone` ++ ++cat $infile $keyname.key >$zonefile ++ ++$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1 ++sed -e 's/bogus/badds/g' < dsset-bogus.example. > dsset-badds.example. +\ No newline at end of file +diff --git a/bin/tests/system/dnssec/ns4/named1.conf b/bin/tests/system/dnssec/ns4/named1.conf +index a129c5e51a..542266f2f6 100644 +--- a/bin/tests/system/dnssec/ns4/named1.conf ++++ b/bin/tests/system/dnssec/ns4/named1.conf +@@ -34,6 +34,9 @@ options { + dnssec-validation yes; + dnssec-must-be-secure mustbesecure.example yes; + ++ nta-lifetime 10s; ++ nta-recheck 7s; ++ + # Note: We only reference the bind.keys file here to confirm that it + # is *not* being used. It contains the real root key, and we're + # using a local toy root zone for the tests, so it wouldn't work. +diff --git a/bin/tests/system/dnssec/ntadiff.pl b/bin/tests/system/dnssec/ntadiff.pl +new file mode 100755 +index 0000000000..d3f5554230 +--- /dev/null ++++ b/bin/tests/system/dnssec/ntadiff.pl +@@ -0,0 +1,13 @@ ++#!/usr/bin/perl -w ++ ++use strict; ++use Time::Piece; ++use Time::Seconds; ++ ++exit 1 if (scalar(@ARGV) != 2); ++ ++my $actual = Time::Piece->strptime($ARGV[0], '%d-%b-%Y %H:%M:%S.000 %z'); ++my $expected = Time::Piece->strptime($ARGV[1], '%s') + ONE_WEEK; ++my $diff = abs($actual - $expected); ++ ++print($diff . "\n"); +diff --git a/bin/tests/system/dnssec/setup.sh b/bin/tests/system/dnssec/setup.sh +index 424792966a..7b2a6bc825 100644 +--- a/bin/tests/system/dnssec/setup.sh ++++ b/bin/tests/system/dnssec/setup.sh +@@ -24,6 +24,8 @@ sh clean.sh + cd ns1 && sh sign.sh + + echo "a.bogus.example. A 10.0.0.22" >>../ns3/bogus.example.db.signed ++echo "b.bogus.example. A 10.0.0.23" >>../ns3/bogus.example.db.signed ++echo "c.bogus.example. A 10.0.0.23" >>../ns3/bogus.example.db.signed + + cd ../ns4 && cp -f named1.conf named.conf + cd ../ns5 && cp -f trusted.conf.bad trusted.conf +diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh +index bb7452f82b..9397b75bb4 100644 +--- a/bin/tests/system/dnssec/tests.sh ++++ b/bin/tests/system/dnssec/tests.sh +@@ -21,6 +21,7 @@ SYSTEMTESTTOP=.. + . $SYSTEMTESTTOP/conf.sh + + RANDFILE=random.data ++DEFAULT_NTA=3bf305731dd26307.nta + + status=0 + n=1 +@@ -811,7 +812,7 @@ if [ -x ${SAMPLE} ] ; then + ret=0 + echo "I:checking failed validation using dns_client ($n)" + $SAMPLE $SAMPLEKEY -p 5300 -t a 10.53.0.4 a.bogus.example > /dev/null 2> sample.out$n || ret=1 +- grep "resolution failed: no valid RRSIG" sample.out$n > /dev/null || ret=1 ++ grep "resolution failed: RRSIG failed to verify" sample.out$n > /dev/null || ret=1 + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` +@@ -1505,7 +1506,7 @@ keyid=`cat ns1/managed.key.id` + linecount=`grep "./RSAMD5/$keyid ; trusted" ns4/named.secroots | wc -l` + [ "$linecount" -eq 1 ] || ret=1 + linecount=`cat ns4/named.secroots | wc -l` +-[ "$linecount" -eq 5 ] || ret=1 ++[ "$linecount" -eq 10 ] || ret=1 + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` +@@ -1585,9 +1586,398 @@ ret=0 + $DIG $DIGOPTS ns algroll. @10.53.0.4 > dig.out.ns4.test$n || ret=1 + grep "NOERROR" dig.out.ns4.test$n > /dev/null || ret=1 + grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n > /dev/null || ret=1 ++n=`expr $n + 1` ++if [ $ret != 0 ]; then echo "I:failed"; fi ++status=`expr $status + $ret` ++ ++echo "I:checking positive and negative validation with negative trust anchors ($n)" ++ret=0 ++# ++# check correct initial behavior ++# ++$DIG $DIGOPTS a.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.1 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.1 > /dev/null || ret=1 ++$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null || ret=1 ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null || ret=1 ++ ++# ++# add negative trust anchors ++# ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 15s bogus.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta badds.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig # reconfig should maintain NTAs ++lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` ++[ "$lines" -eq 2 ] || ret=1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta secure.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta fakenode.secure.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reload # reload should maintain NTAs ++lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` ++[ "$lines" -eq 4 ] || ret=1 ++ ++# ++# check behavior with NTA's in place ++# ++$DIG $DIGOPTS a.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.4 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.4 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.4 > /dev/null && ret=1 ++$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.5 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.5 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.5 > /dev/null && ret=1 ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.6 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.6 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.6 > /dev/null && ret=1 ++$DIG $DIGOPTS a.fakenode.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.7 || ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.7 > /dev/null && ret=1 ++echo "I: dumping secroots" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 secroots | sed 's/^/I:ns4 /' ++grep "bogus.example: expiry" ns4/named.secroots > /dev/null || ret=1 ++grep "badds.example: expiry" ns4/named.secroots > /dev/null || ret=1 ++grep "secure.example: expiry" ns4/named.secroots > /dev/null || ret=1 ++grep "fakenode.secure.example: expiry" ns4/named.secroots > /dev/null || ret=1 ++echo "I: waiting for NTA rechecks/expirations" ++ ++# ++# secure.example and badds.example used default nta-duration ++# (configured as 10s in ns4/named1.conf), but nta recheck interval ++# is configured to 7s, so at t=8 the NTAs for secure.example and ++# fakenode.secure.example should both be lifted, but badds.example ++# should still be going. ++# ++sleep 8 ++$DIG $DIGOPTS b.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.8 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.8 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.8 > /dev/null || ret=1 ++$DIG $DIGOPTS b.fakenode.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.9 || ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.9 > /dev/null || ret=1 ++grep "status: NXDOMAIN" dig.out.ns4.test$n.9 > /dev/null || ret=1 ++$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.10 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.10 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.10 > /dev/null && ret=1 ++ ++# ++# bogus.example was set to expire in 15s, so at t=11 ++# it should still be NTA'd, but badds.example used the default ++# lifetime of 10s, so it should revert to SERVFAIL now. ++# ++sleep 3 ++# check nta table ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n._11 ++lines=`wc -l < rndc.out.ns4.test$n._11` ++[ "$lines" -le 2 ] || ret=1 ++grep "bogus.example: expiry" rndc.out.ns4.test$n._11 > /dev/null || ret=1 ++grep "badds.example: expiry" rndc.out.ns4.test$n._11 > /dev/null && ret=1 ++$DIG $DIGOPTS b.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.11 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.11 > /dev/null && ret=1 ++$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.12 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.12 > /dev/null || ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.12 > /dev/null && ret=1 ++$DIG $DIGOPTS c.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.13 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.13 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.13 > /dev/null || ret=1 ++ ++# ++# at t=16, all the NTAs should have expired. ++# ++sleep 5 ++# check correct behavior after bogus.example expiry ++$DIG $DIGOPTS d.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.14 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.14 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.14 > /dev/null || ret=1 ++$DIG $DIGOPTS c.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.15 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.15 > /dev/null || ret=1 ++# check nta table has been cleaned up now ++lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` ++[ "$lines" -eq 0 ] || ret=1 ++n=`expr $n + 1` ++if [ $ret != 0 ]; then echo "I:failed - checking that all nta's have been lifted"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++echo "I: testing NTA removals ($n)" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta badds.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 ++grep "badds.example: expiry" rndc.out.ns4.test$n.1 > /dev/null || ret=1 ++$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.1 || ret=1 ++grep "^a.badds.example." dig.out.ns4.test$n.1 > /dev/null || ret=1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove badds.example > rndc.out.ns4.test$n.2 ++grep "Negative trust anchor removed: badds.example/_default" rndc.out.ns4.test$n.2 > /dev/null || ret=1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.3 ++grep "badds.example: expiry" rndc.out.ns4.test$n.3 > /dev/null && ret=1 ++$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null || ret=1 ++echo "I: remove non-existent NTA three times" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -r foo > rndc.out.ns4.test$n.4 2>&1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove foo > rndc.out.ns4.test$n.5 2>&1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -r foo > rndc.out.ns4.test$n.6 2>&1 ++grep "'nta' failed: not found" rndc.out.ns4.test$n.6 > /dev/null || ret=1 ++if [ $ret != 0 ]; then echo "I:failed"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++n=`expr $n + 1` ++echo "I: testing NTA with bogus lifetimes ($n)" ++echo "I:check with no nta lifetime specified" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l "" foo > rndc.out.ns4.test$n.1 2>&1 ++grep "'nta' failed: bad ttl" rndc.out.ns4.test$n.1 > /dev/null || ret=1 ++echo "I:check with bad nta lifetime" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l garbage foo > rndc.out.ns4.test$n.2 2>&1 ++grep "'nta' failed: bad ttl" rndc.out.ns4.test$n.2 > /dev/null || ret=1 ++echo "I:check with too long nta lifetime" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l 7d1h foo > rndc.out.ns4.test$n.3 2>&1 ++grep "'nta' failed: out of range" rndc.out.ns4.test$n.3 > /dev/null || ret=1 + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` ++ret=0 ++ ++# ++# check NTA persistence across restarts ++# ++n=`expr $n + 1` ++echo "I: testing NTA persistence across restarts ($n)" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 ++lines=`wc -l < rndc.out.ns4.test$n.1` ++[ "$lines" -eq 0 ] || ret=1 ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 30s bogus.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 10s badds.example 2>&1 | sed 's/^/I:ns4 /' ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.2 ++lines=`wc -l < rndc.out.ns4.test$n.2` ++[ "$lines" -eq 2 ] || ret=1 ++start=`$PERL -e 'print time()."\n";'` ++ ++if [ $ret != 0 ]; then echo "I:failed - NTA persistence: adding NTA's failed"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++echo "I:killing ns4 with SIGTERM" ++cd ns4 ++kill -TERM `cat named.pid` ++rm named.pid ++cd .. ++ ++# ++# ns4 has now shutdown. wait until t=14 when badds.example's NTA ++# (lifetime=10s) would have expired, and then restart ns4. ++# ++echo "I:waiting till 14s have passed since NTAs were added before restarting ns4" ++$PERL -e 'my $delay = '$start' + 14 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' ++ ++if ++ $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 ++then ++ echo "I:restarted server ns4" ++else ++ echo "I:could not restart server ns4" ++ exit 1 ++fi ++ ++echo "I:sleeping for an additional 4 seconds for ns4 to fully startup" ++sleep 4 ++ ++# ++# ns4 should be back up now. The NTA for bogus.example should still be ++# valid, whereas badds.example should not have been added during named ++# startup (as it had already expired). ++# ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.3 ++lines=`wc -l < rndc.out.ns4.test$n.3` ++[ "$lines" -eq 1 ] || ret=1 ++grep "bogus.example: expiry" rndc.out.ns4.test$n.3 > /dev/null || ret=1 ++$DIG $DIGOPTS b.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.4 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.4 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.4 > /dev/null && ret=1 ++$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.5 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.5 > /dev/null || ret=1 ++ ++# cleanup ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove bogus.example > rndc.out.ns4.test$n.6 ++ ++if [ $ret != 0 ]; then echo "I:failed - NTA persistence: restoring NTA failed"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++# ++# check "regular" attribute in NTA file works as expected at named ++# startup. ++# ++n=`expr $n + 1` ++echo "I: testing loading regular attribute from NTA file ($n)" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>/dev/null ++lines=`wc -l < rndc.out.ns4.test$n.1` ++[ "$lines" -eq 0 ] || ret=1 ++# initially, secure.example. validates with AD=1 ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.2 > /dev/null || ret=1 ++ ++echo "I:killing ns4 with SIGTERM" ++cd ns4 ++kill -TERM `cat named.pid` ++rm named.pid ++cd .. ++ ++echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" ++sleep 4 ++ ++# ++# ns4 has now shutdown. add NTA for secure.example. directly into the ++# ${DEFAULT_NTA} file with the regular attribute and some future timestamp. ++# ++year=`date +%Y` ++future="`expr 20 + ${year}`0101010000" ++echo "secure.example. regular $future" > ns4/${DEFAULT_NTA} ++start=`$PERL -e 'print time()."\n";'` ++ ++if ++ $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 ++then ++ echo "I:restarted server ns4" ++else ++ echo "I:could not restart server ns4" ++ exit 1 ++fi ++ ++# nta-recheck is configured as 7s, so at t=10 the NTAs for ++# secure.example. should be lifted as it is not a forced NTA. ++echo "I:waiting till 10s have passed after ns4 was restarted" ++$PERL -e 'my $delay = '$start' + 10 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' ++ ++# secure.example. should now return an AD=1 answer (still validates) as ++# the NTA has been lifted. ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null || ret=1 ++ ++# cleanup ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.4 2>/dev/null ++ ++if [ $ret != 0 ]; then echo "I:failed - NTA persistence: loading regular NTAs failed"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++# ++# check "forced" attribute in NTA file works as expected at named ++# startup. ++# ++n=`expr $n + 1` ++echo "I: testing loading forced attribute from NTA file ($n)" ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>/dev/null ++lines=`wc -l < rndc.out.ns4.test$n.1` ++[ "$lines" -eq 0 ] || ret=1 ++# initially, secure.example. validates with AD=1 ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.2 > /dev/null || ret=1 ++ ++echo "I:killing ns4 with SIGTERM" ++cd ns4 ++kill -TERM `cat named.pid` ++rm named.pid ++cd .. ++ ++echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" ++sleep 4 ++ ++# ++# ns4 has now shutdown. add NTA for secure.example. directly into the ++# ${DEFAULT_NTA} file with the forced attribute and some future timestamp. ++# ++echo "secure.example. forced $future" > ns4/${DEFAULT_NTA} ++start=`$PERL -e 'print time()."\n";'` ++ ++if ++ $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 ++then ++ echo "I:restarted server ns4" ++else ++ echo "I:could not restart server ns4" ++ exit 1 ++fi ++ ++# nta-recheck is configured as 7s, but even at t=10 the NTAs for ++# secure.example. should not be lifted as it is a forced NTA. ++echo "I:waiting till 10s have passed after ns4 was restarted" ++$PERL -e 'my $delay = '$start' + 10 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' ++ ++# secure.example. should now return an AD=0 answer (non-authenticated) ++# as the NTA is still there. ++$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 ++grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 ++grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null && ret=1 ++ ++# cleanup ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.4 2>/dev/null ++ ++if [ $ret != 0 ]; then echo "I:failed - NTA persistence: loading forced NTAs failed"; fi ++status=`expr $status + $ret` ++ret=0 ++ ++# ++# check that NTA lifetime read from file is clamped to 1 week. ++# ++n=`expr $n + 1` ++echo "I: testing loading out of bounds lifetime from NTA file ($n)" ++ ++echo "I:killing ns4 with SIGTERM" ++cd ns4 ++kill -TERM `cat named.pid` ++rm named.pid ++cd .. ++ ++echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" ++sleep 4 ++ ++# ++# ns4 has now shutdown. add NTA for secure.example. directly into the ++# ${DEFAULT_NTA} file with a lifetime well into the future. ++# ++echo "secure.example. forced $future" > ns4/${DEFAULT_NTA} ++added=`$PERL -e 'print time()."\n";'` ++ ++if ++ $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 ++then ++ echo "I:restarted server ns4" ++else ++ echo "I:could not restart server ns4" ++ exit 1 ++fi ++ ++echo "I:sleeping for an additional 4 seconds for ns4 to fully startup" ++sleep 4 ++ ++# dump the NTA to a file ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>rndc.out.ns4.test$n.1err ++lines=`wc -l < rndc.out.ns4.test$n.1` ++[ "$lines" -eq 1 ] || ret=1 ++ts=`awk '{print $3" "$4}' < rndc.out.ns4.test$n.1` ++# rndc nta outputs localtime, so append the timezone ++ts_with_zone="$ts `date +%z`" ++echo "ts=$ts" > rndc.out.ns4.test$n.2 ++echo "ts_with_zone=$ts_with_zone" >> rndc.out.ns4.test$n.2 ++echo "added=$added" >> rndc.out.ns4.test$n.2 ++if $PERL -e 'use Time::Piece; use Time::Seconds;' 2>/dev/null ++then ++ # ntadiff.pl computes $ts_with_zone - ($added + 1week) ++ d=`$PERL ./ntadiff.pl "$ts_with_zone" "$added"` ++ echo "d=$d" >> rndc.out.ns4.test$n.2 ++ # diff from $added(now) + 1week to the clamped NTA lifetime should be ++ # less than a few seconds. ++ [ $d -lt 3610 ] || ret=1 ++else ++ echo "I: skipped ntadiff test; install PERL module Time::Piece" ++fi ++ ++# cleanup ++$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.3 2>/dev/null ++ ++if [ $ret != 0 ]; then echo "I:failed - NTA lifetime clamping failed"; fi ++status=`expr $status + $ret` ++ret=0 + ++echo "I:completed NTA tests" + # Run a minimal update test if possible. This is really just + # a regression test for RT #2399; more tests should be added. + +@@ -1689,7 +2079,7 @@ ret=0 + $DIG $DIGOPTS +noauth expired.example. +dnssec @10.53.0.4 soa > dig.out.ns4.test$n || ret=1 + grep "SERVFAIL" dig.out.ns4.test$n > /dev/null || ret=1 + grep "flags:.*ad.*QUERY" dig.out.ns4.test$n > /dev/null && ret=1 +-grep "expired.example .*: RRSIG has expired" ns4/named.run > /dev/null || ret=1 ++grep "expired.example[ /].*: RRSIG has expired" ns4/named.run > /dev/null || ret=1 + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` +diff --git a/bin/tests/system/rndc/tests.sh b/bin/tests/system/rndc/tests.sh +index 947987bce8..01dbc811ae 100644 +--- a/bin/tests/system/rndc/tests.sh ++++ b/bin/tests/system/rndc/tests.sh +@@ -305,5 +305,11 @@ done + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + ++echo "I:testing rndc nta time limits" ++ret=0 ++$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 2h nta1.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 ++$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 1d nta2.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 ++$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 1w nta3.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 ++$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 8d nta4.example 2>&1 | grep "NTA lifetime cannot exceed one week" > /dev/null || ret=1 + echo "I:exit status: $status" + exit $status +diff --git a/configure.in b/configure.in +index fb2e53e8e1..ee38901937 100644 +--- a/configure.in ++++ b/configure.in +@@ -3710,6 +3710,7 @@ AC_SUBST(BIND9_ISCCFG_BUILDINCLUDE) + AC_SUBST(BIND9_DNS_BUILDINCLUDE) + AC_SUBST(BIND9_LWRES_BUILDINCLUDE) + AC_SUBST(BIND9_BIND9_BUILDINCLUDE) ++AC_SUBST(BIND9_IRS_BUILDINCLUDE) + if test "X$srcdir" != "X"; then + BIND9_ISC_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/isc/include" + BIND9_ISCCC_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/isccc/include" +@@ -3717,6 +3718,7 @@ if test "X$srcdir" != "X"; then + BIND9_DNS_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/dns/include" + BIND9_LWRES_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/lwres/include" + BIND9_BIND9_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/bind9/include" ++ BIND9_IRS_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/irs/include" + else + BIND9_ISC_BUILDINCLUDE="" + BIND9_ISCCC_BUILDINCLUDE="" +@@ -3724,6 +3726,7 @@ else + BIND9_DNS_BUILDINCLUDE="" + BIND9_LWRES_BUILDINCLUDE="" + BIND9_BIND9_BUILDINCLUDE="" ++ BIND9_IRS_BUILDINCLUDE="" + fi + + AC_SUBST_FILE(BIND9_MAKE_INCLUDES) +diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml +index 7a9d60db02..af194d9cfd 100644 +--- a/doc/arm/Bv9ARM-book.xml ++++ b/doc/arm/Bv9ARM-book.xml +@@ -4902,6 +4902,8 @@ badresp:1,adberr:0,findfail:0,valfail:0] + max-refresh-time number ; + min-retry-time number ; + max-retry-time number ; ++ nta-lifetime duration ; ++ nta-recheck duration ; + port ip_port; + additional-from-auth yes_or_no ; + additional-from-cache yes_or_no ; +@@ -5684,11 +5686,76 @@ options { + named. (A planned third option, + external, will disable all automatic + signing and allow DNSSEC data to be submitted into a zone +- via dyanmic update; this is not yet implemented.) ++ via dynamic update; this is not yet implemented.) + + + + ++ ++ nta-lifetime ++ ++ ++ Species the default lifetime, in seconds, ++ that will be used for negative trust anchors added ++ via rndc nta. ++ ++ ++ A negative trust anchor selectively disables ++ DNSSEC validation for zones that known to be ++ failing because of misconfiguration rather than ++ an attack. When data to be validated is ++ at or below an active NTA (and above any other ++ configured trust anchors), named will ++ abort the DNSSEC validation process and treat the data as ++ insecure rather than bogus. This continues until the ++ NTA's lifetime is elapsed. NTAs persist ++ across named restarts. ++ ++ ++ For convienience, TTL-style time unit suffixes can be ++ used to specify the NTA lifetime in seconds, minutes ++ or hours. defaults to ++ one hour. It cannot exceed one week. ++ ++ ++ ++ ++ ++ nta-recheck ++ ++ ++ Species how often to check whether negative ++ trust anchors added via rndc nta ++ are still necessary. ++ ++ ++ A negative trust anchor is normally used when a ++ domain has stopped validating due to operator error; ++ it temporarily disables DNSSEC validation for that ++ domain. In the interest of ensuring that DNSSEC ++ validation is turned back on as soon as possible, ++ named will periodically send a ++ query to the domain, ignoring negative trust anchors, ++ to find out whether it can now be validated. If so, ++ the negative trust anchor is allowed to expire early. ++ ++ ++ Validity checks can be disabled for an indivdiual ++ NTA by using rndc nta -f, or ++ for all NTA's by setting ++ to zero. ++ ++ ++ For convienience, TTL-style time unit suffixes can be ++ used to specify the NTA recheck interval in seconds, ++ minutes or hours. The default is five minutes. It ++ cannot be longer than ++ (which cannot be longer than a week). ++ ++ ++ ++ ++ + + zone-statistics + +@@ -10179,6 +10246,10 @@ ns.domain.com.rpz-nsdname CNAME . + level are inherited by all views, but keys defined in a view + are only used within that view. + ++ ++ Validation below specified names can be temporarily disabled ++ by using rndc nta. ++ + + + +@@ -17037,6 +17108,7 @@ zone "example.com" { + Manual pages + + ++ + + + +diff --git a/lib/bind9/check.c b/lib/bind9/check.c +index 61574e8144..5131b521f4 100644 +--- a/lib/bind9/check.c ++++ b/lib/bind9/check.c +@@ -689,6 +689,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, + const char *str; + dns_name_t *name; + isc_buffer_t b; ++ isc_uint32_t lifetime = 0; + + static intervaltable intervals[] = { + { "cleaning-interval", 60, 28 * 24 * 60 }, /* 28 days */ +@@ -1038,6 +1039,38 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, + result = ISC_R_FAILURE; + } + ++ ++ obj = NULL; ++ (void)cfg_map_get(options, "nta-lifetime", &obj); ++ if (obj != NULL) { ++ lifetime = cfg_obj_asuint32(obj); ++ if (lifetime > 604800) { /* 7 days */ ++ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, ++ "'nta-lifetime' cannot exceed one week"); ++ result = ISC_R_RANGE; ++ } else if (lifetime == 0) { ++ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, ++ "'nta-lifetime' may not be zero"); ++ result = ISC_R_RANGE; ++ } ++ } ++ ++ obj = NULL; ++ (void)cfg_map_get(options, "nta-recheck", &obj); ++ if (obj != NULL) { ++ isc_uint32_t recheck = cfg_obj_asuint32(obj); ++ if (recheck > 604800) { /* 7 days */ ++ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, ++ "'nta-recheck' cannot exceed one week"); ++ result = ISC_R_RANGE; ++ } ++ ++ if (recheck > lifetime) ++ cfg_obj_log(obj, logctx, ISC_LOG_WARNING, ++ "'nta-recheck' (%d seconds) is " ++ "greater than 'nta-lifetime' " ++ "(%d seconds)", recheck, lifetime); ++ } + return (result); + } + +diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in +index ae316c59e2..4478a6c8e6 100644 +--- a/lib/dns/Makefile.in ++++ b/lib/dns/Makefile.in +@@ -66,8 +66,8 @@ DNSOBJS = acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \ + iptable.@O@ journal.@O@ keydata.@O@ keytable.@O@ \ + lib.@O@ log.@O@ lookup.@O@ \ + master.@O@ masterdump.@O@ message.@O@ \ +- name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ order.@O@ peer.@O@ \ +- portlist.@O@ private.@O@ \ ++ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ ++ order.@O@ peer.@O@ portlist.@O@ private.@O@ \ + rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \ + rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ + request.@O@ resolver.@O@ result.@O@ rootns.@O@ \ +@@ -100,7 +100,8 @@ DNSSRCS = acache.c acl.c adb.c byaddr.c \ + dlz.c dns64.c dnssec.c ds.c dyndb.c forward.c iptable.c \ + journal.c keydata.c keytable.c lib.c log.c lookup.c \ + master.c masterdump.c message.c \ +- name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \ ++ name.c ncache.c nsec.c nsec3.c nta.c \ ++ order.c peer.c portlist.c \ + rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \ + rdataset.c rdatasetiter.c rdataslab.c request.c \ + resolver.c result.c rootns.c rpz.c rriterator.c \ +diff --git a/lib/dns/adb.c b/lib/dns/adb.c +index ac89e66172..c38df5bb87 100644 +--- a/lib/dns/adb.c ++++ b/lib/dns/adb.c +@@ -2465,9 +2465,9 @@ dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr, + + result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl); + if (result != ISC_R_SUCCESS) { +- DP(ISC_LOG_INFO, "adb: task-exclusive mode unavailable, " +- "intializing table sizes to %u\n", +- nbuckets[11]); ++ DP(DEF_LEVEL, "adb: task-exclusive mode unavailable, " ++ "intializing table sizes to %u\n", ++ nbuckets[11]); + adb->nentries = nbuckets[11]; + adb->nnames = nbuckets[11]; + +diff --git a/lib/dns/client.c b/lib/dns/client.c +index d3b371b0e4..9835b37bf0 100644 +--- a/lib/dns/client.c ++++ b/lib/dns/client.c +@@ -21,6 +21,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -129,6 +130,8 @@ typedef struct resctx { + isc_mutex_t lock; + dns_client_t *client; + isc_boolean_t want_dnssec; ++ isc_boolean_t want_validation; ++ isc_boolean_t want_cdflag; + + /* Locked */ + ISC_LINK(struct resctx) link; +@@ -302,12 +305,12 @@ getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr, + } + + static isc_result_t +-dns_client_createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, +- unsigned int options, isc_taskmgr_t *taskmgr, +- unsigned int ntasks, isc_socketmgr_t *socketmgr, +- isc_timermgr_t *timermgr, dns_dispatchmgr_t *dispatchmgr, +- dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, +- dns_view_t **viewp) ++createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, ++ unsigned int options, isc_taskmgr_t *taskmgr, ++ unsigned int ntasks, isc_socketmgr_t *socketmgr, ++ isc_timermgr_t *timermgr, dns_dispatchmgr_t *dispatchmgr, ++ dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, ++ dns_view_t **viewp) + { + isc_result_t result; + dns_view_t *view = NULL; +@@ -324,9 +327,9 @@ dns_client_createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, + return (result); + } + +- result = dns_view_createresolver(view, taskmgr, ntasks, 1, socketmgr, +- timermgr, 0, dispatchmgr, +- dispatchv4, dispatchv6); ++ result = dns_view_createresolver(view, taskmgr, ntasks, 1, ++ socketmgr, timermgr, 0, ++ dispatchmgr, dispatchv4, dispatchv6); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); +@@ -485,10 +488,9 @@ dns_client_createx(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, + } + + /* Create the default view for class IN */ +- result = dns_client_createview(mctx, dns_rdataclass_in, options, +- taskmgr, RESOLVER_NTASKS, socketmgr, timermgr, +- dispatchmgr, dispatchv4, dispatchv6, +- &view); ++ result = createview(mctx, dns_rdataclass_in, options, taskmgr, ++ RESOLVER_NTASKS, socketmgr, timermgr, dispatchmgr, ++ dispatchv4, dispatchv6, &view); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_INIT(client->viewlist); +@@ -653,6 +655,46 @@ dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, + return (result); + } + ++isc_result_t ++dns_client_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, ++ const char *dlvname) ++{ ++ isc_result_t result; ++ isc_buffer_t b; ++ dns_view_t *view = NULL; ++ ++ REQUIRE(DNS_CLIENT_VALID(client)); ++ ++ LOCK(&client->lock); ++ result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, ++ rdclass, &view); ++ UNLOCK(&client->lock); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ ++ if (dlvname == NULL) ++ view->dlv = NULL; ++ else { ++ dns_name_t *newdlv; ++ ++ isc_buffer_constinit(&b, dlvname, strlen(dlvname)); ++ isc_buffer_add(&b, strlen(dlvname)); ++ newdlv = dns_fixedname_name(&view->dlv_fixed); ++ result = dns_name_fromtext(newdlv, &b, dns_rootname, ++ DNS_NAME_DOWNCASE, NULL); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ ++ view->dlv = dns_fixedname_name(&view->dlv_fixed); ++ } ++ ++ cleanup: ++ if (view != NULL) ++ dns_view_detach(&view); ++ ++ return (result); ++} ++ + static isc_result_t + getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { + dns_rdataset_t *rdataset; +@@ -703,6 +745,7 @@ fetch_done(isc_task_t *task, isc_event_t *event) { + static inline isc_result_t + start_fetch(resctx_t *rctx) { + isc_result_t result; ++ int fopts = 0; + + /* + * The caller must be holding the rctx's lock. +@@ -710,10 +753,15 @@ start_fetch(resctx_t *rctx) { + + REQUIRE(rctx->fetch == NULL); + ++ if (!rctx->want_cdflag) ++ fopts |= DNS_FETCHOPT_NOCDFLAG; ++ if (!rctx->want_validation) ++ fopts |= DNS_FETCHOPT_NOVALIDATE; ++ + result = dns_resolver_createfetch(rctx->view->resolver, + dns_fixedname_name(&rctx->name), + rctx->type, +- NULL, NULL, NULL, 0, ++ NULL, NULL, NULL, fopts, + rctx->task, fetch_done, rctx, + rctx->rdataset, + rctx->sigrdataset, +@@ -1256,7 +1304,7 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, + isc_mem_t *mctx; + isc_result_t result; + dns_rdataset_t *rdataset, *sigrdataset; +- isc_boolean_t want_dnssec; ++ isc_boolean_t want_dnssec, want_validation, want_cdflag; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(transp != NULL && *transp == NULL); +@@ -1272,6 +1320,8 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, + rdataset = NULL; + sigrdataset = NULL; + want_dnssec = ISC_TF((options & DNS_CLIENTRESOPT_NODNSSEC) == 0); ++ want_validation = ISC_TF((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0); ++ want_cdflag = ISC_TF((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0); + + /* + * Prepare some intermediate resources +@@ -1327,6 +1377,8 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, + rctx->restarts = 0; + rctx->fetch = NULL; + rctx->want_dnssec = want_dnssec; ++ rctx->want_validation = want_validation; ++ rctx->want_cdflag = want_cdflag; + ISC_LIST_INIT(rctx->namelist); + rctx->event = event; + +diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h +index d21dff788d..289dc18918 100644 +--- a/lib/dns/include/dns/client.h ++++ b/lib/dns/include/dns/client.h +@@ -77,10 +77,14 @@ ISC_LANG_BEGINDECLS + /*% + * Optional flags for dns_client_(start)resolve. + */ +-/*%< Disable DNSSEC validation. */ ++/*%< Do not return DNSSEC data (e.g. RRSIGS) with response. */ + #define DNS_CLIENTRESOPT_NODNSSEC 0x01 + /*%< Allow running external context. */ + #define DNS_CLIENTRESOPT_ALLOWRUN 0x02 ++/*%< Don't validate responses. */ ++#define DNS_CLIENTRESOPT_NOVALIDATE 0x04 ++/*%< Don't set the CD flag on upstream queries. */ ++#define DNS_CLIENTRESOPT_NOCDFLAG 0x08 + + /*% + * Optional flags for dns_client_(start)request. +@@ -248,6 +252,26 @@ dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, + *\li Anything else Failure. + */ + ++isc_result_t ++dns_client_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, ++ const char *dlvname); ++/*%< ++ * Specify a name to use for DNSSEC lookaside validation (e.g., ++ * "dlv.isc.org"). If a trusted key has been added for that name, ++ * then DLV will be used during validation. If 'dlvname' is NULL, ++ * then DLV will no longer be used for this client. ++ * ++ * Requires: ++ * ++ *\li 'client' is a valid client. ++ * ++ * Returns: ++ * ++ *\li #ISC_R_SUCCESS On success. ++ * ++ *\li Anything else Failure. ++ */ ++ + isc_result_t + dns_client_resolve(dns_client_t *client, dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, +diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h +index 3f4adaf6e3..c44fca8a40 100644 +--- a/lib/dns/include/dns/keytable.h ++++ b/lib/dns/include/dns/keytable.h +@@ -386,7 +386,7 @@ dns_keytable_detachkeynode(dns_keytable_t *keytable, + + isc_result_t + dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, +- isc_boolean_t *wantdnssecp); ++ dns_name_t *foundname, isc_boolean_t *wantdnssecp); + /*%< + * Is 'name' at or beneath a trusted key? + * +@@ -396,12 +396,16 @@ dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, + * + *\li 'name' is a valid absolute name. + * +- *\li '*wantsdnssecp' is a valid isc_boolean_t. ++ *\li 'foundanme' is NULL or is a pointer to an initialized dns_name_t + * ++ *\li '*wantsdnssecp' is a valid isc_boolean_t. ++ + * Ensures: + * + *\li On success, *wantsdnssecp will be ISC_TRUE if and only if 'name' +- * is at or beneath a trusted key. ++ * is at or beneath a trusted key. If 'foundname' is not NULL, then ++ * it will be updated to contain the name of the closest enclosing ++ * trust anchor. + * + * Returns: + * +diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h +index a3b7e5a71e..d7185678ff 100644 +--- a/lib/dns/include/dns/log.h ++++ b/lib/dns/include/dns/log.h +@@ -78,6 +78,8 @@ LIBDNS_EXTERNAL_DATA extern isc_logmodule_t dns_modules[]; + #define DNS_LOGMODULE_DNSSEC (&dns_modules[27]) + #define DNS_LOGMODULE_CRYPTO (&dns_modules[28]) + #define DNS_LOGMODULE_DYNDB (&dns_modules[29]) ++#define DNS_LOGMODULE_PACKETS (&dns_modules[30]) ++#define DNS_LOGMODULE_NTA (&dns_modules[31]) + + ISC_LANG_BEGINDECLS + +diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h +index 8631248cc2..5164fd5084 100644 +--- a/lib/dns/include/dns/masterdump.h ++++ b/lib/dns/include/dns/masterdump.h +@@ -100,6 +100,10 @@ typedef struct dns_master_style dns_master_style_t; + /*% Report re-signing time. */ + #define DNS_STYLEFLAG_RESIGN 0x04000000U + ++/*% Comment out data by prepending with ";" */ ++#define DNS_STYLEFLAG_COMMENTDATA 0x10000000U ++ ++ + ISC_LANG_BEGINDECLS + + /*** +@@ -148,6 +152,11 @@ LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_simple; + */ + LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_debug; + ++/*% ++ * Similar to dns_master_style_debug but data is prepended with ";" ++ */ ++LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_comment; ++ + /*** + *** Functions + ***/ +diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h +index d999e75a4d..11f3d41318 100644 +--- a/lib/dns/include/dns/message.h ++++ b/lib/dns/include/dns/message.h +@@ -138,6 +138,7 @@ typedef int dns_messagetextflag_t; + #define DNS_MESSAGETEXTFLAG_NOHEADERS 0x0002 + #define DNS_MESSAGETEXTFLAG_ONESOA 0x0004 + #define DNS_MESSAGETEXTFLAG_OMITSOA 0x0008 ++#define DNS_MESSAGETEXTFLAG_COMMENTDATA 0x0010 + + /* + * Dynamic update names for these sections. +@@ -1358,6 +1359,17 @@ dns_message_gettimeadjust(dns_message_t *msg); + *\li msg be a valid message. + */ + ++void ++dns_message_logpacket(dns_message_t *message, const char *description, ++ isc_logcategory_t *category, isc_logmodule_t *module, ++ int level, isc_mem_t *mctx); ++ ++void ++dns_message_logfmtpacket(dns_message_t *message, const char *description, ++ isc_logcategory_t *category, isc_logmodule_t *module, ++ const dns_master_style_t *style, int level, ++ isc_mem_t *mctx); ++ + isc_result_t + dns_message_buildopt(dns_message_t *msg, dns_rdataset_t **opt, + unsigned int version, isc_uint16_t udpsize, +diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h +new file mode 100644 +index 0000000000..20bb86d5c7 +--- /dev/null ++++ b/lib/dns/include/dns/nta.h +@@ -0,0 +1,220 @@ ++/* ++ * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#ifndef DNS_NTA_H ++#define DNS_NTA_H 1 ++ ++/***** ++ ***** Module Info ++ *****/ ++ ++/*! \file ++ * \brief ++ * The NTA module provides services for storing and retrieving negative ++ * trust anchors, and determine whether a given domain is subject to ++ * DNSSEC validation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++ISC_LANG_BEGINDECLS ++ ++struct dns_ntatable { ++ /* Unlocked. */ ++ unsigned int magic; ++ dns_view_t *view; ++ isc_rwlock_t rwlock; ++ isc_uint32_t recheck; ++ isc_taskmgr_t *taskmgr; ++ isc_timermgr_t *timermgr; ++ isc_task_t *task; ++ /* Locked by rwlock. */ ++ isc_uint32_t references; ++ dns_rbt_t *table; ++}; ++ ++#define NTATABLE_MAGIC ISC_MAGIC('N', 'T', 'A', 't') ++#define VALID_NTATABLE(nt) ISC_MAGIC_VALID(nt, NTATABLE_MAGIC) ++ ++isc_result_t ++dns_ntatable_create(dns_view_t *view, ++ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, ++ dns_ntatable_t **ntatablep); ++/*%< ++ * Create an NTA table in view 'view'. ++ * ++ * Requires: ++ * ++ *\li 'view' is a valid view. ++ * ++ *\li 'tmgr' is a valid timer manager. ++ * ++ *\li ntatablep != NULL && *ntatablep == NULL ++ * ++ * Ensures: ++ * ++ *\li On success, *ntatablep is a valid, empty NTA table. ++ * ++ * Returns: ++ * ++ *\li ISC_R_SUCCESS ++ *\li Any other result indicates failure. ++ */ ++ ++void ++dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp); ++/*%< ++ * Attach *targetp to source. ++ * ++ * Requires: ++ * ++ *\li 'source' is a valid ntatable. ++ * ++ *\li 'targetp' points to a NULL dns_ntatable_t *. ++ * ++ * Ensures: ++ * ++ *\li *targetp is attached to source. ++ */ ++ ++void ++dns_ntatable_detach(dns_ntatable_t **ntatablep); ++/*%< ++ * Detach *ntatablep from its ntatable. ++ * ++ * Requires: ++ * ++ *\li 'ntatablep' points to a valid ntatable. ++ * ++ * Ensures: ++ * ++ *\li *ntatablep is NULL. ++ * ++ *\li If '*ntatablep' is the last reference to the ntatable, ++ * all resources used by the ntatable will be freed ++ */ ++ ++isc_result_t ++dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, ++ isc_boolean_t force, isc_stdtime_t now, ++ isc_uint32_t lifetime); ++/*%< ++ * Add a negative trust anchor to 'ntatable' for name 'name', ++ * which will expire at time 'now' + 'lifetime'. If 'force' is ISC_FALSE, ++ * then the name will be checked periodically to see if it's bogus; ++ * if not, then the NTA will be allowed to expire early. ++ * ++ * Notes: ++ * ++ *\li If an NTA already exists in the table, its expiry time ++ * is updated. ++ * ++ * Requires: ++ * ++ *\li 'ntatable' points to a valid ntatable. ++ * ++ *\li 'name' points to a valid name. ++ * ++ * Returns: ++ * ++ *\li ISC_R_SUCCESS ++ * ++ *\li Any other result indicates failure. ++ */ ++ ++isc_result_t ++dns_ntatable_delete(dns_ntatable_t *ntatable, dns_name_t *keyname); ++/*%< ++ * Delete node(s) from 'ntatable' matching name 'keyname' ++ * ++ * Requires: ++ * ++ *\li 'ntatable' points to a valid ntatable. ++ * ++ *\li 'name' is not NULL ++ * ++ * Returns: ++ * ++ *\li ISC_R_SUCCESS ++ * ++ *\li Any other result indicates failure. ++ */ ++ ++isc_result_t ++dns_ntatable_deletenta(dns_ntatable_t *ntatable, dns_name_t *name); ++/*%< ++ * Delete node from 'ntatable' matching the name 'name' ++ * ++ * Requires: ++ * ++ *\li 'ntatable' points to a valid ntatable. ++ *\li 'name' is a valid name. ++ * ++ * Returns: ++ * ++ *\li ISC_R_SUCCESS ++ * ++ *\li Any other result indicates failure. ++ */ ++ ++isc_boolean_t ++dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, ++ dns_name_t *name, dns_name_t *anchor); ++/*%< ++ * Return ISC_TRUE if 'name' is below a non-expired negative trust ++ * anchor which in turn is at or below 'anchor'. ++ * ++ * If 'ntatable' has not been initialized, return ISC_FALSE. ++ * ++ * Requires: ++ * ++ *\li 'ntatable' is NULL or is a valid ntatable. ++ * ++ *\li 'name' is a valid absolute name. ++ */ ++ ++isc_result_t ++dns_ntatable_totext(dns_ntatable_t *ntatable, isc_buffer_t *buf); ++/*%< ++ * Dump the NTA table to buffer 'buf' ++ */ ++ ++isc_result_t ++dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp); ++/*%< ++ * Dump the NTA table to the file opened as 'fp'. ++ */ ++ ++isc_result_t ++dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp); ++/*%< ++ * Save the NTA table to the file opened as 'fp', for later loading. ++ */ ++ISC_LANG_ENDDECLS ++ ++#endif /* DNS_NTA_H */ +diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h +index e8a712a781..10642f2eab 100644 +--- a/lib/dns/include/dns/resolver.h ++++ b/lib/dns/include/dns/resolver.h +@@ -95,7 +95,10 @@ typedef struct dns_fetchevent { + #define DNS_FETCHOPT_NOVALIDATE 0x20 /*%< Disable validation. */ + #define DNS_FETCHOPT_EDNS512 0x40 /*%< Advertise a 512 byte + UDP buffer. */ +-#define DNS_FETCHOPT_WANTNSID 0x80 /*%< Request NSID */ ++#define DNS_FETCHOPT_WANTNSID 0x080 /*%< Request NSID */ ++#define DNS_FETCHOPT_PREFETCH 0x100 /*%< Request NSID */ ++#define DNS_FETCHOPT_NONTA 0x400 /*%< Ignore NTA table. */ ++#define DNS_FETCHOPT_NOCDFLAG 0x200 /*%< Don't set CD flag. */ + + #define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000 + #define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000 +diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h +index 12aacf9ba7..137bfd8567 100644 +--- a/lib/dns/include/dns/result.h ++++ b/lib/dns/include/dns/result.h +@@ -153,8 +153,9 @@ + #define DNS_R_EXPIRED (ISC_RESULTCLASS_DNS + 107) + #define DNS_R_NOTDYNAMIC (ISC_RESULTCLASS_DNS + 108) + #define DNS_R_BADEUI (ISC_RESULTCLASS_DNS + 109) ++#define DNS_R_NTACOVERED (ISC_RESULTCLASS_DNS + 110) + +-#define DNS_R_NRESULTS 110 /*%< Number of results */ ++#define DNS_R_NRESULTS 111 /*%< Number of results */ + + /* + * DNS wire format rcodes. +diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h +index 5dc03deac1..d397b063b7 100644 +--- a/lib/dns/include/dns/types.h ++++ b/lib/dns/include/dns/types.h +@@ -95,6 +95,8 @@ typedef isc_region_t dns_label_t; + typedef struct dns_lookup dns_lookup_t; + typedef struct dns_name dns_name_t; + typedef ISC_LIST(dns_name_t) dns_namelist_t; ++typedef struct dns_nta dns_nta_t; ++typedef struct dns_ntatable dns_ntatable_t; + typedef isc_uint16_t dns_opcode_t; + typedef unsigned char dns_offsets_t[128]; + typedef struct dns_order dns_order_t; +diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h +index b3cfe9992f..88c7b8288d 100644 +--- a/lib/dns/include/dns/validator.h ++++ b/lib/dns/include/dns/validator.h +@@ -165,13 +165,16 @@ struct dns_validator { + unsigned int depth; + unsigned int authcount; + unsigned int authfail; ++ isc_stdtime_t start; + }; + + /*% + * dns_validator_create() options. + */ +-#define DNS_VALIDATOR_DLV 1U +-#define DNS_VALIDATOR_DEFER 2U ++#define DNS_VALIDATOR_DLV 0x0001U ++#define DNS_VALIDATOR_DEFER 0x0002U ++#define DNS_VALIDATOR_NOCDFLAG 0x0004U ++#define DNS_VALIDATOR_NONTA 0x0008U /*% Ignore NTA table */ + + ISC_LANG_BEGINDECLS + +diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h +index 704e5fe3e9..9d8b3373fd 100644 +--- a/lib/dns/include/dns/view.h ++++ b/lib/dns/include/dns/view.h +@@ -190,6 +190,11 @@ struct dns_view { + dns_zone_t * managed_keys; + dns_zone_t * redirect; + ++ dns_ntatable_t * ntatable_priv; /* internal use only */ ++ isc_uint32_t nta_lifetime; ++ isc_uint32_t nta_recheck; ++ char *nta_file; ++ + #ifdef BIND9 + /* File in which to store configuration for newly added zones */ + char * new_zone_file; +@@ -1023,17 +1028,50 @@ dns_view_iscacheshared(dns_view_t *view); + *\li #ISC_FALSE otherwise. + */ + ++isc_result_t ++dns_view_initntatable(dns_view_t *view, ++ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr); ++/*%< ++ * Initialize the negative trust anchor table for the view. ++ * ++ * Requires: ++ * \li 'view' is valid. ++ * ++ * Returns: ++ *\li ISC_R_SUCCESS ++ *\li Any other result indicates failure ++ */ ++ ++isc_result_t ++dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp); ++/*%< ++ * Get the negative trust anchor table for this view. Returns ++ * ISC_R_NOTFOUND if the table not been initialized for the view. ++ * ++ * '*ntp' is attached on success; the caller is responsible for ++ * detaching it with dns_ntatable_detach(). ++ * ++ * Requires: ++ * \li 'view' is valid. ++ * \li 'nta' is not NULL and '*nta' is NULL. ++ * ++ * Returns: ++ *\li ISC_R_SUCCESS ++ *\li ISC_R_NOTFOUND ++ */ ++ + isc_result_t + dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx); + /*%< +- * Initialize security roots for the view. (Note that secroots is ++ * Initialize security roots for the view, detaching any previously ++ * existing security roots first. (Note that secroots_priv is + * NULL until this function is called, so any function using +- * secroots must check its validity first. One way to do this is +- * use dns_view_getsecroots() and check its return value.) ++ * security roots must check that they have been initialized first. ++ * One way to do this is use dns_view_getsecroots() and check its ++ * return value.) + * + * Requires: + * \li 'view' is valid. +- * \li 'view->secroots' is NULL. + * + * Returns: + *\li ISC_R_SUCCESS +@@ -1060,10 +1098,14 @@ dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp); + + isc_result_t + dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, +- isc_boolean_t *secure_domain); ++ isc_stdtime_t now, isc_boolean_t checknta, ++ isc_boolean_t *secure_domain); + /*%< +- * Is 'name' at or beneath a trusted key? Put answer in +- * '*secure_domain'. ++ * Is 'name' at or beneath a trusted key, and not covered by a valid ++ * negative trust anchor? Put answer in '*secure_domain'. ++ * ++ * If 'checknta' is ISC_FALSE, ignore the NTA table in determining ++ * whether this is a secure domain. + * + * Requires: + * \li 'view' is valid. +@@ -1073,6 +1115,20 @@ dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, + *\li Any other value indicates failure + */ + ++isc_boolean_t ++dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, ++ dns_name_t *name, dns_name_t *anchor); ++/*%< ++ * Is there a current negative trust anchor above 'name' and below 'anchor'? ++ * ++ * Requires: ++ * \li 'view' is valid. ++ * ++ * Returns: ++ *\li ISC_R_TRUE ++ *\li ISC_R_FALSE ++ */ ++ + void + dns_view_untrust(dns_view_t *view, dns_name_t *keyname, + dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx); +@@ -1111,6 +1167,24 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx, + void + dns_view_restorekeyring(dns_view_t *view); + ++isc_result_t ++dns_view_saventa(dns_view_t *view); ++/*%< ++ * Save NTA for names in this view to a file. ++ * ++ * Requires: ++ *\li 'view' to be valid. ++ */ ++ ++isc_result_t ++dns_view_loadnta(dns_view_t *view); ++/*%< ++ * Loads NTA for names in this view from a file. ++ * ++ * Requires: ++ *\li 'view' to be valid. ++ */ ++ + ISC_LANG_ENDDECLS + + #endif /* DNS_VIEW_H */ +diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c +index c49847f326..c2025b0203 100644 +--- a/lib/dns/keytable.c ++++ b/lib/dns/keytable.c +@@ -174,6 +174,7 @@ insert(dns_keytable_t *keytable, isc_boolean_t managed, + for (k = node->data; k != NULL; k = k->next) { + if (k->key == NULL) { + k->key = *keyp; ++ *keyp = NULL; /* transfer ownership */ + break; + } + if (dst_key_compare(k->key, *keyp) == ISC_TRUE) +@@ -182,7 +183,7 @@ insert(dns_keytable_t *keytable, isc_boolean_t managed, + + if (k == NULL) + result = ISC_R_SUCCESS; +- else ++ else if (*keyp != NULL) + dst_key_free(keyp); + } + +@@ -523,10 +524,10 @@ dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep) + + isc_result_t + dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, +- isc_boolean_t *wantdnssecp) ++ dns_name_t *foundname, isc_boolean_t *wantdnssecp) + { + isc_result_t result; +- void *data; ++ dns_rbtnode_t *node = NULL; + + /* + * Is 'name' at or beneath a trusted key? +@@ -538,11 +539,10 @@ dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + +- data = NULL; +- result = dns_rbt_findname(keytable->table, name, 0, NULL, &data); +- ++ result = dns_rbt_findnode(keytable->table, name, foundname, &node, ++ NULL, DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { +- INSIST(data != NULL); ++ INSIST(node->data != NULL); + *wantdnssecp = ISC_TRUE; + result = ISC_R_SUCCESS; + } else if (result == ISC_R_NOTFOUND) { +@@ -575,6 +575,8 @@ dns_keytable_dump(dns_keytable_t *keytable, FILE *fp) + + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + for (knode = node->data; knode != NULL; knode = knode->next) { ++ if (knode->key == NULL) ++ continue; + dst_key_format(knode->key, pbuf, sizeof(pbuf)); + fprintf(fp, "%s ; %s\n", pbuf, + knode->managed ? "managed" : "trusted"); +diff --git a/lib/dns/log.c b/lib/dns/log.c +index ff9ca6573c..c223e3b603 100644 +--- a/lib/dns/log.c ++++ b/lib/dns/log.c +@@ -84,6 +84,8 @@ LIBDNS_EXTERNAL_DATA isc_logmodule_t dns_modules[] = { + { "dns/dnssec", 0 }, + { "dns/crypto", 0 }, + { "dns/dyndb", 0 }, ++ { "dns/packets", 0 }, ++ { "dns/nta", 0 }, + { NULL, 0 } + }; + +diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c +index 2717658e69..80fcd4c12d 100644 +--- a/lib/dns/masterdump.c ++++ b/lib/dns/masterdump.c +@@ -158,6 +158,18 @@ dns_master_style_debug = { + 24, 32, 40, 48, 80, 8, UINT_MAX + }; + ++/*% ++ * Similar, but with each line commented out. ++ */ ++LIBDNS_EXTERNAL_DATA const dns_master_style_t ++dns_master_style_comment = { ++ DNS_STYLEFLAG_REL_OWNER | ++ DNS_STYLEFLAG_MULTILINE | ++ DNS_STYLEFLAG_RRCOMMENT | ++ DNS_STYLEFLAG_COMMENTDATA, ++ 24, 32, 40, 48, 80, 8, UINT_MAX ++}; ++ + + #define N_SPACES 10 + static char spaces[N_SPACES+1] = " "; +@@ -291,6 +303,14 @@ totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) { + r.base[0] = '\n'; + isc_buffer_add(&buf, 1); + ++ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) { ++ isc_buffer_availableregion(&buf, &r); ++ if (r.length < 1) ++ return (DNS_R_TEXTTOOLONG); ++ r.base[0] = ';'; ++ isc_buffer_add(&buf, 1); ++ } ++ + result = indent(&col, ctx->style.rdata_column, + ctx->style.tab_width, &buf); + /* +@@ -427,6 +447,12 @@ rdataset_totext(dns_rdataset_t *rdataset, + while (result == ISC_R_SUCCESS) { + column = 0; + ++ /* ++ * Comment? ++ */ ++ if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) ++ isc_buffer_putstr(target, ";"); ++ + /* + * Owner name. + */ +diff --git a/lib/dns/message.c b/lib/dns/message.c +index 06211751e5..2dba3bc8e7 100644 +--- a/lib/dns/message.c ++++ b/lib/dns/message.c +@@ -3579,6 +3579,54 @@ dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) { + return (ISC_R_SUCCESS); + } + ++void ++dns_message_logpacket(dns_message_t *message, const char *description, ++ isc_logcategory_t *category, isc_logmodule_t *module, ++ int level, isc_mem_t *mctx) ++{ ++ dns_message_logfmtpacket(message, description, category, module, ++ &dns_master_style_debug, level, mctx); ++} ++ ++void ++dns_message_logfmtpacket(dns_message_t *message, const char *description, ++ isc_logcategory_t *category, isc_logmodule_t *module, ++ const dns_master_style_t *style, int level, ++ isc_mem_t *mctx) ++{ ++ isc_buffer_t buffer; ++ char *buf = NULL; ++ int len = 1024; ++ isc_result_t result; ++ ++ if (! isc_log_wouldlog(dns_lctx, level)) ++ return; ++ ++ /* ++ * Note that these are multiline debug messages. We want a newline ++ * to appear in the log after each message. ++ */ ++ ++ do { ++ buf = isc_mem_get(mctx, len); ++ if (buf == NULL) ++ break; ++ isc_buffer_init(&buffer, buf, len); ++ result = dns_message_totext(message, style, 0, &buffer); ++ if (result == ISC_R_NOSPACE) { ++ isc_mem_put(mctx, buf, len); ++ len += 1024; ++ } else if (result == ISC_R_SUCCESS) ++ isc_log_write(dns_lctx, category, module, level, ++ "%s%.*s", description, ++ (int)isc_buffer_usedlength(&buffer), ++ buf); ++ } while (result == ISC_R_NOSPACE); ++ ++ if (buf != NULL) ++ isc_mem_put(mctx, buf, len); ++} ++ + isc_result_t + dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp, + unsigned int version, isc_uint16_t udpsize, +diff --git a/lib/dns/nta.c b/lib/dns/nta.c +new file mode 100644 +index 0000000000..7188c4da30 +--- /dev/null ++++ b/lib/dns/nta.c +@@ -0,0 +1,683 @@ ++/* ++ * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++/*! \file */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct dns_nta { ++ unsigned int magic; ++ isc_refcount_t refcount; ++ dns_ntatable_t *ntatable; ++ isc_boolean_t forced; ++ isc_timer_t *timer; ++ dns_fetch_t *fetch; ++ dns_rdataset_t rdataset; ++ dns_rdataset_t sigrdataset; ++ dns_fixedname_t fn; ++ dns_name_t *name; ++ isc_stdtime_t expiry; ++}; ++ ++#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n') ++#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC) ++ ++/* ++ * Obtain a reference to the nta object. Released by ++ * nta_detach. ++ */ ++static void ++nta_ref(dns_nta_t *nta) { ++ isc_refcount_increment(&nta->refcount, NULL); ++} ++ ++static void ++nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { ++ unsigned int refs; ++ dns_nta_t *nta = *ntap; ++ ++ REQUIRE(VALID_NTA(nta)); ++ ++ *ntap = NULL; ++ isc_refcount_decrement(&nta->refcount, &refs); ++ if (refs == 0) { ++ nta->magic = 0; ++ if (nta->timer != NULL) { ++ (void) isc_timer_reset(nta->timer, ++ isc_timertype_inactive, ++ NULL, NULL, ISC_TRUE); ++ isc_timer_detach(&nta->timer); ++ } ++ isc_refcount_destroy(&nta->refcount); ++ if (dns_rdataset_isassociated(&nta->rdataset)) ++ dns_rdataset_disassociate(&nta->rdataset); ++ if (dns_rdataset_isassociated(&nta->sigrdataset)) ++ dns_rdataset_disassociate(&nta->sigrdataset); ++ if (nta->fetch != NULL) { ++ dns_resolver_cancelfetch(nta->fetch); ++ dns_resolver_destroyfetch(&nta->fetch); ++ } ++ isc_mem_put(mctx, nta, sizeof(dns_nta_t)); ++ } ++} ++ ++static void ++free_nta(void *data, void *arg) { ++ dns_nta_t *nta = (dns_nta_t *) data; ++ isc_mem_t *mctx = (isc_mem_t *) arg; ++ ++ nta_detach(mctx, &nta); ++} ++ ++isc_result_t ++dns_ntatable_create(dns_view_t *view, ++ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, ++ dns_ntatable_t **ntatablep) ++{ ++ dns_ntatable_t *ntatable; ++ isc_result_t result; ++ ++ REQUIRE(ntatablep != NULL && *ntatablep == NULL); ++ ++ ntatable = isc_mem_get(view->mctx, sizeof(*ntatable)); ++ if (ntatable == NULL) ++ return (ISC_R_NOMEMORY); ++ ++ ntatable->task = NULL; ++ result = isc_task_create(taskmgr, 0, &ntatable->task); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup_ntatable; ++ isc_task_setname(ntatable->task, "ntatable", ntatable); ++ ++ ntatable->table = NULL; ++ result = dns_rbt_create(view->mctx, free_nta, view->mctx, ++ &ntatable->table); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup_task; ++ ++ result = isc_rwlock_init(&ntatable->rwlock, 0, 0); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup_rbt; ++ ++ ntatable->timermgr = timermgr; ++ ntatable->taskmgr = taskmgr; ++ ++ ntatable->view = view; ++ ntatable->references = 1; ++ ++ ntatable->magic = NTATABLE_MAGIC; ++ *ntatablep = ntatable; ++ ++ return (ISC_R_SUCCESS); ++ ++ cleanup_rbt: ++ dns_rbt_destroy(&ntatable->table); ++ ++ cleanup_task: ++ isc_task_detach(&ntatable->task); ++ ++ cleanup_ntatable: ++ isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); ++ ++ return (result); ++} ++ ++void ++dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) { ++ REQUIRE(VALID_NTATABLE(source)); ++ REQUIRE(targetp != NULL && *targetp == NULL); ++ ++ RWLOCK(&source->rwlock, isc_rwlocktype_write); ++ ++ INSIST(source->references > 0); ++ source->references++; ++ INSIST(source->references != 0); ++ ++ RWUNLOCK(&source->rwlock, isc_rwlocktype_write); ++ ++ *targetp = source; ++} ++ ++void ++dns_ntatable_detach(dns_ntatable_t **ntatablep) { ++ isc_boolean_t destroy = ISC_FALSE; ++ dns_ntatable_t *ntatable; ++ ++ REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep)); ++ ++ ntatable = *ntatablep; ++ *ntatablep = NULL; ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ INSIST(ntatable->references > 0); ++ ntatable->references--; ++ if (ntatable->references == 0) ++ destroy = ISC_TRUE; ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ ++ if (destroy) { ++ dns_rbt_destroy(&ntatable->table); ++ isc_rwlock_destroy(&ntatable->rwlock); ++ if (ntatable->task != NULL) ++ isc_task_detach(&ntatable->task); ++ ntatable->timermgr = NULL; ++ ntatable->taskmgr = NULL; ++ ntatable->magic = 0; ++ isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); ++ } ++} ++ ++static void ++fetch_done(isc_task_t *task, isc_event_t *event) { ++ dns_fetchevent_t *devent = (dns_fetchevent_t *)event; ++ dns_nta_t *nta = devent->ev_arg; ++ isc_result_t eresult = devent->result; ++ dns_ntatable_t *ntatable = nta->ntatable; ++ dns_view_t *view = ntatable->view; ++ isc_stdtime_t now = 0; ++ ++ UNUSED(task); ++ ++ if (dns_rdataset_isassociated(&nta->rdataset)) ++ dns_rdataset_disassociate(&nta->rdataset); ++ if (dns_rdataset_isassociated(&nta->sigrdataset)) ++ dns_rdataset_disassociate(&nta->sigrdataset); ++ if (nta->fetch == devent->fetch) ++ nta->fetch = NULL; ++ dns_resolver_destroyfetch(&devent->fetch); ++ ++ if (devent->node != NULL) ++ dns_db_detachnode(devent->db, &devent->node); ++ if (devent->db != NULL) ++ dns_db_detach(&devent->db); ++ ++ isc_event_free(&event); ++ ++ switch (eresult) { ++ case ISC_R_SUCCESS: ++ case DNS_R_NCACHENXDOMAIN: ++ case DNS_R_NXDOMAIN: ++ case DNS_R_NCACHENXRRSET: ++ case DNS_R_NXRRSET: ++ isc_stdtime_get(&now); ++ nta->expiry = now; ++ break; ++ default: ++ break; ++ } ++ ++ /* ++ * If we're expiring before the next recheck, we might ++ * as well stop the timer now. ++ */ ++ if (nta->timer != NULL && nta->expiry - now < ntatable->recheck) ++ (void) isc_timer_reset(nta->timer, isc_timertype_inactive, ++ NULL, NULL, ISC_TRUE); ++ nta_detach(view->mctx, &nta); ++} ++ ++static void ++checkbogus(isc_task_t *task, isc_event_t *event) { ++ dns_nta_t *nta = event->ev_arg; ++ dns_ntatable_t *ntatable = nta->ntatable; ++ dns_view_t *view = ntatable->view; ++ isc_result_t result; ++ ++ if (nta->fetch != NULL) { ++ dns_resolver_cancelfetch(nta->fetch); ++ nta->fetch = NULL; ++ } ++ if (dns_rdataset_isassociated(&nta->rdataset)) ++ dns_rdataset_disassociate(&nta->rdataset); ++ if (dns_rdataset_isassociated(&nta->sigrdataset)) ++ dns_rdataset_disassociate(&nta->sigrdataset); ++ ++ isc_event_free(&event); ++ ++ nta_ref(nta); ++ result = dns_resolver_createfetch(view->resolver, nta->name, ++ dns_rdatatype_nsec, ++ NULL, NULL, NULL, ++ DNS_FETCHOPT_NONTA, ++ task, fetch_done, nta, ++ &nta->rdataset, ++ &nta->sigrdataset, ++ &nta->fetch); ++ if (result != ISC_R_SUCCESS) ++ nta_detach(view->mctx, &nta); ++} ++ ++static isc_result_t ++settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, isc_uint32_t lifetime) { ++ isc_result_t result; ++ isc_interval_t interval; ++ dns_view_t *view; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ REQUIRE(VALID_NTA(nta)); ++ ++ if (ntatable->timermgr == NULL) ++ return (ISC_R_SUCCESS); ++ ++ view = ntatable->view; ++ if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) ++ return (ISC_R_SUCCESS); ++ ++ isc_interval_set(&interval, view->nta_recheck, 0); ++ result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker, ++ NULL, &interval, ntatable->task, ++ checkbogus, nta, &nta->timer); ++ return (result); ++} ++ ++static isc_result_t ++nta_create(dns_ntatable_t *ntatable, dns_name_t *name, dns_nta_t **target) { ++ isc_result_t result; ++ dns_nta_t *nta = NULL; ++ dns_view_t *view; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ REQUIRE(target != NULL && *target == NULL); ++ ++ view = ntatable->view; ++ ++ nta = isc_mem_get(view->mctx, sizeof(dns_nta_t)); ++ if (nta == NULL) ++ return (ISC_R_NOMEMORY); ++ ++ nta->ntatable = ntatable; ++ nta->expiry = 0; ++ nta->timer = NULL; ++ nta->fetch = NULL; ++ dns_rdataset_init(&nta->rdataset); ++ dns_rdataset_init(&nta->sigrdataset); ++ ++ result = isc_refcount_init(&nta->refcount, 1); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_put(view->mctx, nta, sizeof(dns_nta_t)); ++ return (result); ++ } ++ ++ dns_fixedname_init(&nta->fn); ++ nta->name = dns_fixedname_name(&nta->fn); ++ dns_name_copy(name, nta->name, NULL); ++ ++ nta->magic = NTA_MAGIC; ++ ++ *target = nta; ++ return (ISC_R_SUCCESS); ++} ++ ++isc_result_t ++dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, ++ isc_boolean_t force, isc_stdtime_t now, ++ isc_uint32_t lifetime) ++{ ++ isc_result_t result; ++ dns_nta_t *nta = NULL; ++ dns_rbtnode_t *node; ++ dns_view_t *view; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ ++ view = ntatable->view; ++ ++ result = nta_create(ntatable, name, &nta); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ ++ nta->expiry = now + lifetime; ++ nta->forced = force; ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ ++ node = NULL; ++ result = dns_rbt_addnode(ntatable->table, name, &node); ++ if (result == ISC_R_SUCCESS) { ++ if (!force) ++ (void)settimer(ntatable, nta, lifetime); ++ node->data = nta; ++ nta = NULL; ++ } else if (result == ISC_R_EXISTS) { ++ dns_nta_t *n = node->data; ++ if (n == NULL) { ++ if (!force) ++ (void)settimer(ntatable, nta, lifetime); ++ node->data = nta; ++ nta = NULL; ++ } else { ++ n->expiry = nta->expiry; ++ nta_detach(view->mctx, &nta); ++ } ++ result = ISC_R_SUCCESS; ++ } ++ ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ ++ if (nta != NULL) ++ nta_detach(view->mctx, &nta); ++ ++ return (result); ++} ++ ++/* ++ * Caller must hold a write lock on rwlock. ++ */ ++static isc_result_t ++delete(dns_ntatable_t *ntatable, dns_name_t *name) { ++ isc_result_t result; ++ dns_rbtnode_t *node = NULL; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ REQUIRE(name != NULL); ++ ++ result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL, ++ DNS_RBTFIND_NOOPTIONS, NULL, NULL); ++ if (result == ISC_R_SUCCESS) { ++ if (node->data != NULL) ++ result = dns_rbt_deletenode(ntatable->table, ++ node, ISC_FALSE); ++ else ++ result = ISC_R_NOTFOUND; ++ } else if (result == DNS_R_PARTIALMATCH) ++ result = ISC_R_NOTFOUND; ++ ++ return (result); ++} ++ ++isc_result_t ++dns_ntatable_delete(dns_ntatable_t *ntatable, dns_name_t *name) { ++ isc_result_t result; ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ result = delete(ntatable, name); ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); ++ ++ return (result); ++} ++ ++isc_boolean_t ++dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, ++ dns_name_t *name, dns_name_t *anchor) ++{ ++ isc_result_t result; ++ dns_fixedname_t fn; ++ dns_rbtnode_t *node; ++ dns_name_t *foundname; ++ dns_nta_t *nta = NULL; ++ isc_boolean_t answer = ISC_FALSE; ++ isc_rwlocktype_t locktype = isc_rwlocktype_read; ++ ++ REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable)); ++ REQUIRE(dns_name_isabsolute(name)); ++ ++ if (ntatable == NULL) ++ return (ISC_FALSE); ++ ++ dns_fixedname_init(&fn); ++ foundname = dns_fixedname_name(&fn); ++ ++ relock: ++ RWLOCK(&ntatable->rwlock, locktype); ++ again: ++ node = NULL; ++ result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL, ++ DNS_RBTFIND_NOOPTIONS, NULL, NULL); ++ if (result == DNS_R_PARTIALMATCH) { ++ if (dns_name_issubdomain(foundname, anchor)) ++ result = ISC_R_SUCCESS; ++ } ++ if (result == ISC_R_SUCCESS) { ++ nta = (dns_nta_t *) node->data; ++ answer = ISC_TF(nta->expiry >= now); ++ } ++ ++ /* Deal with expired NTA */ ++ if (result == ISC_R_SUCCESS && !answer) { ++ char nb[DNS_NAME_FORMATSIZE]; ++ ++ if (locktype == isc_rwlocktype_read) { ++ RWUNLOCK(&ntatable->rwlock, locktype); ++ locktype = isc_rwlocktype_write; ++ goto relock; ++ } ++ ++ dns_name_format(foundname, nb, sizeof(nb)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, ++ DNS_LOGMODULE_NTA, ISC_LOG_INFO, ++ "deleting expired NTA at %s", nb); ++ ++ if (nta->timer != NULL) { ++ (void) isc_timer_reset(nta->timer, ++ isc_timertype_inactive, ++ NULL, NULL, ISC_TRUE); ++ isc_timer_detach(&nta->timer); ++ } ++ ++ result = delete(ntatable, foundname); ++ if (result != ISC_R_SUCCESS) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, ++ DNS_LOGMODULE_NTA, ISC_LOG_INFO, ++ "deleting NTA failed: %s", ++ isc_result_totext(result)); ++ } ++ goto again; ++ } ++ RWUNLOCK(&ntatable->rwlock, locktype); ++ ++ return (answer); ++} ++ ++isc_result_t ++dns_ntatable_totext(dns_ntatable_t *ntatable, isc_buffer_t *buf) { ++ isc_result_t result; ++ dns_rbtnode_t *node; ++ dns_rbtnodechain_t chain; ++ isc_boolean_t first = ISC_TRUE; ++ isc_stdtime_t now; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ ++ isc_stdtime_get(&now); ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ dns_rbtnodechain_init(&chain, ntatable->view->mctx); ++ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) ++ goto cleanup; ++ for (;;) { ++ dns_rbtnodechain_current(&chain, NULL, NULL, &node); ++ if (node->data != NULL) { ++ dns_nta_t *n = (dns_nta_t *) node->data; ++ char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; ++ char obuf[DNS_NAME_FORMATSIZE + 200]; ++ dns_fixedname_t fn; ++ dns_name_t *name; ++ isc_time_t t; ++ ++ dns_fixedname_init(&fn); ++ name = dns_fixedname_name(&fn); ++ dns_rbt_fullnamefromnode(node, name); ++ dns_name_format(name, nbuf, sizeof(nbuf)); ++ isc_time_set(&t, n->expiry, 0); ++ isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); ++ ++ snprintf(obuf, sizeof(obuf), "%s%s: %s %s", ++ first ? "" : "\n", nbuf, ++ n->expiry < now ? "expired" : "expiry", ++ tbuf); ++ first = ISC_FALSE; ++ if (1+strlen(obuf) >= isc_buffer_availablelength(buf)) { ++ result = ISC_R_NOSPACE; ++ goto cleanup; ++ } else ++ isc_buffer_putstr(buf, obuf); ++ } ++ result = dns_rbtnodechain_next(&chain, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { ++ if (result == ISC_R_NOMORE) ++ result = ISC_R_SUCCESS; ++ break; ++ } ++ } ++ ++ cleanup: ++ if (isc_buffer_availablelength(buf) != 0) ++ isc_buffer_putuint8(buf, 0); ++ ++ dns_rbtnodechain_invalidate(&chain); ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ return (result); ++} ++ ++isc_result_t ++dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { ++ isc_result_t result; ++ dns_rbtnode_t *node; ++ dns_rbtnodechain_t chain; ++ isc_stdtime_t now; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ ++ isc_stdtime_get(&now); ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ dns_rbtnodechain_init(&chain, ntatable->view->mctx); ++ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) ++ goto cleanup; ++ for (;;) { ++ dns_rbtnodechain_current(&chain, NULL, NULL, &node); ++ if (node->data != NULL) { ++ dns_nta_t *n = (dns_nta_t *) node->data; ++ char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; ++ dns_fixedname_t fn; ++ dns_name_t *name; ++ isc_time_t t; ++ ++ dns_fixedname_init(&fn); ++ name = dns_fixedname_name(&fn); ++ dns_rbt_fullnamefromnode(node, name); ++ dns_name_format(name, nbuf, sizeof(nbuf)); ++ isc_time_set(&t, n->expiry, 0); ++ isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); ++ fprintf(fp, "%s: %s %s\n", nbuf, ++ n->expiry < now ? "expired" : "expiry", ++ tbuf); ++ } ++ result = dns_rbtnodechain_next(&chain, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { ++ if (result == ISC_R_NOMORE) ++ result = ISC_R_SUCCESS; ++ break; ++ } ++ } ++ ++ cleanup: ++ dns_rbtnodechain_invalidate(&chain); ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ return (result); ++} ++ ++isc_result_t ++dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) { ++ isc_result_t result; ++ dns_rbtnode_t *node; ++ dns_rbtnodechain_t chain; ++ isc_stdtime_t now; ++ isc_boolean_t written = ISC_FALSE; ++ ++ REQUIRE(VALID_NTATABLE(ntatable)); ++ ++ isc_stdtime_get(&now); ++ ++ RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ dns_rbtnodechain_init(&chain, ntatable->view->mctx); ++ result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) ++ goto cleanup; ++ ++ for (;;) { ++ dns_rbtnodechain_current(&chain, NULL, NULL, &node); ++ if (node->data != NULL) { ++ dns_nta_t *n = (dns_nta_t *) node->data; ++ if (now <= n->expiry) { ++ isc_buffer_t b; ++ char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80]; ++ dns_fixedname_t fn; ++ dns_name_t *name; ++ ++ dns_fixedname_init(&fn); ++ name = dns_fixedname_name(&fn); ++ dns_rbt_fullnamefromnode(node, name); ++ ++ isc_buffer_init(&b, nbuf, sizeof(nbuf) - 1); ++ result = dns_name_totext(name, ISC_FALSE, &b); ++ if (result != ISC_R_SUCCESS) ++ goto skip; ++ ++ /* Zero terminate. */ ++ isc_buffer_putuint8(&b, 0); ++ ++ isc_buffer_init(&b, tbuf, sizeof(tbuf)); ++ dns_time32_totext(n->expiry, &b); ++ ++ fprintf(fp, "%s %s %s\n", nbuf, ++ n->forced ? "forced" : "regular", ++ tbuf); ++ written = ISC_TRUE; ++ } ++ } ++ skip: ++ result = dns_rbtnodechain_next(&chain, NULL, NULL); ++ if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { ++ if (result == ISC_R_NOMORE) ++ result = ISC_R_SUCCESS; ++ break; ++ } ++ } ++ ++ cleanup: ++ dns_rbtnodechain_invalidate(&chain); ++ RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); ++ ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ else ++ return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND); ++} +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 619646f1d5..79aea7b399 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -1679,6 +1679,30 @@ add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { + ISC_LIST_INITANDAPPEND(fctx->edns512, sa, link); + } + ++static isc_result_t ++issecuredomain(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, ++ isc_stdtime_t now, isc_boolean_t checknta, ++ isc_boolean_t *issecure) ++{ ++ dns_name_t suffix; ++ unsigned int labels; ++ ++ /* ++ * For DS variants we need to check fom the parent domain, ++ * since there may be a negative trust anchor for the name, ++ * while the enclosing domain where the DS record lives is ++ * under a secure entry point. ++ */ ++ labels = dns_name_countlabels(name); ++ if (dns_rdatatype_atparent(type) && labels > 1) { ++ dns_name_init(&suffix, NULL); ++ dns_name_getlabelsequence(name, 1, labels - 1, &suffix); ++ name = &suffix; ++ } ++ ++ return (dns_view_issecuredomain(view, name, now, checknta, issecure)); ++} ++ + static isc_result_t + resquery_send(resquery_t *query) { + fetchctx_t *fctx; +@@ -1768,14 +1792,23 @@ resquery_send(resquery_t *query) { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD; + + /* +- * Set CD if the client says don't validate or the question is +- * under a secure entry point. ++ * Set CD if the client says not to validate, or if the ++ * question is under a secure entry point and this is a ++ * recursive/forward query -- unless the client said not to. + */ +- if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) { ++ if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) ++ /* Do nothing */ ++ ; ++ else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) + fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; +- } else if (res->view->enablevalidation) { +- result = dns_view_issecuredomain(res->view, &fctx->name, +- &secure_domain); ++ else if (res->view->enablevalidation && ++ ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) ++ { ++ isc_boolean_t checknta = ++ ISC_TF((query->options & DNS_FETCHOPT_NONTA) == 0); ++ result = issecuredomain(res->view, &fctx->name, ++ fctx->type, query->start.seconds, ++ checknta, &secure_domain); + if (result != ISC_R_SUCCESS) + secure_domain = ISC_FALSE; + if (res->view->dlv != NULL) +@@ -2402,8 +2435,8 @@ add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_result_t reason, + isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, +- "error (%s%s%s) resolving '%s/%s/%s': %s", +- dns_result_totext(reason), spc, code, ++ "%s%s%s resolving '%s/%s/%s': %s", ++ code, spc, dns_result_totext(reason), + namebuf, typebuf, classbuf, addrbuf); + } + +@@ -4612,6 +4645,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, + isc_task_t *task; + isc_boolean_t fail; + unsigned int valoptions = 0; ++ isc_boolean_t checknta = ISC_TRUE; + + /* + * The appropriate bucket lock must be held. +@@ -4628,18 +4662,26 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, + /* + * Is DNSSEC validation required for this name? + */ ++ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { ++ valoptions |= DNS_VALIDATOR_NONTA; ++ checknta = ISC_FALSE; ++ } ++ + if (res->view->enablevalidation) { +- result = dns_view_issecuredomain(res->view, name, +- &secure_domain); ++ result = issecuredomain(res->view, name, fctx->type, ++ now, checknta, &secure_domain); + if (result != ISC_R_SUCCESS) + return (result); + + if (!secure_domain && res->view->dlv != NULL) { +- valoptions = DNS_VALIDATOR_DLV; ++ valoptions |= DNS_VALIDATOR_DLV; + secure_domain = ISC_TRUE; + } + } + ++ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) ++ valoptions |= DNS_VALIDATOR_NOCDFLAG; ++ + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) + need_validation = ISC_FALSE; + else +@@ -5110,6 +5152,7 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + dns_fetchevent_t *event; + isc_uint32_t ttl; + unsigned int valoptions = 0; ++ isc_boolean_t checknta = ISC_TRUE; + + FCTXTRACE("ncache_message"); + +@@ -5132,18 +5175,26 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + /* + * Is DNSSEC validation required for this name? + */ ++ if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { ++ valoptions |= DNS_VALIDATOR_NONTA; ++ checknta = ISC_FALSE; ++ } ++ + if (fctx->res->view->enablevalidation) { +- result = dns_view_issecuredomain(res->view, name, +- &secure_domain); ++ result = issecuredomain(res->view, name, fctx->type, ++ now, checknta, &secure_domain); + if (result != ISC_R_SUCCESS) + return (result); + + if (!secure_domain && res->view->dlv != NULL) { +- valoptions = DNS_VALIDATOR_DLV; ++ valoptions |= DNS_VALIDATOR_DLV; + secure_domain = ISC_TRUE; + } + } + ++ if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) ++ valoptions |= DNS_VALIDATOR_NOCDFLAG; ++ + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) + need_validation = ISC_FALSE; + else +@@ -6545,7 +6596,8 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) { + FCTXTRACE("continuing to look for parent's NS records"); + result = dns_resolver_createfetch(fctx->res, &fctx->nsname, + dns_rdatatype_ns, domain, +- nsrdataset, NULL, 0, task, ++ nsrdataset, NULL, ++ fctx->options, task, + resume_dslookup, fctx, + &fctx->nsrrset, NULL, + &fctx->nsfetch); +@@ -6654,43 +6706,6 @@ log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, + return; + } + +-static void +-log_packet(dns_message_t *message, int level, isc_mem_t *mctx) { +- isc_buffer_t buffer; +- char *buf = NULL; +- int len = 1024; +- isc_result_t result; +- +- if (! isc_log_wouldlog(dns_lctx, level)) +- return; +- +- /* +- * Note that these are multiline debug messages. We want a newline +- * to appear in the log after each message. +- */ +- +- do { +- buf = isc_mem_get(mctx, len); +- if (buf == NULL) +- break; +- isc_buffer_init(&buffer, buf, len); +- result = dns_message_totext(message, &dns_master_style_debug, +- 0, &buffer); +- if (result == ISC_R_NOSPACE) { +- isc_mem_put(mctx, buf, len); +- len += 1024; +- } else if (result == ISC_R_SUCCESS) +- isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, +- DNS_LOGMODULE_RESOLVER, level, +- "received packet:\n%.*s", +- (int)isc_buffer_usedlength(&buffer), +- buf); +- } while (result == ISC_R_NOSPACE); +- +- if (buf != NULL) +- isc_mem_put(mctx, buf, len); +-} +- + static isc_boolean_t + iscname(fetchctx_t *fctx) { + isc_result_t result; +@@ -6746,7 +6761,7 @@ process_opt(resquery_t *query, dns_rdataset_t *opt) { + case DNS_OPT_NSID: + if (query->options & DNS_FETCHOPT_WANTNSID) + log_nsid(&optbuf, optlen, query, +- ISC_LOG_INFO, ++ ISC_LOG_DEBUG(3), + query->fctx->res->mctx); + isc_buffer_forward(&optbuf, optlen); + break; +@@ -6948,14 +6963,18 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + /* + * Log the incoming packet. + */ +- log_packet(message, ISC_LOG_DEBUG(10), fctx->res->mctx); ++ dns_message_logfmtpacket(message, "received packet:\n", ++ DNS_LOGCATEGORY_RESOLVER, ++ DNS_LOGMODULE_PACKETS, ++ &dns_master_style_comment, ++ ISC_LOG_DEBUG(10), ++ fctx->res->mctx); + + if (message->rdclass != fctx->res->rdclass) { + resend = ISC_TRUE; + FCTXTRACE("bad class"); + goto done; + } +- + /* + * Process receive opt record. + */ +@@ -7480,7 +7499,8 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + + result = dns_resolver_createfetch(fctx->res, &fctx->nsname, + dns_rdatatype_ns, +- NULL, NULL, NULL, 0, task, ++ NULL, NULL, NULL, ++ fctx->options, task, + resume_dslookup, fctx, + &fctx->nsrrset, NULL, + &fctx->nsfetch); +@@ -7786,6 +7806,7 @@ dns_resolver_create(dns_view_t *view, + result = isc_task_create(taskmgr, 0, &task); + if (result != ISC_R_SUCCESS) + goto cleanup_primelock; ++ isc_task_setname(task, "resolver_task", NULL); + + result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, + task, spillattimer_countdown, res, +@@ -8126,7 +8147,7 @@ log_fetch(dns_name_t *name, dns_rdatatype_t type) { + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, +- "createfetch: %s %s", namebuf, typebuf); ++ "fetch: %s/%s", namebuf, typebuf); + } + + isc_result_t +diff --git a/lib/dns/result.c b/lib/dns/result.c +index 39879532d4..4834cb227c 100644 +--- a/lib/dns/result.c ++++ b/lib/dns/result.c +@@ -162,7 +162,8 @@ static const char *text[DNS_R_NRESULTS] = { + "broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */ + "expired", /*%< 107 DNS_R_EXPIRED */ + "not dynamic", /*%< 108 DNS_R_NOTDYNAMIC */ +- "bad EUI" /*%< 109 DNS_R_BADEUI */ ++ "bad EUI", /*%< 109 DNS_R_BADEUI */ ++ "covered by negative trust anchor", /*%< 110 DNS_R_NTACOVERED */ + }; + + static const char *rcode_text[DNS_R_NRCODERESULTS] = { +diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in +index 023e60cead..4bd189f5c7 100644 +--- a/lib/dns/tests/Makefile.in ++++ b/lib/dns/tests/Makefile.in +@@ -41,14 +41,15 @@ OBJS = dnstest.@O@ + SRCS = dnstest.c gost_test.c master_test.c dbiterator_test.c time_test.c \ + private_test.c tsig_test.c update_test.c zonemgr_test.c zt_test.c \ + dbdiff_test.c geoip_test.c dispatch_test.c nsec3_test.c \ +- rdataset_test.c rdata_test.c ++ rdataset_test.c rdata_test.c keytable_test.c + + SUBDIRS = + TARGETS = gost_test@EXEEXT@ master_test@EXEEXT@ dbiterator_test@EXEEXT@ time_test@EXEEXT@ \ + private_test@EXEEXT@ tsig_test@EXEEXT@ update_test@EXEEXT@ zonemgr_test@EXEEXT@ \ + zt_test@EXEEXT@ dbversion_test@EXEEXT@ dbdiff_test@EXEEXT@ geoip_test@EXEEXT@ \ + dispatch_test@EXEEXT@ nsec3_test@EXEEXT@ \ +- rdataset_test@EXEEXT@ rdata_test@EXEEXT@ ++ rdataset_test@EXEEXT@ rdata_test@EXEEXT@ \ ++ keytable_test@EXEEXT@ \ + + @BIND9_MAKE_RULES@ + +@@ -65,6 +66,11 @@ master_test@EXEEXT@: master_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + master_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + ++keytable_test@EXEEXT@: keytable_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ++ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ ++ keytable_test.@O@ dnstest.@O@ ${DNSLIBS} \ ++ ${ISCLIBS} ${LIBS} ++ + time_test@EXEEXT@: time_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + time_test.@O@ dnstest.@O@ ${DNSLIBS} \ +diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c +index f06f526eb3..4f96a9c6b7 100644 +--- a/lib/dns/tests/dnstest.c ++++ b/lib/dns/tests/dnstest.c +@@ -43,8 +43,6 @@ + #include + #include + +-#include +- + #include "dnstest.h" + + isc_mem_t *mctx = NULL; +@@ -187,6 +185,26 @@ dns_test_end() { + isc_mem_destroy(&mctx); + } + ++/* ++ * Create a view. ++ */ ++isc_result_t ++dns_test_makeview(const char *name, dns_view_t **viewp) ++{ ++ isc_result_t result; ++ dns_view_t *view = NULL; ++ ++ CHECK(dns_view_create(mctx, dns_rdataclass_in, name, &view)); ++ *viewp = view; ++ ++ return (ISC_R_SUCCESS); ++ ++ cleanup: ++ if (view != NULL) ++ dns_view_detach(&view); ++ return (result); ++} ++ + /* + * Create a zone with origin 'name', return a pointer to the zone object in + * 'zonep'. If 'view' is set, add the zone to that view; otherwise, create +diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h +index 863ffb6aa5..cd430477b3 100644 +--- a/lib/dns/tests/dnstest.h ++++ b/lib/dns/tests/dnstest.h +@@ -57,6 +57,9 @@ dns_test_begin(FILE *logfile, isc_boolean_t create_managers); + void + dns_test_end(void); + ++isc_result_t ++dns_test_makeview(const char *name, dns_view_t **viewp); ++ + isc_result_t + dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + isc_boolean_t keepview); +diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c +new file mode 100644 +index 0000000000..c41e4081df +--- /dev/null ++++ b/lib/dns/tests/keytable_test.c +@@ -0,0 +1,611 @@ ++/* ++ * Copyright (c) 2014 Infoblox Inc. All Rights Reserved. ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++/*! \file */ ++ ++#include ++ ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "dnstest.h" ++ ++dns_keytable_t *keytable = NULL; ++dns_ntatable_t *ntatable = NULL; ++ ++static const char *keystr1 = "BQEAAAABok+vaUC9neRv8yeT/FEGgN7svR8s7VBUVSBd8NsAiV8AlaAg O5FHar3JQd95i/puZos6Vi6at9/JBbN8qVmO2AuiXxVqfxMKxIcy+LEB 0Vw4NaSJ3N3uaVREso6aTSs98H/25MjcwLOr7SFfXA7bGhZatLtYY/xu kp6Km5hMfkE="; ++static const dns_keytag_t keytag1 = 30591; ++ ++static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/inaJpYv7vH XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/ItBfh/hL2lm Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi79532Qeklxh x8pXWdeAaRU="; ++ ++static dns_view_t *view = NULL; ++ ++/* ++ * Test utilities. In general, these assume input parameters are valid ++ * (checking with ATF_REQUIRE_EQ, thus aborting if not) and unlikely run time ++ * errors (such as memory allocation failure) won't happen. This helps keep ++ * the test code concise. ++ */ ++ ++/* ++ * Utility to convert C-string to dns_name_t. Return a pointer to ++ * static data, and so is not thread safe. ++ */ ++static dns_name_t * ++str2name(const char *namestr) { ++ static dns_fixedname_t fname; ++ static dns_name_t *name; ++ static isc_buffer_t namebuf; ++ void *deconst_namestr; ++ ++ dns_fixedname_init(&fname); ++ name = dns_fixedname_name(&fname); ++ DE_CONST(namestr, deconst_namestr); /* OK, since we don't modify it */ ++ isc_buffer_init(&namebuf, deconst_namestr, strlen(deconst_namestr)); ++ isc_buffer_add(&namebuf, strlen(namestr)); ++ ATF_REQUIRE_EQ(dns_name_fromtext(name, &namebuf, dns_rootname, 0, ++ NULL), ISC_R_SUCCESS); ++ ++ return (name); ++} ++ ++static void ++create_key(isc_uint16_t flags, isc_uint8_t proto, isc_uint8_t alg, ++ const char *keynamestr, const char *keystr, dst_key_t **target) ++{ ++ dns_rdata_dnskey_t keystruct; ++ unsigned char keydata[4096]; ++ isc_buffer_t keydatabuf; ++ unsigned char rrdata[4096]; ++ isc_buffer_t rrdatabuf; ++ isc_region_t r; ++ const dns_rdataclass_t rdclass = dns_rdataclass_in; /* for brevity */ ++ ++ keystruct.common.rdclass = rdclass; ++ keystruct.common.rdtype = dns_rdatatype_dnskey; ++ keystruct.mctx = NULL; ++ ISC_LINK_INIT(&keystruct.common, link); ++ keystruct.flags = flags; ++ keystruct.protocol = proto; ++ keystruct.algorithm = alg; ++ ++ isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); ++ isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); ++ ATF_REQUIRE_EQ(isc_base64_decodestring(keystr, &keydatabuf), ++ ISC_R_SUCCESS); ++ isc_buffer_usedregion(&keydatabuf, &r); ++ keystruct.datalen = r.length; ++ keystruct.data = r.base; ++ ATF_REQUIRE_EQ(dns_rdata_fromstruct(NULL, keystruct.common.rdclass, ++ keystruct.common.rdtype, ++ &keystruct, &rrdatabuf), ++ ISC_R_SUCCESS); ++ ++ ATF_REQUIRE_EQ(dst_key_fromdns(str2name(keynamestr), rdclass, ++ &rrdatabuf, mctx, target), ++ ISC_R_SUCCESS); ++} ++ ++/* Common setup: create a keytable and ntatable to test with a few keys */ ++static void ++create_tables() { ++ isc_result_t result; ++ dst_key_t *key = NULL; ++ isc_stdtime_t now; ++ ++ result = dns_test_makeview("view", &view); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ ATF_REQUIRE_EQ(dns_keytable_create(mctx, &keytable), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_ntatable_create(view, taskmgr, timermgr, ++ &ntatable), ISC_R_SUCCESS); ++ ++ /* Add a normal key */ ++ create_key(257, 3, 5, "example.com", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), ++ ISC_R_SUCCESS); ++ ++ /* Add a null key */ ++ ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, ++ str2name("null.example")), ++ ISC_R_SUCCESS); ++ ++ /* Add a negative trust anchor, duration 1 hour */ ++ isc_stdtime_get(&now); ++ ATF_REQUIRE_EQ(dns_ntatable_add(ntatable, ++ str2name("insecure.example"), ++ ISC_FALSE, now, 3600), ++ ISC_R_SUCCESS); ++} ++ ++static void ++destroy_tables() { ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ if (keytable != NULL) ++ dns_keytable_detach(&keytable); ++ ++ dns_view_detach(&view); ++} ++ ++/* ++ * Individual unit tests ++ */ ++ ++ATF_TC(add); ++ATF_TC_HEAD(add, tc) { ++ atf_tc_set_md_var(tc, "descr", "add keys to the keytable"); ++} ++ATF_TC_BODY(add, tc) { ++ dst_key_t *key = NULL; ++ dns_keynode_t *keynode = NULL; ++ dns_keynode_t *next_keynode = NULL; ++ dns_keynode_t *null_keynode = NULL; ++ ++ UNUSED(tc); ++ ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* ++ * Get the keynode for the example.com key. There's no other key for ++ * the name, so nextkeynode() should return NOTFOUND. ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), ++ &keynode), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, ++ &next_keynode), ISC_R_NOTFOUND); ++ ++ /* ++ * Try to add the same key. This should have no effect, so ++ * nextkeynode() should still return NOTFOUND. ++ */ ++ create_key(257, 3, 5, "example.com", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, ++ &next_keynode), ISC_R_NOTFOUND); ++ ++ /* Add another key (different keydata) */ ++ dns_keytable_detachkeynode(keytable, &keynode); ++ create_key(257, 3, 5, "example.com", keystr2, &key); ++ ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), ++ &keynode), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, ++ &next_keynode), ISC_R_SUCCESS); ++ dns_keytable_detachkeynode(keytable, &next_keynode); ++ ++ /* ++ * Add a normal key to a name that has a null key. The null key node ++ * will be updated with the normal key. ++ */ ++ dns_keytable_detachkeynode(keytable, &keynode); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), ++ &null_keynode), ISC_R_SUCCESS); ++ create_key(257, 3, 5, "null.example", keystr2, &key); ++ ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), ++ &keynode), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(keynode, null_keynode); /* should be the same node */ ++ ATF_REQUIRE(dns_keynode_key(keynode) != NULL); /* now have a key */ ++ dns_keytable_detachkeynode(keytable, &null_keynode); ++ ++ /* ++ * Try to add a null key to a name that already has a key. It's ++ * effectively no-op, so the same key node is still there, with no ++ * no next node. ++ * (Note: this and above checks confirm that if a name has a null key ++ * that's the only key for the name). ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, ++ str2name("null.example")), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), ++ &null_keynode), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(keynode, null_keynode); ++ ATF_REQUIRE(dns_keynode_key(keynode) != NULL); ++ ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, ++ &next_keynode), ISC_R_NOTFOUND); ++ dns_keytable_detachkeynode(keytable, &null_keynode); ++ ++ dns_keytable_detachkeynode(keytable, &keynode); ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(delete); ++ATF_TC_HEAD(delete, tc) { ++ atf_tc_set_md_var(tc, "descr", "delete keys from the keytable"); ++} ++ATF_TC_BODY(delete, tc) { ++ UNUSED(tc); ++ ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* dns_keytable_delete requires exact match */ ++ ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.org")), ++ ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("s.example.com")), ++ ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), ++ ISC_R_SUCCESS); ++ ++ /* works also for nodes with a null key */ ++ ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("null.example")), ++ ISC_R_SUCCESS); ++ ++ /* or a negative trust anchor */ ++ ATF_REQUIRE_EQ(dns_ntatable_delete(ntatable, ++ str2name("insecure.example")), ++ ISC_R_SUCCESS); ++ ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(deletekeynode); ++ATF_TC_HEAD(deletekeynode, tc) { ++ atf_tc_set_md_var(tc, "descr", "delete key nodes from the keytable"); ++} ++ATF_TC_BODY(deletekeynode, tc) { ++ dst_key_t *key = NULL; ++ ++ UNUSED(tc); ++ ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* key name doesn't match */ ++ create_key(257, 3, 5, "example.org", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_NOTFOUND); ++ dst_key_free(&key); ++ ++ /* subdomain match is the same as no match */ ++ create_key(257, 3, 5, "sub.example.com", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_NOTFOUND); ++ dst_key_free(&key); ++ ++ /* name matches but key doesn't match (resulting in PARTIALMATCH) */ ++ create_key(257, 3, 5, "example.com", keystr2, &key); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ DNS_R_PARTIALMATCH); ++ dst_key_free(&key); ++ ++ /* ++ * exact match. after deleting the node the internal rbt node will be ++ * empty, and any delete or deletekeynode attempt should result in ++ * NOTFOUND. ++ */ ++ create_key(257, 3, 5, "example.com", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), ++ ISC_R_NOTFOUND); ++ dst_key_free(&key); ++ ++ /* ++ * The only null key node for a name can be deleted as long as the ++ * passed key name matches. ++ */ ++ create_key(257, 3, 5, "null.example", keystr1, &key); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), ++ ISC_R_NOTFOUND); ++ dst_key_free(&key); ++ ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(find); ++ATF_TC_HEAD(find, tc) { ++ atf_tc_set_md_var(tc, "descr", "check find-variant operations"); ++} ++ATF_TC_BODY(find, tc) { ++ dns_keynode_t *keynode = NULL; ++ dns_fixedname_t fname; ++ dns_name_t *name; ++ ++ UNUSED(tc); ++ ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* ++ * dns_keytable_find() requires exact name match. It matches node ++ * that has a null key, too. But it doesn't match a negative trust ++ * anchor. ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.org"), ++ &keynode), ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("sub.example.com"), ++ &keynode), ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), ++ &keynode), ISC_R_SUCCESS); ++ dns_keytable_detachkeynode(keytable, &keynode); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), ++ &keynode), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_keynode_key(keynode), NULL); ++ dns_keytable_detachkeynode(keytable, &keynode); ++ ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("insecure.example"), ++ &keynode), ISC_R_NOTFOUND); ++ ++ /* ++ * dns_keytable_finddeepestmatch() allows partial match. Also match ++ * nodes with a null key or a negative trust anchor. ++ */ ++ dns_fixedname_init(&fname); ++ name = dns_fixedname_name(&fname); ++ ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, ++ str2name("example.com"), ++ name), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), ISC_TRUE); ++ ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, ++ str2name("s.example.com"), ++ name), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), ISC_TRUE); ++ ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, ++ str2name("example.org"), ++ name), ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, ++ str2name("null.example"), ++ name), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(dns_name_equal(name, str2name("null.example")), ++ ISC_TRUE); ++ ++ /* ++ * dns_keytable_findkeynode() requires exact name, algorithm, keytag ++ * match. If algorithm or keytag doesn't match, should result in ++ * PARTIALMATCH. Same for a node with a null key or a negative trust ++ * anchor. ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("example.org"), ++ 5, keytag1, &keynode), ++ ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("sub.example.com"), ++ 5, keytag1, &keynode), ++ ISC_R_NOTFOUND); ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("example.com"), ++ 4, keytag1, &keynode), ++ DNS_R_PARTIALMATCH); /* different algorithm */ ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("example.com"), ++ 5, keytag1 + 1, &keynode), ++ DNS_R_PARTIALMATCH); /* different keytag */ ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("null.example"), ++ 5, 0, &keynode), ++ DNS_R_PARTIALMATCH); /* null key */ ++ ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, ++ str2name("example.com"), ++ 5, keytag1, &keynode), ++ ISC_R_SUCCESS); /* complete match */ ++ dns_keytable_detachkeynode(keytable, &keynode); ++ ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(issecuredomain); ++ATF_TC_HEAD(issecuredomain, tc) { ++ atf_tc_set_md_var(tc, "descr", "check issecuredomain()"); ++} ++ATF_TC_BODY(issecuredomain, tc) { ++ isc_boolean_t issecure; ++ const char **n; ++ const char *names[] = {"example.com", "sub.example.com", ++ "null.example", "sub.null.example", NULL}; ++ ++ UNUSED(tc); ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* ++ * Domains that are an exact or partial match of a key name are ++ * considered secure. It's the case even if the key is null ++ * (validation will then fail, but that's actually the intended effect ++ * of installing a null key). ++ */ ++ for (n = names; *n != NULL; n++) { ++ ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, ++ str2name(*n), ++ NULL, ++ &issecure), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(issecure, ISC_TRUE); ++ } ++ ++ /* ++ * Domains that are an exact or partial match of a negative trust ++ * anchor are considered insecure. ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, ++ str2name("insecure.example"), ++ NULL, ++ &issecure), ++ ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(issecure, ISC_FALSE); ++ ATF_REQUIRE_EQ(dns_keytable_issecuredomain( ++ keytable, str2name("sub.insecure.example"), ++ NULL, &issecure), ISC_R_SUCCESS); ++ ATF_REQUIRE_EQ(issecure, ISC_FALSE); ++ ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(dump); ++ATF_TC_HEAD(dump, tc) { ++ atf_tc_set_md_var(tc, "descr", "check dns_keytable_dump()"); ++} ++ATF_TC_BODY(dump, tc) { ++ UNUSED(tc); ++ ++ ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); ++ create_tables(); ++ ++ /* ++ * Right now, we only confirm the dump attempt doesn't cause disruption ++ * (so we don't check the dump content). ++ */ ++ ATF_REQUIRE_EQ(dns_keytable_dump(keytable, stdout), ISC_R_SUCCESS); ++ ++ destroy_tables(); ++ dns_test_end(); ++} ++ ++ATF_TC(nta); ++ATF_TC_HEAD(nta, tc) { ++ atf_tc_set_md_var(tc, "descr", "check negative trust anchors"); ++} ++ATF_TC_BODY(nta, tc) { ++ isc_result_t result; ++ dst_key_t *key = NULL; ++ isc_boolean_t issecure, covered; ++ dns_view_t *view = NULL; ++ isc_stdtime_t now; ++ ++ UNUSED(tc); ++ ++ result = dns_test_begin(NULL, ISC_TRUE); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = dns_test_makeview("view", &view); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = isc_task_create(taskmgr, 0, &view->task); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = dns_view_initsecroots(view, mctx); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ result = dns_view_getsecroots(view, &keytable); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = dns_view_initntatable(view, taskmgr, timermgr); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ result = dns_view_getntatable(view, &ntatable); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ create_key(257, 3, 5, "example", keystr1, &key); ++ result = dns_keytable_add(keytable, ISC_FALSE, &key); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ isc_stdtime_get(&now); ++ result = dns_ntatable_add(ntatable, ++ str2name("insecure.example"), ++ ISC_FALSE, now, 1); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ /* Should be secure */ ++ result = dns_view_issecuredomain(view, ++ str2name("test.secure.example"), ++ now, ISC_TRUE, &issecure); ++ ATF_CHECK_EQ(result, ISC_R_SUCCESS); ++ ATF_CHECK(issecure); ++ ++ /* Should not be secure */ ++ result = dns_view_issecuredomain(view, ++ str2name("test.insecure.example"), ++ now, ISC_TRUE, &issecure); ++ ATF_CHECK_EQ(result, ISC_R_SUCCESS); ++ ATF_CHECK(!issecure); ++ ++ /* NTA covered */ ++ covered = dns_view_ntacovers(view, now, str2name("insecure.example"), ++ dns_rootname); ++ ATF_CHECK(covered); ++ ++ /* Not NTA covered */ ++ covered = dns_view_ntacovers(view, now, str2name("secure.example"), ++ dns_rootname); ++ ATF_CHECK(!covered); ++ ++ /* As of now + 2, the NTA should be clear */ ++ result = dns_view_issecuredomain(view, ++ str2name("test.insecure.example"), ++ now + 2, ISC_TRUE, &issecure); ++ ATF_CHECK_EQ(result, ISC_R_SUCCESS); ++ ATF_CHECK(issecure); ++ ++ /* Now check deletion */ ++ result = dns_ntatable_add(ntatable, str2name("new.example"), ++ ISC_FALSE, now, 3600); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = dns_view_issecuredomain(view, str2name("test.new.example"), ++ now, ISC_TRUE, &issecure); ++ ATF_CHECK_EQ(result, ISC_R_SUCCESS); ++ ATF_CHECK(!issecure); ++ ++ result = dns_ntatable_delete(ntatable, str2name("new.example")); ++ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); ++ ++ result = dns_view_issecuredomain(view, str2name("test.new.example"), ++ now, ISC_TRUE, &issecure); ++ ATF_CHECK_EQ(result, ISC_R_SUCCESS); ++ ATF_CHECK(issecure); ++ ++ /* Clean up */ ++ dns_ntatable_detach(&ntatable); ++ dns_keytable_detach(&keytable); ++ dns_view_detach(&view); ++ ++ dns_test_end(); ++} ++ ++/* ++ * Main ++ */ ++ATF_TP_ADD_TCS(tp) { ++ ATF_TP_ADD_TC(tp, add); ++ ATF_TP_ADD_TC(tp, delete); ++ ATF_TP_ADD_TC(tp, deletekeynode); ++ ATF_TP_ADD_TC(tp, find); ++ ATF_TP_ADD_TC(tp, issecuredomain); ++ ATF_TP_ADD_TC(tp, dump); ++ ATF_TP_ADD_TC(tp, nta); ++ ++ return (atf_no_error()); ++} +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index 0c0f7b0c0f..6a9e101803 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -15,8 +15,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + #include + + #include +@@ -1132,6 +1130,8 @@ static inline isc_result_t + create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + isc_taskaction_t callback, const char *caller) + { ++ unsigned int fopts = 0; ++ + if (dns_rdataset_isassociated(&val->frdataset)) + dns_rdataset_disassociate(&val->frdataset); + if (dns_rdataset_isassociated(&val->fsigrdataset)) +@@ -1143,9 +1143,14 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + return (DNS_R_NOVALIDSIG); + } + ++ if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) ++ fopts |= DNS_FETCHOPT_NOCDFLAG; ++ if ((val->options & DNS_VALIDATOR_NONTA) != 0) ++ fopts |= DNS_FETCHOPT_NONTA; ++ + validator_logcreate(val, name, type, caller, "fetch"); + return (dns_resolver_createfetch(val->view->resolver, name, type, +- NULL, NULL, NULL, 0, ++ NULL, NULL, NULL, fopts, + val->event->ev_sender, + callback, val, + &val->frdataset, +@@ -1162,6 +1167,7 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + isc_taskaction_t action, const char *caller) + { + isc_result_t result; ++ unsigned int vopts = 0; + + if (check_deadlock(val, name, type, rdataset, sigrdataset)) { + validator_log(val, ISC_LOG_DEBUG(3), +@@ -1169,9 +1175,12 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + return (DNS_R_NOVALIDSIG); + } + ++ /* OK to clear other options, but preserve NOCDFLAG */ ++ vopts |= (val->options & DNS_VALIDATOR_NOCDFLAG); ++ + validator_logcreate(val, name, type, caller, "validator"); + result = dns_validator_create(val->view, name, type, +- rdataset, sigrdataset, NULL, 0, ++ rdataset, sigrdataset, NULL, vopts, + val->task, action, val, + &val->subvalidator); + if (result == ISC_R_SUCCESS) { +@@ -1550,7 +1559,7 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, + */ + static isc_result_t + validate(dns_validator_t *val, isc_boolean_t resume) { +- isc_result_t result; ++ isc_result_t result, vresult = DNS_R_NOVALIDSIG; + dns_validatorevent_t *event; + dns_rdata_t rdata = DNS_RDATA_INIT; + +@@ -1615,9 +1624,9 @@ validate(dns_validator_t *val, isc_boolean_t resume) { + } + + do { +- result = verify(val, val->key, &rdata, ++ vresult = verify(val, val->key, &rdata, + val->siginfo->keyid); +- if (result == ISC_R_SUCCESS) ++ if (vresult == ISC_R_SUCCESS) + break; + if (val->keynode != NULL) { + dns_keynode_t *nextnode = NULL; +@@ -1641,16 +1650,13 @@ validate(dns_validator_t *val, isc_boolean_t resume) { + break; + } + } while (1); +- if (result != ISC_R_SUCCESS) ++ if (vresult != ISC_R_SUCCESS) + validator_log(val, ISC_LOG_DEBUG(3), + "failed to verify rdataset"); + else { +- isc_stdtime_t now; +- +- isc_stdtime_get(&now); + dns_rdataset_trimttl(event->rdataset, + event->sigrdataset, +- val->siginfo, now, ++ val->siginfo, val->start, + val->view->acceptexpired); + } + +@@ -1675,12 +1681,12 @@ validate(dns_validator_t *val, isc_boolean_t resume) { + validator_log(val, ISC_LOG_DEBUG(3), + "looking for noqname proof"); + return (nsecvalidate(val, ISC_FALSE)); +- } else if (result == ISC_R_SUCCESS) { ++ } else if (vresult == ISC_R_SUCCESS) { + marksecure(event); + validator_log(val, ISC_LOG_DEBUG(3), + "marking as secure, " + "noqname proof not needed"); +- return (result); ++ return (ISC_R_SUCCESS); + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "verify failure: %s", +@@ -1696,7 +1702,7 @@ validate(dns_validator_t *val, isc_boolean_t resume) { + } + + validator_log(val, ISC_LOG_INFO, "no valid signature found"); +- return (DNS_R_NOVALIDSIG); ++ return (vresult); + } + + /*% +@@ -3069,6 +3075,11 @@ startfinddlvsep(dns_validator_t *val, dns_name_t *unsecure) { + markanswer(val, "startfinddlvsep (1)"); + return (ISC_R_SUCCESS); + } ++ if (result == DNS_R_NTACOVERED) { ++ validator_log(val, ISC_LOG_DEBUG(3), "DLV covered by NTA"); ++ validator_done(val, ISC_R_SUCCESS); ++ return (ISC_R_SUCCESS); ++ } + if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), "DLV lookup: %s", + dns_result_totext(result)); +@@ -3158,6 +3169,10 @@ finddlvsep(dns_validator_t *val, isc_boolean_t resume) { + return (DNS_R_NOVALIDSIG); + } + ++ if (((val->options & DNS_VALIDATOR_NONTA) == 0) && ++ dns_view_ntacovers(val->view, val->start, dlvname, val->view->dlv)) ++ return (DNS_R_NTACOVERED); ++ + while (dns_name_countlabels(dlvname) >= + dns_name_countlabels(val->view->dlv) + val->dlvlabels) { + dns_name_format(dlvname, namebuf, sizeof(namebuf)); +@@ -3774,6 +3789,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + dns_fixedname_init(&val->wild); + dns_fixedname_init(&val->nearest); + dns_fixedname_init(&val->closest); ++ isc_stdtime_get(&val->start); + ISC_LINK_INIT(val, link); + val->magic = VALIDATOR_MAGIC; + +@@ -3889,7 +3905,7 @@ dns_validator_destroy(dns_validator_t **validatorp) { + LOCK(&val->lock); + + val->attributes |= VALATTR_SHUTDOWN; +- validator_log(val, ISC_LOG_DEBUG(3), "dns_validator_destroy"); ++ validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy"); + + want_destroy = exit_check(val); + +@@ -3922,8 +3938,8 @@ validator_logv(dns_validator_t *val, isc_logcategory_t *category, + dns_rdatatype_format(val->event->type, typebuf, + sizeof(typebuf)); + isc_log_write(dns_lctx, category, module, level, +- "%.*svalidating @%p: %s %s: %s", depth, spaces, +- val, namebuf, typebuf, msgbuf); ++ "%.*svalidating %s/%s: %s", depth, spaces, ++ namebuf, typebuf, msgbuf); + } else { + isc_log_write(dns_lctx, category, module, level, + "%.*svalidator @%p: %s", depth, spaces, +diff --git a/lib/dns/view.c b/lib/dns/view.c +index 35900b3f0b..01b82e6ff6 100644 +--- a/lib/dns/view.c ++++ b/lib/dns/view.c +@@ -23,6 +23,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -47,6 +48,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -57,10 +59,16 @@ + #include + #include + #include ++#include + #include + #include + #include + ++#define CHECK(op) \ ++ do { result = (op); \ ++ if (result != ISC_R_SUCCESS) goto cleanup; \ ++ } while (0) ++ + #define RESSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_RESSHUTDOWN) != 0) + #define ADBSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_ADBSHUTDOWN) != 0) + #define REQSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_REQSHUTDOWN) != 0) +@@ -77,6 +85,8 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + { + dns_view_t *view; + isc_result_t result; ++ const char nta[] = ".nta"; ++ char buffer[ISC_SHA256_DIGESTSTRINGLENGTH + sizeof(nta)]; + + /* + * Create a view. +@@ -89,6 +99,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + if (view == NULL) + return (ISC_R_NOMEMORY); + ++ view->nta_file = NULL; + view->mctx = NULL; + isc_mem_attach(mctx, &view->mctx); + view->name = isc_mem_strdup(mctx, name); +@@ -96,6 +107,16 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + result = ISC_R_NOMEMORY; + goto cleanup_view; + } ++ ++ isc_sha256_data((void *)view->name, strlen(view->name), buffer); ++ /* Truncate the hash at 16 chars; full length is overkill */ ++ isc_string_printf(buffer + 16, sizeof(nta), "%s", nta); ++ view->nta_file = isc_mem_strdup(mctx, buffer); ++ if (view->nta_file == NULL) { ++ result = ISC_R_NOMEMORY; ++ goto cleanup_name; ++ } ++ + result = isc_mutex_init(&view->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_name; +@@ -112,6 +133,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + } + #endif + view->secroots_priv = NULL; ++ view->ntatable_priv = NULL; + view->fwdtable = NULL; + result = dns_fwdtable_create(mctx, &view->fwdtable); + if (result != ISC_R_SUCCESS) { +@@ -189,6 +211,8 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + view->provideixfr = ISC_TRUE; + view->maxcachettl = 7 * 24 * 3600; + view->maxncachettl = 3 * 3600; ++ view->nta_lifetime = 0; ++ view->nta_recheck = 0; + view->dstport = 53; + view->preferred_glue = 0; + view->flush = ISC_FALSE; +@@ -263,6 +287,9 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + #endif + DESTROYLOCK(&view->lock); + ++ if (view->nta_file != NULL) ++ isc_mem_free(mctx, view->nta_file); ++ + cleanup_name: + isc_mem_free(mctx, view->name); + +@@ -437,6 +464,8 @@ destroy(dns_view_t *view) { + if (view->secroots_priv != NULL) + dns_keytable_detach(&view->secroots_priv); + #ifdef BIND9 ++ if (view->ntatable_priv != NULL) ++ dns_ntatable_detach(&view->ntatable_priv); + for (dns64 = ISC_LIST_HEAD(view->dns64); + dns64 != NULL; + dns64 = ISC_LIST_HEAD(view->dns64)) { +@@ -453,6 +482,7 @@ destroy(dns_view_t *view) { + dns_aclenv_destroy(&view->aclenv); + DESTROYLOCK(&view->lock); + isc_refcount_destroy(&view->references); ++ isc_mem_free(view->mctx, view->nta_file); + isc_mem_free(view->mctx, view->name); + isc_mem_putanddetach(&view->mctx, view, sizeof(*view)); + } +@@ -1786,6 +1816,27 @@ dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp) { + dns_stats_attach(view->resquerystats, statsp); + } + ++isc_result_t ++dns_view_initntatable(dns_view_t *view, ++ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr) ++{ ++ REQUIRE(DNS_VIEW_VALID(view)); ++ if (view->ntatable_priv != NULL) ++ dns_ntatable_detach(&view->ntatable_priv); ++ return (dns_ntatable_create(view, taskmgr, timermgr, ++ &view->ntatable_priv)); ++} ++ ++isc_result_t ++dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp) { ++ REQUIRE(DNS_VIEW_VALID(view)); ++ REQUIRE(ntp != NULL && *ntp == NULL); ++ if (view->ntatable_priv == NULL) ++ return (ISC_R_NOTFOUND); ++ dns_ntatable_attach(view->ntatable_priv, ntp); ++ return (ISC_R_SUCCESS); ++} ++ + isc_result_t + dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx) { + REQUIRE(DNS_VIEW_VALID(view)); +@@ -1804,15 +1855,47 @@ dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp) { + return (ISC_R_SUCCESS); + } + ++isc_boolean_t ++dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, ++ dns_name_t *name, dns_name_t *anchor) ++{ ++ REQUIRE(DNS_VIEW_VALID(view)); ++ ++ if (view->ntatable_priv == NULL) ++ return (ISC_FALSE); ++ ++ return (dns_ntatable_covered(view->ntatable_priv, now, name, anchor)); ++} ++ + isc_result_t + dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, +- isc_boolean_t *secure_domain) { ++ isc_stdtime_t now, isc_boolean_t checknta, ++ isc_boolean_t *secure_domain) ++{ ++ isc_result_t result; ++ isc_boolean_t secure = ISC_FALSE; ++ dns_fixedname_t fn; ++ dns_name_t *anchor; ++ + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->secroots_priv == NULL) + return (ISC_R_NOTFOUND); +- return (dns_keytable_issecuredomain(view->secroots_priv, name, +- secure_domain)); ++ ++ dns_fixedname_init(&fn); ++ anchor = dns_fixedname_name(&fn); ++ ++ result = dns_keytable_issecuredomain(view->secroots_priv, name, ++ anchor, &secure); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ ++ if (checknta && secure && view->ntatable_priv != NULL && ++ dns_ntatable_covered(view->ntatable_priv, now, name, anchor)) ++ secure = ISC_FALSE; ++ ++ *secure_domain = secure; ++ return (ISC_R_SUCCESS); + } + + void +@@ -1882,3 +1965,151 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx, + UNUSED(cfg_destroy); + #endif + } ++ ++isc_result_t ++dns_view_saventa(dns_view_t *view) { ++#ifdef BIND9 ++ isc_result_t result; ++ isc_boolean_t remove = ISC_FALSE; ++ dns_ntatable_t *ntatable = NULL; ++ FILE *fp = NULL; ++ ++ REQUIRE(DNS_VIEW_VALID(view)); ++ ++ if (view->nta_lifetime == 0) ++ return (ISC_R_SUCCESS); ++ ++ /* Open NTA save file for overwrite. */ ++ CHECK(isc_stdio_open(view->nta_file, "w", &fp)); ++ ++ result = dns_view_getntatable(view, &ntatable); ++ if (result == ISC_R_NOTFOUND) { ++ remove = ISC_TRUE; ++ result = ISC_R_SUCCESS; ++ goto cleanup; ++ } else ++ CHECK(result); ++ ++ result = dns_ntatable_save(ntatable, fp); ++ if (result == ISC_R_NOTFOUND) { ++ remove = ISC_TRUE; ++ result = ISC_R_SUCCESS; ++ } ++ ++ cleanup: ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ ++ if (fp != NULL) ++ isc_stdio_close(fp); ++ ++ /* Don't leave half-baked NTA save files lying around. */ ++ if (result != ISC_R_SUCCESS || remove) ++ (void) isc_file_remove(view->nta_file); ++ ++ return (result); ++#else ++ REQUIRE(DNS_VIEW_VALID(view)); ++ return (ISC_R_NOTIMPLEMENTED); ++#endif ++} ++ ++#define TSTR(t) ((t).value.as_textregion.base) ++#define TLEN(t) ((t).value.as_textregion.length) ++ ++isc_result_t ++dns_view_loadnta(dns_view_t *view) { ++#ifdef BIND9 ++ isc_result_t result; ++ dns_ntatable_t *ntatable = NULL; ++ isc_lex_t *lex = NULL; ++ isc_token_t token; ++ isc_stdtime_t now; ++ ++ REQUIRE(DNS_VIEW_VALID(view)); ++ ++ if (view->nta_lifetime == 0) ++ return (ISC_R_SUCCESS); ++ ++ CHECK(isc_lex_create(view->mctx, 1025, &lex)); ++ CHECK(isc_lex_openfile(lex, view->nta_file)); ++ CHECK(dns_view_getntatable(view, &ntatable)); ++ isc_stdtime_get(&now); ++ ++ for (;;) { ++ int options = (ISC_LEXOPT_EOL | ISC_LEXOPT_EOF); ++ char *name, *type, *timestamp; ++ size_t len; ++ dns_fixedname_t fn; ++ dns_name_t *ntaname; ++ isc_buffer_t b; ++ isc_stdtime_t t; ++ isc_boolean_t forced; ++ ++ CHECK(isc_lex_gettoken(lex, options, &token)); ++ if (token.type == isc_tokentype_eof) ++ break; ++ else if (token.type != isc_tokentype_string) ++ CHECK(ISC_R_UNEXPECTEDTOKEN); ++ name = TSTR(token); ++ len = TLEN(token); ++ ++ if (strcmp(name, ".") == 0) ++ ntaname = dns_rootname; ++ else { ++ dns_fixedname_init(&fn); ++ ntaname = dns_fixedname_name(&fn); ++ ++ isc_buffer_init(&b, name, len); ++ isc_buffer_add(&b, len); ++ CHECK(dns_name_fromtext(ntaname, &b, dns_rootname, ++ 0, NULL)); ++ } ++ ++ CHECK(isc_lex_gettoken(lex, options, &token)); ++ if (token.type != isc_tokentype_string) ++ CHECK(ISC_R_UNEXPECTEDTOKEN); ++ type = TSTR(token); ++ ++ if (strcmp(type, "regular") == 0) ++ forced = ISC_FALSE; ++ else if (strcmp(type, "forced") == 0) ++ forced = ISC_TRUE; ++ else ++ CHECK(ISC_R_UNEXPECTEDTOKEN); ++ ++ CHECK(isc_lex_gettoken(lex, options, &token)); ++ if (token.type != isc_tokentype_string) ++ CHECK(ISC_R_UNEXPECTEDTOKEN); ++ timestamp = TSTR(token); ++ CHECK(dns_time32_fromtext(timestamp, &t)); ++ ++ CHECK(isc_lex_gettoken(lex, options, &token)); ++ if (token.type != isc_tokentype_eol && ++ token.type != isc_tokentype_eof) ++ CHECK(ISC_R_UNEXPECTEDTOKEN); ++ ++ if (now <= t) { ++ if (t > (now + 604800)) ++ t = now + 604800; ++ ++ (void) dns_ntatable_add(ntatable, ntaname, ++ forced, 0, t); ++ } ++ }; ++ ++ cleanup: ++ if (ntatable != NULL) ++ dns_ntatable_detach(&ntatable); ++ ++ if (lex != NULL) { ++ isc_lex_close(lex); ++ isc_lex_destroy(&lex); ++ } ++ ++ return (result); ++#else ++ REQUIRE(DNS_VIEW_VALID(view)); ++ return (ISC_R_NOTIMPLEMENTED); ++#endif ++} +diff --git a/lib/export/dns/Makefile.in b/lib/export/dns/Makefile.in +index 887acb9416..4719a5a809 100644 +--- a/lib/export/dns/Makefile.in ++++ b/lib/export/dns/Makefile.in +@@ -59,7 +59,7 @@ DNSOBJS = acl.@O@ adb.@O@ byaddr.@O@ \ + keytable.@O@ \ + lib.@O@ log.@O@ \ + master.@O@ masterdump.@O@ message.@O@ \ +- name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ \ ++ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ + peer.@O@ portlist.@O@ \ + rbt.@O@ rbtdb.@O@ rcode.@O@ rdata.@O@ \ + rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ +@@ -89,7 +89,7 @@ DNSSRCS = acl.c adb.c byaddr.c \ + keytable.c \ + lib.c log.c \ + master.c masterdump.c message.c \ +- name.c ncache.c nsec.c nsec3.c \ ++ name.c ncache.c nsec.c nsec3.c nta.c \ + peer.c portlist.c \ + rbt.c rbtdb.c rcode.c rdata.c \ + rdatalist.c rdataset.c rdatasetiter.c rdataslab.c \ +diff --git a/lib/isc/include/isc/log.h b/lib/isc/include/isc/log.h +index 741c532429..e609d913b7 100644 +--- a/lib/isc/include/isc/log.h ++++ b/lib/isc/include/isc/log.h +@@ -67,8 +67,9 @@ + #define ISC_LOG_PRINTLEVEL 0x0002 + #define ISC_LOG_PRINTCATEGORY 0x0004 + #define ISC_LOG_PRINTMODULE 0x0008 +-#define ISC_LOG_PRINTTAG 0x0010 +-#define ISC_LOG_PRINTALL 0x001F ++#define ISC_LOG_PRINTTAG 0x0010 /* tag and ":" */ ++#define ISC_LOG_PRINTPREFIX 0x0020 /* tag only, no colon */ ++#define ISC_LOG_PRINTALL 0x003F + #define ISC_LOG_DEBUGONLY 0x1000 + #define ISC_LOG_OPENERR 0x8000 /* internal */ + /*@}*/ +@@ -167,6 +168,7 @@ LIBISC_EXTERNAL_DATA extern isc_logmodule_t isc_modules[]; + #define ISC_LOGMODULE_INTERFACE (&isc_modules[2]) + #define ISC_LOGMODULE_TIMER (&isc_modules[3]) + #define ISC_LOGMODULE_FILE (&isc_modules[4]) ++#define ISC_LOGMODULE_OTHER (&isc_modules[5]) + + ISC_LANG_BEGINDECLS + +diff --git a/lib/isc/log.c b/lib/isc/log.c +index 35204cfb9a..65b78ea46d 100644 +--- a/lib/isc/log.c ++++ b/lib/isc/log.c +@@ -205,6 +205,7 @@ LIBISC_EXTERNAL_DATA isc_logmodule_t isc_modules[] = { + { "interface", 0 }, + { "timer", 0 }, + { "file", 0 }, ++ { "other", 0 }, + { NULL, 0 } + }; + +@@ -1413,7 +1414,7 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + const char *iformat; + struct stat statbuf; + isc_boolean_t matched = ISC_FALSE; +- isc_boolean_t printtime, printtag; ++ isc_boolean_t printtime, printtag, printcolon; + isc_boolean_t printcategory, printmodule, printlevel; + isc_logconfig_t *lcfg; + isc_logchannel_t *channel; +@@ -1641,7 +1642,10 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + + printtime = ISC_TF((channel->flags & ISC_LOG_PRINTTIME) + != 0); +- printtag = ISC_TF((channel->flags & ISC_LOG_PRINTTAG) ++ printtag = ISC_TF((channel->flags & ++ (ISC_LOG_PRINTTAG|ISC_LOG_PRINTPREFIX)) ++ != 0 && lcfg->tag != NULL); ++ printcolon = ISC_TF((channel->flags & ISC_LOG_PRINTTAG) + != 0 && lcfg->tag != NULL); + printcategory = ISC_TF((channel->flags & ISC_LOG_PRINTCATEGORY) + != 0); +@@ -1695,11 +1699,12 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + /* FALLTHROUGH */ + + case ISC_LOG_TOFILEDESC: +- fprintf(FILE_STREAM(channel), "%s%s%s%s%s%s%s%s%s%s\n", ++ fprintf(FILE_STREAM(channel), ++ "%s%s%s%s%s%s%s%s%s%s\n", + printtime ? time_string : "", + printtime ? " " : "", + printtag ? lcfg->tag : "", +- printtag ? ": " : "", ++ printcolon ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", + printmodule ? (module != NULL ? module->name +@@ -1742,11 +1747,12 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + printtime ? time_string : "", + printtime ? " " : "", + printtag ? lcfg->tag : "", +- printtag ? ": " : "", ++ printcolon ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", +- printmodule ? (module != NULL ? module->name +- : "no_module") ++ printmodule ? (module != NULL ++ ? module->name ++ : "no_module") + : "", + printmodule ? ": " : "", + printlevel ? level_string : "", +diff --git a/lib/isc/unix/app.c b/lib/isc/unix/app.c +index d97d7c6bbc..3338b60173 100644 +--- a/lib/isc/unix/app.c ++++ b/lib/isc/unix/app.c +@@ -232,7 +232,6 @@ ISC_APPFUNC_SCOPE isc_result_t + isc__app_ctxstart(isc_appctx_t *ctx0) { + isc__appctx_t *ctx = (isc__appctx_t *)ctx0; + isc_result_t result; +- + REQUIRE(VALID_APPCTX(ctx)); + + /* +diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c +index 8773be8446..d9b1df158a 100644 +--- a/lib/isccfg/namedconf.c ++++ b/lib/isccfg/namedconf.c +@@ -29,6 +29,8 @@ + #include + #include + ++#include ++ + #include + #include + #include +@@ -130,6 +132,7 @@ static cfg_type_t cfg_type_sizenodefault; + static cfg_type_t cfg_type_sockaddr4wild; + static cfg_type_t cfg_type_sockaddr6wild; + static cfg_type_t cfg_type_statschannels; ++static cfg_type_t cfg_type_ttlval; + static cfg_type_t cfg_type_view; + static cfg_type_t cfg_type_viewopts; + static cfg_type_t cfg_type_zone; +@@ -1441,6 +1444,8 @@ view_clauses[] = { + { "max-udp-size", &cfg_type_uint32, 0 }, + { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, + { "minimal-responses", &cfg_type_boolean, 0 }, ++ { "nta-recheck", &cfg_type_ttlval, 0 }, ++ { "nta-lifetime", &cfg_type_ttlval, 0 }, + { "preferred-glue", &cfg_type_astring, 0 }, + { "provide-ixfr", &cfg_type_boolean, 0 }, + /* +@@ -3068,3 +3073,43 @@ static cfg_type_t cfg_type_masterselement = { + "masters_element", parse_masterselement, NULL, + doc_masterselement, NULL, NULL + }; ++ ++static isc_result_t ++parse_ttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { ++ isc_result_t result; ++ cfg_obj_t *obj = NULL; ++ isc_uint32_t ttl; ++ ++ UNUSED(type); ++ ++ CHECK(cfg_gettoken(pctx, 0)); ++ if (pctx->token.type != isc_tokentype_string) { ++ result = ISC_R_UNEXPECTEDTOKEN; ++ goto cleanup; ++ } ++ ++ result = dns_ttl_fromtext(&pctx->token.value.as_textregion, &ttl); ++ if (result == ISC_R_RANGE ) { ++ cfg_parser_error(pctx, CFG_LOG_NEAR, "TTL out of range "); ++ return (result); ++ } else if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ ++ CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); ++ obj->value.uint32 = ttl; ++ *ret = obj; ++ return (ISC_R_SUCCESS); ++ ++ cleanup: ++ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected integer and optional unit"); ++ return (result); ++} ++ ++/*% ++ * A TTL value (number + optional unit). ++ */ ++static cfg_type_t cfg_type_ttlval = { ++ "ttlval", parse_ttlval, cfg_print_uint64, cfg_doc_terminal, ++ &cfg_rep_uint64, NULL ++}; ++ +diff --git a/make/includes.in b/make/includes.in +index f2f1b3f451..42b19bcc39 100644 +--- a/make/includes.in ++++ b/make/includes.in +@@ -37,6 +37,9 @@ ISCCFG_INCLUDES = @BIND9_ISCCFG_BUILDINCLUDE@ \ + DNS_INCLUDES = @BIND9_DNS_BUILDINCLUDE@ \ + -I${top_srcdir}/lib/dns/include + ++IRS_INCLUDES = @BIND9_IRS_BUILDINCLUDE@ \ ++ -I${top_srcdir}/lib/irs/include ++ + LWRES_INCLUDES = @BIND9_LWRES_BUILDINCLUDE@ \ + -I${top_srcdir}/lib/lwres/unix/include \ + -I${top_srcdir}/lib/lwres/include +diff --git a/make/rules.in b/make/rules.in +index 37bc50d82f..bc6f92e9f3 100644 +--- a/make/rules.in ++++ b/make/rules.in +@@ -48,9 +48,10 @@ top_builddir = @BIND9_TOP_BUILDDIR@ + ### All + ### + ### Makefile may define: ++### PREREQS + ### TARGETS + +-all: subdirs ${TARGETS} testdirs ++all: ${PREREQS} subdirs ${TARGETS} testdirs + + ### + ### Subdirectories +-- +2.14.4 + diff --git a/SOURCES/bind99-rh1510008-2.patch b/SOURCES/bind99-rh1510008-2.patch new file mode 100644 index 0000000..005b256 --- /dev/null +++ b/SOURCES/bind99-rh1510008-2.patch @@ -0,0 +1,232 @@ +From 4a0a86d84ff11337c363e0540947da136b296b70 Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Fri, 29 Apr 2016 14:17:21 -0700 +Subject: [PATCH] [master] more python2/3 compatibility fixes; use setup.py to + install + +--- + bin/python/Makefile.in | 2 ++ + bin/python/isc/Makefile.in | 28 +++------------------------- + bin/python/isc/__init__.py | 6 ++++-- + bin/python/isc/checkds.py | 5 +++-- + bin/python/isc/coverage.py | 7 +++---- + bin/python/isc/dnskey.py | 4 ++-- + bin/python/isc/keymgr.py | 7 +++---- + bin/python/isc/policy.py | 6 +++++- + bin/python/setup.py | 8 ++++++++ + 9 files changed, 33 insertions(+), 40 deletions(-) + create mode 100644 bin/python/setup.py + +diff --git a/bin/python/Makefile.in b/bin/python/Makefile.in +index 1e4af9c2e2..7ef32cc59b 100644 +--- a/bin/python/Makefile.in ++++ b/bin/python/Makefile.in +@@ -55,9 +55,11 @@ install:: ${TARGETS} installdirs + ${INSTALL_DATA} ${srcdir}/dnssec-checkds.8 ${DESTDIR}${mandir}/man8 + ${INSTALL_DATA} ${srcdir}/dnssec-coverage.8 ${DESTDIR}${mandir}/man8 + ${INSTALL_DATA} ${srcdir}/dnssec-keymgr.8 ${DESTDIR}${mandir}/man8 ++ test -z "${PYTHON}" || ${PYTHON} setup.py install --prefix=${DESTDIR}${prefix} + + clean distclean:: + rm -f ${TARGETS} ++ rm -rf build + + distclean:: + rm -f dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py +diff --git a/bin/python/isc/Makefile.in b/bin/python/isc/Makefile.in +index 425d054cce..a72f6e4054 100644 +--- a/bin/python/isc/Makefile.in ++++ b/bin/python/isc/Makefile.in +@@ -24,44 +24,22 @@ PYTHON = @PYTHON@ + + PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \ + keyevent.py keyzone.py policy.py +-TARGETS = parsetab.py parsetab.pyc \ +- __init__.pyc dnskey.pyc eventlist.py keydict.py \ +- keyevent.pyc keyzone.pyc policy.pyc ++TARGETS = parsetab.py + + @BIND9_MAKE_RULES@ + + %.pyc: %.py + $(PYTHON) -m compileall . + +-parsetab.py parsetab.pyc: policy.py ++parsetab.py: policy.py + $(PYTHON) policy.py parse /dev/null > /dev/null + $(PYTHON) -m parsetab + +-installdirs: +- $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}/isc +- +-install:: ${PYSRCS} installdirs +- ${INSTALL_SCRIPT} __init__.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} __init__.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} dnskey.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} dnskey.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} eventlist.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} eventlist.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keydict.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keydict.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keyevent.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keyevent.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keyzone.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} keyzone.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} policy.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} policy.pyc ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} parsetab.py ${DESTDIR}${libdir} +- ${INSTALL_SCRIPT} parsetab.pyc ${DESTDIR}${libdir} +- + check test: subdirs + + clean distclean:: + rm -f *.pyc parser.out parsetab.py ++ rm -rf __pycache__ build + + distclean:: + rm -Rf utils.py +\ No newline at end of file +diff --git a/bin/python/isc/__init__.py b/bin/python/isc/__init__.py +index 0d79f356fd..10b3c45cf1 100644 +--- a/bin/python/isc/__init__.py ++++ b/bin/python/isc/__init__.py +@@ -13,8 +13,10 @@ + # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-__all__ = ['dnskey', 'eventlist', 'keydict', 'keyevent', 'keyseries', +- 'keyzone', 'policy', 'parsetab', 'utils'] ++__all__ = ['checkds', 'coverage', 'keymgr', 'dnskey', 'eventlist', ++ 'keydict', 'keyevent', 'keyseries', 'keyzone', 'policy', ++ 'parsetab', 'utils'] ++ + from isc.dnskey import * + from isc.eventlist import * + from isc.keydict import * +diff --git a/bin/python/isc/checkds.py b/bin/python/isc/checkds.py +index 64ca12ebc6..2b7da39fc9 100644 +--- a/bin/python/isc/checkds.py ++++ b/bin/python/isc/checkds.py +@@ -42,7 +42,7 @@ class SECRR: + if not rrtext: + raise Exception + +- fields = rrtext.split() ++ fields = rrtext.decode('ascii').split() + if len(fields) < 7: + raise Exception + +@@ -75,7 +75,8 @@ class SECRR: + fields = fields[2:] + + if fields[0].upper() != self.rrtype: +- raise Exception ++ raise Exception('%s does not match %s' % ++ (fields[0].upper(), self.rrtype)) + + self.keyid, self.keyalg, self.hashalg = map(int, fields[1:4]) + self.digest = ''.join(fields[4:]).upper() +diff --git a/bin/python/isc/coverage.py b/bin/python/isc/coverage.py +index c9e89596f7..bfe811bee8 100644 +--- a/bin/python/isc/coverage.py ++++ b/bin/python/isc/coverage.py +@@ -27,9 +27,7 @@ from collections import defaultdict + + prog = 'dnssec-coverage' + +-from isc import * +-from isc.utils import prefix +- ++from isc import dnskey, eventlist, keydict, keyevent, keyzone, utils + + ############################################################################ + # print a fatal error and exit +@@ -139,7 +137,8 @@ def set_path(command, default=None): + def parse_args(): + """Read command line arguments, set global 'args' structure""" + compilezone = set_path('named-compilezone', +- os.path.join(prefix('sbin'), 'named-compilezone')) ++ os.path.join(utils.prefix('sbin'), ++ 'named-compilezone')) + + parser = argparse.ArgumentParser(description=prog + ': checks future ' + + 'DNSKEY coverage for a zone') +diff --git a/bin/python/isc/dnskey.py b/bin/python/isc/dnskey.py +index f1559e7239..14079504b6 100644 +--- a/bin/python/isc/dnskey.py ++++ b/bin/python/isc/dnskey.py +@@ -205,11 +205,11 @@ class dnskey: + raise Exception('unable to generate key: ' + str(stderr)) + + try: +- keystr = stdout.splitlines()[0] ++ keystr = stdout.splitlines()[0].decode('ascii') + newkey = dnskey(keystr, keys_dir, ttl) + return newkey + except Exception as e: +- raise Exception('unable to generate key: %s' % str(e)) ++ raise Exception('unable to parse generated key: %s' % str(e)) + + def generate_successor(self, keygen_bin, **kwargs): + quiet = kwargs.get('quiet', False) +diff --git a/bin/python/isc/keymgr.py b/bin/python/isc/keymgr.py +index a3a9043965..cbe86ab65e 100644 +--- a/bin/python/isc/keymgr.py ++++ b/bin/python/isc/keymgr.py +@@ -20,8 +20,7 @@ from collections import defaultdict + + prog='dnssec-keymgr' + +-from isc import * +-from isc.utils import prefix ++from isc import dnskey, keydict, keyseries, policy, parsetab, utils + + ############################################################################ + # print a fatal error and exit +@@ -63,9 +62,9 @@ def parse_args(): + """ + + keygen = set_path('dnssec-keygen', +- os.path.join(prefix('sbin'), 'dnssec-keygen')) ++ os.path.join(utils.prefix('sbin'), 'dnssec-keygen')) + settime = set_path('dnssec-settime', +- os.path.join(prefix('sbin'), 'dnssec-settime')) ++ os.path.join(utils.prefix('sbin'), 'dnssec-settime')) + + parser = argparse.ArgumentParser(description=prog + ': schedule ' + 'DNSSEC key rollovers according to a ' +diff --git a/bin/python/isc/policy.py b/bin/python/isc/policy.py +index ed106c6c92..dbb4abf010 100644 +--- a/bin/python/isc/policy.py ++++ b/bin/python/isc/policy.py +@@ -104,8 +104,12 @@ class PolicyLex: + t.lexer.skip(1) + + def __init__(self, **kwargs): ++ if 'maketrans' in dir(str): ++ trans = str.maketrans('_', '-') ++ else: ++ trans = maketrans('_', '-') + for r in self.reserved: +- self.reserved_map[r.lower().translate(maketrans('_', '-'))] = r ++ self.reserved_map[r.lower().translate(trans)] = r + self.lexer = lex.lex(object=self, **kwargs) + + def test(self, text): +diff --git a/bin/python/setup.py b/bin/python/setup.py +new file mode 100644 +index 0000000000..d7ea4a4d41 +--- /dev/null ++++ b/bin/python/setup.py +@@ -0,0 +1,8 @@ ++from distutils.core import setup ++setup(name='isc', ++ version='2.0', ++ description='Python functions to support BIND utilities', ++ url='https://www.isc.org/bind', ++ author='Internet Systems Consortium, Inc', ++ license='ISC', ++ packages=['isc']) +-- +2.14.3 + diff --git a/SOURCES/bind99-rh1510008.patch b/SOURCES/bind99-rh1510008.patch new file mode 100644 index 0000000..f6eec74 --- /dev/null +++ b/SOURCES/bind99-rh1510008.patch @@ -0,0 +1,6053 @@ +From 923ea26e87ba7c8b8c818dc6c996604fc709b05a Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Thu, 28 Apr 2016 00:12:33 -0700 +Subject: [PATCH] dnssec-keymgr + +4349. [contrib] kasp2policy: A python script to create a DNSSEC + policy file from an OpenDNSSEC KASP XML file. + +4348. [func] dnssec-keymgr: A new python-based DNSSEC key + management utility, which reads a policy definition + file and can create or update DNSSEC keys as needed + to ensure that a zone's keys match policy, roll over + correctly on schedule, etc. Thanks to Sebastian + Castro for assistance in development. [RT #39211] + +Adapt keymgr and coverage tests to v9.9 +--- + bin/dnssec/dnssec-settime.c | 7 +- + bin/python/.gitignore | 7 + + bin/python/Makefile.in | 20 +- + bin/python/dnssec-keymgr.docbook | 354 ++++++++++ + bin/python/dnssec-keymgr.py.in | 27 + + bin/python/isc/.gitignore | 3 + + bin/python/isc/Makefile.in | 67 ++ + bin/python/isc/__init__.py | 25 + + bin/python/isc/checkds.py | 189 ++++++ + bin/python/isc/coverage.py | 292 +++++++++ + bin/python/isc/dnskey.py | 504 +++++++++++++++ + bin/python/isc/eventlist.py | 171 +++++ + bin/python/isc/keydict.py | 89 +++ + bin/python/isc/keyevent.py | 81 +++ + bin/python/isc/keymgr.py | 152 +++++ + bin/python/isc/keyseries.py | 194 ++++++ + bin/python/isc/keyzone.py | 60 ++ + bin/python/isc/policy.py | 690 ++++++++++++++++++++ + bin/python/isc/tests/Makefile.in | 33 + + bin/python/isc/tests/dnskey_test.py | 57 ++ + bin/python/isc/tests/policy_test.py | 90 +++ + bin/python/isc/tests/test-policies/01-keysize.pol | 41 ++ + .../isc/tests/test-policies/02-prepublish.pol | 31 + + .../isc/tests/test-policies/03-postpublish.pol | 31 + + .../tests/test-policies/04-combined-pre-post.pol | 55 ++ + .../isc/tests/testdata/Kexample.com.+007+35529.key | 8 + + .../tests/testdata/Kexample.com.+007+35529.private | 18 + + bin/python/isc/utils.py.in | 57 ++ + bin/tests/system/conf.sh.in | 9 +- + bin/tests/system/keymgr/01-ksk-inactive/README | 2 + + bin/tests/system/keymgr/01-ksk-inactive/expect | 9 + + bin/tests/system/keymgr/02-zsk-inactive/README | 2 + + bin/tests/system/keymgr/02-zsk-inactive/expect | 9 + + bin/tests/system/keymgr/03-ksk-unpublished/README | 2 + + bin/tests/system/keymgr/03-ksk-unpublished/expect | 9 + + bin/tests/system/keymgr/04-zsk-unpublished/README | 2 + + bin/tests/system/keymgr/04-zsk-unpublished/expect | 9 + + bin/tests/system/keymgr/05-ksk-unpub-active/README | 3 + + bin/tests/system/keymgr/05-ksk-unpub-active/expect | 9 + + bin/tests/system/keymgr/06-zsk-unpub-active/README | 3 + + bin/tests/system/keymgr/06-zsk-unpub-active/expect | 9 + + bin/tests/system/keymgr/07-ksk-ttl/README | 2 + + bin/tests/system/keymgr/07-ksk-ttl/expect | 9 + + bin/tests/system/keymgr/08-zsk-ttl/README | 2 + + bin/tests/system/keymgr/08-zsk-ttl/expect | 9 + + bin/tests/system/keymgr/09-no-keys/README | 1 + + bin/tests/system/keymgr/09-no-keys/expect | 9 + + bin/tests/system/keymgr/10-change-roll/README | 3 + + bin/tests/system/keymgr/10-change-roll/expect | 9 + + bin/tests/system/keymgr/11-many-simul/README | 2 + + bin/tests/system/keymgr/11-many-simul/expect | 9 + + bin/tests/system/keymgr/12-many-active/README | 2 + + bin/tests/system/keymgr/12-many-active/expect | 9 + + bin/tests/system/keymgr/13-noroll/README | 2 + + bin/tests/system/keymgr/13-noroll/expect | 9 + + bin/tests/system/keymgr/14-wrongalg/README | 2 + + bin/tests/system/keymgr/14-wrongalg/expect | 9 + + bin/tests/system/keymgr/15-unspec/README | 2 + + bin/tests/system/keymgr/15-unspec/expect | 9 + + bin/tests/system/keymgr/16-wrongalg-unspec/README | 2 + + bin/tests/system/keymgr/16-wrongalg-unspec/expect | 9 + + bin/tests/system/keymgr/17-noforce/README | 2 + + bin/tests/system/keymgr/17-noforce/expect | 9 + + bin/tests/system/keymgr/clean.sh | 21 + + bin/tests/system/keymgr/policy.conf | 10 + + bin/tests/system/keymgr/policy.good | 170 +++++ + bin/tests/system/keymgr/policy.sample | 40 ++ + bin/tests/system/keymgr/prereq.sh | 30 + + bin/tests/system/keymgr/setup.sh | 214 ++++++ + bin/tests/system/keymgr/testpolicy.py | 29 + + bin/tests/system/keymgr/tests.sh | 106 +++ + configure | 11 + + configure.in | 9 + + contrib/kasp/README | 11 + + contrib/kasp/kasp.xml | 134 ++++ + contrib/kasp/kasp2policy.py | 209 ++++++ + contrib/kasp/policy.good | 24 + + doc/arm/notes.xml | 714 +++++++++++++++++++++ + 78 files changed, 5271 insertions(+), 12 deletions(-) + create mode 100644 bin/python/.gitignore + create mode 100644 bin/python/dnssec-keymgr.docbook + create mode 100644 bin/python/dnssec-keymgr.py.in + create mode 100644 bin/python/isc/.gitignore + create mode 100644 bin/python/isc/Makefile.in + create mode 100644 bin/python/isc/__init__.py + create mode 100644 bin/python/isc/checkds.py + create mode 100644 bin/python/isc/coverage.py + create mode 100644 bin/python/isc/dnskey.py + create mode 100644 bin/python/isc/eventlist.py + create mode 100644 bin/python/isc/keydict.py + create mode 100644 bin/python/isc/keyevent.py + create mode 100644 bin/python/isc/keymgr.py + create mode 100644 bin/python/isc/keyseries.py + create mode 100644 bin/python/isc/keyzone.py + create mode 100644 bin/python/isc/policy.py + create mode 100644 bin/python/isc/tests/Makefile.in + create mode 100644 bin/python/isc/tests/dnskey_test.py + create mode 100644 bin/python/isc/tests/policy_test.py + create mode 100644 bin/python/isc/tests/test-policies/01-keysize.pol + create mode 100644 bin/python/isc/tests/test-policies/02-prepublish.pol + create mode 100644 bin/python/isc/tests/test-policies/03-postpublish.pol + create mode 100644 bin/python/isc/tests/test-policies/04-combined-pre-post.pol + create mode 100644 bin/python/isc/tests/testdata/Kexample.com.+007+35529.key + create mode 100644 bin/python/isc/tests/testdata/Kexample.com.+007+35529.private + create mode 100644 bin/python/isc/utils.py.in + create mode 100644 bin/tests/system/keymgr/01-ksk-inactive/README + create mode 100644 bin/tests/system/keymgr/01-ksk-inactive/expect + create mode 100644 bin/tests/system/keymgr/02-zsk-inactive/README + create mode 100644 bin/tests/system/keymgr/02-zsk-inactive/expect + create mode 100644 bin/tests/system/keymgr/03-ksk-unpublished/README + create mode 100644 bin/tests/system/keymgr/03-ksk-unpublished/expect + create mode 100644 bin/tests/system/keymgr/04-zsk-unpublished/README + create mode 100644 bin/tests/system/keymgr/04-zsk-unpublished/expect + create mode 100644 bin/tests/system/keymgr/05-ksk-unpub-active/README + create mode 100644 bin/tests/system/keymgr/05-ksk-unpub-active/expect + create mode 100644 bin/tests/system/keymgr/06-zsk-unpub-active/README + create mode 100644 bin/tests/system/keymgr/06-zsk-unpub-active/expect + create mode 100644 bin/tests/system/keymgr/07-ksk-ttl/README + create mode 100644 bin/tests/system/keymgr/07-ksk-ttl/expect + create mode 100644 bin/tests/system/keymgr/08-zsk-ttl/README + create mode 100644 bin/tests/system/keymgr/08-zsk-ttl/expect + create mode 100644 bin/tests/system/keymgr/09-no-keys/README + create mode 100644 bin/tests/system/keymgr/09-no-keys/expect + create mode 100644 bin/tests/system/keymgr/10-change-roll/README + create mode 100644 bin/tests/system/keymgr/10-change-roll/expect + create mode 100644 bin/tests/system/keymgr/11-many-simul/README + create mode 100644 bin/tests/system/keymgr/11-many-simul/expect + create mode 100644 bin/tests/system/keymgr/12-many-active/README + create mode 100644 bin/tests/system/keymgr/12-many-active/expect + create mode 100644 bin/tests/system/keymgr/13-noroll/README + create mode 100644 bin/tests/system/keymgr/13-noroll/expect + create mode 100644 bin/tests/system/keymgr/14-wrongalg/README + create mode 100644 bin/tests/system/keymgr/14-wrongalg/expect + create mode 100644 bin/tests/system/keymgr/15-unspec/README + create mode 100644 bin/tests/system/keymgr/15-unspec/expect + create mode 100644 bin/tests/system/keymgr/16-wrongalg-unspec/README + create mode 100644 bin/tests/system/keymgr/16-wrongalg-unspec/expect + create mode 100644 bin/tests/system/keymgr/17-noforce/README + create mode 100644 bin/tests/system/keymgr/17-noforce/expect + create mode 100644 bin/tests/system/keymgr/clean.sh + create mode 100644 bin/tests/system/keymgr/policy.conf + create mode 100644 bin/tests/system/keymgr/policy.good + create mode 100644 bin/tests/system/keymgr/policy.sample + create mode 100644 bin/tests/system/keymgr/prereq.sh + create mode 100644 bin/tests/system/keymgr/setup.sh + create mode 100644 bin/tests/system/keymgr/testpolicy.py + create mode 100644 bin/tests/system/keymgr/tests.sh + create mode 100644 contrib/kasp/README + create mode 100644 contrib/kasp/kasp.xml + create mode 100644 contrib/kasp/kasp2policy.py + create mode 100644 contrib/kasp/policy.good + create mode 100644 doc/arm/notes.xml + +diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c +index c71cac7..71c1ac5 100644 +--- a/bin/dnssec/dnssec-settime.c ++++ b/bin/dnssec/dnssec-settime.c +@@ -492,11 +492,12 @@ main(int argc, char **argv) { + if ((setdel && setinact && del < inact) || + (dst_key_gettime(key, DST_TIME_INACTIVE, + &previnact) == ISC_R_SUCCESS && +- setdel && !setinact && del < previnact) || ++ setdel && !setinact && !unsetinact && del < previnact) || + (dst_key_gettime(key, DST_TIME_DELETE, + &prevdel) == ISC_R_SUCCESS && +- setinact && !setdel && prevdel < inact) || +- (!setdel && !setinact && prevdel < previnact)) ++ setinact && !setdel && !unsetdel && prevdel < inact) || ++ (!setdel && !unsetdel && !setinact && !unsetinact && ++ prevdel < previnact)) + fprintf(stderr, "%s: warning: Key is scheduled to " + "be deleted before it is\n\t" + "scheduled to be inactive.\n", +diff --git a/bin/python/.gitignore b/bin/python/.gitignore +new file mode 100644 +index 0000000..2e6963d +--- /dev/null ++++ b/bin/python/.gitignore +@@ -0,0 +1,7 @@ ++dnssec-checkds ++dnssec-checkds.py ++dnssec-coverage ++dnssec-coverage.py ++dnssec-keymgr ++dnssec-keymgr.py ++*.pyc +diff --git a/bin/python/Makefile.in b/bin/python/Makefile.in +index 12695ed..1e4af9c 100644 +--- a/bin/python/Makefile.in ++++ b/bin/python/Makefile.in +@@ -12,8 +12,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + srcdir = @srcdir@ + VPATH = @srcdir@ + top_srcdir = @top_srcdir@ +@@ -22,11 +20,13 @@ top_srcdir = @top_srcdir@ + + PYTHON = @PYTHON@ + +-TARGETS = dnssec-checkds dnssec-coverage +-SRCS = dnssec-checkds.py dnssec-coverage.py ++TARGETS = dnssec-checkds dnssec-coverage dnssec-keymgr ++SRCS = dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py ++ ++SUBDIRS = isc + +-MANPAGES = dnssec-checkds.8 dnssec-coverage.8 +-HTMLPAGES = dnssec-checkds.html dnssec-coverage.html ++MANPAGES = dnssec-checkds.8 dnssec-coverage.8 dnssec-keymgr.8 ++HTMLPAGES = dnssec-checkds.html dnssec-coverage.html dnssec-keymgr.html + MANOBJS = ${MANPAGES} ${HTMLPAGES} + + @BIND9_MAKE_RULES@ +@@ -35,6 +35,10 @@ dnssec-checkds: dnssec-checkds.py + + dnssec-coverage: dnssec-coverage.py + ++dnssec-keymgr: dnssec-keymgr.py ++ cp -f dnssec-keymgr.py dnssec-keymgr ++ chmod +x dnssec-keymgr ++ + doc man:: ${MANOBJS} + + docclean manclean maintainer-clean:: +@@ -47,11 +51,13 @@ installdirs: + install:: ${TARGETS} installdirs + ${INSTALL_PROGRAM} dnssec-checkds@EXEEXT@ ${DESTDIR}${sbindir} + ${INSTALL_PROGRAM} dnssec-coverage@EXEEXT@ ${DESTDIR}${sbindir} ++ ${INSTALL_PROGRAM} dnssec-keymgr@EXEEXT@ ${DESTDIR}${sbindir} + ${INSTALL_DATA} ${srcdir}/dnssec-checkds.8 ${DESTDIR}${mandir}/man8 + ${INSTALL_DATA} ${srcdir}/dnssec-coverage.8 ${DESTDIR}${mandir}/man8 ++ ${INSTALL_DATA} ${srcdir}/dnssec-keymgr.8 ${DESTDIR}${mandir}/man8 + + clean distclean:: + rm -f ${TARGETS} + + distclean:: +- rm -f dnssec-checkds.py dnssec-coverage.py ++ rm -f dnssec-checkds.py dnssec-coverage.py dnssec-keymgr.py +diff --git a/bin/python/dnssec-keymgr.docbook b/bin/python/dnssec-keymgr.docbook +new file mode 100644 +index 0000000..2cccb49 +--- /dev/null ++++ b/bin/python/dnssec-keymgr.docbook +@@ -0,0 +1,354 @@ ++ ++ ++ ++ ++ ++ 2016-04-03 ++ ++ ++ ISC ++ Internet Systems Consortium, Inc. ++ ++ ++ ++ dnssec-keymgr ++ 8 ++ BIND9 ++ ++ ++ ++ dnssec-keymgr ++ Ensures correct DNSKEY coverage for a zone based on a defined policy ++ ++ ++ ++ ++ 2016 ++ Internet Systems Consortium, Inc. ("ISC") ++ ++ ++ ++ ++ ++ dnssec-keymgr ++ ++ ++ ++ ++ ++ ++ ++ zone ++ ++ ++ ++ DESCRIPTION ++ ++ dnssec-keymgr ++ is a high level Python wrapper to facilitate the key rollover ++ process for zones handled by BIND. It uses the BIND commands ++ for manipulating DNSSEC key metadata: ++ dnssec-keygen and ++ dnssec-settime. ++ ++ ++ DNSSEC policy can be read from a configuration file (default ++ /etc/dnssec.policy), from which the key ++ parameters, publication and rollover schedule, and desired ++ coverage duration for any given zone can be determined. This ++ file may be used to define individual DNSSEC policies on a ++ per-zone basis, or to set a default policy used for all zones. ++ ++ ++ When dnssec-keymgr runs, it examines the DNSSEC ++ keys for one or more zones, comparing their timing metadata against ++ the policies for those zones. If key settings do not conform to the ++ DNSSEC policy (for example, because the policy has been changed), ++ they are automatically corrected. ++ ++ ++ A zone policy can specify a duration for which we want to ++ ensure the key correctness (). It can ++ also specify a rollover period (). ++ If policy indicates that a key should roll over before the ++ coverage period ends, then a successor key will automatically be ++ created and added to the end of the key series. ++ ++ ++ If zones are specified on the command line, ++ dnssec-keymgr will examine only those zones. ++ If a specified zone does not already have keys in place, then ++ keys will be generated for it according to policy. ++ ++ ++ If zones are not specified on the command ++ line, then dnssec-keymgr will search the ++ key directory (either the current working directory or the directory ++ set by the option), and check the keys for ++ all the zones represented in the directory. ++ ++ ++ It is expected that this tool will be run automatically and ++ unattended (for example, by cron). ++ ++ ++ ++ OPTIONS ++ ++ ++ -K directory ++ ++ ++ Sets the directory in which keys can be found. Defaults to the ++ current working directory. ++ ++ ++ ++ ++ ++ -c file ++ ++ ++ If is specified, then the DNSSEC ++ policy is read from . (If not ++ specified, then the policy is read from ++ /etc/policy.conf; if that file ++ doesn't exist, a built-in global default policy is used.) ++ ++ ++ ++ ++ ++ -f ++ ++ ++ Force: allow updating of key events even if they are ++ already in the past. This is not recommended for use with ++ zones in which keys have already been published. However, ++ if a set of keys has been generated all of which have ++ publication and activation dates in the past, but the ++ keys have not been published in a zone as yet, then this ++ option can be used to clean them up and turn them into a ++ proper series of keys with appropriate rollover intervals. ++ ++ ++ ++ ++ ++ -q ++ ++ ++ Quiet: suppress printing of dnssec-keygen ++ and dnssec-settime. ++ ++ ++ ++ ++ ++ -k ++ ++ ++ Only apply policies to KSK keys. ++ ++ ++ ++ ++ ++ -z ++ ++ ++ Only apply policies to ZSK keys. ++ ++ ++ ++ ++ ++ -g keygen path ++ ++ ++ Specifies a path to a dnssec-keygen binary. ++ Used for testing. ++ ++ ++ ++ ++ ++ -s settime path ++ ++ ++ Specifies a path to a dnssec-settime binary. ++ Used for testing. ++ ++ ++ ++ ++ ++ ++ POLICY CONFIGURATION ++ ++ The policy.conf file can specify three kinds ++ of policies: ++ ++ ++ ++ Policy classes ++ () ++ can be inherited by zone policies or other policy classes; these ++ can be used to create sets of different security profiles. For ++ example, a policy class normal might specify ++ 1024-bit key sizes, but a class extra might ++ specify 2048 bits instead; extra would be ++ used for zones that had unusually high security needs. ++ ++ ++ Algorithm policies: ++ ( ) ++ override default per-algorithm settings. For example, by default, ++ RSASHA256 keys use 2048-bit key sizes for both KSK and ZSK. This ++ can be modified using algorithm-policy, and the ++ new key sizes would then be used for any key of type RSASHA256. ++ ++ ++ Zone policies: ++ ( ) ++ set policy for a single zone by name. A zone policy can inherit ++ a policy class by including a option. ++ ++ ++ ++ Options that can be specified in policies: ++ ++ ++ ++ directory ++ ++ Specifies the directory in which keys should be stored. ++ ++ ++ ++ algorithm ++ ++ The key algorithm. If no policy is defined, the default is ++ RSASHA256. ++ ++ ++ ++ keyttl ++ ++ The key TTL. If no policy is defined, the default is one hour. ++ ++ ++ ++ coverage ++ ++ The length of time to ensure that keys will be correct; no action ++ will be taken to create new keys to be activated after this time. ++ This can be represented as a number of seconds, or as a duration using ++ human-readable units (examples: "1y" or "6 months"). ++ A default value for this option can be set in algorithm policies ++ as well as in policy classes or zone policies. ++ If no policy is configured, the default is six months. ++ ++ ++ ++ key-size ++ ++ Specifies the number of bits to use in creating keys. ++ Takes two arguments: keytype (eihter "zsk" or "ksk") and size. ++ A default value for this option can be set in algorithm policies ++ as well as in policy classes or zone policies. If no policy is ++ configured, the default is 1024 bits for DSA keys and 2048 for ++ RSA. ++ ++ ++ ++ roll-period ++ ++ How frequently keys should be rolled over. ++ Takes two arguments: keytype (eihter "zsk" or "ksk") and a duration. ++ A default value for this option can be set in algorithm policies ++ as well as in policy classes or zone policies. If no policy is ++ configured, the default is one year for ZSK's. KSK's do not ++ roll over by default. ++ ++ ++ ++ pre-publish ++ ++ How long before activation a key should be published. Note: If ++ is not set, this value is ignored. ++ Takes two arguments: keytype (either "zsk" or "ksk") and a duration. ++ A default value for this option can be set in algorithm policies ++ as well as in policy classes or zone policies. The default is ++ one month. ++ ++ ++ ++ post-publish ++ ++ How long after inactivation a key should be deleted from the zone. ++ Note: If is not set, this value is ignored. ++ Takes two arguments: keytype (eihter "zsk" or "ksk") and a duration. ++ A default value for this option can be set in algorithm policies ++ as well as in policy classes or zone policies. The default is one ++ month. ++ ++ ++ ++ standby ++ ++ Not yet implemented. ++ ++ ++ ++ ++ ++ REMAINING WORK ++ ++ ++ Enable scheduling of KSK rollovers using the ++ and options to ++ dnssec-keygen and ++ dnssec-settime. Check the parent zone ++ (as in dnssec-checkds) to determine when it's ++ safe for the key to roll. ++ ++ ++ Allow configuration of standby keys and use of the REVOKE bit, ++ for keys that use RFC 5011 semantics. ++ ++ ++ ++ ++ SEE ALSO ++ ++ ++ dnssec-coverage8 ++ , ++ ++ dnssec-keygen8 ++ , ++ ++ dnssec-settime8 ++ , ++ ++ dnssec-checkds8 ++ ++ ++ ++ ++ +diff --git a/bin/python/dnssec-keymgr.py.in b/bin/python/dnssec-keymgr.py.in +new file mode 100644 +index 0000000..23d563d +--- /dev/null ++++ b/bin/python/dnssec-keymgr.py.in +@@ -0,0 +1,27 @@ ++#!@PYTHON@ ++############################################################################ ++# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import os ++import sys ++ ++sys.path.insert(0, os.path.dirname(sys.argv[0])) ++sys.path.insert(1, os.path.join('@prefix@', 'lib')) ++ ++import isc.keymgr ++ ++if __name__ == "__main__": ++ isc.keymgr.main() +diff --git a/bin/python/isc/.gitignore b/bin/python/isc/.gitignore +new file mode 100644 +index 0000000..84554b8 +--- /dev/null ++++ b/bin/python/isc/.gitignore +@@ -0,0 +1,3 @@ ++utils.py ++parsetab.py ++parser.out +diff --git a/bin/python/isc/Makefile.in b/bin/python/isc/Makefile.in +new file mode 100644 +index 0000000..425d054 +--- /dev/null ++++ b/bin/python/isc/Makefile.in +@@ -0,0 +1,67 @@ ++# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++srcdir = @srcdir@ ++VPATH = @srcdir@ ++top_srcdir = @top_srcdir@ ++ ++@BIND9_MAKE_INCLUDES@ ++ ++SUBDIRS = tests ++ ++PYTHON = @PYTHON@ ++ ++PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \ ++ keyevent.py keyzone.py policy.py ++TARGETS = parsetab.py parsetab.pyc \ ++ __init__.pyc dnskey.pyc eventlist.py keydict.py \ ++ keyevent.pyc keyzone.pyc policy.pyc ++ ++@BIND9_MAKE_RULES@ ++ ++%.pyc: %.py ++ $(PYTHON) -m compileall . ++ ++parsetab.py parsetab.pyc: policy.py ++ $(PYTHON) policy.py parse /dev/null > /dev/null ++ $(PYTHON) -m parsetab ++ ++installdirs: ++ $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir}/isc ++ ++install:: ${PYSRCS} installdirs ++ ${INSTALL_SCRIPT} __init__.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} __init__.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} dnskey.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} dnskey.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} eventlist.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} eventlist.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keydict.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keydict.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keyevent.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keyevent.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keyzone.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} keyzone.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} policy.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} policy.pyc ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} parsetab.py ${DESTDIR}${libdir} ++ ${INSTALL_SCRIPT} parsetab.pyc ${DESTDIR}${libdir} ++ ++check test: subdirs ++ ++clean distclean:: ++ rm -f *.pyc parser.out parsetab.py ++ ++distclean:: ++ rm -Rf utils.py +\ No newline at end of file +diff --git a/bin/python/isc/__init__.py b/bin/python/isc/__init__.py +new file mode 100644 +index 0000000..0d79f35 +--- /dev/null ++++ b/bin/python/isc/__init__.py +@@ -0,0 +1,25 @@ ++# Copyright (C) 2015 Internet Systems Consortium. ++# ++# Permission to use, copy, modify, and distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM ++# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL ++# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ++# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING ++# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, ++# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION ++# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ ++__all__ = ['dnskey', 'eventlist', 'keydict', 'keyevent', 'keyseries', ++ 'keyzone', 'policy', 'parsetab', 'utils'] ++from isc.dnskey import * ++from isc.eventlist import * ++from isc.keydict import * ++from isc.keyevent import * ++from isc.keyseries import * ++from isc.keyzone import * ++from isc.policy import * ++from isc.utils import * +diff --git a/bin/python/isc/checkds.py b/bin/python/isc/checkds.py +new file mode 100644 +index 0000000..64ca12e +--- /dev/null ++++ b/bin/python/isc/checkds.py +@@ -0,0 +1,189 @@ ++############################################################################ ++# Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import argparse ++import os ++import sys ++from subprocess import Popen, PIPE ++ ++from isc.utils import prefix,version ++ ++prog = 'dnssec-checkds' ++ ++ ++############################################################################ ++# SECRR class: ++# Class for DS/DLV resource record ++############################################################################ ++class SECRR: ++ hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384'} ++ rrname = '' ++ rrclass = 'IN' ++ keyid = None ++ keyalg = None ++ hashalg = None ++ digest = '' ++ ttl = 0 ++ ++ def __init__(self, rrtext, dlvname = None): ++ if not rrtext: ++ raise Exception ++ ++ fields = rrtext.split() ++ if len(fields) < 7: ++ raise Exception ++ ++ if dlvname: ++ self.rrtype = "DLV" ++ self.dlvname = dlvname.lower() ++ parent = fields[0].lower().strip('.').split('.') ++ parent.reverse() ++ dlv = dlvname.split('.') ++ dlv.reverse() ++ while len(dlv) != 0 and len(parent) != 0 and parent[0] == dlv[0]: ++ parent = parent[1:] ++ dlv = dlv[1:] ++ if dlv: ++ raise Exception ++ parent.reverse() ++ self.parent = '.'.join(parent) ++ self.rrname = self.parent + '.' + self.dlvname + '.' ++ else: ++ self.rrtype = "DS" ++ self.rrname = fields[0].lower() ++ ++ fields = fields[1:] ++ if fields[0].upper() in ['IN', 'CH', 'HS']: ++ self.rrclass = fields[0].upper() ++ fields = fields[1:] ++ else: ++ self.ttl = int(fields[0]) ++ self.rrclass = fields[1].upper() ++ fields = fields[2:] ++ ++ if fields[0].upper() != self.rrtype: ++ raise Exception ++ ++ self.keyid, self.keyalg, self.hashalg = map(int, fields[1:4]) ++ self.digest = ''.join(fields[4:]).upper() ++ ++ def __repr__(self): ++ return '%s %s %s %d %d %d %s' % \ ++ (self.rrname, self.rrclass, self.rrtype, ++ self.keyid, self.keyalg, self.hashalg, self.digest) ++ ++ def __eq__(self, other): ++ return self.__repr__() == other.__repr__() ++ ++ ++############################################################################ ++# check: ++# Fetch DS/DLV RRset for the given zone from the DNS; fetch DNSKEY ++# RRset from the masterfile if specified, or from DNS if not. ++# Generate a set of expected DS/DLV records from the DNSKEY RRset, ++# and report on congruency. ++############################################################################ ++def check(zone, args, masterfile=None, lookaside=None): ++ rrlist = [] ++ cmd = [args.dig, "+noall", "+answer", "-t", "dlv" if lookaside else "ds", ++ "-q", zone + "." + lookaside if lookaside else zone] ++ fp, _ = Popen(cmd, stdout=PIPE).communicate() ++ ++ for line in fp.splitlines(): ++ rrlist.append(SECRR(line, lookaside)) ++ rrlist = sorted(rrlist, key=lambda rr: (rr.keyid, rr.keyalg, rr.hashalg)) ++ ++ klist = [] ++ ++ if masterfile: ++ cmd = [args.dsfromkey, "-f", masterfile] ++ if lookaside: ++ cmd += ["-l", lookaside] ++ cmd.append(zone) ++ fp, _ = Popen(cmd, stdout=PIPE).communicate() ++ else: ++ intods, _ = Popen([args.dig, "+noall", "+answer", "-t", "dnskey", ++ "-q", zone], stdout=PIPE).communicate() ++ cmd = [args.dsfromkey, "-f", "-"] ++ if lookaside: ++ cmd += ["-l", lookaside] ++ cmd.append(zone) ++ fp, _ = Popen(cmd, stdin=PIPE, stdout=PIPE).communicate(intods) ++ ++ for line in fp.splitlines(): ++ klist.append(SECRR(line, lookaside)) ++ ++ if len(klist) < 1: ++ print ("No DNSKEY records found in zone apex") ++ return False ++ ++ found = False ++ for rr in klist: ++ if rr in rrlist: ++ print ("%s for KSK %s/%03d/%05d (%s) found in parent" % ++ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg, ++ rr.keyid, SECRR.hashalgs[rr.hashalg])) ++ found = True ++ else: ++ print ("%s for KSK %s/%03d/%05d (%s) missing from parent" % ++ (rr.rrtype, rr.rrname.strip('.'), rr.keyalg, ++ rr.keyid, SECRR.hashalgs[rr.hashalg])) ++ ++ if not found: ++ print ("No %s records were found for any DNSKEY" % ("DLV" if lookaside else "DS")) ++ ++ return found ++ ++############################################################################ ++# parse_args: ++# Read command line arguments, set global 'args' structure ++############################################################################ ++def parse_args(): ++ parser = argparse.ArgumentParser(description=prog + ': checks DS coverage') ++ ++ bindir = 'bin' ++ sbindir = 'bin' if os.name == 'nt' else 'sbin' ++ ++ parser.add_argument('zone', type=str, help='zone to check') ++ parser.add_argument('-f', '--file', dest='masterfile', type=str, ++ help='zone master file') ++ parser.add_argument('-l', '--lookaside', dest='lookaside', type=str, ++ help='DLV lookaside zone') ++ parser.add_argument('-d', '--dig', dest='dig', ++ default=os.path.join(prefix(bindir), 'dig'), ++ type=str, help='path to \'dig\'') ++ parser.add_argument('-D', '--dsfromkey', dest='dsfromkey', ++ default=os.path.join(prefix(sbindir), ++ 'dnssec-dsfromkey'), ++ type=str, help='path to \'dig\'') ++ parser.add_argument('-v', '--version', action='version', ++ version=version) ++ args = parser.parse_args() ++ ++ args.zone = args.zone.strip('.') ++ if args.lookaside: ++ args.lookaside = args.lookaside.strip('.') ++ ++ return args ++ ++ ++############################################################################ ++# Main ++############################################################################ ++def main(): ++ args = parse_args() ++ found = check(args.zone, args, args.masterfile, args.lookaside) ++ exit(0 if found else 1) +diff --git a/bin/python/isc/coverage.py b/bin/python/isc/coverage.py +new file mode 100644 +index 0000000..c9e8959 +--- /dev/null ++++ b/bin/python/isc/coverage.py +@@ -0,0 +1,292 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++from __future__ import print_function ++import os ++import sys ++import argparse ++import glob ++import re ++import time ++import calendar ++import pprint ++from collections import defaultdict ++ ++prog = 'dnssec-coverage' ++ ++from isc import * ++from isc.utils import prefix ++ ++ ++############################################################################ ++# print a fatal error and exit ++############################################################################ ++def fatal(*args, **kwargs): ++ print(*args, **kwargs) ++ sys.exit(1) ++ ++ ++############################################################################ ++# output: ++############################################################################ ++_firstline = True ++def output(*args, **kwargs): ++ """output text, adding a vertical space this is *not* the first ++ first section being printed since a call to vreset()""" ++ global _firstline ++ if 'skip' in kwargs: ++ skip = kwargs['skip'] ++ kwargs.pop('skip', None) ++ else: ++ skip = True ++ if _firstline: ++ _firstline = False ++ elif skip: ++ print('') ++ if args: ++ print(*args, **kwargs) ++ ++ ++def vreset(): ++ """reset vertical spacing""" ++ global _firstline ++ _firstline = True ++ ++ ++############################################################################ ++# parse_time ++############################################################################ ++def parse_time(s): ++ """ convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds ++ :param s: String with some text representing a time interval ++ :return: Integer with the number of seconds in the time interval ++ """ ++ s = s.strip() ++ ++ # if s is an integer, we're done already ++ try: ++ return int(s) ++ except ValueError: ++ pass ++ ++ # try to parse as a number with a suffix indicating unit of time ++ r = re.compile('([0-9][0-9]*)\s*([A-Za-z]*)') ++ m = r.match(s) ++ if not m: ++ raise ValueError("Cannot parse %s" % s) ++ n, unit = m.groups() ++ n = int(n) ++ unit = unit.lower() ++ if unit.startswith('y'): ++ return n * 31536000 ++ elif unit.startswith('mo'): ++ return n * 2592000 ++ elif unit.startswith('w'): ++ return n * 604800 ++ elif unit.startswith('d'): ++ return n * 86400 ++ elif unit.startswith('h'): ++ return n * 3600 ++ elif unit.startswith('mi'): ++ return n * 60 ++ elif unit.startswith('s'): ++ return n ++ else: ++ raise ValueError("Invalid suffix %s" % unit) ++ ++ ++############################################################################ ++# set_path: ++############################################################################ ++def set_path(command, default=None): ++ """ find the location of a specified command. if a default is supplied ++ and it works, we use it; otherwise we search PATH for a match. ++ :param command: string with a command to look for in the path ++ :param default: default location to use ++ :return: detected location for the desired command ++ """ ++ ++ fpath = default ++ if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK): ++ path = os.environ["PATH"] ++ if not path: ++ path = os.path.defpath ++ for directory in path.split(os.pathsep): ++ fpath = os.path.join(directory, command) ++ if os.path.isfile(fpath) and os.access(fpath, os.X_OK): ++ break ++ fpath = None ++ ++ return fpath ++ ++ ++############################################################################ ++# parse_args: ++############################################################################ ++def parse_args(): ++ """Read command line arguments, set global 'args' structure""" ++ compilezone = set_path('named-compilezone', ++ os.path.join(prefix('sbin'), 'named-compilezone')) ++ ++ parser = argparse.ArgumentParser(description=prog + ': checks future ' + ++ 'DNSKEY coverage for a zone') ++ ++ parser.add_argument('zone', type=str, nargs='*', default=None, ++ help='zone(s) to check' + ++ '(default: all zones in the directory)') ++ parser.add_argument('-K', dest='path', default='.', type=str, ++ help='a directory containing keys to process', ++ metavar='dir') ++ parser.add_argument('-f', dest='filename', type=str, ++ help='zone master file', metavar='file') ++ parser.add_argument('-m', dest='maxttl', type=str, ++ help='the longest TTL in the zone(s)', ++ metavar='time') ++ parser.add_argument('-d', dest='keyttl', type=str, ++ help='the DNSKEY TTL', metavar='time') ++ parser.add_argument('-r', dest='resign', default='1944000', ++ type=str, help='the RRSIG refresh interval ' ++ 'in seconds [default: 22.5 days]', ++ metavar='time') ++ parser.add_argument('-c', dest='compilezone', ++ default=compilezone, type=str, ++ help='path to \'named-compilezone\'', ++ metavar='path') ++ parser.add_argument('-l', dest='checklimit', ++ type=str, default='0', ++ help='Length of time to check for ' ++ 'DNSSEC coverage [default: 0 (unlimited)]', ++ metavar='time') ++ parser.add_argument('-z', dest='no_ksk', ++ action='store_true', default=False, ++ help='Only check zone-signing keys (ZSKs)') ++ parser.add_argument('-k', dest='no_zsk', ++ action='store_true', default=False, ++ help='Only check key-signing keys (KSKs)') ++ parser.add_argument('-D', '--debug', dest='debug_mode', ++ action='store_true', default=False, ++ help='Turn on debugging output') ++ parser.add_argument('-v', '--version', action='version', ++ version=utils.version) ++ ++ args = parser.parse_args() ++ ++ if args.no_zsk and args.no_ksk: ++ fatal("ERROR: -z and -k cannot be used together.") ++ elif args.no_zsk or args.no_ksk: ++ args.keytype = "KSK" if args.no_zsk else "ZSK" ++ else: ++ args.keytype = None ++ ++ if args.filename and len(args.zone) > 1: ++ fatal("ERROR: -f can only be used with one zone.") ++ ++ # convert from time arguments to seconds ++ try: ++ if args.maxttl: ++ m = parse_time(args.maxttl) ++ args.maxttl = m ++ except ValueError: ++ pass ++ ++ try: ++ if args.keyttl: ++ k = parse_time(args.keyttl) ++ args.keyttl = k ++ except ValueError: ++ pass ++ ++ try: ++ if args.resign: ++ r = parse_time(args.resign) ++ args.resign = r ++ except ValueError: ++ pass ++ ++ try: ++ if args.checklimit: ++ lim = args.checklimit ++ r = parse_time(args.checklimit) ++ if r == 0: ++ args.checklimit = None ++ else: ++ args.checklimit = time.time() + r ++ except ValueError: ++ pass ++ ++ # if we've got the values we need from the command line, stop now ++ if args.maxttl and args.keyttl: ++ return args ++ ++ # load keyttl and maxttl data from zonefile ++ if args.zone and args.filename: ++ try: ++ zone = keyzone(args.zone[0], args.filename, args.compilezone) ++ args.maxttl = args.maxttl or zone.maxttl ++ args.keyttl = args.maxttl or zone.keyttl ++ except Exception as e: ++ print("Unable to load zone data from %s: " % args.filename, e) ++ ++ if not args.maxttl: ++ output("WARNING: Maximum TTL value was not specified. Using 1 week\n" ++ "\t (604800 seconds); re-run with the -m option to get more\n" ++ "\t accurate results.") ++ args.maxttl = 604800 ++ ++ return args ++ ++############################################################################ ++# Main ++############################################################################ ++def main(): ++ args = parse_args() ++ ++ print("PHASE 1--Loading keys to check for internal timing problems") ++ ++ try: ++ kd = keydict(path=args.path, zone=args.zone, keyttl=args.keyttl) ++ except Exception as e: ++ fatal('ERROR: Unable to build key dictionary: ' + str(e)) ++ ++ for key in kd: ++ key.check_prepub(output) ++ if key.sep: ++ key.check_postpub(output) ++ else: ++ key.check_postpub(output, args.maxttl + args.resign) ++ ++ output("PHASE 2--Scanning future key events for coverage failures") ++ vreset() ++ ++ try: ++ elist = eventlist(kd) ++ except Exception as e: ++ fatal('ERROR: Unable to build event list: ' + str(e)) ++ ++ errors = False ++ if not args.zone: ++ if not elist.coverage(None, args.keytype, args.checklimit, output): ++ errors = True ++ else: ++ for zone in args.zone: ++ try: ++ if not elist.coverage(zone, args.keytype, ++ args.checklimit, output): ++ errors = True ++ except: ++ output('ERROR: Coverage check failed for zone ' + zone) ++ ++ sys.exit(1 if errors else 0) +diff --git a/bin/python/isc/dnskey.py b/bin/python/isc/dnskey.py +new file mode 100644 +index 0000000..f1559e7 +--- /dev/null ++++ b/bin/python/isc/dnskey.py +@@ -0,0 +1,504 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import os ++import time ++import calendar ++from subprocess import Popen, PIPE ++ ++######################################################################## ++# Class dnskey ++######################################################################## ++class TimePast(Exception): ++ def __init__(self, key, prop, value): ++ super(TimePast, self).__init__('%s time for key %s (%d) is already past' ++ % (prop, key, value)) ++ ++class dnskey: ++ """An individual DNSSEC key. Identified by path, name, algorithm, keyid. ++ Contains a dictionary of metadata events.""" ++ ++ _PROPS = ('Created', 'Publish', 'Activate', 'Inactive', 'Delete', ++ 'Revoke', 'DSPublish', 'SyncPublish', 'SyncDelete') ++ _OPTS = (None, '-P', '-A', '-I', '-D', '-R', None, '-Psync', '-Dsync') ++ ++ _ALGNAMES = (None, 'RSAMD5', 'DH', 'DSA', 'ECC', 'RSASHA1', ++ 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None, ++ 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256', ++ 'ECDSAP384SHA384') ++ ++ def __init__(self, key, directory=None, keyttl=None): ++ # this makes it possible to use algname as a class or instance method ++ if isinstance(key, tuple) and len(key) == 3: ++ self._dir = directory or '.' ++ (name, alg, keyid) = key ++ self.fromtuple(name, alg, keyid, keyttl) ++ ++ self._dir = directory or os.path.dirname(key) or '.' ++ key = os.path.basename(key) ++ ++ (name, alg, keyid) = key.split('+') ++ name = name[1:-1] ++ alg = int(alg) ++ keyid = int(keyid.split('.')[0]) ++ self.fromtuple(name, alg, keyid, keyttl) ++ ++ def fromtuple(self, name, alg, keyid, keyttl): ++ if name.endswith('.'): ++ fullname = name ++ name = name.rstrip('.') ++ else: ++ fullname = name + '.' ++ ++ keystr = "K%s+%03d+%05d" % (fullname, alg, keyid) ++ key_file = self._dir + (self._dir and os.sep or '') + keystr + ".key" ++ private_file = (self._dir + (self._dir and os.sep or '') + ++ keystr + ".private") ++ ++ self.keystr = keystr ++ ++ self.name = name ++ self.alg = int(alg) ++ self.keyid = int(keyid) ++ self.fullname = fullname ++ ++ kfp = open(key_file, "r") ++ for line in kfp: ++ if line[0] == ';': ++ continue ++ tokens = line.split() ++ if not tokens: ++ continue ++ ++ if tokens[1].lower() in ('in', 'ch', 'hs'): ++ septoken = 3 ++ self.ttl = keyttl ++ else: ++ septoken = 4 ++ self.ttl = int(tokens[1]) if not keyttl else keyttl ++ ++ if (int(tokens[septoken]) & 0x1) == 1: ++ self.sep = True ++ else: ++ self.sep = False ++ kfp.close() ++ ++ pfp = open(private_file, "rU") ++ ++ self.metadata = dict() ++ self._changed = dict() ++ self._delete = dict() ++ self._times = dict() ++ self._fmttime = dict() ++ self._timestamps = dict() ++ self._original = dict() ++ self._origttl = None ++ ++ for line in pfp: ++ line = line.strip() ++ if not line or line[0] in ('!#'): ++ continue ++ punctuation = [line.find(c) for c in ':= '] + [len(line)] ++ found = min([pos for pos in punctuation if pos != -1]) ++ name = line[:found].rstrip() ++ value = line[found:].lstrip(":= ").rstrip() ++ self.metadata[name] = value ++ ++ for prop in dnskey._PROPS: ++ self._changed[prop] = False ++ if prop in self.metadata: ++ t = self.parsetime(self.metadata[prop]) ++ self._times[prop] = t ++ self._fmttime[prop] = self.formattime(t) ++ self._timestamps[prop] = self.epochfromtime(t) ++ self._original[prop] = self._timestamps[prop] ++ else: ++ self._times[prop] = None ++ self._fmttime[prop] = None ++ self._timestamps[prop] = None ++ self._original[prop] = None ++ ++ pfp.close() ++ ++ def commit(self, settime_bin, **kwargs): ++ quiet = kwargs.get('quiet', False) ++ cmd = [] ++ first = True ++ ++ if self._origttl is not None: ++ cmd += ["-L", str(self.ttl)] ++ ++ for prop, opt in zip(dnskey._PROPS, dnskey._OPTS): ++ if not opt or not self._changed[prop]: ++ continue ++ ++ delete = False ++ if prop in self._delete and self._delete[prop]: ++ delete = True ++ ++ when = 'none' if delete else self._fmttime[prop] ++ cmd += [opt, when] ++ first = False ++ ++ if cmd: ++ fullcmd = [settime_bin, "-K", self._dir] + cmd + [self.keystr,] ++ if not quiet: ++ print('# ' + ' '.join(fullcmd)) ++ try: ++ p = Popen(fullcmd, stdout=PIPE, stderr=PIPE) ++ stdout, stderr = p.communicate() ++ if stderr: ++ raise Exception(str(stderr)) ++ except Exception as e: ++ raise Exception('unable to run %s: %s' % ++ (settime_bin, str(e))) ++ self._origttl = None ++ for prop in dnskey._PROPS: ++ self._original[prop] = self._timestamps[prop] ++ self._changed[prop] = False ++ ++ @classmethod ++ def generate(cls, keygen_bin, keys_dir, name, alg, keysize, sep, ++ ttl, publish=None, activate=None, **kwargs): ++ quiet = kwargs.get('quiet', False) ++ ++ keygen_cmd = [keygen_bin, "-q", "-K", keys_dir, "-L", str(ttl)] ++ ++ if sep: ++ keygen_cmd.append("-fk") ++ ++ if alg: ++ keygen_cmd += ["-a", alg] ++ ++ if keysize: ++ keygen_cmd += ["-b", str(keysize)] ++ ++ if publish: ++ t = dnskey.timefromepoch(publish) ++ keygen_cmd += ["-P", dnskey.formattime(t)] ++ ++ if activate: ++ t = dnskey.timefromepoch(activate) ++ keygen_cmd += ["-A", dnskey.formattime(activate)] ++ ++ keygen_cmd.append(name) ++ ++ if not quiet: ++ print('# ' + ' '.join(keygen_cmd)) ++ ++ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE) ++ stdout, stderr = p.communicate() ++ if stderr: ++ raise Exception('unable to generate key: ' + str(stderr)) ++ ++ try: ++ keystr = stdout.splitlines()[0] ++ newkey = dnskey(keystr, keys_dir, ttl) ++ return newkey ++ except Exception as e: ++ raise Exception('unable to generate key: %s' % str(e)) ++ ++ def generate_successor(self, keygen_bin, **kwargs): ++ quiet = kwargs.get('quiet', False) ++ ++ if not self.inactive(): ++ raise Exception("predecessor key %s has no inactive date" % self) ++ ++ keygen_cmd = [keygen_bin, "-q", "-K", self._dir, "-S", self.keystr] ++ ++ if self.ttl: ++ keygen_cmd += ["-L", str(self.ttl)] ++ ++ if not quiet: ++ print('# ' + ' '.join(keygen_cmd)) ++ ++ p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE) ++ stdout, stderr = p.communicate() ++ if stderr: ++ raise Exception('unable to generate key: ' + stderr) ++ ++ try: ++ keystr = stdout.splitlines()[0] ++ newkey = dnskey(keystr, self._dir, self.ttl) ++ return newkey ++ except: ++ raise Exception('unable to generate successor for key %s' % self) ++ ++ @staticmethod ++ def algstr(alg): ++ name = None ++ if alg in range(len(dnskey._ALGNAMES)): ++ name = dnskey._ALGNAMES[alg] ++ return name if name else ("%03d" % alg) ++ ++ @staticmethod ++ def algnum(alg): ++ if not alg: ++ return None ++ alg = alg.upper() ++ try: ++ return dnskey._ALGNAMES.index(alg) ++ except ValueError: ++ return None ++ ++ def algname(self, alg=None): ++ return self.algstr(alg or self.alg) ++ ++ @staticmethod ++ def timefromepoch(secs): ++ return time.gmtime(secs) ++ ++ @staticmethod ++ def parsetime(string): ++ return time.strptime(string, "%Y%m%d%H%M%S") ++ ++ @staticmethod ++ def epochfromtime(t): ++ return calendar.timegm(t) ++ ++ @staticmethod ++ def formattime(t): ++ return time.strftime("%Y%m%d%H%M%S", t) ++ ++ def setmeta(self, prop, secs, now, **kwargs): ++ force = kwargs.get('force', False) ++ ++ if self._timestamps[prop] == secs: ++ return ++ ++ if self._original[prop] is not None and \ ++ self._original[prop] < now and not force: ++ raise TimePast(self, prop, self._original[prop]) ++ ++ if secs is None: ++ self._changed[prop] = False \ ++ if self._original[prop] is None else True ++ ++ self._delete[prop] = True ++ self._timestamps[prop] = None ++ self._times[prop] = None ++ self._fmttime[prop] = None ++ return ++ ++ t = self.timefromepoch(secs) ++ self._timestamps[prop] = secs ++ self._times[prop] = t ++ self._fmttime[prop] = self.formattime(t) ++ self._changed[prop] = False if \ ++ self._original[prop] == self._timestamps[prop] else True ++ ++ def gettime(self, prop): ++ return self._times[prop] ++ ++ def getfmttime(self, prop): ++ return self._fmttime[prop] ++ ++ def gettimestamp(self, prop): ++ return self._timestamps[prop] ++ ++ def created(self): ++ return self._timestamps["Created"] ++ ++ def syncpublish(self): ++ return self._timestamps["SyncPublish"] ++ ++ def setsyncpublish(self, secs, now=time.time(), **kwargs): ++ self.setmeta("SyncPublish", secs, now, **kwargs) ++ ++ def publish(self): ++ return self._timestamps["Publish"] ++ ++ def setpublish(self, secs, now=time.time(), **kwargs): ++ self.setmeta("Publish", secs, now, **kwargs) ++ ++ def activate(self): ++ return self._timestamps["Activate"] ++ ++ def setactivate(self, secs, now=time.time(), **kwargs): ++ self.setmeta("Activate", secs, now, **kwargs) ++ ++ def revoke(self): ++ return self._timestamps["Revoke"] ++ ++ def setrevoke(self, secs, now=time.time(), **kwargs): ++ self.setmeta("Revoke", secs, now, **kwargs) ++ ++ def inactive(self): ++ return self._timestamps["Inactive"] ++ ++ def setinactive(self, secs, now=time.time(), **kwargs): ++ self.setmeta("Inactive", secs, now, **kwargs) ++ ++ def delete(self): ++ return self._timestamps["Delete"] ++ ++ def setdelete(self, secs, now=time.time(), **kwargs): ++ self.setmeta("Delete", secs, now, **kwargs) ++ ++ def syncdelete(self): ++ return self._timestamps["SyncDelete"] ++ ++ def setsyncdelete(self, secs, now=time.time(), **kwargs): ++ self.setmeta("SyncDelete", secs, now, **kwargs) ++ ++ def setttl(self, ttl): ++ if ttl is None or self.ttl == ttl: ++ return ++ elif self._origttl is None: ++ self._origttl = self.ttl ++ self.ttl = ttl ++ elif self._origttl == ttl: ++ self._origttl = None ++ self.ttl = ttl ++ else: ++ self.ttl = ttl ++ ++ def keytype(self): ++ return ("KSK" if self.sep else "ZSK") ++ ++ def __str__(self): ++ return ("%s/%s/%05d" ++ % (self.name, self.algname(), self.keyid)) ++ ++ def __repr__(self): ++ return ("%s/%s/%05d (%s)" ++ % (self.name, self.algname(), self.keyid, ++ ("KSK" if self.sep else "ZSK"))) ++ ++ def date(self): ++ return (self.activate() or self.publish() or self.created()) ++ ++ # keys are sorted first by zone name, then by algorithm. within ++ # the same name/algorithm, they are sorted according to their ++ # 'date' value: the activation date if set, OR the publication ++ # if set, OR the creation date. ++ def __lt__(self, other): ++ if self.name != other.name: ++ return self.name < other.name ++ if self.alg != other.alg: ++ return self.alg < other.alg ++ return self.date() < other.date() ++ ++ def check_prepub(self, output=None): ++ def noop(*args, **kwargs): pass ++ if not output: ++ output = noop ++ ++ now = int(time.time()) ++ a = self.activate() ++ p = self.publish() ++ ++ if not a: ++ return False ++ ++ if not p: ++ if a > now: ++ output("WARNING: Key %s is scheduled for\n" ++ "\t activation but not for publication." ++ % repr(self)) ++ return False ++ ++ if p <= now and a <= now: ++ return True ++ ++ if p == a: ++ output("WARNING: %s is scheduled to be\n" ++ "\t published and activated at the same time. This\n" ++ "\t could result in a coverage gap if the zone was\n" ++ "\t previously signed. Activation should be at least\n" ++ "\t %s after publication." ++ % (repr(self), ++ dnskey.duration(self.ttl) or 'one DNSKEY TTL')) ++ return True ++ ++ if a < p: ++ output("WARNING: Key %s is active before it is published" ++ % repr(self)) ++ return False ++ ++ if self.ttl is not None and a - p < self.ttl: ++ output("WARNING: Key %s is activated too soon\n" ++ "\t after publication; this could result in coverage \n" ++ "\t gaps due to resolver caches containing old data.\n" ++ "\t Activation should be at least %s after\n" ++ "\t publication." ++ % (repr(self), ++ dnskey.duration(self.ttl) or 'one DNSKEY TTL')) ++ return False ++ ++ return True ++ ++ def check_postpub(self, output = None, timespan = None): ++ def noop(*args, **kwargs): pass ++ if output is None: ++ output = noop ++ ++ if timespan is None: ++ timespan = self.ttl ++ ++ now = time.time() ++ d = self.delete() ++ i = self.inactive() ++ ++ if not d: ++ return False ++ ++ if not i: ++ if d > now: ++ output("WARNING: Key %s is scheduled for\n" ++ "\t deletion but not for inactivation." % repr(self)) ++ return False ++ ++ if d < now and i < now: ++ return True ++ ++ if d < i: ++ output("WARNING: Key %s is scheduled for\n" ++ "\t deletion before inactivation." ++ % repr(self)) ++ return False ++ ++ if d - i < timespan: ++ output("WARNING: Key %s scheduled for\n" ++ "\t deletion too soon after deactivation; this may \n" ++ "\t result in coverage gaps due to resolver caches\n" ++ "\t containing old data. Deletion should be at least\n" ++ "\t %s after inactivation." ++ % (repr(self), dnskey.duration(timespan))) ++ return False ++ ++ return True ++ ++ @staticmethod ++ def duration(secs): ++ if not secs: ++ return None ++ ++ units = [("year", 60*60*24*365), ++ ("month", 60*60*24*30), ++ ("day", 60*60*24), ++ ("hour", 60*60), ++ ("minute", 60), ++ ("second", 1)] ++ ++ output = [] ++ for unit in units: ++ v, secs = secs // unit[1], secs % unit[1] ++ if v > 0: ++ output.append("%d %s%s" % (v, unit[0], "s" if v > 1 else "")) ++ ++ return ", ".join(output) ++ +diff --git a/bin/python/isc/eventlist.py b/bin/python/isc/eventlist.py +new file mode 100644 +index 0000000..4c91368 +--- /dev/null ++++ b/bin/python/isc/eventlist.py +@@ -0,0 +1,171 @@ ++############################################################################ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++from collections import defaultdict ++from .dnskey import * ++from .keydict import * ++from .keyevent import * ++ ++ ++class eventlist: ++ _K = defaultdict(lambda: defaultdict(list)) ++ _Z = defaultdict(lambda: defaultdict(list)) ++ _zones = set() ++ _kdict = None ++ ++ def __init__(self, kdict): ++ properties = ["SyncPublish", "Publish", "SyncDelete", ++ "Activate", "Inactive", "Delete"] ++ self._kdict = kdict ++ for zone in kdict.zones(): ++ self._zones.add(zone) ++ for alg, keys in kdict[zone].items(): ++ for k in keys.values(): ++ for prop in properties: ++ t = k.gettime(prop) ++ if not t: ++ continue ++ e = keyevent(prop, k, t) ++ if k.sep: ++ self._K[zone][alg].append(e) ++ else: ++ self._Z[zone][alg].append(e) ++ ++ self._K[zone][alg] = sorted(self._K[zone][alg], ++ key=lambda event: event.when) ++ self._Z[zone][alg] = sorted(self._Z[zone][alg], ++ key=lambda event: event.when) ++ ++ # scan events per zone, algorithm, and key type, in order of ++ # occurrance, noting inconsistent states when found ++ def coverage(self, zone, keytype, until, output = None): ++ def noop(*args, **kwargs): pass ++ if not output: ++ output = noop ++ ++ no_zsk = True if (keytype and keytype == "KSK") else False ++ no_ksk = True if (keytype and keytype == "ZSK") else False ++ kok = zok = True ++ found = False ++ ++ if zone and not zone in self._zones: ++ output("ERROR: No key events found for %s" % zone) ++ return False ++ ++ if zone: ++ found = True ++ if not no_ksk: ++ kok = self.checkzone(zone, "KSK", until, output) ++ if not no_zsk: ++ zok = self.checkzone(zone, "ZSK", until, output) ++ else: ++ for z in self._zones: ++ if not no_ksk and z in self._K.keys(): ++ found = True ++ kok = self.checkzone(z, "KSK", until, output) ++ if not no_zsk and z in self._Z.keys(): ++ found = True ++ kok = self.checkzone(z, "ZSK", until, output) ++ ++ if not found: ++ output("ERROR: No key events found") ++ return False ++ ++ return (kok and zok) ++ ++ def checkzone(self, zone, keytype, until, output): ++ allok = True ++ if keytype == "KSK": ++ kz = self._K[zone] ++ else: ++ kz = self._Z[zone] ++ ++ for alg in kz.keys(): ++ output("Checking scheduled %s events for zone %s, " ++ "algorithm %s..." % ++ (keytype, zone, dnskey.algstr(alg))) ++ ok = eventlist.checkset(kz[alg], keytype, until, output) ++ if ok: ++ output("No errors found") ++ allok = allok and ok ++ ++ return allok ++ ++ @staticmethod ++ def showset(eventset, output): ++ if not eventset: ++ return ++ output(" " + eventset[0].showtime() + ":", skip=False) ++ for event in eventset: ++ output(" %s: %s" % (event.what, repr(event.key)), skip=False) ++ ++ @staticmethod ++ def checkset(eventset, keytype, until, output): ++ groups = list() ++ group = list() ++ ++ # collect up all events that have the same time ++ eventsfound = False ++ for event in eventset: ++ # we found an event ++ eventsfound = True ++ ++ # add event to current group ++ if (not group or group[0].when == event.when): ++ group.append(event) ++ ++ # if we're at the end of the list, we're done. if ++ # we've found an event with a later time, start a new group ++ if (group[0].when != event.when): ++ groups.append(group) ++ group = list() ++ group.append(event) ++ ++ if group: ++ groups.append(group) ++ ++ if not eventsfound: ++ output("ERROR: No %s events found" % keytype) ++ return False ++ ++ active = published = None ++ for group in groups: ++ if (until and calendar.timegm(group[0].when) > until): ++ output("Ignoring events after %s" % ++ time.strftime("%a %b %d %H:%M:%S UTC %Y", ++ time.gmtime(until))) ++ return True ++ ++ for event in group: ++ (active, published) = event.status(active, published) ++ ++ eventlist.showset(group, output) ++ ++ # and then check for inconsistencies: ++ if not active: ++ output("ERROR: No %s's are active after this event" % keytype) ++ return False ++ elif not published: ++ output("ERROR: No %s's are published after this event" ++ % keytype) ++ return False ++ elif not published.intersection(active): ++ output("ERROR: No %s's are both active and published " ++ "after this event" % keytype) ++ return False ++ ++ return True ++ +diff --git a/bin/python/isc/keydict.py b/bin/python/isc/keydict.py +new file mode 100644 +index 0000000..cc73dc4 +--- /dev/null ++++ b/bin/python/isc/keydict.py +@@ -0,0 +1,89 @@ ++############################################################################ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++from collections import defaultdict ++from . import dnskey ++import os ++import glob ++ ++ ++######################################################################## ++# Class keydict ++######################################################################## ++class keydict: ++ """ A dictionary of keys, indexed by name, algorithm, and key id """ ++ ++ _keydict = defaultdict(lambda: defaultdict(dict)) ++ _defttl = None ++ _missing = [] ++ ++ def __init__(self, dp=None, **kwargs): ++ self._defttl = kwargs.get('keyttl', None) ++ zones = kwargs.get('zones', None) ++ ++ if not zones: ++ path = kwargs.get('path',None) or '.' ++ self.readall(path) ++ else: ++ for zone in zones: ++ if 'path' in kwargs and kwargs['path'] is not None: ++ path = kwargs['path'] ++ else: ++ path = dp and dp.policy(zone).directory or '.' ++ if not self.readone(path, zone): ++ self._missing.append(zone) ++ ++ def readall(self, path): ++ files = glob.glob(os.path.join(path, '*.private')) ++ ++ for infile in files: ++ key = dnskey(infile, path, self._defttl) ++ self._keydict[key.name][key.alg][key.keyid] = key ++ ++ def readone(self, path, zone): ++ match='K' + zone + '.+*.private' ++ files = glob.glob(os.path.join(path, match)) ++ ++ found = False ++ for infile in files: ++ key = dnskey(infile, path, self._defttl) ++ if key.name != zone: # shouldn't ever happen ++ continue ++ self._keydict[key.name][key.alg][key.keyid] = key ++ found = True ++ ++ return found ++ ++ def __iter__(self): ++ for zone, algorithms in self._keydict.items(): ++ for alg, keys in algorithms.items(): ++ for key in keys.values(): ++ yield key ++ ++ def __getitem__(self, name): ++ return self._keydict[name] ++ ++ def zones(self): ++ return (self._keydict.keys()) ++ ++ def algorithms(self, zone): ++ return (self._keydict[zone].keys()) ++ ++ def keys(self, zone, alg): ++ return (self._keydict[zone][alg].keys()) ++ ++ def missing(self): ++ return (self._missing) +diff --git a/bin/python/isc/keyevent.py b/bin/python/isc/keyevent.py +new file mode 100644 +index 0000000..9025fee +--- /dev/null ++++ b/bin/python/isc/keyevent.py +@@ -0,0 +1,81 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import time ++ ++ ++######################################################################## ++# Class keyevent ++######################################################################## ++class keyevent: ++ """ A discrete key event, e.g., Publish, Activate, Inactive, Delete, ++ etc. Stores the date of the event, and identifying information ++ about the key to which the event will occur.""" ++ ++ def __init__(self, what, key, when=None): ++ self.what = what ++ self.when = when or key.gettime(what) ++ self.key = key ++ self.sep = key.sep ++ self.zone = key.name ++ self.alg = key.alg ++ self.keyid = key.keyid ++ ++ def __repr__(self): ++ return repr((self.when, self.what, self.keyid, self.sep, ++ self.zone, self.alg)) ++ ++ def showtime(self): ++ return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when) ++ ++ # update sets of active and published keys, based on ++ # the contents of this keyevent ++ def status(self, active, published, output = None): ++ def noop(*args, **kwargs): pass ++ if not output: ++ output = noop ++ ++ if not active: ++ active = set() ++ if not published: ++ published = set() ++ ++ if self.what == "Activate": ++ active.add(self.keyid) ++ elif self.what == "Publish": ++ published.add(self.keyid) ++ elif self.what == "Inactive": ++ if self.keyid not in active: ++ output("\tWARNING: %s scheduled to become inactive " ++ "before it is active" ++ % repr(self.key)) ++ else: ++ active.remove(self.keyid) ++ elif self.what == "Delete": ++ if self.keyid in published: ++ published.remove(self.keyid) ++ else: ++ output("WARNING: key %s is scheduled for deletion " ++ "before it is published" % repr(self.key)) ++ elif self.what == "Revoke": ++ # We don't need to worry about the logic of this one; ++ # just stop counting this key as either active or published ++ if self.keyid in published: ++ published.remove(self.keyid) ++ if self.keyid in active: ++ active.remove(self.keyid) ++ ++ return active, published +diff --git a/bin/python/isc/keymgr.py b/bin/python/isc/keymgr.py +new file mode 100644 +index 0000000..a3a9043 +--- /dev/null ++++ b/bin/python/isc/keymgr.py +@@ -0,0 +1,152 @@ ++############################################################################ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++from __future__ import print_function ++import os, sys, argparse, glob, re, time, calendar, pprint ++from collections import defaultdict ++ ++prog='dnssec-keymgr' ++ ++from isc import * ++from isc.utils import prefix ++ ++############################################################################ ++# print a fatal error and exit ++############################################################################ ++def fatal(*args, **kwargs): ++ print(*args, **kwargs) ++ sys.exit(1) ++ ++############################################################################ ++# find the location of an external command ++############################################################################ ++def set_path(command, default=None): ++ """ find the location of a specified command. If a default is supplied, ++ exists and it's an executable, we use it; otherwise we search PATH ++ for an alternative. ++ :param command: command to look for ++ :param default: default value to use ++ :return: PATH with the location of a suitable binary ++ """ ++ fpath = default ++ if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK): ++ path = os.environ["PATH"] ++ if not path: ++ path = os.path.defpath ++ for directory in path.split(os.pathsep): ++ fpath = directory + os.sep + command ++ if os.path.isfile(fpath) and os.access(fpath, os.X_OK): ++ break ++ fpath = None ++ ++ return fpath ++ ++############################################################################ ++# parse arguments ++############################################################################ ++def parse_args(): ++ """ Read command line arguments, returns 'args' object ++ :return: args object properly prepared ++ """ ++ ++ keygen = set_path('dnssec-keygen', ++ os.path.join(prefix('sbin'), 'dnssec-keygen')) ++ settime = set_path('dnssec-settime', ++ os.path.join(prefix('sbin'), 'dnssec-settime')) ++ ++ parser = argparse.ArgumentParser(description=prog + ': schedule ' ++ 'DNSSEC key rollovers according to a ' ++ 'pre-defined policy') ++ ++ parser.add_argument('zone', type=str, nargs='*', default=None, ++ help='Zone(s) to which the policy should be applied ' + ++ '(default: all zones in the directory)') ++ parser.add_argument('-K', dest='path', type=str, ++ help='Directory containing keys', metavar='dir') ++ parser.add_argument('-c', dest='policyfile', type=str, ++ help='Policy definition file', metavar='file') ++ parser.add_argument('-g', dest='keygen', default=keygen, type=str, ++ help='Path to \'dnssec-keygen\'', ++ metavar='path') ++ parser.add_argument('-s', dest='settime', default=settime, type=str, ++ help='Path to \'dnssec-settime\'', ++ metavar='path') ++ parser.add_argument('-k', dest='no_zsk', ++ action='store_true', default=False, ++ help='Only apply policy to key-signing keys (KSKs)') ++ parser.add_argument('-z', dest='no_ksk', ++ action='store_true', default=False, ++ help='Only apply policy to zone-signing keys (ZSKs)') ++ parser.add_argument('-f', '--force', dest='force', action='store_true', ++ default=False, help='Force updates to key events '+ ++ 'even if they are in the past') ++ parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', ++ default=False, help='Update keys silently') ++ parser.add_argument('-v', '--version', action='version', ++ version=utils.version) ++ ++ args = parser.parse_args() ++ ++ if args.no_zsk and args.no_ksk: ++ fatal("ERROR: -z and -k cannot be used together.") ++ ++ if args.keygen is None or args.settime is None: ++ fatal("ERROR: dnssec-keygen/dnssec-settime not found") ++ ++ # if a policy file was specified, check that it exists. ++ # if not, use the default file, unless it doesn't exist ++ if args.policyfile is not None: ++ if not os.path.exists(args.policyfile): ++ fatal('ERROR: Policy file "%s" not found' % args.policyfile) ++ else: ++ args.policyfile = os.path.join(utils.sysconfdir, 'policy.conf') ++ if not os.path.exists(args.policyfile): ++ args.policyfile = None ++ ++ return args ++ ++############################################################################ ++# main ++############################################################################ ++def main(): ++ args = parse_args() ++ ++ # As we may have specific locations for the binaries, we put that info ++ # into a context object that can be passed around ++ context = {'keygen_path': args.keygen, ++ 'settime_path': args.settime, ++ 'keys_path': args.path} ++ ++ try: ++ dp = policy.dnssec_policy(args.policyfile) ++ except Exception as e: ++ fatal('Unable to load DNSSEC policy: ' + str(e)) ++ ++ try: ++ kd = keydict(dp, path=args.path, zones=args.zone) ++ except Exception as e: ++ fatal('Unable to build key dictionary: ' + str(e)) ++ ++ try: ++ ks = keyseries(kd, context=context) ++ except Exception as e: ++ fatal('Unable to build key series: ' + str(e)) ++ ++ try: ++ ks.enforce_policy(dp, ksk=args.no_zsk, zsk=args.no_ksk, ++ force=args.force, quiet=args.quiet) ++ except Exception as e: ++ fatal('Unable to apply policy: ' + str(e)) +diff --git a/bin/python/isc/keyseries.py b/bin/python/isc/keyseries.py +new file mode 100644 +index 0000000..ed09f71 +--- /dev/null ++++ b/bin/python/isc/keyseries.py +@@ -0,0 +1,194 @@ ++############################################################################ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++from collections import defaultdict ++from .dnskey import * ++from .keydict import * ++from .keyevent import * ++from .policy import * ++import time ++ ++ ++class keyseries: ++ _K = defaultdict(lambda: defaultdict(list)) ++ _Z = defaultdict(lambda: defaultdict(list)) ++ _zones = set() ++ _kdict = None ++ _context = None ++ ++ def __init__(self, kdict, now=time.time(), context=None): ++ self._kdict = kdict ++ self._context = context ++ self._zones = set(kdict.missing()) ++ ++ for zone in kdict.zones(): ++ self._zones.add(zone) ++ for alg, keys in kdict[zone].items(): ++ for k in keys.values(): ++ if k.sep: ++ self._K[zone][alg].append(k) ++ else: ++ self._Z[zone][alg].append(k) ++ ++ for group in [self._K[zone][alg], self._Z[zone][alg]]: ++ group.sort() ++ for k in group: ++ if k.delete() and k.delete() < now: ++ group.remove(k) ++ ++ def __iter__(self): ++ for zone in self._zones: ++ for collection in [self._K, self._Z]: ++ if zone not in collection: ++ continue ++ for alg, keys in collection[zone].items(): ++ for key in keys: ++ yield key ++ ++ def dump(self): ++ for k in self: ++ print("%s" % repr(k)) ++ ++ def fixseries(self, keys, policy, now, **kwargs): ++ force = kwargs.get('force', False) ++ if not keys: ++ return ++ ++ # handle the first key ++ key = keys[0] ++ if key.sep: ++ rp = policy.ksk_rollperiod ++ prepub = policy.ksk_prepublish or (30 * 86400) ++ postpub = policy.ksk_postpublish or (30 * 86400) ++ else: ++ rp = policy.zsk_rollperiod ++ prepub = policy.zsk_prepublish or (30 * 86400) ++ postpub = policy.zsk_postpublish or (30 * 86400) ++ ++ # the first key should be published and active ++ p = key.publish() ++ a = key.activate() ++ if not p or p > now: ++ key.setpublish(now) ++ if not a or a > now: ++ key.setactivate(now) ++ ++ if not rp: ++ key.setinactive(None, **kwargs) ++ key.setdelete(None, **kwargs) ++ else: ++ key.setinactive(a + rp, **kwargs) ++ key.setdelete(a + rp + postpub, **kwargs) ++ ++ if policy.keyttl != key.ttl: ++ key.setttl(policy.keyttl) ++ ++ # handle all the subsequent keys ++ prev = key ++ for key in keys[1:]: ++ # if no rollperiod, then all keys after the first in ++ # the series kept inactive. ++ # (XXX: we need to change this to allow standby keys) ++ if not rp: ++ key.setpublish(None, **kwargs) ++ key.setactivate(None, **kwargs) ++ key.setinactive(None, **kwargs) ++ key.setdelete(None, **kwargs) ++ if policy.keyttl != key.ttl: ++ key.setttl(policy.keyttl) ++ continue ++ ++ # otherwise, ensure all dates are set correctly based on ++ # the initial key ++ a = prev.inactive() ++ p = a - prepub ++ key.setactivate(a, **kwargs) ++ key.setpublish(p, **kwargs) ++ key.setinactive(a + rp, **kwargs) ++ key.setdelete(a + rp + postpub, **kwargs) ++ prev.setdelete(a + postpub, **kwargs) ++ if policy.keyttl != key.ttl: ++ key.setttl(policy.keyttl) ++ prev = key ++ ++ # if we haven't got sufficient coverage, create successor key(s) ++ while rp and prev.inactive() and \ ++ prev.inactive() < now + policy.coverage: ++ # commit changes to predecessor: a successor can only be ++ # generated if Inactive has been set in the predecessor key ++ prev.commit(self._context['settime_path'], **kwargs) ++ key = prev.generate_successor(self._context['keygen_path'], ++ **kwargs) ++ ++ key.setinactive(key.activate() + rp, **kwargs) ++ key.setdelete(key.inactive() + postpub, **kwargs) ++ keys.append(key) ++ prev = key ++ ++ # last key? we already know we have sufficient coverage now, so ++ # disable the inactivation of the final key (if it was set), ++ # ensuring that if dnssec-keymgr isn't run again, the last key ++ # in the series will at least remain usable. ++ prev.setinactive(None, **kwargs) ++ prev.setdelete(None, **kwargs) ++ ++ # commit changes ++ for key in keys: ++ key.commit(self._context['settime_path'], **kwargs) ++ ++ ++ def enforce_policy(self, policies, now=time.time(), **kwargs): ++ # If zones is provided as a parameter, use that list. ++ # If not, use what we have in this object ++ zones = kwargs.get('zones', self._zones) ++ keys_dir = kwargs.get('dir', self._context.get('keys_path', None)) ++ force = kwargs.get('force', False) ++ ++ for zone in zones: ++ collections = [] ++ policy = policies.policy(zone) ++ keys_dir = keys_dir or policy.directory or '.' ++ alg = policy.algorithm ++ algnum = dnskey.algnum(alg) ++ if 'ksk' not in kwargs or not kwargs['ksk']: ++ if len(self._Z[zone][algnum]) == 0: ++ k = dnskey.generate(self._context['keygen_path'], ++ keys_dir, zone, alg, ++ policy.zsk_keysize, False, ++ policy.keyttl or 3600, ++ **kwargs) ++ self._Z[zone][algnum].append(k) ++ collections.append(self._Z[zone]) ++ ++ if 'zsk' not in kwargs or not kwargs['zsk']: ++ if len(self._K[zone][algnum]) == 0: ++ k = dnskey.generate(self._context['keygen_path'], ++ keys_dir, zone, alg, ++ policy.ksk_keysize, True, ++ policy.keyttl or 3600, ++ **kwargs) ++ self._K[zone][algnum].append(k) ++ collections.append(self._K[zone]) ++ ++ for collection in collections: ++ for algorithm, keys in collection.items(): ++ if algorithm != algnum: ++ continue ++ try: ++ self.fixseries(keys, policy, now, **kwargs) ++ except Exception as e: ++ raise Exception('%s/%s: %s' % ++ (zone, dnskey.algstr(algnum), str(e))) +diff --git a/bin/python/isc/keyzone.py b/bin/python/isc/keyzone.py +new file mode 100644 +index 0000000..7dfb31a +--- /dev/null ++++ b/bin/python/isc/keyzone.py +@@ -0,0 +1,60 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import os ++import sys ++import re ++from subprocess import Popen, PIPE ++ ++######################################################################## ++# Exceptions ++######################################################################## ++class KeyZoneException(Exception): ++ pass ++ ++######################################################################## ++# class keyzone ++######################################################################## ++class keyzone: ++ """reads a zone file to find data relevant to keys""" ++ ++ def __init__(self, name, filename, czpath): ++ self.maxttl = None ++ self.keyttl = None ++ ++ if not name: ++ return ++ ++ if not czpath or not os.path.isfile(czpath) \ ++ or not os.access(czpath, os.X_OK): ++ raise KeyZoneException('"named-compilezone" not found') ++ return ++ ++ maxttl = keyttl = None ++ ++ fp, _ = Popen([czpath, "-o", "-", name, filename], ++ stdout=PIPE, stderr=PIPE).communicate() ++ for line in fp.splitlines(): ++ if re.search('^[:space:]*;', line): ++ continue ++ fields = line.split() ++ if not maxttl or int(fields[1]) > maxttl: ++ maxttl = int(fields[1]) ++ if fields[3] == "DNSKEY": ++ keyttl = int(fields[1]) ++ ++ self.keyttl = keyttl ++ self.maxttl = maxttl +diff --git a/bin/python/isc/policy.py b/bin/python/isc/policy.py +new file mode 100644 +index 0000000..ed106c6 +--- /dev/null ++++ b/bin/python/isc/policy.py +@@ -0,0 +1,690 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++# policy.py ++# This module implements the parser for the dnssec.policy file. ++############################################################################ ++ ++import re ++import ply.lex as lex ++import ply.yacc as yacc ++from string import * ++from copy import copy ++ ++ ++############################################################################ ++# PolicyLex: a lexer for the policy file syntax. ++############################################################################ ++class PolicyLex: ++ reserved = ('POLICY', ++ 'ALGORITHM_POLICY', ++ 'ZONE', ++ 'ALGORITHM', ++ 'DIRECTORY', ++ 'KEYTTL', ++ 'KEY_SIZE', ++ 'ROLL_PERIOD', ++ 'PRE_PUBLISH', ++ 'POST_PUBLISH', ++ 'COVERAGE', ++ 'STANDBY', ++ 'NONE') ++ ++ tokens = reserved + ('DATESUFFIX', ++ 'KEYTYPE', ++ 'ALGNAME', ++ 'STR', ++ 'QSTRING', ++ 'NUMBER', ++ 'LBRACE', ++ 'RBRACE', ++ 'SEMI') ++ reserved_map = {} ++ ++ t_ignore = ' \t' ++ t_ignore_olcomment = r'(//|\#).*' ++ ++ t_LBRACE = r'\{' ++ t_RBRACE = r'\}' ++ t_SEMI = r';'; ++ ++ def t_newline(self, t): ++ r'\n+' ++ t.lexer.lineno += t.value.count("\n") ++ ++ def t_comment(self, t): ++ r'/\*(.|\n)*?\*/' ++ t.lexer.lineno += t.value.count('\n') ++ ++ def t_DATESUFFIX(self, t): ++ r'(?i)(?<=[0-9 \t])(y(?:ears|ear|ea|e)?|mo(?:nths|nth|nt|n)?|w(?:eeks|eek|ee|e)?|d(?:ays|ay|a)?|h(?:ours|our|ou|o)?|mi(?:nutes|nute|nut|nu|n)?|s(?:econds|econd|econ|eco|ec|e)?)\b' ++ t.value = re.match(r'(?i)(y|mo|w|d|h|mi|s)([a-z]*)', t.value).group(1).lower() ++ return t ++ ++ def t_KEYTYPE(self, t): ++ r'(?i)\b(KSK|ZSK)\b' ++ t.value = t.value.upper() ++ return t ++ ++ def t_ALGNAME(self, t): ++ r'(?i)\b(RSAMD5|DH|DSA|NSEC3DSA|ECC|RSASHA1|NSEC3RSASHA1|RSASHA256|RSASHA512|ECCGOST|ECDSAP256SHA245|ECDSAP384SHA384)\b' ++ t.value = t.value.upper() ++ return t ++ ++ def t_STR(self, t): ++ r'[A-Za-z._-][\w._-]*' ++ t.type = self.reserved_map.get(t.value, "STR") ++ return t ++ ++ def t_QSTRING(self, t): ++ r'"([^"\n]|(\\"))*"' ++ t.type = self.reserved_map.get(t.value, "QSTRING") ++ t.value = t.value[1:-1] ++ return t ++ ++ def t_NUMBER(self, t): ++ r'\d+' ++ t.value = int(t.value) ++ return t ++ ++ def t_error(self, t): ++ print("Illegal character '%s'" % t.value[0]) ++ t.lexer.skip(1) ++ ++ def __init__(self, **kwargs): ++ for r in self.reserved: ++ self.reserved_map[r.lower().translate(maketrans('_', '-'))] = r ++ self.lexer = lex.lex(object=self, **kwargs) ++ ++ def test(self, text): ++ self.lexer.input(text) ++ while True: ++ t = self.lexer.token() ++ if not t: ++ break ++ print(t) ++ ++############################################################################ ++# Policy: this object holds a set of DNSSEC policy settings. ++############################################################################ ++class Policy: ++ is_zone = False ++ is_alg = False ++ is_constructed = False ++ ksk_rollperiod = None ++ zsk_rollperiod = None ++ ksk_prepublish = None ++ zsk_prepublish = None ++ ksk_postpublish = None ++ zsk_postpublish = None ++ ksk_keysize = None ++ zsk_keysize = None ++ ksk_standby = None ++ zsk_standby = None ++ keyttl = None ++ coverage = None ++ directory = None ++ valid_key_sz_per_algo = {'DSA': [512, 1024], ++ 'NSEC3DSA': [512, 1024], ++ 'RSAMD5': [512, 4096], ++ 'RSASHA1': [512, 4096], ++ 'NSEC3RSASHA1': [512, 4096], ++ 'RSASHA256': [512, 4096], ++ 'RSASHA512': [512, 4096], ++ 'ECCGOST': None, ++ 'ECDSAP256SHA245': None, ++ 'ECDSAP384SHA384': None} ++ ++ def __init__(self, name=None, algorithm=None, parent=None): ++ self.name = name ++ self.algorithm = algorithm ++ self.parent = parent ++ pass ++ ++ def __repr__(self): ++ return ("%spolicy %s:\n" ++ "\tinherits %s\n" ++ "\tdirectory %s\n" ++ "\talgorithm %s\n" ++ "\tcoverage %s\n" ++ "\tksk_keysize %s\n" ++ "\tzsk_keysize %s\n" ++ "\tksk_rollperiod %s\n" ++ "\tzsk_rollperiod %s\n" ++ "\tksk_prepublish %s\n" ++ "\tksk_postpublish %s\n" ++ "\tzsk_prepublish %s\n" ++ "\tzsk_postpublish %s\n" ++ "\tksk_standby %s\n" ++ "\tzsk_standby %s\n" ++ "\tkeyttl %s\n" ++ % ++ ((self.is_constructed and 'constructed ' or \ ++ self.is_zone and 'zone ' or \ ++ self.is_alg and 'algorithm ' or ''), ++ self.name or 'UNKNOWN', ++ self.parent and self.parent.name or 'None', ++ self.directory and ('"' + str(self.directory) + '"') or 'None', ++ self.algorithm or 'None', ++ self.coverage and str(self.coverage) or 'None', ++ self.ksk_keysize and str(self.ksk_keysize) or 'None', ++ self.zsk_keysize and str(self.zsk_keysize) or 'None', ++ self.ksk_rollperiod and str(self.ksk_rollperiod) or 'None', ++ self.zsk_rollperiod and str(self.zsk_rollperiod) or 'None', ++ self.ksk_prepublish and str(self.ksk_prepublish) or 'None', ++ self.ksk_postpublish and str(self.ksk_postpublish) or 'None', ++ self.zsk_prepublish and str(self.zsk_prepublish) or 'None', ++ self.zsk_postpublish and str(self.zsk_postpublish) or 'None', ++ self.ksk_standby and str(self.ksk_standby) or 'None', ++ self.zsk_standby and str(self.zsk_standby) or 'None', ++ self.keyttl and str(self.keyttl) or 'None')) ++ ++ def __verify_size(self, key_size, size_range): ++ return (size_range[0] <= key_size <= size_range[1]) ++ ++ def get_name(self): ++ return self.name ++ ++ def constructed(self): ++ return self.is_constructed ++ ++ def validate(self): ++ """ Check if the values in the policy make sense ++ :return: True/False if the policy passes validation ++ """ ++ if self.ksk_rollperiod and \ ++ self.ksk_prepublish is not None and \ ++ self.ksk_prepublish > self.ksk_rollperiod: ++ print(self.ksk_rollperiod) ++ return (False, ++ ('KSK pre-publish period (%d) exceeds rollover period %d' ++ % (self.ksk_prepublish, self.ksk_rollperiod))) ++ ++ if self.ksk_rollperiod and \ ++ self.ksk_postpublish is not None and \ ++ self.ksk_postpublish > self.ksk_rollperiod: ++ return (False, ++ ('KSK post-publish period (%d) exceeds rollover period %d' ++ % (self.ksk_postpublish, self.ksk_rollperiod))) ++ ++ if self.zsk_rollperiod and \ ++ self.zsk_prepublish is not None and \ ++ self.zsk_prepublish >= self.zsk_rollperiod: ++ return (False, ++ ('ZSK pre-publish period (%d) exceeds rollover period %d' ++ % (self.zsk_prepublish, self.zsk_rollperiod))) ++ ++ if self.zsk_rollperiod and \ ++ self.zsk_postpublish is not None and \ ++ self.zsk_postpublish >= self.zsk_rollperiod: ++ return (False, ++ ('ZSK post-publish period (%d) exceeds rollover period %d' ++ % (self.zsk_postpublish, self.zsk_rollperiod))) ++ ++ if self.ksk_rollperiod and \ ++ self.ksk_prepublish and self.ksk_postpublish and \ ++ self.ksk_prepublish + self.ksk_postpublish >= self.ksk_rollperiod: ++ return (False, ++ (('KSK pre/post-publish periods (%d/%d) ' + ++ 'combined exceed rollover period %d') % ++ (self.ksk_prepublish, ++ self.ksk_postpublish, ++ self.ksk_rollperiod))) ++ ++ if self.zsk_rollperiod and \ ++ self.zsk_prepublish and self.zsk_postpublish and \ ++ self.zsk_prepublish + self.zsk_postpublish >= self.zsk_rollperiod: ++ return (False, ++ (('ZSK pre/post-publish periods (%d/%d) ' + ++ 'combined exceed rollover period %d') % ++ (self.zsk_prepublish, ++ self.zsk_postpublish, ++ self.zsk_rollperiod))) ++ ++ if self.algorithm is not None: ++ # Validate the key size ++ key_sz_range = self.valid_key_sz_per_algo.get(self.algorithm) ++ if key_sz_range is not None: ++ # Verify KSK ++ if not self.__verify_size(self.ksk_keysize, key_sz_range): ++ return False, 'KSK key size %d outside valid range %s' \ ++ % (self.ksk_keysize, key_sz_range) ++ ++ # Verify ZSK ++ if not self.__verify_size(self.zsk_keysize, key_sz_range): ++ return False, 'ZSK key size %d outside valid range %s' \ ++ % (self.zsk_keysize, key_sz_range) ++ ++ # Specific check for DSA keys ++ if self.algorithm in ['DSA', 'NSEC3DSA'] and \ ++ self.ksk_keysize % 64 != 0: ++ return False, \ ++ ('KSK key size %d not divisible by 64 ' + ++ 'as required for DSA') % self.ksk_keysize ++ ++ if self.algorithm in ['DSA', 'NSEC3DSA'] and \ ++ self.zsk_keysize % 64 != 0: ++ return False, \ ++ ('ZSK key size %d not divisible by 64 ' + ++ 'as required for DSA') % self.zsk_keysize ++ ++ return True, '' ++ ++############################################################################ ++# dnssec_policy: ++# This class reads a dnssec.policy file and creates a dictionary of ++# DNSSEC policy rules from which a policy for a specific zone can ++# be generated. ++############################################################################ ++class PolicyException(Exception): ++ pass ++ ++class dnssec_policy: ++ alg_policy = {} ++ named_policy = {} ++ zone_policy = {} ++ current = None ++ filename = None ++ initial = True ++ ++ def __init__(self, filename=None, **kwargs): ++ self.plex = PolicyLex() ++ self.tokens = self.plex.tokens ++ if 'debug' not in kwargs: ++ kwargs['debug'] = False ++ if 'write_tables' not in kwargs: ++ kwargs['write_tables'] = False ++ self.parser = yacc.yacc(module=self, **kwargs) ++ ++ # set defaults ++ self.setup('''policy global { algorithm rsasha256; ++ key-size ksk 2048; ++ key-size zsk 2048; ++ roll-period ksk 0; ++ roll-period zsk 1y; ++ pre-publish ksk 1mo; ++ pre-publish zsk 1mo; ++ post-publish ksk 1mo; ++ post-publish zsk 1mo; ++ standby ksk 0; ++ standby zsk 0; ++ keyttl 1h; ++ coverage 6mo; }; ++ policy default { policy global; };''') ++ ++ p = Policy() ++ p.algorithm = None ++ p.is_alg = True ++ p.ksk_keysize = 2048; ++ p.zsk_keysize = 2048; ++ ++ # set default algorithm policies ++ # these need a lower default key size: ++ self.alg_policy['DSA'] = copy(p) ++ self.alg_policy['DSA'].algorithm = "DSA" ++ self.alg_policy['DSA'].name = "DSA" ++ self.alg_policy['DSA'].ksk_keysize = 1024; ++ ++ self.alg_policy['NSEC3DSA'] = copy(p) ++ self.alg_policy['NSEC3DSA'].algorithm = "NSEC3DSA" ++ self.alg_policy['NSEC3DSA'].name = "NSEC3DSA" ++ self.alg_policy['NSEC3DSA'].ksk_keysize = 1024; ++ ++ # these can use default settings ++ self.alg_policy['RSAMD5'] = copy(p) ++ self.alg_policy['RSAMD5'].algorithm = "RSAMD5" ++ self.alg_policy['RSAMD5'].name = "RSAMD5" ++ ++ self.alg_policy['RSASHA1'] = copy(p) ++ self.alg_policy['RSASHA1'].algorithm = "RSASHA1" ++ self.alg_policy['RSASHA1'].name = "RSASHA1" ++ ++ self.alg_policy['NSEC3RSASHA1'] = copy(p) ++ self.alg_policy['NSEC3RSASHA1'].algorithm = "NSEC3RSASHA1" ++ self.alg_policy['NSEC3RSASHA1'].name = "NSEC3RSASHA1" ++ ++ self.alg_policy['RSASHA256'] = copy(p) ++ self.alg_policy['RSASHA256'].algorithm = "RSASHA256" ++ self.alg_policy['RSASHA256'].name = "RSASHA256" ++ ++ self.alg_policy['RSASHA512'] = copy(p) ++ self.alg_policy['RSASHA512'].algorithm = "RSASHA512" ++ self.alg_policy['RSASHA512'].name = "RSASHA512" ++ ++ self.alg_policy['ECCGOST'] = copy(p) ++ self.alg_policy['ECCGOST'].algorithm = "ECCGOST" ++ self.alg_policy['ECCGOST'].name = "ECCGOST" ++ ++ self.alg_policy['ECDSAP256SHA245'] = copy(p) ++ self.alg_policy['ECDSAP256SHA245'].algorithm = "ECDSAP256SHA256" ++ self.alg_policy['ECDSAP256SHA245'].name = "ECDSAP256SHA256" ++ ++ self.alg_policy['ECDSAP384SHA384'] = copy(p) ++ self.alg_policy['ECDSAP384SHA384'].algorithm = "ECDSAP384SHA384" ++ self.alg_policy['ECDSAP384SHA384'].name = "ECDSAP384SHA384" ++ ++ if filename: ++ self.load(filename) ++ ++ def load(self, filename): ++ self.filename = filename ++ self.initial = True ++ with open(filename) as f: ++ text = f.read() ++ self.plex.lexer.lineno = 0 ++ self.parser.parse(text) ++ ++ self.filename = None ++ ++ def setup(self, text): ++ self.initial = True ++ self.plex.lexer.lineno = 0 ++ self.parser.parse(text) ++ ++ def policy(self, zone, **kwargs): ++ z = zone.lower() ++ p = None ++ ++ if z in self.zone_policy: ++ p = self.zone_policy[z] ++ ++ if p is None: ++ p = copy(self.named_policy['default']) ++ p.name = zone ++ p.is_constructed = True ++ ++ if p.algorithm is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent and not parent.algorithm: ++ parent = parent.parent ++ p.algorithm = parent and parent.algorithm or None ++ ++ if p.algorithm in self.alg_policy: ++ ap = self.alg_policy[p.algorithm] ++ else: ++ raise PolicyException('algorithm not found') ++ ++ if p.directory is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent is not None and not parent.directory: ++ parent = parent.parent ++ p.directory = parent and parent.directory ++ ++ if p.coverage is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent and not parent.coverage: ++ parent = parent.parent ++ p.coverage = parent and parent.coverage or ap.coverage ++ ++ if p.ksk_keysize is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.ksk_keysize: ++ parent = parent.parent ++ p.ksk_keysize = parent and parent.ksk_keysize or ap.ksk_keysize ++ ++ if p.zsk_keysize is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.zsk_keysize: ++ parent = parent.parent ++ p.zsk_keysize = parent and parent.zsk_keysize or ap.zsk_keysize ++ ++ if p.ksk_rollperiod is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.ksk_rollperiod: ++ parent = parent.parent ++ p.ksk_rollperiod = parent and \ ++ parent.ksk_rollperiod or ap.ksk_rollperiod ++ ++ if p.zsk_rollperiod is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.zsk_rollperiod: ++ parent = parent.parent ++ p.zsk_rollperiod = parent and \ ++ parent.zsk_rollperiod or ap.zsk_rollperiod ++ ++ if p.ksk_prepublish is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.ksk_prepublish: ++ parent = parent.parent ++ p.ksk_prepublish = parent and \ ++ parent.ksk_prepublish or ap.ksk_prepublish ++ ++ if p.zsk_prepublish is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.zsk_prepublish: ++ parent = parent.parent ++ p.zsk_prepublish = parent and \ ++ parent.zsk_prepublish or ap.zsk_prepublish ++ ++ if p.ksk_postpublish is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.ksk_postpublish: ++ parent = parent.parent ++ p.ksk_postpublish = parent and \ ++ parent.ksk_postpublish or ap.ksk_postpublish ++ ++ if p.zsk_postpublish is None: ++ parent = p.parent or self.named_policy['default'] ++ while parent.parent and not parent.zsk_postpublish: ++ parent = parent.parent ++ p.zsk_postpublish = parent and \ ++ parent.zsk_postpublish or ap.zsk_postpublish ++ ++ if 'novalidate' not in kwargs or not kwargs['novalidate']: ++ (valid, msg) = p.validate() ++ if not valid: ++ raise PolicyException(msg) ++ return None ++ ++ return p ++ ++ ++ def p_policylist(self, p): ++ '''policylist : init policy ++ | policylist policy''' ++ pass ++ ++ def p_init(self, p): ++ "init :" ++ self.initial = False ++ ++ def p_policy(self, p): ++ '''policy : alg_policy ++ | zone_policy ++ | named_policy''' ++ pass ++ ++ def p_name(self, p): ++ '''name : STR ++ | KEYTYPE ++ | DATESUFFIX''' ++ p[0] = p[1] ++ pass ++ ++ def p_new_policy(self, p): ++ "new_policy :" ++ self.current = Policy() ++ ++ def p_alg_policy(self, p): ++ "alg_policy : ALGORITHM_POLICY ALGNAME new_policy alg_option_group SEMI" ++ self.current.name = p[2] ++ self.current.is_alg = True ++ self.alg_policy[p[2]] = self.current ++ pass ++ ++ def p_zone_policy(self, p): ++ "zone_policy : ZONE name new_policy policy_option_group SEMI" ++ self.current.name = p[2] ++ self.current.is_zone = True ++ self.zone_policy[p[2].lower()] = self.current ++ pass ++ ++ def p_named_policy(self, p): ++ "named_policy : POLICY name new_policy policy_option_group SEMI" ++ self.current.name = p[2] ++ self.named_policy[p[2].lower()] = self.current ++ pass ++ ++ def p_duration_1(self, p): ++ "duration : NUMBER" ++ p[0] = p[1] ++ pass ++ ++ def p_duration_2(self, p): ++ "duration : NONE" ++ p[0] = None ++ pass ++ ++ def p_duration_3(self, p): ++ "duration : NUMBER DATESUFFIX" ++ if p[2] == "y": ++ p[0] = p[1] * 31536000 # year ++ elif p[2] == "mo": ++ p[0] = p[1] * 2592000 # month ++ elif p[2] == "w": ++ p[0] = p[1] * 604800 # week ++ elif p[2] == "d": ++ p[0] = p[1] * 86400 # day ++ elif p[2] == "h": ++ p[0] = p[1] * 3600 # hour ++ elif p[2] == "mi": ++ p[0] = p[1] * 60 # minute ++ elif p[2] == "s": ++ p[0] = p[1] # second ++ else: ++ raise PolicyException('invalid duration') ++ ++ def p_policy_option_group(self, p): ++ "policy_option_group : LBRACE policy_option_list RBRACE" ++ pass ++ ++ def p_policy_option_list(self, p): ++ '''policy_option_list : policy_option SEMI ++ | policy_option_list policy_option SEMI''' ++ pass ++ ++ def p_policy_option(self, p): ++ '''policy_option : parent_option ++ | directory_option ++ | coverage_option ++ | rollperiod_option ++ | prepublish_option ++ | postpublish_option ++ | keysize_option ++ | algorithm_option ++ | keyttl_option ++ | standby_option''' ++ pass ++ ++ def p_alg_option_group(self, p): ++ "alg_option_group : LBRACE alg_option_list RBRACE" ++ pass ++ ++ def p_alg_option_list(self, p): ++ '''alg_option_list : alg_option SEMI ++ | alg_option_list alg_option SEMI''' ++ pass ++ ++ def p_alg_option(self, p): ++ '''alg_option : coverage_option ++ | rollperiod_option ++ | prepublish_option ++ | postpublish_option ++ | keyttl_option ++ | keysize_option ++ | standby_option''' ++ pass ++ ++ def p_parent_option(self, p): ++ "parent_option : POLICY name" ++ self.current.parent = self.named_policy[p[2].lower()] ++ ++ def p_directory_option(self, p): ++ "directory_option : DIRECTORY QSTRING" ++ self.current.directory = p[2] ++ ++ def p_coverage_option(self, p): ++ "coverage_option : COVERAGE duration" ++ self.current.coverage = p[2] ++ ++ def p_rollperiod_option(self, p): ++ "rollperiod_option : ROLL_PERIOD KEYTYPE duration" ++ if p[2] == "KSK": ++ self.current.ksk_rollperiod = p[3] ++ else: ++ self.current.zsk_rollperiod = p[3] ++ ++ def p_prepublish_option(self, p): ++ "prepublish_option : PRE_PUBLISH KEYTYPE duration" ++ if p[2] == "KSK": ++ self.current.ksk_prepublish = p[3] ++ else: ++ self.current.zsk_prepublish = p[3] ++ ++ def p_postpublish_option(self, p): ++ "postpublish_option : POST_PUBLISH KEYTYPE duration" ++ if p[2] == "KSK": ++ self.current.ksk_postpublish = p[3] ++ else: ++ self.current.zsk_postpublish = p[3] ++ ++ def p_keysize_option(self, p): ++ "keysize_option : KEY_SIZE KEYTYPE NUMBER" ++ if p[2] == "KSK": ++ self.current.ksk_keysize = p[3] ++ else: ++ self.current.zsk_keysize = p[3] ++ ++ def p_standby_option(self, p): ++ "standby_option : STANDBY KEYTYPE NUMBER" ++ if p[2] == "KSK": ++ self.current.ksk_standby = p[3] ++ else: ++ self.current.zsk_standby = p[3] ++ ++ def p_keyttl_option(self, p): ++ "keyttl_option : KEYTTL duration" ++ self.current.keyttl = p[2] ++ ++ def p_algorithm_option(self, p): ++ "algorithm_option : ALGORITHM ALGNAME" ++ self.current.algorithm = p[2] ++ ++ def p_error(self, p): ++ if p: ++ print("%s%s%d:syntax error near '%s'" % ++ (self.filename or "", ":" if self.filename else "", ++ p.lineno, p.value)) ++ else: ++ if not self.initial: ++ raise PolicyException("%s%s%d:unexpected end of input" % ++ (self.filename or "", ":" if self.filename else "", ++ p and p.lineno or 0)) ++ ++if __name__ == "__main__": ++ import sys ++ if sys.argv[1] == "lex": ++ file = open(sys.argv[2]) ++ text = file.read() ++ file.close() ++ plex = PolicyLex(debug=1) ++ plex.test(text) ++ elif sys.argv[1] == "parse": ++ try: ++ pp = dnssec_policy(sys.argv[2], write_tables=True, debug=True) ++ print(pp.named_policy['default']) ++ print(pp.policy("nonexistent.zone")) ++ except Exception as e: ++ print(e.args[0]) +diff --git a/bin/python/isc/tests/Makefile.in b/bin/python/isc/tests/Makefile.in +new file mode 100644 +index 0000000..506f2cc +--- /dev/null ++++ b/bin/python/isc/tests/Makefile.in +@@ -0,0 +1,33 @@ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++srcdir = @srcdir@ ++VPATH = @srcdir@ ++top_srcdir = @top_srcdir@ ++ ++@BIND9_MAKE_INCLUDES@ ++ ++PYTHON = @PYTHON@ ++ ++PYTESTS = dnskey_test.py policy_test.py ++ ++@BIND9_MAKE_RULES@ ++ ++check test: ++ for test in $(PYTESTS); do \ ++ $(PYTHON) $$test; \ ++ done ++ ++clean distclean:: ++ rm -f *.pyc +diff --git a/bin/python/isc/tests/dnskey_test.py b/bin/python/isc/tests/dnskey_test.py +new file mode 100644 +index 0000000..2a63695 +--- /dev/null ++++ b/bin/python/isc/tests/dnskey_test.py +@@ -0,0 +1,57 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import sys ++import unittest ++sys.path.append('../..') ++from isc import * ++ ++kdict = None ++ ++ ++def getkey(): ++ global kdict ++ if not kdict: ++ kd = keydict(path='testdata') ++ for key in kd: ++ return key ++ ++ ++class DnskeyTest(unittest.TestCase): ++ def test_metdata(self): ++ key = getkey() ++ self.assertEqual(key.created(), 1448055647) ++ self.assertEqual(key.publish(), 1445463714) ++ self.assertEqual(key.activate(), 1448055714) ++ self.assertEqual(key.revoke(), 1479591714) ++ self.assertEqual(key.inactive(), 1511127714) ++ self.assertEqual(key.delete(), 1542663714) ++ self.assertEqual(key.syncpublish(), 1442871714) ++ self.assertEqual(key.syncdelete(), 1448919714) ++ ++ def test_fmttime(self): ++ key = getkey() ++ self.assertEqual(key.getfmttime('Created'), '20151120214047') ++ self.assertEqual(key.getfmttime('Publish'), '20151021214154') ++ self.assertEqual(key.getfmttime('Activate'), '20151120214154') ++ self.assertEqual(key.getfmttime('Revoke'), '20161119214154') ++ self.assertEqual(key.getfmttime('Inactive'), '20171119214154') ++ self.assertEqual(key.getfmttime('Delete'), '20181119214154') ++ self.assertEqual(key.getfmttime('SyncPublish'), '20150921214154') ++ self.assertEqual(key.getfmttime('SyncDelete'), '20151130214154') ++ ++if __name__ == "__main__": ++ unittest.main() +diff --git a/bin/python/isc/tests/policy_test.py b/bin/python/isc/tests/policy_test.py +new file mode 100644 +index 0000000..c35e023 +--- /dev/null ++++ b/bin/python/isc/tests/policy_test.py +@@ -0,0 +1,90 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++ ++import sys ++import unittest ++sys.path.append('../..') ++from isc import * ++ ++ ++class PolicyTest(unittest.TestCase): ++ def test_keysize(self): ++ pol = policy.dnssec_policy() ++ pol.load('test-policies/01-keysize.pol') ++ ++ p = pol.policy('good_rsa.test', novalidate=True) ++ self.assertEqual(p.get_name(), "good_rsa.test") ++ self.assertEqual(p.constructed(), False) ++ self.assertEqual(p.validate(), (True, "")) ++ ++ p = pol.policy('good_dsa.test', novalidate=True) ++ self.assertEqual(p.get_name(), "good_dsa.test") ++ self.assertEqual(p.constructed(), False) ++ self.assertEqual(p.validate(), (True, "")) ++ ++ p = pol.policy('bad_dsa.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (False, 'ZSK key size 769 not divisible by 64 as required for DSA')) ++ ++ def test_prepublish(self): ++ pol = policy.dnssec_policy() ++ pol.load('test-policies/02-prepublish.pol') ++ p = pol.policy('good_prepublish.test', novalidate=True) ++ self.assertEqual(p.validate(), (True, "")) ++ ++ p = pol.policy('bad_prepublish.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (False, 'KSK pre/post-publish periods ' ++ '(10368000/5184000) combined exceed ' ++ 'rollover period 10368000')) ++ ++ def test_postpublish(self): ++ pol = policy.dnssec_policy() ++ pol.load('test-policies/03-postpublish.pol') ++ ++ p = pol.policy('good_postpublish.test', novalidate=True) ++ self.assertEqual(p.validate(), (True, "")) ++ ++ p = pol.policy('bad_postpublish.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (False, 'KSK pre/post-publish periods ' ++ '(10368000/5184000) combined exceed ' ++ 'rollover period 10368000')) ++ ++ def test_combined_pre_post(self): ++ pol = policy.dnssec_policy() ++ pol.load('test-policies/04-combined-pre-post.pol') ++ ++ p = pol.policy('good_combined_pre_post_ksk.test', novalidate=True) ++ self.assertEqual(p.validate(), (True, "")) ++ ++ p = pol.policy('bad_combined_pre_post_ksk.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (False, 'KSK pre/post-publish periods ' ++ '(5184000/5184000) combined exceed ' ++ 'rollover period 10368000')) ++ ++ p = pol.policy('good_combined_pre_post_zsk.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (True, "")) ++ p = pol.policy('bad_combined_pre_post_zsk.test', novalidate=True) ++ self.assertEqual(p.validate(), ++ (False, 'ZSK pre/post-publish periods ' ++ '(5184000/5184000) combined exceed ' ++ 'rollover period 7776000')) ++ ++if __name__ == "__main__": ++ unittest.main() +diff --git a/bin/python/isc/tests/test-policies/01-keysize.pol b/bin/python/isc/tests/test-policies/01-keysize.pol +new file mode 100644 +index 0000000..b54f1e3 +--- /dev/null ++++ b/bin/python/isc/tests/test-policies/01-keysize.pol +@@ -0,0 +1,41 @@ ++policy keysize_rsa { ++ algorithm rsasha1; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 2w; ++ post-publish zsk 2w; ++ roll-period ksk 1y; ++ pre-publish ksk 1mo; ++ post-publish ksk 2mo; ++ keyttl 1h; ++ key-size ksk 2048; ++ key-size zsk 1024; ++}; ++ ++policy keysize_dsa { ++ algorithm dsa; ++ coverage 1y; ++ key-size ksk 2048; ++ key-size zsk 1024; ++}; ++ ++zone good_rsa.test { ++ policy keysize_rsa; ++}; ++ ++zone bad_rsa.test { ++ policy keysize_rsa; ++ key-size ksk 511; ++}; ++ ++zone good_dsa.test { ++ policy keysize_dsa; ++ key-size ksk 1024; ++ key-size zsk 768; ++}; ++ ++zone bad_dsa.test { ++ policy keysize_dsa; ++ key-size ksk 1024; ++ key-size zsk 769; ++}; +diff --git a/bin/python/isc/tests/test-policies/02-prepublish.pol b/bin/python/isc/tests/test-policies/02-prepublish.pol +new file mode 100644 +index 0000000..e4d11c2 +--- /dev/null ++++ b/bin/python/isc/tests/test-policies/02-prepublish.pol +@@ -0,0 +1,31 @@ ++policy prepublish_rsa { ++ algorithm rsasha1; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 2w; ++ post-publish zsk 2w; ++ roll-period ksk 1y; ++ pre-publish ksk 1mo; ++ post-publish ksk 2mo; ++ keyttl 1h; ++ key-size ksk 2048; ++ key-size zsk 1024; ++}; ++ ++// Policy that defines a pre-publish period lower than the rollover period ++zone good_prepublish.test { ++ policy prepublish_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 1mo; ++}; ++ ++// Policy that defines a pre-publish period equal to the rollover period ++zone bad_prepublish.test { ++ policy prepublish_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 4mo; ++}; ++ ++ +diff --git a/bin/python/isc/tests/test-policies/03-postpublish.pol b/bin/python/isc/tests/test-policies/03-postpublish.pol +new file mode 100644 +index 0000000..a4c3a99 +--- /dev/null ++++ b/bin/python/isc/tests/test-policies/03-postpublish.pol +@@ -0,0 +1,31 @@ ++policy postpublish_rsa { ++ algorithm rsasha1; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 2w; ++ post-publish zsk 2w; ++ roll-period ksk 1y; ++ pre-publish ksk 1mo; ++ post-publish ksk 2mo; ++ keyttl 1h; ++ key-size ksk 2048; ++ key-size zsk 1024; ++}; ++ ++// Policy that defines a post-publish period lower than the rollover period ++zone good_postpublish.test { ++ policy postpublish_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 1mo; ++}; ++ ++// Policy that defines a post-publish period equal to the rollover period ++zone bad_postpublish.test { ++ policy postpublish_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 4mo; ++}; ++ ++ +diff --git a/bin/python/isc/tests/test-policies/04-combined-pre-post.pol b/bin/python/isc/tests/test-policies/04-combined-pre-post.pol +new file mode 100644 +index 0000000..5695559 +--- /dev/null ++++ b/bin/python/isc/tests/test-policies/04-combined-pre-post.pol +@@ -0,0 +1,55 @@ ++policy combined_pre_post_rsa { ++ algorithm rsasha1; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 2w; ++ post-publish zsk 2w; ++ roll-period ksk 1y; ++ pre-publish ksk 1mo; ++ post-publish ksk 2mo; ++ keyttl 1h; ++ key-size ksk 2048; ++ key-size zsk 1024; ++}; ++ ++// Policy that defines a combined pre-publish and post-publish period lower ++// than the rollover period ++zone good_combined_pre_post_ksk.test { ++ policy combined_pre_post_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 1mo; ++ post-publish ksk 1mo; ++}; ++ ++// Policy that defines a combined pre-publish and post-publish period higher ++// than the rollover period ++zone bad_combined_pre_post_ksk.test { ++ policy combined_pre_post_rsa; ++ coverage 6mo; ++ roll-period ksk 4mo; ++ pre-publish ksk 2mo; ++ post-publish ksk 2mo; ++}; ++ ++// Policy that defines a combined pre-publish and post-publish period lower ++// than the rollover period ++zone good_combined_pre_post_zsk.test { ++ policy combined_pre_post_rsa; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 1mo; ++ post-publish zsk 1mo; ++}; ++ ++// Policy that defines a combined pre-publish and post-publish period higher ++// than the rollover period ++zone bad_combined_pre_post_zsk.test { ++ policy combined_pre_post_rsa; ++ coverage 1y; ++ roll-period zsk 3mo; ++ pre-publish zsk 2mo; ++ post-publish zsk 2mo; ++}; ++ ++ +diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key +new file mode 100644 +index 0000000..c5afbe2 +--- /dev/null ++++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.key +@@ -0,0 +1,8 @@ ++; This is a key-signing key, keyid 35529, for example.com. ++; Created: 20151120214047 (Fri Nov 20 13:40:47 2015) ++; Publish: 20151021214154 (Wed Oct 21 14:41:54 2015) ++; Activate: 20151120214154 (Fri Nov 20 13:41:54 2015) ++; Revoke: 20161119214154 (Sat Nov 19 13:41:54 2016) ++; Inactive: 20171119214154 (Sun Nov 19 13:41:54 2017) ++; Delete: 20181119214154 (Mon Nov 19 13:41:54 2018) ++example.com. IN DNSKEY 257 3 7 AwEAAbbJK96tY8d4sF6RLxh9SVIhho5s2ZhrcijT5j1SNLECen7QLutj VJPEiG8UgBLaJSGkxPDxOygYv4hwh4JXBSj89o9rNabAJtCa9XzIXSpt /cfiCfvqmcOZb9nepmDCXsC7gn/gbae/4Y5ym9XOiCp8lu+tlFWgRiJ+ kxDGN48rRPrGfpq+SfwM9NUtftVa7B0EFVzDkADKedRj0SSGYOqH+WYH CnWjhPFmgJoAw3/m4slTHW1l+mDwFvsCMjXopg4JV0CNnTybnOmyuIwO LWRhB3q8ze24sYBU1fpE9VAMxZ++4Kqh/2MZFeDAs7iPPKSmI3wkRCW5 pkwDLO5lJ9c= +diff --git a/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private +new file mode 100644 +index 0000000..af22c6a +--- /dev/null ++++ b/bin/python/isc/tests/testdata/Kexample.com.+007+35529.private +@@ -0,0 +1,18 @@ ++Private-key-format: v1.3 ++Algorithm: 7 (NSEC3RSASHA1) ++Modulus: tskr3q1jx3iwXpEvGH1JUiGGjmzZmGtyKNPmPVI0sQJ6ftAu62NUk8SIbxSAEtolIaTE8PE7KBi/iHCHglcFKPz2j2s1psAm0Jr1fMhdKm39x+IJ++qZw5lv2d6mYMJewLuCf+Btp7/hjnKb1c6IKnyW762UVaBGIn6TEMY3jytE+sZ+mr5J/Az01S1+1VrsHQQVXMOQAMp51GPRJIZg6of5ZgcKdaOE8WaAmgDDf+biyVMdbWX6YPAW+wIyNeimDglXQI2dPJuc6bK4jA4tZGEHerzN7bixgFTV+kT1UAzFn77gqqH/YxkV4MCzuI88pKYjfCREJbmmTAMs7mUn1w== ++PublicExponent: AQAB ++PrivateExponent: jfiM6YU1Rd6Y5qrPsK7HP1Ko54DmNbvmzI1hfGmYYZAyQsNCXjQloix5aAW9QGdNhecrzJUhxJAMXFZC+lrKuD5a56R25JDE1Sw21nft3SHXhuQrqw5Z5hIMTWXhRrBR1lMOFnLj2PJxqCmenp+vJYjl1z20RBmbv/keE15SExFRJIJ3G0lI4V0KxprY5rgsT/vID0pS32f7rmXhgEzyWDyuxceTMidBooD5BSeEmSTYa4rvCVZ2vgnzIGSxjYDPJE2rGve2dpvdXQuujRFaf4+/FzjaOgg35rTtUmC9klfB4D6KJIfc1PNUwcH7V0VJ2fFlgZgMYi4W331QORl9sQ== ++Prime1: 479rW3EeoBwHhUKDy5YeyfnMKjhaosrcYhW4resevLzatFrvS/n2KxJnsHoEzmGr2A13naI61RndgVBBOwNDWI3/tQ+aKvcr+V9m4omROV3xYa8s1FsDbEW0Z6G0UheaqRFir8WK98/Lj6Zht1uBXHSPPf91OW0qj+b5gbX7TK8= ++Prime2: zXXlxgIq+Ih6kxsUw4Ith0nd/d2P3d42QYPjxYjsg4xYicPAjva9HltnbBQ2lr4JEG9Yyb8KalSnJUSuvXtn7bGfBzLu8W6omCeVWXQVH4NIu9AjpO16NpMKWGRfiHHbbSYJs1daTZKHC2FEmi18MKX/RauHGGOakFQ/3A/GMVk= ++Exponent1: 0o9UQ1uHNAIWFedUEHJ/jr7LOrGVYnLpZCmu7+S0K0zzatGz8ets44+FnAyDywdUKFDzKSMm/4SFXRwE4vl2VzYZlp2RLG4PEuRYK9OCF6a6F1UsvjxTItQjIbjIDSnTjMINGnMps0lDa1EpgKsyI3eEQ46eI3TBZ//k6D6G0vM= ++Exponent2: d+CYJgXRyJzo17fvT3s+0TbaHWsOq+chROyNEw4m4UIbzpW2XjO8eF/gYgERMLbEVyCAb4XVr+CgfXArfEbqhpciMHMZUyi7mbtOupiuUmqpH1v70Bj3O6xjVtuJmfTEkFSnSEppV+VsgclI26Q6V7Ai1yWTdzl2T0u4zs8tVlE= ++Coefficient: E4EYw76gIChdQDn6+Uh44/xH9Uwmvq3OETR8w/kEZ0xQ8AkTdKFKUp84nlR6gN+ljb2mUxERKrVLwnBsU8EbUlo9UccMbBGkkZ/8MyfGCBb9nUyOFtOxdHY2M0MQadesRptXHt/m30XjdohwmT7qfSIENwtgUOHbwFnn7WPMc/k= ++Created: 20151120214047 ++Publish: 20151021214154 ++Activate: 20151120214154 ++Revoke: 20161119214154 ++Inactive: 20171119214154 ++Delete: 20181119214154 ++SyncPublish: 20150921214154 ++SyncDelete: 20151130214154 +diff --git a/bin/python/isc/utils.py.in b/bin/python/isc/utils.py.in +new file mode 100644 +index 0000000..48b9685 +--- /dev/null ++++ b/bin/python/isc/utils.py.in +@@ -0,0 +1,57 @@ ++############################################################################ ++# Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++# utils.py ++# Grouping shared code in one place ++############################################################################ ++ ++import os ++ ++# These routines permit platform-independent location of BIND 9 tools ++if os.name == 'nt': ++ import win32con ++ import win32api ++ ++ ++def prefix(bindir=''): ++ if os.name != 'nt': ++ return os.path.join('@prefix@', bindir) ++ ++ bind_subkey = "Software\\ISC\\BIND" ++ h_key = None ++ key_found = True ++ try: ++ h_key = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, bind_subkey) ++ except: ++ key_found = False ++ if key_found: ++ try: ++ (named_base, _) = win32api.RegQueryValueEx(h_key, "InstallDir") ++ except: ++ key_found = False ++ win32api.RegCloseKey(h_key) ++ if key_found: ++ return os.path.join(named_base, bindir) ++ return os.path.join(win32api.GetSystemDirectory(), bindir) ++ ++ ++def shellquote(s): ++ if os.name == 'nt': ++ return '"' + s.replace('"', '"\\"') + '"' ++ return "'" + s.replace("'", "'\\''") + "'" ++ ++ ++version = '@BIND9_VERSION@' ++sysconfdir = '@expanded_sysconfdir@' +diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in +index 2bd42f9..930928b 100644 +--- a/bin/tests/system/conf.sh.in ++++ b/bin/tests/system/conf.sh.in +@@ -46,6 +46,7 @@ DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey + IMPORTKEY=$TOP/bin/dnssec/dnssec-importkey + CHECKDS=$TOP/bin/python/dnssec-checkds + COVERAGE=$TOP/bin/python/dnssec-coverage ++KEYMGR=$TOP/bin/python/dnssec-keymgr + CHECKZONE=$TOP/bin/check/named-checkzone + CHECKCONF=$TOP/bin/check/named-checkconf + PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -q -s ${SLOT:-0} -p ${HSMPIN:-1234}" +@@ -60,7 +61,7 @@ SAMPLE=$TOP/lib/export/samples/sample + # load on the machine to make it unusable to other users. + # v6synth + SUBDIRS="acl additional allow_query addzone autosign builtin +- cacheclean checkconf @CHECKDS@ checknames checkzone @COVERAGE@ ++ cacheclean checkconf @CHECKDS@ checknames checkzone @COVERAGE@ @KEYMGR@ + database digdelv dlv dlvauto dlz dlzexternal dname dns64 dnssec dyndb + ecdsa formerr forward glue gost ixfr inline limits logfileconfig + lwresd masterfile masterformat metadata notify nsupdate pending +@@ -70,6 +71,10 @@ SUBDIRS="acl additional allow_query addzone autosign builtin + + # PERL will be an empty string if no perl interpreter was found. + PERL=@PERL@ ++ ++# PYTHON will be an empty string if no python interpreter was found. ++PYTHON=@PYTHON@ ++ + if test -n "$PERL" + then + if $PERL -e "use IO::Socket::INET6;" 2> /dev/null +@@ -83,5 +88,5 @@ else + fi + + export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ +- PERL SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ ++ PERL PYTHON SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ + JOURNALPRINT ARPANAME SAMPLE +diff --git a/bin/tests/system/keymgr/01-ksk-inactive/README b/bin/tests/system/keymgr/01-ksk-inactive/README +new file mode 100644 +index 0000000..b807029 +--- /dev/null ++++ b/bin/tests/system/keymgr/01-ksk-inactive/README +@@ -0,0 +1,2 @@ ++This set includes one KSK rollover. The KSK is deactivated prior to ++its replacement being activated. +diff --git a/bin/tests/system/keymgr/01-ksk-inactive/expect b/bin/tests/system/keymgr/01-ksk-inactive/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/01-ksk-inactive/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/02-zsk-inactive/README b/bin/tests/system/keymgr/02-zsk-inactive/README +new file mode 100644 +index 0000000..bf56562 +--- /dev/null ++++ b/bin/tests/system/keymgr/02-zsk-inactive/README +@@ -0,0 +1,2 @@ ++This set includes one ZSK rollover. The first ZSK is deactivated ++prior to its replacement being activated. +diff --git a/bin/tests/system/keymgr/02-zsk-inactive/expect b/bin/tests/system/keymgr/02-zsk-inactive/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/02-zsk-inactive/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/03-ksk-unpublished/README b/bin/tests/system/keymgr/03-ksk-unpublished/README +new file mode 100644 +index 0000000..0581c67 +--- /dev/null ++++ b/bin/tests/system/keymgr/03-ksk-unpublished/README +@@ -0,0 +1,2 @@ ++This set contains one KSK rollover. The KSK is unpublished before its ++successor is published. +diff --git a/bin/tests/system/keymgr/03-ksk-unpublished/expect b/bin/tests/system/keymgr/03-ksk-unpublished/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/03-ksk-unpublished/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/04-zsk-unpublished/README b/bin/tests/system/keymgr/04-zsk-unpublished/README +new file mode 100644 +index 0000000..589490d +--- /dev/null ++++ b/bin/tests/system/keymgr/04-zsk-unpublished/README +@@ -0,0 +1,2 @@ ++This set contains one ZSK rollover. The ZSK is unpublished before its ++successor is published. +diff --git a/bin/tests/system/keymgr/04-zsk-unpublished/expect b/bin/tests/system/keymgr/04-zsk-unpublished/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/04-zsk-unpublished/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/05-ksk-unpub-active/README b/bin/tests/system/keymgr/05-ksk-unpub-active/README +new file mode 100644 +index 0000000..026028c +--- /dev/null ++++ b/bin/tests/system/keymgr/05-ksk-unpub-active/README +@@ -0,0 +1,3 @@ ++This set includes one KSK rollover. The first KSK is deleted ++and its successor published prior to the first KSK being deactivated ++and its successor activated. +diff --git a/bin/tests/system/keymgr/05-ksk-unpub-active/expect b/bin/tests/system/keymgr/05-ksk-unpub-active/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/05-ksk-unpub-active/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/06-zsk-unpub-active/README b/bin/tests/system/keymgr/06-zsk-unpub-active/README +new file mode 100644 +index 0000000..026028c +--- /dev/null ++++ b/bin/tests/system/keymgr/06-zsk-unpub-active/README +@@ -0,0 +1,3 @@ ++This set includes one KSK rollover. The first KSK is deleted ++and its successor published prior to the first KSK being deactivated ++and its successor activated. +diff --git a/bin/tests/system/keymgr/06-zsk-unpub-active/expect b/bin/tests/system/keymgr/06-zsk-unpub-active/expect +new file mode 100644 +index 0000000..b076310 +--- /dev/null ++++ b/bin/tests/system/keymgr/06-zsk-unpub-active/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1h -m 2h example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/07-ksk-ttl/README b/bin/tests/system/keymgr/07-ksk-ttl/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/07-ksk-ttl/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/07-ksk-ttl/expect b/bin/tests/system/keymgr/07-ksk-ttl/expect +new file mode 100644 +index 0000000..de792a9 +--- /dev/null ++++ b/bin/tests/system/keymgr/07-ksk-ttl/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/08-zsk-ttl/README b/bin/tests/system/keymgr/08-zsk-ttl/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/08-zsk-ttl/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/08-zsk-ttl/expect b/bin/tests/system/keymgr/08-zsk-ttl/expect +new file mode 100644 +index 0000000..de792a9 +--- /dev/null ++++ b/bin/tests/system/keymgr/08-zsk-ttl/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/09-no-keys/README b/bin/tests/system/keymgr/09-no-keys/README +new file mode 100644 +index 0000000..6295fa3 +--- /dev/null ++++ b/bin/tests/system/keymgr/09-no-keys/README +@@ -0,0 +1 @@ ++This directory has no key set, but one will be initialized by dnssec-keymgr. +diff --git a/bin/tests/system/keymgr/09-no-keys/expect b/bin/tests/system/keymgr/09-no-keys/expect +new file mode 100644 +index 0000000..de792a9 +--- /dev/null ++++ b/bin/tests/system/keymgr/09-no-keys/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/10-change-roll/README b/bin/tests/system/keymgr/10-change-roll/README +new file mode 100644 +index 0000000..26073c3 +--- /dev/null ++++ b/bin/tests/system/keymgr/10-change-roll/README +@@ -0,0 +1,3 @@ ++This directory has a key set which is valid, but has a ZSK rollover period ++of only three months. It will be updated to have a ZSK rollover period of ++one year. +diff --git a/bin/tests/system/keymgr/10-change-roll/expect b/bin/tests/system/keymgr/10-change-roll/expect +new file mode 100644 +index 0000000..de792a9 +--- /dev/null ++++ b/bin/tests/system/keymgr/10-change-roll/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/11-many-simul/README b/bin/tests/system/keymgr/11-many-simul/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/11-many-simul/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/11-many-simul/expect b/bin/tests/system/keymgr/11-many-simul/expect +new file mode 100644 +index 0000000..de792a9 +--- /dev/null ++++ b/bin/tests/system/keymgr/11-many-simul/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/12-many-active/README b/bin/tests/system/keymgr/12-many-active/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/12-many-active/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/12-many-active/expect b/bin/tests/system/keymgr/12-many-active/expect +new file mode 100644 +index 0000000..f990a7a +--- /dev/null ++++ b/bin/tests/system/keymgr/12-many-active/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf -f example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/13-noroll/README b/bin/tests/system/keymgr/13-noroll/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/13-noroll/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/13-noroll/expect b/bin/tests/system/keymgr/13-noroll/expect +new file mode 100644 +index 0000000..40616e1 +--- /dev/null ++++ b/bin/tests/system/keymgr/13-noroll/expect +@@ -0,0 +1,9 @@ ++kargs="-f -c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/14-wrongalg/README b/bin/tests/system/keymgr/14-wrongalg/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/14-wrongalg/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/14-wrongalg/expect b/bin/tests/system/keymgr/14-wrongalg/expect +new file mode 100644 +index 0000000..436f05f +--- /dev/null ++++ b/bin/tests/system/keymgr/14-wrongalg/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=4 +diff --git a/bin/tests/system/keymgr/15-unspec/README b/bin/tests/system/keymgr/15-unspec/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/15-unspec/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/15-unspec/expect b/bin/tests/system/keymgr/15-unspec/expect +new file mode 100644 +index 0000000..b1ff4fc +--- /dev/null ++++ b/bin/tests/system/keymgr/15-unspec/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/16-wrongalg-unspec/README b/bin/tests/system/keymgr/16-wrongalg-unspec/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/16-wrongalg-unspec/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/16-wrongalg-unspec/expect b/bin/tests/system/keymgr/16-wrongalg-unspec/expect +new file mode 100644 +index 0000000..7a21dec +--- /dev/null ++++ b/bin/tests/system/keymgr/16-wrongalg-unspec/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf" ++kmatch="" ++kret=0 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=4 +diff --git a/bin/tests/system/keymgr/17-noforce/README b/bin/tests/system/keymgr/17-noforce/README +new file mode 100644 +index 0000000..8b9dc02 +--- /dev/null ++++ b/bin/tests/system/keymgr/17-noforce/README +@@ -0,0 +1,2 @@ ++This set includes a KSK rollover, with insufficient delay between ++prepublication and rollover. +diff --git a/bin/tests/system/keymgr/17-noforce/expect b/bin/tests/system/keymgr/17-noforce/expect +new file mode 100644 +index 0000000..a5bf1f1 +--- /dev/null ++++ b/bin/tests/system/keymgr/17-noforce/expect +@@ -0,0 +1,9 @@ ++kargs="-c policy.conf example.com" ++kmatch="" ++kret=1 ++cargs="-d 1w -m 2w example.com" ++cmatch="" ++cret=0 ++warn=0 ++error=0 ++ok=2 +diff --git a/bin/tests/system/keymgr/clean.sh b/bin/tests/system/keymgr/clean.sh +new file mode 100644 +index 0000000..66d3d08 +--- /dev/null ++++ b/bin/tests/system/keymgr/clean.sh +@@ -0,0 +1,21 @@ ++#!/bin/sh ++# ++# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++rm -f */K*.key ++rm -f */K*.private ++rm -f coverage.* keymgr.* ++rm -f policy.out ++rm -f random.data +diff --git a/bin/tests/system/keymgr/policy.conf b/bin/tests/system/keymgr/policy.conf +new file mode 100644 +index 0000000..e6b7d98 +--- /dev/null ++++ b/bin/tests/system/keymgr/policy.conf +@@ -0,0 +1,10 @@ ++policy default { ++ policy global; ++ algorithm nsec3rsasha1; ++ key-size zsk 1024; ++ pre-publish zsk 6w; ++ post-publish zsk 6w; ++ roll-period zsk 6mo; ++ roll-period ksk 0; ++ coverage 364d; ++}; +diff --git a/bin/tests/system/keymgr/policy.good b/bin/tests/system/keymgr/policy.good +new file mode 100644 +index 0000000..0038a27 +--- /dev/null ++++ b/bin/tests/system/keymgr/policy.good +@@ -0,0 +1,170 @@ ++policy default: ++ inherits global ++ directory None ++ algorithm None ++ coverage None ++ ksk_keysize None ++ zsk_keysize None ++ ksk_rollperiod None ++ zsk_rollperiod None ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish None ++ zsk_postpublish None ++ ksk_standby None ++ zsk_standby None ++ keyttl None ++ ++policy global: ++ inherits None ++ directory None ++ algorithm RSASHA256 ++ coverage 15552000 ++ ksk_keysize 2048 ++ zsk_keysize 2048 ++ ksk_rollperiod None ++ zsk_rollperiod 31536000 ++ ksk_prepublish 2592000 ++ ksk_postpublish 2592000 ++ zsk_prepublish 2592000 ++ zsk_postpublish 2592000 ++ ksk_standby None ++ zsk_standby None ++ keyttl 3600 ++ ++constructed policy example.com: ++ inherits global ++ directory None ++ algorithm RSASHA256 ++ coverage 15552000 ++ ksk_keysize 2048 ++ zsk_keysize 2048 ++ ksk_rollperiod None ++ zsk_rollperiod 31536000 ++ ksk_prepublish 2592000 ++ ksk_postpublish 2592000 ++ zsk_prepublish 2592000 ++ zsk_postpublish 2592000 ++ ksk_standby None ++ zsk_standby None ++ keyttl None ++ ++policy default: ++ inherits None ++ directory "keydir" ++ algorithm RSASHA1 ++ coverage 31536000 ++ ksk_keysize None ++ zsk_keysize None ++ ksk_rollperiod None ++ zsk_rollperiod 15552000 ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish 3628800 ++ zsk_postpublish 3628800 ++ ksk_standby None ++ zsk_standby None ++ keyttl 3600 ++ ++zone policy example.com: ++ inherits extra ++ directory "keydir" ++ algorithm NSEC3RSASHA1 ++ coverage 12960000 ++ ksk_keysize 2048 ++ zsk_keysize 2048 ++ ksk_rollperiod 31536000 ++ zsk_rollperiod 7776000 ++ ksk_prepublish 7776000 ++ ksk_postpublish None ++ zsk_prepublish 3628800 ++ zsk_postpublish 604800 ++ ksk_standby None ++ zsk_standby None ++ keyttl None ++ ++constructed policy example.org: ++ inherits None ++ directory "keydir" ++ algorithm RSASHA1 ++ coverage 31536000 ++ ksk_keysize 2048 ++ zsk_keysize 1024 ++ ksk_rollperiod None ++ zsk_rollperiod 15552000 ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish 3628800 ++ zsk_postpublish 3628800 ++ ksk_standby None ++ zsk_standby None ++ keyttl 3600 ++ ++constructed policy example.net: ++ inherits None ++ directory "keydir" ++ algorithm RSASHA1 ++ coverage 31536000 ++ ksk_keysize 2048 ++ zsk_keysize 1024 ++ ksk_rollperiod None ++ zsk_rollperiod 15552000 ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish 3628800 ++ zsk_postpublish 3628800 ++ ksk_standby None ++ zsk_standby None ++ keyttl 3600 ++ ++algorithm policy RSASHA1: ++ inherits None ++ directory None ++ algorithm None ++ coverage None ++ ksk_keysize 2048 ++ zsk_keysize 1024 ++ ksk_rollperiod None ++ zsk_rollperiod None ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish None ++ zsk_postpublish None ++ ksk_standby None ++ zsk_standby None ++ keyttl None ++ ++algorithm policy DSA: ++ inherits None ++ directory None ++ algorithm DSA ++ coverage None ++ ksk_keysize 1024 ++ zsk_keysize 2048 ++ ksk_rollperiod None ++ zsk_rollperiod None ++ ksk_prepublish None ++ ksk_postpublish None ++ zsk_prepublish None ++ zsk_postpublish None ++ ksk_standby None ++ zsk_standby None ++ keyttl None ++ ++policy extra: ++ inherits default ++ directory None ++ algorithm None ++ coverage 157680000 ++ ksk_keysize None ++ zsk_keysize None ++ ksk_rollperiod 31536000 ++ zsk_rollperiod 7776000 ++ ksk_prepublish 7776000 ++ ksk_postpublish None ++ zsk_prepublish None ++ zsk_postpublish 604800 ++ ksk_standby None ++ zsk_standby None ++ keyttl 7200 ++ +diff --git a/bin/tests/system/keymgr/policy.sample b/bin/tests/system/keymgr/policy.sample +new file mode 100644 +index 0000000..d96a40d +--- /dev/null ++++ b/bin/tests/system/keymgr/policy.sample +@@ -0,0 +1,40 @@ ++# a comment which should be skipped ++ ++algorithm-policy rsasha1 { ++ key-size ksk 2048; ++ key-size zsk 1024; // this too ++}; ++ ++// and this ++ ++policy default { ++ directory "keydir"; ++ algorithm rsasha1; ++ coverage 1y; # another comment ++ roll-period zsk 6mo; // and yet another ++ pre-publish zsk 6w; ++ post-publish zsk 6w; ++ keyttl 1h; ++}; ++ ++policy extra { ++ policy default; ++ coverage 5y; ++ roll-period KSK 1 year; ++ roll-period zsk 3mo; ++ pre-publish ksk 3mo; ++ post-publish zsk 1w; ++ keyttl 2h; ++}; ++ ++/* ++ * and this is also a comment, ++ * and it should be ignored like ++ * the others. ++ */ ++ ++zone example.com { ++ policy extra; ++ coverage 5 mon; ++ algorithm nsec3rsasha1; ++}; +diff --git a/bin/tests/system/keymgr/prereq.sh b/bin/tests/system/keymgr/prereq.sh +new file mode 100644 +index 0000000..be2546e +--- /dev/null ++++ b/bin/tests/system/keymgr/prereq.sh +@@ -0,0 +1,30 @@ ++#!/bin/sh ++# ++# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++ ++../../../tools/genrandom 400 random.data ++ ++if $KEYGEN -q -a RSAMD5 -b 512 -n zone -r random.data foo > /dev/null 2>&1 ++then ++ rm -f Kfoo* ++else ++ echo "I:This test requires cryptography" >&2 ++ echo "I:--with-openssl, or --with-pkcs11 and --enable-native-pkcs11" >&2 ++ exit 1 ++fi ++#exec $SHELL ../testcrypto.sh +diff --git a/bin/tests/system/keymgr/setup.sh b/bin/tests/system/keymgr/setup.sh +new file mode 100644 +index 0000000..0483f51 +--- /dev/null ++++ b/bin/tests/system/keymgr/setup.sh +@@ -0,0 +1,214 @@ ++#!/bin/sh ++# ++# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++ ++RANDFILE=random.data ++KEYGEN="$KEYGEN -qr $RANDFILE" ++ ++$SHELL clean.sh ++../../../tools/genrandom 400 $RANDFILE ++ ++# Test 1: KSK goes inactive before successor is active ++dir=01-ksk-inactive ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 ++ksk2=`$KEYGEN -K $dir -S $ksk1` ++$SETTIME -K $dir -I +7mo $ksk1 > /dev/null 2>&1 ++zsk1=`$KEYGEN -K $dir -3 example.com` ++ ++# Test 2: ZSK goes inactive before successor is active ++dir=02-zsk-inactive ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++zsk1=`$KEYGEN -K $dir -3 example.com` ++$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 ++zsk2=`$KEYGEN -K $dir -S $zsk1` ++$SETTIME -K $dir -I +7mo $zsk1 > /dev/null 2>&1 ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++ ++# Test 3: KSK is unpublished before its successor is published ++dir=03-ksk-unpublished ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 ++ksk2=`$KEYGEN -K $dir -S $ksk1` ++$SETTIME -K $dir -D +6mo $ksk1 > /dev/null 2>&1 ++zsk1=`$KEYGEN -K $dir -3 example.com` ++ ++# Test 4: ZSK is unpublished before its successor is published ++dir=04-zsk-unpublished ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++zsk1=`$KEYGEN -K $dir -3 example.com` ++$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 ++zsk2=`$KEYGEN -K $dir -S $zsk1` ++$SETTIME -K $dir -D +6mo $zsk1 > /dev/null 2>&1 ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++ ++# Test 5: KSK deleted and successor published before KSK is deactivated ++# and successor activated. ++dir=05-ksk-unpub-active ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++$SETTIME -K $dir -I +9mo -D +8mo $ksk1 > /dev/null 2>&1 ++ksk2=`$KEYGEN -K $dir -S $ksk1` ++zsk1=`$KEYGEN -K $dir -3 example.com` ++ ++# Test 6: ZSK deleted and successor published before ZSK is deactivated ++# and successor activated. ++dir=06-zsk-unpub-active ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++zsk1=`$KEYGEN -K $dir -3 example.com` ++$SETTIME -K $dir -I +9mo -D +8mo $zsk1 > /dev/null 2>&1 ++zsk2=`$KEYGEN -K $dir -S $zsk1` ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++ ++# Test 7: KSK rolled with insufficient delay after prepublication. ++dir=07-ksk-ttl ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 ++ksk2=`$KEYGEN -K $dir -S $ksk1` ++$SETTIME -K $dir -P +269d $ksk2 > /dev/null 2>&1 ++zsk1=`$KEYGEN -K $dir -3 example.com` ++ ++# Test 8: ZSK rolled with insufficient delay after prepublication. ++dir=08-zsk-ttl ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++zsk1=`$KEYGEN -K $dir -3 example.com` ++$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 ++zsk2=`$KEYGEN -K $dir -S $zsk1` ++# allow only 1 day between publication and activation ++$SETTIME -K $dir -P +269d $zsk2 > /dev/null 2>&1 ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++ ++# Test 9: No special preparation needed ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ ++# Test 10: Valid key set, but rollover period has changed ++dir=10-change-roll ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++ksk1=`$KEYGEN -K $dir -3fk example.com` ++zsk1=`$KEYGEN -K $dir -3 example.com` ++$SETTIME -K $dir -I +3mo -D +4mo $zsk1 > /dev/null 2>&1 ++zsk2=`$KEYGEN -K $dir -S $zsk1` ++ ++# Test 11: Many keys all simultaneously scheduled to be active in the future ++dir=11-many-simul ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -q3fk -P now+1mo -A now+1mo example.com` ++z1=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` ++z2=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` ++z3=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` ++z4=`$KEYGEN -K $dir -q3 -P now+1mo -A now+1mo example.com` ++ ++# Test 12: Many keys all simultaneously scheduled to be active in the past ++dir=12-many-active ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -q3fk example.com` ++z1=`$KEYGEN -K $dir -q3 example.com` ++z2=`$KEYGEN -K $dir -q3 example.com` ++z3=`$KEYGEN -K $dir -q3 example.com` ++z4=`$KEYGEN -K $dir -q3 example.com` ++ ++# Test 13: Multiple simultaneous keys with no configured roll period ++dir=13-noroll ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -q3fk example.com` ++k2=`$KEYGEN -K $dir -q3fk example.com` ++k3=`$KEYGEN -K $dir -q3fk example.com` ++z1=`$KEYGEN -K $dir -q3 example.com` ++ ++# Test 14: Keys exist but have the wrong algorithm ++dir=14-wrongalg ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -qfk example.com` ++z1=`$KEYGEN -K $dir -q example.com` ++$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null ++z2=`$KEYGEN -K $dir -q -S ${z1}.key` ++$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null ++z3=`$KEYGEN -K $dir -q -S ${z2}.key` ++$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null ++z4=`$KEYGEN -K $dir -q -S ${z3}.key` ++ ++# Test 15: No zones specified; just search the directory for keys ++dir=15-unspec ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -q3fk example.com` ++z1=`$KEYGEN -K $dir -q3 example.com` ++$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null ++z2=`$KEYGEN -K $dir -q -S ${z1}.key` ++$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null ++z3=`$KEYGEN -K $dir -q -S ${z2}.key` ++$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null ++z4=`$KEYGEN -K $dir -q -S ${z3}.key` ++ ++# Test 16: No zones specified; search the directory for keys; ++# keys have the wrong algorithm for their policies ++dir=16-wrongalg-unspec ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -qfk example.com` ++z1=`$KEYGEN -K $dir -q example.com` ++$SETTIME -K $dir -I now+6mo -D now+8mo $z1 > /dev/null ++z2=`$KEYGEN -K $dir -q -S ${z1}.key` ++$SETTIME -K $dir -I now+1y -D now+14mo $z2 > /dev/null ++z3=`$KEYGEN -K $dir -q -S ${z2}.key` ++$SETTIME -K $dir -I now+18mo -D now+20mo $z3 > /dev/null ++z4=`$KEYGEN -K $dir -q -S ${z3}.key` ++ ++# Test 17: Keys are simultaneously active but we run with no force ++# flag (this should fail) ++dir=17-noforce ++echo I:set up $dir ++rm -f $dir/K*.key ++rm -f $dir/K*.private ++k1=`$KEYGEN -K $dir -q3fk example.com` ++z1=`$KEYGEN -K $dir -q3 example.com` ++z2=`$KEYGEN -K $dir -q3 example.com` ++z3=`$KEYGEN -K $dir -q3 example.com` ++z4=`$KEYGEN -K $dir -q3 example.com` +diff --git a/bin/tests/system/keymgr/testpolicy.py b/bin/tests/system/keymgr/testpolicy.py +new file mode 100644 +index 0000000..2dec7ff +--- /dev/null ++++ b/bin/tests/system/keymgr/testpolicy.py +@@ -0,0 +1,29 @@ ++#!/bin/python ++import sys ++sys.path.insert(0, '../../../python') ++from isc import * ++ ++pp = policy.dnssec_policy() ++# print the unmodified default and a generated zone policy ++print pp.named_policy['default'] ++print pp.named_policy['global'] ++print pp.policy('example.com') ++ ++if len(sys.argv) > 0: ++ for policy_file in sys.argv[1:]: ++ pp.load(policy_file) ++ ++ # now print the modified default and generated zone policies ++ print pp.named_policy['default'] ++ print pp.policy('example.com') ++ print pp.policy('example.org') ++ print pp.policy('example.net') ++ ++ # print algorithm policies ++ print pp.alg_policy['RSASHA1'] ++ print pp.alg_policy['DSA'] ++ ++ # print another named policy ++ print pp.named_policy['extra'] ++else: ++ print("ERROR: Please provide an input file") +diff --git a/bin/tests/system/keymgr/tests.sh b/bin/tests/system/keymgr/tests.sh +new file mode 100644 +index 0000000..f598f0a +--- /dev/null ++++ b/bin/tests/system/keymgr/tests.sh +@@ -0,0 +1,106 @@ ++#!/bin/sh ++# ++# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++ ++status=0 ++n=1 ++ ++matchall () { ++ file=$1 ++ echo "$2" | while read matchline; do ++ grep "$matchline" $file > /dev/null 2>&1 || { ++ echo "FAIL" ++ return ++ } ++ done ++} ++ ++echo "I:checking for DNSSEC key coverage issues" ++ret=0 ++for dir in [0-9][0-9]-*; do ++ ret=0 ++ echo "I:$dir ($n)" ++ kargs= cargs= kmatch= cmatch= kret= cret=0 warn= error= ok= ++ . $dir/expect ++ ++ # run keymgr to update keys ++ $KEYMGR -K $dir -s $SETTIME $kargs > keymgr.$n 2>&1 ++ # check that return code matches expectations ++ found=$? ++ if [ $found -ne $kret ]; then ++ echo "keymgr retcode was $found expected $kret" ++ ret=1 ++ fi ++ ++ found=`matchall keymgr.$n "$kmatch"` ++ if [ "$found" = "FAIL" ]; then ++ echo "no match on '$kmatch'" ++ ret=1 ++ fi ++ ++ # now check coverage ++ $COVERAGE -K $dir $cargs > coverage.$n 2>&1 ++ # check that return code matches expectations ++ found=$? ++ if [ $found -ne $cret ]; then ++ echo "coverage retcode was $found expected $cret" ++ ret=1 ++ fi ++ ++ # check for correct number of errors ++ found=`grep ERROR coverage.$n | wc -l` ++ if [ $found -ne $error ]; then ++ echo "error count was $found expected $error" ++ ret=1 ++ fi ++ ++ # check for correct number of warnings ++ found=`grep WARNING coverage.$n | wc -l` ++ if [ $found -ne $warn ]; then ++ echo "warning count was $found expected $warn" ++ ret=1 ++ fi ++ ++ # check for correct number of OKs ++ found=`grep "No errors found" coverage.$n | wc -l` ++ if [ $found -ne $ok ]; then ++ echo "good count was $found expected $ok" ++ ret=1 ++ fi ++ ++ found=`matchall coverage.$n "$cmatch"` ++ if [ "$found" = "FAIL" ]; then ++ echo "no match on '$cmatch'" ++ ret=1 ++ fi ++ ++ n=`expr $n + 1` ++ if [ $ret != 0 ]; then echo "I:failed"; fi ++ status=`expr $status + $ret` ++done ++ ++echo "I:checking policy.conf parser ($n)" ++ret=0 ++${PYTHON} testpolicy.py policy.sample > policy.out ++cmp -s policy.good policy.out || ret=1 ++if [ $ret != 0 ]; then echo "I:failed"; fi ++status=`expr $status + $ret` ++n=`expr $n + 1` ++ ++echo "I:exit status: $status" ++exit $status +diff --git a/configure b/configure +index 31c518a..a299aac 100755 +--- a/configure ++++ b/configure +@@ -1372,7 +1372,9 @@ ISC_PLATFORM_NORETURN_POST + ISC_PLATFORM_NORETURN_PRE + ISC_PLATFORM_HAVELONGLONG + ISC_SOCKADDR_LEN_T ++expanded_sysconfdir + PYTHON_TOOLS ++KEYMGR + COVERAGE + CHECKDS + PYTHON +@@ -12270,15 +12272,18 @@ esac + PYTHON_TOOLS='' + CHECKDS='' + COVERAGE='' ++KEYMGR='' + if test "X$PYTHON" != "X"; then + PYTHON_TOOLS=python + CHECKDS=checkds + COVERAGE=coverage ++ KEYMGR=keymgr + fi + + + + ++ + # + # Special processing of paths depending on whether --prefix, + # --sysconfdir or --localstatedir arguments were given. What's +@@ -12313,6 +12318,8 @@ case "$prefix" in + esac + ;; + esac ++expanded_sysconfdir=`eval echo $sysconfdir` ++ + + # + # Make sure INSTALL uses an absolute path, else it will be wrong in all +@@ -22273,8 +22280,12 @@ do + "bin/nsupdate/Makefile") CONFIG_FILES="$CONFIG_FILES bin/nsupdate/Makefile" ;; + "bin/pkcs11/Makefile") CONFIG_FILES="$CONFIG_FILES bin/pkcs11/Makefile" ;; + "bin/python/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/Makefile" ;; ++ "bin/python/isc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/Makefile" ;; ++ "bin/python/isc/utils.py") CONFIG_FILES="$CONFIG_FILES bin/python/isc/utils.py" ;; ++ "bin/python/isc/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/isc/tests/Makefile" ;; + "bin/python/dnssec-checkds.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-checkds.py" ;; + "bin/python/dnssec-coverage.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-coverage.py" ;; ++ "bin/python/dnssec-keymgr.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-keymgr.py" ;; + "bin/rndc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/rndc/Makefile" ;; + "bin/sdb_tools/Makefile") CONFIG_FILES="$CONFIG_FILES bin/sdb_tools/Makefile" ;; + "bin/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/Makefile" ;; +diff --git a/configure.in b/configure.in +index 529989d..fb2e53e 100644 +--- a/configure.in ++++ b/configure.in +@@ -197,13 +197,16 @@ esac + PYTHON_TOOLS='' + CHECKDS='' + COVERAGE='' ++KEYMGR='' + if test "X$PYTHON" != "X"; then + PYTHON_TOOLS=python + CHECKDS=checkds + COVERAGE=coverage ++ KEYMGR=keymgr + fi + AC_SUBST(CHECKDS) + AC_SUBST(COVERAGE) ++AC_SUBST(KEYMGR) + AC_SUBST(PYTHON_TOOLS) + + # +@@ -240,6 +243,8 @@ case "$prefix" in + esac + ;; + esac ++expanded_sysconfdir=`eval echo $sysconfdir` ++AC_SUBST(expanded_sysconfdir) + + # + # Make sure INSTALL uses an absolute path, else it will be wrong in all +@@ -4042,8 +4047,12 @@ AC_CONFIG_FILES([ + bin/nsupdate/Makefile + bin/pkcs11/Makefile + bin/python/Makefile ++ bin/python/isc/Makefile ++ bin/python/isc/utils.py ++ bin/python/isc/tests/Makefile + bin/python/dnssec-checkds.py + bin/python/dnssec-coverage.py ++ bin/python/dnssec-keymgr.py + bin/rndc/Makefile + bin/tests/Makefile + bin/tests/atomic/Makefile +diff --git a/contrib/kasp/README b/contrib/kasp/README +new file mode 100644 +index 0000000..fb897f1 +--- /dev/null ++++ b/contrib/kasp/README +@@ -0,0 +1,11 @@ ++This directory is for tools and scripts related to the OpenDNSSEC KASP ++("key and signature policy") format. Currently it only contains ++"kasp2policy.py", a python script for converting KASP key policy ++to the "dnssec.policy" format that is used by dnssec-keymgr. ++ ++This depends on PLY (python lex/yacc) and on the "isc.dnskey" module in ++bin/python/isc. ++ ++Basic test: ++$ python kasp2policy.py kasp.xml > policy.out ++$ diff policy.out policy.good +diff --git a/contrib/kasp/kasp.xml b/contrib/kasp/kasp.xml +new file mode 100644 +index 0000000..d94b084 +--- /dev/null ++++ b/contrib/kasp/kasp.xml +@@ -0,0 +1,134 @@ ++ ++ ++ ++ ++ ++ A default policy that will ++ amaze you and your friends ++ ++ PT5M ++ PT5M ++ ++ PT15M ++ PT15M ++ ++ PT2M ++ PT1M ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ PT1M ++ PT0S ++ PT0S ++ ++ ++ ++ 5 ++ PT40M ++ softHSM ++ 1 ++ ++ ++ ++ ++ 5 ++ PT25M ++ softHSM ++ 1 ++ ++ ++ ++ ++ PT0S ++ ++ PT0S ++ PT0S ++ unixtime ++ ++ ++ ++ ++ PT8M ++ ++ PT0S ++ ++ ++ PT0S ++ PT0S ++ ++ ++ ++ ++ A default policy that will amaze you and your friends ++ ++ PT7M ++ PT7M ++ ++ PT15M ++ PT16M ++ ++ PT2M ++ PT1M ++ ++ ++ ++ ++ P120D ++ ++ 1 ++ 5 ++ ++ ++ ++ ++ ++ ++ ++ PT15M ++ PT0S ++ PT0S ++ ++ ++ ++ 7 ++ PT45M ++ softHSM ++ 1 ++ ++ ++ ++ ++ 7 ++ PT25M ++ softHSM ++ 1 ++ ++ ++ ++ ++ PT0S ++ ++ PT0S ++ PT0S ++ unixtime ++ ++ ++ ++ ++ PT12M ++ ++ PT0S ++ ++ ++ PT0S ++ PT0S ++ ++ ++ ++ +diff --git a/contrib/kasp/kasp2policy.py b/contrib/kasp/kasp2policy.py +new file mode 100644 +index 0000000..b78a968 +--- /dev/null ++++ b/contrib/kasp/kasp2policy.py +@@ -0,0 +1,209 @@ ++#!/usr/bin/python ++############################################################################ ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++############################################################################ ++# kasp2policy.py ++# This translates the Keys section of a KASP XML file into a dnssec.policy ++# file that can be used by dnssec-keymgr. ++############################################################################ ++ ++from xml.etree import cElementTree as ET ++from collections import defaultdict ++from isc import dnskey ++import ply.yacc as yacc ++import ply.lex as lex ++import re ++ ++############################################################################ ++# Translate KASP duration values into seconds ++############################################################################ ++class kasptime: ++ class ktlex: ++ tokens = ( 'P', 'T', 'Y', 'M', 'D', 'H', 'S', 'NUM' ) ++ ++ t_P = r'(?i)P' ++ t_T = r'(?i)T' ++ t_Y = r'(?i)Y' ++ t_M = r'(?i)M' ++ t_D = r'(?i)D' ++ t_H = r'(?i)H' ++ t_S = r'(?i)S' ++ ++ def t_NUM(self, t): ++ r'\d+' ++ t.value = int(t.value) ++ return t ++ ++ def t_error(self, t): ++ print("Illegal character '%s'" % t.value[0]) ++ t.lexer.skip(1) ++ ++ def __init__(self): ++ self.lexer = lex.lex(object=self) ++ ++ def __init__(self): ++ self.lexer = self.ktlex() ++ self.tokens = self.lexer.tokens ++ self.parser = yacc.yacc(debug=False, write_tables=False, module=self) ++ ++ def parse(self, text): ++ self.lexer.lexer.lineno = 0 ++ return self.parser.parse(text) ++ ++ def p_ktime_4(self, p): ++ "ktime : P periods T times" ++ p[0] = p[2] + p[4] ++ ++ def p_ktime_3(self, p): ++ "ktime : P T times" ++ p[0] = p[3] ++ ++ def p_ktime_2(self, p): ++ "ktime : P periods" ++ p[0] = p[2] ++ ++ def p_periods_1(self, p): ++ "periods : period" ++ p[0] = p[1] ++ ++ def p_periods_2(self, p): ++ "periods : periods period" ++ p[0] = p[1] + p[2] ++ ++ def p_times_1(self, p): ++ "times : time" ++ p[0] = p[1] ++ ++ def p_times_2(self, p): ++ "times : times time" ++ p[0] = p[1] + p[2] ++ ++ def p_period(self, p): ++ '''period : NUM Y ++ | NUM M ++ | NUM D''' ++ if p[2].lower() == 'y': ++ p[0] = int(p[1]) * 31536000 ++ elif p[2].lower() == 'm': ++ p[0] = int(p[1]) * 2592000 ++ elif p[2].lower() == 'd': ++ p[0] += int(p[1]) * 86400 ++ ++ def p_time(self, p): ++ '''time : NUM H ++ | NUM M ++ | NUM S''' ++ if p[2].lower() == 'h': ++ p[0] = int(p[1]) * 3600 ++ elif p[2].lower() == 'm': ++ p[0] = int(p[1]) * 60 ++ elif p[2].lower() == 's': ++ p[0] = int(p[1]) ++ ++ def p_error(self, p): ++ print("Syntax error") ++ ++############################################################################ ++# Load the contents of a KASP XML file as a python dictionary ++############################################################################ ++class kasp(): ++ @staticmethod ++ def _todict(t): ++ d = {t.tag: {} if t.attrib else None} ++ children = list(t) ++ if children: ++ dd = defaultdict(list) ++ for dc in map(kasp._todict, children): ++ for k, v in dc.iteritems(): ++ dd[k].append(v) ++ d = {t.tag: ++ {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} ++ if t.attrib: ++ d[t.tag].update(('@' + k, v) for k, v in t.attrib.iteritems()) ++ if t.text: ++ text = t.text.strip() ++ if children or t.attrib: ++ if text: ++ d[t.tag]['#text'] = text ++ else: ++ d[t.tag] = text ++ return d ++ ++ def __init__(self, filename): ++ self._dict = kasp._todict(ET.parse(filename).getroot()) ++ ++ def __getitem__(self, key): ++ return self._dict[key] ++ ++ def __len__(self): ++ return len(self._dict) ++ ++ def __iter__(self): ++ return self._dict.__iter__() ++ ++ def __repr__(self): ++ return repr(self._dict) ++ ++############################################################################ ++# Load the contents of a KASP XML file as a python dictionary ++############################################################################ ++if __name__ == "__main__": ++ from pprint import * ++ import sys ++ ++ if len(sys.argv) < 2: ++ print("Usage: kasp2policy ") ++ exit(1) ++ ++ try: ++ kinfo = kasp(sys.argv[1]) ++ except: ++ print("%s: unable to load KASP file '%s'" % (sys.argv[0], sys.argv[1])) ++ exit(1) ++ ++ kt = kasptime() ++ first = True ++ ++ for p in kinfo['KASP']['Policy']: ++ if not p['@name'] or not p['Keys']: continue ++ if not first: ++ print("") ++ first = False ++ if p['Description']: ++ d = p['Description'].strip() ++ print("# %s" % re.sub(r"\n\s*", "\n# ", d)) ++ print("policy %s {" % p['@name']) ++ ksk = p['Keys']['KSK'] ++ zsk = p['Keys']['ZSK'] ++ kalg = ksk['Algorithm'] ++ zalg = zsk['Algorithm'] ++ algnum = kalg['#text'] or zalg['#text'] ++ if algnum: ++ print("\talgorithm %s;" % dnskey.algstr(int(algnum))) ++ if p['Keys']['TTL']: ++ print("\tkeyttl %d;" % kt.parse(p['Keys']['TTL'])) ++ if kalg['@length']: ++ print("\tkey-size ksk %d;" % int(kalg['@length'])) ++ if zalg['@length']: ++ print("\tkey-size zsk %d;" % int(zalg['@length'])) ++ if ksk['Lifetime']: ++ print("\troll-period ksk %d;" % kt.parse(ksk['Lifetime'])) ++ if zsk['Lifetime']: ++ print("\troll-period zsk %d;" % kt.parse(zsk['Lifetime'])) ++ if ksk['Standby']: ++ print("\tstandby ksk %d;" % int(ksk['Standby'])) ++ if zsk['Standby']: ++ print("\tstandby zsk %d;" % int(zsk['Standby'])) ++ print("};") +diff --git a/contrib/kasp/policy.good b/contrib/kasp/policy.good +new file mode 100644 +index 0000000..18c6360 +--- /dev/null ++++ b/contrib/kasp/policy.good +@@ -0,0 +1,24 @@ ++# A default policy that will ++# amaze you and your friends ++policy Policy1 { ++ algorithm RSASHA1; ++ keyttl 60; ++ key-size ksk 2048; ++ key-size zsk 2048; ++ roll-period ksk 2400; ++ roll-period zsk 1500; ++ standby ksk 1; ++ standby zsk 1; ++}; ++ ++# A default policy that will amaze you and your friends ++policy Policy2 { ++ algorithm NSEC3RSASHA1; ++ keyttl 900; ++ key-size ksk 2048; ++ key-size zsk 2048; ++ roll-period ksk 2700; ++ roll-period zsk 1500; ++ standby ksk 1; ++ standby zsk 1; ++}; +diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml +new file mode 100644 +index 0000000..07776b0 +--- /dev/null ++++ b/doc/arm/notes.xml +@@ -0,0 +1,714 @@ ++ ++ ++ ++ ++]> ++ ++ ++
++ ++
Introduction ++ ++ BIND 9.11.0 is a new feature release of BIND, still under development. ++ This document summarizes new features and functional changes that ++ have been introduced on this branch. With each development ++ release leading up to the final BIND 9.11.0 release, this document ++ will be updated with additional features added and bugs fixed. ++ ++
++ ++
Download ++ ++ The latest versions of BIND 9 software can always be found at ++ http://www.isc.org/downloads/. ++ There you will find additional information about each release, ++ source code, and pre-compiled versions for Microsoft Windows ++ operating systems. ++ ++
++ ++
Security Fixes ++ ++ ++ ++ None. ++ ++ ++ ++
++ ++
New Features ++ ++ ++ ++ Added support for DynDB, a new interface for loading zone data ++ from an external database, developed by Red Hat for the FreeIPA ++ project. (Thanks in particular to Adam Tkac and Petr ++ Spacek of Red Hat for the contribution.) ++ ++ ++ Unlike the existing DLZ and SDB interfaces, which provide a ++ limited subset of database functionality within BIND — ++ translating DNS queries into real-time database lookups with ++ relatively poor performance and with no ability to handle ++ DNSSEC-signed data — DynDB is able to fully implement ++ and extend the database API used natively by BIND. ++ ++ ++ A DynDB module could pre-load data from an external data ++ source, then serve it with the same performance and ++ functionality as conventional BIND zones, and with the ++ ability to take advantage of database features not ++ available in BIND, such as multi-master replication. ++ ++ ++ ++ ++ New quotas have been added to limit the queries that are ++ sent by recursive resolvers to authoritative servers ++ experiencing denial-of-service attacks. When configured, ++ these options can both reduce the harm done to authoritative ++ servers and also avoid the resource exhaustion that can be ++ experienced by recursives when they are being used as a ++ vehicle for such an attack. ++ ++ ++ ++ ++ limits the number of ++ simultaneous queries that can be sent to any single ++ authoritative server. The configured value is a starting ++ point; it is automatically adjusted downward if the server is ++ partially or completely non-responsive. The algorithm used to ++ adjust the quota can be configured via the ++ option. ++ ++ ++ ++ ++ limits the number of ++ simultaneous queries that can be sent for names within a ++ single domain. (Note: Unlike "fetches-per-server", this ++ value is not self-tuning.) ++ ++ ++ ++ ++ Statistics counters have also been added to track the number ++ of queries affected by these quotas. ++ ++ ++ ++ ++ Added support for dnstap, a fast, ++ flexible method for capturing and logging DNS traffic, ++ developed by Robert Edmonds at Farsight Security, Inc., ++ whose assistance is gratefully acknowledged. ++ ++ ++ To enable dnstap at compile time, ++ the fstrm and protobuf-c ++ libraries must be available, and BIND must be configured with ++ . ++ ++ ++ A new utility dnstap-read has been added ++ to allow dnstap data to be presented in ++ a human-readable format. ++ ++ ++ For more information on dnstap, see ++ http://dnstap.info. ++ ++ ++ ++ ++ New statistics counters have been added to track traffic ++ sizes, as specified in RSSAC002. Query and response ++ message sizes are broken up into ranges of histogram buckets: ++ TCP and UDP queries of size 0-15, 16-31, ..., 272-288, and 288+, ++ and TCP and UDP responses of size 0-15, 16-31, ..., 4080-4095, ++ and 4096+. These values can be accessed via the XML and JSON ++ statistics channels at, for example, ++ http://localhost:8888/xml/v3/traffic ++ or ++ http://localhost:8888/json/v1/traffic. ++ ++ ++ ++ ++ A new DNSSEC key management utility, ++ dnssec-keymgr, has been added. This tool ++ is meant to run unattended (e.g., under cron). ++ It reads a policy definition file ++ (default: /etc/dnssec.policy) ++ and creates or updates DNSSEC keys as necessary to ensure that a ++ zone's keys match the defined policy for that zone. New keys are ++ created whenever necessary to ensure rollovers occur correctly. ++ Existing keys' timing metadata is adjusted as needed to set the ++ correct rollover period, prepublication interval, etc. If ++ the configured policy changes, keys are corrected automatically. ++ See the dnssec-keymgr man page for full details. ++ ++ ++ Note: dnssec-keymgr depends on Python and on ++ the Python lex/yacc module, PLY. The other Python-based tools, ++ dnssec-coverage and ++ dnssec-checkds, have been ++ refactored and updated as part of this work. ++ ++ ++ (Many thanks to Sebastián ++ Castro for his assistance in developing this tool at the IETF ++ 95 Hackathon in Buenos Aires, April 2016.) ++ ++ ++ ++ ++ The serial number of a dynamically updatable zone can ++ now be set using ++ rndc signing -serial number zonename. ++ This is particularly useful with ++ zones that have been reset. Setting the serial number to a value ++ larger than that on the slaves will trigger an AXFR-style ++ transfer. ++ ++ ++ ++ ++ When answering recursive queries, SERVFAIL responses can now be ++ cached by the server for a limited time; subsequent queries for ++ the same query name and type will return another SERVFAIL until ++ the cache times out. This reduces the frequency of retries ++ when a query is persistently failing, which can be a burden ++ on recursive serviers. The SERVFAIL cache timeout is controlled ++ by , which defaults to 1 second ++ and has an upper limit of 30. ++ ++ ++ ++ ++ The new rndc nta command can now be used to ++ set a "negative trust anchor" (NTA), disabling DNSSEC validation for ++ a specific domain; this can be used when responses from a domain ++ are known to be failing validation due to administrative error ++ rather than because of a spoofing attack. NTAs are strictly ++ temporary; by default they expire after one hour, but can be ++ configured to last up to one week. The default NTA lifetime ++ can be changed by setting the in ++ named.conf. When added, NTAs are stored in a ++ file (viewname.nta) ++ in order to persist across restarts of the named server. ++ ++ ++ ++ ++ The EDNS Client Subnet (ECS) option is now supported for ++ authoritative servers; if a query contains an ECS option then ++ ACLs containing or ++ elements can match against the address encoded in the option. ++ This can be used to select a view for a query, so that different ++ answers can be provided depending on the client network. ++ ++ ++ ++ ++ The EDNS EXPIRE option has been implemented on the client ++ side, allowing a slave server to set the expiration timer ++ correctly when transferring zone data from another slave ++ server. ++ ++ ++ ++ ++ A new zone option controls ++ the formatting of text zone files: When set to ++ full, the zone file will dumped in ++ single-line-per-record format. ++ ++ ++ ++ ++ dig +ednsopt can now be used to set ++ arbitrary EDNS options in DNS requests. ++ ++ ++ ++ ++ dig +ednsflags can now be used to set ++ yet-to-be-defined EDNS flags in DNS requests. ++ ++ ++ ++ ++ dig +[no]ednsnegotiation can now be used enable / ++ disable EDNS version negotiation. ++ ++ ++ ++ ++ dig +header-only can now be used to send ++ queries without a question section. ++ ++ ++ ++ ++ dig +ttlunits causes dig ++ to print TTL values with time-unit suffixes: w, d, h, m, s for ++ weeks, days, hours, minutes, and seconds. ++ ++ ++ ++ ++ dig +zflag can be used to set the last ++ unassigned DNS header flag bit. This bit is normally zero. ++ ++ ++ ++ ++ dig +dscp=value ++ can now be used to set the DSCP code point in outgoing query ++ packets. ++ ++ ++ ++ ++ dig +mapped can now be used to determine ++ if mapped IPv4 addresses can be used. ++ ++ ++ ++ ++ can now be set to ++ date. On update, the serial number will ++ be set to the current date in YYYYMMDDNN format. ++ ++ ++ ++ ++ dnssec-signzone -N date also sets the serial ++ number to YYYYMMDDNN. ++ ++ ++ ++ ++ named -L filename ++ causes named to send log messages to the ++ specified file by default instead of to the system log. ++ ++ ++ ++ ++ The rate limiter configured by the ++ option no longer covers ++ NOTIFY messages; those are now separately controlled by ++ and ++ (the latter of which ++ controls the rate of NOTIFY messages sent when the server ++ is first started up or reconfigured). ++ ++ ++ ++ ++ The default number of tasks and client objects available ++ for serving lightweight resolver queries have been increased, ++ and are now configurable via the new ++ and options in ++ named.conf. [RT #35857] ++ ++ ++ ++ ++ Log output to files can now be buffered by specifying ++ buffered yes; when creating a channel. ++ ++ ++ ++ ++ delv +tcp will exclusively use TCP when ++ sending queries. ++ ++ ++ ++ ++ named will now check to see whether ++ other name server processes are running before starting up. ++ This is implemented in two ways: 1) by refusing to start ++ if the configured network interfaces all return "address ++ in use", and 2) by attempting to acquire a lock on a file ++ specified by the option or ++ the -X command line option. The ++ default lock file is ++ /var/run/named/named.lock. ++ Specifying none will disable the lock ++ file check. ++ ++ ++ ++ ++ rndc delzone can now be applied to zones ++ which were configured in named.conf; ++ it is no longer restricted to zones which were added by ++ rndc addzone. (Note, however, that ++ this does not edit named.conf; the zone ++ must be removed from the configuration or it will return ++ when named is restarted or reloaded.) ++ ++ ++ ++ ++ rndc modzone can be used to reconfigure ++ a zone, using similar syntax to rndc addzone. ++ ++ ++ ++ ++ rndc showzone displays the current ++ configuration for a specified zone. ++ ++ ++ ++ ++ Added server-side support for pipelined TCP queries. Clients ++ may continue sending queries via TCP while previous queries are ++ processed in parallel. Responses are sent when they are ++ ready, not necessarily in the order in which the queries were ++ received. ++ ++ ++ To revert to the former behavior for a particular ++ client address or range of addresses, specify the address prefix ++ in the "keep-response-order" option. To revert to the former ++ behavior for all clients, use "keep-response-order { any; };". ++ ++ ++ ++ ++ The new mdig command is a version of ++ dig that sends multiple pipelined ++ queries and then waits for responses, instead of sending one ++ query and waiting the response before sending the next. [RT #38261] ++ ++ ++ ++ ++ To enable better monitoring and troubleshooting of RFC 5011 ++ trust anchor management, the new rndc managed-keys ++ can be used to check status of trust anchors or to force keys ++ to be refreshed. Also, the managed-keys data file now has ++ easier-to-read comments. [RT #38458] ++ ++ ++ ++ ++ An --enable-querytrace configure switch is ++ now available to enable very verbose query tracelogging. This ++ option can only be set at compile time. This option has a ++ negative performance impact and should be used only for ++ debugging. [RT #37520] ++ ++ ++ ++ ++ A new tcp-only option can be specified ++ in server statements to force ++ named to connect to the specified ++ server via TCP. [RT #37800] ++ ++ ++ ++ ++ The nxdomain-redirect option specifies ++ a DNS namespace to use for NXDOMAIN redirection. When a ++ recursive lookup returns NXDOMAIN, a second lookup is ++ initiated with the specified name appended to the query ++ name. This allows NXDOMAIN redirection data to be supplied ++ by multiple zones configured on the server or by recursive ++ queries to other servers. (The older method, using ++ a single type redirect zone, has ++ better average performance but is less flexible.) [RT #37989] ++ ++ ++ ++ ++ The following types have been implemented: CSYNC, NINFO, RKEY, ++ SINK, TA, TALINK. ++ ++ ++ ++ ++ A new message-compression option can be ++ used to specify whether or not to use name compression when ++ answering queries. Setting this to no ++ results in larger responses, but reduces CPU consumption and ++ may improve throughput. The default is yes. ++ ++ ++ ++ ++ A read-only option is now available in the ++ controls statement to grant non-destructive ++ control channel access. In such cases, a restricted set of ++ rndc commands are allowed, which can ++ report information from named, but cannot ++ reconfigure or stop the server. By default, the control channel ++ access is not restricted to these ++ read-only operations. [RT #40498] ++ ++ ++ ++ ++ When loading a signed zone, named will ++ now check whether an RRSIG's inception time is in the future, ++ and if so, it will regenerate the RRSIG immediately. This helps ++ when a system's clock needs to be reset backwards. ++ ++ ++ ++
++ ++
Feature Changes ++ ++ ++ ++ The timers returned by the statistics channel (indicating current ++ time, server boot time, and most recent reconfiguration time) are ++ now reported with millisecond accuracy. [RT #40082] ++ ++ ++ ++ ++ Updated the compiled-in addresses for H.ROOT-SERVERS.NET ++ and L.ROOT-SERVERS.NET. ++ ++ ++ ++ ++ ACLs containing geoip asnum elements were ++ not correctly matched unless the full organization name was ++ specified in the ACL (as in ++ geoip asnum "AS1234 Example, Inc.";). ++ They can now match against the AS number alone (as in ++ geoip asnum "AS1234";). ++ ++ ++ ++ ++ When using native PKCS#11 cryptography (i.e., ++ configure --enable-native-pkcs11) HSM PINs ++ of up to 256 characters can now be used. ++ ++ ++ ++ ++ NXDOMAIN responses to queries of type DS are now cached separately ++ from those for other types. This helps when using "grafted" zones ++ of type forward, for which the parent zone does not contain a ++ delegation, such as local top-level domains. Previously a query ++ of type DS for such a zone could cause the zone apex to be cached ++ as NXDOMAIN, blocking all subsequent queries. (Note: This ++ change is only helpful when DNSSEC validation is not enabled. ++ "Grafted" zones without a delegation in the parent are not a ++ recommended configuration.) ++ ++ ++ ++ ++ Update forwarding performance has been improved by allowing ++ a single TCP connection to be shared between multiple updates. ++ ++ ++ ++ ++ By default, nsupdate will now check ++ the correctness of hostnames when adding records of type ++ A, AAAA, MX, SOA, NS, SRV or PTR. This behavior can be ++ disabled with check-names no. ++ ++ ++ ++ ++ Added support for OPENPGPKEY type. ++ ++ ++ ++ ++ The names of the files used to store managed keys and added ++ zones for each view are no longer based on the SHA256 hash ++ of the view name, except when this is necessary because the ++ view name contains characters that would be incompatible with use ++ as a file name. For views whose names do not contain forward ++ slashes ('/'), backslashes ('\'), or capital letters - which ++ could potentially cause namespace collision problems on ++ case-insensitive filesystems - files will now be named ++ after the view (for example, internal.mkeys ++ or external.nzf). However, to ensure ++ consistent behavior when upgrading, if a file using the old ++ name format is found to exist, it will continue to be used. ++ ++ ++ ++ ++ "rndc" can now return text output of arbitrary size to ++ the caller. (Prior to this, certain commands such as ++ "rndc tsig-list" and "rndc zonestatus" could return ++ truncated output.) ++ ++ ++ ++ ++ Errors reported when running rndc addzone ++ (e.g., when a zone file cannot be loaded) have been clarified ++ to make it easier to diagnose problems. ++ ++ ++ ++ ++ When encountering an authoritative name server whose name is ++ an alias pointing to another name, the resolver treats ++ this as an error and skips to the next server. Previously ++ this happened silently; now the error will be logged to ++ the newly-created "cname" log category. ++ ++ ++ ++ ++ If named is not configured to validate ++ answers, then allow fallback to plain DNS on timeout even when ++ we know the server supports EDNS. This will allow the server to ++ potentially resolve signed queries when TCP is being ++ blocked. ++ ++ ++ ++ ++ Large inline-signing changes should be less disruptive. ++ Signature generation is now done incrementally; the number ++ of signatures to be generated in each quantum is controlled ++ by "sig-signing-signatures number;". ++ [RT #37927] ++ ++ ++ ++ ++ The experimental SIT option (code point 65001) of BIND ++ 9.10.0 through BIND 9.10.2 has been replaced with the COOKIE ++ option (code point 10). It is no longer experimental, and ++ is sent by default, by both named and ++ dig. ++ ++ ++ The SIT-related named.conf options have been marked as ++ obsolete, and are otherwise ignored. ++ ++ ++ ++ ++ When dig receives a truncated (TC=1) ++ response or a BADCOOKIE response code from a server, it ++ will automatically retry the query using the server COOKIE ++ that was returned by the server in its initial response. ++ [RT #39047] ++ ++ ++ ++ ++ A alternative NXDOMAIN redirect method (nxdomain-redirect) ++ which allows the redirect information to be looked up from ++ a namespace on the Internet rather than requiring a zone ++ to be configured on the server is now available. ++ ++ ++ ++ ++ Retrieving the local port range from net.ipv4.ip_local_port_range ++ on Linux is now supported. ++ ++ ++ ++ ++ Within the option, it is now ++ possible to configure RPZ rewrite logging on a per-zone basis ++ using the clause. ++ ++ ++ ++ ++ The default preferred glue is now the address type of the ++ transport the query was received over. ++ ++ ++ ++ ++ On machines with 2 or more processors (CPU), the default value ++ for the number of UDP listeners has been changed to the number ++ of detected processors minus one. ++ ++ ++ ++ ++ Zone transfers now use smaller message sizes to improve ++ message compression. This results in reduced network usage. ++ ++ ++ ++ ++ Added support for the AVC resource record type (Application ++ Visibility and Control). ++ ++ ++ ++
++ ++
Porting Changes ++ ++ ++ ++ None. ++ ++ ++ ++
++ ++
Bug Fixes ++ ++ ++ ++ None. ++ ++ ++ ++
++
End of Life ++ ++ ++ The end of life for BIND 9.11 is yet to be determined but ++ will not be before BIND 9.13.0 has been released for 6 months. ++ https://www.isc.org/downloads/software-support-policy/ ++ ++
++
Thank You ++ ++ ++ Thank you to everyone who assisted us in making this release possible. ++ If you would like to contribute to ISC to assist us in continuing to ++ make quality open source software, please visit our donations page at ++ http://www.isc.org/donate/. ++ ++
++
+-- +2.14.3 + diff --git a/SOURCES/bind99-rh1549130-2.patch b/SOURCES/bind99-rh1549130-2.patch new file mode 100644 index 0000000..8c9e4c0 --- /dev/null +++ b/SOURCES/bind99-rh1549130-2.patch @@ -0,0 +1,54 @@ +From 9f9ce5d91a944407e13360e9c92c090d23777a8b Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Fri, 27 May 2016 18:39:33 +1000 +Subject: [PATCH] fix merge error + +--- + bin/named/query.c | 31 +++++++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +diff --git a/bin/named/query.c b/bin/named/query.c +index 6e988f5686..2c44e9ff53 100644 +--- a/bin/named/query.c ++++ b/bin/named/query.c +@@ -7195,6 +7195,37 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) + * we know the answer. + */ + ++ /* ++ * If we have a zero ttl from the cache refetch it. ++ */ ++ if (!is_zone && event == NULL && rdataset->ttl == 0 && ++ RECURSIONOK(client)) ++ { ++ if (dns_rdataset_isassociated(rdataset)) ++ dns_rdataset_disassociate(rdataset); ++ if (sigrdataset != NULL && ++ dns_rdataset_isassociated(sigrdataset)) ++ dns_rdataset_disassociate(sigrdataset); ++ if (node != NULL) ++ dns_db_detachnode(db, &node); ++ ++ result = query_recurse(client, qtype, ++ client->query.qname, ++ NULL, NULL, resuming); ++ if (result == ISC_R_SUCCESS) { ++ client->query.attributes |= ++ NS_QUERYATTR_RECURSING; ++ if (dns64) ++ client->query.attributes |= ++ NS_QUERYATTR_DNS64; ++ if (dns64_exclude) ++ client->query.attributes |= ++ NS_QUERYATTR_DNS64EXCLUDE; ++ } else ++ RECURSE_ERROR(result); ++ goto cleanup; ++ } ++ + #ifdef ALLOW_FILTER_AAAA_ON_V4 + /* + * Optionally hide AAAAs from IPv4 clients if there is an A. +-- +2.14.4 + diff --git a/SOURCES/bind99-rh1549130.patch b/SOURCES/bind99-rh1549130.patch new file mode 100644 index 0000000..9f3df4a --- /dev/null +++ b/SOURCES/bind99-rh1549130.patch @@ -0,0 +1,183 @@ +From 02412bfe731d0cb229eb22f0ca4e8fbaed601cbe Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Fri, 27 May 2016 09:59:46 +1000 +Subject: [PATCH] 4377. [bug] Don't reuse zero TTL responses beyond + the current client set (excludes ANY/SIG/RRSIG + queries). [RT #42142] + +(cherry picked from commit aabcb1fde0ca255ff30f0a5c10cbd39f798cc5b7) + +REDIRECT macro is 9.11.0+ +--- + bin/named/query.c | 31 +++++++++++++++ + bin/tests/system/zero/ans5/ans.pl | 81 +++++++++++++++++++++++++++++++++++++++ + bin/tests/system/zero/ns1/root.db | 2 + + bin/tests/system/zero/tests.sh | 13 +++++++ + 4 files changed, 127 insertions(+) + create mode 100644 bin/tests/system/zero/ans5/ans.pl + +diff --git a/bin/named/query.c b/bin/named/query.c +index 2c44e9ff53..3b402f1d01 100644 +--- a/bin/named/query.c ++++ b/bin/named/query.c +@@ -6816,6 +6816,37 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) + goto cleanup; + + case DNS_R_CNAME: ++ /* ++ * If we have a zero ttl from the cache refetch it. ++ */ ++ if (!is_zone && event == NULL && rdataset->ttl == 0 && ++ RECURSIONOK(client)) ++ { ++ if (dns_rdataset_isassociated(rdataset)) ++ dns_rdataset_disassociate(rdataset); ++ if (sigrdataset != NULL && ++ dns_rdataset_isassociated(sigrdataset)) ++ dns_rdataset_disassociate(sigrdataset); ++ if (node != NULL) ++ dns_db_detachnode(db, &node); ++ ++ result = query_recurse(client, qtype, ++ client->query.qname, ++ NULL, NULL, resuming); ++ if (result == ISC_R_SUCCESS) { ++ client->query.attributes |= ++ NS_QUERYATTR_RECURSING; ++ if (dns64) ++ client->query.attributes |= ++ NS_QUERYATTR_DNS64; ++ if (dns64_exclude) ++ client->query.attributes |= ++ NS_QUERYATTR_DNS64EXCLUDE; ++ } else ++ RECURSE_ERROR(result); ++ goto cleanup; ++ } ++ + /* + * Keep a copy of the rdataset. We have to do this because + * query_addrrset may clear 'rdataset' (to prevent the +diff --git a/bin/tests/system/zero/ans5/ans.pl b/bin/tests/system/zero/ans5/ans.pl +new file mode 100644 +index 0000000000..9dfa18e444 +--- /dev/null ++++ b/bin/tests/system/zero/ans5/ans.pl +@@ -0,0 +1,81 @@ ++#!/usr/bin/perl -w ++# ++# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++# ++# Don't respond if the "norespond" file exists; otherwise respond to ++# any A or AAAA query. ++# ++ ++use IO::File; ++use IO::Socket; ++use Net::DNS; ++use Net::DNS::Packet; ++ ++my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.5", ++ LocalPort => 5300, Proto => "udp") or die "$!"; ++ ++my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; ++print $pidf "$$\n" or die "cannot write pid file: $!"; ++$pidf->close or die "cannot close pid file: $!"; ++sub rmpid { unlink "ans.pid"; exit 1; }; ++ ++$SIG{INT} = \&rmpid; ++$SIG{TERM} = \&rmpid; ++ ++my $octet = 0; ++ ++for (;;) { ++ $sock->recv($buf, 512); ++ ++ print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n"; ++ ++ my $packet; ++ ++ if ($Net::DNS::VERSION > 0.68) { ++ $packet = new Net::DNS::Packet(\$buf, 0); ++ $@ and die $@; ++ } else { ++ my $err; ++ ($packet, $err) = new Net::DNS::Packet(\$buf, 0); ++ $err and die $err; ++ } ++ ++ print "REQUEST:\n"; ++ $packet->print; ++ ++ $packet->header->qr(1); ++ ++ my @questions = $packet->question; ++ my $qname = $questions[0]->qname; ++ my $qtype = $questions[0]->qtype; ++ ++ $packet->header->aa(1); ++ if ($qtype eq "A") { ++ $packet->push("answer", ++ new Net::DNS::RR($qname . ++ " 0 A 192.0.2." . $octet)); ++ $octet = $octet + 1; ++ } elsif ($qtype eq "AAAA") { ++ $packet->push("answer", ++ new Net::DNS::RR($qname . ++ " 300 AAAA 2001:db8:beef::1")); ++ } ++ ++ $sock->send($packet->data); ++ print "RESPONSE:\n"; ++ $packet->print; ++ print "\n"; ++} +diff --git a/bin/tests/system/zero/ns1/root.db b/bin/tests/system/zero/ns1/root.db +index 69aca86fb8..beb97cb693 100644 +--- a/bin/tests/system/zero/ns1/root.db ++++ b/bin/tests/system/zero/ns1/root.db +@@ -22,3 +22,5 @@ example. NS ns2.example. + ns2.example. A 10.53.0.2 + example. NS ns4.example. + ns4.example. A 10.53.0.4 ++increment. NS incrementns. ++incrementns A 10.53.0.5 +diff --git a/bin/tests/system/zero/tests.sh b/bin/tests/system/zero/tests.sh +index 15c2906a92..bbb78f0fd8 100644 +--- a/bin/tests/system/zero/tests.sh ++++ b/bin/tests/system/zero/tests.sh +@@ -44,5 +44,18 @@ done + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` + ++echo "I:check repeated recursive lookups of non recurring zero ttl responses get new values" ++count=`( ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++dig +short -p 5300 @10.53.0.3 foo.increment ++) | sort -u | wc -l ` ++if [ $count -ne 7 ] ; then echo "I:failed (count=$count)"; ret=1; fi ++status=`expr $status + $ret` ++ + echo "I:exit status: $status" + exit $status +-- +2.14.4 + diff --git a/SOURCES/bind99-rh1647539.patch b/SOURCES/bind99-rh1647539.patch new file mode 100644 index 0000000..5e02852 --- /dev/null +++ b/SOURCES/bind99-rh1647539.patch @@ -0,0 +1,28 @@ +From 89293e44e0d022463b03a92a30b3790d4569bd50 Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Thu, 20 Feb 2014 23:04:54 +1100 +Subject: [PATCH] 3752. [bug] Address potential REQUIRE failure if + DNS_STYLEFLAG_COMMENTDATA is set when printing out + a rdataset. + +(cherry picked from commit 86856f4f3069bb2d75851b56401ffde18f41198f) +--- + lib/dns/masterdump.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c +index 80fcd4c12d..58dcd3de3e 100644 +--- a/lib/dns/masterdump.c ++++ b/lib/dns/masterdump.c +@@ -451,7 +451,7 @@ rdataset_totext(dns_rdataset_t *rdataset, + * Comment? + */ + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) +- isc_buffer_putstr(target, ";"); ++ RETERR(str_totext(";", target)); + + /* + * Owner name. +-- +2.14.5 + diff --git a/SOURCES/named.conf.sample b/SOURCES/named.conf.sample index f091345..6d504a8 100644 --- a/SOURCES/named.conf.sample +++ b/SOURCES/named.conf.sample @@ -13,6 +13,8 @@ options dump-file "data/cache_dump.db"; statistics-file "data/named_stats.txt"; memstatistics-file "data/named_mem_stats.txt"; + recursing-file "data/named.recursing"; + secroots-file "data/named.secroots"; /* diff --git a/SPECS/bind.spec b/SPECS/bind.spec index d7988d4..48f91b1 100644 --- a/SPECS/bind.spec +++ b/SPECS/bind.spec @@ -17,6 +17,7 @@ %{?!DEVEL: %global DEVEL 1} %global bind_dir /var/named %global chroot_prefix %{bind_dir}/chroot +%global selinuxbooleans named_write_master_zones=1 %if %{SDB} %global chroot_sdb_prefix %{bind_dir}/chroot_sdb %endif @@ -25,7 +26,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind License: ISC Version: 9.9.4 -Release: 61%{?PATCHVER}%{?PREVER}%{?dist} +Release: 73%{?PATCHVER}%{?PREVER}%{?dist} Epoch: 32 Url: http://www.isc.org/products/BIND/ Buildroot:%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -38,7 +39,8 @@ Source7: bind-9.3.1rc1-sdb_tools-Makefile.in Source8: dnszone.schema Source12: README.sdb_pgsql Source25: named.conf.sample -Source28: config-15.tar.bz2 +Source26: named.conf +Source28: config-16.tar.bz2 # Up-to-date bind.keys from upstream # Fetch a new one from page https://www.isc.org/bind-keys Source29: bind.keys @@ -161,6 +163,17 @@ Patch188: bind99-rh1464850-2.patch Patch189: bind99-rh1501531.patch # ISC 4858 Patch190: bind99-CVE-2017-3145.patch +Patch191: bind99-rh1510008.patch +Patch192: bind99-nta.patch +Patch193: bind99-rh1510008-2.patch +Patch194: bind99-fips.patch +Patch195: bind99-fips-tests.patch +# commit c3fbf330bc014f0470371e8da590d14a1d62977e ISC 4377 +Patch196: bind99-rh1549130.patch +# commit cb735b3f902d4bb5f6e30328d5828d38efa63573 +Patch197: bind99-rh1549130-2.patch +Patch198: bind99-CVE-2018-5740.patch +Patch199: bind99-rh1647539.patch # Native PKCS#11 functionality from 9.10 Patch150:bind-9.9-allow_external_dnskey.patch @@ -190,6 +203,8 @@ Requires(postun): systemd Requires: coreutils Requires: systemd-units Requires(post): grep, systemd +Requires(post): shadow-utils +Requires(post): glibc-common Requires(pre): shadow-utils Requires: bind-libs = %{epoch}:%{version}-%{release} Obsoletes: bind-config < 30:9.3.2-34.fc6 @@ -198,9 +213,18 @@ Obsoletes: caching-nameserver < 31:9.4.1-7.fc8 Provides: caching-nameserver = 31:9.4.1-7.fc8 Obsoletes: dnssec-conf < 1.27-2 Provides: dnssec-conf = 1.27-1 +Requires: python-ply +Provides: python-isc = %{epoch}:%{version}-%{release} +Provides: python-bind = %{epoch}:%{version}-%{release} +# selinux_set_booleans requires +Requires(post): policycoreutils-python, libselinux-utils, selinux-policy +Requires(postun): policycoreutils-python, libselinux-utils, selinux-policy +Requires(posttrans): policycoreutils-python, libselinux-utils, selinux-policy BuildRequires: openssl-devel, libtool, autoconf, pkgconfig, libcap-devel BuildRequires: libidn-devel, libxml2-devel, GeoIP-devel BuildRequires: systemd-units +BuildRequires: python-ply +BuildRequires: selinux-policy %if %{SDB} BuildRequires: openldap-devel, postgresql-devel, sqlite-devel, mysql-devel BuildRequires: libdb-devel @@ -467,6 +491,15 @@ tar -xf %{SOURCE48} -C bin/tests/system/geoip/data %patch188 -p1 -b .rh1464850 %patch189 -p1 -b .rh1501531 %patch190 -p1 -b .CVE-2017-3145 +%patch191 -p1 -b .dnssec-keymgr +%patch192 -p1 -b .rh1452091 +%patch193 -p1 -b .dnssec-keymgr-2 +%patch194 -p1 -b .fips +%patch195 -p1 -b .fips-tests +%patch196 -p1 -b .rh1549130 +%patch197 -p1 -b .rh1549130-2 +%patch198 -p1 -b .CVE-2018-5740 +%patch199 -p1 -b .rh1647539 # Override upstream builtin keys cp -fp %{SOURCE29} bind.keys @@ -740,6 +773,7 @@ tar -C ${RPM_BUILD_ROOT} -xjf %{SOURCE28} touch ${RPM_BUILD_ROOT}/etc/rndc.key touch ${RPM_BUILD_ROOT}/etc/rndc.conf mkdir ${RPM_BUILD_ROOT}/etc/named +install -m 640 %{SOURCE26} ${RPM_BUILD_ROOT}%{_sysconfdir}/named.conf install -m 644 bind.keys ${RPM_BUILD_ROOT}/etc/named.iscdlv.key install -m 644 %{SOURCE36} ${RPM_BUILD_ROOT}/etc/trusted-key.key @@ -747,7 +781,7 @@ install -m 644 %{SOURCE36} ${RPM_BUILD_ROOT}/etc/trusted-key.key mkdir -p sample/etc sample/var/named/{data,slaves} install -m 644 %{SOURCE25} sample/etc/named.conf # Copy default configuration to %%doc to make it usable from system-config-bind -install -m 644 ${RPM_BUILD_ROOT}/etc/named.conf named.conf.default +install -m 644 %{SOURCE26} named.conf.default install -m 644 ${RPM_BUILD_ROOT}/etc/named.rfc1912.zones sample/etc/named.rfc1912.zones install -m 644 ${RPM_BUILD_ROOT}/var/named/{named.ca,named.localhost,named.loopback,named.empty} sample/var/named for f in my.internal.zone.db slaves/my.slave.internal.zone.db slaves/my.ddns.internal.zone.db my.external.zone.db; do @@ -765,20 +799,25 @@ install -m 644 %{SOURCE43} ${RPM_BUILD_ROOT}%{_sysconfdir}/rwtab.d/named %pre if [ "$1" -eq 1 ]; then /usr/sbin/groupadd -g %{bind_gid} -f -r named >/dev/null 2>&1 || :; - /usr/sbin/useradd -u %{bind_uid} -r -N -M -g named -s /sbin/nologin -d /var/named -c Named named >/dev/null 2>&1 || :; + /usr/sbin/useradd -u %{bind_uid} -r -N -M -g named -s /bin/false -d /var/named -c Named named >/dev/null 2>&1 || :; fi; :; %post /sbin/ldconfig -%systemd_post named.service if [ "$1" -eq 1 ]; then # Initial installation [ -x /sbin/restorecon ] && /sbin/restorecon /etc/rndc.* /etc/named.* >/dev/null 2>&1 ; # rndc.key has to have correct perms and ownership, CVE-2007-6283 [ -e /etc/rndc.key ] && chown root:named /etc/rndc.key [ -e /etc/rndc.key ] && chmod 0640 /etc/rndc.key +else + # Upgrade, use invalid shell + if getent passwd named | grep ':/sbin/nologin$' >/dev/null; then + usermod -s /bin/false named + fi fi +%systemd_post named.service :; %preun @@ -787,8 +826,16 @@ fi %postun /sbin/ldconfig -# Package upgrade, not uninstall %systemd_postun_with_restart named.service +# Unset on both upgrade and install. Boolean would be unset from now +# until %posttrans on upgrade. Write requests might fail during update. +(export LC_ALL=C; %{selinux_unset_booleans %{selinuxbooleans}}) + +%posttrans +# selinux-policy-targeted is required for following macro to work. +# This package should not depend on it explicitly, but anaconda ensures +# it is installed. Run after all packages are installed. +(export LC_ALL=C; %{selinux_set_booleans %{selinuxbooleans}}) %if %{SDB} %post sdb @@ -940,6 +987,8 @@ rm -rf ${RPM_BUILD_ROOT} %{_sbindir}/named-compilezone %{_sbindir}/isc-hmac-fixup %{_libexecdir}/generate-rndc-key.sh +%{python_sitelib}/isc/ +%{python_sitelib}/*.egg-info %{_mandir}/man1/arpaname.1* %{_mandir}/man5/named.conf.5* %{_mandir}/man5/rndc.conf.5* @@ -964,19 +1013,20 @@ rm -rf ${RPM_BUILD_ROOT} # Hide configuration %defattr(0640,root,named,0750) %dir %{_sysconfdir}/named -%dir %{_localstatedir}/named %config(noreplace) %verify(not link) %{_sysconfdir}/named.conf %config(noreplace) %verify(not link) %{_sysconfdir}/named.rfc1912.zones -%config %verify(not link) %{_localstatedir}/named/named.ca -%config %verify(not link) %{_localstatedir}/named/named.localhost -%config %verify(not link) %{_localstatedir}/named/named.loopback -%config %verify(not link) %{_localstatedir}/named/named.empty +%defattr(0660,root,named,01770) +%dir %{_localstatedir}/named %defattr(0660,named,named,0770) %dir %{_localstatedir}/named/slaves %dir %{_localstatedir}/named/data %dir %{_localstatedir}/named/dynamic %ghost %{_localstatedir}/log/named.log %defattr(0640,root,named,0750) +%config %verify(not link) %{_localstatedir}/named/named.ca +%config %verify(not link) %{_localstatedir}/named/named.localhost +%config %verify(not link) %{_localstatedir}/named/named.loopback +%config %verify(not link) %{_localstatedir}/named/named.empty %ghost %config(noreplace) %{_sysconfdir}/rndc.key # ^- rndc.key now created on first install only if it does not exist # %%verify(not size,not md5) %%config(noreplace) %%attr(0640,root,named) /etc/rndc.conf @@ -1078,12 +1128,13 @@ rm -rf ${RPM_BUILD_ROOT} %dir %{chroot_prefix}/etc/pki/dnssec-keys %dir %{chroot_prefix}/var %dir %{chroot_prefix}/run -%dir %{chroot_prefix}/var/named %ghost %config(noreplace) %{chroot_prefix}/etc/named.conf %defattr(-,root,root,-) %dir %{chroot_prefix}/usr %dir %{chroot_prefix}/%{_libdir} %dir %{chroot_prefix}/%{_libdir}/bind +%defattr(0660,root,named,01770) +%dir %{chroot_prefix}/var/named %defattr(0660,named,named,0770) %dir %{chroot_prefix}/var/tmp %dir %{chroot_prefix}/var/log @@ -1109,8 +1160,9 @@ rm -rf ${RPM_BUILD_ROOT} %dir %{chroot_sdb_prefix}/etc/pki/dnssec-keys %dir %{chroot_sdb_prefix}/var %dir %{chroot_sdb_prefix}/run -%dir %{chroot_sdb_prefix}/var/named %ghost %config(noreplace) %{chroot_sdb_prefix}/etc/named.conf +%defattr(0660,root,named,01770) +%dir %{chroot_sdb_prefix}/var/named %defattr(-,root,root,-) %dir %{chroot_sdb_prefix}/usr %dir %{chroot_sdb_prefix}/%{_libdir} @@ -1155,6 +1207,45 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* Fri Nov 23 2018 Petr Menšík - 32:9.9.4-73 +- Fixes debug level comments (#1647539) + +* Thu Sep 20 2018 Petr Menšík - 32:9.9.4-72 +- Fix automatic selinux boolean named_write_master_zones (#1569466) +- Allow starting named with readonly home again + +* Wed Aug 08 2018 Petr Menšík - 32:9.9.4-71 +- Fix CVE-2018-5740 + +* Sun Jun 24 2018 Petr Menšík - 32:9.9.4-70 +- Fix compiler warnings + +* Thu Jun 21 2018 Petr Menšík - 32:9.9.4-69 +- Refetch always records with TTL 0 (#1549130) + +* Thu Jun 21 2018 Petr Menšík - 32:9.9.4-68 +- Detect and disable MD5 functions in FIPS 140-2 mode (#1519306) + +* Thu Jun 14 2018 Petr Menšík - 32:9.9.4-67 +- Move change of dns_view_t to the end (#1452091) + +* Fri Jun 01 2018 Petr Menšík - 32:9.9.4-66 +- Correct recursing file name (#1435883) +- Use python binary again, install all modules (#1510008) + +* Thu May 31 2018 Petr Menšík - 32:9.9.4-65 +- Add rndc secroots and recursing output files into data (#1435883) + +* Mon May 28 2018 Petr Menšík - 32:9.9.4-64 +- Backported negative trust anchors (#1452091) + +* Mon May 28 2018 Petr Menšík - 32:9.9.4-63 +- Make named home writeable (#1569466) +- Change named shell to /bin/false + +* Tue May 22 2018 Martin Sehnoutka - 32:9.9.4-62 +- Resolves: #1510008 - add support for dnssec-keymgr + * Tue Jan 16 2018 Petr Menšík - 32:9.9.4-61 - Fix CVE-2017-3145