diff --git a/CHANGES b/CHANGES index a02ff61d34..ac7181cd42 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +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] + 3881. [bug] Address memory leak with UPDATE error handling. [RT #36303] diff --git a/bin/named/config.c b/bin/named/config.c index 55155726a3..c841e31e09 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -53,6 +53,7 @@ static char defaultconf[] = "\ options {\n\ automatic-interface-scan yes;\n\ + bindkeys-file \"" NS_SYSCONFDIR "/bind.keys\";\n\ # blackhole {none;};\n" #ifndef WIN32 " coresize default;\n\ @@ -78,8 +79,9 @@ 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\ prefetch 2 9;\n\ recursing-file \"named.recursing\";\n\ diff --git a/bin/named/query.c b/bin/named/query.c index 775e99dd25..a0edf106a8 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -15,8 +15,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - /*! \file */ #include diff --git a/bin/named/server.c b/bin/named/server.c index 679cf28fff..a23db33912 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -826,7 +826,7 @@ configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, return (ISC_R_UNEXPECTED); } - result = dns_view_initntatable(view, mctx); + 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, @@ -3566,6 +3566,16 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, 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) { @@ -3626,6 +3636,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, } else { empty_zones_enable = ISC_FALSE; } + if (empty_zones_enable && !lwresd_g_useresolvconf) { const char *empty; int empty_zone = 0; @@ -7768,6 +7779,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; @@ -7801,11 +7813,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"); @@ -7814,6 +7840,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) @@ -9772,15 +9800,18 @@ 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; + 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_textregion_t tr; + isc_boolean_t ttlset = ISC_FALSE; + + UNUSED(force); dns_fixedname_init(&fn); ntaname = dns_fixedname_name(&fn); @@ -9790,8 +9821,78 @@ ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { if (ptr == NULL) return (ISC_R_UNEXPECTEDEND); + for (;;) { + size_t len; + + /* Check for options */ + ptr = next_token(&args, " \t"); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + len = strlen(ptr); + + if (strncasecmp(ptr, "-dump", len) == 0) + dump = ISC_TRUE; + else if (strncasecmp(ptr, "-remove", len) == 0) { + ntattl = 0; + ttlset = ISC_TRUE; + } else if (strncasecmp(ptr, "-force", len) == 0) { + force = ISC_TRUE; + continue; + } else if (strncasecmp(ptr, "-lifetime", len) == 0) { + 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 > 86400) { + msg = "NTA lifetime cannot exceed one day"; + 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. */ - nametext = next_token(&args, " \t"); + if (nametext == NULL) + nametext = next_token(&args, " \t"); if (nametext == NULL) return (ISC_R_UNEXPECTEDEND); @@ -9804,26 +9905,10 @@ ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { 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); @@ -9835,6 +9920,12 @@ ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { strcmp(view->name, viewname) != 0) continue; + if (view->nta_lifetime == 0 || view->nta_recheck == 0) + continue; + + if (!ttlset) + ntattl = view->nta_lifetime; + if (ntatable != NULL) dns_ntatable_detach(&ntatable); @@ -9851,11 +9942,13 @@ ns_server_nta(ns_server_t *server, char *args, isc_buffer_t *text) { 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, + force, now, ntattl)); - if (ntattl > 0) { - CHECK(dns_ntatable_add(ntatable, ntaname, when)); + 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)); diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index 7c28ac480f..c3c6e78cfc 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -463,10 +463,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. @@ -597,12 +596,19 @@ - nta domain duration + nta + ( -d | -f | -r | -l duration) + domain + view + Sets a DNSSEC negative trust anchor (NTA) for , with a lifetime of - (up to a limit of one day). + . The default lifetime is + configured in named.conf via the + , and defaults to + one hour. The lifetime cannot exceed one day. A negative trust anchor selectively disables @@ -617,13 +623,40 @@ restarted (NTA's do not persist across restarts). - TTL-style suffixes can be used to specify - in seconds, minutes, or hours. + An existing NTA can be removed by using the + option. - If the specified domain already has an NTA, its duration - will be updated to the new value. Setting - to zero will delete the NTA. + 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 . diff --git a/bin/tests/system/dlvauto/tests.sh b/bin/tests/system/dlvauto/tests.sh index a6866980df..e4de957192 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/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index db66cc66da..7d86540b41 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 a4dcad3788..f302d79b00 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -26,11 +26,10 @@ zonefile=example.db ( cd ../ns3 && $SHELL 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 23b9d1d6a9..c655c0d6b6 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 0d2fa4f46a..d5081e7546 100644 --- a/bin/tests/system/dnssec/ns3/secure.example.db.in +++ b/bin/tests/system/dnssec/ns3/secure.example.db.in @@ -26,7 +26,11 @@ ns3 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 fc6a5a24dd..bef02321bb 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -459,3 +459,18 @@ zonefile=siginterval.example.db kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone` zskname=`$KEYGEN -q -3 -r $RANDFILE $zone` cp $infile $zonefile + +# +# 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. diff --git a/bin/tests/system/dnssec/ns4/named1.conf b/bin/tests/system/dnssec/ns4/named1.conf index 2cc3034992..3873723d73 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/setup.sh b/bin/tests/system/dnssec/setup.sh index 23143836b2..f2e4980bb1 100644 --- a/bin/tests/system/dnssec/setup.sh +++ b/bin/tests/system/dnssec/setup.sh @@ -26,6 +26,7 @@ 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 +echo "c.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 2961b75048..67460724e2 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1568,7 +1568,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` @@ -1654,26 +1654,88 @@ 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 +$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 "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 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 /' +# +$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 /' +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 /' +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.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 +# +$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 +$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 +$DIG $DIGOPTS a.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 +$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 "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 + +# +# 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 +$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 +$DIG $DIGOPTS c.secure.example. a @10.53.0.4 > dig.out.ns4.test$n.13 || 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 "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"; fi status=`expr $status + $ret` diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 8616ba0a91..bf99f22cc5 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -4939,6 +4939,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; dscp ip_dscp ; additional-from-auth yes_or_no ; @@ -5746,6 +5748,69 @@ options { + + 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, or until the server is + restarted (NTA's do not persist across 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 day. + + + + + + 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. + + + + + max-zone-ttl diff --git a/lib/bind9/check.c b/lib/bind9/check.c index faa467cba0..262c4f88b5 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -783,6 +783,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; static intervaltable intervals[] = { { "cleaning-interval", 60, 28 * 24 * 60 }, /* 28 days */ @@ -1153,6 +1154,38 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (tresult != ISC_R_SUCCESS) result = tresult; + obj = NULL; + (void)cfg_map_get(options, "nta-lifetime", &obj); + if (obj != NULL) { + lifetime = cfg_obj_asuint32(obj); + if (lifetime > 86400) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'nta-lifetime' cannot exceed one day"); + 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 > 86400) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'nta-recheck' cannot exceed one day"); + 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/cache.c b/lib/dns/cache.c index abe163b35d..07be9d4a31 100644 --- a/lib/dns/cache.c +++ b/lib/dns/cache.c @@ -300,6 +300,8 @@ dns_cache_create3(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr, result = isc_task_create(taskmgr, 1, &dbtask); if (result != ISC_R_SUCCESS) goto cleanup_db; + + isc_task_setname(dbtask, "cache_dbtask", NULL); dns_db_settask(cache->db, dbtask); isc_task_detach(&dbtask); } diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h index 3231171da8..f6826485a9 100644 --- a/lib/dns/include/dns/nta.h +++ b/lib/dns/include/dns/nta.h @@ -28,21 +28,31 @@ * DNSSEC validation. */ +#include #include #include #include #include #include +#include +#include #include +#include +#include +#include ISC_LANG_BEGINDECLS struct dns_ntatable { /* Unlocked. */ unsigned int magic; - isc_mem_t *mctx; + 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; @@ -51,23 +61,18 @@ struct dns_ntatable { #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); +dns_ntatable_create(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, + dns_ntatable_t **ntatablep); /*%< - * Create an NTA table. + * Create an NTA table in view 'view'. * * Requires: * - *\li 'mctx' is a valid memory context. + *\li 'view' is a valid view. + * + *\li 'tmgr' is a valid timer manager. * *\li ntatablep != NULL && *ntatablep == NULL * @@ -116,10 +121,13 @@ dns_ntatable_detach(dns_ntatable_t **ntatablep); isc_result_t dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, - isc_uint32_t expiry); + 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 'expiry'. + * 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: * @@ -191,15 +199,15 @@ dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, */ isc_result_t -dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp); +dns_ntatable_totext(dns_ntatable_t *ntatable, isc_buffer_t *buf); /*%< - * Dump the NTA table on fp. + * Dump the NTA table to buffer 'buf' */ isc_result_t -dns_nta_create(isc_mem_t *mctx, dns_nta_t **target); +dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp); /*%< - * Allocate space for an NTA + * Dump the NTA table to the file opened as 'fp'. */ ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index bea163686f..e9aabc2b8c 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -98,6 +98,7 @@ typedef struct dns_fetchevent { #define DNS_FETCHOPT_WANTNSID 0x080 /*%< Request NSID */ #define DNS_FETCHOPT_PREFETCH 0x100 /*%< Do prefetch */ #define DNS_FETCHOPT_NOCDFLAG 0x200 /*%< Don't set CD flag. */ +#define DNS_FETCHOPT_NONTA 0x400 /*%< Ignore NTA table. */ /* Reserved in use by adb.c 0x00400000 */ #define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000 diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index 0abf1dce1f..505a25e5cd 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -174,6 +174,7 @@ struct dns_validator { #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 3a0c3f8602..0e7808db60 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -152,6 +152,8 @@ struct dns_view { isc_boolean_t requestsit; dns_ttl_t maxcachettl; dns_ttl_t maxncachettl; + isc_uint32_t nta_lifetime; + isc_uint32_t nta_recheck; dns_ttl_t prefetch_trigger; dns_ttl_t prefetch_eligible; in_port_t dstport; @@ -1076,7 +1078,8 @@ dns_view_iscacheshared(dns_view_t *view); */ isc_result_t -dns_view_initntatable(dns_view_t *view, isc_mem_t *mctx); +dns_view_initntatable(dns_view_t *view, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr); /*%< * Initialize the negative trust anchor table for the view. * @@ -1144,11 +1147,15 @@ 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_stdtime_t now, 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, 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. * diff --git a/lib/dns/nta.c b/lib/dns/nta.c index 879ab0ae8e..f48b5424d4 100644 --- a/lib/dns/nta.c +++ b/lib/dns/nta.c @@ -18,20 +18,42 @@ #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_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) + static void nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { unsigned int refs; @@ -42,7 +64,22 @@ nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { *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)); } } @@ -56,32 +93,41 @@ free_nta(void *data, void *arg) { } isc_result_t -dns_ntatable_create(isc_mem_t *mctx, dns_ntatable_t **ntatablep) { +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; - /* - * Create an NTA table. - */ - REQUIRE(ntatablep != NULL && *ntatablep == NULL); - ntatable = isc_mem_get(mctx, sizeof(*ntatable)); + ntatable = isc_mem_get(view->mctx, sizeof(*ntatable)); if (ntatable == NULL) return (ISC_R_NOMEMORY); - ntatable->table = NULL; - result = dns_rbt_create(mctx, free_nta, mctx, &ntatable->table); + 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->mctx = NULL; - isc_mem_attach(mctx, &ntatable->mctx); + ntatable->timermgr = timermgr; + ntatable->taskmgr = taskmgr; + + ntatable->view = view; ntatable->references = 1; + ntatable->magic = NTATABLE_MAGIC; *ntatablep = ntatable; @@ -90,8 +136,11 @@ dns_ntatable_create(isc_mem_t *mctx, dns_ntatable_t **ntatablep) { cleanup_rbt: dns_rbt_destroy(&ntatable->table); + cleanup_task: + isc_task_detach(&ntatable->task); + cleanup_ntatable: - isc_mem_put(mctx, ntatable, sizeof(*ntatable)); + isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); return (result); } @@ -132,43 +181,187 @@ dns_ntatable_detach(dns_ntatable_t **ntatablep) { 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_putanddetach(&ntatable->mctx, - ntatable, sizeof(*ntatable)); + 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; + isc_stdtime_t now; + + UNUSED(task); + + if (dns_rdataset_isassociated(&nta->rdataset)) + dns_rdataset_disassociate(&nta->rdataset); + if (dns_rdataset_isassociated(&nta->sigrdataset)) + dns_rdataset_disassociate(&nta->sigrdataset); + dns_resolver_destroyfetch(&nta->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); +} + +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; + + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + dns_resolver_destroyfetch(&nta->fetch); + } + 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); + + (void)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); +} + +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_uint32_t expiry) + 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)); - result = dns_nta_create(ntatable->mctx, &nta); + view = ntatable->view; + + result = nta_create(ntatable, name, &nta); if (result != ISC_R_SUCCESS) return (result); - nta->expiry = expiry; + nta->expiry = now + lifetime; 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(ntatable->mctx, &nta); + nta_detach(view->mctx, &nta); } result = ISC_R_SUCCESS; } @@ -176,7 +369,7 @@ dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); if (nta != NULL) - nta_detach(ntatable->mctx, &nta); + nta_detach(view->mctx, &nta); return (result); } @@ -268,6 +461,13 @@ dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, 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, @@ -283,15 +483,19 @@ dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, } isc_result_t -dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { +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->mctx); + 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; @@ -300,15 +504,80 @@ dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { if (node->data != NULL) { dns_nta_t *n = (dns_nta_t *) node->data; char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; - dns_name_t name; + char obuf[DNS_NAME_FORMATSIZE + 200]; + dns_fixedname_t fn; + 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)); + 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 : expiry %s\n", nbuf, tbuf); + + snprintf(obuf, sizeof(obuf), "%s%s: %s %s", + first ? "" : "\n", nbuf, + n->expiry < now ? "expired" : "expiry", + tbuf); + first = ISC_FALSE; + if (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; + } + } + + if (isc_buffer_availablelength(buf) != 0) + isc_buffer_putuint8(buf, 0); + + cleanup: + 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) { @@ -323,28 +592,3 @@ dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { 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(dns_nta_t)); - 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 dc14a1d8b3..27b61ae5d5 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1823,7 +1823,8 @@ compute_cc(resquery_t *query, unsigned char *sit, size_t len) { 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) + isc_stdtime_t now, isc_boolean_t checknta, + isc_boolean_t *issecure) { dns_name_t suffix; unsigned int labels; @@ -1841,7 +1842,7 @@ issecuredomain(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, name = &suffix; } - return (dns_view_issecuredomain(view, name, now, issecure)); + return (dns_view_issecuredomain(view, name, now, checknta, issecure)); } static isc_result_t @@ -1944,10 +1945,13 @@ resquery_send(resquery_t *query) { else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; else if (res->view->enablevalidation && - ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) { + ((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, - &secure_domain); + checknta, &secure_domain); if (result != ISC_R_SUCCESS) secure_domain = ISC_FALSE; if (res->view->dlv != NULL) @@ -4833,6 +4837,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. @@ -4849,14 +4854,19 @@ 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 = issecuredomain(res->view, name, fctx->type, - now, &secure_domain); + 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; } } @@ -5362,6 +5372,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"); @@ -5384,14 +5395,19 @@ 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 = issecuredomain(res->view, name, fctx->type, - now, &secure_domain); + 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; } } @@ -8275,6 +8291,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, diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c index 620b76f9a8..8fcb558930 100644 --- a/lib/dns/tests/keytable_test.c +++ b/lib/dns/tests/keytable_test.c @@ -50,6 +50,8 @@ 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 @@ -119,11 +121,16 @@ create_key(isc_uint16_t flags, isc_uint8_t proto, isc_uint8_t alg, /* 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(mctx, &ntatable), 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); @@ -139,7 +146,7 @@ create_tables() { isc_stdtime_get(&now); ATF_REQUIRE_EQ(dns_ntatable_add(ntatable, str2name("insecure.example"), - now + 3600), + ISC_FALSE, now, 3600), ISC_R_SUCCESS); } @@ -149,6 +156,8 @@ destroy_tables() { dns_ntatable_detach(&ntatable); if (keytable != NULL) dns_keytable_detach(&keytable); + + dns_view_detach(&view); } /* @@ -167,7 +176,7 @@ ATF_TC_BODY(add, tc) { UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* @@ -246,7 +255,7 @@ ATF_TC_HEAD(delete, tc) { ATF_TC_BODY(delete, tc) { UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* dns_keytable_delete requires exact match */ @@ -279,7 +288,7 @@ ATF_TC_BODY(deletekeynode, tc) { UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* key name doesn't match */ @@ -340,7 +349,7 @@ ATF_TC_BODY(find, tc) { UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* @@ -432,7 +441,7 @@ ATF_TC_BODY(issecuredomain, tc) { "null.example", "sub.null.example", NULL}; UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* @@ -476,7 +485,7 @@ ATF_TC_HEAD(dump, tc) { ATF_TC_BODY(dump, tc) { UNUSED(tc); - ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_FALSE), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_test_begin(NULL, ISC_TRUE), ISC_R_SUCCESS); create_tables(); /* @@ -508,12 +517,15 @@ ATF_TC_BODY(nta, tc) { 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, mctx); + 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); @@ -524,20 +536,21 @@ ATF_TC_BODY(nta, tc) { isc_stdtime_get(&now); result = dns_ntatable_add(ntatable, - str2name("insecure.example"), now + 1); + 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, &issecure); + 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, &issecure); + now, ISC_TRUE, &issecure); ATF_CHECK_EQ(result, ISC_R_SUCCESS); ATF_CHECK(!issecure); @@ -554,17 +567,17 @@ ATF_TC_BODY(nta, tc) { /* As of now + 2, the NTA should be clear */ result = dns_view_issecuredomain(view, str2name("test.insecure.example"), - now + 2, &issecure); + 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"), now + 3600); + 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, &issecure); + now, ISC_TRUE, &issecure); ATF_CHECK_EQ(result, ISC_R_SUCCESS); ATF_CHECK(!issecure); @@ -572,7 +585,7 @@ ATF_TC_BODY(nta, tc) { ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); result = dns_view_issecuredomain(view, str2name("test.new.example"), - now, &issecure); + now, ISC_TRUE, &issecure); ATF_CHECK_EQ(result, ISC_R_SUCCESS); ATF_CHECK(issecure); diff --git a/lib/dns/validator.c b/lib/dns/validator.c index f7d7d70e20..36fe54330b 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -1139,6 +1139,9 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, 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, fopts, @@ -3164,7 +3167,8 @@ 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)) + 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) >= diff --git a/lib/dns/view.c b/lib/dns/view.c index d3462ffe2f..4a45f1912b 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -192,6 +192,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->prefetch_eligible = 0; view->prefetch_trigger = 0; view->dstport = 53; @@ -1776,11 +1778,14 @@ dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp) { } isc_result_t -dns_view_initntatable(dns_view_t *view, isc_mem_t *mctx) { +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(mctx, &view->ntatable_priv)); + return (dns_ntatable_create(view, taskmgr, timermgr, + &view->ntatable_priv)); } isc_result_t @@ -1825,7 +1830,8 @@ dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, isc_result_t dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, - isc_stdtime_t now, 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; @@ -1845,7 +1851,7 @@ dns_view_issecuredomain(dns_view_t *view, dns_name_t *name, if (result != ISC_R_SUCCESS) return (result); - if (secure && view->ntatable_priv != NULL && + if (checknta && secure && view->ntatable_priv != NULL && dns_ntatable_covered(view->ntatable_priv, now, name, anchor)) secure = ISC_FALSE; diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 838b403e4f..740a354424 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -483,7 +483,6 @@ dns_nsec_isset dns_nsec_nseconly dns_nsec_setbit dns_nsec_typepresent -dns_nta_create dns_ntatable_add dns_ntatable_attach dns_ntatable_covered diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 96ce6c12df..377546d9e1 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -136,6 +136,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; @@ -1544,6 +1545,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 }, { "prefetch", &cfg_type_prefetch, 0 }, { "preferred-glue", &cfg_type_astring, 0 }, { "no-case-compress", &cfg_type_bracketed_aml, 0 }, @@ -3182,7 +3185,7 @@ static cfg_type_t cfg_type_masterselement = { }; static isc_result_t -parse_maxttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { +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; @@ -3213,15 +3216,16 @@ parse_maxttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { } /*% - * A size value (number + optional unit). + * A TTL value (number + optional unit). */ -static cfg_type_t cfg_type_maxttlval = { - "maxttlval", parse_maxttlval, cfg_print_uint64, cfg_doc_terminal, - &cfg_rep_uint64, NULL }; +static cfg_type_t cfg_type_ttlval = { + "ttlval", parse_ttlval, cfg_print_uint64, cfg_doc_terminal, + &cfg_rep_uint64, NULL +}; static isc_result_t parse_maxttl(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - return (parse_enum_or_other(pctx, type, &cfg_type_maxttlval, ret)); + return (parse_enum_or_other(pctx, type, &cfg_type_ttlval, ret)); } /*%