From: Evan Hunt Date: Fri, 4 Aug 2017 23:07:33 +0000 (-0700) Subject: [master] refactor resquery_response() and related functions X-Git-Tag: v9.12.0a1~145 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=61367c604c851f0f3ef6b0fb224640c51a130b53;p=thirdparty%2Fbind9.git [master] refactor resquery_response() and related functions 4669. [func] Iterative query logic in resolver.c has been refactored into smaller functions and commented, for improved readability, maintainability and testability. [RT #45362] --- diff --git a/CHANGES b/CHANGES index d3de6fb3393..8693dfe68ff 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +4669. [func] Iterative query logic in resolver.c has been + refactored into smaller functions and commented, + for improved readability, maintainability and + testability. [RT #45362] + 4668. [bug] Use localtime_r and gmtime_r for thread safety. [RT #45664] diff --git a/bin/named/query.c b/bin/named/query.c index 9d7eff69511..5321a73c87a 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -232,7 +232,7 @@ rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); /*% - * The structure and functions define below implement the query logic + * The structure and functions defined below implement the query logic * that previously lived in the single very complex function query_find(). * The query_ctx_t structure maintains state from function to function. * The call flow for the general query processing algorithm is described diff --git a/bin/tests/system/dnssec/ns4/named4.conf b/bin/tests/system/dnssec/ns4/named4.conf index eac9a3b8e6f..138e534c87b 100644 --- a/bin/tests/system/dnssec/ns4/named4.conf +++ b/bin/tests/system/dnssec/ns4/named4.conf @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013, 2016, 2017 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -18,12 +18,6 @@ options { pid-file "named.pid"; listen-on { 10.53.0.4; }; listen-on-v6 { none; }; - recursion yes; - acache-enable no; - dnssec-enable yes; - dnssec-validation auto; - bindkeys-file "managed.conf"; - dnssec-accept-expired yes; }; key rndc_key { @@ -35,11 +29,6 @@ controls { inet 10.53.0.4 port 9953 allow { any; } keys { rndc_key; }; }; -zone "." { - type hint; - file "../../common/root.hint"; -} - key auth { secret "1234abcd8765"; algorithm hmac-sha256; diff --git a/bin/tests/system/dnssec/ns4/named5.conf b/bin/tests/system/dnssec/ns4/named5.conf deleted file mode 100644 index 7d1d1a07e2e..00000000000 --- a/bin/tests/system/dnssec/ns4/named5.conf +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -// NS4 - -controls { /* empty */ }; - -options { - query-source address 10.53.0.4; - notify-source 10.53.0.4; - transfer-source 10.53.0.4; - port 5300; - pid-file "named.pid"; - listen-on { 10.53.0.4; }; - listen-on-v6 { none; }; -}; - -key rndc_key { - secret "1234abcd8765"; - algorithm hmac-sha256; -}; - -controls { - inet 10.53.0.4 port 9953 allow { any; } keys { rndc_key; }; -}; - -key auth { - secret "1234abcd8765"; - algorithm hmac-sha256; -}; - -include "trusted.conf"; - -view rec { - match-recursive-only yes; - recursion yes; - acache-enable yes; - dnssec-validation yes; - dnssec-accept-expired yes; - - zone "." { - type hint; - file "../../common/root.hint"; - }; - - zone secure.example { - type static-stub; - server-addresses { 10.53.0.4; }; - }; - - zone insecure.secure.example { - type static-stub; - server-addresses { 10.53.0.4; }; - }; -}; - -view auth { - recursion no; - allow-recursion { none; }; - - zone secure.example { - type slave; - masters { 10.53.0.3; }; - }; - - zone insecure.secure.example { - type slave; - masters { 10.53.0.2; }; - }; -}; diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index eea8f358f01..11c46d8ae79 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -2705,10 +2705,6 @@ n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` -cp ns4/named4.conf ns4/named.conf -$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /' -sleep 3 - echo "I:testing TTL is capped at RRSIG expiry time for records in the additional section with dnssec-accept-expired yes; ($n)" ret=0 $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 flush @@ -2726,27 +2722,6 @@ n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` -cp ns4/named4.conf ns4/named.conf -$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /' -sleep 3 - -echo "I:testing TTL is capped at RRSIG expiry time for records in the additional section with acache off; ($n)" -ret=0 -$RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 flush -$DIG +noall +additional +dnssec +cd -p 5300 expiring.example mx @10.53.0.4 > dig.out.ns4.1.$n -$DIG +noall +additional +dnssec -p 5300 expiring.example mx @10.53.0.4 > dig.out.ns4.2.$n -ttls=`awk '$1 != ";;" {print $2}' dig.out.ns4.1.$n` -ttls2=`awk '$1 != ";;" {print $2}' dig.out.ns4.2.$n` -for ttl in ${ttls:-300}; do - [ $ttl -eq 300 ] || ret=1 -done -for ttl in ${ttls2:-0}; do - [ $ttl -le 120 -a $ttl -gt 60 ] || ret=1 -done -n=`expr $n + 1` -if [ $ret != 0 ]; then echo "I:failed"; fi -status=`expr $status + $ret` - echo "I:testing DNSKEY lookup via CNAME ($n)" ret=0 $DIG $DIGOPTS +noauth cnameandkey.secure.example. \ @@ -2917,7 +2892,7 @@ n=`expr $n + 1` if test "$before" = "$after" ; then echo "I:failed"; ret=1; fi status=`expr $status + $ret` -cp ns4/named5.conf ns4/named.conf +cp ns4/named4.conf ns4/named.conf $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 reconfig 2>&1 | sed 's/^/I:ns4 /' sleep 3 diff --git a/bin/tests/system/dnstap/ns3/named.conf b/bin/tests/system/dnstap/ns3/named.conf index f6a74a7bf0a..05c84d21820 100644 --- a/bin/tests/system/dnstap/ns3/named.conf +++ b/bin/tests/system/dnstap/ns3/named.conf @@ -25,6 +25,7 @@ options { dnstap { all; }; send-cookie no; require-server-cookie no; + minimal-responses no; }; server 10.53.0.1 { tcp-only yes; }; diff --git a/bin/tests/system/dnstap/tests.sh b/bin/tests/system/dnstap/tests.sh index 4c06b7c2515..014d2acf737 100644 --- a/bin/tests/system/dnstap/tests.sh +++ b/bin/tests/system/dnstap/tests.sh @@ -358,6 +358,7 @@ if [ $HAS_PYYAML -ne 0 ] ; then fi echo "I:checking dnstap-read hex output" +ret=0 hex=`$DNSTAPREAD -x ns3/dnstap.out | tail -1` echo $hex | $WIRETEST > dnstap.hex grep 'status: NOERROR' dnstap.hex > /dev/null 2>&1 || ret=1 diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml index 6729459a771..1fd4fa67f34 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -273,11 +273,32 @@ - Query logic has been substantially refactored (e.g. query_find - function has been split into smaller functions) for improved - readability, maintainability and testability. [RT #43929] + + + Several areas of code have been refactored for improved + readability, maintainability, and testability: + + + + + The named query logic implemented in + query_find() has been split into + smaller functions with a context structure to maintain state + between them, and extensive comments have been added. + [RT #43929] + + + + + Similarly the iterative query logic implemented in + resquery_response() function has been + split into smaller functions and comments added. [RT #45362] + + + + dnstap logfiles can now be configured to diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index f362c3bcddb..5b655308fad 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -573,6 +573,221 @@ static inline isc_result_t findnoqname(fetchctx_t *fctx, dns_name_t *name, static void fctx_increference(fetchctx_t *fctx); static isc_boolean_t fctx_decreference(fetchctx_t *fctx); +/*% + * The structure and functions defined below implement the resolver + * query (resquery) response handling logic. + * + * When a resolver query is sent and a response is received, the + * resquery_response() event handler is run, which calls the rctx_*() + * functions. The respctx_t structure maintains state from function + * to function. + * + * The call flow is described below: + * + * 1. resquery_response(): + * - Initialize a respctx_t structure (rctx_respinit()). + * - Check for dispatcher failure (rctx_dispfail()). + * - Parse the response (rctx_parse()). + * - Log the response (rctx_logpacket()). + * - Check the parsed response for an OPT record and handle + * EDNS (rctx_opt(), rctx_edns()). + * - Check for a bad or lame server (rctx_badserver(), rctx_lameserver()). + * - Handle delegation-only zones (rctx_delonly_zone()). + * - If RCODE and ANCOUNT suggest this is a positive answer, and + * if so, call rctx_answer(): go to step 2. + * - If RCODE and NSCOUNT suggest this is a negative answer or a + * referral, call rctx_answer_none(): go to step 4. + * - Check the additional section for data that should be cached + * (rctx_additional()). + * - Clean up and finish by calling rctx_done(): go to step 5. + * + * 2. rctx_answer(): + * - If the answer appears to be positive, call rctx_answer_positive(): + * go to step 3. + * - If the response is a malformed delegation (with glue or NS records + * in the answer section), call rctx_answer_none(): go to step 4. + * + * 3. rctx_answer_positive(): + * - Initialize the portions of respctx_t needed for processing an answer + * (rctx_answer_init()). + * - Scan the answer section to find records that are responsive to the + * query (rctx_answer_scan()). + * - For whichever type of response was found, call a separate routine + * to handle it: matching QNAME/QTYPE (rctx_answer_match()), + * CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()), + * or any records returned in response to a query of type ANY + * (rctx_answer_any()). + * - Scan the authority section for NS or other records that may be + * included with a positive answer (rctx_authority_scan()). + * + * 4. rctx_answer_none(): + * - Determine whether this is an NXDOMAIN, NXRRSET, or referral. + * - If referral, set up the resolver to follow the delegation + * (rctx_referral()). + * - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA + * records included with a negative response (rctx_authority_negative()), + * then for DNSSEC proof of nonexistence (rctx_authority_dnssec()). + * + * 5. rctx_done(): + * - Set up chasing of DS records if needed (rctx_chaseds()). + * - If the response wasn't intended for us, wait for another response + * from the dispatcher (rctx_next()). + * - If there is a problem with the responding server, set up another + * query to a different server (rctx_nextserver()). + * - If there is a problem that might be temporary or dependent on + * EDNS options, set up another query to the same server with changed + * options (rctx_resend()). + * - Shut down the fetch context. + */ + +typedef struct respctx { + isc_task_t *task; + dns_dispatchevent_t *devent; + resquery_t *query; + fetchctx_t *fctx; + isc_result_t result; + unsigned int retryopts; /* updated options to pass to + * fctx_query() when resending */ + + dns_rdatatype_t type; /* type being sought (set to + * ANY if qtype was SIG or RRSIG) */ + isc_boolean_t aa; /* authoritative answer? */ + dns_trust_t trust; /* answer trust level */ + isc_boolean_t chaining; /* CNAME/DNAME processing? */ + isc_boolean_t next_server; /* give up, try the next server */ + + badnstype_t broken_type; /* type of name server problem */ + isc_result_t broken_server; + + isc_boolean_t get_nameservers; /* get a new NS rrset at zone cut? */ + isc_boolean_t resend; /* resend this query? */ + isc_boolean_t nextitem; /* invalid response; keep + * listening for the correct one */ + isc_boolean_t truncated; /* response was truncated */ + isc_boolean_t no_response; /* no response was received */ + isc_boolean_t glue_in_answer; /* glue may be in the answer section */ + isc_boolean_t ns_in_answer; /* NS may be in the answer section */ + isc_boolean_t negative; /* is this a negative response? */ + + isc_stdtime_t now; /* time info */ + isc_time_t tnow; + isc_time_t *finish; + + unsigned int dname_labels; + unsigned int domain_labels; /* range of permissible number of + * labels in a DNAME */ + + dns_name_t *aname; /* answer name */ + dns_rdataset_t *ardataset; /* answer rdataset */ + + dns_name_t *cname; /* CNAME name */ + dns_rdataset_t *crdataset; /* CNAME rdataset */ + + dns_name_t *dname; /* DNAME name */ + dns_rdataset_t *drdataset; /* DNAME rdataset */ + + dns_name_t *ns_name; /* NS name */ + dns_rdataset_t *ns_rdataset; /* NS rdataset */ + + dns_name_t *soa_name; /* SOA name in a negative answer */ + dns_name_t *ds_name; /* DS name in a negative answer */ + + dns_name_t *found_name; /* invalid name in negative response */ + dns_rdatatype_t found_type; /* invalid type in negative response */ + + dns_rdataset_t *opt; /* OPT rdataset */ +} respctx_t; + +static void +rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, + resquery_t *query, fetchctx_t *fctx, respctx_t *rctx); + +static void +rctx_answer_init(respctx_t *rctx); + +static void +rctx_answer_scan(respctx_t *rctx); + +static void +rctx_authority_positive(respctx_t *rctx); + +static isc_result_t +rctx_answer_any(respctx_t *rctx); + +static isc_result_t +rctx_answer_match(respctx_t *rctx); + +static isc_result_t +rctx_answer_cname(respctx_t *rctx); + +static isc_result_t +rctx_answer_dname(respctx_t *rctx); + +static isc_result_t +rctx_answer_positive(respctx_t *rctx); + +static isc_result_t +rctx_authority_negative(respctx_t *rctx); + +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx); + +static void +rctx_additional(respctx_t *rctx); + +static isc_result_t +rctx_referral(respctx_t *rctx); + +static isc_result_t +rctx_answer_none(respctx_t *rctx); + +static void +rctx_nextserver(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, + isc_result_t result); + +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo); + +static void +rctx_next(respctx_t *rctx); + +static void +rctx_chaseds(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, isc_result_t result); + +static void +rctx_done(respctx_t *rctx, isc_result_t result); + +static void +rctx_logpacket(respctx_t *rctx); + +static void +rctx_opt(respctx_t *rctx); + +static void +rctx_edns(respctx_t *rctx); + +static isc_result_t +rctx_parse(respctx_t *rctx); + +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result); + +static isc_result_t +rctx_answer(respctx_t *rctx); + +static isc_result_t +rctx_lameserver(respctx_t *rctx); + +static isc_result_t +rctx_dispfail(respctx_t *rctx); + +static void +rctx_delonly_zone(respctx_t *rctx); + +static void +rctx_ncache(respctx_t *rctx); + + /*% * Increment resolver-related statistics counters. */ @@ -5901,7 +6116,7 @@ ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, /* * XXXMPA remove when we follow cnames and adjust the setting - * of FCTX_ATTR_WANTNCACHE in noanswer_response(). + * of FCTX_ATTR_WANTNCACHE in rctx_answer_none(). */ INSIST(fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0); @@ -6145,41 +6360,6 @@ check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type) { } #endif -static void -chase_additional(fetchctx_t *fctx) { - isc_boolean_t rescan; - dns_section_t section = DNS_SECTION_ADDITIONAL; - isc_result_t result; - - again: - rescan = ISC_FALSE; - - for (result = dns_message_firstname(fctx->rmessage, section); - result == ISC_R_SUCCESS; - result = dns_message_nextname(fctx->rmessage, section)) { - dns_name_t *name = NULL; - dns_rdataset_t *rdataset; - dns_message_currentname(fctx->rmessage, DNS_SECTION_ADDITIONAL, - &name); - if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) - continue; - name->attributes &= ~DNS_NAMEATTR_CHASE; - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - if (CHASE(rdataset)) { - rdataset->attributes &= ~DNS_RDATASETATTR_CHASE; - (void)dns_rdataset_additionaldata(rdataset, - check_related, - fctx); - rescan = ISC_TRUE; - } - } - } - if (rescan) - goto again; -} - static isc_boolean_t is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, dns_rdataset_t *rdataset) @@ -6376,754 +6556,1197 @@ trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) { } } -/* - * Handle a no-answer response (NXDOMAIN, NXRRSET, or referral). - * If look_in_options has LOOK_FOR_NS_IN_ANSWER then we look in the answer - * section for the NS RRset if the query type is NS; if it has - * LOOK_FOR_GLUE_IN_ANSWER we look for glue incorrectly returned in the answer - * section for A and AAAA queries. - */ -#define LOOK_FOR_NS_IN_ANSWER 0x1 -#define LOOK_FOR_GLUE_IN_ANSWER 0x2 - -static isc_result_t -noanswer_response(fetchctx_t *fctx, dns_name_t *oqname, - unsigned int look_in_options) -{ - isc_result_t result; - dns_message_t *message; - dns_name_t *name, *qname, *ns_name, *soa_name, *ds_name, *save_name; - dns_rdataset_t *rdataset, *ns_rdataset; - isc_boolean_t aa, negative_response; - dns_rdatatype_t type, save_type; - dns_section_t section; - - FCTXTRACE("noanswer_response"); - - if ((look_in_options & LOOK_FOR_NS_IN_ANSWER) != 0) { - INSIST(fctx->type == dns_rdatatype_ns); - section = DNS_SECTION_ANSWER; - } else - section = DNS_SECTION_AUTHORITY; - - message = fctx->rmessage; - - /* - * Setup qname. - */ - if (oqname == NULL) { - /* - * We have a normal, non-chained negative response or - * referral. - */ - if ((message->flags & DNS_MESSAGEFLAG_AA) != 0) - aa = ISC_TRUE; - else - aa = ISC_FALSE; - qname = &fctx->name; - } else { +static isc_boolean_t +validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) { + if (rdataset->type == dns_rdatatype_nsec3) { /* - * We're being invoked by answer_response() after it has - * followed a CNAME/DNAME chain. + * NSEC3 records are not allowed to + * appear in the answer section. */ - qname = oqname; - aa = ISC_FALSE; + log_formerr(fctx, "NSEC3 in answer"); + return (ISC_FALSE); + } + if (rdataset->type == dns_rdatatype_tkey) { /* - * If the current qname is not a subdomain of the query - * domain, there's no point in looking at the authority - * section without doing DNSSEC validation. - * - * Until we do that validation, we'll just return success - * in this case. + * TKEY is not a valid record in a + * response to any query we can make. */ - if (!dns_name_issubdomain(qname, &fctx->domain)) - return (ISC_R_SUCCESS); + log_formerr(fctx, "TKEY in answer"); + return (ISC_FALSE); + } + if (rdataset->rdclass != fctx->res->rdclass) { + log_formerr(fctx, "Mismatched class in answer"); + return (ISC_FALSE); } + return (ISC_TRUE); +} - /* - * We have to figure out if this is a negative response, or a - * referral. - */ +static void +fctx_increference(fetchctx_t *fctx) { + REQUIRE(VALID_FCTX(fctx)); - /* - * Sometimes we can tell if its a negative response by looking at - * the message header. - */ - negative_response = ISC_FALSE; - if (message->rcode == dns_rcode_nxdomain || - (message->counts[DNS_SECTION_ANSWER] == 0 && - message->counts[DNS_SECTION_AUTHORITY] == 0)) - negative_response = ISC_TRUE; + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + fctx->references++; + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); +} - /* - * Process the authority section. - */ - ns_name = NULL; - ns_rdataset = NULL; - soa_name = NULL; - ds_name = NULL; - save_name = NULL; - save_type = dns_rdatatype_none; - result = dns_message_firstname(message, section); - while (result == ISC_R_SUCCESS) { - name = NULL; - dns_message_currentname(message, section, &name); - if (dns_name_issubdomain(name, &fctx->domain)) { +static isc_boolean_t +fctx_decreference(fetchctx_t *fctx) { + isc_boolean_t bucket_empty = ISC_FALSE; + + REQUIRE(VALID_FCTX(fctx)); + + INSIST(fctx->references > 0); + fctx->references--; + if (fctx->references == 0) { + /* + * No one cares about the result of this fetch anymore. + */ + if (fctx->pending == 0 && fctx->nqueries == 0 && + ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) { /* - * Look for NS/SOA RRsets first. + * This fctx is already shutdown; we were just + * waiting for the last reference to go away. */ - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - type = rdataset->type; - if (type == dns_rdatatype_rrsig) - type = rdataset->covers; - if (((type == dns_rdatatype_ns || - type == dns_rdatatype_soa) && - !dns_name_issubdomain(qname, name))) { - char qbuf[DNS_NAME_FORMATSIZE]; - char nbuf[DNS_NAME_FORMATSIZE]; - char tbuf[DNS_RDATATYPE_FORMATSIZE]; - dns_rdatatype_format(type, tbuf, - sizeof(tbuf)); - dns_name_format(name, nbuf, - sizeof(nbuf)); - dns_name_format(qname, qbuf, - sizeof(qbuf)); - log_formerr(fctx, - "unrelated %s %s in " - "%s authority section", - tbuf, nbuf, qbuf); - goto nextname; - } - if (type == dns_rdatatype_ns) { - /* - * NS or RRSIG NS. - * - * Only one set of NS RRs is allowed. - */ - if (rdataset->type == - dns_rdatatype_ns) { - if (ns_name != NULL && - name != ns_name) { - log_formerr(fctx, - "multiple NS " - "RRsets in " - "authority " - "section"); - return (DNS_R_FORMERR); - } - ns_name = name; - ns_rdataset = rdataset; - } - name->attributes |= - DNS_NAMEATTR_CACHE; - rdataset->attributes |= - DNS_RDATASETATTR_CACHE; - rdataset->trust = dns_trust_glue; - } - if (type == dns_rdatatype_soa) { - /* - * SOA, or RRSIG SOA. - * - * Only one SOA is allowed. - */ - if (rdataset->type == - dns_rdatatype_soa) { - if (soa_name != NULL && - name != soa_name) { - log_formerr(fctx, - "multiple SOA " - "RRs in " - "authority " - "section"); - return (DNS_R_FORMERR); - } - soa_name = name; - } - name->attributes |= - DNS_NAMEATTR_NCACHE; - rdataset->attributes |= - DNS_RDATASETATTR_NCACHE; - if (aa) - rdataset->trust = - dns_trust_authauthority; - else if (ISFORWARDER(fctx->addrinfo)) - rdataset->trust = - dns_trust_answer; - else - rdataset->trust = - dns_trust_additional; - } - } + bucket_empty = fctx_unlink(fctx); + fctx_destroy(fctx); + } else { + /* + * Initiate shutdown. + */ + fctx_shutdown(fctx); } - nextname: - result = dns_message_nextname(message, section); - if (result == ISC_R_NOMORE) - break; - else if (result != ISC_R_SUCCESS) - return (result); } + return (bucket_empty); +} - log_ns_ttl(fctx, "noanswer_response"); +static void +resume_dslookup(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *fevent; + dns_resolver_t *res; + fetchctx_t *fctx; + isc_result_t result; + isc_boolean_t bucket_empty; + isc_boolean_t locked = ISC_FALSE; + unsigned int bucketnum; + dns_rdataset_t nameservers; + dns_fixedname_t fixed; + dns_name_t *domain; - if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && - !dns_name_equal(ns_name, dns_rootname)) - trim_ns_ttl(fctx, ns_name, ns_rdataset); + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; - /* - * A negative response has a SOA record (Type 2) - * and a optional NS RRset (Type 1) or it has neither - * a SOA or a NS RRset (Type 3, handled above) or - * rcode is NXDOMAIN (handled above) in which case - * the NS RRset is allowed (Type 4). - */ - if (soa_name != NULL) - negative_response = ISC_TRUE; + UNUSED(task); + FCTXTRACE("resume_dslookup"); - result = dns_message_firstname(message, section); - while (result == ISC_R_SUCCESS) { + if (fevent->node != NULL) + dns_db_detachnode(fevent->db, &fevent->node); + if (fevent->db != NULL) + dns_db_detach(&fevent->db); + + dns_rdataset_init(&nameservers); + + bucketnum = fctx->bucketnum; + if (fevent->result == ISC_R_CANCELED) { + dns_resolver_destroyfetch(&fctx->nsfetch); + fctx_done(fctx, ISC_R_CANCELED, __LINE__); + } else if (fevent->result == ISC_R_SUCCESS) { + + FCTXTRACE("resuming DS lookup"); + + dns_resolver_destroyfetch(&fctx->nsfetch); + if (dns_rdataset_isassociated(&fctx->nameservers)) + dns_rdataset_disassociate(&fctx->nameservers); + dns_rdataset_clone(fevent->rdataset, &fctx->nameservers); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = ISC_TRUE; + log_ns_ttl(fctx, "resume_dslookup"); + + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + result = dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + goto cleanup; + } + result = fcount_incr(fctx, ISC_TRUE); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + goto cleanup; + } + /* + * Try again. + */ + fctx_try(fctx, ISC_TRUE, ISC_FALSE); + } else { + unsigned int n; + dns_rdataset_t *nsrdataset = NULL; + + /* + * Retrieve state from fctx->nsfetch before we destroy it. + */ + dns_fixedname_init(&fixed); + domain = dns_fixedname_name(&fixed); + dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL); + if (dns_name_equal(&fctx->nsname, domain)) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + dns_resolver_destroyfetch(&fctx->nsfetch); + goto cleanup; + } + if (dns_rdataset_isassociated( + &fctx->nsfetch->private->nameservers)) { + dns_rdataset_clone( + &fctx->nsfetch->private->nameservers, + &nameservers); + nsrdataset = &nameservers; + } else + domain = NULL; + dns_resolver_destroyfetch(&fctx->nsfetch); + n = dns_name_countlabels(&fctx->nsname); + dns_name_getlabelsequence(&fctx->nsname, 1, n - 1, + &fctx->nsname); + + if (dns_rdataset_isassociated(fevent->rdataset)) + dns_rdataset_disassociate(fevent->rdataset); + FCTXTRACE("continuing to look for parent's NS records"); + result = dns_resolver_createfetch(fctx->res, &fctx->nsname, + dns_rdatatype_ns, domain, + nsrdataset, NULL, + fctx->options, task, + resume_dslookup, fctx, + &fctx->nsrrset, NULL, + &fctx->nsfetch); + if (result != ISC_R_SUCCESS) + fctx_done(fctx, result, __LINE__); + else { + LOCK(&res->buckets[bucketnum].lock); + locked = ISC_TRUE; + fctx->references++; + } + } + + cleanup: + if (dns_rdataset_isassociated(&nameservers)) + dns_rdataset_disassociate(&nameservers); + if (dns_rdataset_isassociated(fevent->rdataset)) + dns_rdataset_disassociate(fevent->rdataset); + INSIST(fevent->sigrdataset == NULL); + isc_event_free(&event); + if (!locked) + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) + empty_bucket(res); +} + +static inline void +checknamessection(dns_message_t *message, dns_section_t section) { + isc_result_t result; + dns_name_t *name; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t *rdataset; + + for (result = dns_message_firstname(message, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, section)) + { name = NULL; dns_message_currentname(message, section, &name); - if (dns_name_issubdomain(name, &fctx->domain)) { - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - type = rdataset->type; - if (type == dns_rdatatype_rrsig) - type = rdataset->covers; - if (type == dns_rdatatype_nsec || - type == dns_rdatatype_nsec3) { - /* - * NSEC or RRSIG NSEC. - */ - if (negative_response) { - name->attributes |= - DNS_NAMEATTR_NCACHE; - rdataset->attributes |= - DNS_RDATASETATTR_NCACHE; - } else if (type == dns_rdatatype_nsec) { - name->attributes |= - DNS_NAMEATTR_CACHE; - rdataset->attributes |= - DNS_RDATASETATTR_CACHE; - } - if (aa) - rdataset->trust = - dns_trust_authauthority; - else if (ISFORWARDER(fctx->addrinfo)) - rdataset->trust = - dns_trust_answer; - else - rdataset->trust = - dns_trust_additional; - /* - * No additional data needs to be - * marked. - */ - } else if (type == dns_rdatatype_ds) { - /* - * DS or SIG DS. - * - * These should only be here if - * this is a referral, and there - * should only be one DS RRset. - */ - if (ns_name == NULL) { - log_formerr(fctx, - "DS with no " - "referral"); - return (DNS_R_FORMERR); - } - if (rdataset->type == - dns_rdatatype_ds) { - if (ds_name != NULL && - name != ds_name) { - log_formerr(fctx, - "DS doesn't " - "match " - "referral " - "(NS)"); - return (DNS_R_FORMERR); - } - ds_name = name; - } - name->attributes |= - DNS_NAMEATTR_CACHE; + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_rdataset_current(rdataset, &rdata); + if (!dns_rdata_checkowner(name, rdata.rdclass, + rdata.type, + ISC_FALSE) || + !dns_rdata_checknames(&rdata, name, NULL)) + { rdataset->attributes |= - DNS_RDATASETATTR_CACHE; - if (aa) - rdataset->trust = - dns_trust_authauthority; - else if (ISFORWARDER(fctx->addrinfo)) - rdataset->trust = - dns_trust_answer; - else - rdataset->trust = - dns_trust_additional; + DNS_RDATASETATTR_CHECKNAMES; } + dns_rdata_reset(&rdata); } + } + } +} + +static void +checknames(dns_message_t *message) { + checknamessection(message, DNS_SECTION_ANSWER); + checknamessection(message, DNS_SECTION_AUTHORITY); + checknamessection(message, DNS_SECTION_ADDITIONAL); +} + +/* + * Log server NSID at log level 'level' + */ +static void +log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, + int level, isc_mem_t *mctx) +{ + static const char hex[17] = "0123456789abcdef"; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_uint16_t buflen, i; + unsigned char *p, *nsid; + unsigned char *buf = NULL, *pbuf = NULL; + + /* Allocate buffer for storing hex version of the NSID */ + buflen = (isc_uint16_t)nsid_len * 2 + 1; + buf = isc_mem_get(mctx, buflen); + if (buf == NULL) + goto cleanup; + pbuf = isc_mem_get(mctx, nsid_len + 1); + if (pbuf == NULL) + goto cleanup; + + /* Convert to hex */ + p = buf; + nsid = isc_buffer_current(opt); + for (i = 0; i < nsid_len; i++) { + *p++ = hex[(nsid[i] >> 4) & 0xf]; + *p++ = hex[nsid[i] & 0xf]; + } + *p = '\0'; + + /* Make printable version */ + p = pbuf; + for (i = 0; i < nsid_len; i++) { + if (isprint(nsid[i])) + *p++ = nsid[i]; + else + *p++ = '.'; + } + *p = '\0'; + + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, + "received NSID %s (\"%s\") from %s", buf, pbuf, addrbuf); + cleanup: + if (pbuf != NULL) + isc_mem_put(mctx, pbuf, nsid_len + 1); + if (buf != NULL) + isc_mem_put(mctx, buf, buflen); +} + +static isc_boolean_t +iscname(fetchctx_t *fctx) { + isc_result_t result; + + result = dns_message_findname(fctx->rmessage, DNS_SECTION_ANSWER, + &fctx->name, dns_rdatatype_cname, 0, + NULL, NULL); + return (result == ISC_R_SUCCESS ? ISC_TRUE : ISC_FALSE); +} + +static isc_boolean_t +betterreferral(fetchctx_t *fctx) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + dns_message_t *message = fctx->rmessage; + + for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + if (!isstrictsubdomain(name, &fctx->domain)) + continue; + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + if (rdataset->type == dns_rdatatype_ns) + return (ISC_TRUE); + } + return (ISC_FALSE); +} + +/* + * resquery_response(): + * Handles responses received in response to iterative queries sent by + * resquery_send(). Sets up a response context (respctx_t). + */ +static void +resquery_response(isc_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + resquery_t *query = event->ev_arg; + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + fetchctx_t *fctx; + respctx_t rctx; + + REQUIRE(VALID_QUERY(query)); + fctx = query->fctx; + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + + QTRACE("response"); + + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) + inc_stats(fctx->res, dns_resstatscounter_responsev4); + else + inc_stats(fctx->res, dns_resstatscounter_responsev6); + + (void)isc_timer_touch(fctx->timer); + + if (fctx->res->exiting) { + result = ISC_R_SHUTTINGDOWN; + FCTXTRACE("resolver shutting down"); + rctx_done(&rctx, result); + return; + } + + fctx->timeouts = 0; + fctx->timeout = ISC_FALSE; + fctx->addrinfo = query->addrinfo; + + rctx_respinit(task, devent, query, fctx, &rctx); + + /* + * Check whether the dispatcher has failed; if so we're done + */ + result = rctx_dispfail(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + if (query->tsig != NULL) { + result = dns_message_setquerytsig(fctx->rmessage, query->tsig); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set query tsig", result); + rctx_done(&rctx, result); + return; + } + } + + if (query->tsigkey) { + result = dns_message_settsigkey(fctx->rmessage, query->tsigkey); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set tsig key", result); + rctx_done(&rctx, result); + return; + } + } + + dns_message_setclass(fctx->rmessage, fctx->res->rdclass); + + if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { + if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + dns_adb_setudpsize(fctx->adb, query->addrinfo, + isc_buffer_usedlength(&devent->buffer)); + } else { + dns_adb_plainresponse(fctx->adb, query->addrinfo); + } + } + + /* + * Parse response message. + */ + result = rctx_parse(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Log the incoming packet. + */ + rctx_logpacket(&rctx); + + if (fctx->rmessage->rdclass != fctx->res->rdclass) { + rctx.resend = ISC_TRUE; + FCTXTRACE("bad class"); + rctx_done(&rctx, result); + return; + } + + /* + * Process receive opt record. + */ + rctx.opt = dns_message_getopt(fctx->rmessage); + if (rctx.opt != NULL) { + rctx_opt(&rctx); + } + + if (fctx->rmessage->cc_bad && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * If the COOKIE is bad, assume it is an attack and + * keep listening for a good answer. + */ + rctx.nextitem = ISC_TRUE; + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, + addrbuf, sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "bad cookie from %s", addrbuf); + } + rctx_done(&rctx, result); + return; + } + + /* + * Is the question the same as the one we asked? + * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must have + * the same question. + * FORMERR/NOTIMP if they have a question section then it must match. + */ + switch (fctx->rmessage->rcode) { + case dns_rcode_notimp: + case dns_rcode_formerr: + if (fctx->rmessage->counts[DNS_SECTION_QUESTION] == 0) + break; + case dns_rcode_nxrrset: /* Not expected. */ + case dns_rcode_badcookie: + case dns_rcode_noerror: + case dns_rcode_nxdomain: + case dns_rcode_yxdomain: + case dns_rcode_refused: + case dns_rcode_servfail: + default: + result = same_question(fctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("response did not match question", result); + rctx.nextitem = ISC_TRUE; + rctx_done(&rctx, result); + return; + } + break; + } + + /* + * If the message is signed, check the signature. If not, this + * returns success anyway. + */ + result = dns_message_checksig(fctx->rmessage, fctx->res->view); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("signature check failed", result); + rctx_done(&rctx, result); + return; + } + + /* + * The dispatcher should ensure we only get responses with QR set. + */ + INSIST((fctx->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); + /* + * INSIST() that the message comes from the place we sent it to, + * since the dispatch code should ensure this. + * + * INSIST() that the message id is correct (this should also be + * ensured by the dispatch code). + */ + + rctx_edns(&rctx); + + /* + * Deal with truncated responses by retrying using TCP. + */ + if ((fctx->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) { + rctx.truncated = ISC_TRUE; + } + + if (rctx.truncated) { + inc_stats(fctx->res, dns_resstatscounter_truncated); + if ((rctx.retryopts & DNS_FETCHOPT_TCP) != 0) { + rctx.broken_server = DNS_R_TRUNCATEDTCP; + rctx.next_server = ISC_TRUE; } else { - save_name = name; - save_type = ISC_LIST_HEAD(name->list)->type; + rctx.retryopts |= DNS_FETCHOPT_TCP; + rctx.resend = ISC_TRUE; + } + FCTXTRACE3("message truncated", result); + rctx_done(&rctx, result); + return; + } + + /* + * Is it a query response? + */ + if (fctx->rmessage->opcode != dns_opcode_query) { + rctx.broken_server = DNS_R_UNEXPECTEDOPCODE; + rctx.next_server = ISC_TRUE; + FCTXTRACE("invalid message opcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Update statistics about erroneous responses. + */ + switch (fctx->rmessage->rcode) { + case dns_rcode_noerror: + /* no error */ + break; + case dns_rcode_nxdomain: + inc_stats(fctx->res, dns_resstatscounter_nxdomain); + break; + case dns_rcode_servfail: + inc_stats(fctx->res, dns_resstatscounter_servfail); + break; + case dns_rcode_formerr: + inc_stats(fctx->res, dns_resstatscounter_formerr); + break; + case dns_rcode_refused: + inc_stats(fctx->res, dns_resstatscounter_refused); + break; + case dns_rcode_badvers: + inc_stats(fctx->res, dns_resstatscounter_badvers); + break; + case dns_rcode_badcookie: + inc_stats(fctx->res, dns_resstatscounter_badcookie); + break; + default: + inc_stats(fctx->res, dns_resstatscounter_othererror); + break; + } + + /* + * Bad server? + */ + result = rctx_badserver(&rctx, result); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Lame server? + */ + result = rctx_lameserver(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Handle delegation-only zones like NET or COM. + */ + rctx_delonly_zone(&rctx); + + /* + * Optionally call dns_rdata_checkowner() and dns_rdata_checknames() + * to validate the names in the response message. + */ + if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) { + checknames(fctx->rmessage); + } + + /* + * Clear cache bits. + */ + fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE); + + /* + * Did we get any answers? + */ + if (fctx->rmessage->counts[DNS_SECTION_ANSWER] > 0 && + (fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_yxdomain || + fctx->rmessage->rcode == dns_rcode_nxdomain)) + { + result = rctx_answer(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + } else if (fctx->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 || + fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_nxdomain) + { + /* + * This might be an NXDOMAIN, NXRRSET, or referral. + * Call rctx_answer_none() to determine which it is. + */ + result = rctx_answer_none(&rctx); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_CHASEDSSERVERS: + break; + case DNS_R_DELEGATION: + result = ISC_R_SUCCESS; + break; + default: + /* + * Something has gone wrong. + */ + if (result == DNS_R_FORMERR) + rctx.next_server = ISC_TRUE; + FCTXTRACE3("rctx_answer_none", result); + rctx_done(&rctx, result); + return; + } + } else { + /* + * The server is insane. + */ + /* XXXRTH Log */ + rctx.broken_server = DNS_R_UNEXPECTEDRCODE; + rctx.next_server = ISC_TRUE; + FCTXTRACE("broken server: unexpected rcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Follow additional section data chains. + */ + rctx_additional(&rctx); + + /* + * Cache the cacheable parts of the message. This may also cause + * work to be queued to the DNSSEC validator. + */ + if (WANTCACHE(fctx)) { + result = cache_message(fctx, query->addrinfo, rctx.now); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("cache_message complete", result); + rctx_done(&rctx, result); + return; } - result = dns_message_nextname(message, section); - if (result == ISC_R_NOMORE) - break; - else if (result != ISC_R_SUCCESS) - return (result); } /* - * Trigger lookups for DNS nameservers. + * Negative caching */ - if (negative_response && message->rcode == dns_rcode_noerror && - fctx->type == dns_rdatatype_ds && soa_name != NULL && - dns_name_equal(soa_name, qname) && - !dns_name_equal(qname, dns_rootname)) - return (DNS_R_CHASEDSSERVERS); + rctx_ncache(&rctx); + + rctx_done(&rctx, result); +} + +/* + * rctx_respinit(): + * Initialize the response context structure 'rctx' to all zeroes, then set + * the task, event, query and fctx information from resquery_response(). + */ +static void +rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, + resquery_t *query, fetchctx_t *fctx, respctx_t *rctx) +{ + memset(rctx, 0, sizeof(*rctx)); + + rctx->task = task; + rctx->devent = devent; + rctx->query = query; + rctx->fctx = fctx; + rctx->broken_type = badns_response; + rctx->retryopts = query->options; /* - * Did we find anything? + * XXXRTH We should really get the current time just once. We + * need a routine to convert from an isc_time_t to an + * isc_stdtime_t. */ - if (!negative_response && ns_name == NULL) { - /* - * Nope. - */ - if (oqname != NULL) { - /* - * We've already got a partial CNAME/DNAME chain, - * and haven't found else anything useful here, but - * no error has occurred since we have an answer. - */ - return (ISC_R_SUCCESS); - } else { - /* - * The responder is insane. - */ - if (save_name == NULL) { - log_formerr(fctx, "invalid response"); - return (DNS_R_FORMERR); - } - if (!dns_name_issubdomain(save_name, &fctx->domain)) { - char nbuf[DNS_NAME_FORMATSIZE]; - char dbuf[DNS_NAME_FORMATSIZE]; - char tbuf[DNS_RDATATYPE_FORMATSIZE]; + TIME_NOW(&rctx->tnow); + rctx->finish = &rctx->tnow; + isc_stdtime_get(&rctx->now); - dns_rdatatype_format(save_type, tbuf, - sizeof(tbuf)); - dns_name_format(save_name, nbuf, sizeof(nbuf)); - dns_name_format(&fctx->domain, dbuf, - sizeof(dbuf)); +} - log_formerr(fctx, "Name %s (%s) not subdomain" - " of zone %s -- invalid response", - nbuf, tbuf, dbuf); - } else { - log_formerr(fctx, "invalid response"); - } - return (DNS_R_FORMERR); - } +/* + * rctx_answer_init(): + * Clear and reinitialize those portions of 'rctx' that will be needed + * when scanning the answer section of the response message. This can be + * called more than once if scanning needs to be restarted (though currently + * there are no cases in which this occurs). + */ +static void +rctx_answer_init(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + + rctx->aa = ISC_TF((fctx->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0); + if (rctx->aa) { + rctx->trust = dns_trust_authanswer; + } else { + rctx->trust = dns_trust_answer; } /* - * If we found both NS and SOA, they should be the same name. + * There can be multiple RRSIG and SIG records at a name so + * we treat these types as a subset of ANY. */ - if (ns_name != NULL && soa_name != NULL && ns_name != soa_name) { - log_formerr(fctx, "NS/SOA mismatch"); - return (DNS_R_FORMERR); + rctx->type = fctx->type; + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_sig) + { + rctx->type = dns_rdatatype_any; } /* - * Do we have a referral? (We only want to follow a referral if - * we're not following a chain.) + * Bigger than any valid DNAME label count. */ - if (!negative_response && ns_name != NULL && oqname == NULL) { - /* - * We already know ns_name is a subdomain of fctx->domain. - * If ns_name is equal to fctx->domain, we're not making - * progress. We return DNS_R_FORMERR so that we'll keep - * trying other servers. - */ - if (dns_name_equal(ns_name, &fctx->domain)) { - log_formerr(fctx, "non-improving referral"); - return (DNS_R_FORMERR); - } + rctx->dname_labels = dns_name_countlabels(&fctx->name); + rctx->domain_labels = dns_name_countlabels(&fctx->domain); - /* - * If the referral name is not a parent of the query - * name, consider the responder insane. - */ - if (! dns_name_issubdomain(&fctx->name, ns_name)) { - /* Logged twice */ - log_formerr(fctx, "referral to non-parent"); - FCTXTRACE("referral to non-parent"); - return (DNS_R_FORMERR); - } + rctx->found_type = dns_rdatatype_none; + rctx->aname = NULL; + rctx->ardataset = NULL; + + rctx->cname = NULL; + rctx->crdataset = NULL; + + rctx->dname = NULL; + rctx->drdataset = NULL; + + rctx->ns_name = NULL; + rctx->ns_rdataset = NULL; + + rctx->soa_name = NULL; + rctx->ds_name = NULL; + rctx->found_name = NULL; +} + +/* + * rctx_dispfail(): + * Handle the case where the dispatcher failed + */ +static isc_result_t +rctx_dispfail(respctx_t *rctx) { + dns_dispatchevent_t *devent = rctx->devent; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (devent->result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + if (devent->result == ISC_R_EOF && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { /* - * Mark any additional data related to this rdataset. - * It's important that we do this before we change the - * query domain. - */ - INSIST(ns_rdataset != NULL); - fctx->attributes |= FCTX_ATTR_GLUING; - (void)dns_rdataset_additionaldata(ns_rdataset, check_related, - fctx); -#if CHECK_FOR_GLUE_IN_ANSWER - /* - * Look in the answer section for "glue" that is incorrectly - * returned as a answer. This is needed if the server also - * minimizes the response size by not adding records to the - * additional section that are in the answer section or if - * the record gets dropped due to message size constraints. + * The problem might be that they don't understand EDNS0. + * Turn it off and try again. */ - if ((look_in_options & LOOK_FOR_GLUE_IN_ANSWER) != 0 && - (fctx->type == dns_rdatatype_aaaa || - fctx->type == dns_rdatatype_a)) - (void)dns_rdataset_additionaldata(ns_rdataset, - check_answer, fctx); -#endif - fctx->attributes &= ~FCTX_ATTR_GLUING; + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = ISC_TRUE; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + } else { /* - * NS rdatasets with 0 TTL cause problems. - * dns_view_findzonecut() will not find them when we - * try to follow the referral, and we'll SERVFAIL - * because the best nameservers are now above QDOMAIN. - * We force the TTL to 1 second to prevent this. + * There's no hope for this response. */ - if (ns_rdataset->ttl == 0) - ns_rdataset->ttl = 1; + rctx->next_server = ISC_TRUE; + /* - * Set the current query domain to the referral name. - * - * XXXRTH We should check if we're in forward-only mode, and - * if so we should bail out. + * If this is a network error on an exclusive query + * socket, mark the server as bad so that we won't try + * it for this fetch again. Also adjust finish and + * no_response so that we penalize this address in SRTT + * adjustment later. */ - INSIST(dns_name_countlabels(&fctx->domain) > 0); - fcount_decr(fctx); - dns_name_free(&fctx->domain, fctx->mctx); - if (dns_rdataset_isassociated(&fctx->nameservers)) - dns_rdataset_disassociate(&fctx->nameservers); - dns_name_init(&fctx->domain, NULL); - result = dns_name_dup(ns_name, fctx->mctx, &fctx->domain); - if (result != ISC_R_SUCCESS) - return (result); - result = fcount_incr(fctx, ISC_TRUE); - if (result != ISC_R_SUCCESS) - return (result); - fctx->attributes |= FCTX_ATTR_WANTCACHE; - fctx->ns_ttl_ok = ISC_FALSE; - log_ns_ttl(fctx, "DELEGATION"); - return (DNS_R_DELEGATION); + if (query->exclusivesocket && + (devent->result == ISC_R_HOSTUNREACH || + devent->result == ISC_R_NETUNREACH || + devent->result == ISC_R_CONNREFUSED || + devent->result == ISC_R_CANCELED)) + { + rctx->broken_server = devent->result; + rctx->broken_type = badns_unreachable; + rctx->finish = NULL; + rctx->no_response = ISC_TRUE; + } } + FCTXTRACE3("dispatcher failure", devent->result); + rctx_done(rctx, ISC_R_SUCCESS); + return (ISC_R_COMPLETE); +} - /* - * Since we're not doing a referral, we don't want to cache any - * NS RRs we may have found. - */ - if (ns_name != NULL) - ns_name->attributes &= ~DNS_NAMEATTR_CACHE; +/* + * rctx_parse(): + * Parse the response message. + */ +static isc_result_t +rctx_parse(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; - if (negative_response && oqname == NULL) - fctx->attributes |= FCTX_ATTR_WANTNCACHE; + result = dns_message_parse(fctx->rmessage, &rctx->devent->buffer, 0); + if (result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } - return (ISC_R_SUCCESS); -} + FCTXTRACE3("message failed to parse", result); + + switch (result) { + case ISC_R_UNEXPECTEDEND: + if (fctx->rmessage->question_ok && + (fctx->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0 && + (rctx->retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * We defer retrying via TCP for a bit so we can + * check out this message further. + */ + rctx->truncated = ISC_TRUE; + return (ISC_R_SUCCESS); + } -static isc_boolean_t -validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) { - if (rdataset->type == dns_rdatatype_nsec3) { /* - * NSEC3 records are not allowed to - * appear in the answer section. + * Either the message ended prematurely, + * and/or wasn't marked as being truncated, + * and/or this is a response to a query we + * sent over TCP. In all of these cases, + * something is wrong with the remote + * server and we don't want to retry using + * TCP. */ - log_formerr(fctx, "NSEC3 in answer"); - return (ISC_FALSE); - } - if (rdataset->type == dns_rdatatype_tkey) { + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = ISC_TRUE; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, + dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = result; + rctx->next_server = ISC_TRUE; + } + + rctx_done(rctx, result); + break; + case DNS_R_FORMERR: + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = ISC_TRUE; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, + dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = ISC_TRUE; + } + + rctx_done(rctx, result); + break; + default: /* - * TKEY is not a valid record in a - * response to any query we can make. + * Something bad has happened. */ - log_formerr(fctx, "TKEY in answer"); - return (ISC_FALSE); - } - if (rdataset->rdclass != fctx->res->rdclass) { - log_formerr(fctx, "Mismatched class in answer"); - return (ISC_FALSE); + rctx_done(rctx, result); + break; } - return (ISC_TRUE); + + return (ISC_R_COMPLETE); } -static isc_result_t -answer_response(fetchctx_t *fctx) { +/* + * rctx_opt(): + * Process the OPT record in the response. + */ +static void +rctx_opt(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_rdata_t rdata; + isc_buffer_t optbuf; isc_result_t result; - dns_message_t *message = NULL; - dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL; - dns_name_t *aname = NULL, *cname = NULL, *dname = NULL; - dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; - dns_rdataset_t *ardataset = NULL, *crdataset = NULL; - dns_rdataset_t *drdataset = NULL, *ns_rdataset = NULL; - isc_boolean_t done = ISC_FALSE, aa; - unsigned int dname_labels, domain_labels; - isc_boolean_t chaining = ISC_FALSE; - dns_rdatatype_t type; - dns_view_t *view = NULL; - dns_trust_t trust; + isc_uint16_t optcode; + isc_uint16_t optlen; + unsigned char *optvalue; + dns_adbaddrinfo_t *addrinfo; + unsigned char cookie[8]; + isc_boolean_t seen_cookie = ISC_FALSE; + isc_boolean_t seen_nsid = ISC_FALSE; - REQUIRE(VALID_FCTX(fctx)); + result = dns_rdataset_first(rctx->opt); + if (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(rctx->opt, &rdata); + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) >= 4) { + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(optlen <= isc_buffer_remaininglength(&optbuf)); + switch (optcode) { + case DNS_OPT_NSID: + if (!seen_nsid && + (query->options & + DNS_FETCHOPT_WANTNSID) != 0) + { + log_nsid(&optbuf, optlen, query, + ISC_LOG_DEBUG(3), + fctx->res->mctx); + } + isc_buffer_forward(&optbuf, optlen); + seen_nsid = ISC_TRUE; + break; + case DNS_OPT_COOKIE: + /* + * Only process the first cookie option. + */ + if (seen_cookie) { + isc_buffer_forward(&optbuf, optlen); + break; + } + optvalue = isc_buffer_current(&optbuf); + compute_cc(query, cookie, sizeof(cookie)); + INSIST(fctx->rmessage->cc_bad == 0 && + fctx->rmessage->cc_ok == 0); + if (optlen >= 8U && + memcmp(cookie, optvalue, 8) == 0) { + fctx->rmessage->cc_ok = 1; + inc_stats(fctx->res, + dns_resstatscounter_cookieok); + addrinfo = query->addrinfo; + dns_adb_setcookie(fctx->adb, + addrinfo, optvalue, + optlen); + } else + fctx->rmessage->cc_bad = 1; + isc_buffer_forward(&optbuf, optlen); + inc_stats(fctx->res, + dns_resstatscounter_cookiein); + seen_cookie = ISC_TRUE; + break; + default: + isc_buffer_forward(&optbuf, optlen); + break; + } + } + INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + } +} - FCTXTRACE("answer_response"); +/* + * rctx_edns(): + * Determine whether the remote server is using EDNS correctly or + * incorrectly and record that information if needed. + */ +static void +rctx_edns(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; - message = fctx->rmessage; - qname = &fctx->name; - view = fctx->res->view; - type = fctx->type; + /* + * We have an affirmative response to the query and we have + * previously got a response from this server which indicated + * EDNS may not be supported so we can now cache the lack of + * EDNS support. + */ + if (rctx->opt == NULL && !EDNSOK(query->addrinfo) && + (fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_nxdomain || + fctx->rmessage->rcode == dns_rcode_refused || + fctx->rmessage->rcode == dns_rcode_yxdomain) && + bad_edns(fctx, &query->addrinfo->sockaddr)) + { + dns_message_logpacket2(fctx->rmessage, + "received packet (bad edns) from", + &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, + ISC_LOG_DEBUG(3), + fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, + DNS_FETCHOPT_NOEDNS0); + } else if (rctx->opt == NULL && + (fctx->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 && + !EDNSOK(query->addrinfo) && + (fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_nxdomain) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * We didn't get a OPT record in response to a EDNS query. + * + * Old versions of named incorrectly drop the OPT record + * when there is a signed, truncated response so we check + * that TC is not set. + * + * Record that the server is not talking EDNS. While this + * should be safe to do for any rcode we limit it to NOERROR + * and NXDOMAIN. + */ + dns_message_logpacket2(fctx->rmessage, + "received packet (no opt) from", + &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, + ISC_LOG_DEBUG(3), fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, + DNS_FETCHOPT_NOEDNS0); + } /* - * There can be multiple RRSIG and SIG records at a name so - * we treat these types as a subset of ANY. + * If we get a non error EDNS response record the fact so we + * won't fallback to plain DNS in the future for this server. */ - if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) { - type = dns_rdatatype_any; + if (rctx->opt != NULL && !EDNSOK(query->addrinfo) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 && + (fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_nxdomain || + fctx->rmessage->rcode == dns_rcode_refused || + fctx->rmessage->rcode == dns_rcode_yxdomain)) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, + FCTX_ADDRINFO_EDNSOK); } +} - /* - * Bigger than any valid DNAME label count. - */ - dname_labels = dns_name_countlabels(qname); - domain_labels = dns_name_countlabels(&fctx->domain); +/* + * rctx_answer(): + * We might have answers, or we might have a malformed delegation with + * records in the answer section. Call rctx_answer_positive() or + * rctx_answer_none() as appropriate. + */ +static isc_result_t +rctx_answer(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; - /* - * Perform a single pass looking for the answer, cname or covering - * dname. - */ - for (result = dns_message_firstname(message, DNS_SECTION_ANSWER); - result == ISC_R_SUCCESS; - result = dns_message_nextname(message, DNS_SECTION_ANSWER)) + if ((fctx->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 || + ISFORWARDER(query->addrinfo)) { - int order; - unsigned int nlabels; - dns_namereln_t namereln; + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (AA/fwd)", result); + } + } else if (iscname(fctx) && + fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_cname) + { + /* + * A BIND8 server could return a non-authoritative + * answer when a CNAME is followed. We should treat + * it as a valid answer. + */ + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)", + result); + } + } else if (fctx->type != dns_rdatatype_ns && !betterreferral(fctx)) { + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!NS)", result); + } + } else { + /* + * This may be a delegation. First let's check for + */ - name = NULL; - dns_message_currentname(message, DNS_SECTION_ANSWER, &name); - namereln = dns_name_fullcompare(qname, name, &order, &nlabels); - switch (namereln) { - case dns_namereln_equal: - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) - { - if (rdataset->type == type || - type == dns_rdatatype_any) - { - aname = name; - if (type != dns_rdatatype_any) { - ardataset = rdataset; - } - break; - } - if (rdataset->type == dns_rdatatype_cname) { - cname = name; - crdataset = rdataset; - break; - } + if (fctx->type == dns_rdatatype_ns) { + /* + * A BIND 8 server could incorrectly return a + * non-authoritative answer to an NS query + * instead of a referral. Since this answer + * lacks the SIGs necessary to do DNSSEC + * validation, we must invoke the following + * special kludge to treat it as a referral. + */ + rctx->ns_in_answer = ISC_TRUE; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none (NS)", result); } - break; - - case dns_namereln_subdomain: + } else { /* - * In-scope DNAME records must have at least - * as many labels as the domain being queried. - * They also must be less that qname's labels - * and any previously found dname. + * Some other servers may still somehow include + * an answer when it should return a referral + * with an empty answer. Check to see if we can + * treat this as a referral by ignoring the + * answer. Further more, there may be an + * implementation that moves A/AAAA glue records + * to the answer section for that type of + * delegation when the query is for that glue + * record. glue_in_answer will handle + * such a corner case. */ - if (nlabels >= dname_labels || nlabels < domain_labels) - { - continue; + rctx->glue_in_answer = ISC_TRUE; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none", result); } + } + if (result != DNS_R_DELEGATION) { /* - * We are looking for the shortest DNAME if there - * are multiple ones (which there shouldn't be). + * At this point, AA is not set, the response + * is not a referral, and the server is not a + * forwarder. It is technically lame and it's + * easier to treat it as such than to figure out + * some more elaborate course of action. */ - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) - { - if (rdataset->type != dns_rdatatype_dname) { - continue; - } - dname = name; - drdataset = rdataset; - dname_labels = nlabels; - break; - } - break; - default: - break; + rctx->broken_server = DNS_R_LAME; + rctx->next_server = ISC_TRUE; + rctx_done(rctx, result); + return (ISC_R_COMPLETE); } } - if (dname != NULL) { - aname = NULL; - ardataset = NULL; - cname = NULL; - crdataset = NULL; - } else if (aname != NULL) { - cname = NULL; - crdataset = NULL; + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_FORMERR) { + rctx->next_server = ISC_TRUE; + } + rctx_done(rctx, result); + return (ISC_R_COMPLETE); } - aa = ISC_TF((message->flags & DNS_MESSAGEFLAG_AA) != 0); - trust = aa ? dns_trust_authanswer : dns_trust_answer; + return (ISC_R_SUCCESS); +} - if (aname != NULL && type == dns_rdatatype_any) { - for (rdataset = ISC_LIST_HEAD(aname->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) - { - if (!validinanswer(rdataset, fctx)) { - return (DNS_R_FORMERR); - } - if ((fctx->type == dns_rdatatype_sig || - fctx->type == dns_rdatatype_rrsig) && - rdataset->type != fctx->type) - { - continue; - } - if ((rdataset->type == dns_rdatatype_a || - rdataset->type == dns_rdatatype_aaaa) && - !is_answeraddress_allowed(view, aname, rdataset)) - { - return (DNS_R_SERVFAIL); - } - if ((rdataset->type == dns_rdatatype_cname || - rdataset->type == dns_rdatatype_dname) && - !is_answertarget_allowed(fctx, qname, aname, - rdataset, NULL)) - { - return (DNS_R_SERVFAIL); - } - aname->attributes |= DNS_NAMEATTR_CACHE; - aname->attributes |= DNS_NAMEATTR_ANSWER; - rdataset->attributes |= DNS_RDATASETATTR_ANSWER; - rdataset->attributes |= DNS_RDATASETATTR_CACHE; - rdataset->trust = trust; - (void)dns_rdataset_additionaldata(rdataset, - check_related, - fctx); - } - } else if (aname != NULL) { - if (!validinanswer(ardataset, fctx)) - return (DNS_R_FORMERR); - if ((ardataset->type == dns_rdatatype_a || - ardataset->type == dns_rdatatype_aaaa) && - !is_answeraddress_allowed(view, aname, ardataset)) { - return (DNS_R_SERVFAIL); - } - if ((ardataset->type == dns_rdatatype_cname || - ardataset->type == dns_rdatatype_dname) && - !is_answertarget_allowed(fctx, qname, aname, ardataset, - NULL)) - { - return (DNS_R_SERVFAIL); - } - aname->attributes |= DNS_NAMEATTR_CACHE; - aname->attributes |= DNS_NAMEATTR_ANSWER; - ardataset->attributes |= DNS_RDATASETATTR_ANSWER; - ardataset->attributes |= DNS_RDATASETATTR_CACHE; - ardataset->trust = trust; - (void)dns_rdataset_additionaldata(ardataset, check_related, - fctx); - for (sigrdataset = ISC_LIST_HEAD(aname->list); - sigrdataset != NULL; - sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { - if (!validinanswer(sigrdataset, fctx)) - return (DNS_R_FORMERR); - if (sigrdataset->type != dns_rdatatype_rrsig || - sigrdataset->covers != type) - continue; - sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; - sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; - sigrdataset->trust = trust; - break; - } - } else if (cname != NULL) { - if (!validinanswer(crdataset, fctx)) { - return (DNS_R_FORMERR); - } - if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key || - type == dns_rdatatype_nsec) - { - char buf[DNS_RDATATYPE_FORMATSIZE]; - dns_rdatatype_format(type, buf, sizeof(buf)); - log_formerr(fctx, "CNAME response for %s RR", buf); - return (DNS_R_FORMERR); +/* + * rctx_answer_positive(): + * Handles positive responses. Depending which type of answer this is + * (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine + * to handle it (rctx_answer_match(), rctx_answer_cname(), + * rctx_answer_dname(), rctx_answer_any()). + */ +static isc_result_t +rctx_answer_positive(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + FCTXTRACE("rctx_answer"); + + rctx_answer_init(rctx); + rctx_answer_scan(rctx); + + /* + * Determine which type of positive answer this is: + * type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE. + * Call the appropriate routine to handle the answer type. + */ + if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) { + result = rctx_answer_any(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); } - if (!is_answertarget_allowed(fctx, qname, cname, crdataset, - NULL)) - { - return (DNS_R_SERVFAIL); - } - cname->attributes |= DNS_NAMEATTR_CACHE; - cname->attributes |= DNS_NAMEATTR_ANSWER; - cname->attributes |= DNS_NAMEATTR_CHAINING; - crdataset->attributes |= DNS_RDATASETATTR_ANSWER; - crdataset->attributes |= DNS_RDATASETATTR_CACHE; - crdataset->attributes |= DNS_RDATASETATTR_CHAINING; - crdataset->trust = trust; - for (sigrdataset = ISC_LIST_HEAD(cname->list); - sigrdataset != NULL; - sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) - { - if (!validinanswer(sigrdataset, fctx)) { - return (DNS_R_FORMERR); - } - if (sigrdataset->type != dns_rdatatype_rrsig || - sigrdataset->covers != dns_rdatatype_cname) - { - continue; - } - sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; - sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; - sigrdataset->trust = trust; - break; + } else if (rctx->aname != NULL) { + result = rctx_answer_match(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); } - chaining = ISC_TRUE; - } else if (dname != NULL) { - if (!validinanswer(drdataset, fctx)) { - return (DNS_R_FORMERR); + } else if (rctx->cname != NULL) { + result = rctx_answer_cname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); } - if (!is_answertarget_allowed(fctx, qname, dname, drdataset, - &chaining)) { - return (DNS_R_SERVFAIL); - } - dname->attributes |= DNS_NAMEATTR_CACHE; - dname->attributes |= DNS_NAMEATTR_ANSWER; - dname->attributes |= DNS_NAMEATTR_CHAINING; - drdataset->attributes |= DNS_RDATASETATTR_ANSWER; - drdataset->attributes |= DNS_RDATASETATTR_CACHE; - drdataset->attributes |= DNS_RDATASETATTR_CHAINING; - drdataset->trust = trust; - for (sigrdataset = ISC_LIST_HEAD(dname->list); - sigrdataset != NULL; - sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) - { - if (!validinanswer(sigrdataset, fctx)) { - return (DNS_R_FORMERR); - } - if (sigrdataset->type != dns_rdatatype_rrsig || - sigrdataset->covers != dns_rdatatype_dname) - { - continue; - } - sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; - sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; - sigrdataset->trust = trust; - break; + } else if (rctx->dname != NULL) { + result = rctx_answer_dname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); } } else { log_formerr(fctx, "reply has no answer"); @@ -7138,7 +7761,7 @@ answer_response(fetchctx_t *fctx) { /* * Did chaining end before we got the final answer? */ - if (chaining) { + if (rctx->chaining) { return (ISC_R_SUCCESS); } @@ -7146,1317 +7769,1241 @@ answer_response(fetchctx_t *fctx) { * We didn't end with an incomplete chain, so the rcode should be * "no error". */ - if (message->rcode != dns_rcode_noerror) { + if (fctx->rmessage->rcode != dns_rcode_noerror) { log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE " "indicates error"); return (DNS_R_FORMERR); } /* - * Examine the authority section (if there is one). - * - * We expect there to be only one owner name for all the rdatasets - * in this section, and we expect that it is not external. + * Cache records in the authority section, if + * there are any suitable for caching. */ - result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); - while (!done && result == ISC_R_SUCCESS) { - isc_boolean_t external; - name = NULL; - dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); - external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); - if (!external) { - /* - * We expect to find NS or SIG NS rdatasets, and - * nothing else. - */ - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - if (rdataset->type == dns_rdatatype_ns || - (rdataset->type == dns_rdatatype_rrsig && - rdataset->covers == dns_rdatatype_ns)) { - name->attributes |= - DNS_NAMEATTR_CACHE; - rdataset->attributes |= - DNS_RDATASETATTR_CACHE; - if (aa && !chaining) { - rdataset->trust = - dns_trust_authauthority; - } else { - rdataset->trust = - dns_trust_additional; - } - - if (rdataset->type == dns_rdatatype_ns) - { - ns_name = name; - ns_rdataset = rdataset; - } - /* - * Mark any additional data related - * to this rdataset. - */ - (void)dns_rdataset_additionaldata( - rdataset, - check_related, - fctx); - done = ISC_TRUE; - } - } - } - result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); - } - if (result == ISC_R_NOMORE) - result = ISC_R_SUCCESS; + rctx_authority_positive(rctx); - log_ns_ttl(fctx, "answer_response"); + log_ns_ttl(fctx, "rctx_answer"); - if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && - !dns_name_equal(ns_name, dns_rootname)) - trim_ns_ttl(fctx, ns_name, ns_rdataset); + if (rctx->ns_rdataset != NULL && + dns_name_equal(&fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); + } - return (result); + return (ISC_R_SUCCESS); } +/* + * rctx_answer_scan(): + * Perform a single pass over the answer section of a response, looking + * for an answer that matches QNAME/QTYPE, or a CNAME matching QNAME, or a + * covering DNAME. If more than one rdataset is found matching these + * criteria, then only one is kept. Order of preference is 1) the + * shortest DNAME, 2) the first matching answer, or 3) the first CNAME. + */ static void -fctx_increference(fetchctx_t *fctx) { - REQUIRE(VALID_FCTX(fctx)); - - LOCK(&fctx->res->buckets[fctx->bucketnum].lock); - fctx->references++; - UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); -} +rctx_answer_scan(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_rdataset_t *rdataset = NULL; -static isc_boolean_t -fctx_decreference(fetchctx_t *fctx) { - isc_boolean_t bucket_empty = ISC_FALSE; + for (result = dns_message_firstname(fctx->rmessage, DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(fctx->rmessage, DNS_SECTION_ANSWER)) + { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + dns_name_t *name = NULL; - REQUIRE(VALID_FCTX(fctx)); + dns_message_currentname(fctx->rmessage, DNS_SECTION_ANSWER, + &name); + namereln = dns_name_fullcompare(&fctx->name, name, &order, + &nlabels); + switch (namereln) { + case dns_namereln_equal: + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == rctx->type || + rctx->type == dns_rdatatype_any) + { + rctx->aname = name; + if (rctx->type != dns_rdatatype_any) { + rctx->ardataset = rdataset; + } + break; + } + if (rdataset->type == dns_rdatatype_cname) { + rctx->cname = name; + rctx->crdataset = rdataset; + break; + } + } + break; - INSIST(fctx->references > 0); - fctx->references--; - if (fctx->references == 0) { - /* - * No one cares about the result of this fetch anymore. - */ - if (fctx->pending == 0 && fctx->nqueries == 0 && - ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) { + case dns_namereln_subdomain: /* - * This fctx is already shutdown; we were just - * waiting for the last reference to go away. + * In-scope DNAME records must have at least + * as many labels as the domain being queried. + * They also must be less that qname's labels + * and any previously found dname. */ - bucket_empty = fctx_unlink(fctx); - fctx_destroy(fctx); - } else { + if (nlabels >= rctx->dname_labels || + nlabels < rctx->domain_labels) + { + continue; + } + /* - * Initiate shutdown. + * We are looking for the shortest DNAME if there + * are multiple ones (which there shouldn't be). */ - fctx_shutdown(fctx); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_dname) { + continue; + } + rctx->dname = name; + rctx->drdataset = rdataset; + rctx->dname_labels = nlabels; + break; + } + break; + default: + break; } } - return (bucket_empty); -} -static void -resume_dslookup(isc_task_t *task, isc_event_t *event) { - dns_fetchevent_t *fevent; - dns_resolver_t *res; - fetchctx_t *fctx; - isc_result_t result; - isc_boolean_t bucket_empty; - isc_boolean_t locked = ISC_FALSE; - unsigned int bucketnum; - dns_rdataset_t nameservers; - dns_fixedname_t fixed; - dns_name_t *domain; + /* + * If a DNAME was found, then any CNAME or other answer matching + * QNAME that may also have been found must be ignored. Similarly, + * if a matching answer was found along with a CNAME, the CNAME + * must be ignored. + */ + if (rctx->dname != NULL) { + rctx->aname = NULL; + rctx->ardataset = NULL; + rctx->cname = NULL; + rctx->crdataset = NULL; + } else if (rctx->aname != NULL) { + rctx->cname = NULL; + rctx->crdataset = NULL; + } +} - REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); - fevent = (dns_fetchevent_t *)event; - fctx = event->ev_arg; - REQUIRE(VALID_FCTX(fctx)); - res = fctx->res; +/* + * rctx_answer_any(): + * Handle responses to queries of type ANY. Scan the answer section, + * and as long as each RRset is of a type that is valid in the answer + * section, and the rdata isn't filtered, cache it. + */ +static isc_result_t +rctx_answer_any(respctx_t *rctx) { + dns_rdataset_t *rdataset = NULL; + fetchctx_t *fctx = rctx->fctx; - UNUSED(task); - FCTXTRACE("resume_dslookup"); + for (rdataset = ISC_LIST_HEAD(rctx->aname->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!validinanswer(rdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } - if (fevent->node != NULL) - dns_db_detachnode(fevent->db, &fevent->node); - if (fevent->db != NULL) - dns_db_detach(&fevent->db); + if ((fctx->type == dns_rdatatype_sig || + fctx->type == dns_rdatatype_rrsig) && + rdataset->type != fctx->type) + { + continue; + } - dns_rdataset_init(&nameservers); + if ((rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, + rctx->aname, rdataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } - bucketnum = fctx->bucketnum; - if (fevent->result == ISC_R_CANCELED) { - dns_resolver_destroyfetch(&fctx->nsfetch); - fctx_done(fctx, ISC_R_CANCELED, __LINE__); - } else if (fevent->result == ISC_R_SUCCESS) { + if ((rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, &fctx->name, rctx->aname, + rdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } - FCTXTRACE("resuming DS lookup"); + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = rctx->trust; - dns_resolver_destroyfetch(&fctx->nsfetch); - if (dns_rdataset_isassociated(&fctx->nameservers)) - dns_rdataset_disassociate(&fctx->nameservers); - dns_rdataset_clone(fevent->rdataset, &fctx->nameservers); - fctx->ns_ttl = fctx->nameservers.ttl; - fctx->ns_ttl_ok = ISC_TRUE; - log_ns_ttl(fctx, "resume_dslookup"); + (void)dns_rdataset_additionaldata(rdataset, check_related, + fctx); + } - fcount_decr(fctx); - dns_name_free(&fctx->domain, fctx->mctx); - dns_name_init(&fctx->domain, NULL); - result = dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - goto cleanup; - } - result = fcount_incr(fctx, ISC_TRUE); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - goto cleanup; - } - /* - * Try again. - */ - fctx_try(fctx, ISC_TRUE, ISC_FALSE); - } else { - unsigned int n; - dns_rdataset_t *nsrdataset = NULL; + return (ISC_R_SUCCESS); +} - /* - * Retrieve state from fctx->nsfetch before we destroy it. - */ - dns_fixedname_init(&fixed); - domain = dns_fixedname_name(&fixed); - dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL); - if (dns_name_equal(&fctx->nsname, domain)) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - dns_resolver_destroyfetch(&fctx->nsfetch); - goto cleanup; - } - if (dns_rdataset_isassociated( - &fctx->nsfetch->private->nameservers)) { - dns_rdataset_clone( - &fctx->nsfetch->private->nameservers, - &nameservers); - nsrdataset = &nameservers; - } else - domain = NULL; - dns_resolver_destroyfetch(&fctx->nsfetch); - n = dns_name_countlabels(&fctx->nsname); - dns_name_getlabelsequence(&fctx->nsname, 1, n - 1, - &fctx->nsname); +/* + * rctx_answer_match(): + * Handle responses that match the QNAME/QTYPE of the resolver query. + * If QTYPE is valid in the answer section and the rdata isn't filtered, + * the answer can be cached. If there is additional section data related + * to the answer, it can be cached as well. + */ +static isc_result_t +rctx_answer_match(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; - if (dns_rdataset_isassociated(fevent->rdataset)) - dns_rdataset_disassociate(fevent->rdataset); - FCTXTRACE("continuing to look for parent's NS records"); - result = dns_resolver_createfetch(fctx->res, &fctx->nsname, - dns_rdatatype_ns, domain, - nsrdataset, NULL, - fctx->options, task, - resume_dslookup, fctx, - &fctx->nsrrset, NULL, - &fctx->nsfetch); - if (result != ISC_R_SUCCESS) - fctx_done(fctx, result, __LINE__); - else { - LOCK(&res->buckets[bucketnum].lock); - locked = ISC_TRUE; - fctx->references++; - } + if (!validinanswer(rctx->ardataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); } - cleanup: - if (dns_rdataset_isassociated(&nameservers)) - dns_rdataset_disassociate(&nameservers); - if (dns_rdataset_isassociated(fevent->rdataset)) - dns_rdataset_disassociate(fevent->rdataset); - INSIST(fevent->sigrdataset == NULL); - isc_event_free(&event); - if (!locked) - LOCK(&res->buckets[bucketnum].lock); - bucket_empty = fctx_decreference(fctx); - UNLOCK(&res->buckets[bucketnum].lock); - if (bucket_empty) - empty_bucket(res); -} + if ((rctx->ardataset->type == dns_rdatatype_a || + rctx->ardataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, rctx->aname, + rctx->ardataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + if ((rctx->ardataset->type == dns_rdatatype_cname || + rctx->ardataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, &fctx->name, rctx->aname, + rctx->ardataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } -static inline void -checknamessection(dns_message_t *message, dns_section_t section) { - isc_result_t result; - dns_name_t *name; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_t *rdataset; + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->ardataset->trust = rctx->trust; + (void)dns_rdataset_additionaldata(rctx->ardataset, check_related, fctx); - for (result = dns_message_firstname(message, section); - result == ISC_R_SUCCESS; - result = dns_message_nextname(message, section)) + for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { - name = NULL; - dns_message_currentname(message, section, &name); - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - dns_rdataset_current(rdataset, &rdata); - if (!dns_rdata_checkowner(name, rdata.rdclass, - rdata.type, - ISC_FALSE) || - !dns_rdata_checknames(&rdata, name, NULL)) - { - rdataset->attributes |= - DNS_RDATASETATTR_CHECKNAMES; - } - dns_rdata_reset(&rdata); - } + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); } - } -} -static void -checknames(dns_message_t *message) { + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != rctx->type) + { + continue; + } - checknamessection(message, DNS_SECTION_ANSWER); - checknamessection(message, DNS_SECTION_AUTHORITY); - checknamessection(message, DNS_SECTION_ADDITIONAL); + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + return (ISC_R_SUCCESS); } /* - * Log server NSID at log level 'level' + * rctx_answer_cname(): + * Handle answers containing a CNAME. Cache the CNAME, and flag that + * there may be additional chain answers to find. */ -static void -log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, - int level, isc_mem_t *mctx) -{ - static const char hex[17] = "0123456789abcdef"; - char addrbuf[ISC_SOCKADDR_FORMATSIZE]; - isc_uint16_t buflen, i; - unsigned char *p, *nsid; - unsigned char *buf = NULL, *pbuf = NULL; +static isc_result_t +rctx_answer_cname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; - /* Allocate buffer for storing hex version of the NSID */ - buflen = (isc_uint16_t)nsid_len * 2 + 1; - buf = isc_mem_get(mctx, buflen); - if (buf == NULL) - goto cleanup; - pbuf = isc_mem_get(mctx, nsid_len + 1); - if (pbuf == NULL) - goto cleanup; + if (!validinanswer(rctx->crdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } - /* Convert to hex */ - p = buf; - nsid = isc_buffer_current(opt); - for (i = 0; i < nsid_len; i++) { - *p++ = hex[(nsid[i] >> 4) & 0xf]; - *p++ = hex[nsid[i] & 0xf]; + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_key || + rctx->type == dns_rdatatype_nsec) + { + char buf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(rctx->type, buf, sizeof(buf)); + log_formerr(fctx, "CNAME response for %s RR", buf); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); } - *p = '\0'; - /* Make printable version */ - p = pbuf; - for (i = 0; i < nsid_len; i++) { - if (isprint(nsid[i])) - *p++ = nsid[i]; - else - *p++ = '.'; + if (!is_answertarget_allowed(fctx, &fctx->name, + rctx->cname, rctx->crdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); } - *p = '\0'; - isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, - sizeof(addrbuf)); - isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, level, - "received NSID %s (\"%s\") from %s", buf, pbuf, addrbuf); - cleanup: - if (pbuf != NULL) - isc_mem_put(mctx, pbuf, nsid_len + 1); - if (buf != NULL) - isc_mem_put(mctx, buf, buflen); + rctx->cname->attributes |= DNS_NAMEATTR_CACHE; + rctx->cname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->cname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->crdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_cname) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + rctx->chaining = ISC_TRUE; + return (ISC_R_SUCCESS); } -static isc_boolean_t -iscname(fetchctx_t *fctx) { - isc_result_t result; +/* + * rctx_answer_dname(): + * Handle responses with covering DNAME records. + */ +static isc_result_t +rctx_answer_dname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->drdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); - result = dns_message_findname(fctx->rmessage, DNS_SECTION_ANSWER, - &fctx->name, dns_rdatatype_cname, 0, - NULL, NULL); - return (result == ISC_R_SUCCESS ? ISC_TRUE : ISC_FALSE); -} + } -static isc_boolean_t -betterreferral(fetchctx_t *fctx) { - isc_result_t result; - dns_name_t *name; - dns_rdataset_t *rdataset; - dns_message_t *message = fctx->rmessage; + if (!is_answertarget_allowed(fctx, &fctx->name, rctx->dname, + rctx->drdataset, &rctx->chaining)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } - for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); - result == ISC_R_SUCCESS; - result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) { - name = NULL; - dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); - if (!isstrictsubdomain(name, &fctx->domain)) + rctx->dname->attributes |= DNS_NAMEATTR_CACHE; + rctx->dname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->dname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->drdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_dname) + { continue; - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) - if (rdataset->type == dns_rdatatype_ns) - return (ISC_TRUE); + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; } - return (ISC_FALSE); + + return (ISC_R_SUCCESS); } +/* + * rctx_authority_positive(): + * Examine the records in the authority section (if there are any) for a + * positive answer. We expect the names for all rdatasets in this section + * to be subdomains of the domain being queried; any that are not are + * skipped. We expect to find only *one* owner name; any names + * after the first one processed are ignored. We expect to find only + * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever + * remains can be cached at trust level authauthority or additional + * (depending on whether the AA bit was set on the answer). + */ static void -process_opt(resquery_t *query, dns_rdataset_t *opt) { - dns_rdata_t rdata; - isc_buffer_t optbuf; +rctx_authority_positive(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + isc_boolean_t done = ISC_FALSE; isc_result_t result; - isc_uint16_t optcode; - isc_uint16_t optlen; - unsigned char *optvalue; - dns_adbaddrinfo_t *addrinfo; - unsigned char cookie[8]; - isc_boolean_t seen_cookie = ISC_FALSE; - isc_boolean_t seen_nsid = ISC_FALSE; - result = dns_rdataset_first(opt); - if (result == ISC_R_SUCCESS) { - dns_rdata_init(&rdata); - dns_rdataset_current(opt, &rdata); - isc_buffer_init(&optbuf, rdata.data, rdata.length); - isc_buffer_add(&optbuf, rdata.length); - while (isc_buffer_remaininglength(&optbuf) >= 4) { - optcode = isc_buffer_getuint16(&optbuf); - optlen = isc_buffer_getuint16(&optbuf); - INSIST(optlen <= isc_buffer_remaininglength(&optbuf)); - switch (optcode) { - case DNS_OPT_NSID: - if (!seen_nsid && - query->options & DNS_FETCHOPT_WANTNSID) - log_nsid(&optbuf, optlen, query, - ISC_LOG_DEBUG(3), - query->fctx->res->mctx); - isc_buffer_forward(&optbuf, optlen); - seen_nsid = ISC_TRUE; - break; - case DNS_OPT_COOKIE: - /* - * Only process the first cookie option. - */ - if (seen_cookie) { - isc_buffer_forward(&optbuf, optlen); - break; + result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY); + while (!done && result == ISC_R_SUCCESS) { + dns_name_t *name = NULL; + isc_boolean_t external; + + dns_message_currentname(fctx->rmessage, DNS_SECTION_AUTHORITY, + &name); + external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); + + if (!external) { + dns_rdataset_t *rdataset = NULL; + + /* + * We expect to find NS or SIG NS rdatasets, and + * nothing else. + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_ns || + (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == dns_rdatatype_ns)) + { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else { + rdataset->trust = + dns_trust_additional; + } + + if (rdataset->type == dns_rdatatype_ns) + { + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; + } + /* + * Mark any additional data related + * to this rdataset. + */ + (void)dns_rdataset_additionaldata( + rdataset, + check_related, + fctx); + done = ISC_TRUE; } - optvalue = isc_buffer_current(&optbuf); - compute_cc(query, cookie, sizeof(cookie)); - INSIST(query->fctx->rmessage->cc_bad == 0 && - query->fctx->rmessage->cc_ok == 0); - if (optlen >= 8U && - memcmp(cookie, optvalue, 8) == 0) { - query->fctx->rmessage->cc_ok = 1; - inc_stats(query->fctx->res, - dns_resstatscounter_cookieok); - addrinfo = query->addrinfo; - dns_adb_setcookie(query->fctx->adb, - addrinfo, optvalue, - optlen); - } else - query->fctx->rmessage->cc_bad = 1; - isc_buffer_forward(&optbuf, optlen); - inc_stats(query->fctx->res, - dns_resstatscounter_cookiein); - seen_cookie = ISC_TRUE; - break; - default: - isc_buffer_forward(&optbuf, optlen); - break; } } - INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + + result = dns_message_nextname(fctx->rmessage, + DNS_SECTION_AUTHORITY); } } -static void -resquery_response(isc_task_t *task, isc_event_t *event) { - isc_result_t result = ISC_R_SUCCESS; - resquery_t *query = event->ev_arg; - dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; - isc_boolean_t keep_trying, get_nameservers, resend, nextitem; - isc_boolean_t truncated; - dns_message_t *message; - dns_rdataset_t *opt; - fetchctx_t *fctx; - dns_name_t *fname; - dns_fixedname_t foundname; - isc_stdtime_t now; - isc_time_t tnow, *finish; - dns_adbaddrinfo_t *addrinfo; - unsigned int options; - unsigned int findoptions; - isc_result_t broken_server; - badnstype_t broken_type = badns_response; - isc_boolean_t no_response; - unsigned int bucketnum; - dns_resolver_t *res; - isc_boolean_t bucket_empty; -#ifdef HAVE_DNSTAP - isc_socket_t *sock = NULL; - isc_sockaddr_t localaddr, *la = NULL; - unsigned char zone[DNS_NAME_MAXWIRE]; - dns_dtmsgtype_t dtmsgtype; - dns_compress_t cctx; - isc_region_t zr; - isc_buffer_t zb; -#endif /* HAVE_DNSTAP */ +/* + * rctx_answer_none(): + * Handles a response without an answer: this is either a negative + * response (NXDOMAIN or NXRRSET) or a referral. Determine which it is, + * then either scan the authority section for negative caching and + * DNSSEC proof of nonexistence, or else call rctx_referral(). + */ +static isc_result_t +rctx_answer_none(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; - REQUIRE(VALID_QUERY(query)); - fctx = query->fctx; - options = query->options; - REQUIRE(VALID_FCTX(fctx)); - REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + FCTXTRACE("rctx_answer_none"); - QTRACE("response"); + rctx_answer_init(rctx); - res = fctx->res; - if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) - inc_stats(res, dns_resstatscounter_responsev4); - else - inc_stats(res, dns_resstatscounter_responsev6); + /* + * Sometimes we can tell if its a negative response by looking at + * the message header. + */ + if (fctx->rmessage->rcode == dns_rcode_nxdomain || + (fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0 && + fctx->rmessage->counts[DNS_SECTION_AUTHORITY] == 0)) + { + rctx->negative = ISC_TRUE; + } - (void)isc_timer_touch(fctx->timer); + /* + * Process the authority section + */ + result = rctx_authority_negative(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } - keep_trying = ISC_FALSE; - broken_server = ISC_R_SUCCESS; - get_nameservers = ISC_FALSE; - resend = ISC_FALSE; - nextitem = ISC_FALSE; - truncated = ISC_FALSE; - finish = NULL; - no_response = ISC_FALSE; + log_ns_ttl(fctx, "rctx_answer_none"); - if (res->exiting) { - result = ISC_R_SHUTTINGDOWN; - FCTXTRACE("resolver shutting down"); - goto done; + if (rctx->ns_rdataset != NULL && + dns_name_equal(&fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); } - fctx->timeouts = 0; - fctx->timeout = ISC_FALSE; - fctx->addrinfo = query->addrinfo; + /* + * A negative response has a SOA record (Type 2) + * and a optional NS RRset (Type 1) or it has neither + * a SOA or a NS RRset (Type 3, handled above) or + * rcode is NXDOMAIN (handled above) in which case + * the NS RRset is allowed (Type 4). + */ + if (rctx->soa_name != NULL) { + rctx->negative = ISC_TRUE; + } /* - * XXXRTH We should really get the current time just once. We - * need a routine to convert from an isc_time_t to an - * isc_stdtime_t. + * Process DNSSEC records in the authority section. */ - TIME_NOW(&tnow); - finish = &tnow; - isc_stdtime_get(&now); + result = rctx_authority_dnssec(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } /* - * Did the dispatcher have a problem? + * Trigger lookups for DNS nameservers. */ - if (devent->result != ISC_R_SUCCESS) { - if (devent->result == ISC_R_EOF && - (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { - /* - * The problem might be that they - * don't understand EDNS0. Turn it - * off and try again. - */ - options |= DNS_FETCHOPT_NOEDNS0; - resend = ISC_TRUE; - add_bad_edns(fctx, &query->addrinfo->sockaddr); + if (rctx->negative && fctx->rmessage->rcode == dns_rcode_noerror && + fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL && + dns_name_equal(rctx->soa_name, &fctx->name) && + !dns_name_equal(&fctx->name, dns_rootname)) + { + return (DNS_R_CHASEDSSERVERS); + } + + /* + * Did we find anything? + */ + if (!rctx->negative && rctx->ns_name == NULL) { + /* + * The responder is insane. + */ + if (rctx->found_name == NULL) { + log_formerr(fctx, "invalid response"); + return (DNS_R_FORMERR); + } + if (!dns_name_issubdomain(rctx->found_name, &fctx->domain)) { + char nbuf[DNS_NAME_FORMATSIZE]; + char dbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_rdatatype_format(rctx->found_type, tbuf, + sizeof(tbuf)); + dns_name_format(rctx->found_name, nbuf, sizeof(nbuf)); + dns_name_format(&fctx->domain, dbuf, + sizeof(dbuf)); + + log_formerr(fctx, "Name %s (%s) not subdomain" + " of zone %s -- invalid response", + nbuf, tbuf, dbuf); } else { - /* - * There's no hope for this query. - */ - keep_trying = ISC_TRUE; + log_formerr(fctx, "invalid response"); + } + + return (DNS_R_FORMERR); + } + + /* + * If we found both NS and SOA, they should be the same name. + */ + if (rctx->ns_name != NULL && rctx->soa_name != NULL && + rctx->ns_name != rctx->soa_name) + { + log_formerr(fctx, "NS/SOA mismatch"); + return (DNS_R_FORMERR); + } + + /* + * Handle a referral. + */ + result = rctx_referral(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + + /* + * Since we're not doing a referral, we don't want to cache any + * NS RRs we may have found. + */ + if (rctx->ns_name != NULL) { + rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE; + } + + if (rctx->negative) { + fctx->attributes |= FCTX_ATTR_WANTNCACHE; + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_authority_negative(): + * Scan the authority section of a negative answer, handling + * NS and SOA records. (Note that this function does *not* handle + * DNSSEC records; those are addressed separately in + * rctx_authority_dnssec() below.) + */ +static isc_result_t +rctx_authority_negative(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_section_t section; + dns_rdataset_t *rdataset = NULL; + isc_boolean_t finished = ISC_FALSE; - /* - * If this is a network error on an exclusive query - * socket, mark the server as bad so that we won't try - * it for this fetch again. Also adjust finish and - * no_response so that we penalize this address in SRTT - * adjustment later. - */ - if (query->exclusivesocket && - (devent->result == ISC_R_HOSTUNREACH || - devent->result == ISC_R_NETUNREACH || - devent->result == ISC_R_CONNREFUSED || - devent->result == ISC_R_CANCELED)) { - broken_server = devent->result; - broken_type = badns_unreachable; - finish = NULL; - no_response = ISC_TRUE; - } - } - FCTXTRACE3("dispatcher failure", devent->result); - goto done; + if (rctx->ns_in_answer) { + INSIST(fctx->type == dns_rdatatype_ns); + section = DNS_SECTION_ANSWER; + } else { + section = DNS_SECTION_AUTHORITY; } - message = fctx->rmessage; + result = dns_message_firstname(fctx->rmessage, section); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } - if (query->tsig != NULL) { - result = dns_message_setquerytsig(message, query->tsig); + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(fctx->rmessage, section, &name); + result = dns_message_nextname(fctx->rmessage, section); if (result != ISC_R_SUCCESS) { - FCTXTRACE3("unable to set query tsig", result); - goto done; + finished = ISC_TRUE; } - } - if (query->tsigkey) { - result = dns_message_settsigkey(message, query->tsigkey); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("unable to set tsig key", result); - goto done; + if (!dns_name_issubdomain(name, &fctx->domain)) { + continue; } - } - dns_message_setclass(message, res->rdclass); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_rdatatype_t type = rdataset->type; + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + if (((type == dns_rdatatype_ns || + type == dns_rdatatype_soa) && + !dns_name_issubdomain(&fctx->name, name))) + { + char qbuf[DNS_NAME_FORMATSIZE]; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(type, tbuf, sizeof(tbuf)); + dns_name_format(name, nbuf, sizeof(nbuf)); + dns_name_format(&fctx->name, qbuf, + sizeof(qbuf)); + log_formerr(fctx, + "unrelated %s %s in " + "%s authority section", + tbuf, nbuf, qbuf); + break; + } - if ((options & DNS_FETCHOPT_TCP) == 0) { - if ((options & DNS_FETCHOPT_NOEDNS0) == 0) - dns_adb_setudpsize(fctx->adb, query->addrinfo, - isc_buffer_usedlength(&devent->buffer)); - else - dns_adb_plainresponse(fctx->adb, query->addrinfo); - } - result = dns_message_parse(message, &devent->buffer, 0); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("message failed to parse", result); - switch (result) { - case ISC_R_UNEXPECTEDEND: - if (!message->question_ok || - (message->flags & DNS_MESSAGEFLAG_TC) == 0 || - (options & DNS_FETCHOPT_TCP) != 0) { + switch (type) { + case dns_rdatatype_ns: /* - * Either the message ended prematurely, - * and/or wasn't marked as being truncated, - * and/or this is a response to a query we - * sent over TCP. In all of these cases, - * something is wrong with the remote - * server and we don't want to retry using - * TCP. + * NS or RRSIG NS. + * + * Only one set of NS RRs is allowed. */ - if ((query->options & DNS_FETCHOPT_NOEDNS0) - == 0) { - /* - * The problem might be that they - * don't understand EDNS0. Turn it - * off and try again. - */ - options |= DNS_FETCHOPT_NOEDNS0; - resend = ISC_TRUE; - add_bad_edns(fctx, - &query->addrinfo->sockaddr); - inc_stats(res, - dns_resstatscounter_edns0fail); - } else { - broken_server = result; - keep_trying = ISC_TRUE; + if (rdataset->type == dns_rdatatype_ns) { + if (rctx->ns_name != NULL && + name != rctx->ns_name) + { + log_formerr(fctx, + "multiple NS RRsets " + "in authority section"); + rctx->result = + DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; } - goto done; - } - /* - * We defer retrying via TCP for a bit so we can - * check out this message further. - */ - truncated = ISC_TRUE; - break; - case DNS_R_FORMERR: - if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = dns_trust_glue; + break; + case dns_rdatatype_soa: /* - * The problem might be that they - * don't understand EDNS0. Turn it - * off and try again. + * SOA, or RRSIG SOA. + * + * Only one SOA is allowed. */ - options |= DNS_FETCHOPT_NOEDNS0; - resend = ISC_TRUE; - add_bad_edns(fctx, &query->addrinfo->sockaddr); - inc_stats(res, dns_resstatscounter_edns0fail); - } else { - broken_server = DNS_R_UNEXPECTEDRCODE; - keep_trying = ISC_TRUE; + if (rdataset->type == dns_rdatatype_soa) { + if (rctx->soa_name != NULL && + name != rctx->soa_name) + { + log_formerr(fctx, + "multiple SOA RRs " + "in authority " + "section"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->soa_name = name; + } + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= DNS_RDATASETATTR_NCACHE; + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; } - goto done; - default: - /* - * Something bad has happened. - */ - goto done; } } + return (ISC_R_SUCCESS); +} + +/* + * rctx_ncache(): + * Cache the negatively cacheable parts of the message. This may + * also cause work to be queued to the DNSSEC validator. + */ +static void +rctx_ncache(respctx_t *rctx) { + isc_result_t result; + dns_rdatatype_t covers; + fetchctx_t *fctx = rctx->fctx; + + if (!WANTNCACHE(fctx)) { + return; + } + /* - * Log the incoming packet. + * Cache DS NXDOMAIN seperately to other types. */ - dns_message_logfmtpacket2(message, "received packet from", - &query->addrinfo->sockaddr, - DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_PACKETS, - &dns_master_style_comment, - ISC_LOG_DEBUG(10), res->mctx); + if (fctx->rmessage->rcode == dns_rcode_nxdomain && + fctx->type != dns_rdatatype_ds) + { + covers = dns_rdatatype_any; + } else { + covers = fctx->type; + } -#ifdef HAVE_DNSTAP /* - * Log the response via dnstap. + * Cache any negative cache entries in the message. */ - memset(&zr, 0, sizeof(zr)); - result = dns_compress_init(&cctx, -1, res->mctx); - if (result == ISC_R_SUCCESS) { - isc_buffer_init(&zb, zone, sizeof(zone)); - dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); - result = dns_name_towire(&fctx->domain, &cctx, &zb); - if (result == ISC_R_SUCCESS) - isc_buffer_usedregion(&zb, &zr); - dns_compress_invalidate(&cctx); + result = ncache_message(fctx, rctx->query->addrinfo, covers, rctx->now); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("ncache_message complete", result); } +} - if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { - dtmsgtype = DNS_DTTYPE_FR; +/* + * rctx_authority_dnssec(): + * + * Scan the authority section of a negative answer or referral, + * handling DNSSEC records (i.e. NSEC, NSEC3, DS). + */ +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_section_t section; + dns_rdataset_t *rdataset = NULL; + isc_boolean_t finished = ISC_FALSE; + + if (rctx->ns_in_answer) { + INSIST(fctx->type == dns_rdatatype_ns); + section = DNS_SECTION_ANSWER; } else { - dtmsgtype = DNS_DTTYPE_RR; + section = DNS_SECTION_AUTHORITY; } - if (query->exclusivesocket) { - sock = dns_dispatch_getentrysocket(query->dispentry); - } else { - sock = dns_dispatch_getsocket(query->dispatch); + result = dns_message_firstname(fctx->rmessage, section); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); } - if (sock != NULL) { - result = isc_socket_getsockname(sock, &localaddr); - if (result == ISC_R_SUCCESS) { - la = &localaddr; + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(fctx->rmessage, section, &name); + result = dns_message_nextname(fctx->rmessage, section); + if (result != ISC_R_SUCCESS) { + finished = ISC_TRUE; } - } - dns_dt_send(res->view, dtmsgtype, la, &query->addrinfo->sockaddr, - ISC_TF((query->options & DNS_FETCHOPT_TCP) != 0), - &zr, &query->start, NULL, &devent->buffer); -#endif /* HAVE_DNSTAP */ + if (!dns_name_issubdomain(name, &fctx->domain)) { + /* Invalid name found; preserve it for logging later */ + rctx->found_name = name; + rctx->found_type = ISC_LIST_HEAD(name->list)->type; + continue; + } - if (message->rdclass != res->rdclass) { - resend = ISC_TRUE; - FCTXTRACE("bad class"); - goto done; - } + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_rdatatype_t type = rdataset->type; + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } - /* - * Process receive opt record. - */ - opt = dns_message_getopt(message); - if (opt != NULL) - process_opt(query, opt); + switch (type) { + case dns_rdatatype_nsec: + case dns_rdatatype_nsec3: + if (rctx->negative) { + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= + DNS_RDATASETATTR_NCACHE; + } else if (type == dns_rdatatype_nsec) { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + } - if (message->cc_bad && (options & DNS_FETCHOPT_TCP) == 0) { - /* - * If the COOKIE is bad, assume it is an attack and - * keep listening for a good answer. - */ - nextitem = ISC_TRUE; - if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { - char addrbuf[ISC_SOCKADDR_FORMATSIZE]; - isc_sockaddr_format(&query->addrinfo->sockaddr, - addrbuf, sizeof(addrbuf)); - isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, - "bad cookie from %s", addrbuf); - } - goto done; - } + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + /* + * No additional data needs to be marked. + */ + break; + case dns_rdatatype_ds: + /* + * DS or SIG DS. + * + * These should only be here if this is a + * referral, and there should only be one + * DS RRset. + */ + if (rctx->ns_name == NULL) { + log_formerr(fctx, + "DS with no referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } - /* - * Is the question the same as the one we asked? - * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must have - * the same question. - * FORMERR/NOTIMP if they have a question section then it must match. - */ - switch (message->rcode) { - case dns_rcode_notimp: - case dns_rcode_formerr: - if (message->counts[DNS_SECTION_QUESTION] == 0) - break; - case dns_rcode_nxrrset: /* Not expected. */ - case dns_rcode_badcookie: - case dns_rcode_noerror: - case dns_rcode_nxdomain: - case dns_rcode_yxdomain: - case dns_rcode_refused: - case dns_rcode_servfail: - default: - result = same_question(fctx); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("response did not match question", result); - nextitem = ISC_TRUE; - goto done; - } - break; - } + if (rdataset->type == dns_rdatatype_ds) { + if (rctx->ds_name != NULL && + name != rctx->ds_name) + { + log_formerr(fctx, + "DS doesn't match " + "referral (NS)"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ds_name = name; + } - /* - * If the message is signed, check the signature. If not, this - * returns success anyway. - */ - result = dns_message_checksig(message, res->view); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("signature check failed", result); - goto done; + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; + } + } } - /* - * The dispatcher should ensure we only get responses with QR set. - */ - INSIST((message->flags & DNS_MESSAGEFLAG_QR) != 0); - /* - * INSIST() that the message comes from the place we sent it to, - * since the dispatch code should ensure this. - * - * INSIST() that the message id is correct (this should also be - * ensured by the dispatch code). - */ + return (ISC_R_SUCCESS); +} - /* - * We have an affirmative response to the query and we have - * previously got a response from this server which indicated - * EDNS may not be supported so we can now cache the lack of - * EDNS support. - */ - if (opt == NULL && !EDNSOK(query->addrinfo) && - (message->rcode == dns_rcode_noerror || - message->rcode == dns_rcode_nxdomain || - message->rcode == dns_rcode_refused || - message->rcode == dns_rcode_yxdomain) && - bad_edns(fctx, &query->addrinfo->sockaddr)) { - dns_message_logpacket2(message, - "received packet (bad edns) from", - &query->addrinfo->sockaddr, - DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, - ISC_LOG_DEBUG(3), - res->mctx); - dns_adb_changeflags(fctx->adb, query->addrinfo, - DNS_FETCHOPT_NOEDNS0, - DNS_FETCHOPT_NOEDNS0); - } else if (opt == NULL && (message->flags & DNS_MESSAGEFLAG_TC) == 0 && - !EDNSOK(query->addrinfo) && - (message->rcode == dns_rcode_noerror || - message->rcode == dns_rcode_nxdomain) && - (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { - /* - * We didn't get a OPT record in response to a EDNS query. - * - * Old versions of named incorrectly drop the OPT record - * when there is a signed, truncated response so we check - * that TC is not set. - * - * Record that the server is not talking EDNS. While this - * should be safe to do for any rcode we limit it to NOERROR - * and NXDOMAIN. - */ - dns_message_logpacket2(message, "received packet (no opt) from", - &query->addrinfo->sockaddr, - DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, - ISC_LOG_DEBUG(3), res->mctx); - dns_adb_changeflags(fctx->adb, query->addrinfo, - DNS_FETCHOPT_NOEDNS0, - DNS_FETCHOPT_NOEDNS0); +/* + * rctx_referral(): + * Handles referral responses. Check for sanity, find glue as needed, + * and update the fetch context to follow the delegation. + */ +static isc_result_t +rctx_referral(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + if (rctx->negative || rctx->ns_name == NULL) { + return (ISC_R_SUCCESS); } /* - * If we get a non error EDNS response record the fact so we - * won't fallback to plain DNS in the future for this server. + * We already know ns_name is a subdomain of fctx->domain. + * If ns_name is equal to fctx->domain, we're not making + * progress. We return DNS_R_FORMERR so that we'll keep + * trying other servers. */ - if (opt != NULL && !EDNSOK(query->addrinfo) && - (query->options & DNS_FETCHOPT_NOEDNS0) == 0 && - (message->rcode == dns_rcode_noerror || - message->rcode == dns_rcode_nxdomain || - message->rcode == dns_rcode_refused || - message->rcode == dns_rcode_yxdomain)) { - dns_adb_changeflags(fctx->adb, query->addrinfo, - FCTX_ADDRINFO_EDNSOK, - FCTX_ADDRINFO_EDNSOK); + if (dns_name_equal(rctx->ns_name, &fctx->domain)) { + log_formerr(fctx, "non-improving referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); } /* - * Deal with truncated responses by retrying using TCP. + * If the referral name is not a parent of the query + * name, consider the responder insane. */ - if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) - truncated = ISC_TRUE; - - if (truncated) { - inc_stats(res, dns_resstatscounter_truncated); - if ((options & DNS_FETCHOPT_TCP) != 0) { - broken_server = DNS_R_TRUNCATEDTCP; - keep_trying = ISC_TRUE; - } else { - options |= DNS_FETCHOPT_TCP; - resend = ISC_TRUE; - } - FCTXTRACE3("message truncated", result); - goto done; + if (!dns_name_issubdomain(&fctx->name, rctx->ns_name)) { + /* Logged twice */ + log_formerr(fctx, "referral to non-parent"); + FCTXTRACE("referral to non-parent"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); } /* - * Is it a query response? + * Mark any additional data related to this rdataset. + * It's important that we do this before we change the + * query domain. */ - if (message->opcode != dns_opcode_query) { - /* XXXRTH Log */ - broken_server = DNS_R_UNEXPECTEDOPCODE; - keep_trying = ISC_TRUE; - FCTXTRACE("invalid message opcode"); - goto done; + INSIST(rctx->ns_rdataset != NULL); + fctx->attributes |= FCTX_ATTR_GLUING; + (void)dns_rdataset_additionaldata(rctx->ns_rdataset, + check_related, fctx); +#if CHECK_FOR_GLUE_IN_ANSWER + /* + * Look in the answer section for "glue" that is incorrectly + * returned as a answer. This is needed if the server also + * minimizes the response size by not adding records to the + * additional section that are in the answer section or if + * the record gets dropped due to message size constraints. + */ + if (rctx->glue_in_answer && + (fctx->type == dns_rdatatype_aaaa || + fctx->type == dns_rdatatype_a)) + { + (void)dns_rdataset_additionaldata(rctx->ns_rdataset, + check_answer, fctx); } +#endif + fctx->attributes &= ~FCTX_ATTR_GLUING; /* - * Update statistics about erroneous responses. + * NS rdatasets with 0 TTL cause problems. + * dns_view_findzonecut() will not find them when we + * try to follow the referral, and we'll SERVFAIL + * because the best nameservers are now above QDOMAIN. + * We force the TTL to 1 second to prevent this. */ - if (message->rcode != dns_rcode_noerror) { - switch (message->rcode) { - case dns_rcode_nxdomain: - inc_stats(res, dns_resstatscounter_nxdomain); - break; - case dns_rcode_servfail: - inc_stats(res, dns_resstatscounter_servfail); - break; - case dns_rcode_formerr: - inc_stats(res, dns_resstatscounter_formerr); - break; - case dns_rcode_refused: - inc_stats(res, dns_resstatscounter_refused); - break; - case dns_rcode_badvers: - inc_stats(res, dns_resstatscounter_badvers); - break; - case dns_rcode_badcookie: - inc_stats(res, dns_resstatscounter_badcookie); - break; - default: - inc_stats(res, dns_resstatscounter_othererror); - break; - } + if (rctx->ns_rdataset->ttl == 0) { + rctx->ns_rdataset->ttl = 1; } /* - * Is the remote server broken, or does it dislike us? + * Set the current query domain to the referral name. + * + * XXXRTH We should check if we're in forward-only mode, and + * if so we should bail out. */ - if (message->rcode != dns_rcode_noerror && - message->rcode != dns_rcode_yxdomain && - message->rcode != dns_rcode_nxdomain) { - isc_buffer_t b; - char code[64]; - unsigned char cookie[64]; - - /* - * Some servers do not ignore unknown EDNS options. - */ - if (!NOCOOKIE(query->addrinfo) && - (message->rcode == dns_rcode_formerr || - message->rcode == dns_rcode_notimp || - message->rcode == dns_rcode_refused) && - dns_adb_getcookie(fctx->adb, query->addrinfo, - cookie, sizeof(cookie)) == 0U) { - dns_adb_changeflags(fctx->adb, query->addrinfo, - FCTX_ADDRINFO_NOCOOKIE, - FCTX_ADDRINFO_NOCOOKIE); - resend = ISC_TRUE; - } else if ((message->rcode == dns_rcode_formerr || - message->rcode == dns_rcode_notimp || - (message->rcode == dns_rcode_servfail && - dns_message_getopt(message) == NULL)) && - (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { - /* - * It's very likely they don't like EDNS0. - * If the response code is SERVFAIL, also check if the - * response contains an OPT RR and don't cache the - * failure since it can be returned for various other - * reasons. - * - * XXXRTH We should check if the question - * we're asking requires EDNS0, and - * if so, we should bail out. - */ - options |= DNS_FETCHOPT_NOEDNS0; - resend = ISC_TRUE; - /* - * Remember that they may not like EDNS0. - */ - add_bad_edns(fctx, &query->addrinfo->sockaddr); - inc_stats(res, dns_resstatscounter_edns0fail); - } else if (message->rcode == dns_rcode_formerr) { - if (ISFORWARDER(query->addrinfo)) { - /* - * This forwarder doesn't understand us, - * but other forwarders might. Keep trying. - */ - broken_server = DNS_R_REMOTEFORMERR; - keep_trying = ISC_TRUE; - } else { - /* - * The server doesn't understand us. Since - * all servers for a zone need similar - * capabilities, we assume that we will get - * FORMERR from all servers, and thus we - * cannot make any more progress with this - * fetch. - */ - log_formerr(fctx, "server sent FORMERR"); - result = DNS_R_FORMERR; - } - } else if (message->rcode == dns_rcode_badvers) { - unsigned int version; - isc_boolean_t setnocookie = ISC_FALSE; -#if DNS_EDNS_VERSION > 0 - unsigned int flags, mask; -#endif - - /* - * Some servers return BADVERS to unknown - * EDNS options. This cannot be long term - * strategy. Do not disable COOKIE if we have - * already have received a COOKIE from this - * server. - */ - if (dns_adb_getcookie(fctx->adb, query->addrinfo, - cookie, sizeof(cookie)) == 0U) { - if (!NOCOOKIE(query->addrinfo)) - setnocookie = ISC_TRUE; - dns_adb_changeflags(fctx->adb, query->addrinfo, - FCTX_ADDRINFO_NOCOOKIE, - FCTX_ADDRINFO_NOCOOKIE); - } - - INSIST(opt != NULL); - version = (opt->ttl >> 16) & 0xff; -#if DNS_EDNS_VERSION > 0 - flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) | - DNS_FETCHOPT_EDNSVERSIONSET; - mask = DNS_FETCHOPT_EDNSVERSIONMASK | - DNS_FETCHOPT_EDNSVERSIONSET; -#endif - - /* - * Record that we got a good EDNS response. - */ - if (query->ednsversion > (int)version && - !EDNSOK(query->addrinfo)) { - dns_adb_changeflags(fctx->adb, query->addrinfo, - FCTX_ADDRINFO_EDNSOK, - FCTX_ADDRINFO_EDNSOK); - } + INSIST(dns_name_countlabels(&fctx->domain) > 0); + fcount_decr(fctx); - /* - * RFC 2671 was not clear that unknown options should - * be ignored. RFC 6891 is clear that that they - * should be ignored. If we are supporting the - * experimental EDNS > 0 then perform strict - * version checking of badvers responses. We won't - * be sending COOKIE etc. in that case. - */ -#if DNS_EDNS_VERSION > 0 - if ((int)version < query->ednsversion) { - dns_adb_changeflags(fctx->adb, query->addrinfo, - flags, mask); - resend = ISC_TRUE; - } else { - broken_server = DNS_R_BADVERS; - keep_trying = ISC_TRUE; - } -#else - if (version == 0U && setnocookie) { - resend = ISC_TRUE; - } else { - broken_server = DNS_R_BADVERS; - keep_trying = ISC_TRUE; - } -#endif - } else if (message->rcode == dns_rcode_badcookie && - message->cc_ok) { - /* - * We have recorded the new cookie. - */ - if (BADCOOKIE(query->addrinfo)) - query->options |= DNS_FETCHOPT_TCP; - query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE; - resend = ISC_TRUE; - } else { - /* - * XXXRTH log. - */ - broken_server = DNS_R_UNEXPECTEDRCODE; - INSIST(broken_server != ISC_R_SUCCESS); - keep_trying = ISC_TRUE; - } + dns_name_free(&fctx->domain, fctx->mctx); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } - isc_buffer_init(&b, code, sizeof(code) - 1); - dns_rcode_totext(fctx->rmessage->rcode, &b); - code[isc_buffer_usedlength(&b)] = '\0'; - FCTXTRACE2("remote server broken: returned ", code); - goto done; + dns_name_init(&fctx->domain, NULL); + result = dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); } - /* - * Is the server lame? - */ - if (res->lame_ttl != 0 && !ISFORWARDER(query->addrinfo) && - is_lame(fctx)) { - inc_stats(res, dns_resstatscounter_lame); - log_lame(fctx, query->addrinfo); - result = dns_adb_marklame(fctx->adb, query->addrinfo, - &fctx->name, fctx->type, - now + res->lame_ttl); - if (result != ISC_R_SUCCESS) - isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, - "could not mark server as lame: %s", - isc_result_totext(result)); - broken_server = DNS_R_LAME; - keep_trying = ISC_TRUE; - FCTXTRACE("lame server"); - goto done; + result = fcount_incr(fctx, ISC_TRUE); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); } + fctx->attributes |= FCTX_ATTR_WANTCACHE; + fctx->ns_ttl_ok = ISC_FALSE; + log_ns_ttl(fctx, "DELEGATION"); + rctx->result = DNS_R_DELEGATION; + /* - * Enforce delegations only zones like NET and COM. + * Reinitialize 'rctx' to prepare for following the delegation: + * set the get_nameservers and next_server flags appropriately and + * reset the fetch context counters. + * */ - if (!ISFORWARDER(query->addrinfo) && - dns_view_isdelegationonly(res->view, &fctx->domain) && - !dns_name_equal(&fctx->domain, &fctx->name) && - fix_mustbedelegationornxdomain(message, fctx)) { - char namebuf[DNS_NAME_FORMATSIZE]; - char domainbuf[DNS_NAME_FORMATSIZE]; - char addrbuf[ISC_SOCKADDR_FORMATSIZE]; - char classbuf[64]; - char typebuf[64]; + rctx->get_nameservers = ISC_TRUE; + rctx->next_server = ISC_TRUE; + rctx->fctx->restarts = 0; + rctx->fctx->referrals++; + rctx->fctx->querysent = 0; + rctx->fctx->lamecount = 0; + rctx->fctx->quotacount = 0; + rctx->fctx->neterr = 0; + rctx->fctx->badresp = 0; + rctx->fctx->adberr = 0; - dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); - dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); - dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); - dns_rdataclass_format(res->rdclass, classbuf, - sizeof(classbuf)); - isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, - sizeof(addrbuf)); + return (ISC_R_COMPLETE); +} - isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY, - DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, - "enforced delegation-only for '%s' (%s/%s/%s) " - "from %s", - domainbuf, namebuf, typebuf, classbuf, addrbuf); - } +/* + * rctx_additional(): + * Scan the additional section of a response to find records related + * to answers we were interested in. + */ +static void +rctx_additional(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + isc_boolean_t rescan; + dns_section_t section = DNS_SECTION_ADDITIONAL; + isc_result_t result; - if ((res->options & DNS_RESOLVER_CHECKNAMES) != 0) - checknames(message); + again: + rescan = ISC_FALSE; - /* - * Clear cache bits. - */ - fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE); + for (result = dns_message_firstname(fctx->rmessage, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(fctx->rmessage, section)) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + dns_message_currentname(fctx->rmessage, DNS_SECTION_ADDITIONAL, + &name); + if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) + continue; + name->attributes &= ~DNS_NAMEATTR_CHASE; + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + if (CHASE(rdataset)) { + rdataset->attributes &= ~DNS_RDATASETATTR_CHASE; + (void)dns_rdataset_additionaldata(rdataset, + check_related, + fctx); + rescan = ISC_TRUE; + } + } + } + if (rescan) + goto again; +} - /* - * Did we get any answers? - */ - if (message->counts[DNS_SECTION_ANSWER] > 0 && - (message->rcode == dns_rcode_noerror || - message->rcode == dns_rcode_yxdomain || - message->rcode == dns_rcode_nxdomain)) { +/* + * rctx_nextserver(): + * We found something wrong with the remote server, but it may be + * useful to try another one. + */ +static void +rctx_nextserver(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, + isc_result_t result) +{ + fetchctx_t *fctx = rctx->fctx; + + if (result == DNS_R_FORMERR) + rctx->broken_server = DNS_R_FORMERR; + if (rctx->broken_server != ISC_R_SUCCESS) { /* - * [normal case] - * We've got answers. If it has an authoritative answer or an - * answer from a forwarder, we're done. + * Add this server to the list of bad servers for + * this fctx. */ - if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 || - ISFORWARDER(query->addrinfo)) - { - result = answer_response(fctx); - if (result != ISC_R_SUCCESS) - FCTXTRACE3("answer_response (AA/fwd)", result); - } else if (iscname(fctx) && - fctx->type != dns_rdatatype_any && - fctx->type != dns_rdatatype_cname) - { - /* - * A BIND8 server could return a non-authoritative - * answer when a CNAME is followed. We should treat - * it as a valid answer. - */ - result = answer_response(fctx); - if (result != ISC_R_SUCCESS) - FCTXTRACE3("answer_response (!ANY/!CNAME)", - result); - } else if (fctx->type != dns_rdatatype_ns && - !betterreferral(fctx)) { - /* - * Lame response !!!. - */ - result = answer_response(fctx); - if (result != ISC_R_SUCCESS) - FCTXTRACE3("answer_response (!NS)", result); + add_bad(fctx, addrinfo, rctx->broken_server, + rctx->broken_type); + } + + if (rctx->get_nameservers) { + dns_fixedname_t foundname; + dns_name_t *name, *fname; + unsigned int findoptions = 0; + + dns_fixedname_init(&foundname); + fname = dns_fixedname_name(&foundname); + + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + if (dns_rdatatype_atparent(fctx->type)) + findoptions |= DNS_DBFIND_NOEXACT; + if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) { + name = &fctx->name; } else { - if (fctx->type == dns_rdatatype_ns) { - /* - * A BIND 8 server could incorrectly return a - * non-authoritative answer to an NS query - * instead of a referral. Since this answer - * lacks the SIGs necessary to do DNSSEC - * validation, we must invoke the following - * special kludge to treat it as a referral. - */ - result = noanswer_response(fctx, NULL, - LOOK_FOR_NS_IN_ANSWER); - if (result != ISC_R_SUCCESS) - FCTXTRACE3("noanswer_response (NS)", - result); - } else { - /* - * Some other servers may still somehow include - * an answer when it should return a referral - * with an empty answer. Check to see if we can - * treat this as a referral by ignoring the - * answer. Further more, there may be an - * implementation that moves A/AAAA glue records - * to the answer section for that type of - * delegation when the query is for that glue - * record. LOOK_FOR_GLUE_IN_ANSWER will handle - * such a corner case. - */ - result = noanswer_response(fctx, NULL, - LOOK_FOR_GLUE_IN_ANSWER); - if (result != ISC_R_SUCCESS) - FCTXTRACE3("noanswer_response", result); - } - if (result != DNS_R_DELEGATION) { - /* - * At this point, AA is not set, the response - * is not a referral, and the server is not a - * forwarder. It is technically lame and it's - * easier to treat it as such than to figure out - * some more elaborate course of action. - */ - broken_server = DNS_R_LAME; - keep_trying = ISC_TRUE; - goto done; - } - goto force_referral; + name = &fctx->domain; } + result = dns_view_findzonecut(fctx->res->view, + name, fname, + rctx->now, findoptions, + ISC_TRUE, + &fctx->nameservers, + NULL); if (result != ISC_R_SUCCESS) { - if (result == DNS_R_FORMERR) - keep_trying = ISC_TRUE; - goto done; + FCTXTRACE("couldn't find a zonecut"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; } - } else if (message->counts[DNS_SECTION_AUTHORITY] > 0 || - message->rcode == dns_rcode_noerror || - message->rcode == dns_rcode_nxdomain) { - /* - * NXDOMAIN, NXRDATASET, or referral. - */ - result = noanswer_response(fctx, NULL, 0); - switch (result) { - case ISC_R_SUCCESS: - case DNS_R_CHASEDSSERVERS: - break; - case DNS_R_DELEGATION: - force_referral: - /* - * We don't have the answer, but we know a better - * place to look. - */ - get_nameservers = ISC_TRUE; - keep_trying = ISC_TRUE; - /* - * We have a new set of name servers, and it - * has not experienced any restarts yet. - */ - fctx->restarts = 0; - + if (!dns_name_issubdomain(fname, &fctx->domain)) { /* - * Update local statistics counters collected for each - * new zone. + * The best nameservers are now above our QDOMAIN. */ - fctx->referrals++; - fctx->querysent = 0; - fctx->lamecount = 0; - fctx->quotacount = 0; - fctx->neterr = 0; - fctx->badresp = 0; - fctx->adberr = 0; + FCTXTRACE("nameservers now above QDOMAIN"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } - result = ISC_R_SUCCESS; - break; - default: - /* - * Something has gone wrong. - */ - if (result == DNS_R_FORMERR) - keep_trying = ISC_TRUE; - FCTXTRACE3("noanswer_response", result); - goto done; + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + result = dns_name_dup(fname, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; } - } else { - /* - * The server is insane. - */ - /* XXXRTH Log */ - broken_server = DNS_R_UNEXPECTEDRCODE; - keep_trying = ISC_TRUE; - FCTXTRACE("broken server: unexpected rcode"); - goto done; + result = fcount_incr(fctx, ISC_TRUE); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = ISC_TRUE; + fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE); + fctx_cleanupfinds(fctx); + fctx_cleanupaltfinds(fctx); + fctx_cleanupforwaddrs(fctx); + fctx_cleanupaltaddrs(fctx); + } + + /* + * Try again. + */ + fctx_try(fctx, !rctx->get_nameservers, ISC_FALSE); +} + +/* + * rctx_resend(): + * + * Resend the query, probably with the options changed. Calls fctx_query(), + * passing rctx->retryopts (which is based on query->options, but may have been + * updated since the last time fctx_query() was called). + */ +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + isc_boolean_t bucket_empty; + + FCTXTRACE("resend"); + inc_stats(fctx->res, dns_resstatscounter_retry); + fctx_increference(fctx); + result = fctx_query(fctx, addrinfo, rctx->retryopts); + if (result == ISC_R_SUCCESS) { + return; + } + + fctx_done(fctx, result, __LINE__); + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + if (bucket_empty) { + empty_bucket(fctx->res); + } +} + +/* + * rctx_next(): + * We got what appeared to be a response but it didn't match the question + * or the cookie; it may have been meant for someone else, or it may be a + * spoofing attack. Drop it and continue listening for the response we + * wanted. + */ +static void +rctx_next(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + + FCTXTRACE("nextitem"); + inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem); + INSIST(rctx->query->dispentry != NULL); + dns_message_reset(rctx->fctx->rmessage, DNS_MESSAGE_INTENTPARSE); + result = dns_dispatch_getnext(rctx->query->dispentry, &rctx->devent); + if (result != ISC_R_SUCCESS) { + fctx_done(rctx->fctx, result, __LINE__); } +} - /* - * Follow additional section data chains. - */ - chase_additional(fctx); +/* + * rctx_chaseds(): + * Look up the parent zone's NS records so that DS records can be fetched. + */ +static void +rctx_chaseds(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, isc_result_t result) +{ + fetchctx_t *fctx = rctx->fctx; + unsigned int n; - /* - * Cache the cacheable parts of the message. This may also cause - * work to be queued to the DNSSEC validator. - */ - if (WANTCACHE(fctx)) { - result = cache_message(fctx, query->addrinfo, now); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("cache_message complete", result); - goto done; - } - } + add_bad(fctx, addrinfo, result, rctx->broken_type); + fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE); + fctx_cleanupfinds(fctx); + fctx_cleanupforwaddrs(fctx); - /* - * Ncache the negatively cacheable parts of the message. This may - * also cause work to be queued to the DNSSEC validator. - */ - if (WANTNCACHE(fctx)) { - dns_rdatatype_t covers; + n = dns_name_countlabels(&fctx->name); + dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname); - /* - * Cache DS NXDOMAIN seperately to other types. - */ - if (message->rcode == dns_rcode_nxdomain && - fctx->type != dns_rdatatype_ds) - covers = dns_rdatatype_any; - else - covers = fctx->type; + FCTXTRACE("suspending DS lookup to find parent's NS records"); - /* - * Cache any negative cache entries in the message. - */ - result = ncache_message(fctx, query->addrinfo, covers, now); + result = dns_resolver_createfetch(fctx->res, &fctx->nsname, + dns_rdatatype_ns, + NULL, NULL, NULL, + fctx->options, rctx->task, + resume_dslookup, fctx, + &fctx->nsrrset, NULL, + &fctx->nsfetch); + if (result != ISC_R_SUCCESS) + fctx_done(fctx, result, __LINE__); + else { + fctx_increference(fctx); + result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) - FCTXTRACE3("ncache_message complete", result); + fctx_done(fctx, result, __LINE__); } +} - done: - /* - * Remember the query's addrinfo, in case we need to mark the - * server as broken. - */ - addrinfo = query->addrinfo; +/* + * rctx_done(): + * This resolver query response is finished, either because we encountered + * a problem or because we've gotten all the information from it that we + * can. We either wait for another response, resend the query to the + * same server, resend to a new server, or clean up and shut down the fetch. + */ +static void +rctx_done(respctx_t *rctx, isc_result_t result) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_adbaddrinfo_t *addrinfo = query->addrinfo; FCTXTRACE4("query canceled in response(); ", - no_response ? "no response" : "responding", + rctx->no_response ? "no response" : "responding", result); /* @@ -8464,120 +9011,31 @@ resquery_response(isc_task_t *task, isc_event_t *event) { * * XXXRTH Don't cancel the query if waiting for validation? */ - if (!nextitem) - fctx_cancelquery(&query, &devent, finish, - no_response, ISC_FALSE); + if (!rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, ISC_FALSE); + } #ifdef ENABLE_AFL - if (fuzzing_resolver && (keep_trying || resend || nextitem)) { - if (nextitem) { - fctx_cancelquery(&query, &devent, finish, - no_response, ISC_FALSE); + if (fuzzing_resolver && + (rctx->next_server || rctx->resend || rctx->nextitem)) + { + if (rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, ISC_FALSE); } fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } else #endif - if (keep_trying) { - if (result == DNS_R_FORMERR) - broken_server = DNS_R_FORMERR; - if (broken_server != ISC_R_SUCCESS) { - /* - * Add this server to the list of bad servers for - * this fctx. - */ - add_bad(fctx, addrinfo, broken_server, broken_type); - } - - if (get_nameservers) { - dns_name_t *name; - dns_fixedname_init(&foundname); - fname = dns_fixedname_name(&foundname); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - return; - } - findoptions = 0; - if (dns_rdatatype_atparent(fctx->type)) - findoptions |= DNS_DBFIND_NOEXACT; - if ((options & DNS_FETCHOPT_UNSHARED) == 0) - name = &fctx->name; - else - name = &fctx->domain; - result = dns_view_findzonecut(res->view, - name, fname, - now, findoptions, - ISC_TRUE, - &fctx->nameservers, - NULL); - if (result != ISC_R_SUCCESS) { - FCTXTRACE("couldn't find a zonecut"); - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - return; - } - if (!dns_name_issubdomain(fname, &fctx->domain)) { - /* - * The best nameservers are now above our - * QDOMAIN. - */ - FCTXTRACE("nameservers now above QDOMAIN"); - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - return; - } - - fcount_decr(fctx); - dns_name_free(&fctx->domain, fctx->mctx); - dns_name_init(&fctx->domain, NULL); - result = dns_name_dup(fname, fctx->mctx, &fctx->domain); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - return; - } - result = fcount_incr(fctx, ISC_TRUE); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); - return; - } - fctx->ns_ttl = fctx->nameservers.ttl; - fctx->ns_ttl_ok = ISC_TRUE; - fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE); - fctx_cleanupfinds(fctx); - fctx_cleanupaltfinds(fctx); - fctx_cleanupforwaddrs(fctx); - fctx_cleanupaltaddrs(fctx); - } - /* - * Try again. - */ - fctx_try(fctx, !get_nameservers, ISC_FALSE); - } else if (resend) { - /* - * Resend (probably with changed options). - */ - FCTXTRACE("resend"); - inc_stats(res, dns_resstatscounter_retry); - bucketnum = fctx->bucketnum; - fctx_increference(fctx); - result = fctx_query(fctx, addrinfo, options); - if (result != ISC_R_SUCCESS) { - fctx_done(fctx, result, __LINE__); - LOCK(&res->buckets[bucketnum].lock); - bucket_empty = fctx_decreference(fctx); - UNLOCK(&res->buckets[bucketnum].lock); - if (bucket_empty) - empty_bucket(res); - } - } else if (nextitem) { - /* - * Wait for next item. - */ - FCTXTRACE("nextitem"); - inc_stats(fctx->res, dns_resstatscounter_nextitem); - INSIST(query->dispentry != NULL); - dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE); - result = dns_dispatch_getnext(query->dispentry, &devent); - if (result != ISC_R_SUCCESS) - fctx_done(fctx, result, __LINE__); + if (rctx->next_server) { + rctx_nextserver(rctx, addrinfo, result); + } else if (rctx->resend) { + rctx_resend(rctx, addrinfo); + } else if (rctx->nextitem) { + rctx_next(rctx); + } else if (result == DNS_R_CHASEDSSERVERS) { + rctx_chaseds(rctx, addrinfo, result); } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) { /* * All has gone well so far, but we are waiting for the @@ -8592,39 +9050,319 @@ resquery_response(isc_task_t *task, isc_event_t *event) { result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); - } else if (result == DNS_R_CHASEDSSERVERS) { - unsigned int n; - add_bad(fctx, addrinfo, result, broken_type); - fctx_cancelqueries(fctx, ISC_TRUE, ISC_FALSE); - fctx_cleanupfinds(fctx); - fctx_cleanupforwaddrs(fctx); + } else { + /* + * We're done. + */ + fctx_done(fctx, result, __LINE__); + } + +} - n = dns_name_countlabels(&fctx->name); - dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname); +/* + * rctx_logpacket(): + * Log the incoming packet; also log to DNSTAP if configured. + */ +static void +rctx_logpacket(respctx_t *rctx) { +#ifdef HAVE_DNSTAP + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + isc_socket_t *sock = NULL; + isc_sockaddr_t localaddr, *la = NULL; + unsigned char zone[DNS_NAME_MAXWIRE]; + dns_dtmsgtype_t dtmsgtype; + dns_compress_t cctx; + isc_region_t zr; + isc_buffer_t zb; +#endif /* HAVE_DNSTAP */ - FCTXTRACE("suspending DS lookup to find parent's NS records"); + dns_message_logfmtpacket2(rctx->fctx->rmessage, + "received packet from", + &rctx->query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS, + &dns_master_style_comment, + ISC_LOG_DEBUG(10), + rctx->fctx->res->mctx); - result = dns_resolver_createfetch(res, &fctx->nsname, - dns_rdatatype_ns, - NULL, NULL, NULL, - fctx->options, task, - resume_dslookup, fctx, - &fctx->nsrrset, NULL, - &fctx->nsfetch); - if (result != ISC_R_SUCCESS) - fctx_done(fctx, result, __LINE__); - else { - fctx_increference(fctx); - result = fctx_stopidletimer(fctx); - if (result != ISC_R_SUCCESS) - fctx_done(fctx, result, __LINE__); - } +#ifdef HAVE_DNSTAP + /* + * Log the response via dnstap. + */ + memset(&zr, 0, sizeof(zr)); + result = dns_compress_init(&cctx, -1, fctx->res->mctx); + if (result == ISC_R_SUCCESS) { + isc_buffer_init(&zb, zone, sizeof(zone)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(&fctx->domain, &cctx, &zb); + if (result == ISC_R_SUCCESS) + isc_buffer_usedregion(&zb, &zr); + dns_compress_invalidate(&cctx); + } + + if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + dtmsgtype = DNS_DTTYPE_FR; + } else { + dtmsgtype = DNS_DTTYPE_RR; + } + + if (rctx->query->exclusivesocket) { + sock = dns_dispatch_getentrysocket(rctx->query->dispentry); } else { + sock = dns_dispatch_getsocket(rctx->query->dispatch); + } + + if (sock != NULL) { + result = isc_socket_getsockname(sock, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, + &rctx->query->addrinfo->sockaddr, + ISC_TF((rctx->query->options & DNS_FETCHOPT_TCP) != 0), + &zr, &rctx->query->start, NULL, &rctx->devent->buffer); +#endif /* HAVE_DNSTAP */ +} + +/* + * rctx_badserver(): + * Is the remote server broken, or does it dislike us? + */ +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + isc_buffer_t b; + char code[64]; + unsigned char cookie[64]; + + if (fctx->rmessage->rcode == dns_rcode_noerror || + fctx->rmessage->rcode == dns_rcode_yxdomain || + fctx->rmessage->rcode == dns_rcode_nxdomain) + { + return (ISC_R_SUCCESS); + } + + /* + * Some servers do not ignore unknown EDNS options. + */ + if (!NOCOOKIE(query->addrinfo) && + (fctx->rmessage->rcode == dns_rcode_formerr || + fctx->rmessage->rcode == dns_rcode_notimp || + fctx->rmessage->rcode == dns_rcode_refused) && + dns_adb_getcookie(fctx->adb, query->addrinfo, + cookie, sizeof(cookie)) == 0U) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOCOOKIE, + FCTX_ADDRINFO_NOCOOKIE); + rctx->resend = ISC_TRUE; + } else if ((fctx->rmessage->rcode == dns_rcode_formerr || + fctx->rmessage->rcode == dns_rcode_notimp || + (fctx->rmessage->rcode == dns_rcode_servfail && + dns_message_getopt(fctx->rmessage) == NULL)) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { /* - * We're done. + * It's very likely they don't like EDNS0. + * If the response code is SERVFAIL, also check if the + * response contains an OPT RR and don't cache the + * failure since it can be returned for various other + * reasons. + * + * XXXRTH We should check if the question + * we're asking requires EDNS0, and + * if so, we should bail out. */ - fctx_done(fctx, result, __LINE__); + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = ISC_TRUE; + /* + * Remember that they may not like EDNS0. + */ + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else if (fctx->rmessage->rcode == dns_rcode_formerr) { + if (ISFORWARDER(query->addrinfo)) { + /* + * This forwarder doesn't understand us, + * but other forwarders might. Keep trying. + */ + rctx->broken_server = DNS_R_REMOTEFORMERR; + rctx->next_server = ISC_TRUE; + } else { + /* + * The server doesn't understand us. Since + * all servers for a zone need similar + * capabilities, we assume that we will get + * FORMERR from all servers, and thus we + * cannot make any more progress with this + * fetch. + */ + log_formerr(fctx, "server sent FORMERR"); + result = DNS_R_FORMERR; + } + } else if (fctx->rmessage->rcode == dns_rcode_badvers) { + unsigned int version; + isc_boolean_t setnocookie = ISC_FALSE; +#if DNS_EDNS_VERSION > 0 + unsigned int flags, mask; +#endif + + /* + * Some servers return BADVERS to unknown + * EDNS options. This cannot be long term + * strategy. Do not disable COOKIE if we have + * already have received a COOKIE from this + * server. + */ + if (dns_adb_getcookie(fctx->adb, query->addrinfo, + cookie, sizeof(cookie)) == 0U) { + if (!NOCOOKIE(query->addrinfo)) + setnocookie = ISC_TRUE; + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOCOOKIE, + FCTX_ADDRINFO_NOCOOKIE); + } + + INSIST(rctx->opt != NULL); + version = (rctx->opt->ttl >> 16) & 0xff; +#if DNS_EDNS_VERSION > 0 + flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) | + DNS_FETCHOPT_EDNSVERSIONSET; + mask = DNS_FETCHOPT_EDNSVERSIONMASK | + DNS_FETCHOPT_EDNSVERSIONSET; +#endif + + /* + * Record that we got a good EDNS response. + */ + if (query->ednsversion > (int)version && + !EDNSOK(query->addrinfo)) { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, + FCTX_ADDRINFO_EDNSOK); + } + + /* + * RFC 2671 was not clear that unknown options should + * be ignored. RFC 6891 is clear that that they + * should be ignored. If we are supporting the + * experimental EDNS > 0 then perform strict + * version checking of badvers responses. We won't + * be sending COOKIE etc. in that case. + */ +#if DNS_EDNS_VERSION > 0 + if ((int)version < query->ednsversion) { + dns_adb_changeflags(fctx->adb, query->addrinfo, + flags, mask); + rctx->resend = ISC_TRUE; + } else { + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = ISC_TRUE; + } +#else + if (version == 0U && setnocookie) { + rctx->resend = ISC_TRUE; + } else { + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = ISC_TRUE; + } +#endif + } else if (fctx->rmessage->rcode == dns_rcode_badcookie && + fctx->rmessage->cc_ok) + { + /* + * We have recorded the new cookie. + */ + if (BADCOOKIE(query->addrinfo)) { + rctx->retryopts |= DNS_FETCHOPT_TCP; + } + query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE; + rctx->resend = ISC_TRUE; + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = ISC_TRUE; + } + + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(fctx->rmessage->rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + FCTXTRACE2("remote server broken: returned ", code); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_lameserver(): + * Is the server lame? + */ +static isc_result_t +rctx_lameserver(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (fctx->res->lame_ttl == 0 || ISFORWARDER(query->addrinfo) || + !is_lame(fctx)) + { + return (ISC_R_SUCCESS); + } + + inc_stats(fctx->res, dns_resstatscounter_lame); + log_lame(fctx, query->addrinfo); + result = dns_adb_marklame(fctx->adb, query->addrinfo, + &fctx->name, fctx->type, + rctx->now + fctx->res->lame_ttl); + if (result != ISC_R_SUCCESS) + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, + "could not mark server as lame: %s", + isc_result_totext(result)); + rctx->broken_server = DNS_R_LAME; + rctx->next_server = ISC_TRUE; + FCTXTRACE("lame server"); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_delonly_zone(): + * Handle delegation-only zones like NET and COM. + */ +static void +rctx_delonly_zone(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + + if (ISFORWARDER(rctx->query->addrinfo) || + !dns_view_isdelegationonly(fctx->res->view, &fctx->domain) || + dns_name_equal(&fctx->domain, &fctx->name) || + !fix_mustbedelegationornxdomain(fctx->rmessage, fctx)) + { + return; } + + dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(fctx->res->rdclass, classbuf, + sizeof(classbuf)); + isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "enforced delegation-only for '%s' (%s/%s/%s) from %s", + domainbuf, namebuf, typebuf, classbuf, addrbuf); } /***