From 819ae187b1e3229baa33f475b940df01766b2a74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Thu, 9 Nov 2017 15:21:34 +0100
Subject: [PATCH] Squashed commit of the following:

commit a1d088bf6565826029187ff89e58694eaa236877
Author: Mukund Sivaraman <muks@isc.org>
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 <marka@isc.org>
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 <marka@isc.org>
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 <marka@isc.org>
Date:   Wed Jul 2 14:12:46 2014 +1000

    be consistent about expire time

    (cherry picked from commit 7dbd30979997031ddd539f3543c6d2f9234f7978)

commit 0ebf7e0e4b61113a694e88674865cdf086a4b5c1
Author: Mark Andrews <marka@isc.org>
Date:   Thu Jun 19 10:20:34 2014 +1000

    add #include <isc/print.h>

    (cherry picked from commit 3a37159a95e961066b90c493c5a4c92d3e0ec029)

commit 410c729b2a457334a3d2202777c00a1af1f07f82
Author: Evan Hunt <each@isc.org>
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 <marka@isc.org>
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 <each@isc.org>
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 <http://docbook.sf.net/>
+.\"      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 "#<port>"
+.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 <config.h>
+#include <bind.keys.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+
+#include <isc/app.h>
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/lib.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/parseint.h>
+#include <isc/sockaddr.h>
+#include <isc/socket.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <irs/resconf.h>
+#include <irs/netdb.h>
+
+#include <isccfg/log.h>
+#include <isccfg/namedconf.h>
+
+#include <dns/byaddr.h>
+#include <dns/client.h>
+#include <dns/fixedname.h>
+#include <dns/keytable.h>
+#include <dns/keyvalues.h>
+#include <dns/lib.h>
+#include <dns/log.h>
+#include <dns/masterdump.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/result.h>
+#include <dns/secalg.h>
+#include <dns/view.h>
+
+#include <dst/dst.h>
+#include <dst/result.h>
+
+#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_<rrtype>() functions which causes
+			 * splitwidth to shrink.  This is okay when we're
+			 * using the default width but incorrect in this
+			 * case, so we correct for it
+			 */
+			if (splitwidth)
+				splitwidth += 3;
+			if (result != ISC_R_SUCCESS)
+				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 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+	       "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - 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.
+-->
+
+<refentry id="man.delve">
+
+  <refentryinfo>
+    <date>February 12, 2014</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>delve</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>BIND9</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>delve</refname>
+    <refpurpose>DNS lookup and validation utility</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2014</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>delve</command>
+      <arg choice="opt">@server</arg>
+      <arg><option>-4</option></arg>
+      <arg><option>-6</option></arg>
+      <arg><option>-a <replaceable class="parameter">anchor-file</replaceable></option></arg>
+      <arg><option>-b <replaceable class="parameter">address</replaceable></option></arg>
+      <arg><option>-c <replaceable class="parameter">class</replaceable></option></arg>
+      <arg><option>-d <replaceable class="parameter">level</replaceable></option></arg>
+      <arg><option>-i</option></arg>
+      <arg><option>-m</option></arg>
+      <arg><option>-p <replaceable class="parameter">port#</replaceable></option></arg>
+      <arg><option>-q <replaceable class="parameter">name</replaceable></option></arg>
+      <arg><option>-t <replaceable class="parameter">type</replaceable></option></arg>
+      <arg><option>-x <replaceable class="parameter">addr</replaceable></option></arg>
+      <arg choice="opt">name</arg>
+      <arg choice="opt">type</arg>
+      <arg choice="opt">class</arg>
+      <arg choice="opt" rep="repeat">queryopt</arg>
+    </cmdsynopsis>
+
+    <cmdsynopsis>
+      <command>delve</command>
+      <arg><option>-h</option></arg>
+    </cmdsynopsis>
+
+    <cmdsynopsis>
+      <command>delve</command>
+      <arg choice="opt" rep="repeat">queryopt</arg>
+      <arg choice="opt" rep="repeat">query</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para><command>delve</command>
+      (Domain Entity Lookup &amp; Validation Engine) is a tool for sending
+      DNS queries and validating the results, using the the same internal
+      resolver and validator logic as <command>named</command>.
+    </para>
+    <para>
+      <command>delve</command> 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.
+    </para>
+    <para>
+      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
+      <command>delve</command> 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 <command>delve</command> 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.
+    </para>
+    <para>
+      Unless it is told to query a specific name server,
+      <command>delve</command> will try each of the servers listed in
+      <filename>/etc/resolv.conf</filename>. If no usable server
+      addresses are found, <command>delve</command> will send
+      queries to the localhost addresses (127.0.0.1 for IPv4, ::1
+      for IPv6).
+    </para>
+    <para>
+      When no command line arguments or options are given,
+      <command>delve</command> will perform an NS query for "."
+      (the root zone).
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>SIMPLE USAGE</title>
+
+    <para>
+      A typical invocation of <command>delve</command> looks like:
+      <programlisting> delve @server name type </programlisting>
+      where:
+
+      <variablelist>
+	<varlistentry>
+	  <term><constant>server</constant></term>
+	  <listitem>
+	    <para>
+	      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
+	      <parameter>server</parameter> argument is a hostname,
+	      <command>delve</command> resolves that name before
+	      querying that name server (note, however, that this
+	      initial lookup is <emphasis>not</emphasis> validated
+	      by DNSSEC).
+	    </para>
+	    <para>
+	      If no <parameter>server</parameter> argument is
+	      provided, <command>delve</command> consults
+	      <filename>/etc/resolv.conf</filename>; if an
+	      address is found there, it queries the name server at
+	      that address. If either of the <option>-4</option> or
+	      <option>-6</option> options are in use, then
+	      only addresses for the corresponding transport
+	      will be tried.  If no usable addresses are found,
+	      <command>delve</command> will send queries to
+	      the localhost addresses (127.0.0.1 for IPv4,
+	      ::1 for IPv6).
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><constant>name</constant></term>
+	  <listitem>
+	    <para>
+	      is the domain name to be looked up.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><constant>type</constant></term>
+	  <listitem>
+	    <para>
+	      indicates what type of query is required &mdash;
+	      ANY, A, MX, etc.
+	      <parameter>type</parameter> can be any valid query
+	      type.  If no
+	      <parameter>type</parameter> argument is supplied,
+	      <command>delve</command> will perform a lookup for an
+	      A record.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+      </variablelist>
+    </para>
+
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+    <variablelist>
+      <varlistentry>
+	<term>-a</term>
+	<listitem>
+	  <para>
+	    Specifies a file from which to read DNSSEC trust anchors.
+	    The default is <filename>/etc/bind.keys</filename>, which
+	    is included with <acronym>BIND</acronym> 9 and contains
+	    trust anchors for the root zone (".") and for the ISC
+	    DNSSEC lookaside validation zone ("dlv.isc.org").
+	  </para>
+	  <para>
+	    Keys that do not match the root or DLV trust-anchor
+	    names are ignored; these key names can be overridden
+	    using the <option>+dlv=NAME</option> or
+	    <option>+root=NAME</option> options.
+	  </para>
+	  <para>
+	    Note: When reading the trust anchor file,
+	    <command>delve</command> treats <option>managed-keys</option>
+	    statements and <option>trusted-keys</option> statements
+	    identically.  That is, for a managed key, it is the
+	    <emphasis>initial</emphasis> key that is trusted; RFC 5011
+	    key management is not supported. <command>delve</command>
+	    will not consult the managed-keys database maintained by
+	    <command>named</command>. This means that if either of the
+	    keys in <filename>/etc/bind.keys</filename> is revoked
+	    and rolled over, it will be necessary to update
+	    <filename>/etc/bind.keys</filename> to use DNSSEC
+	    validation in <command>delve</command>.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-b</term>
+	<listitem>
+	  <para>
+	    Sets the source IP address of the query to
+	    <parameter>address</parameter>.  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
+	    "#&lt;port&gt;"
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-c</term>
+	<listitem>
+	  <para>
+	    Sets the query class for the requested data. Currently,
+	    only class "IN" is supported in <command>delve</command>
+	    and any other value is ignored.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-i</term>
+	<listitem>
+	  <para>
+	    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 <command>delve</command> to time out. When it
+	    is necessary to examine invalid data to debug a DNSSEC
+	    problem, use <command>dig +cd</command>.)
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-m</term>
+	<listitem>
+	  <para>
+	    Enables memory usage debugging.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-p</term>
+	<listitem>
+	  <para>
+	    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.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-4</term>
+	<listitem>
+	  <para>
+	    Forces <command>delve</command> to only use IPv4.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-6</term>
+	<listitem>
+	  <para>
+	    Forces <command>delve</command> to only use IPv6.
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-q</term>
+	<listitem>
+	  <para>
+	    Sets the query name to <parameter>name</parameter>.
+	    While the query name can be specified without using the
+	    <option>-q</option>, 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).
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-t</term>
+	<listitem>
+	  <para>
+	    Sets the query type to <parameter>type</parameter>, which
+	    can be any valid query type supported in BIND 9 except
+	    for zone transfer types AXFR and IXFR. As with
+	    <option>-q</option>, this is useful to distinguish
+	    query name type or class when they are ambiguous.
+	    it is sometimes necessary to disambiguate names from types.
+	  </para>
+	  <para>
+	    The default query type is "A", unless the <option>-x</option>
+	    option is supplied to indicate a reverse lookup, in which case
+	    it is "PTR".
+	  </para>
+	</listitem>
+      </varlistentry>
+
+      <varlistentry>
+	<term>-x</term>
+	<listitem>
+	  <para>
+	    Performs a reverse lookup, mapping an addresses to
+	    a name.  <parameter>addr</parameter> is an IPv4 address in
+	    dotted-decimal notation, or a colon-delimited IPv6 address.
+	    When <option>-x</option> is used, there is no need to provide
+	    the <parameter>name</parameter> or <parameter>type</parameter>
+	    arguments.  <command>delve</command> automatically performs a
+	    lookup for a name like <literal>11.12.13.10.in-addr.arpa</literal>
+	    and sets the query type to PTR.  IPv6 addresses are looked up
+	    using nibble format under the IP6.ARPA domain.
+	  </para>
+	</listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>QUERY OPTIONS</title>
+
+    <para><command>delve</command>
+      provides a number of query options which affect the way results are
+      displayed, and in some cases the way lookups are performed.
+    </para>
+
+    <para>
+      Each query option is identified by a keyword preceded by a plus sign
+      (<literal>+</literal>).  Some keywords set or reset an
+      option.  These may be preceded by the string
+      <literal>no</literal> to negate the meaning of that keyword.
+      Other keywords assign values to options like the timeout interval.
+      They have the form <option>+keyword=value</option>.
+      The query options are:
+
+      <variablelist>
+	<varlistentry>
+	  <term><option>+[no]cdflag</option></term>
+	  <listitem>
+	    <para>
+	      Controls whether to set the CD (checking disabled) bit in
+	      queries sent by <command>delve</command>. 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 <command>delve</command> can then
+	      validate internally and report the errors in detail.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]class</option></term>
+	  <listitem>
+	    <para>
+	      Controls whether to display the CLASS when printing
+	      a record. The default is to display the CLASS.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]ttl</option></term>
+	  <listitem>
+	    <para>
+	      Controls whether to display the TTL when printing
+	      a record. The default is to display the TTL.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]rtrace</option></term>
+	  <listitem>
+	    <para>
+	      Toggle resolver fetch logging. This reports the
+	      name and type of each query sent by <command>delve</command>
+	      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.
+	    </para>
+	    <para>
+	      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>-d</option> option will
+	      product the same output (but will affect other logging
+	      categories as well).
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]mtrace</option></term>
+	  <listitem>
+	    <para>
+	      Toggle message logging. This produces a detailed dump of
+	      the responses received by <command>delve</command> in the
+	      process of carrying out the resolution and validation process.
+	    </para>
+	    <para>
+	      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>-d</option> option will produce the same output
+	      (but will affect other logging categories as well).
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]vtrace</option></term>
+	  <listitem>
+	    <para>
+	      Toggle validation logging. This shows the internal
+	      process of the validator as it determines whether an
+	      answer is validly signed, unsigned, or invalid.
+	    </para>
+	    <para>
+	      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>-d</option> option will produce the same output
+	      (but will affect other logging categories as well).
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]short</option></term>
+	  <listitem>
+	    <para>
+	      Provide a terse answer.  The default is to print the answer in a
+	      verbose form.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]comments</option></term>
+	  <listitem>
+	    <para>
+	      Toggle the display of comment lines in the output.  The default
+	      is to print comments.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]rrcomments</option></term>
+	  <listitem>
+	    <para>
+	      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.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]crypto</option></term>
+	  <listitem>
+	    <para>
+	      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 ]".
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]trust</option></term>
+	  <listitem>
+	    <para>
+	      Controls whether to display the trust level when printing
+	      a record. The default is to display the trust level.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]split[=W]</option></term>
+	  <listitem>
+	    <para>
+	      Split long hex- or base64-formatted fields in resource
+	      records into chunks of <parameter>W</parameter> characters
+	      (where <parameter>W</parameter> is rounded up to the nearest
+	      multiple of 4).
+	      <parameter>+nosplit</parameter> or
+	      <parameter>+split=0</parameter> causes fields not to be
+	      split at all.  The default is 56 characters, or 44 characters
+	      when multiline mode is active.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]all</option></term>
+	  <listitem>
+	    <para>
+	      Set or clear the display options
+	      <option>+[no]comments</option>, 
+	      <option>+[no]rrcomments</option>, and
+	      <option>+[no]trust</option> as a group.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]multiline</option></term>
+	  <listitem>
+	    <para>
+	      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 <command>delve</command>
+	      output.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]dnssec</option></term>
+	  <listitem>
+	    <para>
+	      Indicates whether to display RRSIG records in the
+	      <command>delve</command> output.  The default is to
+	      do so.  Note that (unlike in <command>dig</command>)
+	      this does <emphasis>not</emphasis> 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
+	      <option>-i</option> or <option>+noroot</option> and
+	      <option>+nodlv</option>.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]root[=ROOT]</option></term>
+	  <listitem>
+	    <para>
+	      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 <option>-a</option> must be used to specify a file
+	      containing the key.
+	    </para>
+	  </listitem>
+	</varlistentry>
+
+	<varlistentry>
+	  <term><option>+[no]dlv[=DLV]</option></term>
+	  <listitem>
+	    <para>
+	      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
+	      <option>-a</option> must be used to specify a file
+	      containing the DLV key.
+	    </para>
+	  </listitem>
+	</varlistentry>
+      </variablelist>
+
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para><filename>/etc/bind.keys</filename></para>
+    <para><filename>/etc/resolv.conf</filename></para>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para><citerefentry>
+	<refentrytitle>dig</refentrytitle><manvolnum>1</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+	<refentrytitle>named</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>RFC4034</citetitle>,
+      <citetitle>RFC4035</citetitle>,
+      <citetitle>RFC4431</citetitle>,
+      <citetitle>RFC5074</citetitle>,
+      <citetitle>RFC5155</citetitle>.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
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 @@
+<!--
+ - 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$ -->
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<title>delve</title>
+<meta name="generator" content="DocBook XSL Stylesheets V1.71.1">
+</head>
+<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="refentry" lang="en">
+<a name="man.delve"></a><div class="titlepage"></div>
+<div class="refnamediv">
+<h2>Name</h2>
+<p>delve &#8212; DNS lookup and validation utility</p>
+</div>
+<div class="refsynopsisdiv">
+<h2>Synopsis</h2>
+<div class="cmdsynopsis"><p><code class="command">delve</code>  [@server] [<code class="option">-4</code>] [<code class="option">-6</code>] [<code class="option">-a <em class="replaceable"><code>anchor-file</code></em></code>] [<code class="option">-b <em class="replaceable"><code>address</code></em></code>] [<code class="option">-c <em class="replaceable"><code>class</code></em></code>] [<code class="option">-d <em class="replaceable"><code>level</code></em></code>] [<code class="option">-i</code>] [<code class="option">-m</code>] [<code class="option">-p <em class="replaceable"><code>port#</code></em></code>] [<code class="option">-q <em class="replaceable"><code>name</code></em></code>] [<code class="option">-t <em class="replaceable"><code>type</code></em></code>] [<code class="option">-x <em class="replaceable"><code>addr</code></em></code>] [name] [type] [class] [queryopt...]</p></div>
+<div class="cmdsynopsis"><p><code class="command">delve</code>  [<code class="option">-h</code>]</p></div>
+<div class="cmdsynopsis"><p><code class="command">delve</code>  [queryopt...] [query...]</p></div>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2543478"></a><h2>DESCRIPTION</h2>
+<p><span><strong class="command">delve</strong></span>
+      (Domain Entity Lookup &amp; Validation Engine) is a tool for sending
+      DNS queries and validating the results, using the the same internal
+      resolver and validator logic as <span><strong class="command">named</strong></span>.
+    </p>
+<p>
+      <span><strong class="command">delve</strong></span> 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.
+    </p>
+<p>
+      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
+      <span><strong class="command">delve</strong></span> 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 <span><strong class="command">delve</strong></span> 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.
+    </p>
+<p>
+      Unless it is told to query a specific name server,
+      <span><strong class="command">delve</strong></span> will try each of the servers listed in
+      <code class="filename">/etc/resolv.conf</code>. If no usable server
+      addresses are found, <span><strong class="command">delve</strong></span> will send
+      queries to the localhost addresses (127.0.0.1 for IPv4, ::1
+      for IPv6).
+    </p>
+<p>
+      When no command line arguments or options are given,
+      <span><strong class="command">delve</strong></span> will perform an NS query for "."
+      (the root zone).
+    </p>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2543531"></a><h2>SIMPLE USAGE</h2>
+<p>
+      A typical invocation of <span><strong class="command">delve</strong></span> looks like:
+      </p>
+<pre class="programlisting"> delve @server name type </pre>
+<p>
+      where:
+
+      </p>
+<div class="variablelist"><dl>
+<dt><span class="term"><code class="constant">server</code></span></dt>
+<dd>
+<p>
+	      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
+	      <em class="parameter"><code>server</code></em> argument is a hostname,
+	      <span><strong class="command">delve</strong></span> resolves that name before
+	      querying that name server (note, however, that this
+	      initial lookup is <span class="emphasis"><em>not</em></span> validated
+	      by DNSSEC).
+	    </p>
+<p>
+	      If no <em class="parameter"><code>server</code></em> argument is
+	      provided, <span><strong class="command">delve</strong></span> consults
+	      <code class="filename">/etc/resolv.conf</code>; if an
+	      address is found there, it queries the name server at
+	      that address. If either of the <code class="option">-4</code> or
+	      <code class="option">-6</code> options are in use, then
+	      only addresses for the corresponding transport
+	      will be tried.  If no usable addresses are found,
+	      <span><strong class="command">delve</strong></span> will send queries to
+	      the localhost addresses (127.0.0.1 for IPv4,
+	      ::1 for IPv6).
+	    </p>
+</dd>
+<dt><span class="term"><code class="constant">name</code></span></dt>
+<dd><p>
+	      is the domain name to be looked up.
+	    </p></dd>
+<dt><span class="term"><code class="constant">type</code></span></dt>
+<dd><p>
+	      indicates what type of query is required &#8212;
+	      ANY, A, MX, etc.
+	      <em class="parameter"><code>type</code></em> can be any valid query
+	      type.  If no
+	      <em class="parameter"><code>type</code></em> argument is supplied,
+	      <span><strong class="command">delve</strong></span> will perform a lookup for an
+	      A record.
+	    </p></dd>
+</dl></div>
+<p>
+    </p>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2543640"></a><h2>OPTIONS</h2>
+<div class="variablelist"><dl>
+<dt><span class="term">-a</span></dt>
+<dd>
+<p>
+	    Specifies a file from which to read DNSSEC trust anchors.
+	    The default is <code class="filename">/etc/bind.keys</code>, which
+	    is included with <acronym class="acronym">BIND</acronym> 9 and contains
+	    trust anchors for the root zone (".") and for the ISC
+	    DNSSEC lookaside validation zone ("dlv.isc.org").
+	  </p>
+<p>
+	    Keys that do not match the root or DLV trust-anchor
+	    names are ignored; these key names can be overridden
+	    using the <code class="option">+dlv=NAME</code> or
+	    <code class="option">+root=NAME</code> options.
+	  </p>
+<p>
+	    Note: When reading the trust anchor file,
+	    <span><strong class="command">delve</strong></span> treats <code class="option">managed-keys</code>
+	    statements and <code class="option">trusted-keys</code> statements
+	    identically.  That is, for a managed key, it is the
+	    <span class="emphasis"><em>initial</em></span> key that is trusted; RFC 5011
+	    key management is not supported. <span><strong class="command">delve</strong></span>
+	    will not consult the managed-keys database maintained by
+	    <span><strong class="command">named</strong></span>. This means that if either of the
+	    keys in <code class="filename">/etc/bind.keys</code> is revoked
+	    and rolled over, it will be necessary to update
+	    <code class="filename">/etc/bind.keys</code> to use DNSSEC
+	    validation in <span><strong class="command">delve</strong></span>.
+	  </p>
+</dd>
+<dt><span class="term">-b</span></dt>
+<dd><p>
+	    Sets the source IP address of the query to
+	    <em class="parameter"><code>address</code></em>.  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
+	    "#&lt;port&gt;"
+	  </p></dd>
+<dt><span class="term">-c</span></dt>
+<dd><p>
+	    Sets the query class for the requested data. Currently,
+	    only class "IN" is supported in <span><strong class="command">delve</strong></span>
+	    and any other value is ignored.
+	  </p></dd>
+<dt><span class="term">-i</span></dt>
+<dd><p>
+	    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 <span><strong class="command">delve</strong></span> to time out. When it
+	    is necessary to examine invalid data to debug a DNSSEC
+	    problem, use <span><strong class="command">dig +cd</strong></span>.)
+	  </p></dd>
+<dt><span class="term">-m</span></dt>
+<dd><p>
+	    Enables memory usage debugging.
+	  </p></dd>
+<dt><span class="term">-p</span></dt>
+<dd><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.
+	  </p></dd>
+<dt><span class="term">-4</span></dt>
+<dd><p>
+	    Forces <span><strong class="command">delve</strong></span> to only use IPv4.
+	  </p></dd>
+<dt><span class="term">-6</span></dt>
+<dd><p>
+	    Forces <span><strong class="command">delve</strong></span> to only use IPv6.
+	  </p></dd>
+<dt><span class="term">-q</span></dt>
+<dd><p>
+	    Sets the query name to <em class="parameter"><code>name</code></em>.
+	    While the query name can be specified without using the
+	    <code class="option">-q</code>, 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).
+	  </p></dd>
+<dt><span class="term">-t</span></dt>
+<dd>
+<p>
+	    Sets the query type to <em class="parameter"><code>type</code></em>, which
+	    can be any valid query type supported in BIND 9 except
+	    for zone transfer types AXFR and IXFR. As with
+	    <code class="option">-q</code>, this is useful to distinguish
+	    query name type or class when they are ambiguous.
+	    it is sometimes necessary to disambiguate names from types.
+	  </p>
+<p>
+	    The default query type is "A", unless the <code class="option">-x</code>
+	    option is supplied to indicate a reverse lookup, in which case
+	    it is "PTR".
+	  </p>
+</dd>
+<dt><span class="term">-x</span></dt>
+<dd><p>
+	    Performs a reverse lookup, mapping an addresses to
+	    a name.  <em class="parameter"><code>addr</code></em> is an IPv4 address in
+	    dotted-decimal notation, or a colon-delimited IPv6 address.
+	    When <code class="option">-x</code> is used, there is no need to provide
+	    the <em class="parameter"><code>name</code></em> or <em class="parameter"><code>type</code></em>
+	    arguments.  <span><strong class="command">delve</strong></span> automatically performs a
+	    lookup for a name like <code class="literal">11.12.13.10.in-addr.arpa</code>
+	    and sets the query type to PTR.  IPv6 addresses are looked up
+	    using nibble format under the IP6.ARPA domain.
+	  </p></dd>
+</dl></div>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2544042"></a><h2>QUERY OPTIONS</h2>
+<p><span><strong class="command">delve</strong></span>
+      provides a number of query options which affect the way results are
+      displayed, and in some cases the way lookups are performed.
+    </p>
+<p>
+      Each query option is identified by a keyword preceded by a plus sign
+      (<code class="literal">+</code>).  Some keywords set or reset an
+      option.  These may be preceded by the string
+      <code class="literal">no</code> to negate the meaning of that keyword.
+      Other keywords assign values to options like the timeout interval.
+      They have the form <code class="option">+keyword=value</code>.
+      The query options are:
+
+      </p>
+<div class="variablelist"><dl>
+<dt><span class="term"><code class="option">+[no]cdflag</code></span></dt>
+<dd><p>
+	      Controls whether to set the CD (checking disabled) bit in
+	      queries sent by <span><strong class="command">delve</strong></span>. 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 <span><strong class="command">delve</strong></span> can then
+	      validate internally and report the errors in detail.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]class</code></span></dt>
+<dd><p>
+	      Controls whether to display the CLASS when printing
+	      a record. The default is to display the CLASS.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]ttl</code></span></dt>
+<dd><p>
+	      Controls whether to display the TTL when printing
+	      a record. The default is to display the TTL.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]rtrace</code></span></dt>
+<dd>
+<p>
+	      Toggle resolver fetch logging. This reports the
+	      name and type of each query sent by <span><strong class="command">delve</strong></span>
+	      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.
+	    </p>
+<p>
+	      This is equivalent to setting the debug level to 1 in
+	      the "resolver" logging category. Setting the systemwide
+	      debug level to 1 using the <code class="option">-d</code> option will
+	      product the same output (but will affect other logging
+	      categories as well).
+	    </p>
+</dd>
+<dt><span class="term"><code class="option">+[no]mtrace</code></span></dt>
+<dd>
+<p>
+	      Toggle message logging. This produces a detailed dump of
+	      the responses received by <span><strong class="command">delve</strong></span> in the
+	      process of carrying out the resolution and validation process.
+	    </p>
+<p>
+	      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 <code class="option">-d</code> option will produce the same output
+	      (but will affect other logging categories as well).
+	    </p>
+</dd>
+<dt><span class="term"><code class="option">+[no]vtrace</code></span></dt>
+<dd>
+<p>
+	      Toggle validation logging. This shows the internal
+	      process of the validator as it determines whether an
+	      answer is validly signed, unsigned, or invalid.
+	    </p>
+<p>
+	      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 <code class="option">-d</code> option will produce the same output
+	      (but will affect other logging categories as well).
+	    </p>
+</dd>
+<dt><span class="term"><code class="option">+[no]short</code></span></dt>
+<dd><p>
+	      Provide a terse answer.  The default is to print the answer in a
+	      verbose form.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]comments</code></span></dt>
+<dd><p>
+	      Toggle the display of comment lines in the output.  The default
+	      is to print comments.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]rrcomments</code></span></dt>
+<dd><p>
+	      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.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]crypto</code></span></dt>
+<dd><p>
+	      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 ]".
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]trust</code></span></dt>
+<dd><p>
+	      Controls whether to display the trust level when printing
+	      a record. The default is to display the trust level.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]split[=W]</code></span></dt>
+<dd><p>
+	      Split long hex- or base64-formatted fields in resource
+	      records into chunks of <em class="parameter"><code>W</code></em> characters
+	      (where <em class="parameter"><code>W</code></em> is rounded up to the nearest
+	      multiple of 4).
+	      <em class="parameter"><code>+nosplit</code></em> or
+	      <em class="parameter"><code>+split=0</code></em> causes fields not to be
+	      split at all.  The default is 56 characters, or 44 characters
+	      when multiline mode is active.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]all</code></span></dt>
+<dd><p>
+	      Set or clear the display options
+	      <code class="option">+[no]comments</code>, 
+	      <code class="option">+[no]rrcomments</code>, and
+	      <code class="option">+[no]trust</code> as a group.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]multiline</code></span></dt>
+<dd><p>
+	      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 <span><strong class="command">delve</strong></span>
+	      output.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]dnssec</code></span></dt>
+<dd><p>
+	      Indicates whether to display RRSIG records in the
+	      <span><strong class="command">delve</strong></span> output.  The default is to
+	      do so.  Note that (unlike in <span><strong class="command">dig</strong></span>)
+	      this does <span class="emphasis"><em>not</em></span> 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
+	      <code class="option">-i</code> or <code class="option">+noroot</code> and
+	      <code class="option">+nodlv</code>.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]root[=ROOT]</code></span></dt>
+<dd><p>
+	      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 <code class="option">-a</code> must be used to specify a file
+	      containing the key.
+	    </p></dd>
+<dt><span class="term"><code class="option">+[no]dlv[=DLV]</code></span></dt>
+<dd><p>
+	      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
+	      <code class="option">-a</code> must be used to specify a file
+	      containing the DLV key.
+	    </p></dd>
+</dl></div>
+<p>
+
+    </p>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2544529"></a><h2>FILES</h2>
+<p><code class="filename">/etc/bind.keys</code></p>
+<p><code class="filename">/etc/resolv.conf</code></p>
+</div>
+<div class="refsect1" lang="en">
+<a name="id2544544"></a><h2>SEE ALSO</h2>
+<p><span class="citerefentry"><span class="refentrytitle">dig</span>(1)</span>,
+      <span class="citerefentry"><span class="refentrytitle">named</span>(8)</span>,
+      <em class="citetitle">RFC4034</em>,
+      <em class="citetitle">RFC4035</em>,
+      <em class="citetitle">RFC4431</em>,
+      <em class="citetitle">RFC5074</em>,
+      <em class="citetitle">RFC5155</em>.
+    </p>
+</div>
+</div></body>
+</html>
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 <obsolete>;\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 <dns/lib.h>
 #include <dns/master.h>
 #include <dns/masterdump.h>
+#include <dns/nta.h>
 #include <dns/order.h>
 #include <dns/peer.h>
 #include <dns/portlist.h>
@@ -89,6 +90,7 @@
 #include <dns/stats.h>
 #include <dns/tkey.h>
 #include <dns/tsig.h>
+#include <dns/ttl.h>
 #include <dns/view.h>
 #include <dns/zone.h>
 #include <dns/zt.h>
@@ -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 @@
         <term><userinput>secroots <optional><replaceable>view ...</replaceable></optional></userinput></term>
         <listitem>
           <para>
-            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.
           </para>
         </listitem>
       </varlistentry>
@@ -561,6 +560,81 @@
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><userinput>nta
+	<optional>( -d | -f | -r | -l <replaceable>duration</replaceable>)</optional>
+	<replaceable>domain</replaceable>
+	<optional><replaceable>view</replaceable></optional>
+	</userinput></term>
+        <listitem>
+          <para>
+            Sets a DNSSEC negative trust anchor (NTA)
+            for <option>domain</option>, with a lifetime of
+            <option>duration</option>.  The default lifetime is
+            configured in <file>named.conf</file> via the
+            <option>nta-lifetime</option>, and defaults to
+            one hour.  The lifetime cannot exceed one week.
+          </para>
+          <para>
+            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), <command>named</command> will
+            abort the DNSSEC validation process and treat the data as
+            insecure rather than bogus.  This continues until the
+            NTA's lifetime is elapsed.
+          </para>
+          <para>
+            NTAs persist across restarts of the named server.
+            The NTAs for a view are saved in a file called
+            <filename><replaceable>name</replaceable>.nta</filename>,
+            where <replaceable>name</replaceable> 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.
+          </para>
+          <para>
+            An existing NTA can be removed by using the
+            <option>-remove</option> option.
+          </para>
+          <para>
+            An NTA's lifetime can be specified with the
+            <option>-lifetime</option> 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 <option>lifetime</option> to zero
+            is equivalent to <option>-remove</option>.
+          </para>
+          <para>
+            If <option>-dump</option> 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).
+          </para>
+          <para>
+            Normally, <command>named</command> will periodically
+            test to see whether data below an NTA can now be
+            validated (see the <option>nta-recheck</option> 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 <option>-force</option> 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.
+          </para>
+          <para>
+            All of these options can be shortened, i.e., to
+            <option>-l</option>, <option>-r</option>, <option>-d</option>,
+            and <option>-f</option>.
+          </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><userinput>tsig-list</userinput></term>
         <listitem>
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]
     <optional> max-refresh-time <replaceable>number</replaceable> ; </optional>
     <optional> min-retry-time <replaceable>number</replaceable> ; </optional>
     <optional> max-retry-time <replaceable>number</replaceable> ; </optional>
+    <optional> nta-lifetime <replaceable>duration</replaceable> ; </optional>
+    <optional> nta-recheck <replaceable>duration</replaceable> ; </optional>
     <optional> port <replaceable>ip_port</replaceable>; </optional>
     <optional> additional-from-auth <replaceable>yes_or_no</replaceable> ; </optional>
     <optional> additional-from-cache <replaceable>yes_or_no</replaceable> ; </optional>
@@ -5684,11 +5686,76 @@ options {
                   <command>named</command>.  (A planned third option,
                   <literal>external</literal>, 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.)
 		</para>
 	    </listitem>
 	  </varlistentry>
 
+	  <varlistentry>
+	    <term><command>nta-lifetime</command></term>
+	    <listitem>
+		<para>
+		  Species the default lifetime, in seconds,
+		  that will be used for negative trust anchors added
+		  via <command>rndc nta</command>. 
+		</para>
+		<para>
+		  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), <command>named</command> 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 <command>named</command> restarts.
+		</para>
+		<para>
+		  For convienience, TTL-style time unit suffixes can be
+		  used to specify the NTA lifetime in seconds, minutes
+		  or hours.  <option>nta-lifetime</option> defaults to
+		  one hour.  It cannot exceed one week.
+		</para>
+	    </listitem>
+	  </varlistentry>
+
+	  <varlistentry>
+	    <term><command>nta-recheck</command></term>
+	    <listitem>
+		<para>
+                  Species how often to check whether negative
+                  trust anchors added via <command>rndc nta</command>
+                  are still necessary.
+		</para>
+		<para>
+                  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, 
+                  <command>named</command> 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.
+		</para>
+		<para>
+                  Validity checks can be disabled for an indivdiual
+                  NTA by using <command>rndc nta -f</command>, or
+                  for all NTA's by setting <option>nta-recheck</option>
+                  to zero.
+		</para>
+		<para>
+		  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 <option>nta-lifetime</option>
+		  (which cannot be longer than a week).
+		</para>
+	    </listitem>
+	  </varlistentry>
+
+
           <varlistentry>
             <term><command>zone-statistics</command></term>
             <listitem>
@@ -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.
 	  </para>
+	  <para>
+	    Validation below specified names can be temporarily disabled
+	    by using <command>rndc nta</command>.
+	  </para>
 	</sect2>
 
         <sect2>
@@ -17037,6 +17108,7 @@ zone "example.com" {
       <title>Manual pages</title>
       <xi:include href="../../bin/dig/dig.docbook"/>
       <xi:include href="../../bin/dig/host.docbook"/>
+      <xi:include href="../../bin/delve/delve.docbook"/>
       <xi:include href="../../bin/python/dnssec-checkds.docbook"/>
       <xi:include href="../../bin/python/dnssec-coverage.docbook"/>
       <xi:include href="../../bin/dnssec/dnssec-dsfromkey.docbook"/>
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 <stddef.h>
 
 #include <isc/app.h>
+#include <isc/buffer.h>
 #include <isc/mem.h>
 #include <isc/mutex.h>
 #include <isc/sockaddr.h>
@@ -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 <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+
+#include <dns/types.h>
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/view.h>
+
+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 <config.h>
+
+#include <isc/buffer.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/rwlock.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/log.h>
+#include <dns/nta.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rbt.h>
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/result.h>
+#include <dns/time.h>
+
+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 <dns/view.h>
 #include <dns/zone.h>
 
-#include <dst/dst.h>
-
 #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 <config.h>
+
+#include <atf-c.h>
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/util.h>
+
+#include <dns/name.h>
+#include <dns/fixedname.h>
+#include <dns/keytable.h>
+#include <dns/nta.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatastruct.h>
+#include <dns/rootns.h>
+#include <dns/view.h>
+
+#include <dst/dst.h>
+
+#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 <config.h>
 
 #include <isc/base32.h>
@@ -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 <isc/file.h>
 #include <isc/hash.h>
+#include <isc/lex.h>
 #include <isc/print.h>
 #include <isc/sha2.h>
 #include <isc/stats.h>
@@ -47,6 +48,7 @@
 #include <dns/keyvalues.h>
 #include <dns/master.h>
 #include <dns/masterdump.h>
+#include <dns/nta.h>
 #include <dns/order.h>
 #include <dns/peer.h>
 #include <dns/rrl.h>
@@ -57,10 +59,16 @@
 #include <dns/result.h>
 #include <dns/rpz.h>
 #include <dns/stats.h>
+#include <dns/time.h>
 #include <dns/tsig.h>
 #include <dns/zone.h>
 #include <dns/zt.h>
 
+#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 <isc/string.h>
 #include <isc/util.h>
 
+#include <dns/ttl.h>
+
 #include <isccfg/cfg.h>
 #include <isccfg/grammar.h>
 #include <isccfg/log.h>
@@ -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

