From: Evan Hunt Date: Fri, 30 May 2014 05:22:53 +0000 (-0700) Subject: [master] rndc nta X-Git-Tag: v9.11.0a1~1588 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=0cfb24736841b3e98bb25853229a0efabab88bdd;p=thirdparty%2Fbind9.git [master] 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] --- diff --git a/CHANGES b/CHANGES index f5fa916cece..048da3a3c7e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +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] + 3866. [bug] Named could die on disk full in generate_session_key. [RT #36119] diff --git a/bin/named/control.c b/bin/named/control.c index f82535838b2..7f49eb3c890 100644 --- a/bin/named/control.c +++ b/bin/named/control.c @@ -213,6 +213,8 @@ ns_control_docommand(isccc_sexpr_t *message, isc_buffer_t *text) { result = ns_server_signing(ns_g_server, command, text); } else if (command_compare(command, NS_COMMAND_ZONESTATUS)) { result = ns_server_zonestatus(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 fd9359988a1..33a16193d94 100644 --- a/bin/named/include/named/control.h +++ b/bin/named/include/named/control.h @@ -67,6 +67,7 @@ #define NS_COMMAND_SYNC "sync" #define NS_COMMAND_SIGNING "signing" #define NS_COMMAND_ZONESTATUS "zonestatus" +#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 d215c1dd71b..fd6818dca3a 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -392,4 +392,11 @@ ns_server_signing(ns_server_t *server, char *args, isc_buffer_t *text); */ isc_result_t ns_server_zonestatus(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 789ca8a1ca8..679cf28fffc 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,7 @@ #include #include #include +#include #include #include #include @@ -824,6 +826,14 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, return (ISC_R_UNEXPECTED); } + result = dns_view_initntatable(view, mctx); + 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; @@ -9756,3 +9766,133 @@ ns_server_zonestatus(ns_server_t *server, char *args, isc_buffer_t *text) { dns_zone_detach(&zone); return (result); } + +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, *viewname; + isc_stdtime_t now, when; + isc_time_t t; + char tbuf[64]; + const char *msg = NULL; + dns_fixedname_t fn; + dns_name_t *ntaname; + dns_ttl_t ntattl; + isc_textregion_t tr; + + 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); + + /* Get the NTA name. */ + 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)); + } + + /* Get the NTA duration. */ + ptr = next_token(&args, " \t"); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + tr.base = ptr; + tr.length = strlen(ptr); + CHECK(dns_ttl_fromtext(&tr, &ntattl)); + + if (ntattl > 86400) { + msg = "NTA cannot exceed one day"; + CHECK(ISC_R_RANGE); + } + + /* Look for the view name. */ + viewname = next_token(&args, " \t"); + + /* Set up the NTA */ + isc_stdtime_get(&now); + when = now + ntattl; + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + 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 (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_ERROR, + "flush tree '%s' in cache view '%s': %s", + nametext, view->name, + isc_result_totext(result)); + + isc_time_set(&t, when, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + if (ntattl > 0) { + CHECK(dns_ntatable_add(ntatable, ntaname, when)); + + 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_ERROR, + "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_ERROR, + "removed NTA '%s' in view %s", + nametext, view->name); + } + + if (isc_buffer_availablelength(text) == 0) + return (ISC_R_NOSPACE); + + isc_buffer_putuint8(text, 0); + } + isc_task_endexclusive(server->task); + + if (msg != NULL && strlen(msg) < isc_buffer_availablelength(text)) + isc_buffer_putstr(text, msg); + cleanup: + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + return (result); +} diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index e53159d2b6a..7c28ac480f5 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -596,6 +596,38 @@ + + nta domain duration + + + Sets a DNSSEC negative trust anchor (NTA) + for , with a lifetime of + (up to a limit of one day). + + + 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, or until the server is + restarted (NTA's do not persist across restarts). + + + TTL-style suffixes can be used to specify + in seconds, minutes, or hours. + + + If the specified domain already has an NTA, its duration + will be updated to the new value. Setting + to zero will delete the NTA. + + + + tsig-list diff --git a/bin/tests/system/dnssec/setup.sh b/bin/tests/system/dnssec/setup.sh index 12a5f8503d9..23143836b2a 100644 --- a/bin/tests/system/dnssec/setup.sh +++ b/bin/tests/system/dnssec/setup.sh @@ -25,6 +25,7 @@ test -r $RANDFILE || $GENRANDOM 400 $RANDFILE cd ns1 && $SHELL 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 cd ../ns3 && cp -f siginterval1.conf siginterval.conf cd ../ns4 && cp -f named1.conf named.conf diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 9e81c519458..2961b750481 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1648,6 +1648,33 @@ 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 a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.2 || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.2 > /dev/null || ret=1 +# add negative trust anchors +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta bogus.example 15s 2>&1 | sed 's/^/I:ns4 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta secure.example 15s 2>&1 | sed 's/^/I:ns4 /' +# check behavior with NTA's in place +$DIG $DIGOPTS a.bogus.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 +$DIG $DIGOPTS a.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.4 || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.4 > /dev/null && ret=1 +echo "I: waiting for NTA expiration" +sleep 15 +# check correct behavior after expiry +$DIG $DIGOPTS b.bogus.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 +$DIG $DIGOPTS b.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.6 || ret=1 +grep "flags:[^;]* ad[^;]*;" dig.out.ns4.test$n.6 > /dev/null || ret=1 +n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index b0b691790da..a3fc6740e8d 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -10756,6 +10756,10 @@ rate-limit { 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. + diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index 1b83006f613..c687d877234 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -69,8 +69,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@ \ @@ -106,7 +106,8 @@ DNSSRCS = acache.c acl.c adb.c byaddr.c \ dlz.c dns64.c dnssec.c ds.c forward.c geoip.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 rrl.c rriterator.c \ diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index d02c367c071..4e662ca8af2 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -77,7 +77,8 @@ static dst_func_t *dst_t_func[DST_MAX_ALGS]; static isc_entropy_t *dst_entropy_pool = NULL; static unsigned int dst_entropy_flags = 0; -static isc_boolean_t dst_initialized = ISC_FALSE; + +isc_boolean_t dst_initialized = ISC_FALSE; void gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h index 3f4adaf6e39..c44fca8a40c 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 15f39767839..c8d0b59b895 100644 --- a/lib/dns/include/dns/log.h +++ b/lib/dns/include/dns/log.h @@ -78,6 +78,7 @@ 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_PACKETS (&dns_modules[29]) +#define DNS_LOGMODULE_NTA (&dns_modules[30]) ISC_LANG_BEGINDECLS diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h new file mode 100644 index 00000000000..7eeacb747fd --- /dev/null +++ b/lib/dns/include/dns/nta.h @@ -0,0 +1,206 @@ +/* + * 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 + +ISC_LANG_BEGINDECLS + +struct dns_ntatable { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_rwlock_t rwlock; + /* 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) + +struct dns_nta { + unsigned int magic; + isc_refcount_t refcount; + isc_stdtime_t expiry; +}; + +#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n') +#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC) + +isc_result_t +dns_ntatable_create(isc_mem_t *mctx, dns_ntatable_t **ntatablep); +/*%< + * Create an NTA table. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\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_uint32_t expiry); +/*%< + * Add a negative trust anchor to 'ntatable' for name 'name', + * which will expire at time 'expiry'. + * + * 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_dump(dns_ntatable_t *ntatable, FILE *fp); +/*%< + * Dump the NTA table on fp. + */ + +isc_result_t +dns_nta_create(isc_mem_t *mctx, dns_nta_t **target); +/*%< + * Allocate space for an NTA + */ +ISC_LANG_ENDDECLS + +#endif /* DNS_NTA_H */ diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h index 12aacf9ba78..137bfd85671 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 ecb3f04d15d..9da7f7d1565 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -99,6 +99,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 8f640421481..0abf1dce1f1 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -165,6 +165,7 @@ struct dns_validator { unsigned int depth; unsigned int authcount; unsigned int authfail; + isc_stdtime_t start; }; /*% diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 9beadbf64bb..3a0c3f8602e 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -98,10 +98,11 @@ struct dns_view { dns_db_t * hints; /* - * security roots. + * security roots and negative trust anchors. * internal use only; access via * dns_view_getsecroots() */ dns_keytable_t * secroots_priv; + dns_ntatable_t * ntatable_priv; isc_mutex_t lock; isc_boolean_t frozen; @@ -1074,17 +1075,49 @@ dns_view_iscacheshared(dns_view_t *view); *\li #ISC_FALSE otherwise. */ +isc_result_t +dns_view_initntatable(dns_view_t *view, isc_mem_t *mctx); +/*%< + * 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 @@ -1111,10 +1144,10 @@ 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 *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'. * * Requires: * \li 'view' is valid. @@ -1124,6 +1157,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); diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c index 56fefcd2c12..717e37636db 100644 --- a/lib/dns/keytable.c +++ b/lib/dns/keytable.c @@ -524,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? @@ -539,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) { diff --git a/lib/dns/log.c b/lib/dns/log.c index 56b616137e3..663d4203385 100644 --- a/lib/dns/log.c +++ b/lib/dns/log.c @@ -84,6 +84,7 @@ LIBDNS_EXTERNAL_DATA isc_logmodule_t dns_modules[] = { { "dns/dnssec", 0 }, { "dns/crypto", 0 }, { "dns/packets", 0 }, + { "dns/nta", 0 }, { NULL, 0 } }; diff --git a/lib/dns/nta.c b/lib/dns/nta.c new file mode 100644 index 00000000000..3cd7dfca30a --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,350 @@ +/* + * 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 + +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) { + isc_refcount_destroy(&nta->refcount); + 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(isc_mem_t *mctx, dns_ntatable_t **ntatablep) { + dns_ntatable_t *ntatable; + isc_result_t result; + + /* + * Create an NTA table. + */ + + REQUIRE(ntatablep != NULL && *ntatablep == NULL); + + ntatable = isc_mem_get(mctx, sizeof(*ntatable)); + if (ntatable == NULL) + return (ISC_R_NOMEMORY); + + ntatable->table = NULL; + result = dns_rbt_create(mctx, free_nta, mctx, &ntatable->table); + if (result != ISC_R_SUCCESS) + goto cleanup_ntatable; + + result = isc_rwlock_init(&ntatable->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_rbt; + + ntatable->mctx = NULL; + isc_mem_attach(mctx, &ntatable->mctx); + ntatable->references = 1; + ntatable->magic = NTATABLE_MAGIC; + *ntatablep = ntatable; + + return (ISC_R_SUCCESS); + + cleanup_rbt: + dns_rbt_destroy(&ntatable->table); + + cleanup_ntatable: + isc_mem_put(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); + ntatable->magic = 0; + isc_mem_putanddetach(&ntatable->mctx, + ntatable, sizeof(*ntatable)); + } +} + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, + isc_uint32_t expiry) +{ + isc_result_t result; + dns_nta_t *nta = NULL; + dns_rbtnode_t *node; + + REQUIRE(VALID_NTATABLE(ntatable)); + + result = dns_nta_create(ntatable->mctx, &nta); + if (result != ISC_R_SUCCESS) + return (result); + + nta->expiry = expiry; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + node = NULL; + result = dns_rbt_addnode(ntatable->table, name, &node); + if (result == ISC_R_SUCCESS) { + node->data = nta; + nta = NULL; + } else if (result == ISC_R_EXISTS) { + dns_nta_t *n = node->data; + if (n == NULL) { + node->data = nta; + nta = NULL; + } else { + n->expiry = nta->expiry; + nta_detach(ntatable->mctx, &nta); + } + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (nta != NULL) + nta_detach(ntatable->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); + + 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_dump(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + + REQUIRE(VALID_NTATABLE(ntatable)); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, ntatable->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_name_t name; + isc_time_t t; + + dns_name_init(&name, NULL); + dns_rbt_namefromnode(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 : expiry %s\n", nbuf, 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_nta_create(isc_mem_t *mctx, dns_nta_t **target) { + isc_result_t result; + dns_nta_t *nta = NULL; + + REQUIRE(target != NULL && *target == NULL); + + nta = isc_mem_get(mctx, sizeof(dns_nta_t)); + if (nta == NULL) + return (ISC_R_NOMEMORY); + + nta->expiry = 0; + + result = isc_refcount_init(&nta->refcount, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, nta, sizeof(nta)); + return (result); + } + + nta->magic = NTA_MAGIC; + + *target = nta; + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 3a86ec7fa1c..228961ad266 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1821,6 +1821,29 @@ compute_cc(resquery_t *query, unsigned char *sit, size_t len) { } #endif +static isc_result_t +issecuredomain(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, 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, issecure)); +} + static isc_result_t resquery_send(resquery_t *query) { fetchctx_t *fctx; @@ -1922,8 +1945,9 @@ resquery_send(resquery_t *query) { fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; else if (res->view->enablevalidation && ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) { - result = dns_view_issecuredomain(res->view, &fctx->name, - &secure_domain); + result = issecuredomain(res->view, &fctx->name, + fctx->type, query->start.seconds, + &secure_domain); if (result != ISC_R_SUCCESS) secure_domain = ISC_FALSE; if (res->view->dlv != NULL) @@ -4825,8 +4849,8 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, * Is DNSSEC validation required for this name? */ if (res->view->enablevalidation) { - result = dns_view_issecuredomain(res->view, name, - &secure_domain); + result = issecuredomain(res->view, name, fctx->type, + now, &secure_domain); if (result != ISC_R_SUCCESS) return (result); @@ -5360,8 +5384,8 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, * Is DNSSEC validation required for this name? */ if (fctx->res->view->enablevalidation) { - result = dns_view_issecuredomain(res->view, name, - &secure_domain); + result = issecuredomain(res->view, name, fctx->type, + now, &secure_domain); if (result != ISC_R_SUCCESS) return (result); diff --git a/lib/dns/result.c b/lib/dns/result.c index 39879532d48..e4bc5528ca5 100644 --- a/lib/dns/result.c +++ b/lib/dns/result.c @@ -163,6 +163,7 @@ static const char *text[DNS_R_NRESULTS] = { "expired", /*%< 107 DNS_R_EXPIRED */ "not dynamic", /*%< 108 DNS_R_NOTDYNAMIC */ "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 824476714f3..87701061998 100644 --- a/lib/dns/tests/Makefile.in +++ b/lib/dns/tests/Makefile.in @@ -45,6 +45,7 @@ SRCS = db_test.c \ dnstest.c \ geoip_test.c \ gost_test.c \ + keytable_test.c \ master_test.c \ nsec3_test.c \ private_test.c \ @@ -66,6 +67,7 @@ TARGETS = db_test@EXEEXT@ \ dispatch_test@EXEEXT@ \ geoip_test@EXEEXT@ \ gost_test@EXEEXT@ \ + keytable_test@EXEEXT@ \ master_test@EXEEXT@ \ nsec3_test@EXEEXT@ \ private_test@EXEEXT@ \ @@ -94,6 +96,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 1f6edb4c539..c70e5168298 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(void) { 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 863ffb6aa58..cd430477b34 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 00000000000..2d97463d952 --- /dev/null +++ b/lib/dns/tests/keytable_test.c @@ -0,0 +1,598 @@ +/* + * 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="; + +/* + * 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() { + dst_key_t *key = NULL; + isc_stdtime_t now; + + ATF_REQUIRE_EQ(dns_keytable_create(mctx, &keytable), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_ntatable_create(mctx, &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"), + now + 3600), + ISC_R_SUCCESS); +} + +static void +destroy_tables() { + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + if (keytable != NULL) + dns_keytable_detach(&keytable); +} + +/* + * 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_FALSE), 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_FALSE), 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_FALSE), 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_FALSE), 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_FALSE), 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_FALSE), 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 = 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, mctx); + 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"), now + 1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Should be secure */ + result = dns_view_issecuredomain(view, + str2name("test.secure.example"), + now, &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, &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, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Now check deletion */ + result = dns_ntatable_add(ntatable, + str2name("new.example"), now + 3600); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(view, str2name("test.new.example"), + now, &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, &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 6e09762f60e..f7d7d70e204 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -15,8 +15,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - #include #include @@ -1649,12 +1647,9 @@ validate(dns_validator_t *val, isc_boolean_t resume) { 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); } @@ -3075,6 +3070,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)); @@ -3164,6 +3164,9 @@ finddlvsep(dns_validator_t *val, isc_boolean_t resume) { return (DNS_R_NOVALIDSIG); } + if (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)); @@ -3780,6 +3783,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; diff --git a/lib/dns/view.c b/lib/dns/view.c index c93a080747d..d3462ffe2f0 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, } } view->secroots_priv = NULL; + view->ntatable_priv = NULL; view->fwdtable = NULL; result = dns_fwdtable_create(mctx, &view->fwdtable); if (result != ISC_R_SUCCESS) { @@ -444,6 +446,8 @@ destroy(dns_view_t *view) { dns_stats_detach(&view->resquerystats); if (view->secroots_priv != NULL) dns_keytable_detach(&view->secroots_priv); + 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)) { @@ -1771,6 +1775,24 @@ 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_mem_t *mctx) { + REQUIRE(DNS_VIEW_VALID(view)); + if (view->ntatable_priv != NULL) + dns_ntatable_detach(&view->ntatable_priv); + return (dns_ntatable_create(mctx, &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)); @@ -1789,15 +1811,46 @@ 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 *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 (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 diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 746ed48a9e5..b3b71d30f90 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -483,6 +483,14 @@ dns_nsec_isset dns_nsec_nseconly dns_nsec_setbit dns_nsec_typepresent +dns_nta_create +dns_ntatable_add +dns_ntatable_attach +dns_ntatable_covered +dns_ntatable_create +dns_ntatable_delete +dns_ntatable_detach +dns_ntatable_dump dns_opcode_totext dns_opcodestats_create dns_opcodestats_dump diff --git a/lib/dns/win32/libdns.dsp.in b/lib/dns/win32/libdns.dsp.in index f7e5a6b98ae..7df9f0d182f 100644 --- a/lib/dns/win32/libdns.dsp.in +++ b/lib/dns/win32/libdns.dsp.in @@ -276,6 +276,10 @@ SOURCE=..\include\dns\nsec3.h # End Source File # Begin Source File +SOURCE=..\include\dns\nta.h +# End Source File +# Begin Source File + SOURCE=..\include\dns\order.h # End Source File # Begin Source File @@ -614,6 +618,10 @@ SOURCE=..\nsec3.c # End Source File # Begin Source File +SOURCE=..\nta.c +# End Source File +# Begin Source File + SOURCE=..\order.c # End Source File # Begin Source File diff --git a/lib/dns/win32/libdns.mak.in b/lib/dns/win32/libdns.mak.in index 9b96feb07aa..6dd889f69eb 100644 --- a/lib/dns/win32/libdns.mak.in +++ b/lib/dns/win32/libdns.mak.in @@ -166,6 +166,7 @@ CLEAN : -@erase "$(INTDIR)\ncache.obj" -@erase "$(INTDIR)\nsec.obj" -@erase "$(INTDIR)\nsec3.obj" + -@erase "$(INTDIR)\nta.obj" @IF OPENSSL -@erase "$(INTDIR)\openssl_link.obj" -@erase "$(INTDIR)\openssldh_link.obj" @@ -315,6 +316,7 @@ LINK32_OBJS= \ "$(INTDIR)\ncache.obj" \ "$(INTDIR)\nsec.obj" \ "$(INTDIR)\nsec3.obj" \ + "$(INTDIR)\nta.obj" \ "$(INTDIR)\order.obj" \ "$(INTDIR)\peer.obj" \ "$(INTDIR)\portlist.obj" \ @@ -504,6 +506,8 @@ CLEAN : -@erase "$(INTDIR)\nsec.sbr" -@erase "$(INTDIR)\nsec3.obj" -@erase "$(INTDIR)\nsec3.sbr" + -@erase "$(INTDIR)\nta.obj" + -@erase "$(INTDIR)\nta.sbr" @IF OPENSSL -@erase "$(INTDIR)\openssl_link.obj" -@erase "$(INTDIR)\openssl_link.sbr" @@ -705,6 +709,7 @@ BSC32_SBRS= \ "$(INTDIR)\ncache.sbr" \ "$(INTDIR)\nsec.sbr" \ "$(INTDIR)\nsec3.sbr" \ + "$(INTDIR)\nta.sbr" \ "$(INTDIR)\order.sbr" \ "$(INTDIR)\peer.sbr" \ "$(INTDIR)\portlist.sbr" \ @@ -820,6 +825,7 @@ LINK32_OBJS= \ "$(INTDIR)\ncache.obj" \ "$(INTDIR)\nsec.obj" \ "$(INTDIR)\nsec3.obj" \ + "$(INTDIR)\nta.obj" \ "$(INTDIR)\order.obj" \ "$(INTDIR)\peer.obj" \ "$(INTDIR)\portlist.obj" \ @@ -1557,6 +1563,24 @@ SOURCE=..\nsec3.c $(CPP) $(CPP_PROJ) $(SOURCE) +!ENDIF + +SOURCE=..\nta.c + +!IF "$(CFG)" == "libdns - @PLATFORM@ Release" + + +"$(INTDIR)\nta.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "libdns - @PLATFORM@ Debug" + + +"$(INTDIR)\nta.obj" "$(INTDIR)\nta.sbr" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + !ENDIF SOURCE=..\order.c diff --git a/lib/dns/win32/libdns.vcxproj.filters.in b/lib/dns/win32/libdns.vcxproj.filters.in index 7a065da44c7..77f211dd5e3 100644 --- a/lib/dns/win32/libdns.vcxproj.filters.in +++ b/lib/dns/win32/libdns.vcxproj.filters.in @@ -137,6 +137,9 @@ Library Source Files + + Library Source Files + Library Source Files @@ -469,6 +472,9 @@ Library Header Files + + Library Header Files + Library Header Files @@ -639,4 +645,4 @@ Dst Header Files - \ No newline at end of file + diff --git a/lib/dns/win32/libdns.vcxproj.in b/lib/dns/win32/libdns.vcxproj.in index f402d7466b4..daa400f9023 100644 --- a/lib/dns/win32/libdns.vcxproj.in +++ b/lib/dns/win32/libdns.vcxproj.in @@ -152,6 +152,7 @@ + @IF OPENSSL @@ -269,6 +270,7 @@ + @@ -325,4 +327,4 @@ - \ No newline at end of file +