From 819ae187b1e3229baa33f475b940df01766b2a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Thu, 9 Nov 2017 15:21:34 +0100 Subject: [PATCH] Squashed commit of the following: commit a1d088bf6565826029187ff89e58694eaa236877 Author: Mukund Sivaraman Date: Mon Jan 12 09:04:16 2015 +0530 Add NTA persistence (#37087) 4034. [func] When added, negative trust anchors (NTA) are now saved to files (viewname.nta), in order to persist across restarts of the named server. [RT #37087] (cherry picked from commit a6f0e9c985220f0e4509777e6528afb64e0ad576) commit 326288911b82bc42d3fe2b3de7a07e0060ebc840 Author: Mark Andrews Date: Sat Oct 18 10:07:24 2014 +1100 3979. [bug] Negative trust anchor fetches where not properly managed. [RT #37488] (cherry picked from commit 48f97c23b7d59c925fc3f4280972e50b8ef67c35) commit b9facfeab727ae4d6d64cf9f09998b9d4df183ce Author: Mark Andrews Date: Thu Jul 10 10:24:47 2014 +1000 hold a nta reference while fetching (cherry picked from commit dcc7a2738ff7f68535f9d265df61298e4634055c) commit a8cf1c6ac1500f7f292ebe5a4d314285f76e8eae Author: Mark Andrews Date: Wed Jul 2 14:12:46 2014 +1000 be consistent about expire time (cherry picked from commit 7dbd30979997031ddd539f3543c6d2f9234f7978) commit 0ebf7e0e4b61113a694e88674865cdf086a4b5c1 Author: Mark Andrews Date: Thu Jun 19 10:20:34 2014 +1000 add #include (cherry picked from commit 3a37159a95e961066b90c493c5a4c92d3e0ec029) commit 410c729b2a457334a3d2202777c00a1af1f07f82 Author: Evan Hunt Date: Wed Jun 18 16:47:22 2014 -0700 complete NTA work 3882. [func] By default, negative trust anchors will be tested periodically to see whether data below them can be validated, and if so, they will be allowed to expire early. The "rndc nta -force" option overrides this behvaior. The default NTA lifetime and the recheck frequency can be configured by the "nta-lifetime" and "nta-recheck" options. [RT #36146] (cherry picked from commit b8a9632333a92d73a503afe1aaa7990016c8bee9) commit 0ef07209d5140c2fe395a4a7db25dbb860161e62 Author: Mark Andrews Date: Wed Jun 4 11:45:09 2014 +1000 bad size on isc_mem_put (cherry picked from commit f4db7287da3a6d9d15141d674732d98d39aa2461) commit 1d0a452481b92508cd1a7b4de9ab68e22db69a18 Author: Evan Hunt Date: Thu May 29 22:22:53 2014 -0700 rndc nta 3867. [func] "rndc nta" can now be used to set a temporary negative trust anchor, which disables DNSSEC validation below a specified name for a specified period of time (not exceeding 24 hours). This can be used when validation for a domain is known to be failing due to a configuration error on the part of the domain owner rather than a spoofing attack. [RT #29358] (cherry picked from 0cfb24736841b3e98bb25853229a0efabab88bdd) delve 3741. [func] "delve" (domain entity lookup and validation engine): A new tool with dig-like semantics for performing DNS lookups, with internal DNSSEC validation, using the same resolver and validator logic as named. This allows easy validation of DNSSEC data in environments with untrustworthy resolvers, and assists with troubleshooting of DNSSEC problems. (Note: not yet available on win32.) [RT #32406] Additional fixes to adapt to 9.9 codebase fix possible rndc secroots crash 3765. [bug] Fixed a bug in "rndc secroots" that could crash named when dumping an empty keynode. [RT #35469] 3761. [bug] Address dangling reference bug in dns_keytable_add. [RT #35471] add "rndc nta" options to "rndc -h" output rt38571: handle Time::Piece not being supported by perl (cherry-picked from 2b4860c4dc2d700c85f1edd16e6d5c27ccdb4080) NTAs did not survive reoad/reconfig 4251. [bug] NTAs were deleted when the server was reconfigured or reloaded. [RT #41058] Log path of nta file used into log file Revert some irrelevant parts Return NTA persistent not implemented in export libraries Fix name of nta file in tests update NTA limit to a week 3940. [func] "rndc nta" now allows negative trust anchors to be set for up to one week. [RT #37069] complete change #3882 Parse arguments to "rndc nta" so they can be either long or shortened (i.e., both "-dump" and "-d" will work). "rndc nta -r" could hang 3930. [bug] "rndc nta -r" could cause a server hang if the NTA was not found. [RT #36909] allow 1-week nta-lifetime/nta-recheck 3983. [bug] Change #3940 was incomplete: negative trust anchors could be set to last up to a week, but the "nta-lifetime" and "nta-recheck" options were still limted to one day. [RT #37522] Address warnings and uninitialized variables checking that default nta's were lifted due to lifetime were not robust Reset return codes for separate dnssec tests oops, nta lifetime change broke dnssec test remove remains of file_sanitize not backported Always terminate after NTA entry, even in case all entries would not fit --- bin/delve/Makefile.in | 84 ++ bin/delve/delve.1 | 388 ++++++ bin/delve/delve.c | 1609 ++++++++++++++++++++++ bin/delve/delve.docbook | 639 +++++++++ bin/delve/delve.html | 445 ++++++ bin/named/config.c | 2 + bin/named/control.c | 2 + bin/named/include/named/control.h | 1 + bin/named/include/named/server.h | 19 + bin/named/server.c | 343 ++++- bin/rndc/rndc.c | 12 + bin/rndc/rndc.docbook | 82 +- bin/tests/system/checkconf/good.conf | 2 + bin/tests/system/checkconf/tests.sh | 2 - bin/tests/system/dlvauto/tests.sh | 2 +- bin/tests/system/dnssec/clean.sh | 1 + bin/tests/system/dnssec/ns2/example.db.in | 4 + bin/tests/system/dnssec/ns2/sign.sh | 9 +- bin/tests/system/dnssec/ns3/bogus.example.db.in | 1 + bin/tests/system/dnssec/ns3/named.conf | 6 + bin/tests/system/dnssec/ns3/secure.example.db.in | 4 + bin/tests/system/dnssec/ns3/sign.sh | 16 + bin/tests/system/dnssec/ns4/named1.conf | 3 + bin/tests/system/dnssec/ntadiff.pl | 13 + bin/tests/system/dnssec/setup.sh | 2 + bin/tests/system/dnssec/tests.sh | 396 +++++- bin/tests/system/rndc/tests.sh | 6 + configure.in | 3 + doc/arm/Bv9ARM-book.xml | 74 +- lib/bind9/check.c | 33 + lib/dns/Makefile.in | 7 +- lib/dns/adb.c | 6 +- lib/dns/client.c | 82 +- lib/dns/include/dns/client.h | 26 +- lib/dns/include/dns/keytable.h | 10 +- lib/dns/include/dns/log.h | 2 + lib/dns/include/dns/masterdump.h | 9 + lib/dns/include/dns/message.h | 12 + lib/dns/include/dns/nta.h | 220 +++ lib/dns/include/dns/resolver.h | 5 +- lib/dns/include/dns/result.h | 3 +- lib/dns/include/dns/types.h | 2 + lib/dns/include/dns/validator.h | 7 +- lib/dns/include/dns/view.h | 88 +- lib/dns/keytable.c | 16 +- lib/dns/log.c | 2 + lib/dns/masterdump.c | 26 + lib/dns/message.c | 48 + lib/dns/nta.c | 683 +++++++++ lib/dns/resolver.c | 135 +- lib/dns/result.c | 3 +- lib/dns/tests/Makefile.in | 10 +- lib/dns/tests/dnstest.c | 22 +- lib/dns/tests/dnstest.h | 3 + lib/dns/tests/keytable_test.c | 611 ++++++++ lib/dns/validator.c | 52 +- lib/dns/view.c | 237 +++- lib/export/dns/Makefile.in | 4 +- lib/isc/include/isc/log.h | 6 +- lib/isc/log.c | 20 +- lib/isc/unix/app.c | 1 - lib/isccfg/namedconf.c | 45 + make/includes.in | 3 + make/rules.in | 3 +- 64 files changed, 6453 insertions(+), 159 deletions(-) create mode 100644 bin/delve/Makefile.in create mode 100644 bin/delve/delve.1 create mode 100644 bin/delve/delve.c create mode 100644 bin/delve/delve.docbook create mode 100644 bin/delve/delve.html create mode 100755 bin/tests/system/dnssec/ntadiff.pl create mode 100644 lib/dns/include/dns/nta.h create mode 100644 lib/dns/nta.c create mode 100644 lib/dns/tests/keytable_test.c diff --git a/bin/delve/Makefile.in b/bin/delve/Makefile.in new file mode 100644 index 0000000000..bfd5adfea6 --- /dev/null +++ b/bin/delve/Makefile.in @@ -0,0 +1,84 @@ +# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I${srcdir}/include ${DNS_INCLUDES} ${ISC_INCLUDES} \ + ${IRS_INCLUDES} ${ISCCFG_INCLUDES} + +CDEFINES = -DVERSION=\"${VERSION}\" -DSYSCONFDIR=\"${sysconfdir}\" +CWARNINGS = + +ISCCFGLIBS = ../../lib/export/isccfg/libisccfg-export.@A@ +DNSLIBS = ../../lib/export/dns/libdns-export.@A@ @DNS_CRYPTO_LIBS@ +ISCNOSYMLIBS = ../../lib/export/isc/libisc-export.@A@ +ISCLIBS = ../../lib/export/isc/libisc-export.@A@ +IRSLIBS = ../../lib/export/irs/libirs-export.@A@ + +ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@ +DNSDEPLIBS = ../../lib/export/dns/libdns-export.@A@ +ISCDEPLIBS = ../../lib/export/isc/libisc-export.@A@ +IRSDEPLIBS = ../../lib/export/irs/libirs-export.@A@ + +DEPLIBS = ${DNSDEPLIBS} ${IRSDEPLIBS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} + +LIBS = ${DNSLIBS} ${IRSLIBS} ${ISCCFGLIBS} ${ISCLIBS} @LIBS@ +NOSYMLIBS = ${DNSLIBS} ${IRSLIBS} ${ISCCFGLIBS} ${ISCNOSYMLIBS} @LIBS@ + +SUBDIRS = + +TARGETS = delve@EXEEXT@ + +OBJS = delve.@O@ + +SRCS = delve.c + +MANPAGES = delve.1 + +HTMLPAGES = delve.html + +MANOBJS = ${MANPAGES} ${HTMLPAGES} + +@BIND9_MAKE_RULES@ + +delve@EXEEXT@: delve.@O@ ${DEPLIBS} + export BASEOBJS="delve.@O@"; \ + export LIBS0="${DNSLIBS}"; \ + ${FINALBUILDCMD} + +# ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ +# delve.@O@ ${LIBS} + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${bindir} + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man1 + +install:: delve@EXEEXT@ + ${LIBTOOL_MODE_INSTALL} ${INSTALL_PROGRAM} \ + delve@EXEEXT@ ${DESTDIR}${bindir} + ${INSTALL_DATA} ${srcdir}/delve.1 ${DESTDIR}${mandir}/man1 + +doc man:: ${MANOBJS} + +docclean manclean maintainer-clean:: + rm -f ${MANOBJS} + +clean distclean maintainer-clean:: + rm -f ${TARGETS} diff --git a/bin/delve/delve.1 b/bin/delve/delve.1 new file mode 100644 index 0000000000..54abfcce7e --- /dev/null +++ b/bin/delve/delve.1 @@ -0,0 +1,388 @@ +.\" Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +.\" OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.\" +.\" $Id$ +.\" +.hy 0 +.ad l +.\" Title: delve +.\" Author: +.\" Generator: DocBook XSL Stylesheets v1.71.1 +.\" Date: February 12, 2014 +.\" Manual: BIND9 +.\" Source: BIND9 +.\" +.TH "DELVE" "1" "February 12, 2014" "BIND9" "BIND9" +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.SH "NAME" +delve \- DNS lookup and validation utility +.SH "SYNOPSIS" +.HP 6 +\fBdelve\fR [@server] [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIanchor\-file\fR\fR] [\fB\-b\ \fR\fB\fIaddress\fR\fR] [\fB\-c\ \fR\fB\fIclass\fR\fR] [\fB\-d\ \fR\fB\fIlevel\fR\fR] [\fB\-i\fR] [\fB\-m\fR] [\fB\-p\ \fR\fB\fIport#\fR\fR] [\fB\-q\ \fR\fB\fIname\fR\fR] [\fB\-t\ \fR\fB\fItype\fR\fR] [\fB\-x\ \fR\fB\fIaddr\fR\fR] [name] [type] [class] [queryopt...] +.HP 6 +\fBdelve\fR [\fB\-h\fR] +.HP 6 +\fBdelve\fR [queryopt...] [query...] +.SH "DESCRIPTION" +.PP +\fBdelve\fR +(Domain Entity Lookup & Validation Engine) is a tool for sending DNS queries and validating the results, using the the same internal resolver and validator logic as +\fBnamed\fR. +.PP +\fBdelve\fR +will send to a specified name server all queries needed to fetch and validate the requested data; this includes the original requested query, subsequent queries to follow CNAME or DNAME chains, and queries for DNSKEY, DS and DLV records to establish a chain of trust for DNSSEC validation. It does not perform iterative resolution, but simulates the behavior of a name server configured for DNSSEC validating and forwarding. +.PP +By default, responses are validated using built\-in DNSSEC trust anchors for the root zone (".") and for the ISC DNSSEC lookaside validation zone ("dlv.isc.org"). Records returned by +\fBdelve\fR +are either fully validated or were not signed. If validation fails, an explanation of the failure is included in the output; the validation process can be traced in detail. Because +\fBdelve\fR +does not rely on an external server to carry out validation, it can be used to check the validity of DNS responses in environments where local name servers may not be trustworthy. +.PP +Unless it is told to query a specific name server, +\fBdelve\fR +will try each of the servers listed in +\fI/etc/resolv.conf\fR. If no usable server addresses are found, +\fBdelve\fR +will send queries to the localhost addresses (127.0.0.1 for IPv4, ::1 for IPv6). +.PP +When no command line arguments or options are given, +\fBdelve\fR +will perform an NS query for "." (the root zone). +.SH "SIMPLE USAGE" +.PP +A typical invocation of +\fBdelve\fR +looks like: +.sp +.RS 4 +.nf + delve @server name type +.fi +.RE +.sp +where: +.PP +\fBserver\fR +.RS 4 +is the name or IP address of the name server to query. This can be an IPv4 address in dotted\-decimal notation or an IPv6 address in colon\-delimited notation. When the supplied +\fIserver\fR +argument is a hostname, +\fBdelve\fR +resolves that name before querying that name server (note, however, that this initial lookup is +\fInot\fR +validated by DNSSEC). +.sp +If no +\fIserver\fR +argument is provided, +\fBdelve\fR +consults +\fI/etc/resolv.conf\fR; if an address is found there, it queries the name server at that address. If either of the +\fB\-4\fR +or +\fB\-6\fR +options are in use, then only addresses for the corresponding transport will be tried. If no usable addresses are found, +\fBdelve\fR +will send queries to the localhost addresses (127.0.0.1 for IPv4, ::1 for IPv6). +.RE +.PP +\fBname\fR +.RS 4 +is the domain name to be looked up. +.RE +.PP +\fBtype\fR +.RS 4 +indicates what type of query is required \(em ANY, A, MX, etc. +\fItype\fR +can be any valid query type. If no +\fItype\fR +argument is supplied, +\fBdelve\fR +will perform a lookup for an A record. +.RE +.SH "OPTIONS" +.PP +\-a +.RS 4 +Specifies a file from which to read DNSSEC trust anchors. The default is +\fI/etc/bind.keys\fR, which is included with +BIND +9 and contains trust anchors for the root zone (".") and for the ISC DNSSEC lookaside validation zone ("dlv.isc.org"). +.sp +Keys that do not match the root or DLV trust\-anchor names are ignored; these key names can be overridden using the +\fB+dlv=NAME\fR +or +\fB+root=NAME\fR +options. +.sp +Note: When reading the trust anchor file, +\fBdelve\fR +treats +\fBmanaged\-keys\fR +statements and +\fBtrusted\-keys\fR +statements identically. That is, for a managed key, it is the +\fIinitial\fR +key that is trusted; RFC 5011 key management is not supported. +\fBdelve\fR +will not consult the managed\-keys database maintained by +\fBnamed\fR. This means that if either of the keys in +\fI/etc/bind.keys\fR +is revoked and rolled over, it will be necessary to update +\fI/etc/bind.keys\fR +to use DNSSEC validation in +\fBdelve\fR. +.RE +.PP +\-b +.RS 4 +Sets the source IP address of the query to +\fIaddress\fR. This must be a valid address on one of the host's network interfaces or "0.0.0.0" or "::". An optional source port may be specified by appending "#" +.RE +.PP +\-c +.RS 4 +Sets the query class for the requested data. Currently, only class "IN" is supported in +\fBdelve\fR +and any other value is ignored. +.RE +.PP +\-i +.RS 4 +Insecure mode. This disables internal DNSSEC validation. (Note, however, this does not set the CD bit on upstream queries. If the server being queried is performing DNSSEC validation, then it will not return invalid data; this can cause +\fBdelve\fR +to time out. When it is necessary to examine invalid data to debug a DNSSEC problem, use +\fBdig +cd\fR.) +.RE +.PP +\-m +.RS 4 +Enables memory usage debugging. +.RE +.PP +\-p +.RS 4 +Specifies a destination port to use for queries instead of the standard DNS port number 53. This option would be used with a name server that has been configured to listen for queries on a non\-standard port number. +.RE +.PP +\-4 +.RS 4 +Forces +\fBdelve\fR +to only use IPv4. +.RE +.PP +\-6 +.RS 4 +Forces +\fBdelve\fR +to only use IPv6. +.RE +.PP +\-q +.RS 4 +Sets the query name to +\fIname\fR. While the query name can be specified without using the +\fB\-q\fR, it is sometimes necessary to disambiguate names from types or classes (for example, when looking up the name "ns", which could be misinterpreted as the type NS, or "ch", which could be misinterpreted as class CH). +.RE +.PP +\-t +.RS 4 +Sets the query type to +\fItype\fR, which can be any valid query type supported in BIND 9 except for zone transfer types AXFR and IXFR. As with +\fB\-q\fR, this is useful to distinguish query name type or class when they are ambiguous. it is sometimes necessary to disambiguate names from types. +.sp +The default query type is "A", unless the +\fB\-x\fR +option is supplied to indicate a reverse lookup, in which case it is "PTR". +.RE +.PP +\-x +.RS 4 +Performs a reverse lookup, mapping an addresses to a name. +\fIaddr\fR +is an IPv4 address in dotted\-decimal notation, or a colon\-delimited IPv6 address. When +\fB\-x\fR +is used, there is no need to provide the +\fIname\fR +or +\fItype\fR +arguments. +\fBdelve\fR +automatically performs a lookup for a name like +11.12.13.10.in\-addr.arpa +and sets the query type to PTR. IPv6 addresses are looked up using nibble format under the IP6.ARPA domain. +.RE +.SH "QUERY OPTIONS" +.PP +\fBdelve\fR +provides a number of query options which affect the way results are displayed, and in some cases the way lookups are performed. +.PP +Each query option is identified by a keyword preceded by a plus sign (+). Some keywords set or reset an option. These may be preceded by the string +no +to negate the meaning of that keyword. Other keywords assign values to options like the timeout interval. They have the form +\fB+keyword=value\fR. The query options are: +.PP +\fB+[no]cdflag\fR +.RS 4 +Controls whether to set the CD (checking disabled) bit in queries sent by +\fBdelve\fR. This may be useful when troubleshooting DNSSEC problems from behind a validating resolver. A validating resolver will block invalid responses, making it difficult to retrieve them for analysis. Setting the CD flag on queries will cause the resolver to return invalid responses, which +\fBdelve\fR +can then validate internally and report the errors in detail. +.RE +.PP +\fB+[no]class\fR +.RS 4 +Controls whether to display the CLASS when printing a record. The default is to display the CLASS. +.RE +.PP +\fB+[no]ttl\fR +.RS 4 +Controls whether to display the TTL when printing a record. The default is to display the TTL. +.RE +.PP +\fB+[no]rtrace\fR +.RS 4 +Toggle resolver fetch logging. This reports the name and type of each query sent by +\fBdelve\fR +in the process of carrying out the resolution and validation process: this includes including the original query and all subsequent queries to follow CNAMEs and to establish a chain of trust for DNSSEC validation. +.sp +This is equivalent to setting the debug level to 1 in the "resolver" logging category. Setting the systemwide debug level to 1 using the +\fB\-d\fR +option will product the same output (but will affect other logging categories as well). +.RE +.PP +\fB+[no]mtrace\fR +.RS 4 +Toggle message logging. This produces a detailed dump of the responses received by +\fBdelve\fR +in the process of carrying out the resolution and validation process. +.sp +This is equivalent to setting the debug level to 10 for the the "packets" module of the "resolver" logging category. Setting the systemwide debug level to 10 using the +\fB\-d\fR +option will produce the same output (but will affect other logging categories as well). +.RE +.PP +\fB+[no]vtrace\fR +.RS 4 +Toggle validation logging. This shows the internal process of the validator as it determines whether an answer is validly signed, unsigned, or invalid. +.sp +This is equivalent to setting the debug level to 3 for the the "validator" module of the "dnssec" logging category. Setting the systemwide debug level to 3 using the +\fB\-d\fR +option will produce the same output (but will affect other logging categories as well). +.RE +.PP +\fB+[no]short\fR +.RS 4 +Provide a terse answer. The default is to print the answer in a verbose form. +.RE +.PP +\fB+[no]comments\fR +.RS 4 +Toggle the display of comment lines in the output. The default is to print comments. +.RE +.PP +\fB+[no]rrcomments\fR +.RS 4 +Toggle the display of per\-record comments in the output (for example, human\-readable key information about DNSKEY records). The default is to print per\-record comments. +.RE +.PP +\fB+[no]crypto\fR +.RS 4 +Toggle the display of cryptographic fields in DNSSEC records. The contents of these field are unnecessary to debug most DNSSEC validation failures and removing them makes it easier to see the common failures. The default is to display the fields. When omitted they are replaced by the string "[omitted]" or in the DNSKEY case the key id is displayed as the replacement, e.g. "[ key id = value ]". +.RE +.PP +\fB+[no]trust\fR +.RS 4 +Controls whether to display the trust level when printing a record. The default is to display the trust level. +.RE +.PP +\fB+[no]split[=W]\fR +.RS 4 +Split long hex\- or base64\-formatted fields in resource records into chunks of +\fIW\fR +characters (where +\fIW\fR +is rounded up to the nearest multiple of 4). +\fI+nosplit\fR +or +\fI+split=0\fR +causes fields not to be split at all. The default is 56 characters, or 44 characters when multiline mode is active. +.RE +.PP +\fB+[no]all\fR +.RS 4 +Set or clear the display options +\fB+[no]comments\fR, +\fB+[no]rrcomments\fR, and +\fB+[no]trust\fR +as a group. +.RE +.PP +\fB+[no]multiline\fR +.RS 4 +Print long records (such as RRSIG, DNSKEY, and SOA records) in a verbose multi\-line format with human\-readable comments. The default is to print each record on a single line, to facilitate machine parsing of the +\fBdelve\fR +output. +.RE +.PP +\fB+[no]dnssec\fR +.RS 4 +Indicates whether to display RRSIG records in the +\fBdelve\fR +output. The default is to do so. Note that (unlike in +\fBdig\fR) this does +\fInot\fR +control whether to request DNSSEC records or whether to validate them. DNSSEC records are always requested, and validation will always occur unless suppressed by the use of +\fB\-i\fR +or +\fB+noroot\fR +and +\fB+nodlv\fR. +.RE +.PP +\fB+[no]root[=ROOT]\fR +.RS 4 +Indicates whether to perform conventional (non\- lookaside) DNSSEC validation, and if so, specifies the name of a trust anchor. The default is to validate using a trust anchor of "." (the root zone), for which there is a built\-in key. If specifying a different trust anchor, then +\fB\-a\fR +must be used to specify a file containing the key. +.RE +.PP +\fB+[no]dlv[=DLV]\fR +.RS 4 +Indicates whether to perform DNSSEC lookaside validation, and if so, specifies the name of the DLV trust anchor. The default is to perform lookaside validation using a trust anchor of "dlv.isc.org", for which there is a built\-in key. If specifying a different name, then +\fB\-a\fR +must be used to specify a file containing the DLV key. +.RE +.SH "FILES" +.PP +\fI/etc/bind.keys\fR +.PP +\fI/etc/resolv.conf\fR +.SH "SEE ALSO" +.PP +\fBdig\fR(1), +\fBnamed\fR(8), +RFC4034, +RFC4035, +RFC4431, +RFC5074, +RFC5155. +.SH "COPYRIGHT" +Copyright \(co 2014 Internet Systems Consortium, Inc. ("ISC") +.br diff --git a/bin/delve/delve.c b/bin/delve/delve.c new file mode 100644 index 0000000000..9816cff1e4 --- /dev/null +++ b/bin/delve/delve.c @@ -0,0 +1,1609 @@ +/* + * Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +#define MAXNAME (DNS_NAME_MAXTEXT+1) + +/* Variables used internally by delve. */ +char *progname; +static isc_mem_t *mctx = NULL; +static isc_log_t *lctx = NULL; + +/* Configurables */ +static char *server = NULL; +static const char *port = "53"; +static isc_sockaddr_t *srcaddr4 = NULL, *srcaddr6 = NULL; +static char *curqname = NULL, *qname = NULL; +static isc_boolean_t classset = ISC_FALSE; +static dns_rdatatype_t qtype = dns_rdatatype_none; +static isc_boolean_t typeset = ISC_FALSE; + +static unsigned int styleflags = 0; +static isc_uint32_t splitwidth = 0xffffffff; +static isc_boolean_t + showcomments = ISC_TRUE, + showdnssec = ISC_TRUE, + showtrust = ISC_TRUE, + rrcomments = ISC_TRUE, + noclass = ISC_FALSE, + nocrypto = ISC_FALSE, + nottl = ISC_FALSE, + multiline = ISC_FALSE, + short_form = ISC_FALSE; + +static isc_boolean_t + resolve_trace = ISC_FALSE, + validator_trace = ISC_FALSE, + message_trace = ISC_FALSE; + +static isc_boolean_t + use_ipv4 = ISC_TRUE, + use_ipv6 = ISC_TRUE; + +static isc_boolean_t + cdflag = ISC_FALSE, + no_sigs = ISC_FALSE, + root_validation = ISC_TRUE, + dlv_validation = ISC_TRUE; + +static char *anchorfile = NULL; +static char *trust_anchor = NULL; +static char *dlv_anchor = NULL; +static int trusted_keys = 0; + +static dns_fixedname_t afn, dfn; +static dns_name_t *anchor_name = NULL, *dlv_name = NULL; + +/* Default bind.keys contents */ +static char anchortext[] = MANAGED_KEYS; + +/* + * Static function prototypes + */ +static isc_result_t +get_reverse(char *reverse, size_t len, char *value, isc_boolean_t strict); + +static isc_result_t +parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, + const char *desc); + +static void +usage(void) { + fputs( +"Usage: delve [@server] {q-opt} {d-opt} [domain] [q-type] [q-class]\n" +"Where: domain is in the Domain Name System\n" +" q-class is one of (in,hs,ch,...) [default: in]\n" +" q-type is one of (a,any,mx,ns,soa,hinfo,axfr,txt,...) [default:a]\n" +" q-opt is one of:\n" +" -x dot-notation (shortcut for reverse lookups)\n" +" -d level (set debugging level)\n" +" -a anchor-file (specify root and dlv trust anchors)\n" +" -b address[#port] (bind to source address/port)\n" +" -p port (specify port number)\n" +" -q name (specify query name)\n" +" -t type (specify query type)\n" +" -c class (specify query class)\n" +" -4 (use IPv4 query transport only)\n" +" -6 (use IPv6 query transport only)\n" +" -i (disable DNSSEC validation)\n" +" -m (enable memory usage debugging)\n" +" d-opt is of the form +keyword[=value], where keyword is:\n" +" +[no]all (Set or clear all display flags)\n" +" +[no]class (Control display of class)\n" +" +[no]crypto (Control display of cryptographic\n" +" fields in records)\n" +" +[no]multiline (Print records in an expanded format)\n" +" +[no]comments (Control display of comment lines)\n" +" +[no]rrcomments (Control display of per-record " + "comments)\n" +" +[no]short (Short form answer)\n" +" +[no]split=## (Split hex/base64 fields into chunks)\n" +" +[no]ttl (Control display of ttls in records)\n" +" +[no]trust (Control display of trust level)\n" +" +[no]rtrace (Trace resolver fetches)\n" +" +[no]mtrace (Trace messages received)\n" +" +[no]vtrace (Trace validation process)\n" +" +[no]dlv (DNSSEC lookaside validation anchor)\n" +" +[no]root (DNSSEC validation trust anchor)\n" +" +[no]dnssec (Display DNSSEC records)\n" +" -h (print help and exit)\n" +" -v (print version and exit)\n", + stderr); + exit(1); +} + +static void +fatal(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, "%s: ", progname); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(1); +} + +static void +warn(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, "%s: warning: ", progname); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +} + +static isc_logcategory_t categories[] = { + { "delve", 0 }, + { NULL, 0 } +}; +#define LOGCATEGORY_DEFAULT (&categories[0]) +#define LOGMODULE_DEFAULT (&modules[0]) + +static isc_logmodule_t modules[] = { + { "delve", 0 }, + { NULL, 0 } +}; + +static void +delve_log(int level, const char *fmt, ...) { + va_list ap; + char msgbuf[2048]; + + if (! isc_log_wouldlog(lctx, level)) + return; + + va_start(ap, fmt); + + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + isc_log_write(lctx, LOGCATEGORY_DEFAULT, LOGMODULE_DEFAULT, + level, "%s", msgbuf); + va_end(ap); +} + +static int loglevel = 0; + +static void +setup_logging(FILE *errout) { + isc_result_t result; + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + result = isc_log_create(mctx, &lctx, &logconfig); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set up logging"); + + isc_log_registercategories(lctx, categories); + isc_log_registermodules(lctx, modules); + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + cfg_log_init(lctx); + + destination.file.stream = errout; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + + result = isc_log_createchannel(logconfig, "stderr", + ISC_LOG_TOFILEDESC, ISC_LOG_DYNAMIC, + &destination, ISC_LOG_PRINTPREFIX); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set up log channel 'stderr'"); + + isc_log_setdebuglevel(lctx, loglevel); + + result = isc_log_settag(logconfig, ";; "); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set log tag"); + + result = isc_log_usechannel(logconfig, "stderr", + ISC_LOGCATEGORY_DEFAULT, NULL); + if (result != ISC_R_SUCCESS) + fatal("Couldn't attach to log channel 'stderr'"); + + if (resolve_trace && loglevel < 1) { + result = isc_log_createchannel(logconfig, "resolver", + ISC_LOG_TOFILEDESC, + ISC_LOG_DEBUG(1), + &destination, + ISC_LOG_PRINTPREFIX); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set up log channel 'resolver'"); + + result = isc_log_usechannel(logconfig, "resolver", + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER); + if (result != ISC_R_SUCCESS) + fatal("Couldn't attach to log channel 'resolver'"); + } + + if (validator_trace && loglevel < 3) { + result = isc_log_createchannel(logconfig, "validator", + ISC_LOG_TOFILEDESC, + ISC_LOG_DEBUG(3), + &destination, + ISC_LOG_PRINTPREFIX); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set up log channel 'validator'"); + + result = isc_log_usechannel(logconfig, "validator", + DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_VALIDATOR); + if (result != ISC_R_SUCCESS) + fatal("Couldn't attach to log channel 'validator'"); + } + + if (message_trace && loglevel < 10) { + result = isc_log_createchannel(logconfig, "messages", + ISC_LOG_TOFILEDESC, + ISC_LOG_DEBUG(10), + &destination, + ISC_LOG_PRINTPREFIX); + if (result != ISC_R_SUCCESS) + fatal("Couldn't set up log channel 'messages'"); + + result = isc_log_usechannel(logconfig, "messages", + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS); + if (result != ISC_R_SUCCESS) + fatal("Couldn't attach to log channel 'messagse'"); + } +} + +static void +print_status(dns_rdataset_t *rdataset) { + const char *astr = "", *tstr = ""; + + REQUIRE(rdataset != NULL); + + if (!showtrust || !dns_rdataset_isassociated(rdataset)) + return; + + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) + astr = "negative response, "; + + switch (rdataset->trust) { + case dns_trust_none: + tstr = "untrusted"; + break; + case dns_trust_pending_additional: + tstr = "signed additional data, pending validation"; + break; + case dns_trust_pending_answer: + tstr = "signed answer, pending validation"; + break; + case dns_trust_additional: + tstr = "unsigned additional data"; + break; + case dns_trust_glue: + tstr = "glue data"; + break; + case dns_trust_answer: + if (root_validation || dlv_validation) + tstr = "unsigned answer"; + else + tstr = "answer not validated"; + break; + case dns_trust_authauthority: + tstr = "authority data"; + break; + case dns_trust_authanswer: + tstr = "authoritative"; + break; + case dns_trust_secure: + tstr = "fully validated"; + break; + case dns_trust_ultimate: + tstr = "ultimate trust"; + break; + } + + printf("; %s%s\n", astr, tstr); +} + +static isc_result_t +printdata(dns_rdataset_t *rdataset, dns_name_t *owner, + dns_master_style_t *style) +{ + isc_result_t result = ISC_R_SUCCESS; + static dns_trust_t trust; + static isc_boolean_t first = ISC_TRUE; + isc_buffer_t target; + isc_region_t r; + char *t = NULL; + int len = 2048; + + if (!dns_rdataset_isassociated(rdataset)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(owner, namebuf, sizeof(namebuf)); + delve_log(ISC_LOG_DEBUG(4), + "WARN: empty rdataset %s", namebuf); + return (ISC_R_SUCCESS); + } + + if (!showdnssec && rdataset->type == dns_rdatatype_rrsig) + return (ISC_R_SUCCESS); + + isc_buffer_init(&target, t, sizeof(t)); + + if (first || rdataset->trust != trust) { + if (!first && showtrust && !short_form) + putchar('\n'); + print_status(rdataset); + trust = rdataset->trust; + first = ISC_FALSE; + } + + do { + t = isc_mem_get(mctx, len); + if (t == NULL) + return (ISC_R_NOMEMORY); + + isc_buffer_init(&target, t, len); + if (short_form) { + dns_rdata_t rdata = DNS_RDATA_INIT; + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + isc_region_t r; + + if ((rdataset->attributes & + DNS_RDATASETATTR_NEGATIVE) != 0) + continue; + + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tofmttext(&rdata, + dns_rootname, + styleflags, + 0, 60, " ", + &target); + if (result != ISC_R_SUCCESS) + break; + + isc_buffer_availableregion(&target, &r); + if (r.length < 1) { + result = ISC_R_NOSPACE; + break; + } + + r.base[0] = '\n'; + isc_buffer_add(&target, 1); + + dns_rdata_reset(&rdata); + } + } else { + if ((rdataset->attributes & + DNS_RDATASETATTR_NEGATIVE) != 0) + isc_buffer_putstr(&target, "; "); + + result = dns_master_rdatasettotext(owner, rdataset, + style, &target); + } + + if (result == ISC_R_NOSPACE) { + isc_mem_put(mctx, t, len); + len += 1024; + } else + CHECK(result); + } while (result == ISC_R_NOSPACE); + + isc_buffer_usedregion(&target, &r); + printf("%.*s", (int)r.length, (char *)r.base); + + cleanup: + if (t != NULL) + isc_mem_put(mctx, t, len); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +setup_style(dns_master_style_t **stylep) { + isc_result_t result; + dns_master_style_t *style = NULL; + + REQUIRE(stylep != NULL || *stylep == NULL); + + styleflags |= DNS_STYLEFLAG_REL_OWNER; + if (showcomments) + styleflags |= DNS_STYLEFLAG_COMMENT; + if (rrcomments) + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + if (nottl) + styleflags |= DNS_STYLEFLAG_NO_TTL; + if (noclass) + styleflags |= DNS_STYLEFLAG_NO_CLASS; +#if 0 + if (nocrypto) + styleflags |= DNS_STYLEFLAG_NOCRYPTO; +#endif + if (multiline) { + styleflags |= DNS_STYLEFLAG_MULTILINE; + styleflags |= DNS_STYLEFLAG_COMMENT; + } + + if (multiline || (nottl && noclass)) + result = dns_master_stylecreate2(&style, styleflags, + 24, 24, 24, 32, 80, 8, + splitwidth, mctx); + else if (nottl || noclass) + result = dns_master_stylecreate2(&style, styleflags, + 24, 24, 32, 40, 80, 8, + splitwidth, mctx); + else + result = dns_master_stylecreate2(&style, styleflags, + 24, 32, 40, 48, 80, 8, + splitwidth, mctx); + + if (result == ISC_R_SUCCESS) + *stylep = style; + return (result); +} + +static isc_result_t +convert_name(dns_fixedname_t *fn, dns_name_t **name, const char *text) { + isc_result_t result; + isc_buffer_t b; + dns_name_t *n; + size_t len; + + REQUIRE(fn != NULL && name != NULL && text != NULL); + len = strlen(text); + + isc_buffer_constinit(&b, text, len); + isc_buffer_add(&b, len); + dns_fixedname_init(fn); + n = dns_fixedname_name(fn); + + result = dns_name_fromtext(n, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + delve_log(ISC_LOG_ERROR, "failed to convert QNAME %s: %s", + text, isc_result_totext(result)); + return (result); + } + + *name = n; + return (ISC_R_SUCCESS); +} + +static isc_result_t +key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { + dns_rdata_dnskey_t keystruct; + isc_uint32_t flags, proto, alg; + const char *keystr, *keynamestr; + unsigned char keydata[4096]; + isc_buffer_t keydatabuf; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_region_t r; + dns_fixedname_t fkeyname; + dns_name_t *keyname; + isc_result_t result; + isc_boolean_t match_root, match_dlv; + + keynamestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); + CHECK(convert_name(&fkeyname, &keyname, keynamestr)); + + if (!root_validation && !dlv_validation) + return (ISC_R_SUCCESS); + + match_root = dns_name_equal(keyname, anchor_name); + match_dlv = dns_name_equal(keyname, dlv_name); + + if (!match_root && !match_dlv) + return (ISC_R_SUCCESS); + if ((!root_validation && match_root) || (!dlv_validation && match_dlv)) + return (ISC_R_SUCCESS); + + if (match_root) + delve_log(ISC_LOG_DEBUG(3), "adding trust anchor %s", + trust_anchor); + if (match_dlv) + delve_log(ISC_LOG_DEBUG(3), "adding DLV trust anchor %s", + dlv_anchor); + + flags = cfg_obj_asuint32(cfg_tuple_get(key, "flags")); + proto = cfg_obj_asuint32(cfg_tuple_get(key, "protocol")); + alg = cfg_obj_asuint32(cfg_tuple_get(key, "algorithm")); + + keystruct.common.rdclass = dns_rdataclass_in; + keystruct.common.rdtype = dns_rdatatype_dnskey; + /* + * The key data in keystruct is not dynamically allocated. + */ + keystruct.mctx = NULL; + + ISC_LINK_INIT(&keystruct.common, link); + + if (flags > 0xffff) + CHECK(ISC_R_RANGE); + if (proto > 0xff) + CHECK(ISC_R_RANGE); + if (alg > 0xff) + CHECK(ISC_R_RANGE); + + keystruct.flags = (isc_uint16_t)flags; + keystruct.protocol = (isc_uint8_t)proto; + keystruct.algorithm = (isc_uint8_t)alg; + + isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + + keystr = cfg_obj_asstring(cfg_tuple_get(key, "key")); + CHECK(isc_base64_decodestring(keystr, &keydatabuf)); + isc_buffer_usedregion(&keydatabuf, &r); + keystruct.datalen = r.length; + keystruct.data = r.base; + + CHECK(dns_rdata_fromstruct(NULL, + keystruct.common.rdclass, + keystruct.common.rdtype, + &keystruct, &rrdatabuf)); + + CHECK(dns_client_addtrustedkey(client, dns_rdataclass_in, + keyname, &rrdatabuf)); + trusted_keys++; + + cleanup: + if (result == DST_R_NOCRYPTO) + cfg_obj_log(key, lctx, ISC_LOG_ERROR, "no crypto support"); + else if (result == DST_R_UNSUPPORTEDALG) { + cfg_obj_log(key, lctx, ISC_LOG_WARNING, + "skipping trusted key '%s': %s", + keynamestr, isc_result_totext(result)); + result = ISC_R_SUCCESS; + } else if (result != ISC_R_SUCCESS) { + cfg_obj_log(key, lctx, ISC_LOG_ERROR, + "failed to add trusted key '%s': %s", + keynamestr, isc_result_totext(result)); + result = ISC_R_FAILURE; + } + + return (result); +} + +static isc_result_t +load_keys(const cfg_obj_t *keys, dns_client_t *client) { + const cfg_listelt_t *elt, *elt2; + const cfg_obj_t *key, *keylist; + isc_result_t result = ISC_R_SUCCESS; + + for (elt = cfg_list_first(keys); + elt != NULL; + elt = cfg_list_next(elt)) + { + keylist = cfg_listelt_value(elt); + + for (elt2 = cfg_list_first(keylist); + elt2 != NULL; + elt2 = cfg_list_next(elt2)) + { + key = cfg_listelt_value(elt2); + CHECK(key_fromconfig(key, client)); + } + } + + cleanup: + if (result == DST_R_NOCRYPTO) + result = ISC_R_SUCCESS; + return (result); +} + +static isc_result_t +setup_dnsseckeys(dns_client_t *client) { + isc_result_t result; + cfg_parser_t *parser = NULL; + const cfg_obj_t *keys = NULL; + const cfg_obj_t *managed_keys = NULL; + cfg_obj_t *bindkeys = NULL; + const char *filename = anchorfile; + + if (!root_validation && !dlv_validation) + return (ISC_R_SUCCESS); + + if (filename == NULL) + filename = SYSCONFDIR "/bind.keys"; + + if (trust_anchor == NULL) + trust_anchor = isc_mem_strdup(mctx, "."); + if (dlv_anchor == NULL) + dlv_anchor = isc_mem_strdup(mctx, "dlv.isc.org"); + + CHECK(convert_name(&afn, &anchor_name, trust_anchor)); + CHECK(convert_name(&dfn, &dlv_name, dlv_anchor)); + + CHECK(cfg_parser_create(mctx, dns_lctx, &parser)); + + if (access(filename, R_OK) != 0) { + if (anchorfile != NULL) + fatal("Unable to read key file '%s'", anchorfile); + } else { + result = cfg_parse_file(parser, filename, + &cfg_type_bindkeys, &bindkeys); + if (result != ISC_R_SUCCESS) + if (anchorfile != NULL) + fatal("Unable to load keys from '%s'", + anchorfile); + } + + if (bindkeys == NULL) { + isc_buffer_t b; + + isc_buffer_init(&b, anchortext, sizeof(anchortext) - 1); + isc_buffer_add(&b, sizeof(anchortext) - 1); + result = cfg_parse_buffer(parser, &b, &cfg_type_bindkeys, + &bindkeys); + if (result != ISC_R_SUCCESS) + fatal("Unable to parse built-in keys"); + } + + INSIST(bindkeys != NULL); + cfg_map_get(bindkeys, "trusted-keys", &keys); + cfg_map_get(bindkeys, "managed-keys", &managed_keys); + + if (keys != NULL) + CHECK(load_keys(keys, client)); + if (managed_keys != NULL) + CHECK(load_keys(managed_keys, client)); + result = ISC_R_SUCCESS; + + if (trusted_keys == 0) + fatal("No trusted keys were loaded"); + + if (dlv_validation) + dns_client_setdlv(client, dns_rdataclass_in, dlv_anchor); + + cleanup: + if (result != ISC_R_SUCCESS) + delve_log(ISC_LOG_ERROR, "setup_dnsseckeys: %s", + isc_result_totext(result)); + return (result); +} + +static isc_result_t +addserver(dns_client_t *client) { + struct addrinfo hints, *res, *cur; + int gai_error; + struct in_addr in4; + struct in6_addr in6; + isc_sockaddr_t *sa; + isc_sockaddrlist_t servers; + isc_uint32_t destport; + isc_result_t result; + dns_name_t *name = NULL; + + result = parse_uint(&destport, port, 0xffff, "port"); + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse port number"); + + ISC_LIST_INIT(servers); + + if (use_ipv4 && inet_pton(AF_INET, server, &in4) == 1) { + sa = isc_mem_get(mctx, sizeof(*sa)); + ISC_LINK_INIT(sa, link); + isc_sockaddr_fromin(sa, &in4, destport); + ISC_LIST_APPEND(servers, sa, link); + } else if (use_ipv6 && inet_pton(AF_INET6, server, &in6) == 1) { + sa = isc_mem_get(mctx, sizeof(*sa)); + ISC_LINK_INIT(sa, link); + isc_sockaddr_fromin6(sa, &in6, destport); + ISC_LIST_APPEND(servers, sa, link); + } else { + memset(&hints, 0, sizeof(hints)); + if (!use_ipv6) + hints.ai_family = AF_INET; + else if (!use_ipv4) + hints.ai_family = AF_INET6; + else + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + gai_error = getaddrinfo(server, port, &hints, &res); + if (gai_error != 0) { + delve_log(ISC_LOG_ERROR, + "getaddrinfo failed: %s", + gai_strerror(gai_error)); + return (ISC_R_FAILURE); + } + + for (cur = res; cur != NULL; cur = res->ai_next) { + sa = isc_mem_get(mctx, sizeof(*sa)); + memset(sa, 0, sizeof(*sa)); + ISC_LINK_INIT(sa, link); + memmove(&sa->type, res->ai_addr, res->ai_addrlen); + sa->length = res->ai_addrlen; + ISC_LIST_APPEND(servers, sa, link); + freeaddrinfo(res); + } + } + + + CHECK(dns_client_setservers(client, dns_rdataclass_in, name, &servers)); + + cleanup: + while (!ISC_LIST_EMPTY(servers)) { + sa = ISC_LIST_HEAD(servers); + ISC_LIST_UNLINK(servers, sa, link); + isc_mem_put(mctx, sa, sizeof(*sa)); + } + + if (result != ISC_R_SUCCESS) + delve_log(ISC_LOG_ERROR, "addserver: %s", + isc_result_totext(result)); + + return (result); +} + +static isc_result_t +findserver(dns_client_t *client) { + isc_result_t result; + irs_resconf_t *resconf = NULL; + isc_sockaddrlist_t *nameservers; + isc_sockaddr_t *sa, *next; + + result = irs_resconf_load(mctx, "/etc/resolv.conf", &resconf); + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + delve_log(ISC_LOG_ERROR, "irs_resconf_load: %s", + isc_result_totext(result)); + goto cleanup; + } + + /* Get nameservers from resolv.conf */ + nameservers = irs_resconf_getnameservers(resconf); + + /* Remove nameservers using incompatible protocol family */ + for (sa = ISC_LIST_HEAD(*nameservers); sa != NULL; sa = next) { + next = ISC_LIST_NEXT(sa, link); + if (!use_ipv4 && sa->type.sa.sa_family == AF_INET) { + ISC_LIST_UNLINK(*nameservers, sa, link); + isc_mem_put(mctx, sa, sizeof(*sa)); + } + if (!use_ipv6 && sa->type.sa.sa_family == AF_INET6) { + ISC_LIST_UNLINK(*nameservers, sa, link); + isc_mem_put(mctx, sa, sizeof(*sa)); + } + } + + /* None found, use localhost */ + if (ISC_LIST_EMPTY(*nameservers)) { + isc_uint32_t destport; + result = parse_uint(&destport, port, 0xffff, "port"); + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse port number"); + + if (use_ipv4) { + struct in_addr localhost; + localhost.s_addr = htonl(INADDR_LOOPBACK); + sa = isc_mem_get(mctx, sizeof(*sa)); + if (sa == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + isc_sockaddr_fromin(sa, &localhost, destport); + + ISC_LINK_INIT(sa, link); + ISC_LIST_APPEND(*nameservers, sa, link); + } + + if (use_ipv6) { + sa = isc_mem_get(mctx, sizeof(*sa)); + if (sa == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + isc_sockaddr_fromin6(sa, &in6addr_loopback, + destport); + + ISC_LINK_INIT(sa, link); + ISC_LIST_APPEND(*nameservers, sa, link); + } + } + + result = dns_client_setservers(client, dns_rdataclass_in, NULL, + nameservers); + if (result != ISC_R_SUCCESS) + delve_log(ISC_LOG_ERROR, "dns_client_setservers: %s", + isc_result_totext(result)); + +cleanup: + if (resconf != NULL) + irs_resconf_destroy(&resconf); + return (result); +} + +static char * +next_token(char **stringp, const char *delim) { + char *res; + + do { + res = strsep(stringp, delim); + if (res == NULL) + break; + } while (*res == '\0'); + return (res); +} + +static isc_result_t +parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, + const char *desc) { + isc_uint32_t n; + isc_result_t result = isc_parse_uint32(&n, value, 10); + if (result == ISC_R_SUCCESS && n > max) + result = ISC_R_RANGE; + if (result != ISC_R_SUCCESS) { + printf("invalid %s '%s': %s\n", desc, + value, isc_result_totext(result)); + return (result); + } + *uip = n; + return (ISC_R_SUCCESS); +} + +static void +plus_option(char *option) { + isc_result_t result; + char option_store[256]; + char *cmd, *value, *ptr; + isc_boolean_t state = ISC_TRUE; + + strncpy(option_store, option, sizeof(option_store)); + option_store[sizeof(option_store)-1]=0; + ptr = option_store; + cmd = next_token(&ptr,"="); + if (cmd == NULL) { + printf(";; Invalid option %s\n", option_store); + return; + } + value = ptr; + if (strncasecmp(cmd, "no", 2)==0) { + cmd += 2; + state = ISC_FALSE; + } + +#define FULLCHECK(A) \ + do { \ + size_t _l = strlen(cmd); \ + if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \ + goto invalid_option; \ + } while (0) + + switch (cmd[0]) { + case 'a': /* all */ + FULLCHECK("all"); + showcomments = state; + rrcomments = state; + showtrust = state; + break; + case 'c': + switch (cmd[1]) { + case 'd': /* cdflag */ + FULLCHECK("cdflag"); + cdflag = state; + break; + case 'l': /* class */ + FULLCHECK("class"); + noclass = ISC_TF(!state); + break; + case 'o': /* comments */ + FULLCHECK("comments"); + showcomments = state; + break; + case 'r': /* crypto */ + FULLCHECK("crypto"); + nocrypto = ISC_TF(!state); + break; + default: + goto invalid_option; + } + break; + case 'd': + switch (cmd[1]) { + case 'l': /* dlv */ + FULLCHECK("dlv"); + if (state && no_sigs) + break; + dlv_validation = state; + if (value != NULL) + dlv_anchor = isc_mem_strdup(mctx, value); + break; + case 'n': /* dnssec */ + FULLCHECK("dnssec"); + showdnssec = state; + break; + default: + goto invalid_option; + } + break; + case 'm': + switch (cmd[1]) { + case 't': /* mtrace */ + message_trace = state; + if (state) + resolve_trace = state; + break; + case 'u': /* multiline */ + FULLCHECK("multiline"); + multiline = state; + break; + default: + goto invalid_option; + } + break; + case 'r': + switch (cmd[1]) { + case 'o': /* root */ + FULLCHECK("root"); + if (state && no_sigs) + break; + root_validation = state; + if (value != NULL) + trust_anchor = isc_mem_strdup(mctx, value); + break; + case 'r': /* rrcomments */ + FULLCHECK("rrcomments"); + rrcomments = state; + break; + case 't': /* rtrace */ + FULLCHECK("rtrace"); + resolve_trace = state; + break; + default: + goto invalid_option; + } + break; + case 's': + switch (cmd[1]) { + case 'h': /* short */ + FULLCHECK("short"); + short_form = state; + if (short_form) { + multiline = ISC_FALSE; + showcomments = ISC_FALSE; + showtrust = ISC_FALSE; + showdnssec = ISC_FALSE; + } + break; + case 'p': /* split */ + FULLCHECK("split"); + if (value != NULL && !state) + goto invalid_option; + if (!state) { + splitwidth = 0; + break; + } else if (value == NULL) + break; + + result = parse_uint(&splitwidth, value, + 1023, "split"); + if (splitwidth % 4 != 0) { + splitwidth = ((splitwidth + 3) / 4) * 4; + warn("split must be a multiple of 4; " + "adjusting to %d", splitwidth); + } + /* + * There is an adjustment done in the + * totext_() functions which causes + * splitwidth to shrink. This is okay when we're + * using the default width but incorrect in this + * case, so we correct for it + */ + if (splitwidth) + splitwidth += 3; + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse split"); + break; + default: + goto invalid_option; + } + break; + case 't': + switch (cmd[1]) { + case 'r': /* trust */ + FULLCHECK("trust"); + showtrust = state; + break; + case 't': /* ttl */ + FULLCHECK("ttl"); + nottl = ISC_TF(!state); + break; + default: + goto invalid_option; + } + break; + case 'v': /* vtrace */ + FULLCHECK("vtrace"); + validator_trace = state; + if (state) + resolve_trace = state; + break; + default: + invalid_option: + /* + * We can also add a "need_value:" case here if we ever + * add a plus-option that requires a specified value + */ + fprintf(stderr, "Invalid option: +%s\n", option); + usage(); + } + return; +} + +/* + * options: "46a:b:c:d:himp:q:t:vx:"; + */ +static const char *single_dash_opts = "46himv"; +static isc_boolean_t +dash_option(char *option, char *next, isc_boolean_t *open_type_class) { + char opt, *value; + isc_result_t result; + isc_boolean_t value_from_next; + isc_textregion_t tr; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + char textname[MAXNAME]; + struct in_addr in4; + struct in6_addr in6; + isc_sockaddr_t a4, a6; + in_port_t srcport; + isc_uint32_t num; + char *hash; + + while (strpbrk(option, single_dash_opts) == &option[0]) { + /* + * Since the -[46himv] options do not take an argument, + * account for them (in any number and/or combination) + * if they appear as the first character(s) of a q-opt. + */ + opt = option[0]; + switch (opt) { + case '4': + if (isc_net_probeipv4() != ISC_R_SUCCESS) + fatal("IPv4 networking not available"); + if (use_ipv6) { + isc_net_disableipv6(); + use_ipv6 = ISC_FALSE; + } + break; + case '6': + if (isc_net_probeipv6() != ISC_R_SUCCESS) + fatal("IPv6 networking not available"); + if (use_ipv4) { + isc_net_disableipv4(); + use_ipv4 = ISC_FALSE; + } + break; + case 'h': + usage(); + exit(0); + /* NOTREACHED */ + case 'i': + no_sigs = ISC_TRUE; + dlv_validation = ISC_FALSE; + root_validation = ISC_FALSE; + break; + case 'm': + /* handled in preparse_args() */ + break; + case 'v': + fputs("delve " VERSION "\n", stderr); + exit(0); + /* NOTREACHED */ + default: + INSIST(0); + } + if (strlen(option) > 1U) + option = &option[1]; + else + return (ISC_FALSE); + } + opt = option[0]; + if (strlen(option) > 1U) { + value_from_next = ISC_FALSE; + value = &option[1]; + } else { + value_from_next = ISC_TRUE; + value = next; + } + if (value == NULL) + goto invalid_option; + switch (opt) { + case 'a': + anchorfile = isc_mem_strdup(mctx, value); + return (value_from_next); + case 'b': + hash = strchr(value, '#'); + if (hash != NULL) { + result = parse_uint(&num, hash + 1, 0xffff, "port"); + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse port number"); + srcport = num; + *hash = '\0'; + } else + srcport = 0; + + if (inet_pton(AF_INET, value, &in4) == 1) { + if (srcaddr4 != NULL) + fatal("Only one local address per family " + "can be specified\n"); + isc_sockaddr_fromin(&a4, &in4, srcport); + srcaddr4 = &a4; + } else if (inet_pton(AF_INET6, value, &in6) == 1) { + if (srcaddr6 != NULL) + fatal("Only one local address per family " + "can be specified\n"); + isc_sockaddr_fromin6(&a6, &in6, srcport); + srcaddr6 = &a6; + } else { + if (hash != NULL) + *hash = '#'; + fatal("Invalid address %s", value); + } + if (hash != NULL) + *hash = '#'; + return (value_from_next); + case 'c': + if (classset) + warn("extra query class"); + + *open_type_class = ISC_FALSE; + tr.base = value; + tr.length = strlen(value); + result = dns_rdataclass_fromtext(&rdclass, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) + classset = ISC_TRUE; + else if (rdclass != dns_rdataclass_in) + warn("ignoring non-IN query class"); + else + warn("ignoring invalid class"); + return (value_from_next); + case 'd': + result = parse_uint(&num, value, 99, "debug level"); + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse debug level"); + loglevel = num; + return (value_from_next); + case 'p': + port = value; + return (value_from_next); + case 'q': + if (qname != NULL) { + warn("extra query name"); + isc_mem_free(mctx, qname); + } + curqname = value; + return (value_from_next); + case 't': + *open_type_class = ISC_FALSE; + tr.base = value; + tr.length = strlen(value); + result = dns_rdatatype_fromtext(&rdtype, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) { + if (typeset) + warn("extra query type"); + if (rdtype == dns_rdatatype_ixfr || + rdtype == dns_rdatatype_axfr) + fatal("Transfer not supported"); + qtype = rdtype; + typeset = ISC_TRUE; + } else + warn("ignoring invalid type"); + return (value_from_next); + case 'x': + result = get_reverse(textname, sizeof(textname), value, + ISC_FALSE); + if (result == ISC_R_SUCCESS) { + if (curqname != NULL) + warn("extra query name"); + curqname = isc_mem_strdup(mctx, textname); + if (typeset) + warn("extra query type"); + qtype = dns_rdatatype_ptr; + typeset = ISC_TRUE; + } else { + fprintf(stderr, "Invalid IP address %s\n", value); + exit(1); + } + return (value_from_next); + invalid_option: + default: + fprintf(stderr, "Invalid option: -%s\n", option); + usage(); + } + /* NOTREACHED */ + return (ISC_FALSE); +} + +/* + * Check for -m first to determine whether to enable + * memory debugging when setting up the memory context. + */ +static void +preparse_args(int argc, char **argv) { + char *option; + + for (argc--, argv++; argc > 0; argc--, argv++) { + if (argv[0][0] != '-') + continue; + option = &argv[0][1]; + while (strpbrk(option, single_dash_opts) == &option[0]) { + if (option[0] == 'm') { + isc_mem_debugging = ISC_MEM_DEBUGTRACE | + ISC_MEM_DEBUGRECORD; + return; + } + option = &option[1]; + } + } +} + +/* + * Argument parsing is based on dig, but simplified: only one + * QNAME/QCLASS/QTYPE tuple can be specified, and options have + * been removed that aren't applicable to delve. The interface + * should be familiar to dig users, however. + */ +static void +parse_args(int argc, char **argv) { + isc_result_t result; + isc_textregion_t tr; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + isc_boolean_t open_type_class = ISC_TRUE; + + for (; argc > 0; argc--, argv++) { + if (argv[0][0] == '@') { + server = &argv[0][1]; + } else if (argv[0][0] == '+') { + plus_option(&argv[0][1]); + } else if (argv[0][0] == '-') { + if (argc <= 1) { + if (dash_option(&argv[0][1], NULL, + &open_type_class)) + { + argc--; + argv++; + } + } else { + if (dash_option(&argv[0][1], argv[1], + &open_type_class)) + { + argc--; + argv++; + } + } + } else { + /* + * Anything which isn't an option + */ + if (open_type_class) { + tr.base = argv[0]; + tr.length = strlen(argv[0]); + result = dns_rdatatype_fromtext(&rdtype, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) { + if (typeset) + warn("extra query type"); + if (rdtype == dns_rdatatype_ixfr || + rdtype == dns_rdatatype_axfr) + fatal("Transfer not supported"); + qtype = rdtype; + typeset = ISC_TRUE; + continue; + } + result = dns_rdataclass_fromtext(&rdclass, + (isc_textregion_t *)&tr); + if (result == ISC_R_SUCCESS) { + if (classset) + warn("extra query class"); + else if (rdclass != dns_rdataclass_in) + warn("ignoring non-IN " + "query class"); + continue; + } + } + + if (curqname == NULL) + curqname = argv[0]; + } + } + + /* + * If no qname or qtype specified, search for root/NS + * If no qtype specified, use A + */ + if (!typeset) + qtype = dns_rdatatype_a; + + if (curqname == NULL) { + qname = isc_mem_strdup(mctx, "."); + if (!typeset) + qtype = dns_rdatatype_ns; + } else + qname = isc_mem_strdup(mctx, curqname); +} + +static isc_result_t +append_str(const char *text, int len, char **p, char *end) { + if (len > end - *p) + return (ISC_R_NOSPACE); + memmove(*p, text, len); + *p += len; + return (ISC_R_SUCCESS); +} + +static isc_result_t +reverse_octets(const char *in, char **p, char *end) { + char *dot = strchr(in, '.'); + int len; + if (dot != NULL) { + isc_result_t result; + result = reverse_octets(dot + 1, p, end); + if (result != ISC_R_SUCCESS) + return (result); + result = append_str(".", 1, p, end); + if (result != ISC_R_SUCCESS) + return (result); + len = (int)(dot - in); + } else + len = strlen(in); + return (append_str(in, len, p, end)); +} + +static isc_result_t +get_reverse(char *reverse, size_t len, char *value, isc_boolean_t strict) { + int r; + isc_result_t result; + isc_netaddr_t addr; + + addr.family = AF_INET6; + r = inet_pton(AF_INET6, value, &addr.type.in6); + if (r > 0) { + /* This is a valid IPv6 address. */ + dns_fixedname_t fname; + dns_name_t *name; + unsigned int options = 0; + + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + result = dns_byaddr_createptrname2(&addr, options, name); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_format(name, reverse, (unsigned int)len); + return (ISC_R_SUCCESS); + } else { + /* + * Not a valid IPv6 address. Assume IPv4. + * If 'strict' is not set, construct the + * in-addr.arpa name by blindly reversing + * octets whether or not they look like integers, + * so that this can be used for RFC2317 names + * and such. + */ + char *p = reverse; + char *end = reverse + len; + if (strict && inet_pton(AF_INET, value, &addr.type.in) != 1) + return (DNS_R_BADDOTTEDQUAD); + result = reverse_octets(value, &p, end); + if (result != ISC_R_SUCCESS) + return (result); + result = append_str(".in-addr.arpa.", 15, &p, end); + if (result != ISC_R_SUCCESS) + return (result); + return (ISC_R_SUCCESS); + } +} + +int +main(int argc, char *argv[]) { + dns_client_t *client = NULL; + isc_result_t result; + dns_fixedname_t qfn; + dns_name_t *query_name, *response_name; + dns_rdataset_t *rdataset; + dns_namelist_t namelist; + unsigned int resopt; + isc_appctx_t *actx = NULL; + isc_taskmgr_t *taskmgr = NULL; + isc_socketmgr_t *socketmgr = NULL; + isc_timermgr_t *timermgr = NULL; + dns_master_style_t *style = NULL; + struct sigaction sa; + + preparse_args(argc, argv); + progname = argv[0]; + + argc -= optind; + argv += optind; + + isc_lib_register(); + result = dns_lib_init(); + if (result != ISC_R_SUCCESS) + fatal("dns_lib_init failed: %d", result); + + result = isc_mem_create(0, 0, &mctx); + if (result != ISC_R_SUCCESS) + fatal("failed to create mctx"); + + CHECK(isc_appctx_create(mctx, &actx)); + CHECK(isc_taskmgr_createinctx(mctx, actx, 1, 0, &taskmgr)); + CHECK(isc_socketmgr_createinctx(mctx, actx, &socketmgr)); + CHECK(isc_timermgr_createinctx(mctx, actx, &timermgr)); + + parse_args(argc, argv); + + CHECK(setup_style(&style)); + + setup_logging(stderr); + + CHECK(isc_app_ctxstart(actx)); + + /* Unblock SIGINT if it's been blocked by isc_app_ctxstart() */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + if (sigfillset(&sa.sa_mask) != 0 || sigaction(SIGINT, &sa, NULL) < 0) + fatal("Couldn't set up signal handler"); + + /* Create client */ + result = dns_client_createx(mctx, actx, taskmgr, socketmgr, timermgr, + 0, &client); + if (result != ISC_R_SUCCESS) { + delve_log(ISC_LOG_ERROR, "dns_client_create: %s", + isc_result_totext(result)); + goto cleanup; + } + + /* Set the nameserver */ + if (server != NULL) + addserver(client); + else + findserver(client); + + CHECK(setup_dnsseckeys(client)); + + /* Construct QNAME */ + convert_name(&qfn, &query_name, qname); + + /* Set up resolution options */ + resopt = DNS_CLIENTRESOPT_ALLOWRUN | DNS_CLIENTRESOPT_NOCDFLAG; + if (no_sigs) + resopt |= DNS_CLIENTRESOPT_NODNSSEC; + if (!root_validation && !dlv_validation) + resopt |= DNS_CLIENTRESOPT_NOVALIDATE; + if (cdflag) + resopt &= ~DNS_CLIENTRESOPT_NOCDFLAG; + + /* Perform resolution */ + ISC_LIST_INIT(namelist); + result = dns_client_resolve(client, query_name, dns_rdataclass_in, + qtype, resopt, &namelist); + if (result != ISC_R_SUCCESS) + delve_log(ISC_LOG_ERROR, "resolution failed: %s", + isc_result_totext(result)); + + for (response_name = ISC_LIST_HEAD(namelist); + response_name != NULL; + response_name = ISC_LIST_NEXT(response_name, link)) { + for (rdataset = ISC_LIST_HEAD(response_name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + result = printdata(rdataset, response_name, style); + if (result != ISC_R_SUCCESS) + delve_log(ISC_LOG_ERROR, "print data failed"); + } + } + + dns_client_freeresanswer(client, &namelist); + +cleanup: + if (dlv_anchor != NULL) + isc_mem_free(mctx, dlv_anchor); + if (trust_anchor != NULL) + isc_mem_free(mctx, trust_anchor); + if (anchorfile != NULL) + isc_mem_free(mctx, anchorfile); + if (qname != NULL) + isc_mem_free(mctx, qname); + if (style != NULL) + dns_master_styledestroy(&style, mctx); + if (client != NULL) + dns_client_destroy(&client); + if (taskmgr != NULL) + isc_taskmgr_destroy(&taskmgr); + if (timermgr != NULL) + isc_timermgr_destroy(&timermgr); + if (socketmgr != NULL) + isc_socketmgr_destroy(&socketmgr); + if (actx != NULL) + isc_appctx_destroy(&actx); + if (lctx != NULL) + isc_log_destroy(&lctx); + isc_mem_detach(&mctx); + + dns_lib_shutdown(); + + exit(0); +} diff --git a/bin/delve/delve.docbook b/bin/delve/delve.docbook new file mode 100644 index 0000000000..12ff996ba4 --- /dev/null +++ b/bin/delve/delve.docbook @@ -0,0 +1,639 @@ +]> + + + + + + February 12, 2014 + + + + delve + 1 + BIND9 + + + + delve + DNS lookup and validation utility + + + + + 2014 + Internet Systems Consortium, Inc. ("ISC") + + + + + + delve + @server + + + + + + + + + + + + + name + type + class + queryopt + + + + delve + + + + + delve + queryopt + query + + + + + DESCRIPTION + delve + (Domain Entity Lookup & Validation Engine) is a tool for sending + DNS queries and validating the results, using the the same internal + resolver and validator logic as named. + + + delve will send to a specified name server all + queries needed to fetch and validate the requested data; this + includes the original requested query, subsequent queries to follow + CNAME or DNAME chains, and queries for DNSKEY, DS and DLV records + to establish a chain of trust for DNSSEC validation. + It does not perform iterative resolution, but simulates the + behavior of a name server configured for DNSSEC validating and + forwarding. + + + By default, responses are validated using built-in DNSSEC trust + anchors for the root zone (".") and for the ISC DNSSEC lookaside + validation zone ("dlv.isc.org"). Records returned by + delve are either fully validated or + were not signed. If validation fails, an explanation of + the failure is included in the output; the validation process + can be traced in detail. Because delve does + not rely on an external server to carry out validation, it can + be used to check the validity of DNS responses in environments + where local name servers may not be trustworthy. + + + Unless it is told to query a specific name server, + delve will try each of the servers listed in + /etc/resolv.conf. If no usable server + addresses are found, delve will send + queries to the localhost addresses (127.0.0.1 for IPv4, ::1 + for IPv6). + + + When no command line arguments or options are given, + delve will perform an NS query for "." + (the root zone). + + + + + SIMPLE USAGE + + + A typical invocation of delve looks like: + delve @server name type + where: + + + + server + + + is the name or IP address of the name server to query. This + can be an IPv4 address in dotted-decimal notation or an IPv6 + address in colon-delimited notation. When the supplied + server argument is a hostname, + delve resolves that name before + querying that name server (note, however, that this + initial lookup is not validated + by DNSSEC). + + + If no server argument is + provided, delve consults + /etc/resolv.conf; if an + address is found there, it queries the name server at + that address. If either of the or + options are in use, then + only addresses for the corresponding transport + will be tried. If no usable addresses are found, + delve will send queries to + the localhost addresses (127.0.0.1 for IPv4, + ::1 for IPv6). + + + + + + name + + + is the domain name to be looked up. + + + + + + type + + + indicates what type of query is required — + ANY, A, MX, etc. + type can be any valid query + type. If no + type argument is supplied, + delve will perform a lookup for an + A record. + + + + + + + + + + + OPTIONS + + + -a + + + Specifies a file from which to read DNSSEC trust anchors. + The default is /etc/bind.keys, which + is included with BIND 9 and contains + trust anchors for the root zone (".") and for the ISC + DNSSEC lookaside validation zone ("dlv.isc.org"). + + + Keys that do not match the root or DLV trust-anchor + names are ignored; these key names can be overridden + using the or + options. + + + Note: When reading the trust anchor file, + delve treats + statements and statements + identically. That is, for a managed key, it is the + initial key that is trusted; RFC 5011 + key management is not supported. delve + will not consult the managed-keys database maintained by + named. This means that if either of the + keys in /etc/bind.keys is revoked + and rolled over, it will be necessary to update + /etc/bind.keys to use DNSSEC + validation in delve. + + + + + + -b + + + Sets the source IP address of the query to + address. This must be a valid address + on one of the host's network interfaces or "0.0.0.0" or "::". + An optional source port may be specified by appending + "#<port>" + + + + + + -c + + + Sets the query class for the requested data. Currently, + only class "IN" is supported in delve + and any other value is ignored. + + + + + + -i + + + Insecure mode. This disables internal DNSSEC validation. + (Note, however, this does not set the CD bit on upstream + queries. If the server being queried is performing DNSSEC + validation, then it will not return invalid data; this + can cause delve to time out. When it + is necessary to examine invalid data to debug a DNSSEC + problem, use dig +cd.) + + + + + + -m + + + Enables memory usage debugging. + + + + + + -p + + + Specifies a destination port to use for queries instead of + the standard DNS port number 53. This option would be used + with a name server that has been configured to listen + for queries on a non-standard port number. + + + + + + -4 + + + Forces delve to only use IPv4. + + + + + + -6 + + + Forces delve to only use IPv6. + + + + + + -q + + + Sets the query name to name. + While the query name can be specified without using the + , it is sometimes necessary to disambiguate + names from types or classes (for example, when looking up the + name "ns", which could be misinterpreted as the type NS, + or "ch", which could be misinterpreted as class CH). + + + + + + -t + + + Sets the query type to type, which + can be any valid query type supported in BIND 9 except + for zone transfer types AXFR and IXFR. As with + , this is useful to distinguish + query name type or class when they are ambiguous. + it is sometimes necessary to disambiguate names from types. + + + The default query type is "A", unless the + option is supplied to indicate a reverse lookup, in which case + it is "PTR". + + + + + + -x + + + Performs a reverse lookup, mapping an addresses to + a name. addr is an IPv4 address in + dotted-decimal notation, or a colon-delimited IPv6 address. + When is used, there is no need to provide + the name or type + arguments. delve automatically performs a + lookup for a name like 11.12.13.10.in-addr.arpa + and sets the query type to PTR. IPv6 addresses are looked up + using nibble format under the IP6.ARPA domain. + + + + + + + + QUERY OPTIONS + + delve + provides a number of query options which affect the way results are + displayed, and in some cases the way lookups are performed. + + + + Each query option is identified by a keyword preceded by a plus sign + (+). Some keywords set or reset an + option. These may be preceded by the string + no to negate the meaning of that keyword. + Other keywords assign values to options like the timeout interval. + They have the form . + The query options are: + + + + + + + Controls whether to set the CD (checking disabled) bit in + queries sent by delve. This may be useful + when troubleshooting DNSSEC problems from behind a validating + resolver. A validating resolver will block invalid responses, + making it difficult to retrieve them for analysis. Setting + the CD flag on queries will cause the resolver to return + invalid responses, which delve can then + validate internally and report the errors in detail. + + + + + + + + + Controls whether to display the CLASS when printing + a record. The default is to display the CLASS. + + + + + + + + + Controls whether to display the TTL when printing + a record. The default is to display the TTL. + + + + + + + + + Toggle resolver fetch logging. This reports the + name and type of each query sent by delve + in the process of carrying out the resolution and validation + process: this includes including the original query and + all subsequent queries to follow CNAMEs and to establish a + chain of trust for DNSSEC validation. + + + This is equivalent to setting the debug level to 1 in + the "resolver" logging category. Setting the systemwide + debug level to 1 using the option will + product the same output (but will affect other logging + categories as well). + + + + + + + + + Toggle message logging. This produces a detailed dump of + the responses received by delve in the + process of carrying out the resolution and validation process. + + + This is equivalent to setting the debug level to 10 + for the the "packets" module of the "resolver" logging + category. Setting the systemwide debug level to 10 using + the option will produce the same output + (but will affect other logging categories as well). + + + + + + + + + Toggle validation logging. This shows the internal + process of the validator as it determines whether an + answer is validly signed, unsigned, or invalid. + + + This is equivalent to setting the debug level to 3 + for the the "validator" module of the "dnssec" logging + category. Setting the systemwide debug level to 3 using + the option will produce the same output + (but will affect other logging categories as well). + + + + + + + + + Provide a terse answer. The default is to print the answer in a + verbose form. + + + + + + + + + Toggle the display of comment lines in the output. The default + is to print comments. + + + + + + + + + Toggle the display of per-record comments in the output (for + example, human-readable key information about DNSKEY records). + The default is to print per-record comments. + + + + + + + + + Toggle the display of cryptographic fields in DNSSEC records. + The contents of these field are unnecessary to debug most DNSSEC + validation failures and removing them makes it easier to see + the common failures. The default is to display the fields. + When omitted they are replaced by the string "[omitted]" or + in the DNSKEY case the key id is displayed as the replacement, + e.g. "[ key id = value ]". + + + + + + + + + Controls whether to display the trust level when printing + a record. The default is to display the trust level. + + + + + + + + + Split long hex- or base64-formatted fields in resource + records into chunks of W characters + (where W is rounded up to the nearest + multiple of 4). + +nosplit or + +split=0 causes fields not to be + split at all. The default is 56 characters, or 44 characters + when multiline mode is active. + + + + + + + + + Set or clear the display options + , + , and + as a group. + + + + + + + + + Print long records (such as RRSIG, DNSKEY, and SOA records) + in a verbose multi-line format with human-readable comments. + The default is to print each record on a single line, to + facilitate machine parsing of the delve + output. + + + + + + + + + Indicates whether to display RRSIG records in the + delve output. The default is to + do so. Note that (unlike in dig) + this does not control whether to + request DNSSEC records or whether to validate them. + DNSSEC records are always requested, and validation + will always occur unless suppressed by the use of + or and + . + + + + + + + + + Indicates whether to perform conventional (non- + lookaside) DNSSEC validation, and if so, specifies the + name of a trust anchor. The default is to validate using + a trust anchor of "." (the root zone), for which there is + a built-in key. If specifying a different trust anchor, + then must be used to specify a file + containing the key. + + + + + + + + + Indicates whether to perform DNSSEC lookaside validation, + and if so, specifies the name of the DLV trust anchor. + The default is to perform lookaside validation using + a trust anchor of "dlv.isc.org", for which there is a + built-in key. If specifying a different name, then + must be used to specify a file + containing the DLV key. + + + + + + + + + + FILES + /etc/bind.keys + /etc/resolv.conf + + + + SEE ALSO + + dig1 + , + + named8 + , + RFC4034, + RFC4035, + RFC4431, + RFC5074, + RFC5155. + + + diff --git a/bin/delve/delve.html b/bin/delve/delve.html new file mode 100644 index 0000000000..34c30d2cdd --- /dev/null +++ b/bin/delve/delve.html @@ -0,0 +1,445 @@ + + + + + +delve + + +
+
+
+

Name

+

delve — DNS lookup and validation utility

+
+
+

Synopsis

+

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

+

delve [-h]

+

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

+
+
+

DESCRIPTION

+

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

+

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

+

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

+

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

+

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

+
+
+

SIMPLE USAGE

+

+ A typical invocation of delve looks like: +

+
 delve @server name type 
+

+ where: + +

+
+
server
+
+

+ is the name or IP address of the name server to query. This + can be an IPv4 address in dotted-decimal notation or an IPv6 + address in colon-delimited notation. When the supplied + server argument is a hostname, + delve resolves that name before + querying that name server (note, however, that this + initial lookup is not validated + by DNSSEC). +

+

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

+
+
name
+

+ is the domain name to be looked up. +

+
type
+

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

+
+

+

+
+
+

OPTIONS

+
+
-a
+
+

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

+

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

+

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

+
+
-b
+

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

+
-c
+

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

+
-i
+

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

+
-m
+

+ Enables memory usage debugging. +

+
-p
+

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

+
-4
+

+ Forces delve to only use IPv4. +

+
-6
+

+ Forces delve to only use IPv6. +

+
-q
+

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

+
-t
+
+

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

+

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

+
+
-x
+

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

+
+
+
+

QUERY OPTIONS

+

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

+

+ Each query option is identified by a keyword preceded by a plus sign + (+). Some keywords set or reset an + option. These may be preceded by the string + no to negate the meaning of that keyword. + Other keywords assign values to options like the timeout interval. + They have the form +keyword=value. + The query options are: + +

+
+
+[no]cdflag
+

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

+
+[no]class
+

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

+
+[no]ttl
+

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

+
+[no]rtrace
+
+

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

+

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

+
+
+[no]mtrace
+
+

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

+

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

+
+
+[no]vtrace
+
+

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

+

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

+
+
+[no]short
+

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

+
+[no]comments
+

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

+
+[no]rrcomments
+

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

+
+[no]crypto
+

+ Toggle the display of cryptographic fields in DNSSEC records. + The contents of these field are unnecessary to debug most DNSSEC + validation failures and removing them makes it easier to see + the common failures. The default is to display the fields. + When omitted they are replaced by the string "[omitted]" or + in the DNSKEY case the key id is displayed as the replacement, + e.g. "[ key id = value ]". +

+
+[no]trust
+

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

+
+[no]split[=W]
+

+ Split long hex- or base64-formatted fields in resource + records into chunks of W characters + (where W is rounded up to the nearest + multiple of 4). + +nosplit or + +split=0 causes fields not to be + split at all. The default is 56 characters, or 44 characters + when multiline mode is active. +

+
+[no]all
+

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

+
+[no]multiline
+

+ Print long records (such as RRSIG, DNSKEY, and SOA records) + in a verbose multi-line format with human-readable comments. + The default is to print each record on a single line, to + facilitate machine parsing of the delve + output. +

+
+[no]dnssec
+

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

+
+[no]root[=ROOT]
+

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

+
+[no]dlv[=DLV]
+

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

+
+

+ +

+
+
+

FILES

+

/etc/bind.keys

+

/etc/resolv.conf

+
+
+

SEE ALSO

+

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

+
+
+ diff --git a/bin/named/config.c b/bin/named/config.c index b43c0fcb0e..818ed3797a 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -78,6 +78,8 @@ options {\n\ memstatistics-file \"named.memstats\";\n\ multiple-cnames no;\n\ # named-xfer ;\n\ + nta-lifetime 3600;\n\ + nta-recheck 300;\n\ # pid-file \"" NS_LOCALSTATEDIR "/run/named/named.pid\"; /* or /lwresd.pid */\n\ bindkeys-file \"" NS_SYSCONFDIR "/bind.keys\";\n\ port 53;\n\ diff --git a/bin/named/control.c b/bin/named/control.c index 86fa691bd2..7a8e71f19b 100644 --- a/bin/named/control.c +++ b/bin/named/control.c @@ -210,6 +210,8 @@ ns_control_docommand(isccc_sexpr_t *message, isc_buffer_t *text) { result = ns_server_del_zone(ns_g_server, command); } else if (command_compare(command, NS_COMMAND_SIGNING)) { result = ns_server_signing(ns_g_server, command, text); + } else if (command_compare(command, NS_COMMAND_NTA)) { + result = ns_server_nta(ns_g_server, command, text); } else { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h index 52ed583a5a..1c8275477f 100644 --- a/bin/named/include/named/control.h +++ b/bin/named/include/named/control.h @@ -66,6 +66,7 @@ #define NS_COMMAND_DELZONE "delzone" #define NS_COMMAND_SYNC "sync" #define NS_COMMAND_SIGNING "signing" +#define NS_COMMAND_NTA "nta" isc_result_t ns_controls_create(ns_server_t *server, ns_controls_t **ctrlsp); diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 83622f406a..0107b51e08 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -251,6 +251,18 @@ ns_server_togglequerylog(ns_server_t *server, char *args); * but can also be used as a toggle for backward comptibility.) */ +/*% + * Save the current NTAs for all views to files. + */ +isc_result_t +ns_server_saventa(ns_server_t *server); + +/*% + * Load NTAs for all views from files. + */ +isc_result_t +ns_server_loadnta(ns_server_t *server); + /*% * Dump the current statistics to the statistics file. */ @@ -365,4 +377,11 @@ ns_server_del_zone(ns_server_t *server, char *args); */ isc_result_t ns_server_signing(ns_server_t *server, char *args, isc_buffer_t *text); + +/*% + * Adds a Negative Trust Anchor (NTA) for a specified name and + * duration, in a particular view if specified, or in all views. + */ +isc_result_t +ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text); #endif /* NAMED_SERVER_H */ diff --git a/bin/named/server.c b/bin/named/server.c index a00cf1596a..0c297049ed 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -73,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ #include #include #include +#include #include #include #include @@ -812,6 +814,14 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, return (ISC_R_UNEXPECTED); } + result = dns_view_initntatable(view, ns_g_taskmgr, ns_g_timermgr); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "couldn't create NTA table"); + return (ISC_R_UNEXPECTED); + } + if (auto_dlv && view->rdclass == dns_rdataclass_in) { const cfg_obj_t *builtin_keys = NULL; const cfg_obj_t *builtin_managed_keys = NULL; @@ -3263,6 +3273,16 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, if (result == ISC_R_SUCCESS) CHECK(mustbesecure(obj, view->resolver)); + obj = NULL; + result = ns_config_get(maps, "nta-recheck", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_recheck = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "nta-lifetime", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_lifetime = cfg_obj_asuint32(obj); + obj = NULL; result = ns_config_get(maps, "preferred-glue", &obj); if (result == ISC_R_SUCCESS) { @@ -3348,6 +3368,7 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, } else { empty_zones_enable = ISC_FALSE; } + if (empty_zones_enable && !lwresd_g_useresolvconf) { const char *empty; int empty_zone = 0; @@ -5069,9 +5090,10 @@ load_configuration(const char *filename, ns_server_t *server, maps[i] = NULL; /* - * If bind.keys exists, load it. If "dnssec-lookaside auto" - * is turned on, the keys found there will be used as default - * trust anchors. + * If bind.keys exists, load it. If "dnssec-validation auto" + * is turned on, the root key found there will be used as a + * default trust anchor, and if "dnssec-lookaside auto" is + * turned on, then the DLV key found there will too. */ obj = NULL; result = ns_config_get(maps, "bindkeys-file", &obj); @@ -5852,6 +5874,8 @@ load_configuration(const char *filename, ns_server_t *server, server->flushonshutdown = ISC_FALSE; } + (void) ns_server_loadnta(server); + result = ISC_R_SUCCESS; cleanup: @@ -6147,6 +6171,8 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { cfg_obj_destroy(ns_g_parser, &ns_g_config); cfg_parser_destroy(&ns_g_parser); + (void) ns_server_saventa(server); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; view = view_next) { @@ -7297,6 +7323,7 @@ isc_result_t ns_server_dumpsecroots(ns_server_t *server, char *args) { dns_view_t *view; dns_keytable_t *secroots = NULL; + dns_ntatable_t *ntatable = NULL; isc_result_t result; char *ptr; FILE *fp = NULL; @@ -7330,11 +7357,25 @@ ns_server_dumpsecroots(ns_server_t *server, char *args) { result = ISC_R_SUCCESS; continue; } - fprintf(fp, "\n Start view %s\n\n", view->name); + fprintf(fp, "\n Start view %s\n", view->name); + fprintf(fp, " Secure roots:\n\n"); result = dns_keytable_dump(secroots, fp); if (result != ISC_R_SUCCESS) fprintf(fp, " dumpsecroots failed: %s\n", isc_result_totext(result)); + + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + fprintf(fp, "\n Negative trust anchors:\n\n"); + result = dns_ntatable_dump(ntatable, fp); + if (result != ISC_R_SUCCESS) + fprintf(fp, " dumpntatable failed: %s\n", + isc_result_totext(result)); } if (ptr != NULL) ptr = next_token(&args, " \t"); @@ -7343,6 +7384,8 @@ ns_server_dumpsecroots(ns_server_t *server, char *args) { cleanup: if (secroots != NULL) dns_keytable_detach(&secroots); + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); if (fp != NULL) (void)isc_stdio_close(fp); if (result == ISC_R_SUCCESS) @@ -8800,3 +8843,295 @@ ns_server_signing(ns_server_t *server, char *args, isc_buffer_t *text) { return (result); } + +static isc_result_t +putstr(isc_buffer_t *b, const char *str) { + unsigned int l = strlen(str); + + /* + * Use >= to leave space for NUL termination. + */ + if (l >= isc_buffer_availablelength(b)) + return (ISC_R_NOSPACE); + + isc_buffer_putmem(b, (const unsigned char *)str, l); + return (ISC_R_SUCCESS); +} + +static inline isc_boolean_t +argcheck(char *cmd, const char *full) { + size_t l; + + if (cmd == NULL || cmd[0] != '-') + return (ISC_FALSE); + + cmd++; + l = strlen(cmd); + if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) + return (ISC_FALSE); + + return (ISC_TRUE); +} + +isc_result_t +ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { + dns_view_t *view; + dns_ntatable_t *ntatable = NULL; + isc_result_t result; + char *ptr, *nametext = NULL, *viewname; + isc_stdtime_t now, when; + isc_time_t t; + char tbuf[64]; + const char *msg = NULL; + isc_boolean_t dump = ISC_FALSE, force = ISC_FALSE; + dns_fixedname_t fn; + dns_name_t *ntaname; + dns_ttl_t ntattl; + isc_boolean_t ttlset = ISC_FALSE, excl = ISC_FALSE; + + UNUSED(force); + + dns_fixedname_init(&fn); + ntaname = dns_fixedname_name(&fn); + + /* Skip the command name. */ + ptr = next_token(&args, " \t"); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + for (;;) { + /* Check for options */ + ptr = next_token(&args, " \t"); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (argcheck(ptr, "dump")) + dump = ISC_TRUE; + else if (argcheck(ptr, "remove")) { + ntattl = 0; + ttlset = ISC_TRUE; + } else if (argcheck(ptr, "force")) { + force = ISC_TRUE; + continue; + } else if (argcheck(ptr, "lifetime")) { + isc_textregion_t tr; + + ptr = next_token(&args, " \t"); + if (ptr == NULL) { + msg = "No lifetime specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + tr.base = ptr; + tr.length = strlen(ptr); + result = dns_ttl_fromtext(&tr, &ntattl); + if (result != ISC_R_SUCCESS) { + msg = "could not parse NTA lifetime"; + CHECK(result); + } + + if (ntattl > 604800) { + msg = "NTA lifetime cannot exceed one week"; + CHECK(ISC_R_RANGE); + } + + ttlset = ISC_TRUE; + continue; + } else + nametext = ptr; + + break; + } + + /* + * If -dump was specified, list NTA's and return + */ + if (dump) { + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + CHECK(dns_ntatable_totext(ntatable, text)); + } + + goto cleanup; + } + + /* Get the NTA name. */ + if (nametext == NULL) + nametext = next_token(&args, " \t"); + if (nametext == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcmp(nametext, ".") == 0) + ntaname = dns_rootname; + else { + isc_buffer_t b; + isc_buffer_init(&b, nametext, strlen(nametext)); + isc_buffer_add(&b, strlen(nametext)); + CHECK(dns_name_fromtext(ntaname, &b, dns_rootname, 0, NULL)); + } + + /* Look for the view name. */ + viewname = next_token(&args, " \t"); + + isc_stdtime_get(&now); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + excl = ISC_TRUE; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewname != NULL && + strcmp(view->name, viewname) != 0) + continue; + + if (view->nta_lifetime == 0) + continue; + + if (!ttlset) + ntattl = view->nta_lifetime; + + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + + result = dns_view_flushnode(view, ntaname, ISC_TRUE); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flush tree '%s' in cache view '%s': %s", + nametext, view->name, + isc_result_totext(result)); + + if (ntattl != 0) { + CHECK(dns_ntatable_add(ntatable, ntaname, + force, now, ntattl)); + + when = now + ntattl; + isc_time_set(&t, when, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + CHECK(putstr(text, "Negative trust anchor added: ")); + CHECK(putstr(text, nametext)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ", expires ")); + CHECK(putstr(text, tbuf)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "added NTA '%s' (%d sec) in view '%s'", + nametext, ntattl, view->name); + } else { + CHECK(dns_ntatable_delete(ntatable, ntaname)); + + CHECK(putstr(text, "Negative trust anchor removed: ")); + CHECK(putstr(text, nametext)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "removed NTA '%s' in view %s", + nametext, view->name); + } + + if (isc_buffer_availablelength(text) == 0) + return (ISC_R_NOSPACE); + result = dns_view_saventa(view); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + + + + isc_buffer_putuint8(text, 0); + } + cleanup: + if (msg != NULL && strlen(msg) < isc_buffer_availablelength(text)) + isc_buffer_putstr(text, msg); + if (excl) + isc_task_endexclusive(server->task); + + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + return (result); +} + +isc_result_t +ns_server_saventa(ns_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_saventa(view); + + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file '%s' " + "for view '%s': %s", + view->nta_file, view->name, + isc_result_totext(result)); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "written NTA file '%s' for view '%s'", + view->nta_file, view->name); + } + + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_loadnta(ns_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_loadnta(view); + + if ((result != ISC_R_SUCCESS) && + (result != ISC_R_FILENOTFOUND) && + (result != ISC_R_NOTFOUND)) + { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error loading NTA file '%s' " + "for view '%s': %s", + view->nta_file, view->name, + isc_result_totext(result)); + } else if (result == ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "loaded NTA file '%s' for view '%s'", + view->nta_file, view->name); + } + } + + return (ISC_R_SUCCESS); +} diff --git a/bin/rndc/rndc.c b/bin/rndc/rndc.c index c67223bbbb..81e629f15c 100644 --- a/bin/rndc/rndc.c +++ b/bin/rndc/rndc.c @@ -157,6 +157,18 @@ command is one of the following:\n\ Delete a TKEY-negotiated TSIG key.\n\ validation newstate [view]\n\ Enable / disable DNSSEC validation.\n\ + nta [-lifetime duration] [-force] domain [view]\n\ + Set a negative trust anchor, disabling DNSSEC validation\n\ + for the given domain.\n\ + Using -lifetime specifies the duration of the NTA, up\n\ + to one day.\n\ + Using -force prevents the NTA from expiring before its\n\ + full lifetime, even if the domain can validate sooner.\n\ + nta -remove domain [view]\n\ + Remove a negative trust anchor, re-enabling validation\n\ + for the given domain.\n\ + nta -dump\n\ + List all negative trust anchors.\n\ addzone [\"file\"] zone [class [view]] { zone-options }\n\ Add zone to given view. Requires new-zone-file option.\n\ delzone [\"file\"] zone [class [view]]\n\ diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index ac2e54d98d..3c2061e26f 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -427,10 +427,9 @@ secroots view ... - Dump the server's security roots to the secroots - file for the specified views. If no view is - specified, security roots for all - views are dumped. + Dump the server's security roots and negative trust anchors + to the secroots file for the specified views. If no view is + specified, all views are dumped. @@ -561,6 +560,81 @@ + + nta + ( -d | -f | -r | -l duration) + domain + view + + + + Sets a DNSSEC negative trust anchor (NTA) + for , with a lifetime of + . The default lifetime is + configured in named.conf via the + , and defaults to + one hour. The lifetime cannot exceed one week. + + + A negative trust anchor selectively disables + DNSSEC validation for zones that known to be + failing because of misconfiguration rather than + an attack. When data to be validated is + at or below an active NTA (and above any other + configured trust anchors), named will + abort the DNSSEC validation process and treat the data as + insecure rather than bogus. This continues until the + NTA's lifetime is elapsed. + + + NTAs persist across restarts of the named server. + The NTAs for a view are saved in a file called + name.nta, + where name is the + name of the view, or if it contains characters + that are incompatible with use as a file name, a + cryptographic hash generated from the name + of the view. + + + An existing NTA can be removed by using the + option. + + + An NTA's lifetime can be specified with the + option. TTL-style + suffixes can be used to specify the lifetime in + seconds, minutes, or hours. If the specified NTA + already exists, its lifetime will be updated to the + new value. Setting to zero + is equivalent to . + + + If is used, any other arguments + are ignored, and a list of existing NTAs is printed + (note that this may include NTAs that are expired but + have not yet been cleaned up). + + + Normally, named will periodically + test to see whether data below an NTA can now be + validated (see the option + in the Administrator Reference Manual for details). + If data can be validated, then the NTA is regarded as + no longer necessary, and will be allowed to expire + early. The overrides this + behavior and forces an NTA to persist for its entire + lifetime, regardless of whether data could be + validated if the NTA were not present. + + + All of these options can be shortened, i.e., to + , , , + and . + + + + tsig-list diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index a6310cd1e2..cf7c745722 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -67,6 +67,8 @@ options { serial-query-rate 100; server-id none; max-cache-size 20000000000000; + nta-recheck 604800; + nta-lifetime 604800; zone-statistics none; }; view "first" { diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 64563154df..7ddd9c789f 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -12,8 +12,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id$ - SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh diff --git a/bin/tests/system/dlvauto/tests.sh b/bin/tests/system/dlvauto/tests.sh index 25bb8e30d8..138a72b850 100644 --- a/bin/tests/system/dlvauto/tests.sh +++ b/bin/tests/system/dlvauto/tests.sh @@ -51,7 +51,7 @@ linecount=`grep "\./RSAMD5/.* ; managed" ns2/named.secroots | wc -l` linecount=`grep "dlv.isc.org/RSAMD5/.* ; managed" ns2/named.secroots | wc -l` [ "$linecount" -eq 2 ] || ret=1 linecount=`cat ns2/named.secroots | wc -l` -[ "$linecount" -eq 13 ] || ret=1 +[ "$linecount" -eq 28 ] || ret=1 n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` diff --git a/bin/tests/system/dnssec/clean.sh b/bin/tests/system/dnssec/clean.sh index 0f333409b3..3b91a96818 100644 --- a/bin/tests/system/dnssec/clean.sh +++ b/bin/tests/system/dnssec/clean.sh @@ -69,3 +69,4 @@ rm -f ns6/optout-tld.db rm -f nosign.before rm -f signing.out* rm -f canonical?.* +rm -f ns*/*.nta diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index 7be1044204..e22999f445 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -65,6 +65,10 @@ ns.insecure A 10.53.0.3 bogus NS ns.bogus ns.bogus A 10.53.0.3 +; A subdomain with a corrupt DS +badds NS ns.badds +ns.badds A 10.53.0.3 + ; A dynamic secure subdomain dynamic NS dynamic dynamic A 10.53.0.3 diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 5b5d337a5d..118b8a6d6b 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -30,11 +30,10 @@ zonefile=example.db ( cd ../ns3 && sh sign.sh ) -for subdomain in secure bogus dynamic keyless nsec3 optout nsec3-unknown \ - optout-unknown multiple rsasha256 rsasha512 kskonly update-nsec3 \ - auto-nsec auto-nsec3 secure.below-cname ttlpatch split-dnssec \ - split-smart expired expiring upper lower - +for subdomain in secure badds bogus dynamic keyless nsec3 optout \ + nsec3-unknown optout-unknown multiple rsasha256 rsasha512 \ + kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \ + ttlpatch split-dnssec split-smart expired expiring upper lower do cp ../ns3/dsset-$subdomain.example. . done diff --git a/bin/tests/system/dnssec/ns3/bogus.example.db.in b/bin/tests/system/dnssec/ns3/bogus.example.db.in index e83d07bea0..80fb5e3992 100644 --- a/bin/tests/system/dnssec/ns3/bogus.example.db.in +++ b/bin/tests/system/dnssec/ns3/bogus.example.db.in @@ -28,5 +28,6 @@ ns A 10.53.0.3 a A 10.0.0.1 b A 10.0.0.2 +c A 10.0.0.3 d A 10.0.0.4 z A 10.0.0.26 diff --git a/bin/tests/system/dnssec/ns3/named.conf b/bin/tests/system/dnssec/ns3/named.conf index e745a7525f..6ef21b39c6 100644 --- a/bin/tests/system/dnssec/ns3/named.conf +++ b/bin/tests/system/dnssec/ns3/named.conf @@ -68,6 +68,12 @@ zone "bogus.example" { allow-update { any; }; }; +zone "badds.example" { + type master; + file "badds.example.db.signed"; + allow-update { any; }; +}; + zone "dynamic.example" { type master; file "dynamic.example.db.signed"; diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in index c9de3e5b6d..816f766fcb 100644 --- a/bin/tests/system/dnssec/ns3/secure.example.db.in +++ b/bin/tests/system/dnssec/ns3/secure.example.db.in @@ -28,7 +28,11 @@ ns A 10.53.0.3 a A 10.0.0.1 b A 10.0.0.2 +c A 10.0.0.3 d A 10.0.0.4 +e A 10.0.0.5 +f A 10.0.0.6 +g A 10.0.0.7 z A 10.0.0.26 a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 x CNAME a diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index 24b0fed7dd..4f010168cf 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -451,3 +451,19 @@ kskname=`$KEYGEN -I $now+90s -q -r $RANDFILE -f KSK $zone` zskname=`$KEYGEN -q -r $RANDFILE $zone` cp $infile $zonefile $SIGNER -S -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1 + + +# +# A zone with a bad DS in the parent +# (sourced from bogus.example.db.in) +# +zone=badds.example. +infile=bogus.example.db.in +zonefile=badds.example.db + +keyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 768 -n zone $zone` + +cat $infile $keyname.key >$zonefile + +$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1 +sed -e 's/bogus/badds/g' < dsset-bogus.example. > dsset-badds.example. \ No newline at end of file diff --git a/bin/tests/system/dnssec/ns4/named1.conf b/bin/tests/system/dnssec/ns4/named1.conf index a129c5e51a..542266f2f6 100644 --- a/bin/tests/system/dnssec/ns4/named1.conf +++ b/bin/tests/system/dnssec/ns4/named1.conf @@ -34,6 +34,9 @@ options { dnssec-validation yes; dnssec-must-be-secure mustbesecure.example yes; + nta-lifetime 10s; + nta-recheck 7s; + # Note: We only reference the bind.keys file here to confirm that it # is *not* being used. It contains the real root key, and we're # using a local toy root zone for the tests, so it wouldn't work. diff --git a/bin/tests/system/dnssec/ntadiff.pl b/bin/tests/system/dnssec/ntadiff.pl new file mode 100755 index 0000000000..d3f5554230 --- /dev/null +++ b/bin/tests/system/dnssec/ntadiff.pl @@ -0,0 +1,13 @@ +#!/usr/bin/perl -w + +use strict; +use Time::Piece; +use Time::Seconds; + +exit 1 if (scalar(@ARGV) != 2); + +my $actual = Time::Piece->strptime($ARGV[0], '%d-%b-%Y %H:%M:%S.000 %z'); +my $expected = Time::Piece->strptime($ARGV[1], '%s') + ONE_WEEK; +my $diff = abs($actual - $expected); + +print($diff . "\n"); diff --git a/bin/tests/system/dnssec/setup.sh b/bin/tests/system/dnssec/setup.sh index 424792966a..7b2a6bc825 100644 --- a/bin/tests/system/dnssec/setup.sh +++ b/bin/tests/system/dnssec/setup.sh @@ -24,6 +24,8 @@ sh clean.sh cd ns1 && sh sign.sh echo "a.bogus.example. A 10.0.0.22" >>../ns3/bogus.example.db.signed +echo "b.bogus.example. A 10.0.0.23" >>../ns3/bogus.example.db.signed +echo "c.bogus.example. A 10.0.0.23" >>../ns3/bogus.example.db.signed cd ../ns4 && cp -f named1.conf named.conf cd ../ns5 && cp -f trusted.conf.bad trusted.conf diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index bb7452f82b..9397b75bb4 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -21,6 +21,7 @@ SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh RANDFILE=random.data +DEFAULT_NTA=3bf305731dd26307.nta status=0 n=1 @@ -811,7 +812,7 @@ if [ -x ${SAMPLE} ] ; then ret=0 echo "I:checking failed validation using dns_client ($n)" $SAMPLE $SAMPLEKEY -p 5300 -t a 10.53.0.4 a.bogus.example > /dev/null 2> sample.out$n || ret=1 - grep "resolution failed: no valid RRSIG" sample.out$n > /dev/null || ret=1 + grep "resolution failed: RRSIG failed to verify" sample.out$n > /dev/null || ret=1 n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` @@ -1505,7 +1506,7 @@ keyid=`cat ns1/managed.key.id` linecount=`grep "./RSAMD5/$keyid ; trusted" ns4/named.secroots | wc -l` [ "$linecount" -eq 1 ] || ret=1 linecount=`cat ns4/named.secroots | wc -l` -[ "$linecount" -eq 5 ] || ret=1 +[ "$linecount" -eq 10 ] || ret=1 n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` @@ -1585,9 +1586,398 @@ ret=0 $DIG $DIGOPTS ns algroll. @10.53.0.4 > dig.out.ns4.test$n || ret=1 grep "NOERROR" dig.out.ns4.test$n > /dev/null || ret=1 grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:checking positive and negative validation with negative trust anchors ($n)" +ret=0 +# +# check correct initial behavior +# +$DIG $DIGOPTS a.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.1 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.1 > /dev/null || ret=1 +$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null || ret=1 +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null || ret=1 + +# +# add negative trust anchors +# +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 15s bogus.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta badds.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig # reconfig should maintain NTAs +lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` +[ "$lines" -eq 2 ] || ret=1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta secure.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta fakenode.secure.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reload # reload should maintain NTAs +lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` +[ "$lines" -eq 4 ] || ret=1 + +# +# check behavior with NTA's in place +# +$DIG $DIGOPTS a.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.4 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.4 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.4 > /dev/null && ret=1 +$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.5 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.5 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.5 > /dev/null && ret=1 +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.6 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.6 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.6 > /dev/null && ret=1 +$DIG $DIGOPTS a.fakenode.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.7 || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.7 > /dev/null && ret=1 +echo "I: dumping secroots" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 secroots | sed 's/^/I:ns4 /' +grep "bogus.example: expiry" ns4/named.secroots > /dev/null || ret=1 +grep "badds.example: expiry" ns4/named.secroots > /dev/null || ret=1 +grep "secure.example: expiry" ns4/named.secroots > /dev/null || ret=1 +grep "fakenode.secure.example: expiry" ns4/named.secroots > /dev/null || ret=1 +echo "I: waiting for NTA rechecks/expirations" + +# +# secure.example and badds.example used default nta-duration +# (configured as 10s in ns4/named1.conf), but nta recheck interval +# is configured to 7s, so at t=8 the NTAs for secure.example and +# fakenode.secure.example should both be lifted, but badds.example +# should still be going. +# +sleep 8 +$DIG $DIGOPTS b.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.8 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.8 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.8 > /dev/null || ret=1 +$DIG $DIGOPTS b.fakenode.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.9 || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.9 > /dev/null || ret=1 +grep "status: NXDOMAIN" dig.out.ns4.test$n.9 > /dev/null || ret=1 +$DIG $DIGOPTS badds.example. soa @10.53.0.4 > dig.out.ns4.test$n.10 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.10 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.10 > /dev/null && ret=1 + +# +# bogus.example was set to expire in 15s, so at t=11 +# it should still be NTA'd, but badds.example used the default +# lifetime of 10s, so it should revert to SERVFAIL now. +# +sleep 3 +# check nta table +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n._11 +lines=`wc -l < rndc.out.ns4.test$n._11` +[ "$lines" -le 2 ] || ret=1 +grep "bogus.example: expiry" rndc.out.ns4.test$n._11 > /dev/null || ret=1 +grep "badds.example: expiry" rndc.out.ns4.test$n._11 > /dev/null && ret=1 +$DIG $DIGOPTS b.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.11 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.11 > /dev/null && ret=1 +$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.12 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.12 > /dev/null || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.12 > /dev/null && ret=1 +$DIG $DIGOPTS c.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.13 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.13 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.13 > /dev/null || ret=1 + +# +# at t=16, all the NTAs should have expired. +# +sleep 5 +# check correct behavior after bogus.example expiry +$DIG $DIGOPTS d.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.14 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.14 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.14 > /dev/null || ret=1 +$DIG $DIGOPTS c.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.15 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.15 > /dev/null || ret=1 +# check nta table has been cleaned up now +lines=`$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d | wc -l` +[ "$lines" -eq 0 ] || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed - checking that all nta's have been lifted"; fi +status=`expr $status + $ret` +ret=0 + +echo "I: testing NTA removals ($n)" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta badds.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 +grep "badds.example: expiry" rndc.out.ns4.test$n.1 > /dev/null || ret=1 +$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.1 || ret=1 +grep "^a.badds.example." dig.out.ns4.test$n.1 > /dev/null || ret=1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove badds.example > rndc.out.ns4.test$n.2 +grep "Negative trust anchor removed: badds.example/_default" rndc.out.ns4.test$n.2 > /dev/null || ret=1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.3 +grep "badds.example: expiry" rndc.out.ns4.test$n.3 > /dev/null && ret=1 +$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null || ret=1 +echo "I: remove non-existent NTA three times" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -r foo > rndc.out.ns4.test$n.4 2>&1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove foo > rndc.out.ns4.test$n.5 2>&1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -r foo > rndc.out.ns4.test$n.6 2>&1 +grep "'nta' failed: not found" rndc.out.ns4.test$n.6 > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` +ret=0 + +n=`expr $n + 1` +echo "I: testing NTA with bogus lifetimes ($n)" +echo "I:check with no nta lifetime specified" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l "" foo > rndc.out.ns4.test$n.1 2>&1 +grep "'nta' failed: bad ttl" rndc.out.ns4.test$n.1 > /dev/null || ret=1 +echo "I:check with bad nta lifetime" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l garbage foo > rndc.out.ns4.test$n.2 2>&1 +grep "'nta' failed: bad ttl" rndc.out.ns4.test$n.2 > /dev/null || ret=1 +echo "I:check with too long nta lifetime" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -l 7d1h foo > rndc.out.ns4.test$n.3 2>&1 +grep "'nta' failed: out of range" rndc.out.ns4.test$n.3 > /dev/null || ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +ret=0 + +# +# check NTA persistence across restarts +# +n=`expr $n + 1` +echo "I: testing NTA persistence across restarts ($n)" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 +lines=`wc -l < rndc.out.ns4.test$n.1` +[ "$lines" -eq 0 ] || ret=1 +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 30s bogus.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 10s badds.example 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.2 +lines=`wc -l < rndc.out.ns4.test$n.2` +[ "$lines" -eq 2 ] || ret=1 +start=`$PERL -e 'print time()."\n";'` + +if [ $ret != 0 ]; then echo "I:failed - NTA persistence: adding NTA's failed"; fi +status=`expr $status + $ret` +ret=0 + +echo "I:killing ns4 with SIGTERM" +cd ns4 +kill -TERM `cat named.pid` +rm named.pid +cd .. + +# +# ns4 has now shutdown. wait until t=14 when badds.example's NTA +# (lifetime=10s) would have expired, and then restart ns4. +# +echo "I:waiting till 14s have passed since NTAs were added before restarting ns4" +$PERL -e 'my $delay = '$start' + 14 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' + +if + $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 +then + echo "I:restarted server ns4" +else + echo "I:could not restart server ns4" + exit 1 +fi + +echo "I:sleeping for an additional 4 seconds for ns4 to fully startup" +sleep 4 + +# +# ns4 should be back up now. The NTA for bogus.example should still be +# valid, whereas badds.example should not have been added during named +# startup (as it had already expired). +# +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.3 +lines=`wc -l < rndc.out.ns4.test$n.3` +[ "$lines" -eq 1 ] || ret=1 +grep "bogus.example: expiry" rndc.out.ns4.test$n.3 > /dev/null || ret=1 +$DIG $DIGOPTS b.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.4 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.4 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.4 > /dev/null && ret=1 +$DIG $DIGOPTS a.badds.example. a @10.53.0.4 > dig.out.ns4.test$n.5 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.5 > /dev/null || ret=1 + +# cleanup +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove bogus.example > rndc.out.ns4.test$n.6 + +if [ $ret != 0 ]; then echo "I:failed - NTA persistence: restoring NTA failed"; fi +status=`expr $status + $ret` +ret=0 + +# +# check "regular" attribute in NTA file works as expected at named +# startup. +# +n=`expr $n + 1` +echo "I: testing loading regular attribute from NTA file ($n)" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>/dev/null +lines=`wc -l < rndc.out.ns4.test$n.1` +[ "$lines" -eq 0 ] || ret=1 +# initially, secure.example. validates with AD=1 +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.2 > /dev/null || ret=1 + +echo "I:killing ns4 with SIGTERM" +cd ns4 +kill -TERM `cat named.pid` +rm named.pid +cd .. + +echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" +sleep 4 + +# +# ns4 has now shutdown. add NTA for secure.example. directly into the +# ${DEFAULT_NTA} file with the regular attribute and some future timestamp. +# +year=`date +%Y` +future="`expr 20 + ${year}`0101010000" +echo "secure.example. regular $future" > ns4/${DEFAULT_NTA} +start=`$PERL -e 'print time()."\n";'` + +if + $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 +then + echo "I:restarted server ns4" +else + echo "I:could not restart server ns4" + exit 1 +fi + +# nta-recheck is configured as 7s, so at t=10 the NTAs for +# secure.example. should be lifted as it is not a forced NTA. +echo "I:waiting till 10s have passed after ns4 was restarted" +$PERL -e 'my $delay = '$start' + 10 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' + +# secure.example. should now return an AD=1 answer (still validates) as +# the NTA has been lifted. +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null || ret=1 + +# cleanup +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.4 2>/dev/null + +if [ $ret != 0 ]; then echo "I:failed - NTA persistence: loading regular NTAs failed"; fi +status=`expr $status + $ret` +ret=0 + +# +# check "forced" attribute in NTA file works as expected at named +# startup. +# +n=`expr $n + 1` +echo "I: testing loading forced attribute from NTA file ($n)" +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>/dev/null +lines=`wc -l < rndc.out.ns4.test$n.1` +[ "$lines" -eq 0 ] || ret=1 +# initially, secure.example. validates with AD=1 +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.2 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.2 > /dev/null || ret=1 + +echo "I:killing ns4 with SIGTERM" +cd ns4 +kill -TERM `cat named.pid` +rm named.pid +cd .. + +echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" +sleep 4 + +# +# ns4 has now shutdown. add NTA for secure.example. directly into the +# ${DEFAULT_NTA} file with the forced attribute and some future timestamp. +# +echo "secure.example. forced $future" > ns4/${DEFAULT_NTA} +start=`$PERL -e 'print time()."\n";'` + +if + $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 +then + echo "I:restarted server ns4" +else + echo "I:could not restart server ns4" + exit 1 +fi + +# nta-recheck is configured as 7s, but even at t=10 the NTAs for +# secure.example. should not be lifted as it is a forced NTA. +echo "I:waiting till 10s have passed after ns4 was restarted" +$PERL -e 'my $delay = '$start' + 10 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' + +# secure.example. should now return an AD=0 answer (non-authenticated) +# as the NTA is still there. +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.3 || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n.3 > /dev/null && ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.3 > /dev/null && ret=1 + +# cleanup +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.4 2>/dev/null + +if [ $ret != 0 ]; then echo "I:failed - NTA persistence: loading forced NTAs failed"; fi +status=`expr $status + $ret` +ret=0 + +# +# check that NTA lifetime read from file is clamped to 1 week. +# +n=`expr $n + 1` +echo "I: testing loading out of bounds lifetime from NTA file ($n)" + +echo "I:killing ns4 with SIGTERM" +cd ns4 +kill -TERM `cat named.pid` +rm named.pid +cd .. + +echo "I:sleeping for an additional 4 seconds for ns4 to fully shutdown" +sleep 4 + +# +# ns4 has now shutdown. add NTA for secure.example. directly into the +# ${DEFAULT_NTA} file with a lifetime well into the future. +# +echo "secure.example. forced $future" > ns4/${DEFAULT_NTA} +added=`$PERL -e 'print time()."\n";'` + +if + $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns4 +then + echo "I:restarted server ns4" +else + echo "I:could not restart server ns4" + exit 1 +fi + +echo "I:sleeping for an additional 4 seconds for ns4 to fully startup" +sleep 4 + +# dump the NTA to a file +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 2>rndc.out.ns4.test$n.1err +lines=`wc -l < rndc.out.ns4.test$n.1` +[ "$lines" -eq 1 ] || ret=1 +ts=`awk '{print $3" "$4}' < rndc.out.ns4.test$n.1` +# rndc nta outputs localtime, so append the timezone +ts_with_zone="$ts `date +%z`" +echo "ts=$ts" > rndc.out.ns4.test$n.2 +echo "ts_with_zone=$ts_with_zone" >> rndc.out.ns4.test$n.2 +echo "added=$added" >> rndc.out.ns4.test$n.2 +if $PERL -e 'use Time::Piece; use Time::Seconds;' 2>/dev/null +then + # ntadiff.pl computes $ts_with_zone - ($added + 1week) + d=`$PERL ./ntadiff.pl "$ts_with_zone" "$added"` + echo "d=$d" >> rndc.out.ns4.test$n.2 + # diff from $added(now) + 1week to the clamped NTA lifetime should be + # less than a few seconds. + [ $d -lt 3610 ] || ret=1 +else + echo "I: skipped ntadiff test; install PERL module Time::Piece" +fi + +# cleanup +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -remove secure.example > rndc.out.ns4.test$n.3 2>/dev/null + +if [ $ret != 0 ]; then echo "I:failed - NTA lifetime clamping failed"; fi +status=`expr $status + $ret` +ret=0 +echo "I:completed NTA tests" # Run a minimal update test if possible. This is really just # a regression test for RT #2399; more tests should be added. @@ -1689,7 +2079,7 @@ ret=0 $DIG $DIGOPTS +noauth expired.example. +dnssec @10.53.0.4 soa > dig.out.ns4.test$n || ret=1 grep "SERVFAIL" dig.out.ns4.test$n > /dev/null || ret=1 grep "flags:.*ad.*QUERY" dig.out.ns4.test$n > /dev/null && ret=1 -grep "expired.example .*: RRSIG has expired" ns4/named.run > /dev/null || ret=1 +grep "expired.example[ /].*: RRSIG has expired" ns4/named.run > /dev/null || ret=1 n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` diff --git a/bin/tests/system/rndc/tests.sh b/bin/tests/system/rndc/tests.sh index 947987bce8..01dbc811ae 100644 --- a/bin/tests/system/rndc/tests.sh +++ b/bin/tests/system/rndc/tests.sh @@ -305,5 +305,11 @@ done if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:testing rndc nta time limits" +ret=0 +$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 2h nta1.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 +$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 1d nta2.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 +$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 1w nta3.example 2>&1 | grep "Negative trust anchor added" > /dev/null || ret=1 +$RNDC -s 10.53.0.4 -p 9956 -c ns4/key6.conf nta -l 8d nta4.example 2>&1 | grep "NTA lifetime cannot exceed one week" > /dev/null || ret=1 echo "I:exit status: $status" exit $status diff --git a/configure.in b/configure.in index fb2e53e8e1..ee38901937 100644 --- a/configure.in +++ b/configure.in @@ -3710,6 +3710,7 @@ AC_SUBST(BIND9_ISCCFG_BUILDINCLUDE) AC_SUBST(BIND9_DNS_BUILDINCLUDE) AC_SUBST(BIND9_LWRES_BUILDINCLUDE) AC_SUBST(BIND9_BIND9_BUILDINCLUDE) +AC_SUBST(BIND9_IRS_BUILDINCLUDE) if test "X$srcdir" != "X"; then BIND9_ISC_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/isc/include" BIND9_ISCCC_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/isccc/include" @@ -3717,6 +3718,7 @@ if test "X$srcdir" != "X"; then BIND9_DNS_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/dns/include" BIND9_LWRES_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/lwres/include" BIND9_BIND9_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/bind9/include" + BIND9_IRS_BUILDINCLUDE="-I${BIND9_TOP_BUILDDIR}/lib/irs/include" else BIND9_ISC_BUILDINCLUDE="" BIND9_ISCCC_BUILDINCLUDE="" @@ -3724,6 +3726,7 @@ else BIND9_DNS_BUILDINCLUDE="" BIND9_LWRES_BUILDINCLUDE="" BIND9_BIND9_BUILDINCLUDE="" + BIND9_IRS_BUILDINCLUDE="" fi AC_SUBST_FILE(BIND9_MAKE_INCLUDES) diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 7a9d60db02..af194d9cfd 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -4902,6 +4902,8 @@ badresp:1,adberr:0,findfail:0,valfail:0] max-refresh-time number ; min-retry-time number ; max-retry-time number ; + nta-lifetime duration ; + nta-recheck duration ; port ip_port; additional-from-auth yes_or_no ; additional-from-cache yes_or_no ; @@ -5684,11 +5686,76 @@ options { named. (A planned third option, external, will disable all automatic signing and allow DNSSEC data to be submitted into a zone - via dyanmic update; this is not yet implemented.) + via dynamic update; this is not yet implemented.) + + nta-lifetime + + + Species the default lifetime, in seconds, + that will be used for negative trust anchors added + via rndc nta. + + + A negative trust anchor selectively disables + DNSSEC validation for zones that known to be + failing because of misconfiguration rather than + an attack. When data to be validated is + at or below an active NTA (and above any other + configured trust anchors), named will + abort the DNSSEC validation process and treat the data as + insecure rather than bogus. This continues until the + NTA's lifetime is elapsed. NTAs persist + across named restarts. + + + For convienience, TTL-style time unit suffixes can be + used to specify the NTA lifetime in seconds, minutes + or hours. defaults to + one hour. It cannot exceed one week. + + + + + + nta-recheck + + + Species how often to check whether negative + trust anchors added via rndc nta + are still necessary. + + + A negative trust anchor is normally used when a + domain has stopped validating due to operator error; + it temporarily disables DNSSEC validation for that + domain. In the interest of ensuring that DNSSEC + validation is turned back on as soon as possible, + named will periodically send a + query to the domain, ignoring negative trust anchors, + to find out whether it can now be validated. If so, + the negative trust anchor is allowed to expire early. + + + Validity checks can be disabled for an indivdiual + NTA by using rndc nta -f, or + for all NTA's by setting + to zero. + + + For convienience, TTL-style time unit suffixes can be + used to specify the NTA recheck interval in seconds, + minutes or hours. The default is five minutes. It + cannot be longer than + (which cannot be longer than a week). + + + + + zone-statistics @@ -10179,6 +10246,10 @@ ns.domain.com.rpz-nsdname CNAME . level are inherited by all views, but keys defined in a view are only used within that view. + + Validation below specified names can be temporarily disabled + by using rndc nta. + @@ -17037,6 +17108,7 @@ zone "example.com" { Manual pages + diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 61574e8144..5131b521f4 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -689,6 +689,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, const char *str; dns_name_t *name; isc_buffer_t b; + isc_uint32_t lifetime = 0; static intervaltable intervals[] = { { "cleaning-interval", 60, 28 * 24 * 60 }, /* 28 days */ @@ -1038,6 +1039,38 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, result = ISC_R_FAILURE; } + + obj = NULL; + (void)cfg_map_get(options, "nta-lifetime", &obj); + if (obj != NULL) { + lifetime = cfg_obj_asuint32(obj); + if (lifetime > 604800) { /* 7 days */ + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'nta-lifetime' cannot exceed one week"); + result = ISC_R_RANGE; + } else if (lifetime == 0) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'nta-lifetime' may not be zero"); + result = ISC_R_RANGE; + } + } + + obj = NULL; + (void)cfg_map_get(options, "nta-recheck", &obj); + if (obj != NULL) { + isc_uint32_t recheck = cfg_obj_asuint32(obj); + if (recheck > 604800) { /* 7 days */ + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'nta-recheck' cannot exceed one week"); + result = ISC_R_RANGE; + } + + if (recheck > lifetime) + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "'nta-recheck' (%d seconds) is " + "greater than 'nta-lifetime' " + "(%d seconds)", recheck, lifetime); + } return (result); } diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index ae316c59e2..4478a6c8e6 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -66,8 +66,8 @@ DNSOBJS = acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \ iptable.@O@ journal.@O@ keydata.@O@ keytable.@O@ \ lib.@O@ log.@O@ lookup.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ - name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ order.@O@ peer.@O@ \ - portlist.@O@ private.@O@ \ + name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ + order.@O@ peer.@O@ portlist.@O@ private.@O@ \ rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \ rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ request.@O@ resolver.@O@ result.@O@ rootns.@O@ \ @@ -100,7 +100,8 @@ DNSSRCS = acache.c acl.c adb.c byaddr.c \ dlz.c dns64.c dnssec.c ds.c dyndb.c forward.c iptable.c \ journal.c keydata.c keytable.c lib.c log.c lookup.c \ master.c masterdump.c message.c \ - name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \ + name.c ncache.c nsec.c nsec3.c nta.c \ + order.c peer.c portlist.c \ rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \ rdataset.c rdatasetiter.c rdataslab.c request.c \ resolver.c result.c rootns.c rpz.c rriterator.c \ diff --git a/lib/dns/adb.c b/lib/dns/adb.c index ac89e66172..c38df5bb87 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -2465,9 +2465,9 @@ dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr, result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl); if (result != ISC_R_SUCCESS) { - DP(ISC_LOG_INFO, "adb: task-exclusive mode unavailable, " - "intializing table sizes to %u\n", - nbuckets[11]); + DP(DEF_LEVEL, "adb: task-exclusive mode unavailable, " + "intializing table sizes to %u\n", + nbuckets[11]); adb->nentries = nbuckets[11]; adb->nnames = nbuckets[11]; diff --git a/lib/dns/client.c b/lib/dns/client.c index d3b371b0e4..9835b37bf0 100644 --- a/lib/dns/client.c +++ b/lib/dns/client.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -129,6 +130,8 @@ typedef struct resctx { isc_mutex_t lock; dns_client_t *client; isc_boolean_t want_dnssec; + isc_boolean_t want_validation; + isc_boolean_t want_cdflag; /* Locked */ ISC_LINK(struct resctx) link; @@ -302,12 +305,12 @@ getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr, } static isc_result_t -dns_client_createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, - unsigned int options, isc_taskmgr_t *taskmgr, - unsigned int ntasks, isc_socketmgr_t *socketmgr, - isc_timermgr_t *timermgr, dns_dispatchmgr_t *dispatchmgr, - dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, - dns_view_t **viewp) +createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, + unsigned int options, isc_taskmgr_t *taskmgr, + unsigned int ntasks, isc_socketmgr_t *socketmgr, + isc_timermgr_t *timermgr, dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, + dns_view_t **viewp) { isc_result_t result; dns_view_t *view = NULL; @@ -324,9 +327,9 @@ dns_client_createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, return (result); } - result = dns_view_createresolver(view, taskmgr, ntasks, 1, socketmgr, - timermgr, 0, dispatchmgr, - dispatchv4, dispatchv6); + result = dns_view_createresolver(view, taskmgr, ntasks, 1, + socketmgr, timermgr, 0, + dispatchmgr, dispatchv4, dispatchv6); if (result != ISC_R_SUCCESS) { dns_view_detach(&view); return (result); @@ -485,10 +488,9 @@ dns_client_createx(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, } /* Create the default view for class IN */ - result = dns_client_createview(mctx, dns_rdataclass_in, options, - taskmgr, RESOLVER_NTASKS, socketmgr, timermgr, - dispatchmgr, dispatchv4, dispatchv6, - &view); + result = createview(mctx, dns_rdataclass_in, options, taskmgr, + RESOLVER_NTASKS, socketmgr, timermgr, dispatchmgr, + dispatchv4, dispatchv6, &view); if (result != ISC_R_SUCCESS) goto cleanup; ISC_LIST_INIT(client->viewlist); @@ -653,6 +655,46 @@ dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, return (result); } +isc_result_t +dns_client_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, + const char *dlvname) +{ + isc_result_t result; + isc_buffer_t b; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) + goto cleanup; + + if (dlvname == NULL) + view->dlv = NULL; + else { + dns_name_t *newdlv; + + isc_buffer_constinit(&b, dlvname, strlen(dlvname)); + isc_buffer_add(&b, strlen(dlvname)); + newdlv = dns_fixedname_name(&view->dlv_fixed); + result = dns_name_fromtext(newdlv, &b, dns_rootname, + DNS_NAME_DOWNCASE, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + + view->dlv = dns_fixedname_name(&view->dlv_fixed); + } + + cleanup: + if (view != NULL) + dns_view_detach(&view); + + return (result); +} + static isc_result_t getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { dns_rdataset_t *rdataset; @@ -703,6 +745,7 @@ fetch_done(isc_task_t *task, isc_event_t *event) { static inline isc_result_t start_fetch(resctx_t *rctx) { isc_result_t result; + int fopts = 0; /* * The caller must be holding the rctx's lock. @@ -710,10 +753,15 @@ start_fetch(resctx_t *rctx) { REQUIRE(rctx->fetch == NULL); + if (!rctx->want_cdflag) + fopts |= DNS_FETCHOPT_NOCDFLAG; + if (!rctx->want_validation) + fopts |= DNS_FETCHOPT_NOVALIDATE; + result = dns_resolver_createfetch(rctx->view->resolver, dns_fixedname_name(&rctx->name), rctx->type, - NULL, NULL, NULL, 0, + NULL, NULL, NULL, fopts, rctx->task, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset, @@ -1256,7 +1304,7 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, isc_mem_t *mctx; isc_result_t result; dns_rdataset_t *rdataset, *sigrdataset; - isc_boolean_t want_dnssec; + isc_boolean_t want_dnssec, want_validation, want_cdflag; REQUIRE(DNS_CLIENT_VALID(client)); REQUIRE(transp != NULL && *transp == NULL); @@ -1272,6 +1320,8 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, rdataset = NULL; sigrdataset = NULL; want_dnssec = ISC_TF((options & DNS_CLIENTRESOPT_NODNSSEC) == 0); + want_validation = ISC_TF((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0); + want_cdflag = ISC_TF((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0); /* * Prepare some intermediate resources @@ -1327,6 +1377,8 @@ dns_client_startresolve(dns_client_t *client, dns_name_t *name, rctx->restarts = 0; rctx->fetch = NULL; rctx->want_dnssec = want_dnssec; + rctx->want_validation = want_validation; + rctx->want_cdflag = want_cdflag; ISC_LIST_INIT(rctx->namelist); rctx->event = event; diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h index d21dff788d..289dc18918 100644 --- a/lib/dns/include/dns/client.h +++ b/lib/dns/include/dns/client.h @@ -77,10 +77,14 @@ ISC_LANG_BEGINDECLS /*% * Optional flags for dns_client_(start)resolve. */ -/*%< Disable DNSSEC validation. */ +/*%< Do not return DNSSEC data (e.g. RRSIGS) with response. */ #define DNS_CLIENTRESOPT_NODNSSEC 0x01 /*%< Allow running external context. */ #define DNS_CLIENTRESOPT_ALLOWRUN 0x02 +/*%< Don't validate responses. */ +#define DNS_CLIENTRESOPT_NOVALIDATE 0x04 +/*%< Don't set the CD flag on upstream queries. */ +#define DNS_CLIENTRESOPT_NOCDFLAG 0x08 /*% * Optional flags for dns_client_(start)request. @@ -248,6 +252,26 @@ dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, *\li Anything else Failure. */ +isc_result_t +dns_client_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, + const char *dlvname); +/*%< + * Specify a name to use for DNSSEC lookaside validation (e.g., + * "dlv.isc.org"). If a trusted key has been added for that name, + * then DLV will be used during validation. If 'dlvname' is NULL, + * then DLV will no longer be used for this client. + * + * Requires: + * + *\li 'client' is a valid client. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + isc_result_t dns_client_resolve(dns_client_t *client, dns_name_t *name, dns_rdataclass_t rdclass, dns_rdatatype_t type, diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h index 3f4adaf6e3..c44fca8a40 100644 --- a/lib/dns/include/dns/keytable.h +++ b/lib/dns/include/dns/keytable.h @@ -386,7 +386,7 @@ dns_keytable_detachkeynode(dns_keytable_t *keytable, isc_result_t dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, - isc_boolean_t *wantdnssecp); + dns_name_t *foundname, isc_boolean_t *wantdnssecp); /*%< * Is 'name' at or beneath a trusted key? * @@ -396,12 +396,16 @@ dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, * *\li 'name' is a valid absolute name. * - *\li '*wantsdnssecp' is a valid isc_boolean_t. + *\li 'foundanme' is NULL or is a pointer to an initialized dns_name_t * + *\li '*wantsdnssecp' is a valid isc_boolean_t. + * Ensures: * *\li On success, *wantsdnssecp will be ISC_TRUE if and only if 'name' - * is at or beneath a trusted key. + * is at or beneath a trusted key. If 'foundname' is not NULL, then + * it will be updated to contain the name of the closest enclosing + * trust anchor. * * Returns: * diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h index a3b7e5a71e..d7185678ff 100644 --- a/lib/dns/include/dns/log.h +++ b/lib/dns/include/dns/log.h @@ -78,6 +78,8 @@ LIBDNS_EXTERNAL_DATA extern isc_logmodule_t dns_modules[]; #define DNS_LOGMODULE_DNSSEC (&dns_modules[27]) #define DNS_LOGMODULE_CRYPTO (&dns_modules[28]) #define DNS_LOGMODULE_DYNDB (&dns_modules[29]) +#define DNS_LOGMODULE_PACKETS (&dns_modules[30]) +#define DNS_LOGMODULE_NTA (&dns_modules[31]) ISC_LANG_BEGINDECLS diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h index 8631248cc2..5164fd5084 100644 --- a/lib/dns/include/dns/masterdump.h +++ b/lib/dns/include/dns/masterdump.h @@ -100,6 +100,10 @@ typedef struct dns_master_style dns_master_style_t; /*% Report re-signing time. */ #define DNS_STYLEFLAG_RESIGN 0x04000000U +/*% Comment out data by prepending with ";" */ +#define DNS_STYLEFLAG_COMMENTDATA 0x10000000U + + ISC_LANG_BEGINDECLS /*** @@ -148,6 +152,11 @@ LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_simple; */ LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_debug; +/*% + * Similar to dns_master_style_debug but data is prepended with ";" + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_comment; + /*** *** Functions ***/ diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index d999e75a4d..11f3d41318 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -138,6 +138,7 @@ typedef int dns_messagetextflag_t; #define DNS_MESSAGETEXTFLAG_NOHEADERS 0x0002 #define DNS_MESSAGETEXTFLAG_ONESOA 0x0004 #define DNS_MESSAGETEXTFLAG_OMITSOA 0x0008 +#define DNS_MESSAGETEXTFLAG_COMMENTDATA 0x0010 /* * Dynamic update names for these sections. @@ -1358,6 +1359,17 @@ dns_message_gettimeadjust(dns_message_t *msg); *\li msg be a valid message. */ +void +dns_message_logpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx); + +void +dns_message_logfmtpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx); + isc_result_t dns_message_buildopt(dns_message_t *msg, dns_rdataset_t **opt, unsigned int version, isc_uint16_t udpsize, diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h new file mode 100644 index 0000000000..20bb86d5c7 --- /dev/null +++ b/lib/dns/include/dns/nta.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DNS_NTA_H +#define DNS_NTA_H 1 + +/***** + ***** Module Info + *****/ + +/*! \file + * \brief + * The NTA module provides services for storing and retrieving negative + * trust anchors, and determine whether a given domain is subject to + * DNSSEC validation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +struct dns_ntatable { + /* Unlocked. */ + unsigned int magic; + dns_view_t *view; + isc_rwlock_t rwlock; + isc_uint32_t recheck; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + isc_task_t *task; + /* Locked by rwlock. */ + isc_uint32_t references; + dns_rbt_t *table; +}; + +#define NTATABLE_MAGIC ISC_MAGIC('N', 'T', 'A', 't') +#define VALID_NTATABLE(nt) ISC_MAGIC_VALID(nt, NTATABLE_MAGIC) + +isc_result_t +dns_ntatable_create(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, + dns_ntatable_t **ntatablep); +/*%< + * Create an NTA table in view 'view'. + * + * Requires: + * + *\li 'view' is a valid view. + * + *\li 'tmgr' is a valid timer manager. + * + *\li ntatablep != NULL && *ntatablep == NULL + * + * Ensures: + * + *\li On success, *ntatablep is a valid, empty NTA table. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li Any other result indicates failure. + */ + +void +dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + *\li 'source' is a valid ntatable. + * + *\li 'targetp' points to a NULL dns_ntatable_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + */ + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep); +/*%< + * Detach *ntatablep from its ntatable. + * + * Requires: + * + *\li 'ntatablep' points to a valid ntatable. + * + * Ensures: + * + *\li *ntatablep is NULL. + * + *\li If '*ntatablep' is the last reference to the ntatable, + * all resources used by the ntatable will be freed + */ + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, + isc_boolean_t force, isc_stdtime_t now, + isc_uint32_t lifetime); +/*%< + * Add a negative trust anchor to 'ntatable' for name 'name', + * which will expire at time 'now' + 'lifetime'. If 'force' is ISC_FALSE, + * then the name will be checked periodically to see if it's bogus; + * if not, then the NTA will be allowed to expire early. + * + * Notes: + * + *\li If an NTA already exists in the table, its expiry time + * is updated. + * + * Requires: + * + *\li 'ntatable' points to a valid ntatable. + * + *\li 'name' points to a valid name. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_ntatable_delete(dns_ntatable_t *ntatable, dns_name_t *keyname); +/*%< + * Delete node(s) from 'ntatable' matching name 'keyname' + * + * Requires: + * + *\li 'ntatable' points to a valid ntatable. + * + *\li 'name' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_ntatable_deletenta(dns_ntatable_t *ntatable, dns_name_t *name); +/*%< + * Delete node from 'ntatable' matching the name 'name' + * + * Requires: + * + *\li 'ntatable' points to a valid ntatable. + *\li 'name' is a valid name. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_boolean_t +dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, + dns_name_t *name, dns_name_t *anchor); +/*%< + * Return ISC_TRUE if 'name' is below a non-expired negative trust + * anchor which in turn is at or below 'anchor'. + * + * If 'ntatable' has not been initialized, return ISC_FALSE. + * + * Requires: + * + *\li 'ntatable' is NULL or is a valid ntatable. + * + *\li 'name' is a valid absolute name. + */ + +isc_result_t +dns_ntatable_totext(dns_ntatable_t *ntatable, isc_buffer_t *buf); +/*%< + * Dump the NTA table to buffer 'buf' + */ + +isc_result_t +dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp); +/*%< + * Dump the NTA table to the file opened as 'fp'. + */ + +isc_result_t +dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp); +/*%< + * Save the NTA table to the file opened as 'fp', for later loading. + */ +ISC_LANG_ENDDECLS + +#endif /* DNS_NTA_H */ diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index e8a712a781..10642f2eab 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -95,7 +95,10 @@ typedef struct dns_fetchevent { #define DNS_FETCHOPT_NOVALIDATE 0x20 /*%< Disable validation. */ #define DNS_FETCHOPT_EDNS512 0x40 /*%< Advertise a 512 byte UDP buffer. */ -#define DNS_FETCHOPT_WANTNSID 0x80 /*%< Request NSID */ +#define DNS_FETCHOPT_WANTNSID 0x080 /*%< Request NSID */ +#define DNS_FETCHOPT_PREFETCH 0x100 /*%< Request NSID */ +#define DNS_FETCHOPT_NONTA 0x400 /*%< Ignore NTA table. */ +#define DNS_FETCHOPT_NOCDFLAG 0x200 /*%< Don't set CD flag. */ #define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000 #define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000 diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h index 12aacf9ba7..137bfd8567 100644 --- a/lib/dns/include/dns/result.h +++ b/lib/dns/include/dns/result.h @@ -153,8 +153,9 @@ #define DNS_R_EXPIRED (ISC_RESULTCLASS_DNS + 107) #define DNS_R_NOTDYNAMIC (ISC_RESULTCLASS_DNS + 108) #define DNS_R_BADEUI (ISC_RESULTCLASS_DNS + 109) +#define DNS_R_NTACOVERED (ISC_RESULTCLASS_DNS + 110) -#define DNS_R_NRESULTS 110 /*%< Number of results */ +#define DNS_R_NRESULTS 111 /*%< Number of results */ /* * DNS wire format rcodes. diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 5dc03deac1..d397b063b7 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -95,6 +95,8 @@ typedef isc_region_t dns_label_t; typedef struct dns_lookup dns_lookup_t; typedef struct dns_name dns_name_t; typedef ISC_LIST(dns_name_t) dns_namelist_t; +typedef struct dns_nta dns_nta_t; +typedef struct dns_ntatable dns_ntatable_t; typedef isc_uint16_t dns_opcode_t; typedef unsigned char dns_offsets_t[128]; typedef struct dns_order dns_order_t; diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index b3cfe9992f..88c7b8288d 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -165,13 +165,16 @@ struct dns_validator { unsigned int depth; unsigned int authcount; unsigned int authfail; + isc_stdtime_t start; }; /*% * dns_validator_create() options. */ -#define DNS_VALIDATOR_DLV 1U -#define DNS_VALIDATOR_DEFER 2U +#define DNS_VALIDATOR_DLV 0x0001U +#define DNS_VALIDATOR_DEFER 0x0002U +#define DNS_VALIDATOR_NOCDFLAG 0x0004U +#define DNS_VALIDATOR_NONTA 0x0008U /*% Ignore NTA table */ ISC_LANG_BEGINDECLS diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 704e5fe3e9..9d8b3373fd 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -190,6 +190,11 @@ struct dns_view { dns_zone_t * managed_keys; dns_zone_t * redirect; + dns_ntatable_t * ntatable_priv; /* internal use only */ + isc_uint32_t nta_lifetime; + isc_uint32_t nta_recheck; + char *nta_file; + #ifdef BIND9 /* File in which to store configuration for newly added zones */ char * new_zone_file; @@ -1023,17 +1028,50 @@ dns_view_iscacheshared(dns_view_t *view); *\li #ISC_FALSE otherwise. */ +isc_result_t +dns_view_initntatable(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr); +/*%< + * Initialize the negative trust anchor table for the view. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_SUCCESS + *\li Any other result indicates failure + */ + +isc_result_t +dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp); +/*%< + * Get the negative trust anchor table for this view. Returns + * ISC_R_NOTFOUND if the table not been initialized for the view. + * + * '*ntp' is attached on success; the caller is responsible for + * detaching it with dns_ntatable_detach(). + * + * Requires: + * \li 'view' is valid. + * \li 'nta' is not NULL and '*nta' is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + */ + isc_result_t dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx); /*%< - * Initialize security roots for the view. (Note that secroots is + * Initialize security roots for the view, detaching any previously + * existing security roots first. (Note that secroots_priv is * NULL until this function is called, so any function using - * secroots must check its validity first. One way to do this is - * use dns_view_getsecroots() and check its return value.) + * security roots must check that they have been initialized first. + * One way to do this is use dns_view_getsecroots() and check its + * return value.) * * Requires: * \li 'view' is valid. - * \li 'view->secroots' is NULL. * * Returns: *\li ISC_R_SUCCESS @@ -1060,10 +1098,14 @@ dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp); isc_result_t dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, - isc_boolean_t *secure_domain); + isc_stdtime_t now, isc_boolean_t checknta, + isc_boolean_t *secure_domain); /*%< - * Is 'name' at or beneath a trusted key? Put answer in - * '*secure_domain'. + * Is 'name' at or beneath a trusted key, and not covered by a valid + * negative trust anchor? Put answer in '*secure_domain'. + * + * If 'checknta' is ISC_FALSE, ignore the NTA table in determining + * whether this is a secure domain. * * Requires: * \li 'view' is valid. @@ -1073,6 +1115,20 @@ dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, *\li Any other value indicates failure */ +isc_boolean_t +dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, + dns_name_t *name, dns_name_t *anchor); +/*%< + * Is there a current negative trust anchor above 'name' and below 'anchor'? + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_TRUE + *\li ISC_R_FALSE + */ + void dns_view_untrust(dns_view_t *view, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx); @@ -1111,6 +1167,24 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx, void dns_view_restorekeyring(dns_view_t *view); +isc_result_t +dns_view_saventa(dns_view_t *view); +/*%< + * Save NTA for names in this view to a file. + * + * Requires: + *\li 'view' to be valid. + */ + +isc_result_t +dns_view_loadnta(dns_view_t *view); +/*%< + * Loads NTA for names in this view from a file. + * + * Requires: + *\li 'view' to be valid. + */ + ISC_LANG_ENDDECLS #endif /* DNS_VIEW_H */ diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c index c49847f326..c2025b0203 100644 --- a/lib/dns/keytable.c +++ b/lib/dns/keytable.c @@ -174,6 +174,7 @@ insert(dns_keytable_t *keytable, isc_boolean_t managed, for (k = node->data; k != NULL; k = k->next) { if (k->key == NULL) { k->key = *keyp; + *keyp = NULL; /* transfer ownership */ break; } if (dst_key_compare(k->key, *keyp) == ISC_TRUE) @@ -182,7 +183,7 @@ insert(dns_keytable_t *keytable, isc_boolean_t managed, if (k == NULL) result = ISC_R_SUCCESS; - else + else if (*keyp != NULL) dst_key_free(keyp); } @@ -523,10 +524,10 @@ dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep) isc_result_t dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, - isc_boolean_t *wantdnssecp) + dns_name_t *foundname, isc_boolean_t *wantdnssecp) { isc_result_t result; - void *data; + dns_rbtnode_t *node = NULL; /* * Is 'name' at or beneath a trusted key? @@ -538,11 +539,10 @@ dns_keytable_issecuredomain(dns_keytable_t *keytable, dns_name_t *name, RWLOCK(&keytable->rwlock, isc_rwlocktype_read); - data = NULL; - result = dns_rbt_findname(keytable->table, name, 0, NULL, &data); - + result = dns_rbt_findnode(keytable->table, name, foundname, &node, + NULL, DNS_RBTFIND_NOOPTIONS, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { - INSIST(data != NULL); + INSIST(node->data != NULL); *wantdnssecp = ISC_TRUE; result = ISC_R_SUCCESS; } else if (result == ISC_R_NOTFOUND) { @@ -575,6 +575,8 @@ dns_keytable_dump(dns_keytable_t *keytable, FILE *fp) dns_rbtnodechain_current(&chain, NULL, NULL, &node); for (knode = node->data; knode != NULL; knode = knode->next) { + if (knode->key == NULL) + continue; dst_key_format(knode->key, pbuf, sizeof(pbuf)); fprintf(fp, "%s ; %s\n", pbuf, knode->managed ? "managed" : "trusted"); diff --git a/lib/dns/log.c b/lib/dns/log.c index ff9ca6573c..c223e3b603 100644 --- a/lib/dns/log.c +++ b/lib/dns/log.c @@ -84,6 +84,8 @@ LIBDNS_EXTERNAL_DATA isc_logmodule_t dns_modules[] = { { "dns/dnssec", 0 }, { "dns/crypto", 0 }, { "dns/dyndb", 0 }, + { "dns/packets", 0 }, + { "dns/nta", 0 }, { NULL, 0 } }; diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c index 2717658e69..80fcd4c12d 100644 --- a/lib/dns/masterdump.c +++ b/lib/dns/masterdump.c @@ -158,6 +158,18 @@ dns_master_style_debug = { 24, 32, 40, 48, 80, 8, UINT_MAX }; +/*% + * Similar, but with each line commented out. + */ +LIBDNS_EXTERNAL_DATA const dns_master_style_t +dns_master_style_comment = { + DNS_STYLEFLAG_REL_OWNER | + DNS_STYLEFLAG_MULTILINE | + DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_COMMENTDATA, + 24, 32, 40, 48, 80, 8, UINT_MAX +}; + #define N_SPACES 10 static char spaces[N_SPACES+1] = " "; @@ -291,6 +303,14 @@ totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) { r.base[0] = '\n'; isc_buffer_add(&buf, 1); + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) { + isc_buffer_availableregion(&buf, &r); + if (r.length < 1) + return (DNS_R_TEXTTOOLONG); + r.base[0] = ';'; + isc_buffer_add(&buf, 1); + } + result = indent(&col, ctx->style.rdata_column, ctx->style.tab_width, &buf); /* @@ -427,6 +447,12 @@ rdataset_totext(dns_rdataset_t *rdataset, while (result == ISC_R_SUCCESS) { column = 0; + /* + * Comment? + */ + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) + isc_buffer_putstr(target, ";"); + /* * Owner name. */ diff --git a/lib/dns/message.c b/lib/dns/message.c index 06211751e5..2dba3bc8e7 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -3579,6 +3579,54 @@ dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) { return (ISC_R_SUCCESS); } +void +dns_message_logpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx) +{ + dns_message_logfmtpacket(message, description, category, module, + &dns_master_style_debug, level, mctx); +} + +void +dns_message_logfmtpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx) +{ + isc_buffer_t buffer; + char *buf = NULL; + int len = 1024; + isc_result_t result; + + if (! isc_log_wouldlog(dns_lctx, level)) + return; + + /* + * Note that these are multiline debug messages. We want a newline + * to appear in the log after each message. + */ + + do { + buf = isc_mem_get(mctx, len); + if (buf == NULL) + break; + isc_buffer_init(&buffer, buf, len); + result = dns_message_totext(message, style, 0, &buffer); + if (result == ISC_R_NOSPACE) { + isc_mem_put(mctx, buf, len); + len += 1024; + } else if (result == ISC_R_SUCCESS) + isc_log_write(dns_lctx, category, module, level, + "%s%.*s", description, + (int)isc_buffer_usedlength(&buffer), + buf); + } while (result == ISC_R_NOSPACE); + + if (buf != NULL) + isc_mem_put(mctx, buf, len); +} + isc_result_t dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp, unsigned int version, isc_uint16_t udpsize, diff --git a/lib/dns/nta.c b/lib/dns/nta.c new file mode 100644 index 0000000000..7188c4da30 --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dns_nta { + unsigned int magic; + isc_refcount_t refcount; + dns_ntatable_t *ntatable; + isc_boolean_t forced; + isc_timer_t *timer; + dns_fetch_t *fetch; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; + dns_fixedname_t fn; + dns_name_t *name; + isc_stdtime_t expiry; +}; + +#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n') +#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC) + +/* + * Obtain a reference to the nta object. Released by + * nta_detach. + */ +static void +nta_ref(dns_nta_t *nta) { + isc_refcount_increment(&nta->refcount, NULL); +} + +static void +nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { + unsigned int refs; + dns_nta_t *nta = *ntap; + + REQUIRE(VALID_NTA(nta)); + + *ntap = NULL; + isc_refcount_decrement(&nta->refcount, &refs); + if (refs == 0) { + nta->magic = 0; + if (nta->timer != NULL) { + (void) isc_timer_reset(nta->timer, + isc_timertype_inactive, + NULL, NULL, ISC_TRUE); + isc_timer_detach(&nta->timer); + } + isc_refcount_destroy(&nta->refcount); + if (dns_rdataset_isassociated(&nta->rdataset)) + dns_rdataset_disassociate(&nta->rdataset); + if (dns_rdataset_isassociated(&nta->sigrdataset)) + dns_rdataset_disassociate(&nta->sigrdataset); + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + dns_resolver_destroyfetch(&nta->fetch); + } + isc_mem_put(mctx, nta, sizeof(dns_nta_t)); + } +} + +static void +free_nta(void *data, void *arg) { + dns_nta_t *nta = (dns_nta_t *) data; + isc_mem_t *mctx = (isc_mem_t *) arg; + + nta_detach(mctx, &nta); +} + +isc_result_t +dns_ntatable_create(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, + dns_ntatable_t **ntatablep) +{ + dns_ntatable_t *ntatable; + isc_result_t result; + + REQUIRE(ntatablep != NULL && *ntatablep == NULL); + + ntatable = isc_mem_get(view->mctx, sizeof(*ntatable)); + if (ntatable == NULL) + return (ISC_R_NOMEMORY); + + ntatable->task = NULL; + result = isc_task_create(taskmgr, 0, &ntatable->task); + if (result != ISC_R_SUCCESS) + goto cleanup_ntatable; + isc_task_setname(ntatable->task, "ntatable", ntatable); + + ntatable->table = NULL; + result = dns_rbt_create(view->mctx, free_nta, view->mctx, + &ntatable->table); + if (result != ISC_R_SUCCESS) + goto cleanup_task; + + result = isc_rwlock_init(&ntatable->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_rbt; + + ntatable->timermgr = timermgr; + ntatable->taskmgr = taskmgr; + + ntatable->view = view; + ntatable->references = 1; + + ntatable->magic = NTATABLE_MAGIC; + *ntatablep = ntatable; + + return (ISC_R_SUCCESS); + + cleanup_rbt: + dns_rbt_destroy(&ntatable->table); + + cleanup_task: + isc_task_detach(&ntatable->task); + + cleanup_ntatable: + isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); + + return (result); +} + +void +dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) { + REQUIRE(VALID_NTATABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + RWLOCK(&source->rwlock, isc_rwlocktype_write); + + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); + + RWUNLOCK(&source->rwlock, isc_rwlocktype_write); + + *targetp = source; +} + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep) { + isc_boolean_t destroy = ISC_FALSE; + dns_ntatable_t *ntatable; + + REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep)); + + ntatable = *ntatablep; + *ntatablep = NULL; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + INSIST(ntatable->references > 0); + ntatable->references--; + if (ntatable->references == 0) + destroy = ISC_TRUE; + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (destroy) { + dns_rbt_destroy(&ntatable->table); + isc_rwlock_destroy(&ntatable->rwlock); + if (ntatable->task != NULL) + isc_task_detach(&ntatable->task); + ntatable->timermgr = NULL; + ntatable->taskmgr = NULL; + ntatable->magic = 0; + isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); + } +} + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; + dns_nta_t *nta = devent->ev_arg; + isc_result_t eresult = devent->result; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = ntatable->view; + isc_stdtime_t now = 0; + + UNUSED(task); + + if (dns_rdataset_isassociated(&nta->rdataset)) + dns_rdataset_disassociate(&nta->rdataset); + if (dns_rdataset_isassociated(&nta->sigrdataset)) + dns_rdataset_disassociate(&nta->sigrdataset); + if (nta->fetch == devent->fetch) + nta->fetch = NULL; + dns_resolver_destroyfetch(&devent->fetch); + + if (devent->node != NULL) + dns_db_detachnode(devent->db, &devent->node); + if (devent->db != NULL) + dns_db_detach(&devent->db); + + isc_event_free(&event); + + switch (eresult) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_NXRRSET: + isc_stdtime_get(&now); + nta->expiry = now; + break; + default: + break; + } + + /* + * If we're expiring before the next recheck, we might + * as well stop the timer now. + */ + if (nta->timer != NULL && nta->expiry - now < ntatable->recheck) + (void) isc_timer_reset(nta->timer, isc_timertype_inactive, + NULL, NULL, ISC_TRUE); + nta_detach(view->mctx, &nta); +} + +static void +checkbogus(isc_task_t *task, isc_event_t *event) { + dns_nta_t *nta = event->ev_arg; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = ntatable->view; + isc_result_t result; + + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + nta->fetch = NULL; + } + if (dns_rdataset_isassociated(&nta->rdataset)) + dns_rdataset_disassociate(&nta->rdataset); + if (dns_rdataset_isassociated(&nta->sigrdataset)) + dns_rdataset_disassociate(&nta->sigrdataset); + + isc_event_free(&event); + + nta_ref(nta); + result = dns_resolver_createfetch(view->resolver, nta->name, + dns_rdatatype_nsec, + NULL, NULL, NULL, + DNS_FETCHOPT_NONTA, + task, fetch_done, nta, + &nta->rdataset, + &nta->sigrdataset, + &nta->fetch); + if (result != ISC_R_SUCCESS) + nta_detach(view->mctx, &nta); +} + +static isc_result_t +settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, isc_uint32_t lifetime) { + isc_result_t result; + isc_interval_t interval; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(VALID_NTA(nta)); + + if (ntatable->timermgr == NULL) + return (ISC_R_SUCCESS); + + view = ntatable->view; + if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) + return (ISC_R_SUCCESS); + + isc_interval_set(&interval, view->nta_recheck, 0); + result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker, + NULL, &interval, ntatable->task, + checkbogus, nta, &nta->timer); + return (result); +} + +static isc_result_t +nta_create(dns_ntatable_t *ntatable, dns_name_t *name, dns_nta_t **target) { + isc_result_t result; + dns_nta_t *nta = NULL; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(target != NULL && *target == NULL); + + view = ntatable->view; + + nta = isc_mem_get(view->mctx, sizeof(dns_nta_t)); + if (nta == NULL) + return (ISC_R_NOMEMORY); + + nta->ntatable = ntatable; + nta->expiry = 0; + nta->timer = NULL; + nta->fetch = NULL; + dns_rdataset_init(&nta->rdataset); + dns_rdataset_init(&nta->sigrdataset); + + result = isc_refcount_init(&nta->refcount, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(view->mctx, nta, sizeof(dns_nta_t)); + return (result); + } + + dns_fixedname_init(&nta->fn); + nta->name = dns_fixedname_name(&nta->fn); + dns_name_copy(name, nta->name, NULL); + + nta->magic = NTA_MAGIC; + + *target = nta; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, + isc_boolean_t force, isc_stdtime_t now, + isc_uint32_t lifetime) +{ + isc_result_t result; + dns_nta_t *nta = NULL; + dns_rbtnode_t *node; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + + view = ntatable->view; + + result = nta_create(ntatable, name, &nta); + if (result != ISC_R_SUCCESS) + return (result); + + nta->expiry = now + lifetime; + nta->forced = force; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + node = NULL; + result = dns_rbt_addnode(ntatable->table, name, &node); + if (result == ISC_R_SUCCESS) { + if (!force) + (void)settimer(ntatable, nta, lifetime); + node->data = nta; + nta = NULL; + } else if (result == ISC_R_EXISTS) { + dns_nta_t *n = node->data; + if (n == NULL) { + if (!force) + (void)settimer(ntatable, nta, lifetime); + node->data = nta; + nta = NULL; + } else { + n->expiry = nta->expiry; + nta_detach(view->mctx, &nta); + } + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (nta != NULL) + nta_detach(view->mctx, &nta); + + return (result); +} + +/* + * Caller must hold a write lock on rwlock. + */ +static isc_result_t +delete(dns_ntatable_t *ntatable, dns_name_t *name) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(name != NULL); + + result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (node->data != NULL) + result = dns_rbt_deletenode(ntatable->table, + node, ISC_FALSE); + else + result = ISC_R_NOTFOUND; + } else if (result == DNS_R_PARTIALMATCH) + result = ISC_R_NOTFOUND; + + return (result); +} + +isc_result_t +dns_ntatable_delete(dns_ntatable_t *ntatable, dns_name_t *name) { + isc_result_t result; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + result = delete(ntatable, name); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_boolean_t +dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, + dns_name_t *name, dns_name_t *anchor) +{ + isc_result_t result; + dns_fixedname_t fn; + dns_rbtnode_t *node; + dns_name_t *foundname; + dns_nta_t *nta = NULL; + isc_boolean_t answer = ISC_FALSE; + isc_rwlocktype_t locktype = isc_rwlocktype_read; + + REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable)); + REQUIRE(dns_name_isabsolute(name)); + + if (ntatable == NULL) + return (ISC_FALSE); + + dns_fixedname_init(&fn); + foundname = dns_fixedname_name(&fn); + + relock: + RWLOCK(&ntatable->rwlock, locktype); + again: + node = NULL; + result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == DNS_R_PARTIALMATCH) { + if (dns_name_issubdomain(foundname, anchor)) + result = ISC_R_SUCCESS; + } + if (result == ISC_R_SUCCESS) { + nta = (dns_nta_t *) node->data; + answer = ISC_TF(nta->expiry >= now); + } + + /* Deal with expired NTA */ + if (result == ISC_R_SUCCESS && !answer) { + char nb[DNS_NAME_FORMATSIZE]; + + if (locktype == isc_rwlocktype_read) { + RWUNLOCK(&ntatable->rwlock, locktype); + locktype = isc_rwlocktype_write; + goto relock; + } + + dns_name_format(foundname, nb, sizeof(nb)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting expired NTA at %s", nb); + + if (nta->timer != NULL) { + (void) isc_timer_reset(nta->timer, + isc_timertype_inactive, + NULL, NULL, ISC_TRUE); + isc_timer_detach(&nta->timer); + } + + result = delete(ntatable, foundname); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting NTA failed: %s", + isc_result_totext(result)); + } + goto again; + } + RWUNLOCK(&ntatable->rwlock, locktype); + + return (answer); +} + +isc_result_t +dns_ntatable_totext(dns_ntatable_t *ntatable, isc_buffer_t *buf) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_boolean_t first = ISC_TRUE; + isc_stdtime_t now; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, ntatable->view->mctx); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) + goto cleanup; + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *) node->data; + char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; + char obuf[DNS_NAME_FORMATSIZE + 200]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + dns_fixedname_init(&fn); + name = dns_fixedname_name(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + snprintf(obuf, sizeof(obuf), "%s%s: %s %s", + first ? "" : "\n", nbuf, + n->expiry < now ? "expired" : "expiry", + tbuf); + first = ISC_FALSE; + if (1+strlen(obuf) >= isc_buffer_availablelength(buf)) { + result = ISC_R_NOSPACE; + goto cleanup; + } else + isc_buffer_putstr(buf, obuf); + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + break; + } + } + + cleanup: + if (isc_buffer_availablelength(buf) != 0) + isc_buffer_putuint8(buf, 0); + + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + return (result); +} + +isc_result_t +dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_stdtime_t now; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, ntatable->view->mctx); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) + goto cleanup; + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *) node->data; + char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + dns_fixedname_init(&fn); + name = dns_fixedname_name(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + fprintf(fp, "%s: %s %s\n", nbuf, + n->expiry < now ? "expired" : "expiry", + tbuf); + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + break; + } + } + + cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + return (result); +} + +isc_result_t +dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_stdtime_t now; + isc_boolean_t written = ISC_FALSE; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, ntatable->view->mctx); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) + goto cleanup; + + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *) node->data; + if (now <= n->expiry) { + isc_buffer_t b; + char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + + dns_fixedname_init(&fn); + name = dns_fixedname_name(&fn); + dns_rbt_fullnamefromnode(node, name); + + isc_buffer_init(&b, nbuf, sizeof(nbuf) - 1); + result = dns_name_totext(name, ISC_FALSE, &b); + if (result != ISC_R_SUCCESS) + goto skip; + + /* Zero terminate. */ + isc_buffer_putuint8(&b, 0); + + isc_buffer_init(&b, tbuf, sizeof(tbuf)); + dns_time32_totext(n->expiry, &b); + + fprintf(fp, "%s %s %s\n", nbuf, + n->forced ? "forced" : "regular", + tbuf); + written = ISC_TRUE; + } + } + skip: + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + break; + } + } + + cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + + if (result != ISC_R_SUCCESS) + return (result); + else + return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND); +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 619646f1d5..79aea7b399 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1679,6 +1679,30 @@ add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { ISC_LIST_INITANDAPPEND(fctx->edns512, sa, link); } +static isc_result_t +issecuredomain(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, isc_boolean_t checknta, + isc_boolean_t *issecure) +{ + dns_name_t suffix; + unsigned int labels; + + /* + * For DS variants we need to check fom the parent domain, + * since there may be a negative trust anchor for the name, + * while the enclosing domain where the DS record lives is + * under a secure entry point. + */ + labels = dns_name_countlabels(name); + if (dns_rdatatype_atparent(type) && labels > 1) { + dns_name_init(&suffix, NULL); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } + + return (dns_view_issecuredomain(view, name, now, checknta, issecure)); +} + static isc_result_t resquery_send(resquery_t *query) { fetchctx_t *fctx; @@ -1768,14 +1792,23 @@ resquery_send(resquery_t *query) { fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD; /* - * Set CD if the client says don't validate or the question is - * under a secure entry point. + * Set CD if the client says not to validate, or if the + * question is under a secure entry point and this is a + * recursive/forward query -- unless the client said not to. */ - if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) + /* Do nothing */ + ; + else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; - } else if (res->view->enablevalidation) { - result = dns_view_issecuredomain(res->view, &fctx->name, - &secure_domain); + else if (res->view->enablevalidation && + ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) + { + isc_boolean_t checknta = + ISC_TF((query->options & DNS_FETCHOPT_NONTA) == 0); + result = issecuredomain(res->view, &fctx->name, + fctx->type, query->start.seconds, + checknta, &secure_domain); if (result != ISC_R_SUCCESS) secure_domain = ISC_FALSE; if (res->view->dlv != NULL) @@ -2402,8 +2435,8 @@ add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_result_t reason, isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, - "error (%s%s%s) resolving '%s/%s/%s': %s", - dns_result_totext(reason), spc, code, + "%s%s%s resolving '%s/%s/%s': %s", + code, spc, dns_result_totext(reason), namebuf, typebuf, classbuf, addrbuf); } @@ -4612,6 +4645,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, isc_task_t *task; isc_boolean_t fail; unsigned int valoptions = 0; + isc_boolean_t checknta = ISC_TRUE; /* * The appropriate bucket lock must be held. @@ -4628,18 +4662,26 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, /* * Is DNSSEC validation required for this name? */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = ISC_FALSE; + } + if (res->view->enablevalidation) { - result = dns_view_issecuredomain(res->view, name, - &secure_domain); + result = issecuredomain(res->view, name, fctx->type, + now, checknta, &secure_domain); if (result != ISC_R_SUCCESS) return (result); if (!secure_domain && res->view->dlv != NULL) { - valoptions = DNS_VALIDATOR_DLV; + valoptions |= DNS_VALIDATOR_DLV; secure_domain = ISC_TRUE; } } + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) + valoptions |= DNS_VALIDATOR_NOCDFLAG; + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) need_validation = ISC_FALSE; else @@ -5110,6 +5152,7 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_fetchevent_t *event; isc_uint32_t ttl; unsigned int valoptions = 0; + isc_boolean_t checknta = ISC_TRUE; FCTXTRACE("ncache_message"); @@ -5132,18 +5175,26 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, /* * Is DNSSEC validation required for this name? */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = ISC_FALSE; + } + if (fctx->res->view->enablevalidation) { - result = dns_view_issecuredomain(res->view, name, - &secure_domain); + result = issecuredomain(res->view, name, fctx->type, + now, checknta, &secure_domain); if (result != ISC_R_SUCCESS) return (result); if (!secure_domain && res->view->dlv != NULL) { - valoptions = DNS_VALIDATOR_DLV; + valoptions |= DNS_VALIDATOR_DLV; secure_domain = ISC_TRUE; } } + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) + valoptions |= DNS_VALIDATOR_NOCDFLAG; + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) need_validation = ISC_FALSE; else @@ -6545,7 +6596,8 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) { FCTXTRACE("continuing to look for parent's NS records"); result = dns_resolver_createfetch(fctx->res, &fctx->nsname, dns_rdatatype_ns, domain, - nsrdataset, NULL, 0, task, + nsrdataset, NULL, + fctx->options, task, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); @@ -6654,43 +6706,6 @@ log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, return; } -static void -log_packet(dns_message_t *message, int level, isc_mem_t *mctx) { - isc_buffer_t buffer; - char *buf = NULL; - int len = 1024; - isc_result_t result; - - if (! isc_log_wouldlog(dns_lctx, level)) - return; - - /* - * Note that these are multiline debug messages. We want a newline - * to appear in the log after each message. - */ - - do { - buf = isc_mem_get(mctx, len); - if (buf == NULL) - break; - isc_buffer_init(&buffer, buf, len); - result = dns_message_totext(message, &dns_master_style_debug, - 0, &buffer); - if (result == ISC_R_NOSPACE) { - isc_mem_put(mctx, buf, len); - len += 1024; - } else if (result == ISC_R_SUCCESS) - isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, level, - "received packet:\n%.*s", - (int)isc_buffer_usedlength(&buffer), - buf); - } while (result == ISC_R_NOSPACE); - - if (buf != NULL) - isc_mem_put(mctx, buf, len); -} - static isc_boolean_t iscname(fetchctx_t *fctx) { isc_result_t result; @@ -6746,7 +6761,7 @@ process_opt(resquery_t *query, dns_rdataset_t *opt) { case DNS_OPT_NSID: if (query->options & DNS_FETCHOPT_WANTNSID) log_nsid(&optbuf, optlen, query, - ISC_LOG_INFO, + ISC_LOG_DEBUG(3), query->fctx->res->mctx); isc_buffer_forward(&optbuf, optlen); break; @@ -6948,14 +6963,18 @@ resquery_response(isc_task_t *task, isc_event_t *event) { /* * Log the incoming packet. */ - log_packet(message, ISC_LOG_DEBUG(10), fctx->res->mctx); + dns_message_logfmtpacket(message, "received packet:\n", + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS, + &dns_master_style_comment, + ISC_LOG_DEBUG(10), + fctx->res->mctx); if (message->rdclass != fctx->res->rdclass) { resend = ISC_TRUE; FCTXTRACE("bad class"); goto done; } - /* * Process receive opt record. */ @@ -7480,7 +7499,8 @@ resquery_response(isc_task_t *task, isc_event_t *event) { result = dns_resolver_createfetch(fctx->res, &fctx->nsname, dns_rdatatype_ns, - NULL, NULL, NULL, 0, task, + NULL, NULL, NULL, + fctx->options, task, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); @@ -7786,6 +7806,7 @@ dns_resolver_create(dns_view_t *view, result = isc_task_create(taskmgr, 0, &task); if (result != ISC_R_SUCCESS) goto cleanup_primelock; + isc_task_setname(task, "resolver_task", NULL); result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, task, spillattimer_countdown, res, @@ -8126,7 +8147,7 @@ log_fetch(dns_name_t *name, dns_rdatatype_t type) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level, - "createfetch: %s %s", namebuf, typebuf); + "fetch: %s/%s", namebuf, typebuf); } isc_result_t diff --git a/lib/dns/result.c b/lib/dns/result.c index 39879532d4..4834cb227c 100644 --- a/lib/dns/result.c +++ b/lib/dns/result.c @@ -162,7 +162,8 @@ static const char *text[DNS_R_NRESULTS] = { "broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */ "expired", /*%< 107 DNS_R_EXPIRED */ "not dynamic", /*%< 108 DNS_R_NOTDYNAMIC */ - "bad EUI" /*%< 109 DNS_R_BADEUI */ + "bad EUI", /*%< 109 DNS_R_BADEUI */ + "covered by negative trust anchor", /*%< 110 DNS_R_NTACOVERED */ }; static const char *rcode_text[DNS_R_NRCODERESULTS] = { diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in index 023e60cead..4bd189f5c7 100644 --- a/lib/dns/tests/Makefile.in +++ b/lib/dns/tests/Makefile.in @@ -41,14 +41,15 @@ OBJS = dnstest.@O@ SRCS = dnstest.c gost_test.c master_test.c dbiterator_test.c time_test.c \ private_test.c tsig_test.c update_test.c zonemgr_test.c zt_test.c \ dbdiff_test.c geoip_test.c dispatch_test.c nsec3_test.c \ - rdataset_test.c rdata_test.c + rdataset_test.c rdata_test.c keytable_test.c SUBDIRS = TARGETS = gost_test@EXEEXT@ master_test@EXEEXT@ dbiterator_test@EXEEXT@ time_test@EXEEXT@ \ private_test@EXEEXT@ tsig_test@EXEEXT@ update_test@EXEEXT@ zonemgr_test@EXEEXT@ \ zt_test@EXEEXT@ dbversion_test@EXEEXT@ dbdiff_test@EXEEXT@ geoip_test@EXEEXT@ \ dispatch_test@EXEEXT@ nsec3_test@EXEEXT@ \ - rdataset_test@EXEEXT@ rdata_test@EXEEXT@ + rdataset_test@EXEEXT@ rdata_test@EXEEXT@ \ + keytable_test@EXEEXT@ \ @BIND9_MAKE_RULES@ @@ -65,6 +66,11 @@ master_test@EXEEXT@: master_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} master_test.@O@ dnstest.@O@ ${DNSLIBS} \ ${ISCLIBS} ${LIBS} +keytable_test@EXEEXT@: keytable_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + keytable_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + time_test@EXEEXT@: time_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ time_test.@O@ dnstest.@O@ ${DNSLIBS} \ diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c index f06f526eb3..4f96a9c6b7 100644 --- a/lib/dns/tests/dnstest.c +++ b/lib/dns/tests/dnstest.c @@ -43,8 +43,6 @@ #include #include -#include - #include "dnstest.h" isc_mem_t *mctx = NULL; @@ -187,6 +185,26 @@ dns_test_end() { isc_mem_destroy(&mctx); } +/* + * Create a view. + */ +isc_result_t +dns_test_makeview(const char *name, dns_view_t **viewp) +{ + isc_result_t result; + dns_view_t *view = NULL; + + CHECK(dns_view_create(mctx, dns_rdataclass_in, name, &view)); + *viewp = view; + + return (ISC_R_SUCCESS); + + cleanup: + if (view != NULL) + dns_view_detach(&view); + return (result); +} + /* * Create a zone with origin 'name', return a pointer to the zone object in * 'zonep'. If 'view' is set, add the zone to that view; otherwise, create diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h index 863ffb6aa5..cd430477b3 100644 --- a/lib/dns/tests/dnstest.h +++ b/lib/dns/tests/dnstest.h @@ -57,6 +57,9 @@ dns_test_begin(FILE *logfile, isc_boolean_t create_managers); void dns_test_end(void); +isc_result_t +dns_test_makeview(const char *name, dns_view_t **viewp); + isc_result_t dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, isc_boolean_t keepview); diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c new file mode 100644 index 0000000000..c41e4081df --- /dev/null +++ b/lib/dns/tests/keytable_test.c @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2014 Infoblox Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +dns_keytable_t *keytable = NULL; +dns_ntatable_t *ntatable = NULL; + +static const char *keystr1 = "BQEAAAABok+vaUC9neRv8yeT/FEGgN7svR8s7VBUVSBd8NsAiV8AlaAg O5FHar3JQd95i/puZos6Vi6at9/JBbN8qVmO2AuiXxVqfxMKxIcy+LEB 0Vw4NaSJ3N3uaVREso6aTSs98H/25MjcwLOr7SFfXA7bGhZatLtYY/xu kp6Km5hMfkE="; +static const dns_keytag_t keytag1 = 30591; + +static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/inaJpYv7vH XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/ItBfh/hL2lm Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi79532Qeklxh x8pXWdeAaRU="; + +static dns_view_t *view = NULL; + +/* + * Test utilities. In general, these assume input parameters are valid + * (checking with ATF_REQUIRE_EQ, thus aborting if not) and unlikely run time + * errors (such as memory allocation failure) won't happen. This helps keep + * the test code concise. + */ + +/* + * Utility to convert C-string to dns_name_t. Return a pointer to + * static data, and so is not thread safe. + */ +static dns_name_t * +str2name(const char *namestr) { + static dns_fixedname_t fname; + static dns_name_t *name; + static isc_buffer_t namebuf; + void *deconst_namestr; + + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + DE_CONST(namestr, deconst_namestr); /* OK, since we don't modify it */ + isc_buffer_init(&namebuf, deconst_namestr, strlen(deconst_namestr)); + isc_buffer_add(&namebuf, strlen(namestr)); + ATF_REQUIRE_EQ(dns_name_fromtext(name, &namebuf, dns_rootname, 0, + NULL), ISC_R_SUCCESS); + + return (name); +} + +static void +create_key(isc_uint16_t flags, isc_uint8_t proto, isc_uint8_t alg, + const char *keynamestr, const char *keystr, dst_key_t **target) +{ + dns_rdata_dnskey_t keystruct; + unsigned char keydata[4096]; + isc_buffer_t keydatabuf; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_region_t r; + const dns_rdataclass_t rdclass = dns_rdataclass_in; /* for brevity */ + + keystruct.common.rdclass = rdclass; + keystruct.common.rdtype = dns_rdatatype_dnskey; + keystruct.mctx = NULL; + ISC_LINK_INIT(&keystruct.common, link); + keystruct.flags = flags; + keystruct.protocol = proto; + keystruct.algorithm = alg; + + isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + ATF_REQUIRE_EQ(isc_base64_decodestring(keystr, &keydatabuf), + ISC_R_SUCCESS); + isc_buffer_usedregion(&keydatabuf, &r); + keystruct.datalen = r.length; + keystruct.data = r.base; + ATF_REQUIRE_EQ(dns_rdata_fromstruct(NULL, keystruct.common.rdclass, + keystruct.common.rdtype, + &keystruct, &rrdatabuf), + ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(dst_key_fromdns(str2name(keynamestr), rdclass, + &rrdatabuf, mctx, target), + ISC_R_SUCCESS); +} + +/* Common setup: create a keytable and ntatable to test with a few keys */ +static void +create_tables() { + isc_result_t result; + dst_key_t *key = NULL; + isc_stdtime_t now; + + result = dns_test_makeview("view", &view); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(dns_keytable_create(mctx, &keytable), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_ntatable_create(view, taskmgr, timermgr, + &ntatable), ISC_R_SUCCESS); + + /* Add a normal key */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), + ISC_R_SUCCESS); + + /* Add a null key */ + ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, + str2name("null.example")), + ISC_R_SUCCESS); + + /* Add a negative trust anchor, duration 1 hour */ + isc_stdtime_get(&now); + ATF_REQUIRE_EQ(dns_ntatable_add(ntatable, + str2name("insecure.example"), + ISC_FALSE, now, 3600), + ISC_R_SUCCESS); +} + +static void +destroy_tables() { + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + if (keytable != NULL) + dns_keytable_detach(&keytable); + + dns_view_detach(&view); +} + +/* + * Individual unit tests + */ + +ATF_TC(add); +ATF_TC_HEAD(add, tc) { + atf_tc_set_md_var(tc, "descr", "add keys to the keytable"); +} +ATF_TC_BODY(add, tc) { + dst_key_t *key = NULL; + dns_keynode_t *keynode = NULL; + dns_keynode_t *next_keynode = NULL; + dns_keynode_t *null_keynode = NULL; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* + * Get the keynode for the example.com key. There's no other key for + * the name, so nextkeynode() should return NOTFOUND. + */ + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + + /* + * Try to add the same key. This should have no effect, so + * nextkeynode() should still return NOTFOUND. + */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + + /* Add another key (different keydata) */ + dns_keytable_detachkeynode(keytable, &keynode); + create_key(257, 3, 5, "example.com", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &next_keynode); + + /* + * Add a normal key to a name that has a null key. The null key node + * will be updated with the normal key. + */ + dns_keytable_detachkeynode(keytable, &keynode); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), ISC_R_SUCCESS); + create_key(257, 3, 5, "null.example", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, ISC_FALSE, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(keynode, null_keynode); /* should be the same node */ + ATF_REQUIRE(dns_keynode_key(keynode) != NULL); /* now have a key */ + dns_keytable_detachkeynode(keytable, &null_keynode); + + /* + * Try to add a null key to a name that already has a key. It's + * effectively no-op, so the same key node is still there, with no + * no next node. + * (Note: this and above checks confirm that if a name has a null key + * that's the only key for the name). + */ + ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, + str2name("null.example")), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(keynode, null_keynode); + ATF_REQUIRE(dns_keynode_key(keynode) != NULL); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + dns_keytable_detachkeynode(keytable, &null_keynode); + + dns_keytable_detachkeynode(keytable, &keynode); + destroy_tables(); + dns_test_end(); +} + +ATF_TC(delete); +ATF_TC_HEAD(delete, tc) { + atf_tc_set_md_var(tc, "descr", "delete keys from the keytable"); +} +ATF_TC_BODY(delete, tc) { + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* dns_keytable_delete requires exact match */ + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.org")), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("s.example.com")), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), + ISC_R_SUCCESS); + + /* works also for nodes with a null key */ + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("null.example")), + ISC_R_SUCCESS); + + /* or a negative trust anchor */ + ATF_REQUIRE_EQ(dns_ntatable_delete(ntatable, + str2name("insecure.example")), + ISC_R_SUCCESS); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(deletekeynode); +ATF_TC_HEAD(deletekeynode, tc) { + atf_tc_set_md_var(tc, "descr", "delete key nodes from the keytable"); +} +ATF_TC_BODY(deletekeynode, tc) { + dst_key_t *key = NULL; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* key name doesn't match */ + create_key(257, 3, 5, "example.org", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* subdomain match is the same as no match */ + create_key(257, 3, 5, "sub.example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* name matches but key doesn't match (resulting in PARTIALMATCH) */ + create_key(257, 3, 5, "example.com", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + DNS_R_PARTIALMATCH); + dst_key_free(&key); + + /* + * exact match. after deleting the node the internal rbt node will be + * empty, and any delete or deletekeynode attempt should result in + * NOTFOUND. + */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* + * The only null key node for a name can be deleted as long as the + * passed key name matches. + */ + create_key(257, 3, 5, "null.example", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + dst_key_free(&key); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(find); +ATF_TC_HEAD(find, tc) { + atf_tc_set_md_var(tc, "descr", "check find-variant operations"); +} +ATF_TC_BODY(find, tc) { + dns_keynode_t *keynode = NULL; + dns_fixedname_t fname; + dns_name_t *name; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* + * dns_keytable_find() requires exact name match. It matches node + * that has a null key, too. But it doesn't match a negative trust + * anchor. + */ + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.org"), + &keynode), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("sub.example.com"), + &keynode), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keynode_key(keynode), NULL); + dns_keytable_detachkeynode(keytable, &keynode); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("insecure.example"), + &keynode), ISC_R_NOTFOUND); + + /* + * dns_keytable_finddeepestmatch() allows partial match. Also match + * nodes with a null key or a negative trust anchor. + */ + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("example.com"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), ISC_TRUE); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("s.example.com"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), ISC_TRUE); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("example.org"), + name), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("null.example"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("null.example")), + ISC_TRUE); + + /* + * dns_keytable_findkeynode() requires exact name, algorithm, keytag + * match. If algorithm or keytag doesn't match, should result in + * PARTIALMATCH. Same for a node with a null key or a negative trust + * anchor. + */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.org"), + 5, keytag1, &keynode), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("sub.example.com"), + 5, keytag1, &keynode), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 4, keytag1, &keynode), + DNS_R_PARTIALMATCH); /* different algorithm */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 5, keytag1 + 1, &keynode), + DNS_R_PARTIALMATCH); /* different keytag */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("null.example"), + 5, 0, &keynode), + DNS_R_PARTIALMATCH); /* null key */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 5, keytag1, &keynode), + ISC_R_SUCCESS); /* complete match */ + dns_keytable_detachkeynode(keytable, &keynode); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(issecuredomain); +ATF_TC_HEAD(issecuredomain, tc) { + atf_tc_set_md_var(tc, "descr", "check issecuredomain()"); +} +ATF_TC_BODY(issecuredomain, tc) { + isc_boolean_t issecure; + const char **n; + const char *names[] = {"example.com", "sub.example.com", + "null.example", "sub.null.example", NULL}; + + UNUSED(tc); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* + * Domains that are an exact or partial match of a key name are + * considered secure. It's the case even if the key is null + * (validation will then fail, but that's actually the intended effect + * of installing a null key). + */ + for (n = names; *n != NULL; n++) { + ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, + str2name(*n), + NULL, + &issecure), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(issecure, ISC_TRUE); + } + + /* + * Domains that are an exact or partial match of a negative trust + * anchor are considered insecure. + */ + ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, + str2name("insecure.example"), + NULL, + &issecure), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(issecure, ISC_FALSE); + ATF_REQUIRE_EQ(dns_keytable_issecuredomain( + keytable, str2name("sub.insecure.example"), + NULL, &issecure), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(issecure, ISC_FALSE); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(dump); +ATF_TC_HEAD(dump, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_keytable_dump()"); +} +ATF_TC_BODY(dump, tc) { + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); + create_tables(); + + /* + * Right now, we only confirm the dump attempt doesn't cause disruption + * (so we don't check the dump content). + */ + ATF_REQUIRE_EQ(dns_keytable_dump(keytable, stdout), ISC_R_SUCCESS); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(nta); +ATF_TC_HEAD(nta, tc) { + atf_tc_set_md_var(tc, "descr", "check negative trust anchors"); +} +ATF_TC_BODY(nta, tc) { + isc_result_t result; + dst_key_t *key = NULL; + isc_boolean_t issecure, covered; + dns_view_t *view = NULL; + isc_stdtime_t now; + + UNUSED(tc); + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makeview("view", &view); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &view->task); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_initsecroots(view, mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_view_getsecroots(view, &keytable); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_initntatable(view, taskmgr, timermgr); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_view_getntatable(view, &ntatable); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + create_key(257, 3, 5, "example", keystr1, &key); + result = dns_keytable_add(keytable, ISC_FALSE, &key); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_stdtime_get(&now); + result = dns_ntatable_add(ntatable, + str2name("insecure.example"), + ISC_FALSE, now, 1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Should be secure */ + result = dns_view_issecuredomain(view, + str2name("test.secure.example"), + now, ISC_TRUE, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Should not be secure */ + result = dns_view_issecuredomain(view, + str2name("test.insecure.example"), + now, ISC_TRUE, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(!issecure); + + /* NTA covered */ + covered = dns_view_ntacovers(view, now, str2name("insecure.example"), + dns_rootname); + ATF_CHECK(covered); + + /* Not NTA covered */ + covered = dns_view_ntacovers(view, now, str2name("secure.example"), + dns_rootname); + ATF_CHECK(!covered); + + /* As of now + 2, the NTA should be clear */ + result = dns_view_issecuredomain(view, + str2name("test.insecure.example"), + now + 2, ISC_TRUE, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Now check deletion */ + result = dns_ntatable_add(ntatable, str2name("new.example"), + ISC_FALSE, now, 3600); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(view, str2name("test.new.example"), + now, ISC_TRUE, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(!issecure); + + result = dns_ntatable_delete(ntatable, str2name("new.example")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(view, str2name("test.new.example"), + now, ISC_TRUE, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Clean up */ + dns_ntatable_detach(&ntatable); + dns_keytable_detach(&keytable); + dns_view_detach(&view); + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, add); + ATF_TP_ADD_TC(tp, delete); + ATF_TP_ADD_TC(tp, deletekeynode); + ATF_TP_ADD_TC(tp, find); + ATF_TP_ADD_TC(tp, issecuredomain); + ATF_TP_ADD_TC(tp, dump); + ATF_TP_ADD_TC(tp, nta); + + return (atf_no_error()); +} diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 0c0f7b0c0f..6a9e101803 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -15,8 +15,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - #include #include @@ -1132,6 +1130,8 @@ static inline isc_result_t create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, isc_taskaction_t callback, const char *caller) { + unsigned int fopts = 0; + if (dns_rdataset_isassociated(&val->frdataset)) dns_rdataset_disassociate(&val->frdataset); if (dns_rdataset_isassociated(&val->fsigrdataset)) @@ -1143,9 +1143,14 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, return (DNS_R_NOVALIDSIG); } + if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) + fopts |= DNS_FETCHOPT_NOCDFLAG; + if ((val->options & DNS_VALIDATOR_NONTA) != 0) + fopts |= DNS_FETCHOPT_NONTA; + validator_logcreate(val, name, type, caller, "fetch"); return (dns_resolver_createfetch(val->view->resolver, name, type, - NULL, NULL, NULL, 0, + NULL, NULL, NULL, fopts, val->event->ev_sender, callback, val, &val->frdataset, @@ -1162,6 +1167,7 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, isc_taskaction_t action, const char *caller) { isc_result_t result; + unsigned int vopts = 0; if (check_deadlock(val, name, type, rdataset, sigrdataset)) { validator_log(val, ISC_LOG_DEBUG(3), @@ -1169,9 +1175,12 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, return (DNS_R_NOVALIDSIG); } + /* OK to clear other options, but preserve NOCDFLAG */ + vopts |= (val->options & DNS_VALIDATOR_NOCDFLAG); + validator_logcreate(val, name, type, caller, "validator"); result = dns_validator_create(val->view, name, type, - rdataset, sigrdataset, NULL, 0, + rdataset, sigrdataset, NULL, vopts, val->task, action, val, &val->subvalidator); if (result == ISC_R_SUCCESS) { @@ -1550,7 +1559,7 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, */ static isc_result_t validate(dns_validator_t *val, isc_boolean_t resume) { - isc_result_t result; + isc_result_t result, vresult = DNS_R_NOVALIDSIG; dns_validatorevent_t *event; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -1615,9 +1624,9 @@ validate(dns_validator_t *val, isc_boolean_t resume) { } do { - result = verify(val, val->key, &rdata, + vresult = verify(val, val->key, &rdata, val->siginfo->keyid); - if (result == ISC_R_SUCCESS) + if (vresult == ISC_R_SUCCESS) break; if (val->keynode != NULL) { dns_keynode_t *nextnode = NULL; @@ -1641,16 +1650,13 @@ validate(dns_validator_t *val, isc_boolean_t resume) { break; } } while (1); - if (result != ISC_R_SUCCESS) + if (vresult != ISC_R_SUCCESS) validator_log(val, ISC_LOG_DEBUG(3), "failed to verify rdataset"); else { - isc_stdtime_t now; - - isc_stdtime_get(&now); dns_rdataset_trimttl(event->rdataset, event->sigrdataset, - val->siginfo, now, + val->siginfo, val->start, val->view->acceptexpired); } @@ -1675,12 +1681,12 @@ validate(dns_validator_t *val, isc_boolean_t resume) { validator_log(val, ISC_LOG_DEBUG(3), "looking for noqname proof"); return (nsecvalidate(val, ISC_FALSE)); - } else if (result == ISC_R_SUCCESS) { + } else if (vresult == ISC_R_SUCCESS) { marksecure(event); validator_log(val, ISC_LOG_DEBUG(3), "marking as secure, " "noqname proof not needed"); - return (result); + return (ISC_R_SUCCESS); } else { validator_log(val, ISC_LOG_DEBUG(3), "verify failure: %s", @@ -1696,7 +1702,7 @@ validate(dns_validator_t *val, isc_boolean_t resume) { } validator_log(val, ISC_LOG_INFO, "no valid signature found"); - return (DNS_R_NOVALIDSIG); + return (vresult); } /*% @@ -3069,6 +3075,11 @@ startfinddlvsep(dns_validator_t *val, dns_name_t *unsecure) { markanswer(val, "startfinddlvsep (1)"); return (ISC_R_SUCCESS); } + if (result == DNS_R_NTACOVERED) { + validator_log(val, ISC_LOG_DEBUG(3), "DLV covered by NTA"); + validator_done(val, ISC_R_SUCCESS); + return (ISC_R_SUCCESS); + } if (result != ISC_R_SUCCESS) { validator_log(val, ISC_LOG_DEBUG(3), "DLV lookup: %s", dns_result_totext(result)); @@ -3158,6 +3169,10 @@ finddlvsep(dns_validator_t *val, isc_boolean_t resume) { return (DNS_R_NOVALIDSIG); } + if (((val->options & DNS_VALIDATOR_NONTA) == 0) && + dns_view_ntacovers(val->view, val->start, dlvname, val->view->dlv)) + return (DNS_R_NTACOVERED); + while (dns_name_countlabels(dlvname) >= dns_name_countlabels(val->view->dlv) + val->dlvlabels) { dns_name_format(dlvname, namebuf, sizeof(namebuf)); @@ -3774,6 +3789,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_fixedname_init(&val->wild); dns_fixedname_init(&val->nearest); dns_fixedname_init(&val->closest); + isc_stdtime_get(&val->start); ISC_LINK_INIT(val, link); val->magic = VALIDATOR_MAGIC; @@ -3889,7 +3905,7 @@ dns_validator_destroy(dns_validator_t **validatorp) { LOCK(&val->lock); val->attributes |= VALATTR_SHUTDOWN; - validator_log(val, ISC_LOG_DEBUG(3), "dns_validator_destroy"); + validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy"); want_destroy = exit_check(val); @@ -3922,8 +3938,8 @@ validator_logv(dns_validator_t *val, isc_logcategory_t *category, dns_rdatatype_format(val->event->type, typebuf, sizeof(typebuf)); isc_log_write(dns_lctx, category, module, level, - "%.*svalidating @%p: %s %s: %s", depth, spaces, - val, namebuf, typebuf, msgbuf); + "%.*svalidating %s/%s: %s", depth, spaces, + namebuf, typebuf, msgbuf); } else { isc_log_write(dns_lctx, category, module, level, "%.*svalidator @%p: %s", depth, spaces, diff --git a/lib/dns/view.c b/lib/dns/view.c index 35900b3f0b..01b82e6ff6 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -57,10 +59,16 @@ #include #include #include +#include #include #include #include +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) goto cleanup; \ + } while (0) + #define RESSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_RESSHUTDOWN) != 0) #define ADBSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_ADBSHUTDOWN) != 0) #define REQSHUTDOWN(v) (((v)->attributes & DNS_VIEWATTR_REQSHUTDOWN) != 0) @@ -77,6 +85,8 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, { dns_view_t *view; isc_result_t result; + const char nta[] = ".nta"; + char buffer[ISC_SHA256_DIGESTSTRINGLENGTH + sizeof(nta)]; /* * Create a view. @@ -89,6 +99,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, if (view == NULL) return (ISC_R_NOMEMORY); + view->nta_file = NULL; view->mctx = NULL; isc_mem_attach(mctx, &view->mctx); view->name = isc_mem_strdup(mctx, name); @@ -96,6 +107,16 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, result = ISC_R_NOMEMORY; goto cleanup_view; } + + isc_sha256_data((void *)view->name, strlen(view->name), buffer); + /* Truncate the hash at 16 chars; full length is overkill */ + isc_string_printf(buffer + 16, sizeof(nta), "%s", nta); + view->nta_file = isc_mem_strdup(mctx, buffer); + if (view->nta_file == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_name; + } + result = isc_mutex_init(&view->lock); if (result != ISC_R_SUCCESS) goto cleanup_name; @@ -112,6 +133,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, } #endif view->secroots_priv = NULL; + view->ntatable_priv = NULL; view->fwdtable = NULL; result = dns_fwdtable_create(mctx, &view->fwdtable); if (result != ISC_R_SUCCESS) { @@ -189,6 +211,8 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->provideixfr = ISC_TRUE; view->maxcachettl = 7 * 24 * 3600; view->maxncachettl = 3 * 3600; + view->nta_lifetime = 0; + view->nta_recheck = 0; view->dstport = 53; view->preferred_glue = 0; view->flush = ISC_FALSE; @@ -263,6 +287,9 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, #endif DESTROYLOCK(&view->lock); + if (view->nta_file != NULL) + isc_mem_free(mctx, view->nta_file); + cleanup_name: isc_mem_free(mctx, view->name); @@ -437,6 +464,8 @@ destroy(dns_view_t *view) { if (view->secroots_priv != NULL) dns_keytable_detach(&view->secroots_priv); #ifdef BIND9 + if (view->ntatable_priv != NULL) + dns_ntatable_detach(&view->ntatable_priv); for (dns64 = ISC_LIST_HEAD(view->dns64); dns64 != NULL; dns64 = ISC_LIST_HEAD(view->dns64)) { @@ -453,6 +482,7 @@ destroy(dns_view_t *view) { dns_aclenv_destroy(&view->aclenv); DESTROYLOCK(&view->lock); isc_refcount_destroy(&view->references); + isc_mem_free(view->mctx, view->nta_file); isc_mem_free(view->mctx, view->name); isc_mem_putanddetach(&view->mctx, view, sizeof(*view)); } @@ -1786,6 +1816,27 @@ dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp) { dns_stats_attach(view->resquerystats, statsp); } +isc_result_t +dns_view_initntatable(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr) +{ + REQUIRE(DNS_VIEW_VALID(view)); + if (view->ntatable_priv != NULL) + dns_ntatable_detach(&view->ntatable_priv); + return (dns_ntatable_create(view, taskmgr, timermgr, + &view->ntatable_priv)); +} + +isc_result_t +dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ntp != NULL && *ntp == NULL); + if (view->ntatable_priv == NULL) + return (ISC_R_NOTFOUND); + dns_ntatable_attach(view->ntatable_priv, ntp); + return (ISC_R_SUCCESS); +} + isc_result_t dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx) { REQUIRE(DNS_VIEW_VALID(view)); @@ -1804,15 +1855,47 @@ dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp) { return (ISC_R_SUCCESS); } +isc_boolean_t +dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, + dns_name_t *name, dns_name_t *anchor) +{ + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->ntatable_priv == NULL) + return (ISC_FALSE); + + return (dns_ntatable_covered(view->ntatable_priv, now, name, anchor)); +} + isc_result_t dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, - isc_boolean_t *secure_domain) { + isc_stdtime_t now, isc_boolean_t checknta, + isc_boolean_t *secure_domain) +{ + isc_result_t result; + isc_boolean_t secure = ISC_FALSE; + dns_fixedname_t fn; + dns_name_t *anchor; + REQUIRE(DNS_VIEW_VALID(view)); if (view->secroots_priv == NULL) return (ISC_R_NOTFOUND); - return (dns_keytable_issecuredomain(view->secroots_priv, name, - secure_domain)); + + dns_fixedname_init(&fn); + anchor = dns_fixedname_name(&fn); + + result = dns_keytable_issecuredomain(view->secroots_priv, name, + anchor, &secure); + if (result != ISC_R_SUCCESS) + return (result); + + if (checknta && secure && view->ntatable_priv != NULL && + dns_ntatable_covered(view->ntatable_priv, now, name, anchor)) + secure = ISC_FALSE; + + *secure_domain = secure; + return (ISC_R_SUCCESS); } void @@ -1882,3 +1965,151 @@ dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx, UNUSED(cfg_destroy); #endif } + +isc_result_t +dns_view_saventa(dns_view_t *view) { +#ifdef BIND9 + isc_result_t result; + isc_boolean_t remove = ISC_FALSE; + dns_ntatable_t *ntatable = NULL; + FILE *fp = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->nta_lifetime == 0) + return (ISC_R_SUCCESS); + + /* Open NTA save file for overwrite. */ + CHECK(isc_stdio_open(view->nta_file, "w", &fp)); + + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + remove = ISC_TRUE; + result = ISC_R_SUCCESS; + goto cleanup; + } else + CHECK(result); + + result = dns_ntatable_save(ntatable, fp); + if (result == ISC_R_NOTFOUND) { + remove = ISC_TRUE; + result = ISC_R_SUCCESS; + } + + cleanup: + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + + if (fp != NULL) + isc_stdio_close(fp); + + /* Don't leave half-baked NTA save files lying around. */ + if (result != ISC_R_SUCCESS || remove) + (void) isc_file_remove(view->nta_file); + + return (result); +#else + REQUIRE(DNS_VIEW_VALID(view)); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +#define TSTR(t) ((t).value.as_textregion.base) +#define TLEN(t) ((t).value.as_textregion.length) + +isc_result_t +dns_view_loadnta(dns_view_t *view) { +#ifdef BIND9 + isc_result_t result; + dns_ntatable_t *ntatable = NULL; + isc_lex_t *lex = NULL; + isc_token_t token; + isc_stdtime_t now; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->nta_lifetime == 0) + return (ISC_R_SUCCESS); + + CHECK(isc_lex_create(view->mctx, 1025, &lex)); + CHECK(isc_lex_openfile(lex, view->nta_file)); + CHECK(dns_view_getntatable(view, &ntatable)); + isc_stdtime_get(&now); + + for (;;) { + int options = (ISC_LEXOPT_EOL | ISC_LEXOPT_EOF); + char *name, *type, *timestamp; + size_t len; + dns_fixedname_t fn; + dns_name_t *ntaname; + isc_buffer_t b; + isc_stdtime_t t; + isc_boolean_t forced; + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type == isc_tokentype_eof) + break; + else if (token.type != isc_tokentype_string) + CHECK(ISC_R_UNEXPECTEDTOKEN); + name = TSTR(token); + len = TLEN(token); + + if (strcmp(name, ".") == 0) + ntaname = dns_rootname; + else { + dns_fixedname_init(&fn); + ntaname = dns_fixedname_name(&fn); + + isc_buffer_init(&b, name, len); + isc_buffer_add(&b, len); + CHECK(dns_name_fromtext(ntaname, &b, dns_rootname, + 0, NULL)); + } + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_string) + CHECK(ISC_R_UNEXPECTEDTOKEN); + type = TSTR(token); + + if (strcmp(type, "regular") == 0) + forced = ISC_FALSE; + else if (strcmp(type, "forced") == 0) + forced = ISC_TRUE; + else + CHECK(ISC_R_UNEXPECTEDTOKEN); + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_string) + CHECK(ISC_R_UNEXPECTEDTOKEN); + timestamp = TSTR(token); + CHECK(dns_time32_fromtext(timestamp, &t)); + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_eol && + token.type != isc_tokentype_eof) + CHECK(ISC_R_UNEXPECTEDTOKEN); + + if (now <= t) { + if (t > (now + 604800)) + t = now + 604800; + + (void) dns_ntatable_add(ntatable, ntaname, + forced, 0, t); + } + }; + + cleanup: + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + + if (lex != NULL) { + isc_lex_close(lex); + isc_lex_destroy(&lex); + } + + return (result); +#else + REQUIRE(DNS_VIEW_VALID(view)); + return (ISC_R_NOTIMPLEMENTED); +#endif +} diff --git a/lib/export/dns/Makefile.in b/lib/export/dns/Makefile.in index 887acb9416..4719a5a809 100644 --- a/lib/export/dns/Makefile.in +++ b/lib/export/dns/Makefile.in @@ -59,7 +59,7 @@ DNSOBJS = acl.@O@ adb.@O@ byaddr.@O@ \ keytable.@O@ \ lib.@O@ log.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ - name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ \ + name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ peer.@O@ portlist.@O@ \ rbt.@O@ rbtdb.@O@ rcode.@O@ rdata.@O@ \ rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ @@ -89,7 +89,7 @@ DNSSRCS = acl.c adb.c byaddr.c \ keytable.c \ lib.c log.c \ master.c masterdump.c message.c \ - name.c ncache.c nsec.c nsec3.c \ + name.c ncache.c nsec.c nsec3.c nta.c \ peer.c portlist.c \ rbt.c rbtdb.c rcode.c rdata.c \ rdatalist.c rdataset.c rdatasetiter.c rdataslab.c \ diff --git a/lib/isc/include/isc/log.h b/lib/isc/include/isc/log.h index 741c532429..e609d913b7 100644 --- a/lib/isc/include/isc/log.h +++ b/lib/isc/include/isc/log.h @@ -67,8 +67,9 @@ #define ISC_LOG_PRINTLEVEL 0x0002 #define ISC_LOG_PRINTCATEGORY 0x0004 #define ISC_LOG_PRINTMODULE 0x0008 -#define ISC_LOG_PRINTTAG 0x0010 -#define ISC_LOG_PRINTALL 0x001F +#define ISC_LOG_PRINTTAG 0x0010 /* tag and ":" */ +#define ISC_LOG_PRINTPREFIX 0x0020 /* tag only, no colon */ +#define ISC_LOG_PRINTALL 0x003F #define ISC_LOG_DEBUGONLY 0x1000 #define ISC_LOG_OPENERR 0x8000 /* internal */ /*@}*/ @@ -167,6 +168,7 @@ LIBISC_EXTERNAL_DATA extern isc_logmodule_t isc_modules[]; #define ISC_LOGMODULE_INTERFACE (&isc_modules[2]) #define ISC_LOGMODULE_TIMER (&isc_modules[3]) #define ISC_LOGMODULE_FILE (&isc_modules[4]) +#define ISC_LOGMODULE_OTHER (&isc_modules[5]) ISC_LANG_BEGINDECLS diff --git a/lib/isc/log.c b/lib/isc/log.c index 35204cfb9a..65b78ea46d 100644 --- a/lib/isc/log.c +++ b/lib/isc/log.c @@ -205,6 +205,7 @@ LIBISC_EXTERNAL_DATA isc_logmodule_t isc_modules[] = { { "interface", 0 }, { "timer", 0 }, { "file", 0 }, + { "other", 0 }, { NULL, 0 } }; @@ -1413,7 +1414,7 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, const char *iformat; struct stat statbuf; isc_boolean_t matched = ISC_FALSE; - isc_boolean_t printtime, printtag; + isc_boolean_t printtime, printtag, printcolon; isc_boolean_t printcategory, printmodule, printlevel; isc_logconfig_t *lcfg; isc_logchannel_t *channel; @@ -1641,7 +1642,10 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, printtime = ISC_TF((channel->flags & ISC_LOG_PRINTTIME) != 0); - printtag = ISC_TF((channel->flags & ISC_LOG_PRINTTAG) + printtag = ISC_TF((channel->flags & + (ISC_LOG_PRINTTAG|ISC_LOG_PRINTPREFIX)) + != 0 && lcfg->tag != NULL); + printcolon = ISC_TF((channel->flags & ISC_LOG_PRINTTAG) != 0 && lcfg->tag != NULL); printcategory = ISC_TF((channel->flags & ISC_LOG_PRINTCATEGORY) != 0); @@ -1695,11 +1699,12 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, /* FALLTHROUGH */ case ISC_LOG_TOFILEDESC: - fprintf(FILE_STREAM(channel), "%s%s%s%s%s%s%s%s%s%s\n", + fprintf(FILE_STREAM(channel), + "%s%s%s%s%s%s%s%s%s%s\n", printtime ? time_string : "", printtime ? " " : "", printtag ? lcfg->tag : "", - printtag ? ": " : "", + printcolon ? ": " : "", printcategory ? category->name : "", printcategory ? ": " : "", printmodule ? (module != NULL ? module->name @@ -1742,11 +1747,12 @@ isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, printtime ? time_string : "", printtime ? " " : "", printtag ? lcfg->tag : "", - printtag ? ": " : "", + printcolon ? ": " : "", printcategory ? category->name : "", printcategory ? ": " : "", - printmodule ? (module != NULL ? module->name - : "no_module") + printmodule ? (module != NULL + ? module->name + : "no_module") : "", printmodule ? ": " : "", printlevel ? level_string : "", diff --git a/lib/isc/unix/app.c b/lib/isc/unix/app.c index d97d7c6bbc..3338b60173 100644 --- a/lib/isc/unix/app.c +++ b/lib/isc/unix/app.c @@ -232,7 +232,6 @@ ISC_APPFUNC_SCOPE isc_result_t isc__app_ctxstart(isc_appctx_t *ctx0) { isc__appctx_t *ctx = (isc__appctx_t *)ctx0; isc_result_t result; - REQUIRE(VALID_APPCTX(ctx)); /* diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 8773be8446..d9b1df158a 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -29,6 +29,8 @@ #include #include +#include + #include #include #include @@ -130,6 +132,7 @@ static cfg_type_t cfg_type_sizenodefault; static cfg_type_t cfg_type_sockaddr4wild; static cfg_type_t cfg_type_sockaddr6wild; static cfg_type_t cfg_type_statschannels; +static cfg_type_t cfg_type_ttlval; static cfg_type_t cfg_type_view; static cfg_type_t cfg_type_viewopts; static cfg_type_t cfg_type_zone; @@ -1441,6 +1444,8 @@ view_clauses[] = { { "max-udp-size", &cfg_type_uint32, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, { "minimal-responses", &cfg_type_boolean, 0 }, + { "nta-recheck", &cfg_type_ttlval, 0 }, + { "nta-lifetime", &cfg_type_ttlval, 0 }, { "preferred-glue", &cfg_type_astring, 0 }, { "provide-ixfr", &cfg_type_boolean, 0 }, /* @@ -3068,3 +3073,43 @@ static cfg_type_t cfg_type_masterselement = { "masters_element", parse_masterselement, NULL, doc_masterselement, NULL, NULL }; + +static isc_result_t +parse_ttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_uint32_t ttl; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + result = dns_ttl_fromtext(&pctx->token.value.as_textregion, &ttl); + if (result == ISC_R_RANGE ) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "TTL out of range "); + return (result); + } else if (result != ISC_R_SUCCESS) + goto cleanup; + + CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); + obj->value.uint32 = ttl; + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected integer and optional unit"); + return (result); +} + +/*% + * A TTL value (number + optional unit). + */ +static cfg_type_t cfg_type_ttlval = { + "ttlval", parse_ttlval, cfg_print_uint64, cfg_doc_terminal, + &cfg_rep_uint64, NULL +}; + diff --git a/make/includes.in b/make/includes.in index f2f1b3f451..42b19bcc39 100644 --- a/make/includes.in +++ b/make/includes.in @@ -37,6 +37,9 @@ ISCCFG_INCLUDES = @BIND9_ISCCFG_BUILDINCLUDE@ \ DNS_INCLUDES = @BIND9_DNS_BUILDINCLUDE@ \ -I${top_srcdir}/lib/dns/include +IRS_INCLUDES = @BIND9_IRS_BUILDINCLUDE@ \ + -I${top_srcdir}/lib/irs/include + LWRES_INCLUDES = @BIND9_LWRES_BUILDINCLUDE@ \ -I${top_srcdir}/lib/lwres/unix/include \ -I${top_srcdir}/lib/lwres/include diff --git a/make/rules.in b/make/rules.in index 37bc50d82f..bc6f92e9f3 100644 --- a/make/rules.in +++ b/make/rules.in @@ -48,9 +48,10 @@ top_builddir = @BIND9_TOP_BUILDDIR@ ### All ### ### Makefile may define: +### PREREQS ### TARGETS -all: subdirs ${TARGETS} testdirs +all: ${PREREQS} subdirs ${TARGETS} testdirs ### ### Subdirectories -- 2.14.4