From 96912e44b0b180de56f47d438f91c9e70925bd16 Mon Sep 17 00:00:00 2001 From: wpk Date: Wed, 8 Feb 2017 22:15:01 +0100 Subject: [PATCH] 4573. [func] Query logic has been substantially refactored (e.g. query_find function has been split into smaller functions) for improved readability, maintainability --- CHANGES | 5 + bin/named/query.c | 8551 +++++++++++++++++++--------------- doc/arm/notes.xml | 7 + lib/isc/include/isc/result.h | 3 +- lib/isc/result.c | 2 + 5 files changed, 4684 insertions(+), 3884 deletions(-) diff --git a/CHANGES b/CHANGES index 404f3856f4..a696770b44 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +4573. [func] 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] + 4572. [func] The "dnstap-output" option can now take "size" and "versions" parameters to indicate the maximum size a dnstap log file can grow before rolling to a new diff --git a/bin/named/query.c b/bin/named/query.c index d66c28dc16..aca54a7db3 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,27 @@ #define dns64_bis_return_excluded_addresses 1 #endif +/*% + * Maximum number of chained queries before we give up + * to prevent CNAME loops. + */ +#define MAX_RESTARTS 16 + +#define QUERY_ERROR(qctx, r) \ +do { \ + qctx->result = r; \ + qctx->want_restart = ISC_FALSE; \ + qctx->line = __LINE__; \ +} while (0) + +#define RECURSE_ERROR(qctx, r) \ +do { \ + if ((r) == DNS_R_DUPLICATE || (r) == DNS_R_DROP) \ + QUERY_ERROR(qctx, r); \ + else \ + QUERY_ERROR(qctx, DNS_R_SERVFAIL); \ +} while (0) + /*% Partial answer? */ #define PARTIALANSWER(c) (((c)->query.attributes & \ NS_QUERYATTR_PARTIALANSWER) != 0) @@ -159,8 +181,10 @@ client_trace(ns_client_t *client, int level, const char *message) { } } #define CTRACE(l,m) client_trace(client, l, m) +#define CCTRACE(l,m) client_trace(qctx->client, l, m) #else #define CTRACE(l,m) ((void)m) +#define CCTRACE(l,m) ((void)m) #endif /* WANT_QUERYTRACE */ @@ -191,9 +215,6 @@ typedef struct client_additionalctx { dns_rdataset_t *rdataset; } client_additionalctx_t; -static isc_result_t -query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype); - static isc_boolean_t validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); @@ -215,6 +236,236 @@ static isc_boolean_t 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 + * 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 + * below: + * + * 1. Set up query context and other resources for a client + * query (query_setup()) + * + * 2. Start the search (query_start()) + * + * 3. Identify authoritative data sources which may have an answer; + * search them (query_lookup()). If an answer is found, go to 7. + * + * 4. If recursion or cache access are allowed, search the cache + * (query_lookup() again, using the cache database) to find a better + * answer. If an answer is found, go to 7. + * + * 5. If recursion is allowed, begin recursion (query_recurse()). + * Go to X to clean up this phase of the query. When recursion + * is complete, processing will resume at 6. + * + * 6. Resume from recursion; set up query context for resumed processing. + * + * 7. Determine what sort of answer we've found (query_gotanswer()) + * and call other functions accordingly: + * - not found (auth or cache), go to 8 + * - delegation, go to 9 + * - no such domain (auth), go to 10 + * - empty answer (auth), go to 11 + * - negative response (cache), go to 12 + * - answer found, go to 13 + * + * 8. The answer was not found in the database (query_notfound(). + * Set up a referral and go to 9. + * + * 9. Handle a delegation response (query_delegation()). If we are + * allowed to recurse, go to 5, otherwise go to X to clean up and + * return the delegation to the client. + * + * 10. No such domain (query_nxdomain()). Attempt redirection; if + * unsuccessful, add authority section records (query_addsoa(), + * query_addauth()), then go to X to return NXDOMAIN to client. + * + * 11. Empty answer (query_nodata()). Add authority section records + * (query_addsoa(), query_addauth()) and signatures if authoritative + * (query_sign_nodata()) then go to X and return + * NOERROR/ANCOUNT=0 to client. + * + * 12. No such domain or empty answer returned from cache (query_ncache()). + * Set response code appropriately, go to 11. + * + * 13. Prepare a response (query_prepresponse()) and then fill it + * appropriately (query_respond(), or for type ANY, + * query_respond_any()). + * + * 14. If a restart is needed due to CNAME/DNAME chaining, go to 2. + * Clean up resources. If recursing, stop and wait for the event + * handler to be called back (step 6). If an answer is ready, + * return it to the client. + * + * (XXX: This description omits several special cases including + * DNS64, filter-aaaa, RPZ, RRL, and the SERVFAIL cache.) + */ + +typedef struct query_ctx { + isc_buffer_t *dbuf; /* name buffer */ + dns_name_t *fname; /* found name from DB lookup */ + dns_rdataset_t *rdataset; /* found rdataset */ + dns_rdataset_t *sigrdataset; /* found sigrdataset */ + dns_rdataset_t *noqname; /* rdataset needing + * NOQNAME proof */ + dns_rdatatype_t qtype; + dns_rdatatype_t type; + + unsigned int options; /* DB lookup options */ + + isc_boolean_t redirected; /* nxdomain redirected? */ + isc_boolean_t is_zone; /* is DB a zone DB? */ + isc_boolean_t is_staticstub_zone; + isc_boolean_t resuming; /* resumed from recursion? */ + isc_boolean_t dns64, dns64_exclude; + isc_boolean_t authoritative; /* authoritative query? */ + isc_boolean_t want_restart; /* CNAME chain or other + * restart needed */ + isc_boolean_t need_wildcardproof; /* wilcard proof needed */ + isc_boolean_t nxrewrite; /* negative answer from RPZ */ + dns_fixedname_t wildcardname; /* name needing wcard proof */ + dns_fixedname_t dsname; /* name needing DS */ + + ns_client_t *client; /* client object */ + dns_fetchevent_t *event; /* recursion event */ + + dns_db_t *db; /* zone or cache database */ + dns_dbversion_t *version; /* DB version */ + dns_dbnode_t *node; /* DB node */ + + dns_db_t *zdb; /* zone DB values, saved */ + dns_name_t *zfname; /* while searching cache */ + dns_dbversion_t *zversion; /* for a better answer */ + dns_rdataset_t *zrdataset; + dns_rdataset_t *zsigrdataset; + + dns_rpz_st_t *rpz_st; /* RPZ state */ + dns_zone_t *zone; /* zone to search */ + + isc_result_t result; /* query result */ + int line; /* line to report error */ +} query_ctx_t; + +static void +query_trace(query_ctx_t *qctx); + +static void +qctx_init(ns_client_t *client, dns_fetchevent_t *event, + dns_rdatatype_t qtype, query_ctx_t *qctx); + +static isc_result_t +query_setup(ns_client_t *client, dns_rdatatype_t qtype); + +static isc_result_t +query_start(query_ctx_t *qctx); + +static isc_result_t +query_lookup(query_ctx_t *qctx); + +static void +fetch_callback(isc_task_t *task, isc_event_t *event); + +static isc_result_t +query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, + dns_name_t *qdomain, dns_rdataset_t *nameservers, + isc_boolean_t resuming); + +static isc_result_t +query_resume(query_ctx_t *qctx); + +static isc_result_t +query_sfcache(query_ctx_t *qctx); + +static isc_result_t +query_checkrrl(query_ctx_t *qctx, isc_result_t result); + +static isc_result_t +query_checkrpz(query_ctx_t *qctx, isc_result_t result); + +static isc_result_t +query_rpzcname(query_ctx_t *qctx, dns_name_t *cname); + +static isc_result_t +query_gotanswer(query_ctx_t *qctx, isc_result_t result); + +static void +query_addnoqnameproof(query_ctx_t *qctx); + +static isc_result_t +query_respond_any(query_ctx_t *qctx); + +static isc_result_t +query_respond(query_ctx_t *qctx); + +static isc_result_t +query_dns64(query_ctx_t *qctx); + +static void +query_filter64(query_ctx_t *qctx); + +static isc_result_t +query_notfound(query_ctx_t *qctx); + +static isc_result_t +query_zone_delegation(query_ctx_t *qctx); + +static isc_result_t +query_delegation(query_ctx_t *qctx); + +static void +query_addds(query_ctx_t *qctx); + +static isc_result_t +query_nodata(query_ctx_t *qctx, isc_result_t result); + +static isc_result_t +query_sign_nodata(query_ctx_t *qctx); + +static void +query_addnxrrsetnsec(query_ctx_t *qctx); + +static isc_result_t +query_nxdomain(query_ctx_t *qctx, isc_boolean_t empty_wild); + +static isc_result_t +query_redirect(query_ctx_t *qctx); + +static isc_result_t +query_ncache(query_ctx_t *qctx, isc_result_t result); + +static isc_result_t +query_cname(query_ctx_t *qctx); + +static isc_result_t +query_dname(query_ctx_t *qctx); + +static isc_result_t +query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl); + +static isc_result_t +query_prepresponse(query_ctx_t *qctx); + +static isc_result_t +query_addsoa(query_ctx_t *qctx, unsigned int override_ttl, + dns_section_t section); + +static isc_result_t +query_addns(query_ctx_t *qctx); + +static void +query_addbestns(query_ctx_t *qctx); + +static void +query_addwildcardproof(query_ctx_t *qctx, isc_boolean_t ispositive, + isc_boolean_t nodata); + +static void +query_addauth(query_ctx_t *qctx); + +static isc_result_t +query_done(query_ctx_t *qctx); + /*% * Increment query statistics counters. */ @@ -478,15 +729,15 @@ ns_query_free(ns_client_t *client) { query_reset(client, ISC_TRUE); } +/*% + * Allocate a name buffer. + */ static inline isc_result_t query_newnamebuf(ns_client_t *client) { isc_buffer_t *dbuf; isc_result_t result; CTRACE(ISC_LOG_DEBUG(3), "query_newnamebuf"); - /*% - * Allocate a name buffer. - */ dbuf = NULL; result = isc_buffer_allocate(client->mctx, &dbuf, 1024); @@ -501,6 +752,9 @@ query_newnamebuf(ns_client_t *client) { return (ISC_R_SUCCESS); } +/*% + * Get a name buffer from the pool, or allocate a new one if needed. + */ static inline isc_buffer_t * query_getnamebuf(ns_client_t *client) { isc_buffer_t *dbuf; @@ -580,9 +834,7 @@ query_releasename(ns_client_t *client, dns_name_t **namep) { } static inline dns_name_t * -query_newname(ns_client_t *client, isc_buffer_t *dbuf, - isc_buffer_t *nbuf) -{ +query_newname(ns_client_t *client, isc_buffer_t *dbuf, isc_buffer_t *nbuf) { dns_name_t *name; isc_region_t r; isc_result_t result; @@ -706,7 +958,7 @@ ns_query_init(ns_client_t *client) { client->query.redirect.rdataset = NULL; client->query.redirect.sigrdataset = NULL; client->query.redirect.authoritative = ISC_FALSE; - client->query.redirect.is_zone = ISC_FALSE; + client->query.redirect.is_zone = ISC_FALSE; dns_fixedname_init(&client->query.redirect.fixed); client->query.redirect.fname = dns_fixedname_name(&client->query.redirect.fixed); @@ -2192,16 +2444,20 @@ query_addadditional2(void *arg, const dns_name_t *name, dns_rdatatype_t qtype) { if (mname != fname) { if (mname != NULL) { /* - * A different type of this name is - * already stored in the additional - * section. We'll reuse the name. - * Note that this should happen at most - * once. Otherwise, fname->link could - * leak below. + * A different type of this + * name is already stored + * in the additional + * section. We'll reuse + * the name. Note that + * this should happen at + * most once. Otherwise, + * fname->link could leak + * below. */ INSIST(mname0 == NULL); - query_releasename(client, &fname); + query_releasename(client, + &fname); fname = mname; mname0 = mname; } else @@ -2300,335 +2556,21 @@ query_addrdataset(ns_client_t *client, dns_name_t *fname, CTRACE(ISC_LOG_DEBUG(3), "query_addrdataset: done"); } -static isc_result_t -query_dns64(ns_client_t *client, dns_name_t **namep, dns_rdataset_t *rdataset, - dns_rdataset_t *sigrdataset, isc_buffer_t *dbuf, - dns_section_t section) -{ - dns_name_t *name, *mname; - dns_rdata_t *dns64_rdata; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdatalist_t *dns64_rdatalist; - dns_rdataset_t *dns64_rdataset; - dns_rdataset_t *mrdataset; - isc_buffer_t *buffer; - isc_region_t r; - isc_result_t result; - dns_view_t *view = client->view; - isc_netaddr_t netaddr; - dns_dns64_t *dns64; - unsigned int flags = 0; - - /*% - * To the current response for 'client', add the answer RRset - * '*rdatasetp' and an optional signature set '*sigrdatasetp', with - * owner name '*namep', to section 'section', unless they are - * already there. Also add any pertinent additional data. - * - * If 'dbuf' is not NULL, then '*namep' is the name whose data is - * stored in 'dbuf'. In this case, query_addrrset() guarantees that - * when it returns the name will either have been kept or released. - */ - CTRACE(ISC_LOG_DEBUG(3), "query_dns64"); - name = *namep; - mname = NULL; - mrdataset = NULL; - buffer = NULL; - dns64_rdata = NULL; - dns64_rdataset = NULL; - dns64_rdatalist = NULL; - result = dns_message_findname(client->message, section, - name, dns_rdatatype_aaaa, - rdataset->covers, - &mname, &mrdataset); - if (result == ISC_R_SUCCESS) { - /* - * We've already got an RRset of the given name and type. - * There's nothing else to do; - */ - CTRACE(ISC_LOG_DEBUG(3), - "query_dns64: dns_message_findname succeeded: done"); - if (dbuf != NULL) - query_releasename(client, namep); - return (ISC_R_SUCCESS); - } else if (result == DNS_R_NXDOMAIN) { - /* - * The name doesn't exist. - */ - if (dbuf != NULL) - query_keepname(client, name, dbuf); - dns_message_addname(client->message, name, section); - *namep = NULL; - mname = name; - } else { - RUNTIME_CHECK(result == DNS_R_NXRRSET); - if (dbuf != NULL) - query_releasename(client, namep); - } - - if (rdataset->trust != dns_trust_secure && - (section == DNS_SECTION_ANSWER || - section == DNS_SECTION_AUTHORITY)) - client->query.attributes &= ~NS_QUERYATTR_SECURE; - - isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); - - result = isc_buffer_allocate(client->mctx, &buffer, view->dns64cnt * - 16 * dns_rdataset_count(rdataset)); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_message_gettemprdataset(client->message, &dns64_rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_message_gettemprdatalist(client->message, - &dns64_rdatalist); - if (result != ISC_R_SUCCESS) - goto cleanup; - - dns_rdatalist_init(dns64_rdatalist); - dns64_rdatalist->rdclass = dns_rdataclass_in; - dns64_rdatalist->type = dns_rdatatype_aaaa; - if (client->query.dns64_ttl != ISC_UINT32_MAX) - dns64_rdatalist->ttl = ISC_MIN(rdataset->ttl, - client->query.dns64_ttl); - else - dns64_rdatalist->ttl = ISC_MIN(rdataset->ttl, 600); - - if (RECURSIONOK(client)) - flags |= DNS_DNS64_RECURSIVE; - - /* - * We use the signatures from the A lookup to set DNS_DNS64_DNSSEC - * as this provides a easy way to see if the answer was signed. - */ - if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) - flags |= DNS_DNS64_DNSSEC; - - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - for (dns64 = ISC_LIST_HEAD(client->view->dns64); - dns64 != NULL; dns64 = dns_dns64_next(dns64)) { - - dns_rdataset_current(rdataset, &rdata); - isc_buffer_availableregion(buffer, &r); - INSIST(r.length >= 16); - result = dns_dns64_aaaafroma(dns64, &netaddr, - client->signer, - &ns_g_server->aclenv, - flags, rdata.data, r.base); - if (result != ISC_R_SUCCESS) { - dns_rdata_reset(&rdata); - continue; - } - isc_buffer_add(buffer, 16); - isc_buffer_remainingregion(buffer, &r); - isc_buffer_forward(buffer, 16); - result = dns_message_gettemprdata(client->message, - &dns64_rdata); - if (result != ISC_R_SUCCESS) - goto cleanup; - dns_rdata_init(dns64_rdata); - dns_rdata_fromregion(dns64_rdata, dns_rdataclass_in, - dns_rdatatype_aaaa, &r); - ISC_LIST_APPEND(dns64_rdatalist->rdata, dns64_rdata, - link); - dns64_rdata = NULL; - dns_rdata_reset(&rdata); - } - } - if (result != ISC_R_NOMORE) - goto cleanup; - - if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) - goto cleanup; - - result = dns_rdatalist_tordataset(dns64_rdatalist, dns64_rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - dns_rdataset_setownercase(dns64_rdataset, mname); - client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; - dns64_rdataset->trust = rdataset->trust; - query_addrdataset(client, mname, dns64_rdataset); - dns64_rdataset = NULL; - dns64_rdatalist = NULL; - dns_message_takebuffer(client->message, &buffer); - inc_stats(client, dns_nsstatscounter_dns64); - result = ISC_R_SUCCESS; - - cleanup: - if (buffer != NULL) - isc_buffer_free(&buffer); - - if (dns64_rdata != NULL) - dns_message_puttemprdata(client->message, &dns64_rdata); - - if (dns64_rdataset != NULL) - dns_message_puttemprdataset(client->message, &dns64_rdataset); - - if (dns64_rdatalist != NULL) { - for (dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata); - dns64_rdata != NULL; - dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata)) - { - ISC_LIST_UNLINK(dns64_rdatalist->rdata, - dns64_rdata, link); - dns_message_puttemprdata(client->message, &dns64_rdata); - } - dns_message_puttemprdatalist(client->message, &dns64_rdatalist); - } - - CTRACE(ISC_LOG_DEBUG(3), "query_dns64: done"); - return (result); -} - -static void -query_filter64(ns_client_t *client, dns_name_t **namep, - dns_rdataset_t *rdataset, isc_buffer_t *dbuf, - dns_section_t section) -{ - dns_name_t *name, *mname; - dns_rdata_t *myrdata; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdatalist_t *myrdatalist; - dns_rdataset_t *myrdataset; - isc_buffer_t *buffer; - isc_region_t r; - isc_result_t result; - unsigned int i; - - CTRACE(ISC_LOG_DEBUG(3), "query_filter64"); - - INSIST(client->query.dns64_aaaaok != NULL); - INSIST(client->query.dns64_aaaaoklen == dns_rdataset_count(rdataset)); - - name = *namep; - mname = NULL; - buffer = NULL; - myrdata = NULL; - myrdataset = NULL; - myrdatalist = NULL; - result = dns_message_findname(client->message, section, - name, dns_rdatatype_aaaa, - rdataset->covers, - &mname, &myrdataset); - if (result == ISC_R_SUCCESS) { - /* - * We've already got an RRset of the given name and type. - * There's nothing else to do; - */ - CTRACE(ISC_LOG_DEBUG(3), - "query_filter64: dns_message_findname succeeded: done"); - if (dbuf != NULL) - query_releasename(client, namep); - return; - } else if (result == DNS_R_NXDOMAIN) { - mname = name; - *namep = NULL; - } else { - RUNTIME_CHECK(result == DNS_R_NXRRSET); - if (dbuf != NULL) - query_releasename(client, namep); - dbuf = NULL; - } - - if (rdataset->trust != dns_trust_secure && - (section == DNS_SECTION_ANSWER || - section == DNS_SECTION_AUTHORITY)) - client->query.attributes &= ~NS_QUERYATTR_SECURE; - - result = isc_buffer_allocate(client->mctx, &buffer, - 16 * dns_rdataset_count(rdataset)); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_message_gettemprdataset(client->message, &myrdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_message_gettemprdatalist(client->message, &myrdatalist); - if (result != ISC_R_SUCCESS) - goto cleanup; - - dns_rdatalist_init(myrdatalist); - myrdatalist->rdclass = dns_rdataclass_in; - myrdatalist->type = dns_rdatatype_aaaa; - myrdatalist->ttl = rdataset->ttl; - - i = 0; - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - if (!client->query.dns64_aaaaok[i++]) - continue; - dns_rdataset_current(rdataset, &rdata); - INSIST(rdata.length == 16); - isc_buffer_putmem(buffer, rdata.data, rdata.length); - isc_buffer_remainingregion(buffer, &r); - isc_buffer_forward(buffer, rdata.length); - result = dns_message_gettemprdata(client->message, &myrdata); - if (result != ISC_R_SUCCESS) - goto cleanup; - dns_rdata_init(myrdata); - dns_rdata_fromregion(myrdata, dns_rdataclass_in, - dns_rdatatype_aaaa, &r); - ISC_LIST_APPEND(myrdatalist->rdata, myrdata, link); - myrdata = NULL; - dns_rdata_reset(&rdata); - } - if (result != ISC_R_NOMORE) - goto cleanup; - - result = dns_rdatalist_tordataset(myrdatalist, myrdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - dns_rdataset_setownercase(myrdataset, name); - client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; - if (mname == name) { - if (dbuf != NULL) - query_keepname(client, name, dbuf); - dns_message_addname(client->message, name, section); - dbuf = NULL; - } - myrdataset->trust = rdataset->trust; - query_addrdataset(client, mname, myrdataset); - myrdataset = NULL; - myrdatalist = NULL; - dns_message_takebuffer(client->message, &buffer); - - cleanup: - if (buffer != NULL) - isc_buffer_free(&buffer); - - if (myrdata != NULL) - dns_message_puttemprdata(client->message, &myrdata); - - if (myrdataset != NULL) - dns_message_puttemprdataset(client->message, &myrdataset); - - if (myrdatalist != NULL) { - for (myrdata = ISC_LIST_HEAD(myrdatalist->rdata); - myrdata != NULL; - myrdata = ISC_LIST_HEAD(myrdatalist->rdata)) - { - ISC_LIST_UNLINK(myrdatalist->rdata, myrdata, link); - dns_message_puttemprdata(client->message, &myrdata); - } - dns_message_puttemprdatalist(client->message, &myrdatalist); - } - if (dbuf != NULL) - query_releasename(client, &name); - - CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done"); -} - static void query_addrrset(ns_client_t *client, dns_name_t **namep, dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp, isc_buffer_t *dbuf, dns_section_t section) { - dns_name_t *name, *mname; - dns_rdataset_t *rdataset, *mrdataset, *sigrdataset; + dns_name_t *name = *namep, *mname = NULL; + dns_rdataset_t *rdataset = *rdatasetp, *mrdataset = NULL; + dns_rdataset_t *sigrdataset = NULL; isc_result_t result; + CTRACE(ISC_LOG_DEBUG(3), "query_addrrset"); + + if (sigrdatasetp != NULL) + sigrdataset = *sigrdatasetp; + /*% * To the current response for 'client', add the answer RRset * '*rdatasetp' and an optional signature set '*sigrdatasetp', with @@ -2639,15 +2581,6 @@ query_addrrset(ns_client_t *client, dns_name_t **namep, * stored in 'dbuf'. In this case, query_addrrset() guarantees that * when it returns the name will either have been kept or released. */ - CTRACE(ISC_LOG_DEBUG(3), "query_addrrset"); - name = *namep; - rdataset = *rdatasetp; - if (sigrdatasetp != NULL) - sigrdataset = *sigrdatasetp; - else - sigrdataset = NULL; - mname = NULL; - mrdataset = NULL; result = dns_message_findname(client->message, section, name, rdataset->type, rdataset->covers, &mname, &mrdataset); @@ -2681,6 +2614,7 @@ query_addrrset(ns_client_t *client, dns_name_t **namep, (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY)) client->query.attributes &= ~NS_QUERYATTR_SECURE; + /* * Note: we only add SIGs if we've added the type they cover, so * we do not need to check if the SIG rdataset is already in the @@ -2695,315 +2629,10 @@ query_addrrset(ns_client_t *client, dns_name_t **namep, ISC_LIST_APPEND(mname->list, sigrdataset, link); *sigrdatasetp = NULL; } + CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: done"); } -static inline isc_result_t -query_addsoa(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version, - unsigned int override_ttl, isc_boolean_t isassociated, - dns_section_t section) -{ - dns_name_t *name; - dns_dbnode_t *node; - isc_result_t result, eresult; - dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; - dns_rdataset_t **sigrdatasetp = NULL; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - - CTRACE(ISC_LOG_DEBUG(3), "query_addsoa"); - /* - * Initialization. - */ - eresult = ISC_R_SUCCESS; - name = NULL; - rdataset = NULL; - node = NULL; - - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - - /* - * Don't add the SOA record for test which set "-T nosoa". - */ - if (ns_g_nosoa && (!WANTDNSSEC(client) || !isassociated)) - return (ISC_R_SUCCESS); - - /* - * Get resources and make 'name' be the database origin. - */ - result = dns_message_gettempname(client->message, &name); - if (result != ISC_R_SUCCESS) - return (result); - dns_name_init(name, NULL); - dns_name_clone(dns_db_origin(db), name); - rdataset = query_newrdataset(client); - if (rdataset == NULL) { - CTRACE(ISC_LOG_ERROR, "unable to allocate rdataset"); - eresult = DNS_R_SERVFAIL; - goto cleanup; - } - if (WANTDNSSEC(client) && dns_db_issecure(db)) { - sigrdataset = query_newrdataset(client); - if (sigrdataset == NULL) { - CTRACE(ISC_LOG_ERROR, "unable to allocate sigrdataset"); - eresult = DNS_R_SERVFAIL; - goto cleanup; - } - } - - /* - * Find the SOA. - */ - result = dns_db_getoriginnode(db, &node); - if (result == ISC_R_SUCCESS) { - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_soa, 0, client->now, - rdataset, sigrdataset); - } else { - dns_fixedname_t foundname; - dns_name_t *fname; - - dns_fixedname_init(&foundname); - fname = dns_fixedname_name(&foundname); - - result = dns_db_findext(db, name, version, dns_rdatatype_soa, - client->query.dboptions, 0, &node, - fname, &cm, &ci, rdataset, sigrdataset); - } - if (result != ISC_R_SUCCESS) { - /* - * This is bad. We tried to get the SOA RR at the zone top - * and it didn't work! - */ - CTRACE(ISC_LOG_ERROR, "unable to find SOA RR at zone apex"); - eresult = DNS_R_SERVFAIL; - } else { - /* - * Extract the SOA MINIMUM. - */ - dns_rdata_soa_t soa; - dns_rdata_t rdata = DNS_RDATA_INIT; - result = dns_rdataset_first(rdataset); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &soa, NULL); - if (result != ISC_R_SUCCESS) - goto cleanup; - - if (override_ttl != ISC_UINT32_MAX && - override_ttl < rdataset->ttl) { - rdataset->ttl = override_ttl; - if (sigrdataset != NULL) - sigrdataset->ttl = override_ttl; - } - - /* - * Add the SOA and its SIG to the response, with the - * TTLs adjusted per RFC2308 section 3. - */ - if (rdataset->ttl > soa.minimum) - rdataset->ttl = soa.minimum; - if (sigrdataset != NULL && sigrdataset->ttl > soa.minimum) - sigrdataset->ttl = soa.minimum; - - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - - if (section == DNS_SECTION_ADDITIONAL) - rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; - query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, - section); - } - - cleanup: - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (name != NULL) - query_releasename(client, &name); - if (node != NULL) - dns_db_detachnode(db, &node); - - return (eresult); -} - -static inline isc_result_t -query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) { - dns_name_t *name, *fname; - dns_dbnode_t *node; - isc_result_t result, eresult; - dns_fixedname_t foundname; - dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; - dns_rdataset_t **sigrdatasetp = NULL; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - - CTRACE(ISC_LOG_DEBUG(3), "query_addns"); - /* - * Initialization. - */ - eresult = ISC_R_SUCCESS; - name = NULL; - rdataset = NULL; - node = NULL; - dns_fixedname_init(&foundname); - fname = dns_fixedname_name(&foundname); - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - - /* - * Get resources and make 'name' be the database origin. - */ - result = dns_message_gettempname(client->message, &name); - if (result != ISC_R_SUCCESS) { - CTRACE(ISC_LOG_DEBUG(3), - "query_addns: dns_message_gettempname failed: done"); - return (result); - } - dns_name_init(name, NULL); - dns_name_clone(dns_db_origin(db), name); - rdataset = query_newrdataset(client); - if (rdataset == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_addns: query_newrdataset failed"); - eresult = DNS_R_SERVFAIL; - goto cleanup; - } - if (WANTDNSSEC(client) && dns_db_issecure(db)) { - sigrdataset = query_newrdataset(client); - if (sigrdataset == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_addns: query_newrdataset failed"); - eresult = DNS_R_SERVFAIL; - goto cleanup; - } - } - - /* - * Find the NS rdataset. - */ - result = dns_db_getoriginnode(db, &node); - if (result == ISC_R_SUCCESS) { - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_ns, 0, client->now, - rdataset, sigrdataset); - } else { - CTRACE(ISC_LOG_DEBUG(3), "query_addns: calling dns_db_find"); - result = dns_db_findext(db, name, NULL, dns_rdatatype_ns, - client->query.dboptions, 0, &node, - fname, &cm, &ci, rdataset, sigrdataset); - CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_db_find complete"); - } - if (result != ISC_R_SUCCESS) { - CTRACE(ISC_LOG_ERROR, - "query_addns: " - "dns_db_findrdataset or dns_db_find failed"); - /* - * This is bad. We tried to get the NS rdataset at the zone - * top and it didn't work! - */ - eresult = DNS_R_SERVFAIL; - } else { - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, - DNS_SECTION_AUTHORITY); - } - - cleanup: - CTRACE(ISC_LOG_DEBUG(3), "query_addns: cleanup"); - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (name != NULL) - query_releasename(client, &name); - if (node != NULL) - dns_db_detachnode(db, &node); - - CTRACE(ISC_LOG_DEBUG(3), "query_addns: done"); - return (eresult); -} - -static isc_result_t -query_add_cname(ns_client_t *client, dns_name_t *qname, dns_name_t *tname, - dns_trust_t trust, dns_ttl_t ttl) -{ - dns_rdataset_t *rdataset; - dns_rdatalist_t *rdatalist; - dns_rdata_t *rdata; - isc_region_t r; - dns_name_t *aname; - isc_result_t result; - - /* - * We assume the name data referred to by tname won't go away. - */ - - aname = NULL; - result = dns_message_gettempname(client->message, &aname); - if (result != ISC_R_SUCCESS) - return (result); - result = dns_name_dup(qname, client->mctx, aname); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - return (result); - } - - rdatalist = NULL; - result = dns_message_gettemprdatalist(client->message, &rdatalist); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - return (result); - } - rdata = NULL; - result = dns_message_gettemprdata(client->message, &rdata); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - dns_message_puttemprdatalist(client->message, &rdatalist); - return (result); - } - rdataset = NULL; - result = dns_message_gettemprdataset(client->message, &rdataset); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - dns_message_puttemprdatalist(client->message, &rdatalist); - dns_message_puttemprdata(client->message, &rdata); - return (result); - } - rdatalist->type = dns_rdatatype_cname; - rdatalist->rdclass = client->message->rdclass; - rdatalist->ttl = ttl; - - dns_name_toregion(tname, &r); - rdata->data = r.base; - rdata->length = r.length; - rdata->rdclass = client->message->rdclass; - rdata->type = dns_rdatatype_cname; - - ISC_LIST_APPEND(rdatalist->rdata, rdata, link); - RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) - == ISC_R_SUCCESS); - rdataset->trust = trust; - dns_rdataset_setownercase(rdataset, aname); - - query_addrrset(client, &aname, &rdataset, NULL, NULL, - DNS_SECTION_ANSWER); - if (rdataset != NULL) { - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - dns_message_puttemprdataset(client->message, &rdataset); - } - if (aname != NULL) - dns_message_puttempname(client->message, &aname); - - return (ISC_R_SUCCESS); -} - /* * Mark the RRsets as secure. Update the cache (db) to reflect the * change in trust level. @@ -3176,194 +2805,6 @@ validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, return (ISC_FALSE); } -static void -query_addbestns(ns_client_t *client) { - dns_db_t *db, *zdb; - dns_dbnode_t *node; - dns_name_t *fname, *zfname; - dns_rdataset_t *rdataset, *sigrdataset, *zrdataset, *zsigrdataset; - isc_boolean_t is_zone, use_zone; - isc_buffer_t *dbuf; - isc_result_t result; - dns_dbversion_t *version; - dns_zone_t *zone; - isc_buffer_t b; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - - CTRACE(ISC_LOG_DEBUG(3), "query_addbestns"); - fname = NULL; - zfname = NULL; - rdataset = NULL; - zrdataset = NULL; - sigrdataset = NULL; - zsigrdataset = NULL; - node = NULL; - db = NULL; - zdb = NULL; - version = NULL; - zone = NULL; - is_zone = ISC_FALSE; - use_zone = ISC_FALSE; - - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - - /* - * Find the right database. - */ - result = query_getdb(client, client->query.qname, dns_rdatatype_ns, 0, - &zone, &db, &version, &is_zone); - if (result != ISC_R_SUCCESS) - goto cleanup; - - db_find: - /* - * We'll need some resources... - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - rdataset = query_newrdataset(client); - if (fname == NULL || rdataset == NULL) - goto cleanup; - /* - * Get the RRSIGs if the client requested them or if we may - * need to validate answers from the cache. - */ - if (WANTDNSSEC(client) || !is_zone) { - sigrdataset = query_newrdataset(client); - if (sigrdataset == NULL) - goto cleanup; - } - - /* - * Now look for the zonecut. - */ - if (is_zone) { - result = dns_db_findext(db, client->query.qname, version, - dns_rdatatype_ns, - client->query.dboptions, - client->now, &node, fname, - &cm, &ci, rdataset, sigrdataset); - if (result != DNS_R_DELEGATION) - goto cleanup; - if (USECACHE(client)) { - query_keepname(client, fname, dbuf); - dns_db_detachnode(db, &node); - SAVE(zdb, db); - SAVE(zfname, fname); - SAVE(zrdataset, rdataset); - SAVE(zsigrdataset, sigrdataset); - version = NULL; - dns_db_attach(client->view->cachedb, &db); - is_zone = ISC_FALSE; - goto db_find; - } - } else { - result = dns_db_findzonecut(db, client->query.qname, - client->query.dboptions, - client->now, &node, fname, - rdataset, sigrdataset); - if (result == ISC_R_SUCCESS) { - if (zfname != NULL && - !dns_name_issubdomain(fname, zfname)) { - /* - * We found a zonecut in the cache, but our - * zone delegation is better. - */ - use_zone = ISC_TRUE; - } - } else if (result == ISC_R_NOTFOUND && zfname != NULL) { - /* - * We didn't find anything in the cache, but we - * have a zone delegation, so use it. - */ - use_zone = ISC_TRUE; - } else - goto cleanup; - } - - if (use_zone) { - query_releasename(client, &fname); - /* - * We've already done query_keepname() on - * zfname, so we must set dbuf to NULL to - * prevent query_addrrset() from trying to - * call query_keepname() again. - */ - dbuf = NULL; - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - - if (node != NULL) - dns_db_detachnode(db, &node); - dns_db_detach(&db); - - RESTORE(db, zdb); - RESTORE(fname, zfname); - RESTORE(rdataset, zrdataset); - RESTORE(sigrdataset, zsigrdataset); - } - - /* - * Attempt to validate RRsets that are pending or that are glue. - */ - if ((DNS_TRUST_PENDING(rdataset->trust) || - (sigrdataset != NULL && DNS_TRUST_PENDING(sigrdataset->trust))) - && !validate(client, db, fname, rdataset, sigrdataset) && - !PENDINGOK(client->query.dboptions)) - goto cleanup; - - if ((DNS_TRUST_GLUE(rdataset->trust) || - (sigrdataset != NULL && DNS_TRUST_GLUE(sigrdataset->trust))) && - !validate(client, db, fname, rdataset, sigrdataset) && - SECURE(client) && WANTDNSSEC(client)) - goto cleanup; - - /* - * If the answer is secure only add NS records if they are secure - * when the client may be looking for AD in the response. - */ - if (SECURE(client) && (WANTDNSSEC(client) || WANTAD(client)) && - ((rdataset->trust != dns_trust_secure) || - (sigrdataset != NULL && sigrdataset->trust != dns_trust_secure))) - goto cleanup; - - /* - * If the client doesn't want DNSSEC we can discard the sigrdataset - * now. - */ - if (!WANTDNSSEC(client)) - query_putrdataset(client, &sigrdataset); - query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, - DNS_SECTION_AUTHORITY); - - cleanup: - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); - if (node != NULL) - dns_db_detachnode(db, &node); - if (db != NULL) - dns_db_detach(&db); - if (zone != NULL) - dns_zone_detach(&zone); - if (zdb != NULL) { - query_putrdataset(client, &zrdataset); - if (zsigrdataset != NULL) - query_putrdataset(client, &zsigrdataset); - if (zfname != NULL) - query_releasename(client, &zfname); - dns_db_detach(&zdb); - } -} - static void fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) { if (*rdataset == NULL) @@ -3384,540 +2825,6 @@ fixfname(ns_client_t *client, dns_name_t **fname, isc_buffer_t **dbuf, } } -static void -query_addds(ns_client_t *client, dns_db_t *db, dns_dbnode_t *node, - dns_dbversion_t *version, dns_name_t *name) -{ - dns_fixedname_t fixed; - dns_name_t *fname = NULL; - dns_name_t *rname; - dns_rdataset_t *rdataset, *sigrdataset; - isc_buffer_t *dbuf, b; - isc_result_t result; - unsigned int count; - - CTRACE(ISC_LOG_DEBUG(3), "query_addds"); - rname = NULL; - rdataset = NULL; - sigrdataset = NULL; - - /* - * We'll need some resources... - */ - rdataset = query_newrdataset(client); - sigrdataset = query_newrdataset(client); - if (rdataset == NULL || sigrdataset == NULL) - goto cleanup; - - /* - * Look for the DS record, which may or may not be present. - */ - result = dns_db_findrdataset(db, node, version, dns_rdatatype_ds, 0, - client->now, rdataset, sigrdataset); - /* - * If we didn't find it, look for an NSEC. - */ - if (result == ISC_R_NOTFOUND) - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_nsec, 0, client->now, - rdataset, sigrdataset); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) - goto addnsec3; - if (!dns_rdataset_isassociated(rdataset) || - !dns_rdataset_isassociated(sigrdataset)) - goto addnsec3; - - /* - * We've already added the NS record, so if the name's not there, - * we have other problems. Use this name rather than calling - * query_addrrset(). - */ - result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY); - if (result != ISC_R_SUCCESS) - goto cleanup; - - rname = NULL; - dns_message_currentname(client->message, DNS_SECTION_AUTHORITY, - &rname); - result = dns_message_findtype(rname, dns_rdatatype_ns, 0, NULL); - if (result != ISC_R_SUCCESS) - goto cleanup; - - ISC_LIST_APPEND(rname->list, rdataset, link); - ISC_LIST_APPEND(rname->list, sigrdataset, link); - rdataset = NULL; - sigrdataset = NULL; - return; - - addnsec3: - if (!dns_db_iszone(db)) - goto cleanup; - /* - * Add the NSEC3 which proves the DS does not exist. - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - dns_fixedname_init(&fixed); - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - query_findclosestnsec3(name, db, version, client, rdataset, - sigrdataset, fname, ISC_TRUE, - dns_fixedname_name(&fixed)); - if (!dns_rdataset_isassociated(rdataset)) - goto cleanup; - query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, - DNS_SECTION_AUTHORITY); - /* - * Did we find the closest provable encloser instead? - * If so add the nearest to the closest provable encloser. - */ - if (!dns_name_equal(name, dns_fixedname_name(&fixed))) { - count = dns_name_countlabels(dns_fixedname_name(&fixed)) + 1; - dns_name_getlabelsequence(name, - dns_name_countlabels(name) - count, - count, dns_fixedname_name(&fixed)); - fixfname(client, &fname, &dbuf, &b); - fixrdataset(client, &rdataset); - fixrdataset(client, &sigrdataset); - if (fname == NULL || rdataset == NULL || sigrdataset == NULL) - goto cleanup; - query_findclosestnsec3(dns_fixedname_name(&fixed), db, version, - client, rdataset, sigrdataset, fname, - ISC_FALSE, NULL); - if (!dns_rdataset_isassociated(rdataset)) - goto cleanup; - query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, - DNS_SECTION_AUTHORITY); - } - - cleanup: - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); -} - -static void -query_addwildcardproof(ns_client_t *client, dns_db_t *db, - dns_dbversion_t *version, dns_name_t *name, - isc_boolean_t ispositive, isc_boolean_t nodata) -{ - isc_buffer_t *dbuf, b; - dns_name_t *fname; - dns_rdataset_t *rdataset, *sigrdataset; - dns_fixedname_t wfixed; - dns_name_t *wname; - dns_dbnode_t *node; - unsigned int options; - unsigned int olabels, nlabels, labels; - isc_result_t result; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_nsec_t nsec; - isc_boolean_t have_wname; - int order; - dns_fixedname_t cfixed; - dns_name_t *cname; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - - CTRACE(ISC_LOG_DEBUG(3), "query_addwildcardproof"); - fname = NULL; - rdataset = NULL; - sigrdataset = NULL; - node = NULL; - - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - - /* - * Get the NOQNAME proof then if !ispositive - * get the NOWILDCARD proof. - * - * DNS_DBFIND_NOWILD finds the NSEC records that covers the - * name ignoring any wildcard. From the owner and next names - * of this record you can compute which wildcard (if it exists) - * will match by finding the longest common suffix of the - * owner name and next names with the qname and prefixing that - * with the wildcard label. - * - * e.g. - * Given: - * example SOA - * example NSEC b.example - * b.example A - * b.example NSEC a.d.example - * a.d.example A - * a.d.example NSEC g.f.example - * g.f.example A - * g.f.example NSEC z.i.example - * z.i.example A - * z.i.example NSEC example - * - * QNAME: - * a.example -> example NSEC b.example - * owner common example - * next common example - * wild *.example - * d.b.example -> b.example NSEC a.d.example - * owner common b.example - * next common example - * wild *.b.example - * a.f.example -> a.d.example NSEC g.f.example - * owner common example - * next common f.example - * wild *.f.example - * j.example -> z.i.example NSEC example - * owner common example - * next common example - * wild *.example - */ - options = client->query.dboptions | DNS_DBFIND_NOWILD; - dns_fixedname_init(&wfixed); - wname = dns_fixedname_name(&wfixed); - again: - have_wname = ISC_FALSE; - /* - * We'll need some resources... - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - rdataset = query_newrdataset(client); - sigrdataset = query_newrdataset(client); - if (fname == NULL || rdataset == NULL || sigrdataset == NULL) - goto cleanup; - - result = dns_db_findext(db, name, version, dns_rdatatype_nsec, - options, 0, &node, fname, &cm, &ci, - rdataset, sigrdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - - if (!dns_rdataset_isassociated(rdataset)) { - /* - * No NSEC proof available, return NSEC3 proofs instead. - */ - dns_fixedname_init(&cfixed); - cname = dns_fixedname_name(&cfixed); - /* - * Find the closest encloser. - */ - dns_name_copy(name, cname, NULL); - while (result == DNS_R_NXDOMAIN) { - labels = dns_name_countlabels(cname) - 1; - /* - * Sanity check. - */ - if (labels == 0U) - goto cleanup; - dns_name_split(cname, labels, NULL, cname); - result = dns_db_findext(db, cname, version, - dns_rdatatype_nsec, - options, 0, NULL, fname, - &cm, &ci, NULL, NULL); - } - /* - * Add closest (provable) encloser NSEC3. - */ - query_findclosestnsec3(cname, db, version, client, rdataset, - sigrdataset, fname, ISC_TRUE, cname); - if (!dns_rdataset_isassociated(rdataset)) - goto cleanup; - if (!ispositive) - query_addrrset(client, &fname, &rdataset, &sigrdataset, - dbuf, DNS_SECTION_AUTHORITY); - - /* - * Replace resources which were consumed by query_addrrset. - */ - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - } - - if (rdataset == NULL) - rdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - - if (sigrdataset == NULL) - sigrdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - - if (fname == NULL || rdataset == NULL || sigrdataset == NULL) - goto cleanup; - /* - * Add no qname proof. - */ - labels = dns_name_countlabels(cname) + 1; - if (dns_name_countlabels(name) == labels) - dns_name_copy(name, wname, NULL); - else - dns_name_split(name, labels, NULL, wname); - - query_findclosestnsec3(wname, db, version, client, rdataset, - sigrdataset, fname, ISC_FALSE, NULL); - if (!dns_rdataset_isassociated(rdataset)) - goto cleanup; - query_addrrset(client, &fname, &rdataset, &sigrdataset, - dbuf, DNS_SECTION_AUTHORITY); - - if (ispositive) - goto cleanup; - - /* - * Replace resources which were consumed by query_addrrset. - */ - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - } - - if (rdataset == NULL) - rdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - - if (sigrdataset == NULL) - sigrdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - - if (fname == NULL || rdataset == NULL || sigrdataset == NULL) - goto cleanup; - /* - * Add the no wildcard proof. - */ - result = dns_name_concatenate(dns_wildcardname, - cname, wname, NULL); - if (result != ISC_R_SUCCESS) - goto cleanup; - - query_findclosestnsec3(wname, db, version, client, rdataset, - sigrdataset, fname, nodata, NULL); - if (!dns_rdataset_isassociated(rdataset)) - goto cleanup; - query_addrrset(client, &fname, &rdataset, &sigrdataset, - dbuf, DNS_SECTION_AUTHORITY); - - goto cleanup; - } else if (result == DNS_R_NXDOMAIN) { - if (!ispositive) - result = dns_rdataset_first(rdataset); - if (result == ISC_R_SUCCESS) { - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &nsec, NULL); - } - if (result == ISC_R_SUCCESS) { - (void)dns_name_fullcompare(name, fname, &order, - &olabels); - (void)dns_name_fullcompare(name, &nsec.next, &order, - &nlabels); - /* - * Check for a pathological condition created when - * serving some malformed signed zones and bail out. - */ - if (dns_name_countlabels(name) == nlabels) - goto cleanup; - - if (olabels > nlabels) - dns_name_split(name, olabels, NULL, wname); - else - dns_name_split(name, nlabels, NULL, wname); - result = dns_name_concatenate(dns_wildcardname, - wname, wname, NULL); - if (result == ISC_R_SUCCESS) - have_wname = ISC_TRUE; - dns_rdata_freestruct(&nsec); - } - query_addrrset(client, &fname, &rdataset, &sigrdataset, - dbuf, DNS_SECTION_AUTHORITY); - } - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); - if (have_wname) { - ispositive = ISC_TRUE; /* prevent loop */ - if (!dns_name_equal(name, wname)) { - name = wname; - goto again; - } - } - cleanup: - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); -} - -static void -query_addnxrrsetnsec(ns_client_t *client, dns_db_t *db, - dns_dbversion_t *version, dns_name_t **namep, - dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp) -{ - dns_name_t *name; - dns_rdataset_t *sigrdataset; - dns_rdata_t sigrdata; - dns_rdata_rrsig_t sig; - unsigned int labels; - isc_buffer_t *dbuf, b; - dns_name_t *fname; - isc_result_t result; - - name = *namep; - if ((name->attributes & DNS_NAMEATTR_WILDCARD) == 0) { - query_addrrset(client, namep, rdatasetp, sigrdatasetp, - NULL, DNS_SECTION_AUTHORITY); - return; - } - - if (sigrdatasetp == NULL) - return; - - sigrdataset = *sigrdatasetp; - if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) - return; - result = dns_rdataset_first(sigrdataset); - if (result != ISC_R_SUCCESS) - return; - dns_rdata_init(&sigrdata); - dns_rdataset_current(sigrdataset, &sigrdata); - result = dns_rdata_tostruct(&sigrdata, &sig, NULL); - if (result != ISC_R_SUCCESS) - return; - - labels = dns_name_countlabels(name); - if ((unsigned int)sig.labels + 1 >= labels) - return; - - /* XXX */ - query_addwildcardproof(client, db, version, client->query.qname, - ISC_TRUE, ISC_FALSE); - - /* - * We'll need some resources... - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - return; - fname = query_newname(client, dbuf, &b); - if (fname == NULL) - return; - dns_name_split(name, sig.labels + 1, NULL, fname); - /* This will succeed, since we've stripped labels. */ - RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname, fname, fname, - NULL) == ISC_R_SUCCESS); - query_addrrset(client, &fname, rdatasetp, sigrdatasetp, - dbuf, DNS_SECTION_AUTHORITY); -} - -static void -query_resume(isc_task_t *task, isc_event_t *event) { - dns_fetchevent_t *devent = (dns_fetchevent_t *)event; - dns_fetch_t *fetch; - ns_client_t *client; - isc_boolean_t fetch_canceled, client_shuttingdown; - isc_result_t result; - isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_ERRORS; - int errorloglevel; - - /* - * Resume a query after recursion. - */ - - UNUSED(task); - - REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); - client = devent->ev_arg; - REQUIRE(NS_CLIENT_VALID(client)); - REQUIRE(task == client->task); - REQUIRE(RECURSING(client)); - - LOCK(&client->query.fetchlock); - if (client->query.fetch != NULL) { - /* - * This is the fetch we've been waiting for. - */ - INSIST(devent->fetch == client->query.fetch); - client->query.fetch = NULL; - fetch_canceled = ISC_FALSE; - /* - * Update client->now. - */ - isc_stdtime_get(&client->now); - } else { - /* - * This is a fetch completion event for a canceled fetch. - * Clean up and don't resume the find. - */ - fetch_canceled = ISC_TRUE; - } - UNLOCK(&client->query.fetchlock); - INSIST(client->query.fetch == NULL); - - client->query.attributes &= ~NS_QUERYATTR_RECURSING; - fetch = devent->fetch; - devent->fetch = NULL; - - /* - * If this client is shutting down, or this transaction - * has timed out, do not resume the find. - */ - client_shuttingdown = ns_client_shuttingdown(client); - if (fetch_canceled || client_shuttingdown) { - if (devent->node != NULL) - dns_db_detachnode(devent->db, &devent->node); - if (devent->db != NULL) - dns_db_detach(&devent->db); - query_putrdataset(client, &devent->rdataset); - if (devent->sigrdataset != NULL) - query_putrdataset(client, &devent->sigrdataset); - isc_event_free(&event); - if (fetch_canceled) { - CTRACE(ISC_LOG_ERROR, "fetch cancelled"); - query_error(client, DNS_R_SERVFAIL, __LINE__); - } else - query_next(client, ISC_R_CANCELED); - /* - * This may destroy the client. - */ - ns_client_detach(&client); - } else { - result = query_find(client, devent, 0); - if (result != ISC_R_SUCCESS) { - if (result == DNS_R_SERVFAIL) - errorloglevel = ISC_LOG_DEBUG(2); - else - errorloglevel = ISC_LOG_DEBUG(4); - if (isc_log_wouldlog(ns_g_lctx, errorloglevel)) { - dns_resolver_logfetch(fetch, ns_g_lctx, - logcategory, - NS_LOGMODULE_QUERY, - errorloglevel, ISC_FALSE); - } - } - } - - dns_resolver_destroyfetch(&fetch); -} - static void prefetch_done(isc_task_t *task, isc_event_t *event) { dns_fetchevent_t *devent = (dns_fetchevent_t *)event; @@ -3997,137 +2904,6 @@ query_prefetch(ns_client_t *client, dns_name_t *qname, dns_rdataset_clearprefetch(rdataset); } -static isc_result_t -query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, - dns_name_t *qdomain, dns_rdataset_t *nameservers, - isc_boolean_t resuming) -{ - isc_result_t result; - dns_rdataset_t *rdataset, *sigrdataset; - isc_sockaddr_t *peeraddr; - - if (!resuming) - inc_stats(client, dns_nsstatscounter_recursion); - - /* - * We are about to recurse, which means that this client will - * be unavailable for serving new requests for an indeterminate - * amount of time. If this client is currently responsible - * for handling incoming queries, set up a new client - * object to handle them while we are waiting for a - * response. There is no need to replace TCP clients - * because those have already been replaced when the - * connection was accepted (if allowed by the TCP quota). - */ - if (client->recursionquota == NULL) { - result = isc_quota_attach(&ns_g_server->recursionquota, - &client->recursionquota); - - isc_stats_increment(ns_g_server->nsstats, - dns_nsstatscounter_recursclients); - - if (result == ISC_R_SOFTQUOTA) { - static isc_stdtime_t last = 0; - isc_stdtime_t now; - isc_stdtime_get(&now); - if (now != last) { - last = now; - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_QUERY, - ISC_LOG_WARNING, - "recursive-clients soft limit " - "exceeded (%d/%d/%d), " - "aborting oldest query", - client->recursionquota->used, - client->recursionquota->soft, - client->recursionquota->max); - } - ns_client_killoldestquery(client); - result = ISC_R_SUCCESS; - } else if (result == ISC_R_QUOTA) { - static isc_stdtime_t last = 0; - isc_stdtime_t now; - isc_stdtime_get(&now); - if (now != last) { - last = now; - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_QUERY, - ISC_LOG_WARNING, - "no more recursive clients " - "(%d/%d/%d): %s", - ns_g_server->recursionquota.used, - ns_g_server->recursionquota.soft, - ns_g_server->recursionquota.max, - isc_result_totext(result)); - } - ns_client_killoldestquery(client); - } - if (result == ISC_R_SUCCESS && !client->mortal && - !TCP(client)) { - result = ns_client_replace(client); - if (result != ISC_R_SUCCESS) { - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_QUERY, - ISC_LOG_WARNING, - "ns_client_replace() failed: %s", - isc_result_totext(result)); - isc_quota_detach(&client->recursionquota); - isc_stats_decrement(ns_g_server->nsstats, - dns_nsstatscounter_recursclients); - } - } - if (result != ISC_R_SUCCESS) - return (result); - ns_client_recursing(client); - } - - /* - * Invoke the resolver. - */ - REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); - REQUIRE(client->query.fetch == NULL); - - rdataset = query_newrdataset(client); - if (rdataset == NULL) - return (ISC_R_NOMEMORY); - if (WANTDNSSEC(client)) { - sigrdataset = query_newrdataset(client); - if (sigrdataset == NULL) { - query_putrdataset(client, &rdataset); - return (ISC_R_NOMEMORY); - } - } else - sigrdataset = NULL; - - if (client->query.timerset == ISC_FALSE) - ns_client_settimeout(client, 60); - if (!TCP(client)) - peeraddr = &client->peeraddr; - else - peeraddr = NULL; - result = dns_resolver_createfetch3(client->view->resolver, - qname, qtype, qdomain, nameservers, - NULL, peeraddr, client->message->id, - client->query.fetchoptions, 0, NULL, - client->task, query_resume, client, - rdataset, sigrdataset, - &client->query.fetch); - - if (result == ISC_R_SUCCESS) { - /* - * Record that we're waiting for an event. A client which - * is shutting down will not be destroyed until all the - * events have been received. - */ - } else { - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - } - - return (result); -} - static inline void rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp) @@ -4630,7 +3406,7 @@ rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, * better served with simple wildcards. Making them work would * require complications to get the number of labels matched * in the name or the found name to the main DNS_R_DNAME case - * in query_find(). The domain also does not appear in the + * in query_dname(). The domain also does not appear in the * summary database at the right level, so this happens only * with a single policy zone when we have no summary database. * Treat it as a miss. @@ -5634,76 +4410,6 @@ rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, return (ISC_TRUE); } -/* - * Add a CNAME to the query response, including translating foo.evil.com and - * *.evil.com CNAME *.example.com - * to - * foo.evil.com CNAME foo.evil.com.example.com - */ -static isc_result_t -rpz_add_cname(ns_client_t *client, dns_rpz_st_t *st, - dns_name_t *cname, dns_name_t *fname, isc_buffer_t *dbuf) -{ - dns_fixedname_t prefix, suffix; - unsigned int labels; - isc_result_t result; - - CTRACE(ISC_LOG_DEBUG(3), "rpz_add_cname"); - - labels = dns_name_countlabels(cname); - if (labels > 2 && dns_name_iswildcard(cname)) { - dns_fixedname_init(&prefix); - dns_name_split(client->query.qname, 1, - dns_fixedname_name(&prefix), NULL); - dns_fixedname_init(&suffix); - dns_name_split(cname, labels-1, - NULL, dns_fixedname_name(&suffix)); - result = dns_name_concatenate(dns_fixedname_name(&prefix), - dns_fixedname_name(&suffix), - fname, NULL); - if (result == DNS_R_NAMETOOLONG) - client->message->rcode = dns_rcode_yxdomain; - } else { - result = dns_name_copy(cname, fname, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - } - if (result != ISC_R_SUCCESS) - return (result); - query_keepname(client, fname, dbuf); - result = query_add_cname(client, client->query.qname, - fname, dns_trust_authanswer, st->m.ttl); - if (result != ISC_R_SUCCESS) - return (result); - rpz_log_rewrite(client, ISC_FALSE, st->m.policy, - st->m.type, st->m.zone, st->p_name, fname, - st->m.rpz->num); - ns_client_qnamereplace(client, fname); - /* - * Turn off DNSSEC because the results of a - * response policy zone cannot verify. - */ - client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | - NS_CLIENTATTR_WANTAD); - return (ISC_R_SUCCESS); -} - -#define MAX_RESTARTS 16 - -#define QUERY_ERROR(r) \ -do { \ - eresult = r; \ - want_restart = ISC_FALSE; \ - line = __LINE__; \ -} while (0) - -#define RECURSE_ERROR(r) \ -do { \ - if ((r) == DNS_R_DUPLICATE || (r) == DNS_R_DROP) \ - QUERY_ERROR(r); \ - else \ - QUERY_ERROR(DNS_R_SERVFAIL); \ -} while (0) - /* * Extract a network address from the RDATA of an A or AAAA * record. @@ -5733,152 +4439,6 @@ rdata_tonetaddr(const dns_rdata_t *rdata, isc_netaddr_t *netaddr) { } } -/* - * Find the sort order of 'rdata' in the topology-like - * ACL forming the second element in a 2-element top-level - * sortlist statement. - */ -static int -query_sortlist_order_2element(const dns_rdata_t *rdata, const void *arg) { - isc_netaddr_t netaddr; - - if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) - return (INT_MAX); - return (ns_sortlist_addrorder2(&netaddr, arg)); -} - -/* - * Find the sort order of 'rdata' in the matching element - * of a 1-element top-level sortlist statement. - */ -static int -query_sortlist_order_1element(const dns_rdata_t *rdata, const void *arg) { - isc_netaddr_t netaddr; - - if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) - return (INT_MAX); - return (ns_sortlist_addrorder1(&netaddr, arg)); -} - -/* - * Find the sortlist statement that applies to 'client' and set up - * the sortlist info in in client->message appropriately. - */ -static void -setup_query_sortlist(ns_client_t *client) { - isc_netaddr_t netaddr; - dns_rdatasetorderfunc_t order = NULL; - const void *order_arg = NULL; - - isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); - switch (ns_sortlist_setup(client->view->sortlist, - &netaddr, &order_arg)) { - case NS_SORTLISTTYPE_1ELEMENT: - order = query_sortlist_order_1element; - break; - case NS_SORTLISTTYPE_2ELEMENT: - order = query_sortlist_order_2element; - break; - case NS_SORTLISTTYPE_NONE: - order = NULL; - break; - default: - INSIST(0); - break; - } - dns_message_setsortorder(client->message, order, order_arg); -} - -static void -query_addnoqnameproof(ns_client_t *client, dns_rdataset_t *rdataset) { - isc_buffer_t *dbuf, b; - dns_name_t *fname; - dns_rdataset_t *neg, *negsig; - isc_result_t result = ISC_R_NOMEMORY; - - CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof"); - - fname = NULL; - neg = NULL; - negsig = NULL; - - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - neg = query_newrdataset(client); - negsig = query_newrdataset(client); - if (fname == NULL || neg == NULL || negsig == NULL) - goto cleanup; - - result = dns_rdataset_getnoqname(rdataset, fname, neg, negsig); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - - query_addrrset(client, &fname, &neg, &negsig, dbuf, - DNS_SECTION_AUTHORITY); - - if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) == 0) - goto cleanup; - - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); - } - if (neg == NULL) - neg = query_newrdataset(client); - else if (dns_rdataset_isassociated(neg)) - dns_rdataset_disassociate(neg); - if (negsig == NULL) - negsig = query_newrdataset(client); - else if (dns_rdataset_isassociated(negsig)) - dns_rdataset_disassociate(negsig); - if (fname == NULL || neg == NULL || negsig == NULL) - goto cleanup; - result = dns_rdataset_getclosest(rdataset, fname, neg, negsig); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - - query_addrrset(client, &fname, &neg, &negsig, dbuf, - DNS_SECTION_AUTHORITY); - - cleanup: - if (neg != NULL) - query_putrdataset(client, &neg); - if (negsig != NULL) - query_putrdataset(client, &negsig); - if (fname != NULL) - query_releasename(client, &fname); -} - -static inline void -answer_in_glue(ns_client_t *client, dns_rdatatype_t qtype) { - dns_name_t *name; - dns_message_t *msg; - dns_section_t section = DNS_SECTION_ADDITIONAL; - dns_rdataset_t *rdataset = NULL; - - msg = client->message; - for (name = ISC_LIST_HEAD(msg->sections[section]); - name != NULL; - name = ISC_LIST_NEXT(name, link)) - if (dns_name_equal(name, client->query.qname)) { - for (rdataset = ISC_LIST_HEAD(name->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) - if (rdataset->type == qtype) - break; - break; - } - if (rdataset != NULL) { - ISC_LIST_UNLINK(msg->sections[section], name, link); - ISC_LIST_PREPEND(msg->sections[section], name, link); - ISC_LIST_UNLINK(name->list, rdataset, link); - ISC_LIST_PREPEND(name->list, rdataset, link); - rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; - } -} - #define NS_NAME_INIT(A,B) \ { \ DNS_NAME_MAGIC, \ @@ -6473,98 +5033,120 @@ redirect2(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, return (result); } -/* - * Do the bulk of query processing for the current query of 'client'. - * If 'event' is non-NULL, we are returning from recursion and 'qtype' - * is ignored. Otherwise, 'qtype' is the query type. +/*% + * Initialize query context 'qctx'. Run by query_setup() when + * first handling a client query, and by query_resume() when + * returning from recursion. */ -static isc_result_t -query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) +static void +qctx_init(ns_client_t *client, dns_fetchevent_t *event, + dns_rdatatype_t qtype, query_ctx_t *qctx) { - dns_db_t *db, *zdb; - dns_dbnode_t *node; - dns_rdatatype_t type = qtype; - dns_name_t *fname, *zfname, *tname, *prefix; - dns_rdataset_t *rdataset, *trdataset; - dns_rdataset_t *sigrdataset, *zrdataset, *zsigrdataset; - dns_rdataset_t **sigrdatasetp; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdatasetiter_t *rdsiter; - isc_boolean_t want_restart, is_zone, need_wildcardproof; - isc_boolean_t is_staticstub_zone; - isc_boolean_t authoritative = ISC_FALSE; - unsigned int n, nlabels; - dns_namereln_t namereln; - int order; - isc_buffer_t *dbuf; - isc_buffer_t b; - isc_result_t result, eresult, tresult; - dns_fixedname_t fixed; - dns_fixedname_t wildcardname; - dns_dbversion_t *version, *zversion; - dns_zone_t *zone; - dns_rdata_cname_t cname; - dns_rdata_dname_t dname; - unsigned int options; - isc_boolean_t empty_wild; - dns_rdataset_t *noqname; - dns_rpz_st_t *rpz_st; - isc_boolean_t resuming; - int line = -1; - isc_boolean_t dns64_exclude, dns64, rpz; - isc_boolean_t nxrewrite = ISC_FALSE; - isc_boolean_t redirected = ISC_FALSE; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - char errmsg[256]; - isc_boolean_t associated; - dns_section_t section; - dns_ttl_t ttl; - isc_boolean_t failcache; - isc_uint32_t flags; + REQUIRE(qctx != NULL); + REQUIRE(client != NULL); + + /* Set this first so CCTRACE will work */ + qctx->client = client; + + CCTRACE(ISC_LOG_DEBUG(3), "qctx_create"); + + qctx->event = event; + qctx->qtype = qctx->type = qtype; + qctx->result = ISC_R_SUCCESS; + qctx->fname = NULL; + qctx->zfname = NULL; + qctx->rdataset = NULL; + qctx->zrdataset = NULL; + qctx->sigrdataset = NULL; + qctx->zsigrdataset = NULL; + qctx->zversion = NULL; + qctx->node = NULL; + qctx->db = NULL; + qctx->zdb = NULL; + qctx->version = NULL; + qctx->zone = NULL; + qctx->need_wildcardproof = ISC_FALSE; + qctx->redirected = ISC_FALSE; + qctx->dns64_exclude = qctx->dns64 = ISC_FALSE; + qctx->options = 0; + qctx->resuming = ISC_FALSE; + qctx->is_zone = ISC_FALSE; + qctx->is_staticstub_zone = ISC_FALSE; + qctx->nxrewrite = ISC_FALSE; + qctx->authoritative = ISC_FALSE; +} + +/*% + * Clean up and disassociate the rdataset and node pointers in qctx. + */ +static void +qctx_clean(query_ctx_t *qctx) { + if (qctx->rdataset != NULL && + dns_rdataset_isassociated(qctx->rdataset)) + { + dns_rdataset_disassociate(qctx->rdataset); + } + if (qctx->sigrdataset != NULL && + dns_rdataset_isassociated(qctx->sigrdataset)) + { + dns_rdataset_disassociate(qctx->sigrdataset); + } + if (qctx->db != NULL && qctx->node != NULL) { + dns_db_detachnode(qctx->db, &qctx->node); + } +} + +/*% + * Free any allocated memory associated with qctx. + */ +static void +qctx_freedata(query_ctx_t *qctx) { + if (qctx->rdataset != NULL) { + query_putrdataset(qctx->client, &qctx->rdataset); + } + + if (qctx->sigrdataset != NULL) { + query_putrdataset(qctx->client, &qctx->sigrdataset); + } + + if (qctx->fname != NULL) { + query_releasename(qctx->client, &qctx->fname); + } + + if (qctx->db != NULL) { + dns_db_detach(&qctx->db); + } + + if (qctx->zone != NULL) { + dns_zone_detach(&qctx->zone); + } + + if (qctx->zdb != NULL) { + query_putrdataset(qctx->client, &qctx->zrdataset); + if (qctx->zsigrdataset != NULL) + query_putrdataset(qctx->client, &qctx->zsigrdataset); + if (qctx->zfname != NULL) + query_releasename(qctx->client, &qctx->zfname); + dns_db_detach(&qctx->zdb); + } + + if (qctx->event != NULL) { + isc_event_free(ISC_EVENT_PTR(&qctx->event)); + } +} + +/*% + * Log detailed information about the query immediately after + * the client request or a return from recursion. + */ +static void +query_trace(query_ctx_t *qctx) { #ifdef WANT_QUERYTRACE char mbuf[BUFSIZ]; char qbuf[DNS_NAME_FORMATSIZE]; - char tbuf[DNS_RDATATYPE_FORMATSIZE]; -#endif - dns_name_t *rpzqname; - CTRACE(ISC_LOG_DEBUG(3), "query_find"); - - /* - * One-time initialization. - * - * It's especially important to initialize anything that the cleanup - * code might cleanup. - */ - - eresult = ISC_R_SUCCESS; - fname = NULL; - zfname = NULL; - rdataset = NULL; - zrdataset = NULL; - sigrdataset = NULL; - zsigrdataset = NULL; - zversion = NULL; - node = NULL; - db = NULL; - zdb = NULL; - version = NULL; - zone = NULL; - need_wildcardproof = ISC_FALSE; - empty_wild = ISC_FALSE; - dns64_exclude = dns64 = rpz = ISC_FALSE; - options = 0; - resuming = ISC_FALSE; - is_zone = ISC_FALSE; - is_staticstub_zone = ISC_FALSE; - - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - -#ifdef WANT_QUERYTRACE - if (client->query.origqname != NULL) - dns_name_format(client->query.origqname, qbuf, + if (qctx->client->query.origqname != NULL) + dns_name_format(qctx->client->query.origqname, qbuf, sizeof(qbuf)); else snprintf(qbuf, sizeof(qbuf), ""); @@ -6572,296 +5154,115 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) snprintf(mbuf, sizeof(mbuf) - 1, "client attr:0x%x, query attr:0x%X, restarts:%d, " "origqname:%s, timer:%d, authdb:%d, referral:%d", - client->attributes, - client->query.attributes, - client->query.restarts, qbuf, - (int) client->query.timerset, - (int) client->query.authdbset, - (int) client->query.isreferral); - CTRACE(ISC_LOG_DEBUG(3), mbuf); + qctx->client->attributes, + qctx->client->query.attributes, + qctx->client->query.restarts, qbuf, + (int) qctx->client->query.timerset, + (int) qctx->client->query.authdbset, + (int) qctx->client->query.isreferral); + CCTRACE(ISC_LOG_DEBUG(3), mbuf); +#else + UNUSED(qctx); #endif +} - if (event != NULL) { - /* - * We're returning from recursion. Restore the query context - * and resume. - */ - want_restart = ISC_FALSE; +/* + * Set up query processing for the current query of 'client'. + * Calls qctx_init() to initialize a query context, checks + * the SERVFAIL cache, then hands off processing to query_start(). + * + * This is called only from ns_query_start(), to begin a query + * for the first time. Restarting an existing query (for + * instance, to handle CNAME lookups), is done by calling + * query_start() again with the same query context. Resuming from + * recursion is handled by query_resume(). + */ +static isc_result_t +query_setup(ns_client_t *client, dns_rdatatype_t qtype) { + isc_result_t result; + query_ctx_t qctx; - rpz_st = client->query.rpz_st; - if (rpz_st != NULL && - (rpz_st->state & DNS_RPZ_RECURSING) != 0) - { - CTRACE(ISC_LOG_DEBUG(3), "resume from RPZ recursion"); -#ifdef WANT_QUERYTRACE - { - char pbuf[DNS_NAME_FORMATSIZE] = ""; - char fbuf[DNS_NAME_FORMATSIZE] = ""; - if (rpz_st->r_name != NULL) - dns_name_format(rpz_st->r_name, - qbuf, sizeof(qbuf)); - else - snprintf(qbuf, sizeof(qbuf), - ""); - if (rpz_st->p_name != NULL) - dns_name_format(rpz_st->p_name, - pbuf, sizeof(pbuf)); - if (rpz_st->fname != NULL) - dns_name_format(rpz_st->fname, - fbuf, sizeof(fbuf)); - - snprintf(mbuf, sizeof(mbuf) - 1, - "rpz rname:%s, pname:%s, fname:%s", - qbuf, pbuf, fbuf); - CTRACE(ISC_LOG_DEBUG(3), mbuf); - } -#endif - - is_zone = rpz_st->q.is_zone; - authoritative = rpz_st->q.authoritative; - RESTORE(zone, rpz_st->q.zone); - RESTORE(node, rpz_st->q.node); - RESTORE(db, rpz_st->q.db); - RESTORE(rdataset, rpz_st->q.rdataset); - RESTORE(sigrdataset, rpz_st->q.sigrdataset); - qtype = rpz_st->q.qtype; - - rpz_st->r.db = event->db; - if (event->node != NULL) - dns_db_detachnode(event->db, &event->node); - rpz_st->r.r_type = event->qtype; - rpz_st->r.r_rdataset = event->rdataset; - query_putrdataset(client, &event->sigrdataset); - } else if (REDIRECT(client)) { - /* - * Restore saved state. - */ - CTRACE(ISC_LOG_DEBUG(3), - "resume from redirect recursion"); -#ifdef WANT_QUERYTRACE - dns_name_format(client->query.redirect.fname, - qbuf, sizeof(qbuf)); - dns_rdatatype_format(client->query.redirect.qtype, - tbuf, sizeof(tbuf)); - snprintf(mbuf, sizeof(mbuf) - 1, - "redirect fname:%s, qtype:%s, auth:%d", - qbuf, tbuf, - client->query.redirect.authoritative); - CTRACE(ISC_LOG_DEBUG(3), mbuf); -#endif - qtype = client->query.redirect.qtype; - INSIST(client->query.redirect.rdataset != NULL); - RESTORE(rdataset, client->query.redirect.rdataset); - RESTORE(sigrdataset, - client->query.redirect.sigrdataset); - RESTORE(db, client->query.redirect.db); - RESTORE(node, client->query.redirect.node); - RESTORE(zone, client->query.redirect.zone); - authoritative = client->query.redirect.authoritative; - is_zone = client->query.redirect.is_zone; - - /* - * Free resources used while recursing. - */ - query_putrdataset(client, &event->rdataset); - query_putrdataset(client, &event->sigrdataset); - if (event->node != NULL) - dns_db_detachnode(event->db, &event->node); - if (event->db != NULL) - dns_db_detach(&event->db); - } else { - CTRACE(ISC_LOG_DEBUG(3), - "resume from normal recursion"); - authoritative = ISC_FALSE; - - qtype = event->qtype; - db = event->db; - node = event->node; - rdataset = event->rdataset; - sigrdataset = event->sigrdataset; - } - INSIST(rdataset != NULL); - - if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) - type = dns_rdatatype_any; - else - type = qtype; - - if (DNS64(client)) { - client->query.attributes &= ~NS_QUERYATTR_DNS64; - dns64 = ISC_TRUE; - } - if (DNS64EXCLUDE(client)) { - client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE; - dns64_exclude = ISC_TRUE; - } - - if (rpz_st != NULL && - (rpz_st->state & DNS_RPZ_RECURSING) != 0) - { - /* - * Has response policy changed out from under us? - */ - if (rpz_st->rpz_ver != client->view->rpzs->rpz_ver) { - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_QUERY, ISC_LOG_INFO, - "query_find: RPZ settings " - "out of date " - "(rpz_ver %d, expected %d)", - client->view->rpzs->rpz_ver, - rpz_st->rpz_ver); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - } - - /* - * We'll need some resources... - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: query_getnamebuf failed (1)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - fname = query_newname(client, dbuf, &b); - if (fname == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: query_newname failed (1)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - if (rpz_st != NULL && - (rpz_st->state & DNS_RPZ_RECURSING) != 0) { - tname = rpz_st->fname; - } else if (REDIRECT(client)) { - tname = client->query.redirect.fname; - } else { - tname = dns_fixedname_name(&event->foundname); - } - result = dns_name_copy(tname, fname, NULL); - if (result != ISC_R_SUCCESS) { - CTRACE(ISC_LOG_ERROR, - "query_find: dns_name_copy failed"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - if (rpz_st != NULL && - (rpz_st->state & DNS_RPZ_RECURSING) != 0) { - rpz_st->r.r_result = event->result; - result = rpz_st->q.result; - isc_event_free(ISC_EVENT_PTR(&event)); - } else if (REDIRECT(client)) { - result = client->query.redirect.result; - is_zone = client->query.redirect.is_zone; - } else { - result = event->result; - } - resuming = ISC_TRUE; - goto resume; - } + qctx_init(client, NULL, qtype, &qctx); + query_trace(&qctx); /* - * Not returning from recursion. - * - * First, check for a recent match in the view's SERVFAIL cache. - * If we find one, and it was from a query with CD=1, *or* - * if the current query has CD=0, then we can just return - * SERVFAIL now. + * Check SERVFAIL cache */ - if (RECURSIONOK(client)) { - flags = 0; -#ifdef ENABLE_AFL - if (ns_g_fuzz_type == ns_fuzz_resolver) { - failcache = ISC_FALSE; - } else { - failcache = dns_badcache_find(client->view->failcache, - client->query.qname, qtype, - &flags, &client->tnow); - } -#else - failcache = dns_badcache_find(client->view->failcache, - client->query.qname, qtype, - &flags, &client->tnow); -#endif - if (failcache && - (((flags & NS_FAILCACHE_CD) != 0) || - ((client->message->flags & DNS_MESSAGEFLAG_CD) == 0))) - { - if (isc_log_wouldlog(ns_g_lctx, ISC_LOG_DEBUG(1))) { - char namebuf[DNS_NAME_FORMATSIZE]; - char typename[DNS_RDATATYPE_FORMATSIZE]; - - dns_name_format(client->query.qname, - namebuf, sizeof(namebuf)); - dns_rdatatype_format(qtype, typename, - sizeof(typename)); - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_QUERY, - ISC_LOG_DEBUG(1), - "servfail cache hit %s/%s (%s)", - namebuf, typename, - ((flags & NS_FAILCACHE_CD) != 0) - ? "CD=1" - : "CD=0"); - } - client->attributes |= NS_CLIENTATTR_NOSETFC; - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } + result = query_sfcache(&qctx); + if (result != ISC_R_COMPLETE) { + return (result); } /* * If it's a SIG query, we'll iterate the node. */ - if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) - type = dns_rdatatype_any; - else - type = qtype; + if (qctx.qtype == dns_rdatatype_rrsig || + qctx.qtype == dns_rdatatype_sig) + { + qctx.type = dns_rdatatype_any; + } else { + qctx.type = qctx.qtype; + } - restart: - CTRACE(ISC_LOG_DEBUG(3), "query_find: restart"); - want_restart = ISC_FALSE; - authoritative = ISC_FALSE; - version = NULL; - zversion = NULL; - need_wildcardproof = ISC_FALSE; - rpz = ISC_FALSE; + return (query_start(&qctx)); +} - if (client->view->checknames && - !dns_rdata_checkowner(client->query.qname, - client->message->rdclass, - qtype, ISC_FALSE)) { +/*% + * Starting point for a client query or a chaining query. + * + * Called first by query_setup(), and then again as often as needed to + * follow a CNAME chain. Determines which authoritative database to + * search, then hands off processing to query_lookup(). + */ +static isc_result_t +query_start(query_ctx_t *qctx) { + isc_result_t result; + CCTRACE(ISC_LOG_DEBUG(3), "query_start"); + qctx->want_restart = ISC_FALSE; + qctx->authoritative = ISC_FALSE; + qctx->version = NULL; + qctx->zversion = NULL; + qctx->need_wildcardproof = ISC_FALSE; + + if (qctx->client->view->checknames && + !dns_rdata_checkowner(qctx->client->query.qname, + qctx->client->message->rdclass, + qctx->qtype, ISC_FALSE)) + { char namebuf[DNS_NAME_FORMATSIZE]; char typename[DNS_RDATATYPE_FORMATSIZE]; char classname[DNS_RDATACLASS_FORMATSIZE]; - dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); - dns_rdatatype_format(qtype, typename, sizeof(typename)); - dns_rdataclass_format(client->message->rdclass, classname, - sizeof(classname)); - ns_client_log(client, DNS_LOGCATEGORY_SECURITY, + dns_name_format(qctx->client->query.qname, + namebuf, sizeof(namebuf)); + dns_rdatatype_format(qctx->qtype, typename, sizeof(typename)); + dns_rdataclass_format(qctx->client->message->rdclass, + classname, sizeof(classname)); + ns_client_log(qctx->client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_QUERY, ISC_LOG_ERROR, "check-names failure %s/%s/%s", namebuf, typename, classname); - QUERY_ERROR(DNS_R_REFUSED); - goto cleanup; + QUERY_ERROR(qctx, DNS_R_REFUSED); + return (query_done(qctx)); } /* * First we must find the right database. */ - options &= DNS_GETDB_NOLOG; /* Preserve DNS_GETDB_NOLOG. */ - if (dns_rdatatype_atparent(qtype) && - !dns_name_equal(client->query.qname, dns_rootname)) - options |= DNS_GETDB_NOEXACT; - result = query_getdb(client, client->query.qname, qtype, options, - &zone, &db, &version, &is_zone); - if (ISC_UNLIKELY((result != ISC_R_SUCCESS || !is_zone) && - qtype == dns_rdatatype_ds && - !RECURSIONOK(client) && - (options & DNS_GETDB_NOEXACT) != 0)) + qctx->options &= DNS_GETDB_NOLOG; /* Preserve DNS_GETDB_NOLOG. */ + if (dns_rdatatype_atparent(qctx->qtype) && + !dns_name_equal(qctx->client->query.qname, dns_rootname)) + { + qctx->options |= DNS_GETDB_NOEXACT; + } + + result = query_getdb(qctx->client, qctx->client->query.qname, + qctx->qtype, qctx->options, &qctx->zone, + &qctx->db, &qctx->version, &qctx->is_zone); + if (ISC_UNLIKELY((result != ISC_R_SUCCESS || !qctx->is_zone) && + qctx->qtype == dns_rdatatype_ds && + !RECURSIONOK(qctx->client) && + (qctx->options & DNS_GETDB_NOEXACT) != 0)) { /* * If the query type is DS, look to see if we are @@ -6870,136 +5271,674 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) dns_db_t *tdb = NULL; dns_zone_t *tzone = NULL; dns_dbversion_t *tversion = NULL; + isc_result_t tresult; - tresult = query_getzonedb(client, client->query.qname, qtype, - DNS_GETDB_PARTIAL, &tzone, &tdb, - &tversion); + tresult = query_getzonedb(qctx->client, + qctx->client->query.qname, + qctx->qtype, + DNS_GETDB_PARTIAL, + &tzone, &tdb, &tversion); if (tresult == ISC_R_SUCCESS) { - options &= ~DNS_GETDB_NOEXACT; - query_putrdataset(client, &rdataset); - if (db != NULL) - dns_db_detach(&db); - if (zone != NULL) - dns_zone_detach(&zone); - version = NULL; - RESTORE(version, tversion); - RESTORE(db, tdb); - RESTORE(zone, tzone); - is_zone = ISC_TRUE; + qctx->options &= ~DNS_GETDB_NOEXACT; + query_putrdataset(qctx->client, &qctx->rdataset); + if (qctx->db != NULL) { + dns_db_detach(&qctx->db); + } + if (qctx->zone != NULL) { + dns_zone_detach(&qctx->zone); + } + qctx->version = NULL; + RESTORE(qctx->version, tversion); + RESTORE(qctx->db, tdb); + RESTORE(qctx->zone, tzone); + qctx->is_zone = ISC_TRUE; result = ISC_R_SUCCESS; } else { - if (tdb != NULL) + if (tdb != NULL) { dns_db_detach(&tdb); - if (tzone != NULL) + } + if (tzone != NULL) { dns_zone_detach(&tzone); + } } } if (result != ISC_R_SUCCESS) { if (result == DNS_R_REFUSED) { - if (WANTRECURSION(client)) { - inc_stats(client, + if (WANTRECURSION(qctx->client)) { + inc_stats(qctx->client, dns_nsstatscounter_recurserej); - } else - inc_stats(client, dns_nsstatscounter_authrej); - if (!PARTIALANSWER(client)) - QUERY_ERROR(DNS_R_REFUSED); + } else { + inc_stats(qctx->client, + dns_nsstatscounter_authrej); + } + if (!PARTIALANSWER(qctx->client)) { + QUERY_ERROR(qctx, DNS_R_REFUSED); + } } else { - CTRACE(ISC_LOG_ERROR, - "query_find: query_getdb failed"); - QUERY_ERROR(DNS_R_SERVFAIL); + CCTRACE(ISC_LOG_ERROR, + "query_start: query_getdb failed"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); } - goto cleanup; + return (query_done(qctx)); } - is_staticstub_zone = ISC_FALSE; - if (is_zone) { - authoritative = ISC_TRUE; - if (zone != NULL && - dns_zone_gettype(zone) == dns_zone_staticstub) - is_staticstub_zone = ISC_TRUE; + qctx->is_staticstub_zone = ISC_FALSE; + if (qctx->is_zone) { + qctx->authoritative = ISC_TRUE; + if (qctx->zone != NULL && + dns_zone_gettype(qctx->zone) == dns_zone_staticstub) + { + qctx->is_staticstub_zone = ISC_TRUE; + } } - if (event == NULL && client->query.restarts == 0) { - if (is_zone) { - if (zone != NULL) { + if (qctx->event == NULL && qctx->client->query.restarts == 0) { + if (qctx->is_zone) { + if (qctx->zone != NULL) { /* * if is_zone = true, zone = NULL then this is * a DLZ zone. Don't attempt to attach zone. */ - dns_zone_attach(zone, &client->query.authzone); + dns_zone_attach(qctx->zone, + &qctx->client->query.authzone); } - dns_db_attach(db, &client->query.authdb); + dns_db_attach(qctx->db, &qctx->client->query.authdb); } - client->query.authdbset = ISC_TRUE; + qctx->client->query.authdbset = ISC_TRUE; /* Track TCP vs UDP stats per zone */ - if (TCP(client)) - inc_stats(client, dns_nsstatscounter_tcp); - else - inc_stats(client, dns_nsstatscounter_udp); + if (TCP(qctx->client)) { + inc_stats(qctx->client, dns_nsstatscounter_tcp); + } else { + inc_stats(qctx->client, dns_nsstatscounter_udp); + } } - db_find: - CTRACE(ISC_LOG_DEBUG(3), "query_find: db_find"); + return (query_lookup(qctx)); +} + +/*% + * Perform a local database lookup, in either an authoritative or + * cache database. If unable to answer, call query_done(); otherwise + * hand off processing to query_gotanswer(). + */ +static isc_result_t +query_lookup(query_ctx_t *qctx) { + isc_buffer_t b; + isc_result_t result; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CCTRACE(ISC_LOG_DEBUG(3), "query_lookup"); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, qctx->client, NULL); + /* * We'll need some resources... */ - dbuf = query_getnamebuf(client); - if (ISC_UNLIKELY(dbuf == NULL)) { - CTRACE(ISC_LOG_ERROR, - "query_find: query_getnamebuf failed (2)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; + qctx->dbuf = query_getnamebuf(qctx->client); + if (ISC_UNLIKELY(qctx->dbuf == NULL)) { + CCTRACE(ISC_LOG_ERROR, + "query_lookup: query_getnamebuf failed (2)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); } - fname = query_newname(client, dbuf, &b); - rdataset = query_newrdataset(client); - if (ISC_UNLIKELY(fname == NULL || rdataset == NULL)) { - CTRACE(ISC_LOG_ERROR, - "query_find: query_newname failed (2)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; + + qctx->fname = query_newname(qctx->client, qctx->dbuf, &b); + qctx->rdataset = query_newrdataset(qctx->client); + + if (ISC_UNLIKELY(qctx->fname == NULL || qctx->rdataset == NULL)) { + CCTRACE(ISC_LOG_ERROR, + "query_lookup: query_newname failed (2)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); } - if (WANTDNSSEC(client) && (!is_zone || dns_db_issecure(db))) { - sigrdataset = query_newrdataset(client); - if (sigrdataset == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: query_newrdataset failed (2)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; + + if (WANTDNSSEC(qctx->client) && + (!qctx->is_zone || dns_db_issecure(qctx->db))) + { + qctx->sigrdataset = query_newrdataset(qctx->client); + if (qctx->sigrdataset == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_lookup: query_newrdataset failed (2)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); } } /* - * Now look for an answer in the database. If this is a dns64 - * AAAA lookup on a rpz database adjust the qname. + * Now look for an answer in the database. */ - if (dns64 && rpz) - rpzqname = client->query.rpz_st->p_name; - else - rpzqname = client->query.qname; + result = dns_db_findext(qctx->db, qctx->client->query.qname, + qctx->version, qctx->type, + qctx->client->query.dboptions, + qctx->client->now, &qctx->node, + qctx->fname, &cm, &ci, + qctx->rdataset, qctx->sigrdataset); - result = dns_db_findext(db, rpzqname, version, type, - client->query.dboptions, client->now, - &node, fname, &cm, &ci, rdataset, sigrdataset); - /* - * Fixup fname and sigrdataset. - */ - if (dns64 && rpz) { - isc_result_t rresult; - - rresult = dns_name_copy(client->query.qname, fname, NULL); - RUNTIME_CHECK(rresult == ISC_R_SUCCESS); - if (sigrdataset != NULL && - dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); + if (!qctx->is_zone) { + dns_cache_updatestats(qctx->client->view->cache, result); } - if (!is_zone) - dns_cache_updatestats(client->view->cache, result); + return (query_gotanswer(qctx, result)); +} - resume: - CTRACE(ISC_LOG_DEBUG(3), "query_find: resume"); +/* + * Event handler to resume processing a query after recursion. + * If the query has timed out or been canceled or the system + * is shutting down, clean up and exit; otherwise, call + * query_resume() to continue the ongoing work. + */ +static void +fetch_callback(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; + dns_fetch_t *fetch; + ns_client_t *client; + isc_boolean_t fetch_canceled, client_shuttingdown; + isc_result_t result; + isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_ERRORS; + int errorloglevel; + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + client = devent->ev_arg; + REQUIRE(NS_CLIENT_VALID(client)); + REQUIRE(task == client->task); + REQUIRE(RECURSING(client)); + + LOCK(&client->query.fetchlock); + if (client->query.fetch != NULL) { + /* + * This is the fetch we've been waiting for. + */ + INSIST(devent->fetch == client->query.fetch); + client->query.fetch = NULL; + fetch_canceled = ISC_FALSE; + /* + * Update client->now. + */ + isc_stdtime_get(&client->now); + } else { + /* + * This is a fetch completion event for a canceled fetch. + * Clean up and don't resume the find. + */ + fetch_canceled = ISC_TRUE; + } + UNLOCK(&client->query.fetchlock); + INSIST(client->query.fetch == NULL); + + client->query.attributes &= ~NS_QUERYATTR_RECURSING; + fetch = devent->fetch; + devent->fetch = NULL; + + /* + * If this client is shutting down, or this transaction + * has timed out, do not resume the find. + */ + client_shuttingdown = ns_client_shuttingdown(client); + if (fetch_canceled || client_shuttingdown) { + if (devent->node != NULL) + dns_db_detachnode(devent->db, &devent->node); + if (devent->db != NULL) + dns_db_detach(&devent->db); + query_putrdataset(client, &devent->rdataset); + if (devent->sigrdataset != NULL) + query_putrdataset(client, &devent->sigrdataset); + isc_event_free(&event); + if (fetch_canceled) { + CTRACE(ISC_LOG_ERROR, "fetch cancelled"); + query_error(client, DNS_R_SERVFAIL, __LINE__); + } else { + query_next(client, ISC_R_CANCELED); + } + /* + * This may destroy the client. + */ + ns_client_detach(&client); + } else { + query_ctx_t qctx; + + qctx_init(client, devent, 0, &qctx); + query_trace(&qctx); + + result = query_resume(&qctx); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_SERVFAIL) { + errorloglevel = ISC_LOG_DEBUG(2); + } else { + errorloglevel = ISC_LOG_DEBUG(4); + } + if (isc_log_wouldlog(ns_g_lctx, errorloglevel)) { + dns_resolver_logfetch(fetch, ns_g_lctx, + logcategory, + NS_LOGMODULE_QUERY, + errorloglevel, ISC_FALSE); + } + } + } + + dns_resolver_destroyfetch(&fetch); +} + +/*% + * Prepare client for recursion, then create a resolver fetch, with + * the event callback set to fetch_callback(). Afterward we terminate + * this phase of the query, and resume with a new query context when + * recursion completes. + */ +static isc_result_t +query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, + dns_name_t *qdomain, dns_rdataset_t *nameservers, + isc_boolean_t resuming) +{ + isc_result_t result; + dns_rdataset_t *rdataset, *sigrdataset; + isc_sockaddr_t *peeraddr = NULL; + + if (!resuming) + inc_stats(client, dns_nsstatscounter_recursion); + + /* + * We are about to recurse, which means that this client will + * be unavailable for serving new requests for an indeterminate + * amount of time. If this client is currently responsible + * for handling incoming queries, set up a new client + * object to handle them while we are waiting for a + * response. There is no need to replace TCP clients + * because those have already been replaced when the + * connection was accepted (if allowed by the TCP quota). + */ + if (client->recursionquota == NULL) { + result = isc_quota_attach(&ns_g_server->recursionquota, + &client->recursionquota); + + isc_stats_increment(ns_g_server->nsstats, + dns_nsstatscounter_recursclients); + + if (result == ISC_R_SOFTQUOTA) { + static isc_stdtime_t last = 0; + isc_stdtime_t now; + isc_stdtime_get(&now); + if (now != last) { + last = now; + ns_client_log(client, NS_LOGCATEGORY_CLIENT, + NS_LOGMODULE_QUERY, + ISC_LOG_WARNING, + "recursive-clients soft limit " + "exceeded (%d/%d/%d), " + "aborting oldest query", + client->recursionquota->used, + client->recursionquota->soft, + client->recursionquota->max); + } + ns_client_killoldestquery(client); + result = ISC_R_SUCCESS; + } else if (result == ISC_R_QUOTA) { + static isc_stdtime_t last = 0; + isc_stdtime_t now; + isc_stdtime_get(&now); + if (now != last) { + last = now; + ns_client_log(client, NS_LOGCATEGORY_CLIENT, + NS_LOGMODULE_QUERY, + ISC_LOG_WARNING, + "no more recursive clients " + "(%d/%d/%d): %s", + ns_g_server->recursionquota.used, + ns_g_server->recursionquota.soft, + ns_g_server->recursionquota.max, + isc_result_totext(result)); + } + ns_client_killoldestquery(client); + } + if (result == ISC_R_SUCCESS && !client->mortal && + !TCP(client)) { + result = ns_client_replace(client); + if (result != ISC_R_SUCCESS) { + ns_client_log(client, NS_LOGCATEGORY_CLIENT, + NS_LOGMODULE_QUERY, + ISC_LOG_WARNING, + "ns_client_replace() failed: %s", + isc_result_totext(result)); + isc_quota_detach(&client->recursionquota); + isc_stats_decrement(ns_g_server->nsstats, + dns_nsstatscounter_recursclients); + } + } + if (result != ISC_R_SUCCESS) + return (result); + ns_client_recursing(client); + } + + /* + * Invoke the resolver. + */ + REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); + REQUIRE(client->query.fetch == NULL); + + rdataset = query_newrdataset(client); + if (rdataset == NULL) { + return (ISC_R_NOMEMORY); + } + + if (WANTDNSSEC(client)) { + sigrdataset = query_newrdataset(client); + if (sigrdataset == NULL) { + query_putrdataset(client, &rdataset); + return (ISC_R_NOMEMORY); + } + } else { + sigrdataset = NULL; + } + + if (client->query.timerset == ISC_FALSE) { + ns_client_settimeout(client, 60); + } + + if (!TCP(client)) { + peeraddr = &client->peeraddr; + } + + result = dns_resolver_createfetch3(client->view->resolver, + qname, qtype, qdomain, nameservers, + NULL, peeraddr, client->message->id, + client->query.fetchoptions, 0, NULL, + client->task, fetch_callback, + client, rdataset, sigrdataset, + &client->query.fetch); + if (result != ISC_R_SUCCESS) { + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) { + query_putrdataset(client, &sigrdataset); + } + } + + /* + * We're now waiting for a fetch event. A client which is + * shutting down will not be destroyed until all the events + * have been received. + */ + + return (result); +} + +/*% + * Restores the query context after resuming from recursion, and + * continues the query processing if needed. + */ +static isc_result_t +query_resume(query_ctx_t *qctx) { + isc_result_t result; + dns_name_t *tname; + isc_buffer_t b; +#ifdef WANT_QUERYTRACE + char mbuf[BUFSIZ]; + char qbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; +#endif + + qctx->want_restart = ISC_FALSE; + + qctx->rpz_st = qctx->client->query.rpz_st; + if (qctx->rpz_st != NULL && + (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) + { + CCTRACE(ISC_LOG_DEBUG(3), "resume from RPZ recursion"); +#ifdef WANT_QUERYTRACE + { + char pbuf[DNS_NAME_FORMATSIZE] = ""; + char fbuf[DNS_NAME_FORMATSIZE] = ""; + if (qctx->rpz_st->r_name != NULL) + dns_name_format(qctx->rpz_st->r_name, + qbuf, sizeof(qbuf)); + else + snprintf(qbuf, sizeof(qbuf), + ""); + if (qctx->rpz_st->p_name != NULL) + dns_name_format(qctx->rpz_st->p_name, + pbuf, sizeof(pbuf)); + if (qctx->rpz_st->fname != NULL) + dns_name_format(qctx->rpz_st->fname, + fbuf, sizeof(fbuf)); + + snprintf(mbuf, sizeof(mbuf) - 1, + "rpz rname:%s, pname:%s, qctx->fname:%s", + qbuf, pbuf, fbuf); + CCTRACE(ISC_LOG_DEBUG(3), mbuf); + } +#endif + + qctx->is_zone = qctx->rpz_st->q.is_zone; + qctx->authoritative = qctx->rpz_st->q.authoritative; + RESTORE(qctx->zone, qctx->rpz_st->q.zone); + RESTORE(qctx->node, qctx->rpz_st->q.node); + RESTORE(qctx->db, qctx->rpz_st->q.db); + RESTORE(qctx->rdataset, qctx->rpz_st->q.rdataset); + RESTORE(qctx->sigrdataset, qctx->rpz_st->q.sigrdataset); + qctx->qtype = qctx->rpz_st->q.qtype; + + qctx->rpz_st->r.db = qctx->event->db; + if (qctx->event->node != NULL) + dns_db_detachnode(qctx->event->db, &qctx->event->node); + qctx->rpz_st->r.r_type = qctx->event->qtype; + qctx->rpz_st->r.r_rdataset = qctx->event->rdataset; + query_putrdataset(qctx->client, &qctx->event->sigrdataset); + } else if (REDIRECT(qctx->client)) { + /* + * Restore saved state. + */ + CCTRACE(ISC_LOG_DEBUG(3), + "resume from redirect recursion"); +#ifdef WANT_QUERYTRACE + dns_name_format(qctx->client->query.redirect.fname, + qbuf, sizeof(qbuf)); + dns_rdatatype_format(qctx->client->query.redirect.qtype, + tbuf, sizeof(tbuf)); + snprintf(mbuf, sizeof(mbuf) - 1, + "redirect qctx->fname:%s, qtype:%s, auth:%d", + qbuf, tbuf, + qctx->client->query.redirect.authoritative); + CCTRACE(ISC_LOG_DEBUG(3), mbuf); +#endif + qctx->qtype = qctx->client->query.redirect.qtype; + INSIST(qctx->client->query.redirect.rdataset != NULL); + RESTORE(qctx->rdataset, qctx->client->query.redirect.rdataset); + RESTORE(qctx->sigrdataset, + qctx->client->query.redirect.sigrdataset); + RESTORE(qctx->db, qctx->client->query.redirect.db); + RESTORE(qctx->node, qctx->client->query.redirect.node); + RESTORE(qctx->zone, qctx->client->query.redirect.zone); + qctx->authoritative = + qctx->client->query.redirect.authoritative; + qctx->is_zone = qctx->client->query.redirect.is_zone; + + /* + * Free resources used while recursing. + */ + query_putrdataset(qctx->client, &qctx->event->rdataset); + query_putrdataset(qctx->client, &qctx->event->sigrdataset); + if (qctx->event->node != NULL) + dns_db_detachnode(qctx->event->db, &qctx->event->node); + if (qctx->event->db != NULL) + dns_db_detach(&qctx->event->db); + } else { + CCTRACE(ISC_LOG_DEBUG(3), + "resume from normal recursion"); + qctx->authoritative = ISC_FALSE; + + qctx->qtype = qctx->event->qtype; + qctx->db = qctx->event->db; + qctx->node = qctx->event->node; + qctx->rdataset = qctx->event->rdataset; + qctx->sigrdataset = qctx->event->sigrdataset; + } + INSIST(qctx->rdataset != NULL); + + if (qctx->qtype == dns_rdatatype_rrsig || + qctx->qtype == dns_rdatatype_sig) + { + qctx->type = dns_rdatatype_any; + } else { + qctx->type = qctx->qtype; + } + + if (DNS64(qctx->client)) { + qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64; + qctx->dns64 = ISC_TRUE; + } + + if (DNS64EXCLUDE(qctx->client)) { + qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE; + qctx->dns64_exclude = ISC_TRUE; + } + + if (qctx->rpz_st != NULL && + (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) + { + /* + * Has response policy changed out from under us? + */ + if (qctx->rpz_st->rpz_ver != qctx->client->view->rpzs->rpz_ver) + { + ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, + NS_LOGMODULE_QUERY, ISC_LOG_INFO, + "query_resume: RPZ settings " + "out of date " + "(rpz_ver %d, expected %d)", + qctx->client->view->rpzs->rpz_ver, + qctx->rpz_st->rpz_ver); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + } + + /* + * We'll need some resources... + */ + qctx->dbuf = query_getnamebuf(qctx->client); + if (qctx->dbuf == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_resume: query_getnamebuf failed (1)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + + qctx->fname = query_newname(qctx->client, qctx->dbuf, &b); + if (qctx->fname == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_resume: query_newname failed (1)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + + if (qctx->rpz_st != NULL && + (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { + tname = qctx->rpz_st->fname; + } else if (REDIRECT(qctx->client)) { + tname = qctx->client->query.redirect.fname; + } else { + tname = dns_fixedname_name(&qctx->event->foundname); + } + + result = dns_name_copy(tname, qctx->fname, NULL); + if (result != ISC_R_SUCCESS) { + CCTRACE(ISC_LOG_ERROR, + "query_resume: dns_name_copy failed"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + + if (qctx->rpz_st != NULL && + (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0) { + qctx->rpz_st->r.r_result = qctx->event->result; + result = qctx->rpz_st->q.result; + isc_event_free(ISC_EVENT_PTR(&qctx->event)); + } else if (REDIRECT(qctx->client)) { + result = qctx->client->query.redirect.result; + qctx->is_zone = qctx->client->query.redirect.is_zone; + } else { + result = qctx->event->result; + } + + qctx->resuming = ISC_TRUE; + + return (query_gotanswer(qctx, result)); +} + +/*% + * If the query is recursive, check the SERVFAIL cache to see whether + * identical queries have failed recently. If we find a match, and it was + * from a query with CD=1, *or* if the current query has CD=0, then we just + * return SERVFAIL again. + */ +static isc_result_t +query_sfcache(query_ctx_t *qctx) { + isc_boolean_t failcache; + isc_uint32_t flags; + + /* + * The SERVFAIL cache doesn't apply to authoritative queries. + */ + if (!RECURSIONOK(qctx->client)) { + return (ISC_R_COMPLETE); + } + + flags = 0; +#ifdef ENABLE_AFL + if (ns_g_fuzz_type == ns_fuzz_resolver) { + failcache = ISC_FALSE; + } else { + failcache = + dns_badcache_find(qctx->client->view->failcache, + qctx->client->query.qname, + qctx->qtype, &flags, + &qctx->client->tnow); + } +#else + failcache = dns_badcache_find(qctx->client->view->failcache, + qctx->client->query.qname, + qctx->qtype, &flags, + &qctx->client->tnow); +#endif + if (failcache && + (((flags & NS_FAILCACHE_CD) != 0) || + ((qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0))) + { + if (isc_log_wouldlog(ns_g_lctx, ISC_LOG_DEBUG(1))) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typename[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(qctx->client->query.qname, + namebuf, sizeof(namebuf)); + dns_rdatatype_format(qctx->qtype, typename, + sizeof(typename)); + ns_client_log(qctx->client, + NS_LOGCATEGORY_CLIENT, + NS_LOGMODULE_QUERY, + ISC_LOG_DEBUG(1), + "servfail cache hit %s/%s (%s)", + namebuf, typename, + ((flags & NS_FAILCACHE_CD) != 0) + ? "CD=1" + : "CD=0"); + } + + qctx->client->attributes |= NS_CLIENTATTR_NOSETFC; + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + + return (ISC_R_COMPLETE); +} + +/*% + * Handle response rate limiting (RRL). + */ +static isc_result_t +query_checkrrl(query_ctx_t *qctx, isc_result_t result) { /* * Rate limit these responses to this client. * Do not delay counting and handling obvious referrals, @@ -7010,46 +5949,50 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * Don't mess with responses rewritten by RPZ * Count each response at most once. */ - if (client->view->rrl != NULL && !HAVECOOKIE(client) && - ((fname != NULL && dns_name_isabsolute(fname)) || - (result == ISC_R_NOTFOUND && !RECURSIONOK(client))) && - !(result == DNS_R_DELEGATION && !is_zone && RECURSIONOK(client)) && - (client->query.rpz_st == NULL || - (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0)&& - (client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) + if (qctx->client->view->rrl != NULL && + !HAVECOOKIE(qctx->client) && + ((qctx->fname != NULL && dns_name_isabsolute(qctx->fname)) || + (result == ISC_R_NOTFOUND && !RECURSIONOK(qctx->client))) && + !(result == DNS_R_DELEGATION && + !qctx->is_zone && RECURSIONOK(qctx->client)) && + (qctx->client->query.rpz_st == NULL || + (qctx->client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) && + (qctx->client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) { dns_rdataset_t nc_rdataset; isc_boolean_t wouldlog; + dns_fixedname_t fixed; + const dns_name_t *constname; char log_buf[DNS_RRL_LOG_BUF_LEN]; isc_result_t nc_result, resp_result; dns_rrl_result_t rrl_result; - const dns_name_t *constname; - client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; + qctx->client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP); - constname = fname; + constname = qctx->fname; if (result == DNS_R_NXDOMAIN) { /* * Use the database origin name to rate limit NXDOMAIN */ - if (db != NULL) - constname = dns_db_origin(db); + if (qctx->db != NULL) + constname = dns_db_origin(qctx->db); resp_result = result; } else if (result == DNS_R_NCACHENXDOMAIN && - rdataset != NULL && - dns_rdataset_isassociated(rdataset) && - (rdataset->attributes & + qctx->rdataset != NULL && + dns_rdataset_isassociated(qctx->rdataset) && + (qctx->rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { /* * Try to use owner name in the negative cache SOA. */ dns_fixedname_init(&fixed); dns_rdataset_init(&nc_rdataset); - for (nc_result = dns_rdataset_first(rdataset); + for (nc_result = dns_rdataset_first(qctx->rdataset); nc_result == ISC_R_SUCCESS; - nc_result = dns_rdataset_next(rdataset)) { - dns_ncache_current(rdataset, + nc_result = dns_rdataset_next(qctx->rdataset)) + { + dns_ncache_current(qctx->rdataset, dns_fixedname_name(&fixed), &nc_rdataset); if (nc_rdataset.type == dns_rdatatype_soa) { @@ -7076,9 +6019,13 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) } else { resp_result = ISC_R_SUCCESS; } - rrl_result = dns_rrl(client->view, &client->peeraddr, - TCP(client), client->message->rdclass, - qtype, constname, resp_result, client->now, + + rrl_result = dns_rrl(qctx->client->view, + &qctx->client->peeraddr, + TCP(qctx->client), + qctx->client->message->rdclass, + qctx->qtype, constname, + resp_result, qctx->client->now, wouldlog, log_buf, sizeof(log_buf)); if (rrl_result != DNS_RRL_RESULT_OK) { /* @@ -7092,1228 +6039,2673 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * with other truncated responses in RespTruncated. */ if (wouldlog) { - ns_client_log(client, DNS_LOGCATEGORY_RRL, + ns_client_log(qctx->client, DNS_LOGCATEGORY_RRL, NS_LOGMODULE_QUERY, DNS_RRL_LOG_DROP, "%s", log_buf); } - if (!client->view->rrl->log_only) { + + if (!qctx->client->view->rrl->log_only) { if (rrl_result == DNS_RRL_RESULT_DROP) { /* * These will also be counted in * dns_nsstatscounter_dropped */ - inc_stats(client, + inc_stats(qctx->client, dns_nsstatscounter_ratedropped); - QUERY_ERROR(DNS_R_DROP); + QUERY_ERROR(qctx, DNS_R_DROP); } else { /* * These will also be counted in * dns_nsstatscounter_truncatedresp */ - inc_stats(client, + inc_stats(qctx->client, dns_nsstatscounter_rateslipped); - if (WANTCOOKIE(client)) { - client->message->flags &= + if (WANTCOOKIE(qctx->client)) { + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; - client->message->flags &= + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; - client->message->rcode = + qctx->client->message->rcode = dns_rcode_badcookie; } else { - client->message->flags |= + qctx->client->message->flags |= DNS_MESSAGEFLAG_TC; if (resp_result == DNS_R_NXDOMAIN) - client->message->rcode = - dns_rcode_nxdomain; + { + qctx->client->message->rcode = + dns_rcode_nxdomain; + } } } - goto cleanup; + return (DNS_R_DROP); } } - } else if (!TCP(client) && client->view->requireservercookie && - WANTCOOKIE(client) && !HAVECOOKIE(client)) { - client->message->flags &= ~DNS_MESSAGEFLAG_AA; - client->message->flags &= ~DNS_MESSAGEFLAG_AD; - client->message->rcode = dns_rcode_badcookie; - goto cleanup; - } - - if (!RECURSING(client) && - !dns_name_equal(client->query.qname, dns_rootname)) + } else if (!TCP(qctx->client) && + qctx->client->view->requireservercookie && + WANTCOOKIE(qctx->client) && !HAVECOOKIE(qctx->client)) { - isc_result_t rresult; - - rresult = rpz_rewrite(client, qtype, result, resuming, - rdataset, sigrdataset); - rpz_st = client->query.rpz_st; - switch (rresult) { - case ISC_R_SUCCESS: - break; - case DNS_R_DISALLOWED: - goto norpz; - case DNS_R_DELEGATION: - /* - * recursing for NS names or addresses, - * so save the main query state - */ - rpz_st->q.qtype = qtype; - rpz_st->q.is_zone = is_zone; - rpz_st->q.authoritative = authoritative; - SAVE(rpz_st->q.zone, zone); - SAVE(rpz_st->q.db, db); - SAVE(rpz_st->q.node, node); - SAVE(rpz_st->q.rdataset, rdataset); - SAVE(rpz_st->q.sigrdataset, sigrdataset); - dns_name_copy(fname, rpz_st->fname, NULL); - rpz_st->q.result = result; - client->query.attributes |= NS_QUERYATTR_RECURSING; - goto cleanup; - default: - RECURSE_ERROR(rresult); - goto cleanup; - } - - if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS) - rpz_st->state |= DNS_RPZ_REWRITTEN; - if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS && - rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && - (rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY || - !TCP(client)) && - rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) - { - /* - * We got a hit and are going to answer with our - * fiction. Ensure that we answer with the name - * we looked up even if we were stopped short - * in recursion or for a deferral. - */ - rresult = dns_name_copy(client->query.qname, - fname, NULL); - RUNTIME_CHECK(rresult == ISC_R_SUCCESS); - rpz_clean(&zone, &db, &node, NULL); - if (rpz_st->m.rdataset != NULL) { - query_putrdataset(client, &rdataset); - RESTORE(rdataset, rpz_st->m.rdataset); - } else if (rdataset != NULL && - dns_rdataset_isassociated(rdataset)) { - dns_rdataset_disassociate(rdataset); - } - version = NULL; - - RESTORE(node, rpz_st->m.node); - RESTORE(db, rpz_st->m.db); - RESTORE(version, rpz_st->m.version); - RESTORE(zone, rpz_st->m.zone); - - switch (rpz_st->m.policy) { - case DNS_RPZ_POLICY_TCP_ONLY: - client->message->flags |= DNS_MESSAGEFLAG_TC; - if (result == DNS_R_NXDOMAIN || - result == DNS_R_NCACHENXDOMAIN) - client->message->rcode = - dns_rcode_nxdomain; - rpz_log_rewrite(client, ISC_FALSE, - rpz_st->m.policy, - rpz_st->m.type, zone, - rpz_st->p_name, NULL, - rpz_st->m.rpz->num); - goto cleanup; - case DNS_RPZ_POLICY_DROP: - QUERY_ERROR(DNS_R_DROP); - rpz_log_rewrite(client, ISC_FALSE, - rpz_st->m.policy, - rpz_st->m.type, zone, - rpz_st->p_name, NULL, - rpz_st->m.rpz->num); - goto cleanup; - case DNS_RPZ_POLICY_NXDOMAIN: - result = DNS_R_NXDOMAIN; - nxrewrite = ISC_TRUE; - rpz = ISC_TRUE; - break; - case DNS_RPZ_POLICY_NODATA: - result = DNS_R_NXRRSET; - nxrewrite = ISC_TRUE; - rpz = ISC_TRUE; - break; - case DNS_RPZ_POLICY_RECORD: - result = rpz_st->m.result; - if (qtype == dns_rdatatype_any && - result != DNS_R_CNAME) { - /* - * We will add all of the rdatasets of - * the node by iterating later, - * and set the TTL then. - */ - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - } else { - /* - * We will add this rdataset. - */ - rdataset->ttl = ISC_MIN(rdataset->ttl, - rpz_st->m.ttl); - } - rpz = ISC_TRUE; - break; - case DNS_RPZ_POLICY_WILDCNAME: - result = dns_rdataset_first(rdataset); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &cname, - NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdata_reset(&rdata); - result = rpz_add_cname(client, rpz_st, - &cname.cname, - fname, dbuf); - if (result != ISC_R_SUCCESS) - goto cleanup; - fname = NULL; - want_restart = ISC_TRUE; - goto cleanup; - case DNS_RPZ_POLICY_CNAME: - /* - * Add overridding CNAME from a named.conf - * response-policy statement - */ - result = rpz_add_cname(client, rpz_st, - &rpz_st->m.rpz->cname, - fname, dbuf); - if (result != ISC_R_SUCCESS) - goto cleanup; - fname = NULL; - want_restart = ISC_TRUE; - goto cleanup; - default: - INSIST(0); - } - - /* - * Turn off DNSSEC because the results of a - * response policy zone cannot verify. - */ - client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | - NS_CLIENTATTR_WANTAD); - client->message->flags &= ~DNS_MESSAGEFLAG_AD; - query_putrdataset(client, &sigrdataset); - is_zone = ISC_TRUE; - rpz_log_rewrite(client, ISC_FALSE, rpz_st->m.policy, - rpz_st->m.type, zone, rpz_st->p_name, - NULL, rpz_st->m.rpz->num); - } + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; + qctx->client->message->rcode = dns_rcode_badcookie; + return (DNS_R_DROP); + } + + return (ISC_R_SUCCESS); +} + +/*% + * Do any RPZ rewriting that may be needed for this query. + */ +static isc_result_t +query_checkrpz(query_ctx_t *qctx, isc_result_t result) { + isc_result_t rresult; + + rresult = rpz_rewrite(qctx->client, qctx->qtype, + result, qctx->resuming, + qctx->rdataset, qctx->sigrdataset); + qctx->rpz_st = qctx->client->query.rpz_st; + switch (rresult) { + case ISC_R_SUCCESS: + break; + case DNS_R_DISALLOWED: + return (result); + case DNS_R_DELEGATION: + /* + * recursing for NS names or addresses, + * so save the main query state + */ + qctx->rpz_st->q.qtype = qctx->qtype; + qctx->rpz_st->q.is_zone = qctx->is_zone; + qctx->rpz_st->q.authoritative = qctx->authoritative; + SAVE(qctx->rpz_st->q.zone, qctx->zone); + SAVE(qctx->rpz_st->q.db, qctx->db); + SAVE(qctx->rpz_st->q.node, qctx->node); + SAVE(qctx->rpz_st->q.rdataset, qctx->rdataset); + SAVE(qctx->rpz_st->q.sigrdataset, qctx->sigrdataset); + dns_name_copy(qctx->fname, qctx->rpz_st->fname, NULL); + qctx->rpz_st->q.result = result; + qctx->client->query.attributes |= NS_QUERYATTR_RECURSING; + return (ISC_R_COMPLETE); + default: + RECURSE_ERROR(qctx, rresult); + return (ISC_R_COMPLETE);; + } + + if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS) { + qctx->rpz_st->state |= DNS_RPZ_REWRITTEN; + } + + if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS && + qctx->rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && + (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY || + !TCP(qctx->client)) && + qctx->rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) + { + /* + * We got a hit and are going to answer with our + * fiction. Ensure that we answer with the name + * we looked up even if we were stopped short + * in recursion or for a deferral. + */ + rresult = dns_name_copy(qctx->client->query.qname, + qctx->fname, NULL); + RUNTIME_CHECK(rresult == ISC_R_SUCCESS); + rpz_clean(&qctx->zone, &qctx->db, &qctx->node, NULL); + if (qctx->rpz_st->m.rdataset != NULL) { + query_putrdataset(qctx->client, &qctx->rdataset); + RESTORE(qctx->rdataset, qctx->rpz_st->m.rdataset); + } else { + qctx_clean(qctx); + } + qctx->version = NULL; + + RESTORE(qctx->node, qctx->rpz_st->m.node); + RESTORE(qctx->db, qctx->rpz_st->m.db); + RESTORE(qctx->version, qctx->rpz_st->m.version); + RESTORE(qctx->zone, qctx->rpz_st->m.zone); + + switch (qctx->rpz_st->m.policy) { + case DNS_RPZ_POLICY_TCP_ONLY: + qctx->client->message->flags |= DNS_MESSAGEFLAG_TC; + if (result == DNS_R_NXDOMAIN || + result == DNS_R_NCACHENXDOMAIN) + qctx->client->message->rcode = + dns_rcode_nxdomain; + rpz_log_rewrite(qctx->client, ISC_FALSE, + qctx->rpz_st->m.policy, + qctx->rpz_st->m.type, qctx->zone, + qctx->rpz_st->p_name, NULL, + qctx->rpz_st->m.rpz->num); + return (ISC_R_COMPLETE); + case DNS_RPZ_POLICY_DROP: + QUERY_ERROR(qctx, DNS_R_DROP); + rpz_log_rewrite(qctx->client, ISC_FALSE, + qctx->rpz_st->m.policy, + qctx->rpz_st->m.type, qctx->zone, + qctx->rpz_st->p_name, NULL, + qctx->rpz_st->m.rpz->num); + return (ISC_R_COMPLETE); + case DNS_RPZ_POLICY_NXDOMAIN: + result = DNS_R_NXDOMAIN; + qctx->nxrewrite = ISC_TRUE; + break; + case DNS_RPZ_POLICY_NODATA: + result = DNS_R_NXRRSET; + qctx->nxrewrite = ISC_TRUE; + break; + case DNS_RPZ_POLICY_RECORD: + result = qctx->rpz_st->m.result; + if (qctx->qtype == dns_rdatatype_any && + result != DNS_R_CNAME) { + /* + * We will add all of the rdatasets of + * the node by iterating later, + * and set the TTL then. + */ + if (dns_rdataset_isassociated(qctx->rdataset)) + dns_rdataset_disassociate(qctx->rdataset); + } else { + /* + * We will add this rdataset. + */ + qctx->rdataset->ttl = + ISC_MIN(qctx->rdataset->ttl, + qctx->rpz_st->m.ttl); + } + break; + case DNS_RPZ_POLICY_WILDCNAME: { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_cname_t cname; + result = dns_rdataset_first(qctx->rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(qctx->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + result = query_rpzcname(qctx, &cname.cname); + if (result != ISC_R_SUCCESS) + return (ISC_R_COMPLETE); + qctx->fname = NULL; + qctx->want_restart = ISC_TRUE; + return (ISC_R_COMPLETE); + } + case DNS_RPZ_POLICY_CNAME: + /* + * Add overridding CNAME from a named.conf + * response-policy statement + */ + result = query_rpzcname(qctx, + &qctx->rpz_st->m.rpz->cname); + if (result != ISC_R_SUCCESS) + return (ISC_R_COMPLETE); + qctx->fname = NULL; + qctx->want_restart = ISC_TRUE; + return (ISC_R_COMPLETE); + default: + INSIST(0); + } + + /* + * Turn off DNSSEC because the results of a + * response policy zone cannot verify. + */ + qctx->client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | + NS_CLIENTATTR_WANTAD); + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD; + query_putrdataset(qctx->client, &qctx->sigrdataset); + qctx->rpz_st->q.is_zone = qctx->is_zone; + qctx->is_zone = ISC_TRUE; + rpz_log_rewrite(qctx->client, ISC_FALSE, + qctx->rpz_st->m.policy, + qctx->rpz_st->m.type, qctx->zone, + qctx->rpz_st->p_name, NULL, + qctx->rpz_st->m.rpz->num); + } + + return (result); +} + +/*% + * Add a CNAME to a query response, including translating foo.evil.com and + * *.evil.com CNAME *.example.com + * to + * foo.evil.com CNAME foo.evil.com.example.com + */ +static isc_result_t +query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) { + ns_client_t *client = qctx->client; + dns_fixedname_t prefix, suffix; + unsigned int labels; + isc_result_t result; + + CTRACE(ISC_LOG_DEBUG(3), "query_rpzcname"); + + labels = dns_name_countlabels(cname); + if (labels > 2 && dns_name_iswildcard(cname)) { + dns_fixedname_init(&prefix); + dns_name_split(client->query.qname, 1, + dns_fixedname_name(&prefix), NULL); + dns_fixedname_init(&suffix); + dns_name_split(cname, labels-1, + NULL, dns_fixedname_name(&suffix)); + result = dns_name_concatenate(dns_fixedname_name(&prefix), + dns_fixedname_name(&suffix), + qctx->fname, NULL); + if (result == DNS_R_NAMETOOLONG) { + client->message->rcode = dns_rcode_yxdomain; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + } else { + result = dns_name_copy(cname, qctx->fname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + query_keepname(client, qctx->fname, qctx->dbuf); + result = query_addcname(qctx, dns_trust_authanswer, + qctx->rpz_st->m.ttl); + if (result != ISC_R_SUCCESS) { + return (result); + } + + rpz_log_rewrite(client, ISC_FALSE, qctx->rpz_st->m.policy, + qctx->rpz_st->m.type, qctx->rpz_st->m.zone, + qctx->rpz_st->p_name, qctx->fname, + qctx->rpz_st->m.rpz->num); + + ns_client_qnamereplace(client, qctx->fname); + + /* + * Turn off DNSSEC because the results of a + * response policy zone cannot verify. + */ + client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC | + NS_CLIENTATTR_WANTAD); + + return (ISC_R_SUCCESS); +} + +/*% + * Continue after doing a database lookup or returning from + * recursion, and call out to the next function depending on the + * result from the search. + */ +static isc_result_t +query_gotanswer(query_ctx_t *qctx, isc_result_t result) { + char errmsg[256]; + + CCTRACE(ISC_LOG_DEBUG(3), "query_gotanswer"); + + if (query_checkrrl(qctx, result) != ISC_R_SUCCESS) { + return (query_done(qctx)); + } + + if (!RECURSING(qctx->client) && + !dns_name_equal(qctx->client->query.qname, dns_rootname)) + { + result = query_checkrpz(qctx, result); + if (result == ISC_R_COMPLETE) + return (query_done(qctx)); } - norpz: switch (result) { case ISC_R_SUCCESS: - /* - * This case is handled in the main line below. - */ - break; + return (query_prepresponse(qctx)); + case DNS_R_GLUE: case DNS_R_ZONECUT: - /* - * These cases are handled in the main line below. - */ - INSIST(is_zone); - authoritative = ISC_FALSE; - break; + INSIST(qctx->is_zone); + qctx->authoritative = ISC_FALSE; + return (query_prepresponse(qctx)); + case ISC_R_NOTFOUND: - /* - * The cache doesn't even have the root NS. Get them from - * the hints DB. - */ - INSIST(!is_zone); - if (db != NULL) - dns_db_detach(&db); + return (query_notfound(qctx)); - if (client->view->hints == NULL) { - /* We have no hints. */ - result = ISC_R_FAILURE; - } else { - dns_db_attach(client->view->hints, &db); - result = dns_db_findext(db, dns_rootname, - NULL, dns_rdatatype_ns, - 0, client->now, &node, - fname, &cm, &ci, - rdataset, sigrdataset); - } - if (result != ISC_R_SUCCESS) { - /* - * Nonsensical root hints may require cleanup. - */ - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (sigrdataset != NULL && - dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - - /* - * We don't have any root server hints, but - * we may have working forwarders, so try to - * recurse anyway. - */ - if (RECURSIONOK(client)) { - INSIST(!REDIRECT(client)); - result = query_recurse(client, qtype, - client->query.qname, - NULL, NULL, resuming); - if (result == ISC_R_SUCCESS) { - client->query.attributes |= - NS_QUERYATTR_RECURSING; - if (dns64) - client->query.attributes |= - NS_QUERYATTR_DNS64; - if (dns64_exclude) - client->query.attributes |= - NS_QUERYATTR_DNS64EXCLUDE; - } else - RECURSE_ERROR(result); - goto cleanup; - } else { - /* Unable to give root server referral. */ - CTRACE(ISC_LOG_ERROR, - "unable to give root server referral"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - } - /* - * XXXRTH We should trigger root server priming here. - */ - /* FALLTHROUGH */ case DNS_R_DELEGATION: - authoritative = ISC_FALSE; - if (is_zone) { - /* - * Look to see if we are authoritative for the - * child zone if the query type is DS. - */ - if (!RECURSIONOK(client) && - (options & DNS_GETDB_NOEXACT) != 0 && - qtype == dns_rdatatype_ds) { - dns_db_t *tdb = NULL; - dns_zone_t *tzone = NULL; - dns_dbversion_t *tversion = NULL; - result = query_getzonedb(client, - client->query.qname, - qtype, - DNS_GETDB_PARTIAL, - &tzone, &tdb, - &tversion); - if (result == ISC_R_SUCCESS) { - options &= ~DNS_GETDB_NOEXACT; - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, - &sigrdataset); - if (fname != NULL) - query_releasename(client, - &fname); - if (node != NULL) - dns_db_detachnode(db, &node); - if (db != NULL) - dns_db_detach(&db); - if (zone != NULL) - dns_zone_detach(&zone); - version = NULL; - RESTORE(version, tversion); - RESTORE(db, tdb); - RESTORE(zone, tzone); - authoritative = ISC_TRUE; - goto db_find; - } - if (tdb != NULL) - dns_db_detach(&tdb); - if (tzone != NULL) - dns_zone_detach(&tzone); - } - /* - * We're authoritative for an ancestor of QNAME. - */ - if (!USECACHE(client) || !RECURSIONOK(client)) { - dns_fixedname_init(&fixed); - dns_name_copy(fname, - dns_fixedname_name(&fixed), NULL); - - /* - * If we don't have a cache, this is the best - * answer. - * - * If the client is making a nonrecursive - * query we always give out the authoritative - * delegation. This way even if we get - * junk in our cache, we won't fail in our - * role as the delegating authority if another - * nameserver asks us about a delegated - * subzone. - * - * We enable the retrieval of glue for this - * database by setting client->query.gluedb. - */ - client->query.gluedb = db; - client->query.isreferral = ISC_TRUE; - /* - * We must ensure NOADDITIONAL is off, - * because the generation of - * additional data is required in - * delegations. - */ - client->query.attributes &= - ~NS_QUERYATTR_NOADDITIONAL; - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - query_addrrset(client, &fname, - &rdataset, sigrdatasetp, - dbuf, DNS_SECTION_AUTHORITY); - client->query.gluedb = NULL; - if (WANTDNSSEC(client)) - query_addds(client, db, node, version, - dns_fixedname_name(&fixed)); - } else { - /* - * We might have a better answer or delegation - * in the cache. We'll remember the current - * values of fname, rdataset, and sigrdataset. - * We'll then go looking for QNAME in the - * cache. If we find something better, we'll - * use it instead. - */ - query_keepname(client, fname, dbuf); - dns_db_detachnode(db, &node); - SAVE(zdb, db); - SAVE(zfname, fname); - SAVE(zversion, version); - SAVE(zrdataset, rdataset); - SAVE(zsigrdataset, sigrdataset); - dns_db_attach(client->view->cachedb, &db); - is_zone = ISC_FALSE; - goto db_find; - } - } else { - if (zfname != NULL && - (!dns_name_issubdomain(fname, zfname) || - (is_staticstub_zone && - dns_name_equal(fname, zfname)))) { - /* - * In the following cases use "authoritative" - * data instead of the cache delegation: - * 1. We've already got a delegation from - * authoritative data, and it is better - * than what we found in the cache. - * 2. The query name matches the origin name - * of a static-stub zone. This needs to be - * considered for the case where the NS of - * the static-stub zone and the cached NS - * are different. We still need to contact - * the nameservers configured in the - * static-stub zone. - */ - query_releasename(client, &fname); - /* - * We've already done query_keepname() on - * zfname, so we must set dbuf to NULL to - * prevent query_addrrset() from trying to - * call query_keepname() again. - */ - dbuf = NULL; - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, - &sigrdataset); - version = NULL; - - RESTORE(fname, zfname); - RESTORE(version, zversion); - RESTORE(rdataset, zrdataset); - RESTORE(sigrdataset, zsigrdataset); - /* - * We don't clean up zdb here because we - * may still need it. It will get cleaned - * up by the main cleanup code. - */ - } - - if (RECURSIONOK(client)) { - /* - * Recurse! - */ - INSIST(!REDIRECT(client)); - if (dns_rdatatype_atparent(type)) - result = query_recurse(client, qtype, - client->query.qname, - NULL, NULL, resuming); - else if (dns64) - result = query_recurse(client, - dns_rdatatype_a, - client->query.qname, - NULL, NULL, resuming); - else - result = query_recurse(client, qtype, - client->query.qname, - fname, rdataset, - resuming); - - if (result == ISC_R_SUCCESS) { - client->query.attributes |= - NS_QUERYATTR_RECURSING; - if (dns64) - client->query.attributes |= - NS_QUERYATTR_DNS64; - if (dns64_exclude) - client->query.attributes |= - NS_QUERYATTR_DNS64EXCLUDE; - } else if (result == DNS_R_DUPLICATE || - result == DNS_R_DROP) - QUERY_ERROR(result); - else - RECURSE_ERROR(result); - } else { - dns_fixedname_init(&fixed); - dns_name_copy(fname, - dns_fixedname_name(&fixed), NULL); - /* - * This is the best answer. - */ - client->query.attributes |= - NS_QUERYATTR_CACHEGLUEOK; - client->query.gluedb = zdb; - client->query.isreferral = ISC_TRUE; - /* - * We must ensure NOADDITIONAL is off, - * because the generation of - * additional data is required in - * delegations. - */ - client->query.attributes &= - ~NS_QUERYATTR_NOADDITIONAL; - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - query_addrrset(client, &fname, - &rdataset, sigrdatasetp, - dbuf, DNS_SECTION_AUTHORITY); - client->query.gluedb = NULL; - client->query.attributes &= - ~NS_QUERYATTR_CACHEGLUEOK; - if (WANTDNSSEC(client)) - query_addds(client, db, node, version, - dns_fixedname_name(&fixed)); - } - } - goto cleanup; + return (query_delegation(qctx)); case DNS_R_EMPTYNAME: + return (query_nodata(qctx, DNS_R_EMPTYNAME)); case DNS_R_NXRRSET: - iszone_nxrrset: - INSIST(is_zone); - -#ifdef dns64_bis_return_excluded_addresses - if (dns64) -#else - if (dns64 && !dns64_exclude) -#endif - { - /* - * Restore the answers from the previous AAAA lookup. - */ - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - RESTORE(rdataset, client->query.dns64_aaaa); - RESTORE(sigrdataset, client->query.dns64_sigaaaa); - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: " - "query_getnamebuf failed (3)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - fname = query_newname(client, dbuf, &b); - if (fname == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: " - "query_newname failed (3)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - } - dns_name_copy(client->query.qname, fname, NULL); - dns64 = ISC_FALSE; -#ifdef dns64_bis_return_excluded_addresses - /* - * Resume the diverted processing of the AAAA response? - */ - if (dns64_excluded) - break; -#endif - } else if (result == DNS_R_NXRRSET && - !ISC_LIST_EMPTY(client->view->dns64) && - client->message->rdclass == dns_rdataclass_in && - qtype == dns_rdatatype_aaaa) - { - /* - * Look to see if there are A records for this - * name. - */ - SAVE(client->query.dns64_aaaa, rdataset); - SAVE(client->query.dns64_sigaaaa, sigrdataset); - client->query.dns64_ttl = dns64_ttl(db, version); - query_releasename(client, &fname); - dns_db_detachnode(db, &node); - type = qtype = dns_rdatatype_a; - dns64 = ISC_TRUE; - goto db_find; - } - - /* - * Look for a NSEC3 record if we don't have a NSEC record. - */ - nxrrset_rrsig: - if (redirected) - goto cleanup; - if (!dns_rdataset_isassociated(rdataset) && - WANTDNSSEC(client)) { - if ((fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { - dns_name_t *found; - dns_name_t *qname; - - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - qname = client->query.qname; - - query_findclosestnsec3(qname, db, version, - client, rdataset, - sigrdataset, fname, - ISC_TRUE, found); - /* - * Did we find the closest provable encloser - * instead? If so add the nearest to the - * closest provable encloser. - */ - if (dns_rdataset_isassociated(rdataset) && - !dns_name_equal(qname, found) && - !(ns_g_nonearest && - qtype != dns_rdatatype_ds)) - { - unsigned int count; - unsigned int skip; - - /* - * Add the closest provable encloser. - */ - query_addrrset(client, &fname, - &rdataset, &sigrdataset, - dbuf, - DNS_SECTION_AUTHORITY); - - count = dns_name_countlabels(found) - + 1; - skip = dns_name_countlabels(qname) - - count; - dns_name_getlabelsequence(qname, skip, - count, - found); - - fixfname(client, &fname, &dbuf, &b); - fixrdataset(client, &rdataset); - fixrdataset(client, &sigrdataset); - if (fname == NULL || - rdataset == NULL || - sigrdataset == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: " - "failure getting " - "closest encloser"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - /* - * 'nearest' doesn't exist so - * 'exist' is set to ISC_FALSE. - */ - query_findclosestnsec3(found, db, - version, - client, - rdataset, - sigrdataset, - fname, - ISC_FALSE, - NULL); - } - } else { - query_releasename(client, &fname); - query_addwildcardproof(client, db, version, - client->query.qname, - ISC_FALSE, ISC_TRUE); - } - } - if (dns_rdataset_isassociated(rdataset)) { - /* - * If we've got a NSEC record, we need to save the - * name now because we're going call query_addsoa() - * below, and it needs to use the name buffer. - */ - query_keepname(client, fname, dbuf); - } else if (fname != NULL) { - /* - * We're not going to use fname, and need to release - * our hold on the name buffer so query_addsoa() - * may use it. - */ - query_releasename(client, &fname); - } - - /* - * Add SOA to the additional section if generated by a RPZ - * rewrite. - */ - associated = dns_rdataset_isassociated(rdataset); - section = nxrewrite ? DNS_SECTION_ADDITIONAL : - DNS_SECTION_AUTHORITY; - - result = query_addsoa(client, db, version, ISC_UINT32_MAX, - associated, section); - if (result != ISC_R_SUCCESS) { - QUERY_ERROR(result); - goto cleanup; - } - - /* - * Add NSEC record if we found one. - */ - if (WANTDNSSEC(client)) { - if (dns_rdataset_isassociated(rdataset)) - query_addnxrrsetnsec(client, db, version, - &fname, &rdataset, - &sigrdataset); - } - goto cleanup; + return (query_nodata(qctx, DNS_R_NXRRSET)); case DNS_R_EMPTYWILD: - empty_wild = ISC_TRUE; - /* FALLTHROUGH */ + return (query_nxdomain(qctx, ISC_TRUE)); case DNS_R_NXDOMAIN: - INSIST(is_zone || REDIRECT(client)); - if (!empty_wild) { - tresult = redirect(client, fname, rdataset, &node, - &db, &version, type); - if (tresult == ISC_R_SUCCESS) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect); - break; - } - if (tresult == DNS_R_NXRRSET) { - redirected = ISC_TRUE; - goto iszone_nxrrset; - } - if (tresult == DNS_R_NCACHENXRRSET) { - redirected = ISC_TRUE; - is_zone = ISC_FALSE; - goto ncache_nxrrset; - } - tresult = redirect2(client, fname, rdataset, &node, - &db, &version, type, &is_zone); - if (tresult == DNS_R_CONTINUE) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect_rlookup); - client->query.redirect.qtype = qtype; - INSIST(rdataset != NULL); - SAVE(client->query.redirect.rdataset, rdataset); - SAVE(client->query.redirect.sigrdataset, - sigrdataset); - SAVE(client->query.redirect.db, db); - SAVE(client->query.redirect.node, node); - SAVE(client->query.redirect.zone, zone); - client->query.redirect.result = DNS_R_NXDOMAIN; - dns_name_copy(fname, - client->query.redirect.fname, - NULL); - client->query.redirect.authoritative = - authoritative; - client->query.redirect.is_zone = is_zone; - goto cleanup; - } - if (tresult == ISC_R_SUCCESS) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect); - break; - } - if (tresult == DNS_R_NXRRSET) { - redirected = ISC_TRUE; - goto iszone_nxrrset; - } - if (tresult == DNS_R_NCACHENXRRSET) { - redirected = ISC_TRUE; - is_zone = ISC_FALSE; - goto ncache_nxrrset; - } - } - if (dns_rdataset_isassociated(rdataset)) { - /* - * If we've got a NSEC record, we need to save the - * name now because we're going call query_addsoa() - * below, and it needs to use the name buffer. - */ - query_keepname(client, fname, dbuf); - } else if (fname != NULL) { - /* - * We're not going to use fname, and need to release - * our hold on the name buffer so query_addsoa() - * may use it. - */ - query_releasename(client, &fname); - } - - /* - * Add SOA to the additional section if generated by a - * RPZ rewrite. - * - * If the query was for a SOA record force the - * ttl to zero so that it is possible for clients to find - * the containing zone of an arbitrary name with a stub - * resolver and not have it cached. - */ - associated = dns_rdataset_isassociated(rdataset); - section = nxrewrite ? DNS_SECTION_ADDITIONAL : - DNS_SECTION_AUTHORITY; - ttl = ISC_UINT32_MAX; - if (!nxrewrite && qtype == dns_rdatatype_soa && - zone != NULL && dns_zone_getzeronosoattl(zone)) - ttl = 0; - result = query_addsoa(client, db, version, ttl, associated, - section); - if (result != ISC_R_SUCCESS) { - QUERY_ERROR(result); - goto cleanup; - } - - if (WANTDNSSEC(client)) { - /* - * Add NSEC record if we found one. - */ - if (dns_rdataset_isassociated(rdataset)) - query_addrrset(client, &fname, &rdataset, - &sigrdataset, - NULL, DNS_SECTION_AUTHORITY); - query_addwildcardproof(client, db, version, - client->query.qname, ISC_FALSE, - ISC_FALSE); - } - - /* - * Set message rcode. - */ - if (empty_wild) - client->message->rcode = dns_rcode_noerror; - else - client->message->rcode = dns_rcode_nxdomain; - goto cleanup; + return (query_nxdomain(qctx, ISC_FALSE)); case DNS_R_NCACHENXDOMAIN: - tresult = redirect(client, fname, rdataset, &node, - &db, &version, type); - if (tresult == ISC_R_SUCCESS) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect); - break; - } - if (tresult == DNS_R_NXRRSET) { - redirected = ISC_TRUE; - is_zone = ISC_TRUE; - goto iszone_nxrrset; - } - if (tresult == DNS_R_NCACHENXRRSET) { - redirected = ISC_TRUE; - result = tresult; - goto ncache_nxrrset; - } - tresult = redirect2(client, fname, rdataset, &node, - &db, &version, type, &is_zone); - if (tresult == DNS_R_CONTINUE) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect_rlookup); - SAVE(client->query.redirect.db, db); - SAVE(client->query.redirect.node, node); - SAVE(client->query.redirect.zone, zone); - client->query.redirect.qtype = qtype; - INSIST(rdataset != NULL); - SAVE(client->query.redirect.rdataset, rdataset); - SAVE(client->query.redirect.sigrdataset, sigrdataset); - client->query.redirect.result = DNS_R_NCACHENXDOMAIN; - dns_name_copy(fname, client->query.redirect.fname, - NULL); - client->query.redirect.authoritative = authoritative; - client->query.redirect.is_zone = is_zone; - goto cleanup; - } - if (tresult == ISC_R_SUCCESS) { - inc_stats(client, - dns_nsstatscounter_nxdomainredirect); - break; - } - if (tresult == DNS_R_NXRRSET) { - redirected = ISC_TRUE; - is_zone = ISC_TRUE; - goto iszone_nxrrset; - } - if (tresult == DNS_R_NCACHENXRRSET) { - redirected = ISC_TRUE; - result = tresult; - goto ncache_nxrrset; - } - /* FALLTHROUGH */ + result = query_redirect(qctx); + if (result != ISC_R_COMPLETE) + return (result); + return (query_ncache(qctx, DNS_R_NCACHENXDOMAIN)); case DNS_R_NCACHENXRRSET: - ncache_nxrrset: - INSIST(!is_zone); - authoritative = ISC_FALSE; - /* - * Set message rcode, if required. - */ - if (result == DNS_R_NCACHENXDOMAIN) - client->message->rcode = dns_rcode_nxdomain; - /* - * Look for RFC 1918 leakage from Internet. - */ - if (result == DNS_R_NCACHENXDOMAIN && - qtype == dns_rdatatype_ptr && - client->message->rdclass == dns_rdataclass_in && - dns_name_countlabels(fname) == 7) - warn_rfc1918(client, fname, rdataset); - -#ifdef dns64_bis_return_excluded_addresses - if (dns64) -#else - if (dns64 && !dns64_exclude) -#endif - { - /* - * Restore the answers from the previous AAAA lookup. - */ - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - RESTORE(rdataset, client->query.dns64_aaaa); - RESTORE(sigrdataset, client->query.dns64_sigaaaa); - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: " - "query_getnamebuf failed (4)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - fname = query_newname(client, dbuf, &b); - if (fname == NULL) { - CTRACE(ISC_LOG_ERROR, - "query_find: " - "query_newname failed (4)"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } - } - dns_name_copy(client->query.qname, fname, NULL); - dns64 = ISC_FALSE; -#ifdef dns64_bis_return_excluded_addresses - if (dns64_excluded) - break; -#endif - } else if (result == DNS_R_NCACHENXRRSET && - !ISC_LIST_EMPTY(client->view->dns64) && - client->message->rdclass == dns_rdataclass_in && - qtype == dns_rdatatype_aaaa) - { - /* - * Look to see if there are A records for this - * name. - */ - - /* - * If the ttl is zero we need to workout if we have just - * decremented to zero or if there was no negative cache - * ttl in the answer. - */ - if (rdataset->ttl != 0) - client->query.dns64_ttl = rdataset->ttl; - else if (dns_rdataset_first(rdataset) == ISC_R_SUCCESS) - client->query.dns64_ttl = 0; - SAVE(client->query.dns64_aaaa, rdataset); - SAVE(client->query.dns64_sigaaaa, sigrdataset); - query_releasename(client, &fname); - dns_db_detachnode(db, &node); - type = qtype = dns_rdatatype_a; - dns64 = ISC_TRUE; - goto db_find; - } - - /* - * We don't call query_addrrset() because we don't need any - * of its extra features (and things would probably break!). - */ - if (dns_rdataset_isassociated(rdataset)) { - query_keepname(client, fname, dbuf); - dns_message_addname(client->message, fname, - DNS_SECTION_AUTHORITY); - ISC_LIST_APPEND(fname->list, rdataset, link); - fname = NULL; - rdataset = NULL; - } - goto cleanup; + return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); case DNS_R_CNAME: - /* - * If we have a zero ttl from the cache refetch it. - */ - if (!is_zone && event == NULL && rdataset->ttl == 0 && - RECURSIONOK(client)) - { - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (sigrdataset != NULL && - dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - if (node != NULL) - dns_db_detachnode(db, &node); + return (query_cname(qctx)); - INSIST(!REDIRECT(client)); - result = query_recurse(client, qtype, - client->query.qname, - NULL, NULL, resuming); - if (result == ISC_R_SUCCESS) { - client->query.attributes |= - NS_QUERYATTR_RECURSING; - if (dns64) - client->query.attributes |= - NS_QUERYATTR_DNS64; - if (dns64_exclude) - client->query.attributes |= - NS_QUERYATTR_DNS64EXCLUDE; - } else - RECURSE_ERROR(result); - goto cleanup; - } - - /* - * Keep a copy of the rdataset. We have to do this because - * query_addrrset may clear 'rdataset' (to prevent the - * cleanup code from cleaning it up). - */ - trdataset = rdataset; - /* - * Add the CNAME to the answer section. - */ - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - if (WANTDNSSEC(client) && - (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) - { - dns_fixedname_init(&wildcardname); - dns_name_copy(fname, dns_fixedname_name(&wildcardname), - NULL); - need_wildcardproof = ISC_TRUE; - } - if (NOQNAME(rdataset) && WANTDNSSEC(client)) - noqname = rdataset; - else - noqname = NULL; - if (!is_zone && RECURSIONOK(client)) - query_prefetch(client, fname, rdataset); - query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, - DNS_SECTION_ANSWER); - if (noqname != NULL) - query_addnoqnameproof(client, noqname); - /* - * We set the PARTIALANSWER attribute so that if anything goes - * wrong later on, we'll return what we've got so far. - */ - client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; - /* - * Reset qname to be the target name of the CNAME and restart - * the query. - */ - tname = NULL; - result = dns_message_gettempname(client->message, &tname); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_rdataset_first(trdataset); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - dns_rdataset_current(trdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &cname, NULL); - dns_rdata_reset(&rdata); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - dns_name_init(tname, NULL); - result = dns_name_dup(&cname.cname, client->mctx, tname); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); - dns_rdata_freestruct(&cname); - goto cleanup; - } - dns_rdata_freestruct(&cname); - ns_client_qnamereplace(client, tname); - want_restart = ISC_TRUE; - if (!WANTRECURSION(client)) - options |= DNS_GETDB_NOLOG; - goto addauth; case DNS_R_DNAME: - /* - * Compare the current qname to the found name. We need - * to know how many labels and bits are in common because - * we're going to have to split qname later on. - */ - namereln = dns_name_fullcompare(client->query.qname, fname, - &order, &nlabels); - INSIST(namereln == dns_namereln_subdomain); - /* - * Keep a copy of the rdataset. We have to do this because - * query_addrrset may clear 'rdataset' (to prevent the - * cleanup code from cleaning it up). - */ - trdataset = rdataset; - /* - * Add the DNAME to the answer section. - */ - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - if (WANTDNSSEC(client) && - (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) - { - dns_fixedname_init(&wildcardname); - dns_name_copy(fname, dns_fixedname_name(&wildcardname), - NULL); - need_wildcardproof = ISC_TRUE; - } - if (!is_zone && RECURSIONOK(client)) - query_prefetch(client, fname, rdataset); - query_addrrset(client, &fname, &rdataset, sigrdatasetp, dbuf, - DNS_SECTION_ANSWER); - /* - * We set the PARTIALANSWER attribute so that if anything goes - * wrong later on, we'll return what we've got so far. - */ - client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; - /* - * Get the target name of the DNAME. - */ - tname = NULL; - result = dns_message_gettempname(client->message, &tname); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_rdataset_first(trdataset); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - dns_rdataset_current(trdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &dname, NULL); - dns_rdata_reset(&rdata); - if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - dns_name_clone(&dname.dname, tname); - dns_rdata_freestruct(&dname); - /* - * Construct the new qname consisting of - * . - */ - dns_fixedname_init(&fixed); - prefix = dns_fixedname_name(&fixed); - dns_name_split(client->query.qname, nlabels, prefix, NULL); - INSIST(fname == NULL); - dbuf = query_getnamebuf(client); - if (dbuf == NULL) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - fname = query_newname(client, dbuf, &b); - if (fname == NULL) { - dns_message_puttempname(client->message, &tname); - goto cleanup; - } - result = dns_name_concatenate(prefix, tname, fname, NULL); - dns_message_puttempname(client->message, &tname); + return (query_dname(qctx)); - /* - * RFC2672, section 4.1, subsection 3c says - * we should return YXDOMAIN if the constructed - * name would be too long. - */ - if (result == DNS_R_NAMETOOLONG) - client->message->rcode = dns_rcode_yxdomain; - if (result != ISC_R_SUCCESS) - goto cleanup; - - query_keepname(client, fname, dbuf); - /* - * Synthesize a CNAME consisting of - * CNAME - * with - * - * Synthesize a CNAME so old old clients that don't understand - * DNAME can chain. - * - * We do not try to synthesize a signature because we hope - * that security aware servers will understand DNAME. Also, - * even if we had an online key, making a signature - * on-the-fly is costly, and not really legitimate anyway - * since the synthesized CNAME is NOT in the zone. - */ - result = query_add_cname(client, client->query.qname, fname, - trdataset->trust, trdataset->ttl); - if (result != ISC_R_SUCCESS) - goto cleanup; - /* - * Switch to the new qname and restart. - */ - ns_client_qnamereplace(client, fname); - fname = NULL; - want_restart = ISC_TRUE; - if (!WANTRECURSION(client)) - options |= DNS_GETDB_NOLOG; - goto addauth; default: /* * Something has gone wrong. */ snprintf(errmsg, sizeof(errmsg) - 1, - "query_find: unexpected error after resuming: %s", + "query_gotanswer: unexpected error: %s", isc_result_totext(result)); - CTRACE(ISC_LOG_ERROR, errmsg); - QUERY_ERROR(DNS_R_SERVFAIL); + CCTRACE(ISC_LOG_ERROR, errmsg); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } +} + +static void +query_addnoqnameproof(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + isc_buffer_t *dbuf, b; + dns_name_t *fname = NULL; + dns_rdataset_t *neg = NULL, *negsig = NULL; + isc_result_t result = ISC_R_NOMEMORY; + + CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof"); + + if (qctx->noqname == NULL) { + return; + } + + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { goto cleanup; } - if (WANTDNSSEC(client) && - (fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) + fname = query_newname(client, dbuf, &b); + neg = query_newrdataset(client); + negsig = query_newrdataset(client); + if (fname == NULL || neg == NULL || negsig == NULL) { + goto cleanup; + } + + result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + query_addrrset(client, &fname, &neg, &negsig, dbuf, + DNS_SECTION_AUTHORITY); + + if ((qctx->noqname->attributes & DNS_RDATASETATTR_CLOSEST) == 0) { + goto cleanup; + } + + if (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + } + + if (neg == NULL) { + neg = query_newrdataset(client); + } else if (dns_rdataset_isassociated(neg)) { + dns_rdataset_disassociate(neg); + } + + if (negsig == NULL) { + negsig = query_newrdataset(client); + } else if (dns_rdataset_isassociated(negsig)) { + dns_rdataset_disassociate(negsig); + } + + if (fname == NULL || neg == NULL || negsig == NULL) + goto cleanup; + result = dns_rdataset_getclosest(qctx->noqname, fname, neg, negsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + query_addrrset(client, &fname, &neg, &negsig, dbuf, + DNS_SECTION_AUTHORITY); + + cleanup: + if (neg != NULL) { + query_putrdataset(client, &neg); + } + if (negsig != NULL) { + query_putrdataset(client, &negsig); + } + if (fname != NULL) { + query_releasename(client, &fname); + } +} + +/*% + * Build the response for a query for type ANY. + */ +static isc_result_t +query_respond_any(query_ctx_t *qctx) { + dns_name_t *tname; + int rdatasets_found = 0; + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result; + dns_rdatatype_t onetype = 0; /* type to use for minimal-any */ +#ifdef ALLOW_FILTER_AAAA + isc_boolean_t have_aaaa, have_a, have_sig; + + /* + * If we are not authoritative, assume there is an A record + * even in if it is not in our cache. This assumption could + * be wrong but it is a good bet. + */ + have_aaaa = ISC_FALSE; + have_a = !qctx->authoritative; + have_sig = ISC_FALSE; +#endif + + result = dns_db_allrdatasets(qctx->db, qctx->node, + qctx->version, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + CCTRACE(ISC_LOG_ERROR, + "query_respond_any: allrdatasets failed"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + + /* + * Calling query_addrrset() with a non-NULL dbuf is going + * to either keep or release the name. We don't want it to + * release fname, since we may have to call query_addrrset() + * more than once. That means we have to call query_keepname() + * now, and pass a NULL dbuf to query_addrrset(). + * + * If we do a query_addrrset() below, we must set qctx->fname to + * NULL before leaving this block, otherwise we might try to + * cleanup qctx->fname even though we're using it! + */ + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + tname = qctx->fname; + + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, qctx->rdataset); +#ifdef ALLOW_FILTER_AAAA + /* + * Notice the presence of A and AAAAs so + * that AAAAs can be hidden from IPv4 clients. + */ + if (qctx->client->filter_aaaa != dns_aaaa_ok) { + if (qctx->rdataset->type == dns_rdatatype_aaaa) + have_aaaa = ISC_TRUE; + else if (qctx->rdataset->type == dns_rdatatype_a) + have_a = ISC_TRUE; + } +#endif + if (qctx->is_zone && qctx->qtype == dns_rdatatype_any && + !dns_db_issecure(qctx->db) && + dns_rdatatype_isdnssec(qctx->rdataset->type)) + { + /* + * The zone is transitioning from insecure + * to secure. Hide the dnssec records from + * ANY queries. + */ + dns_rdataset_disassociate(qctx->rdataset); + } else if (qctx->client->view->minimal_any && + !TCP(qctx->client) && !WANTDNSSEC(qctx->client) && + qctx->qtype == dns_rdatatype_any && + (qctx->rdataset->type == dns_rdatatype_sig || + qctx->rdataset->type == dns_rdatatype_rrsig)) + { + CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: " + "minimal-any skip signature"); + dns_rdataset_disassociate(qctx->rdataset); + } else if (qctx->client->view->minimal_any && + !TCP(qctx->client) && onetype != 0 && + qctx->rdataset->type != onetype && + qctx->rdataset->covers != onetype) + { + CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: " + "minimal-any skip rdataset"); + dns_rdataset_disassociate(qctx->rdataset); + } else if ((qctx->qtype == dns_rdatatype_any || + qctx->rdataset->type == qctx->qtype) && + qctx->rdataset->type != 0) + { +#ifdef ALLOW_FILTER_AAAA + if (dns_rdatatype_isdnssec(qctx->rdataset->type)) + have_sig = ISC_TRUE; +#endif + + if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) + { + qctx->noqname = qctx->rdataset; + } else { + qctx->noqname = NULL; + } + + qctx->rpz_st = qctx->client->query.rpz_st; + if (qctx->rpz_st != NULL) + qctx->rdataset->ttl = + ISC_MIN(qctx->rdataset->ttl, + qctx->rpz_st->m.ttl); + + if (!qctx->is_zone && RECURSIONOK(qctx->client)) { + dns_name_t *name; + name = (qctx->fname != NULL) + ? qctx->fname + : tname; + query_prefetch(qctx->client, name, + qctx->rdataset); + } + + /* + * Remember the first RRtype we find so we + * can skip others with minimal-any. + */ + if (qctx->rdataset->type == dns_rdatatype_sig || + qctx->rdataset->type == dns_rdatatype_rrsig) + onetype = qctx->rdataset->covers; + else + onetype = qctx->rdataset->type; + + query_addrrset(qctx->client, + (qctx->fname != NULL) + ? &qctx->fname + : &tname, + &qctx->rdataset, NULL, + NULL, DNS_SECTION_ANSWER); + + query_addnoqnameproof(qctx); + + rdatasets_found++; + INSIST(tname != NULL); + + /* + * rdataset is non-NULL only in certain + * pathological cases involving DNAMEs. + */ + if (qctx->rdataset != NULL) + query_putrdataset(qctx->client, + &qctx->rdataset); + + qctx->rdataset = query_newrdataset(qctx->client); + if (qctx->rdataset == NULL) + break; + } else { + /* + * We're not interested in this rdataset. + */ + dns_rdataset_disassociate(qctx->rdataset); + } + result = dns_rdatasetiter_next(rdsiter); + } + +#ifdef ALLOW_FILTER_AAAA + /* + * Filter AAAAs if there is an A and there is no signature + * or we are supposed to break DNSSEC. + */ + if (qctx->client->filter_aaaa == dns_aaaa_break_dnssec) + qctx->client->attributes |= NS_CLIENTATTR_FILTER_AAAA; + else if (qctx->client->filter_aaaa != dns_aaaa_ok && + have_aaaa && have_a && + (!have_sig || !WANTDNSSEC(qctx->client))) + qctx->client->attributes |= NS_CLIENTATTR_FILTER_AAAA; +#endif + if (qctx->fname != NULL) + dns_message_puttempname(qctx->client->message, &qctx->fname); + + if (rdatasets_found == 0) { + /* + * No matching rdatasets found in cache. If we were + * searching for RRSIG/SIG, that's probably okay; + * otherwise this is an error condition. + */ + if ((qctx->qtype == dns_rdatatype_rrsig || + qctx->qtype == dns_rdatatype_sig) && + result == ISC_R_NOMORE) + { + isc_buffer_t b; + if (!qctx->is_zone) { + qctx->authoritative = ISC_FALSE; + dns_rdatasetiter_destroy(&rdsiter); + qctx->client->attributes &= ~NS_CLIENTATTR_RA; + query_addauth(qctx); + return (query_done(qctx)); + } + + if (qctx->qtype == dns_rdatatype_rrsig && + dns_db_issecure(qctx->db)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(qctx->client->query.qname, + namebuf, + sizeof(namebuf)); + ns_client_log(qctx->client, + DNS_LOGCATEGORY_DNSSEC, + NS_LOGMODULE_QUERY, + ISC_LOG_WARNING, + "missing signature for %s", + namebuf); + } + + dns_rdatasetiter_destroy(&rdsiter); + qctx->fname = query_newname(qctx->client, + qctx->dbuf, &b); + return (query_sign_nodata(qctx)); + } else { + CCTRACE(ISC_LOG_ERROR, + "query_respond_any: " + "no matching rdatasets in cache"); + result = DNS_R_SERVFAIL; + } + } + + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_NOMORE) { + CCTRACE(ISC_LOG_ERROR, + "query_respond_any: dns_rdatasetiter_destroy failed"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + } else { + query_addauth(qctx); + } + + return (query_done(qctx)); +} + +/* + * Set the expire time, if requested, when answering from a + * slave or master zone. + */ +static void +query_getexpire(query_ctx_t *qctx) { + dns_zone_t *raw = NULL, *mayberaw; + + if (qctx->zone == NULL || !qctx->is_zone || + qctx->qtype != dns_rdatatype_soa || + qctx->client->query.restarts != 0 || + (qctx->client->attributes & NS_CLIENTATTR_WANTEXPIRE) == 0) { - dns_fixedname_init(&wildcardname); - dns_name_copy(fname, dns_fixedname_name(&wildcardname), NULL); - need_wildcardproof = ISC_TRUE; + return; + } + + dns_zone_getraw(qctx->zone, &raw); + mayberaw = (raw != NULL) ? raw : qctx->zone; + + if (dns_zone_gettype(mayberaw) == dns_zone_slave) { + isc_time_t expiretime; + isc_uint32_t secs; + dns_zone_getexpiretime(qctx->zone, &expiretime); + secs = isc_time_seconds(&expiretime); + if (secs >= qctx->client->now && + qctx->result == ISC_R_SUCCESS) + { + qctx->client->attributes |= + NS_CLIENTATTR_HAVEEXPIRE; + qctx->client->expire = secs - qctx->client->now; + } + } else if (dns_zone_gettype(mayberaw) == dns_zone_master) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + + result = dns_rdataset_first(qctx->rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdataset_current(qctx->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + qctx->client->expire = soa.expire; + qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; + } + + if (raw != NULL) { + dns_zone_detach(&raw); + } +} + +#ifdef ALLOW_FILTER_AAAA +/* + * Optionally hide AAAAs from IPv4 clients if there is an A. + * + * We add the AAAAs now, but might refuse to render them later + * after DNSSEC is figured out. + * + * This could be more efficient, but the whole idea is + * so fundamentally wrong, unavoidably inaccurate, and + * unneeded that it is best to keep it as short as possible. + */ +static isc_result_t +query_filter_aaaa(query_ctx_t *qctx) { + isc_result_t result; + + if (qctx->client->filter_aaaa != dns_aaaa_break_dnssec && + (qctx->client->filter_aaaa != dns_aaaa_filter || + (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL && + dns_rdataset_isassociated(qctx->sigrdataset)))) + { + return (ISC_R_COMPLETE); + } + + if (qctx->qtype == dns_rdatatype_aaaa) { + dns_rdataset_t *trdataset; + trdataset = query_newrdataset(qctx->client); + result = dns_db_findrdataset(qctx->db, qctx->node, + qctx->version, + dns_rdatatype_a, 0, + qctx->client->now, + trdataset, NULL); + if (dns_rdataset_isassociated(trdataset)) { + dns_rdataset_disassociate(trdataset); + } + query_putrdataset(qctx->client, &trdataset); + + /* + * We have an AAAA but the A is not in our cache. + * Assume any result other than DNS_R_DELEGATION + * or ISC_R_NOTFOUND means there is no A and + * so AAAAs are ok. + * + * Assume there is no A if we can't recurse + * for this client, although that could be + * the wrong answer. What else can we do? + * Besides, that we have the AAAA and are using + * this mechanism suggests that we care more + * about As than AAAAs and would have cached + * the A if it existed. + */ + if (result == ISC_R_SUCCESS) { + qctx->client->attributes |= + NS_CLIENTATTR_FILTER_AAAA; + + } else if (qctx->authoritative || + !RECURSIONOK(qctx->client) || + (result != DNS_R_DELEGATION && + result != ISC_R_NOTFOUND)) + { + qctx->client->attributes &= + ~NS_CLIENTATTR_FILTER_AAAA; + } else { + /* + * This is an ugly kludge to recurse + * for the A and discard the result. + * + * Continue to add the AAAA now. + * We'll make a note to not render it + * if the recursion for the A succeeds. + */ + INSIST(!REDIRECT(qctx->client)); + result = query_recurse(qctx->client, + dns_rdatatype_a, + qctx->client->query.qname, + NULL, NULL, qctx->resuming); + if (result == ISC_R_SUCCESS) { + qctx->client->attributes |= + NS_CLIENTATTR_FILTER_AAAA_RC; + qctx->client->query.attributes |= + NS_QUERYATTR_RECURSING; + } + } + } else if (qctx->qtype == dns_rdatatype_a && + (qctx->client->attributes & + NS_CLIENTATTR_FILTER_AAAA_RC) != 0) + { + qctx->client->attributes &= ~NS_CLIENTATTR_FILTER_AAAA_RC; + qctx->client->attributes |= NS_CLIENTATTR_FILTER_AAAA; + qctx_clean(qctx); + + return (query_done(qctx)); + } + + return (ISC_R_COMPLETE); +} +#endif + +/*% + * Build a repsonse for a "normal" query, for a type other than ANY, + * for which we have an answer (either positive or negative). + */ +static isc_result_t +query_respond(query_ctx_t *qctx) { + dns_rdataset_t **sigrdatasetp = NULL; + isc_result_t result; + + /* + * If we have a zero ttl from the cache, refetch. + */ + if (!qctx->is_zone && qctx->event == NULL && + qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client)) + { + qctx_clean(qctx); + + INSIST(!REDIRECT(qctx->client)); + result = query_recurse(qctx->client, qctx->qtype, + qctx->client->query.qname, + NULL, NULL, qctx->resuming); + if (result == ISC_R_SUCCESS) { + qctx->client->query.attributes |= + NS_QUERYATTR_RECURSING; + if (qctx->dns64) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64; + if (qctx->dns64_exclude) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64EXCLUDE; + } else { + RECURSE_ERROR(qctx, result); + } + + return (query_done(qctx)); + } + +#ifdef ALLOW_FILTER_AAAA + result = query_filter_aaaa(qctx); + if (result != ISC_R_COMPLETE) + return (result); +#endif + /* + * Check to see if the AAAA RRset has non-excluded addresses + * in it. If not look for a A RRset. + */ + INSIST(qctx->client->query.dns64_aaaaok == NULL); + + if (qctx->qtype == dns_rdatatype_aaaa && !qctx->dns64_exclude && + !ISC_LIST_EMPTY(qctx->client->view->dns64) && + qctx->client->message->rdclass == dns_rdataclass_in && + !dns64_aaaaok(qctx->client, qctx->rdataset, qctx->sigrdataset)) + { + /* + * Look to see if there are A records for this name. + */ + qctx->client->query.dns64_ttl = qctx->rdataset->ttl; + SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset); + SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset); + query_releasename(qctx->client, &qctx->fname); + dns_db_detachnode(qctx->db, &qctx->node); + qctx->type = qctx->qtype = dns_rdatatype_a; + qctx->rpz_st = qctx->client->query.rpz_st; + if (qctx->rpz_st != NULL) { + /* + * Arrange for RPZ rewriting of any A records. + */ + if ((qctx->rpz_st->state & DNS_RPZ_REWRITTEN) != 0) + qctx->is_zone = qctx->rpz_st->q.is_zone; + rpz_st_clear(qctx->client); + } + qctx->dns64_exclude = qctx->dns64 = ISC_TRUE; + + return (query_lookup(qctx)); + } + + if (qctx->sigrdataset != NULL) { + sigrdatasetp = &qctx->sigrdataset; + } + + if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { + qctx->noqname = qctx->rdataset; + } else { + qctx->noqname = NULL; + } + + /* + * BIND 8 priming queries need the additional section. + */ + if (qctx->is_zone && qctx->qtype == dns_rdatatype_ns && + dns_name_equal(qctx->client->query.qname, dns_rootname)) + { + qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; + } + + /* + * Set expire time + */ + query_getexpire(qctx); + + if (qctx->dns64) { + result = query_dns64(qctx); + dns_rdataset_disassociate(qctx->rdataset); + dns_message_puttemprdataset(qctx->client->message, + &qctx->rdataset); + if (result == ISC_R_NOMORE) { +#ifndef dns64_bis_return_excluded_addresses + if (qctx->dns64_exclude) { + if (!qctx->is_zone) + return (query_done(qctx)); + /* + * Add a fake SOA record. + */ + (void)query_addsoa(qctx, 600, + DNS_SECTION_AUTHORITY); + return (query_done(qctx)); + } +#endif + if (qctx->is_zone) { + return (query_nodata(qctx, DNS_R_NXDOMAIN)); + } else { + return (query_ncache(qctx, DNS_R_NXDOMAIN)); + } + } else if (result != ISC_R_SUCCESS) { + qctx->result = result; + return (query_done(qctx)); + } + } else if (qctx->client->query.dns64_aaaaok != NULL) { + query_filter64(qctx); + query_putrdataset(qctx->client, &qctx->rdataset); + } else { + if (!qctx->is_zone && RECURSIONOK(qctx->client)) + query_prefetch(qctx->client, qctx->fname, + qctx->rdataset); + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, sigrdatasetp, + qctx->dbuf, DNS_SECTION_ANSWER); + } + + query_addnoqnameproof(qctx); + + /* + * We shouldn't ever fail to add 'rdataset' + * because it's already in the answer. + */ + INSIST(qctx->rdataset == NULL); + + query_addauth(qctx); + + return (query_done(qctx)); +} + +static isc_result_t +query_dns64(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + dns_name_t *name, *mname; + dns_rdata_t *dns64_rdata; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t *dns64_rdatalist; + dns_rdataset_t *dns64_rdataset; + dns_rdataset_t *mrdataset; + isc_buffer_t *buffer; + isc_region_t r; + isc_result_t result; + dns_view_t *view = client->view; + isc_netaddr_t netaddr; + dns_dns64_t *dns64; + unsigned int flags = 0; + + /*% + * To the current response for 'qctx->client', add the answer RRset + * '*rdatasetp' and an optional signature set '*sigrdatasetp', with + * owner name '*namep', to the answer section, unless they are + * already there. Also add any pertinent additional data. + * + * If 'qctx->dbuf' is not NULL, then 'qctx->fname' is the name + * whose data is stored 'qctx->dbuf'. In this case, + * query_addrrset() guarantees that when it returns the name + * will either have been kept or released. + */ + CTRACE(ISC_LOG_DEBUG(3), "query_dns64"); + + qctx->qtype = qctx->type = dns_rdatatype_aaaa; + + name = qctx->fname; + mname = NULL; + mrdataset = NULL; + buffer = NULL; + dns64_rdata = NULL; + dns64_rdataset = NULL; + dns64_rdatalist = NULL; + result = dns_message_findname(client->message, DNS_SECTION_ANSWER, + name, dns_rdatatype_aaaa, + qctx->rdataset->covers, + &mname, &mrdataset); + if (result == ISC_R_SUCCESS) { + /* + * We've already got an RRset of the given name and type. + * There's nothing else to do; + */ + CTRACE(ISC_LOG_DEBUG(3), + "query_dns64: dns_message_findname succeeded: done"); + if (qctx->dbuf != NULL) + query_releasename(client, &qctx->fname); + return (ISC_R_SUCCESS); + } else if (result == DNS_R_NXDOMAIN) { + /* + * The name doesn't exist. + */ + if (qctx->dbuf != NULL) + query_keepname(client, name, qctx->dbuf); + dns_message_addname(client->message, name, DNS_SECTION_ANSWER); + qctx->fname = NULL; + mname = name; + } else { + RUNTIME_CHECK(result == DNS_R_NXRRSET); + if (qctx->dbuf != NULL) + query_releasename(client, &qctx->fname); + } + + if (qctx->rdataset->trust != dns_trust_secure) { + client->query.attributes &= ~NS_QUERYATTR_SECURE; + } + + isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); + + result = isc_buffer_allocate(client->mctx, &buffer, + view->dns64cnt * 16 * + dns_rdataset_count(qctx->rdataset)); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdataset(client->message, + &dns64_rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdatalist(client->message, + &dns64_rdatalist); + if (result != ISC_R_SUCCESS) + goto cleanup; + + dns_rdatalist_init(dns64_rdatalist); + dns64_rdatalist->rdclass = dns_rdataclass_in; + dns64_rdatalist->type = dns_rdatatype_aaaa; + if (client->query.dns64_ttl != ISC_UINT32_MAX) + dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl, + client->query.dns64_ttl); + else + dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl, 600); + + if (RECURSIONOK(client)) + flags |= DNS_DNS64_RECURSIVE; + + /* + * We use the signatures from the A lookup to set DNS_DNS64_DNSSEC + * as this provides a easy way to see if the answer was signed. + */ + if (qctx->sigrdataset != NULL && + dns_rdataset_isassociated(qctx->sigrdataset)) + flags |= DNS_DNS64_DNSSEC; + + for (result = dns_rdataset_first(qctx->rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(qctx->rdataset)) { + for (dns64 = ISC_LIST_HEAD(client->view->dns64); + dns64 != NULL; dns64 = dns_dns64_next(dns64)) { + + dns_rdataset_current(qctx->rdataset, &rdata); + isc_buffer_availableregion(buffer, &r); + INSIST(r.length >= 16); + result = dns_dns64_aaaafroma(dns64, &netaddr, + client->signer, + &ns_g_server->aclenv, + flags, rdata.data, r.base); + if (result != ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + continue; + } + isc_buffer_add(buffer, 16); + isc_buffer_remainingregion(buffer, &r); + isc_buffer_forward(buffer, 16); + result = dns_message_gettemprdata(client->message, + &dns64_rdata); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdata_init(dns64_rdata); + dns_rdata_fromregion(dns64_rdata, dns_rdataclass_in, + dns_rdatatype_aaaa, &r); + ISC_LIST_APPEND(dns64_rdatalist->rdata, dns64_rdata, + link); + dns64_rdata = NULL; + dns_rdata_reset(&rdata); + } + } + if (result != ISC_R_NOMORE) + goto cleanup; + + if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) + goto cleanup; + + result = dns_rdatalist_tordataset(dns64_rdatalist, dns64_rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdataset_setownercase(dns64_rdataset, mname); + client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; + dns64_rdataset->trust = qctx->rdataset->trust; + query_addrdataset(client, mname, dns64_rdataset); + dns64_rdataset = NULL; + dns64_rdatalist = NULL; + dns_message_takebuffer(client->message, &buffer); + inc_stats(client, dns_nsstatscounter_dns64); + result = ISC_R_SUCCESS; + + cleanup: + if (buffer != NULL) + isc_buffer_free(&buffer); + + if (dns64_rdata != NULL) + dns_message_puttemprdata(client->message, &dns64_rdata); + + if (dns64_rdataset != NULL) + dns_message_puttemprdataset(client->message, &dns64_rdataset); + + if (dns64_rdatalist != NULL) { + for (dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata); + dns64_rdata != NULL; + dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata)) + { + ISC_LIST_UNLINK(dns64_rdatalist->rdata, + dns64_rdata, link); + dns_message_puttemprdata(client->message, &dns64_rdata); + } + dns_message_puttemprdatalist(client->message, &dns64_rdatalist); + } + + CTRACE(ISC_LOG_DEBUG(3), "query_dns64: done"); + return (result); +} + +static void +query_filter64(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + dns_name_t *name, *mname; + dns_rdata_t *myrdata; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t *myrdatalist; + dns_rdataset_t *myrdataset; + isc_buffer_t *buffer; + isc_region_t r; + isc_result_t result; + unsigned int i; + + CTRACE(ISC_LOG_DEBUG(3), "query_filter64"); + + INSIST(client->query.dns64_aaaaok != NULL); + INSIST(client->query.dns64_aaaaoklen == + dns_rdataset_count(qctx->rdataset)); + + name = qctx->fname; + mname = NULL; + buffer = NULL; + myrdata = NULL; + myrdataset = NULL; + myrdatalist = NULL; + result = dns_message_findname(client->message, DNS_SECTION_ANSWER, + name, dns_rdatatype_aaaa, + qctx->rdataset->covers, + &mname, &myrdataset); + if (result == ISC_R_SUCCESS) { + /* + * We've already got an RRset of the given name and type. + * There's nothing else to do; + */ + CTRACE(ISC_LOG_DEBUG(3), + "query_filter64: dns_message_findname succeeded: done"); + if (qctx->dbuf != NULL) + query_releasename(client, &qctx->fname); + return; + } else if (result == DNS_R_NXDOMAIN) { + mname = name; + qctx->fname = NULL; + } else { + RUNTIME_CHECK(result == DNS_R_NXRRSET); + if (qctx->dbuf != NULL) + query_releasename(client, &qctx->fname); + qctx->dbuf = NULL; + } + + if (qctx->rdataset->trust != dns_trust_secure) { + client->query.attributes &= ~NS_QUERYATTR_SECURE; + } + + result = isc_buffer_allocate(client->mctx, &buffer, + 16 * dns_rdataset_count(qctx->rdataset)); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdataset(client->message, &myrdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdatalist(client->message, &myrdatalist); + if (result != ISC_R_SUCCESS) + goto cleanup; + + dns_rdatalist_init(myrdatalist); + myrdatalist->rdclass = dns_rdataclass_in; + myrdatalist->type = dns_rdatatype_aaaa; + myrdatalist->ttl = qctx->rdataset->ttl; + + i = 0; + for (result = dns_rdataset_first(qctx->rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(qctx->rdataset)) { + if (!client->query.dns64_aaaaok[i++]) + continue; + dns_rdataset_current(qctx->rdataset, &rdata); + INSIST(rdata.length == 16); + isc_buffer_putmem(buffer, rdata.data, rdata.length); + isc_buffer_remainingregion(buffer, &r); + isc_buffer_forward(buffer, rdata.length); + result = dns_message_gettemprdata(client->message, &myrdata); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdata_init(myrdata); + dns_rdata_fromregion(myrdata, dns_rdataclass_in, + dns_rdatatype_aaaa, &r); + ISC_LIST_APPEND(myrdatalist->rdata, myrdata, link); + myrdata = NULL; + dns_rdata_reset(&rdata); + } + if (result != ISC_R_NOMORE) + goto cleanup; + + result = dns_rdatalist_tordataset(myrdatalist, myrdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdataset_setownercase(myrdataset, name); + client->query.attributes |= NS_QUERYATTR_NOADDITIONAL; + if (mname == name) { + if (qctx->dbuf != NULL) + query_keepname(client, name, qctx->dbuf); + dns_message_addname(client->message, name, + DNS_SECTION_ANSWER); + qctx->dbuf = NULL; + } + myrdataset->trust = qctx->rdataset->trust; + query_addrdataset(client, mname, myrdataset); + myrdataset = NULL; + myrdatalist = NULL; + dns_message_takebuffer(client->message, &buffer); + + cleanup: + if (buffer != NULL) + isc_buffer_free(&buffer); + + if (myrdata != NULL) + dns_message_puttemprdata(client->message, &myrdata); + + if (myrdataset != NULL) + dns_message_puttemprdataset(client->message, &myrdataset); + + if (myrdatalist != NULL) { + for (myrdata = ISC_LIST_HEAD(myrdatalist->rdata); + myrdata != NULL; + myrdata = ISC_LIST_HEAD(myrdatalist->rdata)) + { + ISC_LIST_UNLINK(myrdatalist->rdata, myrdata, link); + dns_message_puttemprdata(client->message, &myrdata); + } + dns_message_puttemprdatalist(client->message, &myrdatalist); + } + if (qctx->dbuf != NULL) + query_releasename(client, &name); + + CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done"); +} + +/*% + * Handle the case of a name not being found in a database lookup. + * Called from query_gotanswer(). Passes off processing to + * query_delegation() for a root referral if appropriate. + */ +static isc_result_t +query_notfound(query_ctx_t *qctx) { + isc_result_t result; + + INSIST(!qctx->is_zone); + + if (qctx->db != NULL) + dns_db_detach(&qctx->db); + + /* + * If the cache doesn't even have the root NS, + * try to get that from the hints DB. + */ + if (qctx->client->view->hints != NULL) { + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, qctx->client, NULL); + + dns_db_attach(qctx->client->view->hints, &qctx->db); + result = dns_db_findext(qctx->db, dns_rootname, + NULL, dns_rdatatype_ns, + 0, qctx->client->now, &qctx->node, + qctx->fname, &cm, &ci, + qctx->rdataset, qctx->sigrdataset); + } else { + /* We have no hints. */ + result = ISC_R_FAILURE; + } + if (result != ISC_R_SUCCESS) { + /* + * Nonsensical root hints may require cleanup. + */ + qctx_clean(qctx); + + /* + * We don't have any root server hints, but + * we may have working forwarders, so try to + * recurse anyway. + */ + if (RECURSIONOK(qctx->client)) { + INSIST(!REDIRECT(qctx->client)); + result = query_recurse(qctx->client, qctx->qtype, + qctx->client->query.qname, + NULL, NULL, qctx->resuming); + if (result == ISC_R_SUCCESS) { + qctx->client->query.attributes |= + NS_QUERYATTR_RECURSING; + if (qctx->dns64) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64; + if (qctx->dns64_exclude) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64EXCLUDE; + } else + RECURSE_ERROR(qctx, result); + return (query_done(qctx)); + } else { + /* Unable to give root server referral. */ + CCTRACE(ISC_LOG_ERROR, + "unable to give root server referral"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + } + + return (query_delegation(qctx)); +} + +/*% + * Handle a delegation response from an authoritative lookup. This + * may trigger additional lookups, e.g. from the cache database to + * see if we have a better answer; if that is not allowed, return the + * delegation to the client and call query_done(). + */ +static isc_result_t +query_zone_delegation(query_ctx_t *qctx) { + isc_result_t result; + dns_rdataset_t **sigrdatasetp = NULL; + + /* + * If the query type is DS, look to see if we are + * authoritative for the child zone + */ + if (!RECURSIONOK(qctx->client) && + (qctx->options & DNS_GETDB_NOEXACT) != 0 && + qctx->qtype == dns_rdatatype_ds) + { + dns_db_t *tdb = NULL; + dns_zone_t *tzone = NULL; + dns_dbversion_t *tversion = NULL; + result = query_getzonedb(qctx->client, + qctx->client->query.qname, + qctx->qtype, + DNS_GETDB_PARTIAL, + &tzone, &tdb, + &tversion); + if (result != ISC_R_SUCCESS) { + if (tdb != NULL) + dns_db_detach(&tdb); + if (tzone != NULL) + dns_zone_detach(&tzone); + } else { + qctx->options &= ~DNS_GETDB_NOEXACT; + query_putrdataset(qctx->client, + &qctx->rdataset); + if (qctx->sigrdataset != NULL) + query_putrdataset(qctx->client, + &qctx->sigrdataset); + if (qctx->fname != NULL) + query_releasename(qctx->client, + &qctx->fname); + if (qctx->node != NULL) + dns_db_detachnode(qctx->db, + &qctx->node); + if (qctx->db != NULL) + dns_db_detach(&qctx->db); + if (qctx->zone != NULL) + dns_zone_detach(&qctx->zone); + qctx->version = NULL; + RESTORE(qctx->version, tversion); + RESTORE(qctx->db, tdb); + RESTORE(qctx->zone, tzone); + qctx->authoritative = ISC_TRUE; + + return (query_lookup(qctx)); + } + } + + if (USECACHE(qctx->client) && RECURSIONOK(qctx->client)) { + /* + * We might have a better answer or delegation in the + * cache. We'll remember the current values of fname, + * rdataset, and sigrdataset. We'll then go looking for + * QNAME in the cache. If we find something better, we'll + * use it instead. If not, then query_lookup() calls + * query_notfound() which calls query_delegation(), and + * we'll restore these values there. + */ + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + dns_db_detachnode(qctx->db, &qctx->node); + SAVE(qctx->zdb, qctx->db); + SAVE(qctx->zfname, qctx->fname); + SAVE(qctx->zversion, qctx->version); + SAVE(qctx->zrdataset, qctx->rdataset); + SAVE(qctx->zsigrdataset, qctx->sigrdataset); + dns_db_attach(qctx->client->view->cachedb, &qctx->db); + qctx->is_zone = ISC_FALSE; + + return (query_lookup(qctx)); + } + + /* + * qctx->fname could be released in + * query_addrrset(), so save a copy of it + * here in case we need it + */ + dns_fixedname_init(&qctx->dsname); + dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->dsname), NULL); + + /* + * If we don't have a cache, this is the best + * answer. + * + * If the client is making a nonrecursive + * query we always give out the authoritative + * delegation. This way even if we get + * junk in our cache, we won't fail in our + * role as the delegating authority if another + * nameserver asks us about a delegated + * subzone. + * + * We enable the retrieval of glue for this + * database by setting client->query.gluedb. + */ + qctx->client->query.gluedb = qctx->db; + qctx->client->query.isreferral = ISC_TRUE; + /* + * We must ensure NOADDITIONAL is off, + * because the generation of + * additional data is required in + * delegations. + */ + qctx->client->query.attributes &= + ~NS_QUERYATTR_NOADDITIONAL; + if (qctx->sigrdataset != NULL) + sigrdatasetp = &qctx->sigrdataset; + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, sigrdatasetp, + qctx->dbuf, DNS_SECTION_AUTHORITY); + qctx->client->query.gluedb = NULL; + + /* + * Add a DS if needed + */ + query_addds(qctx); + + return (query_done(qctx)); +} + +/*% + * Handle delegation responses, including root referrals. + * + * If the delegation was returned from authoritative data, + * call query_zone_delgation(). Otherwise, we can start + * recursion if allowed; or else return the delegation to the + * client and call query_done(). + */ +static isc_result_t +query_delegation(query_ctx_t *qctx) { + isc_result_t result; + dns_rdataset_t **sigrdatasetp = NULL; + + qctx->authoritative = ISC_FALSE; + + if (qctx->is_zone) { + return (query_zone_delegation(qctx)); + } + + if (qctx->zfname != NULL && + (!dns_name_issubdomain(qctx->fname, qctx->zfname) || + (qctx->is_staticstub_zone && + dns_name_equal(qctx->fname, qctx->zfname)))) + { + /* + * In the following cases use "authoritative" + * data instead of the cache delegation: + * 1. We've already got a delegation from + * authoritative data, and it is better + * than what we found in the cache. + * (See the comment above.) + * 2. The query name matches the origin name + * of a static-stub zone. This needs to be + * considered for the case where the NS of + * the static-stub zone and the cached NS + * are different. We still need to contact + * the nameservers configured in the + * static-stub zone. + */ + query_releasename(qctx->client, &qctx->fname); + + /* + * We've already done query_keepname() on + * qctx->zfname, so we must set dbuf to NULL to + * prevent query_addrrset() from trying to + * call query_keepname() again. + */ + qctx->dbuf = NULL; + query_putrdataset(qctx->client, &qctx->rdataset); + if (qctx->sigrdataset != NULL) + query_putrdataset(qctx->client, + &qctx->sigrdataset); + qctx->version = NULL; + + RESTORE(qctx->fname, qctx->zfname); + RESTORE(qctx->version, qctx->zversion); + RESTORE(qctx->rdataset, qctx->zrdataset); + RESTORE(qctx->sigrdataset, qctx->zsigrdataset); + + /* + * We don't clean up zdb here because we + * may still need it. It will get cleaned + * up by the main cleanup code in query_done(). + */ + } + + if (RECURSIONOK(qctx->client)) { + /* + * We have a delegation and recursion is allowed, + * so we call query_recurse() to follow it. + * This phase of the query processing is done; + * we'll resume via fetch_callback() and + * query_resume() when the recursion is complete. + */ + dns_name_t *qname = qctx->client->query.qname; + + INSIST(!REDIRECT(qctx->client)); + + if (dns_rdatatype_atparent(qctx->type)) { + /* + * Parent is recursive for this rdata + * type (i.e., DS) + */ + result = query_recurse(qctx->client, + qctx->qtype, qname, + NULL, NULL, + qctx->resuming); + } else if (qctx->dns64) { + /* + * Look up an A record so we can + * synthesize DNS64 + */ + result = query_recurse(qctx->client, + dns_rdatatype_a, qname, + NULL, NULL, + qctx->resuming); + } else { + /* + * Any other recursion + */ + result = query_recurse(qctx->client, + qctx->qtype, qname, + qctx->fname, + qctx->rdataset, + qctx->resuming); + } + if (result == ISC_R_SUCCESS) { + qctx->client->query.attributes |= + NS_QUERYATTR_RECURSING; + if (qctx->dns64) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64; + if (qctx->dns64_exclude) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64EXCLUDE; + } else if (result == DNS_R_DUPLICATE || result == DNS_R_DROP) { + QUERY_ERROR(qctx, result); + } else { + RECURSE_ERROR(qctx, result); + } + + return (query_done(qctx)); + } + + /* + * We have a delegation but recursion is not + * allowed, so return the delegation to the client. + * + * qctx->fname could be released in + * query_addrrset(), so save a copy of it + * here in case we need it + */ + dns_fixedname_init(&qctx->dsname); + dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->dsname), NULL); + + /* + * This is the best answer. + */ + qctx->client->query.attributes |= NS_QUERYATTR_CACHEGLUEOK; + qctx->client->query.gluedb = qctx->zdb; + qctx->client->query.isreferral = ISC_TRUE; + + /* + * We must ensure NOADDITIONAL is off, + * because the generation of + * additional data is required in + * delegations. + */ + qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; + if (qctx->sigrdataset != NULL) { + sigrdatasetp = &qctx->sigrdataset; + } + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, sigrdatasetp, + qctx->dbuf, DNS_SECTION_AUTHORITY); + qctx->client->query.gluedb = NULL; + qctx->client->query.attributes &= ~NS_QUERYATTR_CACHEGLUEOK; + + /* + * Add a DS if needed + */ + query_addds(qctx); + + return (query_done(qctx)); +} + +/*% + * Add a DS record if needed. + */ +static void +query_addds(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + dns_fixedname_t fixed; + dns_name_t *fname = NULL; + dns_name_t *rname = NULL; + dns_name_t *name; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + isc_buffer_t *dbuf, b; + isc_result_t result; + unsigned int count; + + CTRACE(ISC_LOG_DEBUG(3), "query_addds"); + + /* + * DS not needed. + */ + if (!WANTDNSSEC(client)) { + return; + } + + /* + * We'll need some resources... + */ + rdataset = query_newrdataset(client); + sigrdataset = query_newrdataset(client); + if (rdataset == NULL || sigrdataset == NULL) + goto cleanup; + + /* + * Look for the DS record, which may or may not be present. + */ + result = dns_db_findrdataset(qctx->db, qctx->node, qctx->version, + dns_rdatatype_ds, 0, client->now, + rdataset, sigrdataset); + /* + * If we didn't find it, look for an NSEC. + */ + if (result == ISC_R_NOTFOUND) + result = dns_db_findrdataset(qctx->db, qctx->node, + qctx->version, + dns_rdatatype_nsec, + 0, client->now, + rdataset, sigrdataset); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) + goto addnsec3; + if (!dns_rdataset_isassociated(rdataset) || + !dns_rdataset_isassociated(sigrdataset)) + goto addnsec3; + + /* + * We've already added the NS record, so if the name's not there, + * we have other problems. Use this name rather than calling + * query_addrrset(). + */ + result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) + goto cleanup; + + rname = NULL; + dns_message_currentname(client->message, DNS_SECTION_AUTHORITY, + &rname); + result = dns_message_findtype(rname, dns_rdatatype_ns, 0, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + + ISC_LIST_APPEND(rname->list, rdataset, link); + ISC_LIST_APPEND(rname->list, sigrdataset, link); + rdataset = NULL; + sigrdataset = NULL; + return; + + addnsec3: + if (!dns_db_iszone(qctx->db)) + goto cleanup; + /* + * Add the NSEC3 which proves the DS does not exist. + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + dns_fixedname_init(&fixed); + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(sigrdataset)) + dns_rdataset_disassociate(sigrdataset); + name = dns_fixedname_name(&qctx->dsname); + query_findclosestnsec3(name, qctx->db, qctx->version, client, rdataset, + sigrdataset, fname, ISC_TRUE, + dns_fixedname_name(&fixed)); + if (!dns_rdataset_isassociated(rdataset)) + goto cleanup; + query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, + DNS_SECTION_AUTHORITY); + /* + * Did we find the closest provable encloser instead? + * If so add the nearest to the closest provable encloser. + */ + if (!dns_name_equal(name, dns_fixedname_name(&fixed))) { + count = dns_name_countlabels(dns_fixedname_name(&fixed)) + 1; + dns_name_getlabelsequence(name, + dns_name_countlabels(name) - count, + count, dns_fixedname_name(&fixed)); + fixfname(client, &fname, &dbuf, &b); + fixrdataset(client, &rdataset); + fixrdataset(client, &sigrdataset); + if (fname == NULL || rdataset == NULL || sigrdataset == NULL) + goto cleanup; + query_findclosestnsec3(dns_fixedname_name(&fixed), + qctx->db, qctx->version, client, + rdataset, sigrdataset, fname, + ISC_FALSE, NULL); + if (!dns_rdataset_isassociated(rdataset)) + goto cleanup; + query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, + DNS_SECTION_AUTHORITY); + } + + cleanup: + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) + query_putrdataset(client, &sigrdataset); + if (fname != NULL) + query_releasename(client, &fname); +} + +/*% + * Handle authoritative NOERROR/NODATA responses. + */ +static isc_result_t +query_nodata(query_ctx_t *qctx, isc_result_t result) { +#ifdef dns64_bis_return_excluded_addresses + if (qctx->dns64) +#else + if (qctx->dns64 && !qctx->dns64_exclude) +#endif + { + isc_buffer_t b; + /* + * Restore the answers from the previous AAAA lookup. + */ + if (qctx->rdataset != NULL) + query_putrdataset(qctx->client, &qctx->rdataset); + if (qctx->sigrdataset != NULL) + query_putrdataset(qctx->client, &qctx->sigrdataset); + RESTORE(qctx->rdataset, qctx->client->query.dns64_aaaa); + RESTORE(qctx->sigrdataset, qctx->client->query.dns64_sigaaaa); + if (qctx->fname == NULL) { + qctx->dbuf = query_getnamebuf(qctx->client); + if (qctx->dbuf == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_nodata: " + "query_getnamebuf failed (3)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx));; + } + qctx->fname = query_newname(qctx->client, + qctx->dbuf, &b); + if (qctx->fname == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_nodata: " + "query_newname failed (3)"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx));; + } + } + dns_name_copy(qctx->client->query.qname, qctx->fname, NULL); + qctx->dns64 = ISC_FALSE; +#ifdef dns64_bis_return_excluded_addresses + /* + * Resume the diverted processing of the AAAA response? + */ + if (qctx->dns64_exclude) + return (query_prepresponse(qctx)); +#endif + } else if ((result == DNS_R_NXRRSET || + result == DNS_R_NCACHENXRRSET) && + !ISC_LIST_EMPTY(qctx->client->view->dns64) && + qctx->client->message->rdclass == dns_rdataclass_in && + qctx->qtype == dns_rdatatype_aaaa) + { + /* + * Look to see if there are A records for this name. + */ + switch (result) { + case DNS_R_NCACHENXRRSET: + /* + * This is from the negative cache; if the ttl is + * zero, we need to work out whether we have just + * decremented to zero or there was no negative + * cache ttl in the answer. + */ + if (qctx->rdataset->ttl != 0) { + qctx->client->query.dns64_ttl = + qctx->rdataset->ttl; + break; + } + if (dns_rdataset_first(qctx->rdataset) == ISC_R_SUCCESS) + qctx->client->query.dns64_ttl = 0; + break; + case DNS_R_NXRRSET: + qctx->client->query.dns64_ttl = + dns64_ttl(qctx->db, qctx->version); + break; + default: + INSIST(0); + } + + SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset); + SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset); + query_releasename(qctx->client, &qctx->fname); + dns_db_detachnode(qctx->db, &qctx->node); + qctx->type = qctx->qtype = dns_rdatatype_a; + qctx->rpz_st = qctx->client->query.rpz_st; + if (qctx->rpz_st != NULL) { + /* + * Arrange for RPZ rewriting of any A records. + */ + if ((qctx->rpz_st->state & DNS_RPZ_REWRITTEN) != 0) + qctx->is_zone = qctx->rpz_st->q.is_zone; + rpz_st_clear(qctx->client); + } + qctx->dns64 = ISC_TRUE; + return (query_lookup(qctx)); + } + + if (qctx->is_zone) { + return (query_sign_nodata(qctx)); + } else { + /* + * We don't call query_addrrset() because we don't need any + * of its extra features (and things would probably break!). + */ + if (dns_rdataset_isassociated(qctx->rdataset)) { + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + dns_message_addname(qctx->client->message, + qctx->fname, + DNS_SECTION_AUTHORITY); + ISC_LIST_APPEND(qctx->fname->list, + qctx->rdataset, link); + qctx->fname = NULL; + qctx->rdataset = NULL; + } + } + + return (query_done(qctx)); +} + +/*% + * Add RRSIGs for NOERROR/NODATA responses when answering authoritatively. + */ +isc_result_t +query_sign_nodata(query_ctx_t *qctx) { + dns_section_t section; + isc_result_t result; + /* + * Look for a NSEC3 record if we don't have a NSEC record. + */ + if (qctx->redirected) + return (query_done(qctx)); + if (!dns_rdataset_isassociated(qctx->rdataset) && + WANTDNSSEC(qctx->client)) + { + if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { + dns_name_t *found; + dns_name_t *qname; + dns_fixedname_t fixed; + isc_buffer_t b; + + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + qname = qctx->client->query.qname; + + query_findclosestnsec3(qname, qctx->db, qctx->version, + qctx->client, qctx->rdataset, + qctx->sigrdataset, qctx->fname, + ISC_TRUE, found); + /* + * Did we find the closest provable encloser + * instead? If so add the nearest to the + * closest provable encloser. + */ + if (dns_rdataset_isassociated(qctx->rdataset) && + !dns_name_equal(qname, found) && + !(ns_g_nonearest && + qctx->qtype != dns_rdatatype_ds)) + { + unsigned int count; + unsigned int skip; + + /* + * Add the closest provable encloser. + */ + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, + &qctx->sigrdataset, + qctx->dbuf, + DNS_SECTION_AUTHORITY); + + count = dns_name_countlabels(found) + + 1; + skip = dns_name_countlabels(qname) - + count; + dns_name_getlabelsequence(qname, skip, + count, + found); + + fixfname(qctx->client, &qctx->fname, + &qctx->dbuf, &b); + fixrdataset(qctx->client, &qctx->rdataset); + fixrdataset(qctx->client, &qctx->sigrdataset); + if (qctx->fname == NULL || + qctx->rdataset == NULL || + qctx->sigrdataset == NULL) { + CCTRACE(ISC_LOG_ERROR, + "query_sign_nodata: " + "failure getting " + "closest encloser"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + /* + * 'nearest' doesn't exist so + * 'exist' is set to ISC_FALSE. + */ + query_findclosestnsec3(found, qctx->db, + qctx->version, + qctx->client, + qctx->rdataset, + qctx->sigrdataset, + qctx->fname, + ISC_FALSE, + NULL); + } + } else { + query_releasename(qctx->client, &qctx->fname); + query_addwildcardproof(qctx, ISC_FALSE, ISC_TRUE); + } + } + if (dns_rdataset_isassociated(qctx->rdataset)) { + /* + * If we've got a NSEC record, we need to save the + * name now because we're going call query_addsoa() + * below, and it needs to use the name buffer. + */ + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + } else if (qctx->fname != NULL) { + /* + * We're not going to use fname, and need to release + * our hold on the name buffer so query_addsoa() + * may use it. + */ + query_releasename(qctx->client, &qctx->fname); + } + + /* + * Add SOA to the additional section if generated by a RPZ + * rewrite. + */ + section = qctx->nxrewrite ? DNS_SECTION_ADDITIONAL + : DNS_SECTION_AUTHORITY; + + result = query_addsoa(qctx, ISC_UINT32_MAX, section); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(qctx, result); + return (query_done(qctx)); + } + + /* + * Add NSEC record if we found one. + */ + if (WANTDNSSEC(qctx->client) && + dns_rdataset_isassociated(qctx->rdataset)) + { + query_addnxrrsetnsec(qctx); + } + + return (query_done(qctx)); +} + +static void +query_addnxrrsetnsec(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + dns_rdata_t sigrdata; + dns_rdata_rrsig_t sig; + unsigned int labels; + isc_buffer_t *dbuf, b; + dns_name_t *fname; + + if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) { + query_addrrset(client, &qctx->fname, + &qctx->rdataset, &qctx->sigrdataset, + NULL, DNS_SECTION_AUTHORITY); + return; + } + + if (qctx->sigrdataset == NULL || + !dns_rdataset_isassociated(qctx->sigrdataset)) + { + return; + } + + if (dns_rdataset_first(qctx->sigrdataset) != ISC_R_SUCCESS) { + return; + } + + dns_rdata_init(&sigrdata); + dns_rdataset_current(qctx->sigrdataset, &sigrdata); + if (dns_rdata_tostruct(&sigrdata, &sig, NULL) != ISC_R_SUCCESS) { + return; + } + + labels = dns_name_countlabels(qctx->fname); + if ((unsigned int)sig.labels + 1 >= labels) { + return; + } + + query_addwildcardproof(qctx, ISC_TRUE, ISC_FALSE); + + /* + * We'll need some resources... + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { + return; + } + + fname = query_newname(client, dbuf, &b); + if (fname == NULL) { + return; + } + + dns_name_split(qctx->fname, sig.labels + 1, NULL, fname); + /* This will succeed, since we've stripped labels. */ + RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname, fname, fname, + NULL) == ISC_R_SUCCESS); + query_addrrset(client, &fname, &qctx->rdataset, &qctx->sigrdataset, + dbuf, DNS_SECTION_AUTHORITY); +} + +/*% + * Handle NXDOMAIN and empty wildcard responses. + */ +static isc_result_t +query_nxdomain(query_ctx_t *qctx, isc_boolean_t empty_wild) { + dns_section_t section; + isc_uint32_t ttl; + isc_result_t result; + + INSIST(qctx->is_zone || REDIRECT(qctx->client)); + + if (!empty_wild) { + result = query_redirect(qctx); + if (result != ISC_R_COMPLETE) + return (result); + } + + if (dns_rdataset_isassociated(qctx->rdataset)) { + /* + * If we've got a NSEC record, we need to save the + * name now because we're going call query_addsoa() + * below, and it needs to use the name buffer. + */ + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + } else if (qctx->fname != NULL) { + /* + * We're not going to use fname, and need to release + * our hold on the name buffer so query_addsoa() + * may use it. + */ + query_releasename(qctx->client, &qctx->fname); + } + + /* + * Add SOA to the additional section if generated by a + * RPZ rewrite. + * + * If the query was for a SOA record force the + * ttl to zero so that it is possible for clients to find + * the containing zone of an arbitrary name with a stub + * resolver and not have it cached. + */ + section = qctx->nxrewrite ? DNS_SECTION_ADDITIONAL + : DNS_SECTION_AUTHORITY; + ttl = ISC_UINT32_MAX; + if (!qctx->nxrewrite && qctx->qtype == dns_rdatatype_soa && + qctx->zone != NULL && dns_zone_getzeronosoattl(qctx->zone)) + { + ttl = 0; + } + result = query_addsoa(qctx, ttl, section); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(qctx, result); + return (query_done(qctx)); + } + + if (WANTDNSSEC(qctx->client)) { + /* + * Add NSEC record if we found one. + */ + if (dns_rdataset_isassociated(qctx->rdataset)) + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, &qctx->sigrdataset, + NULL, DNS_SECTION_AUTHORITY); + query_addwildcardproof(qctx, ISC_FALSE, ISC_FALSE); + } + + /* + * Set message rcode. + */ + if (empty_wild) + qctx->client->message->rcode = dns_rcode_noerror; + else + qctx->client->message->rcode = dns_rcode_nxdomain; + + return (query_done(qctx)); +} + +/* + * Handle both types of NXDOMAIN redirection, calling redirect() + * (which implements type redirect zones) and redirect2() (which + * implements recursive nxdomain-redirect lookups). + * + * Any result code other than ISC_R_COMPLETE means redirection was + * successful and the result code should be returned up the call stack. + * + * ISC_R_COMPLETE means we reached the end of this function without + * redirecting, so query processing should continue past it. + */ +static isc_result_t +query_redirect(query_ctx_t *qctx) { + isc_result_t result; + + result = redirect(qctx->client, qctx->fname, qctx->rdataset, + &qctx->node, &qctx->db, &qctx->version, + qctx->type); + switch (result) { + case ISC_R_SUCCESS: + inc_stats(qctx->client, + dns_nsstatscounter_nxdomainredirect); + return (query_prepresponse(qctx)); + case DNS_R_NXRRSET: + qctx->redirected = ISC_TRUE; + qctx->is_zone = ISC_TRUE; + return (query_nodata(qctx, DNS_R_NXRRSET)); + case DNS_R_NCACHENXRRSET: + qctx->redirected = ISC_TRUE; + qctx->is_zone = ISC_FALSE; + return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); + default: + break; + } + + result = redirect2(qctx->client, qctx->fname, qctx->rdataset, + &qctx->node, &qctx->db, &qctx->version, + qctx->type, &qctx->is_zone); + switch (result) { + case ISC_R_SUCCESS: + inc_stats(qctx->client, + dns_nsstatscounter_nxdomainredirect); + return (query_prepresponse(qctx)); + case DNS_R_CONTINUE: + inc_stats(qctx->client, + dns_nsstatscounter_nxdomainredirect_rlookup); + SAVE(qctx->client->query.redirect.db, qctx->db); + SAVE(qctx->client->query.redirect.node, qctx->node); + SAVE(qctx->client->query.redirect.zone, qctx->zone); + qctx->client->query.redirect.qtype = qctx->qtype; + INSIST(qctx->rdataset != NULL); + SAVE(qctx->client->query.redirect.rdataset, qctx->rdataset); + SAVE(qctx->client->query.redirect.sigrdataset, + qctx->sigrdataset); + qctx->client->query.redirect.result = DNS_R_NCACHENXDOMAIN; + dns_name_copy(qctx->fname, qctx->client->query.redirect.fname, + NULL); + qctx->client->query.redirect.authoritative = + qctx->authoritative; + qctx->client->query.redirect.is_zone = qctx->is_zone; + return (query_done(qctx)); + case DNS_R_NXRRSET: + qctx->redirected = ISC_TRUE; + qctx->is_zone = ISC_TRUE; + return (query_nodata(qctx, DNS_R_NXRRSET)); + case DNS_R_NCACHENXRRSET: + qctx->redirected = ISC_TRUE; + qctx->is_zone = ISC_FALSE; + return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); + default: + break; + } + + return (ISC_R_COMPLETE); +} + +/*% + * Handle negative cache responses, DNS_R_NCACHENXRRSET or + * DNS_R_NCACHENXDOMAIN. (Note: may also be called with result + * set to DNS_R_NXDOMAIN when handling DNS64 lookups.) + */ +static isc_result_t +query_ncache(query_ctx_t *qctx, isc_result_t result) { + INSIST(!qctx->is_zone); + INSIST(result == DNS_R_NCACHENXDOMAIN || + result == DNS_R_NCACHENXRRSET || + result == DNS_R_NXDOMAIN); + + qctx->authoritative = ISC_FALSE; + + if (result == DNS_R_NCACHENXDOMAIN) { + /* + * Set message rcode. (This is not done when + * result == DNS_R_NXDOMAIN because that means we're + * being called after a DNS64 lookup and don't want + * to update the rcode now.) + */ + qctx->client->message->rcode = dns_rcode_nxdomain; + + /* Look for RFC 1918 leakage from Internet. */ + if (qctx->qtype == dns_rdatatype_ptr && + qctx->client->message->rdclass == dns_rdataclass_in && + dns_name_countlabels(qctx->fname) == 7) + { + warn_rfc1918(qctx->client, qctx->fname, qctx->rdataset); + } + } + + return (query_nodata(qctx, result)); +} + +/* + * Handle CNAME responses. + */ +static isc_result_t +query_cname(query_ctx_t *qctx) { + isc_result_t result; + dns_name_t *tname; + dns_rdataset_t *trdataset; + dns_rdataset_t **sigrdatasetp = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_cname_t cname; + + /* + * If we have a zero ttl from the cache refetch it. + */ + if (!qctx->is_zone && qctx->event == NULL && + qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client)) + { + qctx_clean(qctx); + + INSIST(!REDIRECT(qctx->client)); + + result = query_recurse(qctx->client, qctx->qtype, + qctx->client->query.qname, + NULL, NULL, qctx->resuming); + if (result == ISC_R_SUCCESS) { + qctx->client->query.attributes |= + NS_QUERYATTR_RECURSING; + if (qctx->dns64) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64; + if (qctx->dns64_exclude) + qctx->client->query.attributes |= + NS_QUERYATTR_DNS64EXCLUDE; + } else { + RECURSE_ERROR(qctx, result); + } + + return (query_done(qctx)); + } + + /* + * Keep a copy of the rdataset. We have to do this because + * query_addrrset may clear 'rdataset' (to prevent the + * cleanup code from cleaning it up). + */ + trdataset = qctx->rdataset; + + /* + * Add the CNAME to the answer section. + */ + if (qctx->sigrdataset != NULL) + sigrdatasetp = &qctx->sigrdataset; + + if (WANTDNSSEC(qctx->client) && + (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) + { + dns_fixedname_init(&qctx->wildcardname); + dns_name_copy(qctx->fname, + dns_fixedname_name(&qctx->wildcardname), NULL); + qctx->need_wildcardproof = ISC_TRUE; + } + + if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { + qctx->noqname = qctx->rdataset; + } else { + qctx->noqname = NULL; + } + + if (!qctx->is_zone && RECURSIONOK(qctx->client)) + query_prefetch(qctx->client, qctx->fname, qctx->rdataset); + + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, sigrdatasetp, qctx->dbuf, + DNS_SECTION_ANSWER); + + query_addnoqnameproof(qctx); + + /* + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. + */ + qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; + + /* + * Reset qname to be the target name of the CNAME and restart + * the query. + */ + tname = NULL; + result = dns_message_gettempname(qctx->client->message, &tname); + if (result != ISC_R_SUCCESS) + return (query_done(qctx)); + + result = dns_rdataset_first(trdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + + dns_rdataset_current(trdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + + dns_name_init(tname, NULL); + result = dns_name_dup(&cname.cname, qctx->client->mctx, tname); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + dns_rdata_freestruct(&cname); + return (query_done(qctx)); + } + + dns_rdata_freestruct(&cname); + ns_client_qnamereplace(qctx->client, tname); + qctx->want_restart = ISC_TRUE; + if (!WANTRECURSION(qctx->client)) + qctx->options |= DNS_GETDB_NOLOG; + + query_addauth(qctx); + + return (query_done(qctx)); +} + +/* + * Handle DNAME responses. + */ +static isc_result_t +query_dname(query_ctx_t *qctx) { + dns_name_t *tname, *prefix; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dname_t dname; + dns_fixedname_t fixed; + dns_rdataset_t *trdataset; + dns_rdataset_t **sigrdatasetp = NULL; + dns_namereln_t namereln; + isc_buffer_t b; + int order; + isc_result_t result; + unsigned int nlabels; + + /* + * Compare the current qname to the found name. We need + * to know how many labels and bits are in common because + * we're going to have to split qname later on. + */ + namereln = dns_name_fullcompare(qctx->client->query.qname, qctx->fname, + &order, &nlabels); + INSIST(namereln == dns_namereln_subdomain); + + /* + * Keep a copy of the rdataset. We have to do this because + * query_addrrset may clear 'rdataset' (to prevent the + * cleanup code from cleaning it up). + */ + trdataset = qctx->rdataset; + + /* + * Add the DNAME to the answer section. + */ + if (qctx->sigrdataset != NULL) + sigrdatasetp = &qctx->sigrdataset; + + if (WANTDNSSEC(qctx->client) && + (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) + { + dns_fixedname_init(&qctx->wildcardname); + dns_name_copy(qctx->fname, + dns_fixedname_name(&qctx->wildcardname), NULL); + qctx->need_wildcardproof = ISC_TRUE; + } + + if (!qctx->is_zone && RECURSIONOK(qctx->client)) + query_prefetch(qctx->client, qctx->fname, qctx->rdataset); + query_addrrset(qctx->client, &qctx->fname, + &qctx->rdataset, sigrdatasetp, qctx->dbuf, + DNS_SECTION_ANSWER); + + /* + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. + */ + qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; + + /* + * Get the target name of the DNAME. + */ + tname = NULL; + result = dns_message_gettempname(qctx->client->message, &tname); + if (result != ISC_R_SUCCESS) + return (query_done(qctx)); + + result = dns_rdataset_first(trdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + + dns_rdataset_current(trdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + + dns_name_clone(&dname.dname, tname); + dns_rdata_freestruct(&dname); + + /* + * Construct the new qname consisting of + * . + */ + dns_fixedname_init(&fixed); + prefix = dns_fixedname_name(&fixed); + dns_name_split(qctx->client->query.qname, nlabels, prefix, NULL); + INSIST(qctx->fname == NULL); + qctx->dbuf = query_getnamebuf(qctx->client); + if (qctx->dbuf == NULL) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + qctx->fname = query_newname(qctx->client, qctx->dbuf, &b); + if (qctx->fname == NULL) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); + } + result = dns_name_concatenate(prefix, tname, qctx->fname, NULL); + dns_message_puttempname(qctx->client->message, &tname); + + /* + * RFC2672, section 4.1, subsection 3c says + * we should return YXDOMAIN if the constructed + * name would be too long. + */ + if (result == DNS_R_NAMETOOLONG) + qctx->client->message->rcode = dns_rcode_yxdomain; + if (result != ISC_R_SUCCESS) + return (query_done(qctx)); + + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + + /* + * Synthesize a CNAME consisting of + * CNAME + * with + * + * Synthesize a CNAME so old old clients that don't understand + * DNAME can chain. + * + * We do not try to synthesize a signature because we hope + * that security aware servers will understand DNAME. Also, + * even if we had an online key, making a signature + * on-the-fly is costly, and not really legitimate anyway + * since the synthesized CNAME is NOT in the zone. + */ + result = query_addcname(qctx, trdataset->trust, trdataset->ttl); + if (result != ISC_R_SUCCESS) + return (query_done(qctx)); + + /* + * Switch to the new qname and restart. + */ + ns_client_qnamereplace(qctx->client, qctx->fname); + qctx->fname = NULL; + qctx->want_restart = ISC_TRUE; + if (!WANTRECURSION(qctx->client)) + qctx->options |= DNS_GETDB_NOLOG; + + query_addauth(qctx); + + return (query_done(qctx)); +} + +/*% + * Add CNAME to repsonse. + */ +static isc_result_t +query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl) { + ns_client_t *client = qctx->client; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdata_t *rdata = NULL; + isc_region_t r; + dns_name_t *aname = NULL; + isc_result_t result; + + result = dns_message_gettempname(client->message, &aname); + if (result != ISC_R_SUCCESS) + return (result); + result = dns_name_dup(client->query.qname, client->mctx, aname); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + return (result); + } + + result = dns_message_gettemprdatalist(client->message, &rdatalist); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + return (result); + } + + result = dns_message_gettemprdata(client->message, &rdata); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + dns_message_puttemprdatalist(client->message, &rdatalist); + return (result); + } + + result = dns_message_gettemprdataset(client->message, &rdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(client->message, &aname); + dns_message_puttemprdatalist(client->message, &rdatalist); + dns_message_puttemprdata(client->message, &rdata); + return (result); + } + + rdatalist->type = dns_rdatatype_cname; + rdatalist->rdclass = client->message->rdclass; + rdatalist->ttl = ttl; + + dns_name_toregion(qctx->fname, &r); + rdata->data = r.base; + rdata->length = r.length; + rdata->rdclass = client->message->rdclass; + rdata->type = dns_rdatatype_cname; + + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) + == ISC_R_SUCCESS); + rdataset->trust = trust; + dns_rdataset_setownercase(rdataset, aname); + + query_addrrset(client, &aname, &rdataset, NULL, NULL, + DNS_SECTION_ANSWER); + if (rdataset != NULL) { + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + dns_message_puttemprdataset(client->message, &rdataset); + } + if (aname != NULL) + dns_message_puttempname(client->message, &aname); + + return (ISC_R_SUCCESS); +} + +/*% + * Prepare to respond: determine whether a wildcard proof is needed, + * check whether to filter AAAA answers, then hand off to query_respond() + * or (for type ANY queries) query_respond_any(). + */ +static isc_result_t +query_prepresponse(query_ctx_t *qctx) { + if (WANTDNSSEC(qctx->client) && + (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0) + { + dns_fixedname_init(&qctx->wildcardname); + dns_name_copy(qctx->fname, + dns_fixedname_name(&qctx->wildcardname), NULL); + qctx->need_wildcardproof = ISC_TRUE; } #ifdef ALLOW_FILTER_AAAA @@ -8322,523 +8714,474 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * clients if there is an A; filter-aaaa-on-v6 option does the same * for IPv6 clients. */ - client->filter_aaaa = dns_aaaa_ok; - if (client->view->v4_aaaa != dns_aaaa_ok || - client->view->v6_aaaa != dns_aaaa_ok) + qctx->client->filter_aaaa = dns_aaaa_ok; + if (qctx->client->view->v4_aaaa != dns_aaaa_ok || + qctx->client->view->v6_aaaa != dns_aaaa_ok) { - result = ns_client_checkaclsilent(client, NULL, - client->view->aaaa_acl, + isc_result_t result; + result = ns_client_checkaclsilent(qctx->client, NULL, + qctx->client->view->aaaa_acl, ISC_TRUE); if (result == ISC_R_SUCCESS && - client->view->v4_aaaa != dns_aaaa_ok && - is_v4_client(client)) - client->filter_aaaa = client->view->v4_aaaa; + qctx->client->view->v4_aaaa != dns_aaaa_ok && + is_v4_client(qctx->client)) + qctx->client->filter_aaaa = qctx->client->view->v4_aaaa; else if (result == ISC_R_SUCCESS && - client->view->v6_aaaa != dns_aaaa_ok && - is_v6_client(client)) - client->filter_aaaa = client->view->v6_aaaa; + qctx->client->view->v6_aaaa != dns_aaaa_ok && + is_v6_client(qctx->client)) + qctx->client->filter_aaaa = qctx->client->view->v6_aaaa; } #endif - if (type == dns_rdatatype_any) { - /* - * For minimal-any, we only add records that - * match this type or cover this type. - */ - dns_rdatatype_t onetype = 0; -#ifdef ALLOW_FILTER_AAAA - isc_boolean_t have_aaaa, have_a, have_sig; + if (qctx->type == dns_rdatatype_any) { + return (query_respond_any(qctx)); + } else { + return (query_respond(qctx)); + } +} - /* - * If we are not authoritative, assume there is a A - * even in if it is not in our cache. This assumption could - * be wrong but it is a good bet. - */ - have_aaaa = ISC_FALSE; - have_a = !authoritative; - have_sig = ISC_FALSE; -#endif - /* - * XXXRTH Need to handle zonecuts with special case - * code. - */ - n = 0; - rdsiter = NULL; - result = dns_db_allrdatasets(db, node, version, 0, &rdsiter); - if (result != ISC_R_SUCCESS) { - CTRACE(ISC_LOG_ERROR, - "query_find: type any; allrdatasets failed"); - QUERY_ERROR(DNS_R_SERVFAIL); +/*% + * Add SOA to the authority section when sending negative responses + * (or to the additional section if sending negative responses triggered + * by RPZ rewriting.) + */ +static isc_result_t +query_addsoa(query_ctx_t *qctx, unsigned int override_ttl, + dns_section_t section) +{ + ns_client_t *client = qctx->client; + dns_name_t *name; + dns_dbnode_t *node; + isc_result_t result, eresult; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t **sigrdatasetp = NULL; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CTRACE(ISC_LOG_DEBUG(3), "query_addsoa"); + /* + * Initialization. + */ + eresult = ISC_R_SUCCESS; + name = NULL; + rdataset = NULL; + node = NULL; + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); + + /* + * Don't add the SOA record for test which set "-T nosoa". + */ + if (ns_g_nosoa && + (!WANTDNSSEC(client) || !dns_rdataset_isassociated(qctx->rdataset))) + { + return (ISC_R_SUCCESS); + } + + /* + * Get resources and make 'name' be the database origin. + */ + result = dns_message_gettempname(client->message, &name); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_init(name, NULL); + dns_name_clone(dns_db_origin(qctx->db), name); + rdataset = query_newrdataset(client); + if (rdataset == NULL) { + CTRACE(ISC_LOG_ERROR, "unable to allocate rdataset"); + eresult = DNS_R_SERVFAIL; + goto cleanup; + } + if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) { + sigrdataset = query_newrdataset(client); + if (sigrdataset == NULL) { + CTRACE(ISC_LOG_ERROR, "unable to allocate sigrdataset"); + eresult = DNS_R_SERVFAIL; goto cleanup; } + } + /* + * Find the SOA. + */ + result = dns_db_getoriginnode(qctx->db, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(qctx->db, node, qctx->version, + dns_rdatatype_soa, 0, + client->now, + rdataset, sigrdataset); + } else { + dns_fixedname_t foundname; + dns_name_t *fname; + + dns_fixedname_init(&foundname); + fname = dns_fixedname_name(&foundname); + + result = dns_db_findext(qctx->db, name, qctx->version, + dns_rdatatype_soa, + client->query.dboptions, + 0, &node, fname, &cm, &ci, + rdataset, sigrdataset); + } + if (result != ISC_R_SUCCESS) { /* - * Calling query_addrrset() with a non-NULL dbuf is going - * to either keep or release the name. We don't want it to - * release fname, since we may have to call query_addrrset() - * more than once. That means we have to call query_keepname() - * now, and pass a NULL dbuf to query_addrrset(). - * - * If we do a query_addrrset() below, we must set fname to - * NULL before leaving this block, otherwise we might try to - * cleanup fname even though we're using it! + * This is bad. We tried to get the SOA RR at the zone top + * and it didn't work! */ - query_keepname(client, fname, dbuf); - tname = fname; - result = dns_rdatasetiter_first(rdsiter); - while (result == ISC_R_SUCCESS) { - dns_rdatasetiter_current(rdsiter, rdataset); -#ifdef ALLOW_FILTER_AAAA - /* - * Notice the presence of A and AAAAs so - * that AAAAs can be hidden from IPv4 clients. - */ - if (client->filter_aaaa != dns_aaaa_ok) { - if (rdataset->type == dns_rdatatype_aaaa) - have_aaaa = ISC_TRUE; - else if (rdataset->type == dns_rdatatype_a) - have_a = ISC_TRUE; - } -#endif - if (is_zone && qtype == dns_rdatatype_any && - !dns_db_issecure(db) && - dns_rdatatype_isdnssec(rdataset->type)) { - /* - * The zone is transitioning from insecure - * to secure. Hide the dnssec records from - * ANY queries. - */ - dns_rdataset_disassociate(rdataset); - } else if (client->view->minimal_any && - !TCP(client) && !WANTDNSSEC(client) && - qtype == dns_rdatatype_any && - (rdataset->type == dns_rdatatype_sig || - rdataset->type == dns_rdatatype_rrsig)) { - CTRACE(ISC_LOG_DEBUG(5), "query_find: " - "minimal-any skip signature"); - dns_rdataset_disassociate(rdataset); - } else if (client->view->minimal_any && - !TCP(client) && onetype != 0 && - rdataset->type != onetype && - rdataset->covers != onetype) { - CTRACE(ISC_LOG_DEBUG(5), "query_find: " - "minimal-any skip rdataset"); - dns_rdataset_disassociate(rdataset); - } else if ((qtype == dns_rdatatype_any || - rdataset->type == qtype) && rdataset->type != 0) { -#ifdef ALLOW_FILTER_AAAA - if (dns_rdatatype_isdnssec(rdataset->type)) - have_sig = ISC_TRUE; -#endif - if (NOQNAME(rdataset) && WANTDNSSEC(client)) - noqname = rdataset; - else - noqname = NULL; - rpz_st = client->query.rpz_st; - if (rpz_st != NULL) - rdataset->ttl = ISC_MIN(rdataset->ttl, - rpz_st->m.ttl); - if (!is_zone && RECURSIONOK(client)) { - dns_name_t *name; - name = (fname != NULL) ? fname : tname; - query_prefetch(client, name, rdataset); - } - /* - * Remember the first RRtype we find so we - * can skip others with minimal-any. - */ - if (rdataset->type == dns_rdatatype_sig || - rdataset->type == dns_rdatatype_rrsig) - onetype = rdataset->covers; - else - onetype = rdataset->type; - query_addrrset(client, - fname != NULL ? &fname : &tname, - &rdataset, NULL, - NULL, DNS_SECTION_ANSWER); - if (noqname != NULL) - query_addnoqnameproof(client, noqname); - n++; - INSIST(tname != NULL); - /* - * rdataset is non-NULL only in certain - * pathological cases involving DNAMEs. - */ - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - rdataset = query_newrdataset(client); - if (rdataset == NULL) - break; - } else { - /* - * We're not interested in this rdataset. - */ - dns_rdataset_disassociate(rdataset); - } - result = dns_rdatasetiter_next(rdsiter); - } - -#ifdef ALLOW_FILTER_AAAA - /* - * Filter AAAAs if there is an A and there is no signature - * or we are supposed to break DNSSEC. - */ - if (client->filter_aaaa == dns_aaaa_break_dnssec) - client->attributes |= NS_CLIENTATTR_FILTER_AAAA; - else if (client->filter_aaaa != dns_aaaa_ok && - have_aaaa && have_a && - (!have_sig || !WANTDNSSEC(client))) - client->attributes |= NS_CLIENTATTR_FILTER_AAAA; -#endif - if (fname != NULL) - dns_message_puttempname(client->message, &fname); - - if (n == 0) { - /* - * No matching rdatasets found in cache. If we were - * searching for RRSIG/SIG, that's probably okay; - * otherwise this is an error condition. - */ - if ((qtype == dns_rdatatype_rrsig || - qtype == dns_rdatatype_sig) && - result == ISC_R_NOMORE) { - if (!is_zone) { - authoritative = ISC_FALSE; - dns_rdatasetiter_destroy(&rdsiter); - client->attributes &= ~NS_CLIENTATTR_RA; - goto addauth; - } - - if (qtype == dns_rdatatype_rrsig && - dns_db_issecure(db)) { - char namebuf[DNS_NAME_FORMATSIZE]; - dns_name_format(client->query.qname, - namebuf, - sizeof(namebuf)); - ns_client_log(client, - DNS_LOGCATEGORY_DNSSEC, - NS_LOGMODULE_QUERY, - ISC_LOG_WARNING, - "missing signature " - "for %s", namebuf); - } - - dns_rdatasetiter_destroy(&rdsiter); - fname = query_newname(client, dbuf, &b); - goto nxrrset_rrsig; - } else { - CTRACE(ISC_LOG_ERROR, - "query_find: no matching rdatasets " - "in cache"); - result = DNS_R_SERVFAIL; - } - } - - dns_rdatasetiter_destroy(&rdsiter); - if (result != ISC_R_NOMORE) { - CTRACE(ISC_LOG_ERROR, - "query_find: dns_rdatasetiter_destroy failed"); - QUERY_ERROR(DNS_R_SERVFAIL); - goto cleanup; - } + CTRACE(ISC_LOG_ERROR, "unable to find SOA RR at zone apex"); + eresult = DNS_R_SERVFAIL; } else { /* - * This is the "normal" case -- an ordinary question to which - * we know the answer. + * Extract the SOA MINIMUM. */ - - /* - * If we have a zero ttl from the cache refetch it. - */ - if (!is_zone && event == NULL && rdataset->ttl == 0 && - RECURSIONOK(client)) - { - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (sigrdataset != NULL && - dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - - INSIST(!REDIRECT(client)); - result = query_recurse(client, qtype, - client->query.qname, - NULL, NULL, resuming); - if (result == ISC_R_SUCCESS) { - client->query.attributes |= - NS_QUERYATTR_RECURSING; - if (dns64) - client->query.attributes |= - NS_QUERYATTR_DNS64; - if (dns64_exclude) - client->query.attributes |= - NS_QUERYATTR_DNS64EXCLUDE; - } else - RECURSE_ERROR(result); + dns_rdata_soa_t soa; + dns_rdata_t rdata = DNS_RDATA_INIT; + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + if (result != ISC_R_SUCCESS) goto cleanup; - } -#ifdef ALLOW_FILTER_AAAA - /* - * Optionally hide AAAAs from IPv4 clients if there is an A. - * We add the AAAAs now, but might refuse to render them later - * after DNSSEC is figured out. - * This could be more efficient, but the whole idea is - * so fundamentally wrong, unavoidably inaccurate, and - * unneeded that it is best to keep it as short as possible. - */ - if (client->filter_aaaa == dns_aaaa_break_dnssec || - (client->filter_aaaa == dns_aaaa_filter && - (!WANTDNSSEC(client) || sigrdataset == NULL || - !dns_rdataset_isassociated(sigrdataset)))) + if (override_ttl != ISC_UINT32_MAX && + override_ttl < rdataset->ttl) { - if (qtype == dns_rdatatype_aaaa) { - trdataset = query_newrdataset(client); - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_a, 0, - client->now, - trdataset, NULL); - if (dns_rdataset_isassociated(trdataset)) - dns_rdataset_disassociate(trdataset); - query_putrdataset(client, &trdataset); - - /* - * We have an AAAA but the A is not in our cache. - * Assume any result other than DNS_R_DELEGATION - * or ISC_R_NOTFOUND means there is no A and - * so AAAAs are ok. - * Assume there is no A if we can't recurse - * for this client, although that could be - * the wrong answer. What else can we do? - * Besides, that we have the AAAA and are using - * this mechanism suggests that we care more - * about As than AAAAs and would have cached - * the A if it existed. - */ - if (result == ISC_R_SUCCESS) { - client->attributes |= - NS_CLIENTATTR_FILTER_AAAA; - - } else if (authoritative || - !RECURSIONOK(client) || - (result != DNS_R_DELEGATION && - result != ISC_R_NOTFOUND)) { - client->attributes &= - ~NS_CLIENTATTR_FILTER_AAAA; - } else { - /* - * This is an ugly kludge to recurse - * for the A and discard the result. - * - * Continue to add the AAAA now. - * We'll make a note to not render it - * if the recursion for the A succeeds. - */ - INSIST(!REDIRECT(client)); - result = query_recurse(client, - dns_rdatatype_a, - client->query.qname, - NULL, NULL, resuming); - if (result == ISC_R_SUCCESS) { - client->attributes |= - NS_CLIENTATTR_FILTER_AAAA_RC; - client->query.attributes |= - NS_QUERYATTR_RECURSING; - } - } - - } else if (qtype == dns_rdatatype_a && - (client->attributes & - NS_CLIENTATTR_FILTER_AAAA_RC) != 0) { - client->attributes &= - ~NS_CLIENTATTR_FILTER_AAAA_RC; - client->attributes |= - NS_CLIENTATTR_FILTER_AAAA; - dns_rdataset_disassociate(rdataset); - if (sigrdataset != NULL && - dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - goto cleanup; - } + rdataset->ttl = override_ttl; + if (sigrdataset != NULL) + sigrdataset->ttl = override_ttl; } -#endif + /* - * Check to see if the AAAA RRset has non-excluded addresses - * in it. If not look for a A RRset. + * Add the SOA and its SIG to the response, with the + * TTLs adjusted per RFC2308 section 3. */ - INSIST(client->query.dns64_aaaaok == NULL); - - if (qtype == dns_rdatatype_aaaa && !dns64_exclude && - !ISC_LIST_EMPTY(client->view->dns64) && - client->message->rdclass == dns_rdataclass_in && - !dns64_aaaaok(client, rdataset, sigrdataset)) { - /* - * Look to see if there are A records for this - * name. - */ - client->query.dns64_ttl = rdataset->ttl; - SAVE(client->query.dns64_aaaa, rdataset); - SAVE(client->query.dns64_sigaaaa, sigrdataset); - query_releasename(client, &fname); - dns_db_detachnode(db, &node); - type = qtype = dns_rdatatype_a; - dns64_exclude = dns64 = ISC_TRUE; - goto db_find; - } + if (rdataset->ttl > soa.minimum) + rdataset->ttl = soa.minimum; + if (sigrdataset != NULL && sigrdataset->ttl > soa.minimum) + sigrdataset->ttl = soa.minimum; if (sigrdataset != NULL) sigrdatasetp = &sigrdataset; else sigrdatasetp = NULL; - if (NOQNAME(rdataset) && WANTDNSSEC(client)) - noqname = rdataset; - else - noqname = NULL; - /* - * BIND 8 priming queries need the additional section. - */ - if (is_zone && qtype == dns_rdatatype_ns && - dns_name_equal(client->query.qname, dns_rootname)) - client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; - /* - * Return the time to expire for slave and master zones. - */ - if (zone != NULL && is_zone && qtype == dns_rdatatype_soa && - (client->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0 && - client->query.restarts == 0) { - dns_zone_t *raw = NULL, *mayberaw; - - dns_zone_getraw(zone, &raw); - mayberaw = (raw != NULL) ? raw : zone; - - if (dns_zone_gettype(mayberaw) == dns_zone_slave) { - isc_time_t expiretime; - isc_uint32_t secs; - dns_zone_getexpiretime(zone, &expiretime); - secs = isc_time_seconds(&expiretime); - if (secs >= client->now && - result == ISC_R_SUCCESS) { - client->attributes |= - NS_CLIENTATTR_HAVEEXPIRE; - client->expire = secs - client->now; - } - } - if (dns_zone_gettype(mayberaw) == dns_zone_master) { - dns_rdata_soa_t soa; - result = dns_rdataset_first(rdataset); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &soa, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - client->expire = soa.expire; - client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; - } - if (raw != NULL) - dns_zone_detach(&raw); - } - - if (dns64) { - qtype = type = dns_rdatatype_aaaa; - result = query_dns64(client, &fname, rdataset, - sigrdataset, dbuf, - DNS_SECTION_ANSWER); - dns_rdataset_disassociate(rdataset); - dns_message_puttemprdataset(client->message, &rdataset); - if (result == ISC_R_NOMORE) { -#ifndef dns64_bis_return_excluded_addresses - if (dns64_exclude) { - if (!is_zone) - goto cleanup; - /* - * Add a fake SOA record. - */ - (void)query_addsoa(client, db, version, - 600, ISC_FALSE, - DNS_SECTION_AUTHORITY); - goto cleanup; - } -#endif - if (is_zone) - goto iszone_nxrrset; - else - goto ncache_nxrrset; - } else if (result != ISC_R_SUCCESS) { - eresult = result; - goto cleanup; - } - } else if (client->query.dns64_aaaaok != NULL) { - query_filter64(client, &fname, rdataset, dbuf, - DNS_SECTION_ANSWER); - query_putrdataset(client, &rdataset); - } else { - if (!is_zone && RECURSIONOK(client)) - query_prefetch(client, fname, rdataset); - query_addrrset(client, &fname, &rdataset, - sigrdatasetp, dbuf, DNS_SECTION_ANSWER); - } - - if (noqname != NULL) - query_addnoqnameproof(client, noqname); - /* - * We shouldn't ever fail to add 'rdataset' - * because it's already in the answer. - */ - INSIST(rdataset == NULL); + if (section == DNS_SECTION_ADDITIONAL) + rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + query_addrrset(client, &name, &rdataset, + sigrdatasetp, NULL, section); } - addauth: - CTRACE(ISC_LOG_DEBUG(3), "query_find: addauth"); - /* - * Add NS records to the authority section (if we haven't already - * added them to the answer section). - */ - if (!want_restart && !NOAUTHORITY(client)) { - if (is_zone) { - if (!((qtype == dns_rdatatype_ns || - qtype == dns_rdatatype_any) && - dns_name_equal(client->query.qname, - dns_db_origin(db)))) - (void)query_addns(client, db, version); - } else if (qtype != dns_rdatatype_ns) { - if (fname != NULL) - query_releasename(client, &fname); - query_addbestns(client); - } - } - - /* - * Add NSEC records to the authority section if they're needed for - * DNSSEC wildcard proofs. - */ - if (need_wildcardproof && dns_db_issecure(db)) - query_addwildcardproof(client, db, version, - dns_fixedname_name(&wildcardname), - ISC_TRUE, ISC_FALSE); cleanup: - CTRACE(ISC_LOG_DEBUG(3), "query_find: cleanup"); - /* - * General cleanup. - */ - rpz_st = client->query.rpz_st; - if (rpz_st != NULL && (rpz_st->state & DNS_RPZ_RECURSING) == 0) { - rpz_match_clear(rpz_st); - rpz_st->state &= ~DNS_RPZ_DONE_QNAME; - } - if (rdataset != NULL) - query_putrdataset(client, &rdataset); + query_putrdataset(client, &rdataset); if (sigrdataset != NULL) query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); + if (name != NULL) + query_releasename(client, &name); if (node != NULL) - dns_db_detachnode(db, &node); - if (db != NULL) + dns_db_detachnode(qctx->db, &node); + + return (eresult); +} + +/*% + * Add NS to authority section (used when the zone apex is already known). + */ +static isc_result_t +query_addns(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + isc_result_t result, eresult; + dns_name_t *name = NULL, *fname; + dns_dbnode_t *node = NULL; + dns_fixedname_t foundname; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t **sigrdatasetp = NULL; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CTRACE(ISC_LOG_DEBUG(3), "query_addns"); + + /* + * Initialization. + */ + eresult = ISC_R_SUCCESS; + dns_fixedname_init(&foundname); + fname = dns_fixedname_name(&foundname); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); + + /* + * Get resources and make 'name' be the database origin. + */ + result = dns_message_gettempname(client->message, &name); + if (result != ISC_R_SUCCESS) { + CTRACE(ISC_LOG_DEBUG(3), + "query_addns: dns_message_gettempname failed: done"); + return (result); + } + dns_name_init(name, NULL); + dns_name_clone(dns_db_origin(qctx->db), name); + rdataset = query_newrdataset(client); + if (rdataset == NULL) { + CTRACE(ISC_LOG_ERROR, + "query_addns: query_newrdataset failed"); + eresult = DNS_R_SERVFAIL; + goto cleanup; + } + + if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) { + sigrdataset = query_newrdataset(client); + if (sigrdataset == NULL) { + CTRACE(ISC_LOG_ERROR, + "query_addns: query_newrdataset failed"); + eresult = DNS_R_SERVFAIL; + goto cleanup; + } + } + + /* + * Find the NS rdataset. + */ + result = dns_db_getoriginnode(qctx->db, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(qctx->db, node, qctx->version, + dns_rdatatype_ns, 0, client->now, + rdataset, sigrdataset); + } else { + CTRACE(ISC_LOG_DEBUG(3), "query_addns: calling dns_db_find"); + result = dns_db_findext(qctx->db, name, NULL, dns_rdatatype_ns, + client->query.dboptions, 0, &node, + fname, &cm, &ci, rdataset, sigrdataset); + CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_db_find complete"); + } + if (result != ISC_R_SUCCESS) { + CTRACE(ISC_LOG_ERROR, + "query_addns: " + "dns_db_findrdataset or dns_db_find failed"); + /* + * This is bad. We tried to get the NS rdataset at the zone + * top and it didn't work! + */ + eresult = DNS_R_SERVFAIL; + } else { + if (sigrdataset != NULL) { + sigrdatasetp = &sigrdataset; + } + query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, + DNS_SECTION_AUTHORITY); + } + + cleanup: + CTRACE(ISC_LOG_DEBUG(3), "query_addns: cleanup"); + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) { + query_putrdataset(client, &sigrdataset); + } + if (name != NULL) { + query_releasename(client, &name); + } + if (node != NULL) { + dns_db_detachnode(qctx->db, &node); + } + + CTRACE(ISC_LOG_DEBUG(3), "query_addns: done"); + return (eresult); +} + +/*% + * Find the zone cut and add the best NS rrset to the authority section. + */ +static void +query_addbestns(query_ctx_t *qctx) { + ns_client_t *client = qctx->client; + dns_db_t *db = NULL, *zdb = NULL; + dns_dbnode_t *node = NULL; + dns_name_t *fname = NULL, *zfname = NULL; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t *zrdataset = NULL, *zsigrdataset = NULL; + isc_boolean_t is_zone = ISC_FALSE, use_zone = ISC_FALSE; + isc_buffer_t *dbuf = NULL; + isc_result_t result; + dns_dbversion_t *version = NULL; + dns_zone_t *zone = NULL; + isc_buffer_t b; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CTRACE(ISC_LOG_DEBUG(3), "query_addbestns"); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); + + /* + * Find the right database. + */ + result = query_getdb(client, client->query.qname, dns_rdatatype_ns, 0, + &zone, &db, &version, &is_zone); + if (result != ISC_R_SUCCESS) + goto cleanup; + + db_find: + /* + * We'll need some resources... + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { + goto cleanup; + } + fname = query_newname(client, dbuf, &b); + rdataset = query_newrdataset(client); + if (fname == NULL || rdataset == NULL) { + goto cleanup; + } + + /* + * Get the RRSIGs if the client requested them or if we may + * need to validate answers from the cache. + */ + if (WANTDNSSEC(client) || !is_zone) { + sigrdataset = query_newrdataset(client); + if (sigrdataset == NULL) { + goto cleanup; + } + } + + /* + * Now look for the zonecut. + */ + if (is_zone) { + result = dns_db_findext(db, client->query.qname, version, + dns_rdatatype_ns, + client->query.dboptions, + client->now, &node, fname, + &cm, &ci, rdataset, sigrdataset); + if (result != DNS_R_DELEGATION) { + goto cleanup; + } + if (USECACHE(client)) { + query_keepname(client, fname, dbuf); + dns_db_detachnode(db, &node); + SAVE(zdb, db); + SAVE(zfname, fname); + SAVE(zrdataset, rdataset); + SAVE(zsigrdataset, sigrdataset); + version = NULL; + dns_db_attach(client->view->cachedb, &db); + is_zone = ISC_FALSE; + goto db_find; + } + } else { + result = dns_db_findzonecut(db, client->query.qname, + client->query.dboptions, + client->now, &node, fname, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + if (zfname != NULL && + !dns_name_issubdomain(fname, zfname)) { + /* + * We found a zonecut in the cache, but our + * zone delegation is better. + */ + use_zone = ISC_TRUE; + } + } else if (result == ISC_R_NOTFOUND && zfname != NULL) { + /* + * We didn't find anything in the cache, but we + * have a zone delegation, so use it. + */ + use_zone = ISC_TRUE; + } else { + goto cleanup; + } + } + + if (use_zone) { + query_releasename(client, &fname); + /* + * We've already done query_keepname() on + * zfname, so we must set dbuf to NULL to + * prevent query_addrrset() from trying to + * call query_keepname() again. + */ + dbuf = NULL; + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) { + query_putrdataset(client, &sigrdataset); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } dns_db_detach(&db); - if (zone != NULL) + + RESTORE(db, zdb); + RESTORE(fname, zfname); + RESTORE(rdataset, zrdataset); + RESTORE(sigrdataset, zsigrdataset); + } + + /* + * Attempt to validate RRsets that are pending or that are glue. + */ + if ((DNS_TRUST_PENDING(rdataset->trust) || + (sigrdataset != NULL && DNS_TRUST_PENDING(sigrdataset->trust))) && + !validate(client, db, fname, rdataset, sigrdataset) && + !PENDINGOK(client->query.dboptions)) + { + goto cleanup; + } + + if ((DNS_TRUST_GLUE(rdataset->trust) || + (sigrdataset != NULL && DNS_TRUST_GLUE(sigrdataset->trust))) && + !validate(client, db, fname, rdataset, sigrdataset) && + SECURE(client) && WANTDNSSEC(client)) + { + goto cleanup; + } + + /* + * If the answer is secure only add NS records if they are secure + * when the client may be looking for AD in the response. + */ + if (SECURE(client) && (WANTDNSSEC(client) || WANTAD(client)) && + ((rdataset->trust != dns_trust_secure) || + (sigrdataset != NULL && sigrdataset->trust != dns_trust_secure))) + { + goto cleanup; + } + + /* + * If the client doesn't want DNSSEC we can discard the sigrdataset + * now. + */ + if (!WANTDNSSEC(client)) { + query_putrdataset(client, &sigrdataset); + } + + query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, + DNS_SECTION_AUTHORITY); + + cleanup: + if (rdataset != NULL) { + query_putrdataset(client, &rdataset); + } + if (sigrdataset != NULL) { + query_putrdataset(client, &sigrdataset); + } + if (fname != NULL) { + query_releasename(client, &fname); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (zone != NULL) { dns_zone_detach(&zone); + } if (zdb != NULL) { query_putrdataset(client, &zrdataset); if (zsigrdataset != NULL) @@ -8847,88 +9190,530 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) query_releasename(client, &zfname); dns_db_detach(&zdb); } - if (event != NULL) - isc_event_free(ISC_EVENT_PTR(&event)); +} + +static void +query_addwildcardproof(query_ctx_t *qctx, isc_boolean_t ispositive, + isc_boolean_t nodata) +{ + ns_client_t *client = qctx->client; + isc_buffer_t *dbuf, b; + dns_name_t *name; + dns_name_t *fname = NULL; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_fixedname_t wfixed; + dns_name_t *wname; + dns_dbnode_t *node = NULL; + unsigned int options; + unsigned int olabels, nlabels, labels; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec_t nsec; + isc_boolean_t have_wname; + int order; + dns_fixedname_t cfixed; + dns_name_t *cname; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CTRACE(ISC_LOG_DEBUG(3), "query_addwildcardproof"); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); /* - * AA bit. + * If a name has been specifically flagged as needing + * a wildcard proof then it will have been copied to + * qctx->wildcardname. Otherwise we just use the client + * QNAME. */ - if (client->query.restarts == 0 && !authoritative) { + if (qctx->need_wildcardproof) { + name = dns_fixedname_name(&qctx->wildcardname); + } else { + name = client->query.qname; + } + + /* + * Get the NOQNAME proof then if !ispositive + * get the NOWILDCARD proof. + * + * DNS_DBFIND_NOWILD finds the NSEC records that covers the + * name ignoring any wildcard. From the owner and next names + * of this record you can compute which wildcard (if it exists) + * will match by finding the longest common suffix of the + * owner name and next names with the qname and prefixing that + * with the wildcard label. + * + * e.g. + * Given: + * example SOA + * example NSEC b.example + * b.example A + * b.example NSEC a.d.example + * a.d.example A + * a.d.example NSEC g.f.example + * g.f.example A + * g.f.example NSEC z.i.example + * z.i.example A + * z.i.example NSEC example + * + * QNAME: + * a.example -> example NSEC b.example + * owner common example + * next common example + * wild *.example + * d.b.example -> b.example NSEC a.d.example + * owner common b.example + * next common example + * wild *.b.example + * a.f.example -> a.d.example NSEC g.f.example + * owner common example + * next common f.example + * wild *.f.example + * j.example -> z.i.example NSEC example + * owner common example + * next common example + * wild *.example + */ + options = client->query.dboptions | DNS_DBFIND_NOWILD; + dns_fixedname_init(&wfixed); + wname = dns_fixedname_name(&wfixed); + again: + have_wname = ISC_FALSE; + /* + * We'll need some resources... + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + rdataset = query_newrdataset(client); + sigrdataset = query_newrdataset(client); + if (fname == NULL || rdataset == NULL || sigrdataset == NULL) + goto cleanup; + + result = dns_db_findext(qctx->db, name, qctx->version, + dns_rdatatype_nsec, options, 0, &node, + fname, &cm, &ci, rdataset, sigrdataset); + if (node != NULL) + dns_db_detachnode(qctx->db, &node); + + if (!dns_rdataset_isassociated(rdataset)) { /* - * We're not authoritative, so we must ensure the AA bit - * isn't set. + * No NSEC proof available, return NSEC3 proofs instead. */ - client->message->flags &= ~DNS_MESSAGEFLAG_AA; + dns_fixedname_init(&cfixed); + cname = dns_fixedname_name(&cfixed); + /* + * Find the closest encloser. + */ + dns_name_copy(name, cname, NULL); + while (result == DNS_R_NXDOMAIN) { + labels = dns_name_countlabels(cname) - 1; + /* + * Sanity check. + */ + if (labels == 0U) + goto cleanup; + dns_name_split(cname, labels, NULL, cname); + result = dns_db_findext(qctx->db, cname, qctx->version, + dns_rdatatype_nsec, + options, 0, NULL, fname, + &cm, &ci, NULL, NULL); + } + /* + * Add closest (provable) encloser NSEC3. + */ + query_findclosestnsec3(cname, qctx->db, qctx->version, + client, rdataset, sigrdataset, + fname, ISC_TRUE, cname); + if (!dns_rdataset_isassociated(rdataset)) + goto cleanup; + if (!ispositive) + query_addrrset(client, &fname, &rdataset, &sigrdataset, + dbuf, DNS_SECTION_AUTHORITY); + + /* + * Replace resources which were consumed by query_addrrset. + */ + if (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + } + + if (rdataset == NULL) + rdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + + if (sigrdataset == NULL) + sigrdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(sigrdataset)) + dns_rdataset_disassociate(sigrdataset); + + if (fname == NULL || rdataset == NULL || sigrdataset == NULL) + goto cleanup; + /* + * Add no qname proof. + */ + labels = dns_name_countlabels(cname) + 1; + if (dns_name_countlabels(name) == labels) + dns_name_copy(name, wname, NULL); + else + dns_name_split(name, labels, NULL, wname); + + query_findclosestnsec3(wname, qctx->db, qctx->version, + client, rdataset, sigrdataset, + fname, ISC_FALSE, NULL); + if (!dns_rdataset_isassociated(rdataset)) + goto cleanup; + query_addrrset(client, &fname, &rdataset, &sigrdataset, + dbuf, DNS_SECTION_AUTHORITY); + + if (ispositive) + goto cleanup; + + /* + * Replace resources which were consumed by query_addrrset. + */ + if (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + } + + if (rdataset == NULL) + rdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + + if (sigrdataset == NULL) + sigrdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(sigrdataset)) + dns_rdataset_disassociate(sigrdataset); + + if (fname == NULL || rdataset == NULL || sigrdataset == NULL) + goto cleanup; + /* + * Add the no wildcard proof. + */ + result = dns_name_concatenate(dns_wildcardname, + cname, wname, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + + query_findclosestnsec3(wname, qctx->db, qctx->version, + client, rdataset, sigrdataset, + fname, nodata, NULL); + if (!dns_rdataset_isassociated(rdataset)) + goto cleanup; + query_addrrset(client, &fname, &rdataset, &sigrdataset, + dbuf, DNS_SECTION_AUTHORITY); + + goto cleanup; + } else if (result == DNS_R_NXDOMAIN) { + if (!ispositive) + result = dns_rdataset_first(rdataset); + if (result == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec, NULL); + } + if (result == ISC_R_SUCCESS) { + (void)dns_name_fullcompare(name, fname, &order, + &olabels); + (void)dns_name_fullcompare(name, &nsec.next, &order, + &nlabels); + /* + * Check for a pathological condition created when + * serving some malformed signed zones and bail out. + */ + if (dns_name_countlabels(name) == nlabels) + goto cleanup; + + if (olabels > nlabels) + dns_name_split(name, olabels, NULL, wname); + else + dns_name_split(name, nlabels, NULL, wname); + result = dns_name_concatenate(dns_wildcardname, + wname, wname, NULL); + if (result == ISC_R_SUCCESS) + have_wname = ISC_TRUE; + dns_rdata_freestruct(&nsec); + } + query_addrrset(client, &fname, &rdataset, &sigrdataset, + dbuf, DNS_SECTION_AUTHORITY); + } + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) + query_putrdataset(client, &sigrdataset); + if (fname != NULL) + query_releasename(client, &fname); + if (have_wname) { + ispositive = ISC_TRUE; /* prevent loop */ + if (!dns_name_equal(name, wname)) { + name = wname; + goto again; + } + } + cleanup: + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) + query_putrdataset(client, &sigrdataset); + if (fname != NULL) + query_releasename(client, &fname); +} + +/*% + * Add NS records, and NSEC/NSEC3 wildcard proof records if needed, + * to the authority section. + */ +static void +query_addauth(query_ctx_t *qctx) { + CCTRACE(ISC_LOG_DEBUG(3), "query_addauth"); + /* + * Add NS records to the authority section (if we haven't already + * added them to the answer section). + */ + if (!qctx->want_restart && !NOAUTHORITY(qctx->client)) { + if (qctx->is_zone) { + if (!((qctx->qtype == dns_rdatatype_ns || + qctx->qtype == dns_rdatatype_any) && + dns_name_equal(qctx->client->query.qname, + dns_db_origin(qctx->db)))) + { + (void)query_addns(qctx); + } + } else if (qctx->qtype != dns_rdatatype_ns) { + if (qctx->fname != NULL) { + query_releasename(qctx->client, &qctx->fname); + } + query_addbestns(qctx); + } } /* - * Restart the query? + * Add NSEC records to the authority section if they're needed for + * DNSSEC wildcard proofs. */ - if (want_restart && client->query.restarts < MAX_RESTARTS) { - client->query.restarts++; - goto restart; + if (qctx->need_wildcardproof && dns_db_issecure(qctx->db)) + query_addwildcardproof(qctx, ISC_TRUE, ISC_FALSE); +} + +/* + * Find the sort order of 'rdata' in the topology-like + * ACL forming the second element in a 2-element top-level + * sortlist statement. + */ +static int +query_sortlist_order_2element(const dns_rdata_t *rdata, const void *arg) { + isc_netaddr_t netaddr; + + if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) + return (INT_MAX); + return (ns_sortlist_addrorder2(&netaddr, arg)); +} + +/* + * Find the sort order of 'rdata' in the matching element + * of a 1-element top-level sortlist statement. + */ +static int +query_sortlist_order_1element(const dns_rdata_t *rdata, const void *arg) { + isc_netaddr_t netaddr; + + if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) + return (INT_MAX); + return (ns_sortlist_addrorder1(&netaddr, arg)); +} + +/* + * Find the sortlist statement that applies to 'client' and set up + * the sortlist info in in client->message appropriately. + */ +static void +query_setup_sortlist(query_ctx_t *qctx) { + isc_netaddr_t netaddr; + dns_rdatasetorderfunc_t order = NULL; + const void *order_arg = NULL; + + isc_netaddr_fromsockaddr(&netaddr, &qctx->client->peeraddr); + switch (ns_sortlist_setup(qctx->client->view->sortlist, + &netaddr, &order_arg)) + { + case NS_SORTLISTTYPE_1ELEMENT: + order = query_sortlist_order_1element; + break; + case NS_SORTLISTTYPE_2ELEMENT: + order = query_sortlist_order_2element; + break; + case NS_SORTLISTTYPE_NONE: + order = NULL; + break; + default: + INSIST(0); + break; } - if (eresult != ISC_R_SUCCESS && - (!PARTIALANSWER(client) || WANTRECURSION(client) - || eresult == DNS_R_DROP)) { - if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) { + dns_message_setsortorder(qctx->client->message, order, order_arg); +} + +/* + * When sending a referral, if the answer to the question is + * in the glue, sort it to the start of the additional section. + */ +static inline void +query_glueanswer(query_ctx_t *qctx) { + const dns_namelist_t *secs = qctx->client->message->sections; + const dns_section_t section = DNS_SECTION_ADDITIONAL; + dns_name_t *name; + dns_message_t *msg; + dns_rdataset_t *rdataset = NULL; + + if (!ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) || + qctx->client->message->rcode != dns_rcode_noerror || + (qctx->qtype != dns_rdatatype_a && + qctx->qtype != dns_rdatatype_aaaa)) + { + return; + } + + msg = qctx->client->message; + for (name = ISC_LIST_HEAD(msg->sections[section]); + name != NULL; + name = ISC_LIST_NEXT(name, link)) + if (dns_name_equal(name, qctx->client->query.qname)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + if (rdataset->type == qctx->qtype) + break; + break; + } + if (rdataset != NULL) { + ISC_LIST_UNLINK(msg->sections[section], name, link); + ISC_LIST_PREPEND(msg->sections[section], name, link); + ISC_LIST_UNLINK(name->list, rdataset, link); + ISC_LIST_PREPEND(name->list, rdataset, link); + rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + } +} + +/*% + * Finalize this phase of the query process: + * + * - Clean up + * - If we have an answer ready (positive or negative), send it. + * - If we need to restart for a chaining query, call query_start() again. + * - If we've started recursion, then just clean up; things will be + * restarted via fetch_callback()/query_resume(). + */ +static isc_result_t +query_done(query_ctx_t *qctx) { + const dns_namelist_t *secs = qctx->client->message->sections; + CCTRACE(ISC_LOG_DEBUG(3), "query_done"); + + /* + * General cleanup. + */ + qctx->rpz_st = qctx->client->query.rpz_st; + if (qctx->rpz_st != NULL && + (qctx->rpz_st->state & DNS_RPZ_RECURSING) == 0) + { + rpz_match_clear(qctx->rpz_st); + qctx->rpz_st->state &= ~DNS_RPZ_DONE_QNAME; + } + + qctx_clean(qctx); + qctx_freedata(qctx); + + /* + * Clear the AA bit if we're not authoritative. + */ + if (qctx->client->query.restarts == 0 && !qctx->authoritative) { + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; + } + + /* + * Do we need to restart the query (e.g. for CNAME chaining)? + */ + if (qctx->want_restart && qctx->client->query.restarts < MAX_RESTARTS) { + qctx->client->query.restarts++; + return (query_start(qctx)); + } + + if (qctx->result != ISC_R_SUCCESS && + (!PARTIALANSWER(qctx->client) || WANTRECURSION(qctx->client) || + qctx->result == DNS_R_DROP)) + { + if (qctx->result == DNS_R_DUPLICATE || + qctx->result == DNS_R_DROP) + { /* * This was a duplicate query that we are * recursing on or the result of rate limiting. * Don't send a response now for a duplicate query, * because the original will still cause a response. */ - query_next(client, eresult); + query_next(qctx->client, qctx->result); } else { /* * If we don't have any answer to give the client, * or if the client requested recursion and thus wanted * the complete answer, send an error response. */ - INSIST(line >= 0); - query_error(client, eresult, line); + INSIST(qctx->line >= 0); + query_error(qctx->client, qctx->result, qctx->line); } - ns_client_detach(&client); - } else if (!RECURSING(client)) { - /* - * We are done. Set up sortlist data for the message - * rendering code, make a final tweak to the AA bit if the - * auth-nxdomain config option says so, then render and - * send the response. - */ - setup_query_sortlist(client); - /* - * If this is a referral and the answer to the question - * is in the glue sort it to the start of the additional - * section. - */ - if (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_ANSWER]) && - client->message->rcode == dns_rcode_noerror && - (qtype == dns_rdatatype_a || qtype == dns_rdatatype_aaaa)) - answer_in_glue(client, qtype); - - if (client->message->rcode == dns_rcode_nxdomain && - client->view->auth_nxdomain == ISC_TRUE) - client->message->flags |= DNS_MESSAGEFLAG_AA; - - /* - * If the response is somehow unexpected for the client and this - * is a result of recursion, return an error to the caller - * to indicate it may need to be logged. - */ - if (resuming && - (ISC_LIST_EMPTY(client->message->sections[DNS_SECTION_ANSWER]) || - client->message->rcode != dns_rcode_noerror)) - eresult = ISC_R_FAILURE; - - query_send(client); - ns_client_detach(&client); + ns_client_detach(&qctx->client); + return (qctx->result); } - CTRACE(ISC_LOG_DEBUG(3), "query_find: done"); - return (eresult); + /* + * If we're recursing then just return; the query will + * resume when recursion ends. + */ + if (RECURSING(qctx->client)) { + return (qctx->result); + } + + /* + * We are done. Set up sortlist data for the message + * rendering code, sort the answer to the front of the + * additional section if necessary, make a final tweak + * to the AA bit if the auth-nxdomain config option + * says so, then render and send the response. + */ + query_setup_sortlist(qctx); + query_glueanswer(qctx); + + if (qctx->client->message->rcode == dns_rcode_nxdomain && + qctx->client->view->auth_nxdomain == ISC_TRUE) + { + qctx->client->message->flags |= DNS_MESSAGEFLAG_AA; + } + + /* + * If the response is somehow unexpected for the client and this + * is a result of recursion, return an error to the caller + * to indicate it may need to be logged. + */ + if (qctx->resuming && + (ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) || + qctx->client->message->rcode != dns_rcode_noerror)) + { + qctx->result = ISC_R_FAILURE; + } + + query_send(qctx->client); + + ns_client_detach(&qctx->client); + return (qctx->result); } static inline void @@ -8955,7 +9740,7 @@ log_query(ns_client_t *client, unsigned int flags, unsigned int extflags) { if (client->ednsversion >= 0) snprintf(ednsbuf, sizeof(ednsbuf), "E(%d)", client->ednsversion); - + if (HAVEECS(client)) { strlcpy(ecsbuf, " [ECS ", sizeof(ecsbuf)); dns_ecs_format(&client->ecs, ecsbuf + 6, sizeof(ecsbuf) - 6); @@ -9077,8 +9862,8 @@ ns_query_start(ns_client_t *client) { break; } - if ((client->view->cachedb == NULL) - || (!client->view->additionalfromcache)) { + if (client->view->cachedb == NULL || !client->view->additionalfromcache) + { /* * We don't have a cache. Turn off cache support and * recursion. @@ -9144,7 +9929,7 @@ ns_query_start(ns_client_t *client) { if (dns_rdatatype_ismeta(qtype)) { switch (qtype) { case dns_rdatatype_any: - break; /* Let query_find handle it. */ + break; /* Let the query logic handle it. */ case dns_rdatatype_ixfr: case dns_rdatatype_axfr: ns_xfr_start(client, rdataset->type); @@ -9248,5 +10033,5 @@ ns_query_start(ns_client_t *client) { qclient = NULL; ns_client_attach(client, &qclient); - (void)query_find(qclient, NULL, qtype); + (void)query_setup(qclient, qtype); } diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml index 5ccef960e6..5cf7679c9e 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -128,6 +128,13 @@
New Features + + + 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] + + dnstap logfiles can now be configured to diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h index 6f7ecf213c..ec5f38936d 100644 --- a/lib/isc/include/isc/result.h +++ b/lib/isc/include/isc/result.h @@ -81,9 +81,10 @@ #define ISC_R_UNSET 61 /*%< unset */ #define ISC_R_MULTIPLE 62 /*%< multiple */ #define ISC_R_WOULDBLOCK 63 /*%< would block */ +#define ISC_R_COMPLETE 64 /*%< complete */ /*% Not a result code: the number of results. */ -#define ISC_R_NRESULTS 64 +#define ISC_R_NRESULTS 65 ISC_LANG_BEGINDECLS diff --git a/lib/isc/result.c b/lib/isc/result.c index 071dac02ec..bb8c6eb596 100644 --- a/lib/isc/result.c +++ b/lib/isc/result.c @@ -96,6 +96,7 @@ static const char *description[ISC_R_NRESULTS] = { "unset", /*%< 61 */ "multiple", /*%< 62 */ "would block", /*%< 63 */ + "complete", /*%< 64 */ }; static const char *identifier[ISC_R_NRESULTS] = { @@ -163,6 +164,7 @@ static const char *identifier[ISC_R_NRESULTS] = { "ISC_R_UNSET", "ISC_R_MULTIPLE", "ISC_R_WOULDBLOCK", + "ISC_R_COMPLETE", }; #define ISC_RESULT_RESULTSET 2