From: wpk Date: Wed, 8 Feb 2017 21:15:01 +0000 (+0100) Subject: 4573. [func] Query logic has been substantially refactored (e.g. query_find function... X-Git-Tag: v9.12.0a1~452 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=96912e44b0b180de56f47d438f91c9e70925bd16;p=thirdparty%2Fbind9.git 4573. [func] Query logic has been substantially refactored (e.g. query_find function has been split into smaller functions) for improved readability, maintainability --- diff --git a/CHANGES b/CHANGES index 404f3856f43..a696770b443 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 d66c28dc162..aca54a7db3a 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,24 +2556,20 @@ 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) +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_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; + dns_name_t *name = *namep, *mname = NULL; + dns_rdataset_t *rdataset = *rdatasetp, *mrdataset = NULL; + dns_rdataset_t *sigrdataset = NULL; isc_result_t result; - dns_view_t *view = client->view; - isc_netaddr_t netaddr; - dns_dns64_t *dns64; - unsigned int flags = 0; + + CTRACE(ISC_LOG_DEBUG(3), "query_addrrset"); + + if (sigrdatasetp != NULL) + sigrdataset = *sigrdatasetp; /*% * To the current response for 'client', add the answer RRset @@ -2329,28 +2581,20 @@ query_dns64(ns_client_t *client, dns_name_t **namep, dns_rdataset_t *rdataset, * 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, + name, rdataset->type, 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"); + "query_addrrset: dns_message_findname succeeded: done"); if (dbuf != NULL) query_releasename(client, namep); - return (ISC_R_SUCCESS); + if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) + mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + return; } else if (result == DNS_R_NXDOMAIN) { /* * The name doesn't exist. @@ -2371,6441 +2615,6884 @@ query_dns64(ns_client_t *client, dns_name_t **namep, dns_rdataset_t *rdataset, section == DNS_SECTION_AUTHORITY)) client->query.attributes &= ~NS_QUERYATTR_SECURE; - isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); + /* + * 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 + * response. + */ + query_addrdataset(client, mname, rdataset); + *rdatasetp = NULL; + if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { + /* + * We have a signature. Add it to the response. + */ + ISC_LIST_APPEND(mname->list, sigrdataset, link); + *sigrdatasetp = NULL; + } - 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; + CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: done"); +} - 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); +/* + * Mark the RRsets as secure. Update the cache (db) to reflect the + * change in trust level. + */ +static void +mark_secure(ns_client_t *client, dns_db_t *db, dns_name_t *name, + dns_rdata_rrsig_t *rrsig, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) +{ + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + isc_stdtime_t now; - if (RECURSIONOK(client)) - flags |= DNS_DNS64_RECURSIVE; + rdataset->trust = dns_trust_secure; + sigrdataset->trust = dns_trust_secure; + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); /* - * 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. + * Save the updated secure state. Ignore failures. */ - if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) - flags |= DNS_DNS64_DNSSEC; + result = dns_db_findnodeext(db, name, ISC_TRUE, &cm, &ci, &node); + if (result != ISC_R_SUCCESS) + return; - 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)) { + isc_stdtime_get(&now); + dns_rdataset_trimttl(rdataset, sigrdataset, rrsig, now, + client->view->acceptexpired); - 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; + (void)dns_db_addrdataset(db, node, NULL, client->now, rdataset, + 0, NULL); + (void)dns_db_addrdataset(db, node, NULL, client->now, sigrdataset, + 0, NULL); + dns_db_detachnode(db, &node); +} - if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) - goto cleanup; +/* + * Find the secure key that corresponds to rrsig. + * Note: 'keyrdataset' maintains state between successive calls, + * there may be multiple keys with the same keyid. + * Return ISC_FALSE if we have exhausted all the possible keys. + */ +static isc_boolean_t +get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, + dns_rdataset_t *keyrdataset, dst_key_t **keyp) +{ + isc_result_t result; + dns_dbnode_t *node = NULL; + isc_boolean_t secure = ISC_FALSE; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; - 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; + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); - cleanup: - if (buffer != NULL) - isc_buffer_free(&buffer); + if (!dns_rdataset_isassociated(keyrdataset)) { + result = dns_db_findnodeext(db, &rrsig->signer, ISC_FALSE, + &cm, &ci, &node); + if (result != ISC_R_SUCCESS) + return (ISC_FALSE); - if (dns64_rdata != NULL) - dns_message_puttemprdata(client->message, &dns64_rdata); + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_dnskey, 0, + client->now, keyrdataset, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) + return (ISC_FALSE); - if (dns64_rdataset != NULL) - dns_message_puttemprdataset(client->message, &dns64_rdataset); + if (keyrdataset->trust != dns_trust_secure) + return (ISC_FALSE); - 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); + result = dns_rdataset_first(keyrdataset); + } else + result = dns_rdataset_next(keyrdataset); + + for ( ; result == ISC_R_SUCCESS; + result = dns_rdataset_next(keyrdataset)) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t b; + + dns_rdataset_current(keyrdataset, &rdata); + isc_buffer_init(&b, rdata.data, rdata.length); + isc_buffer_add(&b, rdata.length); + result = dst_key_fromdns(&rrsig->signer, rdata.rdclass, &b, + client->mctx, keyp); + if (result != ISC_R_SUCCESS) + continue; + if (rrsig->algorithm == (dns_secalg_t)dst_key_alg(*keyp) && + rrsig->keyid == (dns_keytag_t)dst_key_id(*keyp) && + dst_key_iszonekey(*keyp)) { + secure = ISC_TRUE; + break; } - dns_message_puttemprdatalist(client->message, &dns64_rdatalist); + dst_key_free(keyp); } - - CTRACE(ISC_LOG_DEBUG(3), "query_dns64: done"); - return (result); + return (secure); } -static void -query_filter64(ns_client_t *client, dns_name_t **namep, - dns_rdataset_t *rdataset, isc_buffer_t *dbuf, - dns_section_t section) +static isc_boolean_t +verify(dst_key_t *key, dns_name_t *name, dns_rdataset_t *rdataset, + dns_rdata_t *rdata, ns_client_t *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"); + dns_fixedname_t fixed; + isc_boolean_t ignore = ISC_FALSE; - INSIST(client->query.dns64_aaaaok != NULL); - INSIST(client->query.dns64_aaaaoklen == dns_rdataset_count(rdataset)); + dns_fixedname_init(&fixed); - 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; +again: + result = dns_dnssec_verify3(name, rdataset, key, ignore, + client->view->maxbits, client->mctx, + rdata, NULL); + if (result == DNS_R_SIGEXPIRED && client->view->acceptexpired) { + ignore = ISC_TRUE; + goto again; } + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) + return (ISC_TRUE); + return (ISC_FALSE); +} - 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; +/* + * Validate the rdataset if possible with available records. + */ +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) +{ + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t rrsig; + dst_key_t *key = NULL; + dns_rdataset_t keyrdataset; - dns_rdatalist_init(myrdatalist); - myrdatalist->rdclass = dns_rdataclass_in; - myrdatalist->type = dns_rdatatype_aaaa; - myrdatalist->ttl = rdataset->ttl; + if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) + return (ISC_FALSE); - i = 0; - for (result = dns_rdataset_first(rdataset); + for (result = dns_rdataset_first(sigrdataset); 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_rdataset_next(sigrdataset)) { - 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; + dns_rdata_reset(&rdata); + dns_rdataset_current(sigrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + if (result != ISC_R_SUCCESS) + return (ISC_FALSE); + if (!dns_resolver_algorithm_supported(client->view->resolver, + name, rrsig.algorithm)) + continue; + if (!dns_name_issubdomain(name, &rrsig.signer)) + continue; + dns_rdataset_init(&keyrdataset); + do { + if (!get_key(client, db, &rrsig, &keyrdataset, &key)) + break; + if (verify(key, name, rdataset, &rdata, client)) { + dst_key_free(&key); + dns_rdataset_disassociate(&keyrdataset); + mark_secure(client, db, name, &rrsig, + rdataset, sigrdataset); + return (ISC_TRUE); + } + dst_key_free(&key); + } while (1); + if (dns_rdataset_isassociated(&keyrdataset)) + dns_rdataset_disassociate(&keyrdataset); } - myrdataset->trust = rdataset->trust; - query_addrdataset(client, mname, myrdataset); - myrdataset = NULL; - myrdatalist = NULL; - dns_message_takebuffer(client->message, &buffer); + return (ISC_FALSE); +} - cleanup: - if (buffer != NULL) - isc_buffer_free(&buffer); +static void +fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) { + if (*rdataset == NULL) + *rdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(*rdataset)) + dns_rdataset_disassociate(*rdataset); +} - if (myrdata != NULL) - dns_message_puttemprdata(client->message, &myrdata); +static void +fixfname(ns_client_t *client, dns_name_t **fname, isc_buffer_t **dbuf, + isc_buffer_t *nbuf) +{ + if (*fname == NULL) { + *dbuf = query_getnamebuf(client); + if (*dbuf == NULL) + return; + *fname = query_newname(client, *dbuf, nbuf); + } +} - if (myrdataset != NULL) - dns_message_puttemprdataset(client->message, &myrdataset); +static void +prefetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; + ns_client_t *client; - 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); + UNUSED(task); - CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done"); + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + client = devent->ev_arg; + REQUIRE(NS_CLIENT_VALID(client)); + REQUIRE(task == client->task); + + LOCK(&client->query.fetchlock); + if (client->query.prefetch != NULL) { + INSIST(devent->fetch == client->query.prefetch); + client->query.prefetch = NULL; + } + UNLOCK(&client->query.fetchlock); + if (devent->fetch != NULL) + dns_resolver_destroyfetch(&devent->fetch); + if (devent->node != NULL) + dns_db_detachnode(devent->db, &devent->node); + if (devent->db != NULL) + dns_db_detach(&devent->db); + query_putrdataset(client, &devent->rdataset); + isc_event_free(&event); + ns_client_detach(&client); } 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) +query_prefetch(ns_client_t *client, dns_name_t *qname, + dns_rdataset_t *rdataset) { - dns_name_t *name, *mname; - dns_rdataset_t *rdataset, *mrdataset, *sigrdataset; isc_result_t result; + isc_sockaddr_t *peeraddr; + dns_rdataset_t *tmprdataset; + ns_client_t *dummy = NULL; + unsigned int options; - /*% - * 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_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); - if (result == ISC_R_SUCCESS) { - /* - * We've already got an RRset of the given name and type. - */ - CTRACE(ISC_LOG_DEBUG(3), - "query_addrrset: dns_message_findname succeeded: done"); - if (dbuf != NULL) - query_releasename(client, namep); - if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) - mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + if (client->query.prefetch != NULL || + client->view->prefetch_trigger == 0U || + rdataset->ttl > client->view->prefetch_trigger || + (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0) return; - } 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 (client->recursionquota == NULL) { + result = isc_quota_attach(&ns_g_server->recursionquota, + &client->recursionquota); + if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) + result = ns_client_replace(client); + if (result != ISC_R_SUCCESS) + return; + isc_stats_increment(ns_g_server->nsstats, + dns_nsstatscounter_recursclients); } - if (rdataset->trust != dns_trust_secure && - (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 - * response. - */ - query_addrdataset(client, mname, rdataset); - *rdatasetp = NULL; - if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { - /* - * We have a signature. Add it to the response. - */ - ISC_LIST_APPEND(mname->list, sigrdataset, link); - *sigrdatasetp = NULL; + tmprdataset = query_newrdataset(client); + if (tmprdataset == NULL) + return; + if (!TCP(client)) + peeraddr = &client->peeraddr; + else + peeraddr = NULL; + ns_client_attach(client, &dummy); + options = client->query.fetchoptions | DNS_FETCHOPT_PREFETCH; + result = dns_resolver_createfetch3(client->view->resolver, + qname, rdataset->type, NULL, NULL, + NULL, peeraddr, client->message->id, + options, 0, NULL, client->task, + prefetch_done, client, + tmprdataset, NULL, + &client->query.prefetch); + if (result != ISC_R_SUCCESS) { + query_putrdataset(client, &tmprdataset); + ns_client_detach(&dummy); } - CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: done"); + dns_rdataset_clearprefetch(rdataset); } -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) +static inline void +rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_rdataset_t **rdatasetp) { - 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; + if (nodep != NULL && *nodep != NULL) { + REQUIRE(dbp != NULL && *dbp != NULL); + dns_db_detachnode(*dbp, nodep); + } + if (dbp != NULL && *dbp != NULL) + dns_db_detach(dbp); + if (zonep != NULL && *zonep != NULL) + dns_zone_detach(zonep); + if (rdatasetp != NULL && *rdatasetp != NULL && + dns_rdataset_isassociated(*rdatasetp)) + dns_rdataset_disassociate(*rdatasetp); +} - CTRACE(ISC_LOG_DEBUG(3), "query_addsoa"); - /* - * Initialization. - */ - eresult = ISC_R_SUCCESS; - name = NULL; - rdataset = NULL; - node = NULL; +static inline void +rpz_match_clear(dns_rpz_st_t *st) { + rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); + st->m.version = NULL; +} - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); +static inline isc_result_t +rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp) { + REQUIRE(rdatasetp != NULL); - /* - * Don't add the SOA record for test which set "-T nosoa". - */ - if (ns_g_nosoa && (!WANTDNSSEC(client) || !isassociated)) - return (ISC_R_SUCCESS); + CTRACE(ISC_LOG_DEBUG(3), "rpz_ready"); - /* - * 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; + if (*rdatasetp == NULL) { + *rdatasetp = query_newrdataset(client); + if (*rdatasetp == NULL) { + CTRACE(ISC_LOG_ERROR, + "rpz_ready: query_newrdataset failed"); + return (DNS_R_SERVFAIL); } + } else if (dns_rdataset_isassociated(*rdatasetp)) { + dns_rdataset_disassociate(*rdatasetp); } + return (ISC_R_SUCCESS); +} - /* - * 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; +static void +rpz_st_clear(ns_client_t *client) { + dns_rpz_st_t *st = client->query.rpz_st; - dns_fixedname_init(&foundname); - fname = dns_fixedname_name(&foundname); + CTRACE(ISC_LOG_DEBUG(3), "rpz_st_clear"); - 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 (st->m.rdataset != NULL) + query_putrdataset(client, &st->m.rdataset); + rpz_match_clear(st); - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; + rpz_clean(NULL, &st->r.db, NULL, NULL); + if (st->r.ns_rdataset != NULL) + query_putrdataset(client, &st->r.ns_rdataset); + if (st->r.r_rdataset != NULL) + query_putrdataset(client, &st->r.r_rdataset); - if (section == DNS_SECTION_ADDITIONAL) - rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; - query_addrrset(client, &name, &rdataset, sigrdatasetp, NULL, - section); - } + rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL); + if (st->q.rdataset != NULL) + query_putrdataset(client, &st->q.rdataset); + if (st->q.sigrdataset != NULL) + query_putrdataset(client, &st->q.sigrdataset); + st->state = 0; + st->m.type = DNS_RPZ_TYPE_BAD; + st->m.policy = DNS_RPZ_POLICY_MISS; +} - 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); +static dns_rpz_zbits_t +rpz_get_zbits(ns_client_t *client, + dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type) +{ + dns_rpz_st_t *st; + dns_rpz_zbits_t zbits; - return (eresult); -} + REQUIRE(client != NULL); + REQUIRE(client->query.rpz_st != NULL); -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; + st = client->query.rpz_st; - 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); + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits = st->have.client_ip; + break; + case DNS_RPZ_TYPE_QNAME: + zbits = st->have.qname; + break; + case DNS_RPZ_TYPE_IP: + if (ip_type == dns_rdatatype_a) { + zbits = st->have.ipv4; + } else if (ip_type == dns_rdatatype_aaaa) { + zbits = st->have.ipv6; + } else { + zbits = st->have.ip; + } + break; + case DNS_RPZ_TYPE_NSDNAME: + zbits = st->have.nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + if (ip_type == dns_rdatatype_a) { + zbits = st->have.nsipv4; + } else if (ip_type == dns_rdatatype_aaaa) { + zbits = st->have.nsipv6; + } else { + zbits = st->have.nsip; + } + break; + default: + INSIST(0); + break; + } /* - * Get resources and make 'name' be the database origin. + * Choose + * the earliest configured policy zone (rpz->num) + * QNAME over IP over NSDNAME over NSIP (rpz_type) + * the smallest name, + * the longest IP address prefix, + * the lexically smallest address. */ - 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; + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.type >= rpz_type) { + zbits &= DNS_RPZ_ZMASK(st->m.rpz->num); + } else{ + zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1; } } /* - * Find the NS rdataset. + * If the client wants recursion, allow only compatible policies. */ - 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); + if (!RECURSIONOK(client)) + zbits &= st->popt.no_rd_ok; - CTRACE(ISC_LOG_DEBUG(3), "query_addns: done"); - return (eresult); + return (zbits); } -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; +static void +query_rpzfetch(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t type) { isc_result_t result; + isc_sockaddr_t *peeraddr; + dns_rdataset_t *tmprdataset; + ns_client_t *dummy = NULL; + unsigned int options; - /* - * We assume the name data referred to by tname won't go away. - */ + if (client->query.prefetch != NULL) + return; - 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); + if (client->recursionquota == NULL) { + result = isc_quota_attach(&ns_g_server->recursionquota, + &client->recursionquota); + if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) + result = ns_client_replace(client); + if (result != ISC_R_SUCCESS) + return; + isc_stats_increment(ns_g_server->nsstats, + dns_nsstatscounter_recursclients); } - rdatalist = NULL; - result = dns_message_gettemprdatalist(client->message, &rdatalist); + tmprdataset = query_newrdataset(client); + if (tmprdataset == NULL) + return; + if (!TCP(client)) + peeraddr = &client->peeraddr; + else + peeraddr = NULL; + ns_client_attach(client, &dummy); + options = client->query.fetchoptions; + result = dns_resolver_createfetch3(client->view->resolver, qname, type, + NULL, NULL, NULL, peeraddr, + client->message->id, options, 0, + NULL, client->task, prefetch_done, + client, tmprdataset, NULL, + &client->query.prefetch); if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - return (result); + query_putrdataset(client, &tmprdataset); + ns_client_detach(&dummy); } - 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); +} + +/* + * Get an NS, A, or AAAA rrset related to the response for the client + * to check the contents of that rrset for hits by eligible policy zones. + */ +static isc_result_t +rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, + dns_rpz_type_t rpz_type, dns_db_t **dbp, + dns_dbversion_t *version, dns_rdataset_t **rdatasetp, + isc_boolean_t resuming) +{ + dns_rpz_st_t *st; + isc_boolean_t is_zone; + dns_dbnode_t *node; + dns_fixedname_t fixed; + dns_name_t *found; + isc_result_t result; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + + CTRACE(ISC_LOG_DEBUG(3), "rpz_rrset_find"); + + st = client->query.rpz_st; + if ((st->state & DNS_RPZ_RECURSING) != 0) { + INSIST(st->r.r_type == type); + INSIST(dns_name_equal(name, st->r_name)); + INSIST(*rdatasetp == NULL || + !dns_rdataset_isassociated(*rdatasetp)); + INSIST(*dbp == NULL); + st->state &= ~DNS_RPZ_RECURSING; + *dbp = st->r.db; + st->r.db = NULL; + if (*rdatasetp != NULL) + query_putrdataset(client, rdatasetp); + *rdatasetp = st->r.r_rdataset; + st->r.r_rdataset = NULL; + result = st->r.r_result; + if (result == DNS_R_DELEGATION) { + CTRACE(ISC_LOG_ERROR, "RPZ recursing"); + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " rpz_rrset_find(1)", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + result = DNS_R_SERVFAIL; + } + return (result); } - rdataset = NULL; - result = dns_message_gettemprdataset(client->message, &rdataset); + + result = rpz_ready(client, rdatasetp); if (result != ISC_R_SUCCESS) { - dns_message_puttempname(client->message, &aname); - dns_message_puttemprdatalist(client->message, &rdatalist); - dns_message_puttemprdata(client->message, &rdata); + st->m.policy = DNS_RPZ_POLICY_ERROR; 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); + if (*dbp != NULL) { + is_zone = ISC_FALSE; + } else { + dns_zone_t *zone; - 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); + version = NULL; + zone = NULL; + result = query_getdb(client, name, type, 0, &zone, dbp, + &version, &is_zone); + if (result != ISC_R_SUCCESS) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " rpz_rrset_find(2)", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + if (zone != NULL) + dns_zone_detach(&zone); + return (result); + } + if (zone != NULL) + dns_zone_detach(&zone); } - if (aname != NULL) - dns_message_puttempname(client->message, &aname); - return (ISC_R_SUCCESS); + node = NULL; + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); + result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK, + client->now, &node, found, + &cm, &ci, *rdatasetp, NULL); + if (result == DNS_R_DELEGATION && is_zone && USECACHE(client)) { + /* + * Try the cache if we're authoritative for an + * ancestor but not the domain itself. + */ + rpz_clean(NULL, dbp, &node, rdatasetp); + version = NULL; + dns_db_attach(client->view->cachedb, dbp); + result = dns_db_findext(*dbp, name, version, dns_rdatatype_ns, + 0, client->now, &node, found, + &cm, &ci, *rdatasetp, NULL); + } + rpz_clean(NULL, dbp, &node, NULL); + if (result == DNS_R_DELEGATION) { + rpz_clean(NULL, NULL, NULL, rdatasetp); + /* + * Recurse for NS rrset or A or AAAA rrset for an NS. + * Do not recurse for addresses for the query name. + */ + if (rpz_type == DNS_RPZ_TYPE_IP) { + result = DNS_R_NXRRSET; + } else if (!client->view->rpzs->p.nsip_wait_recurse) { + query_rpzfetch(client, name, type); + result = DNS_R_NXRRSET; + } else { + dns_name_copy(name, st->r_name, NULL); + result = query_recurse(client, type, st->r_name, + NULL, NULL, resuming); + if (result == ISC_R_SUCCESS) { + st->state |= DNS_RPZ_RECURSING; + result = DNS_R_DELEGATION; + } + } + } + return (result); } /* - * Mark the RRsets as secure. Update the cache (db) to reflect the - * change in trust level. + * Compute a policy owner name, p_name, in a policy zone given the needed + * policy type and the trigger name. */ -static void -mark_secure(ns_client_t *client, dns_db_t *db, dns_name_t *name, - dns_rdata_rrsig_t *rrsig, dns_rdataset_t *rdataset, - dns_rdataset_t *sigrdataset) +static isc_result_t +rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, + dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_name_t *trig_name) { + dns_offsets_t prefix_offsets; + dns_name_t prefix, *suffix; + unsigned int first, labels; isc_result_t result; - dns_dbnode_t *node = NULL; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - isc_stdtime_t now; - rdataset->trust = dns_trust_secure; - sigrdataset->trust = dns_trust_secure; - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + CTRACE(ISC_LOG_DEBUG(3), "rpz_get_p_name"); /* - * Save the updated secure state. Ignore failures. + * The policy owner name consists of a suffix depending on the type + * and policy zone and a prefix that is the longest possible string + * from the trigger name that keesp the resulting policy owner name + * from being too long. */ - result = dns_db_findnodeext(db, name, ISC_TRUE, &cm, &ci, &node); - if (result != ISC_R_SUCCESS) - return; - - isc_stdtime_get(&now); - dns_rdataset_trimttl(rdataset, sigrdataset, rrsig, now, - client->view->acceptexpired); + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + suffix = &rpz->client_ip; + break; + case DNS_RPZ_TYPE_QNAME: + suffix = &rpz->origin; + break; + case DNS_RPZ_TYPE_IP: + suffix = &rpz->ip; + break; + case DNS_RPZ_TYPE_NSDNAME: + suffix = &rpz->nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + suffix = &rpz->nsip; + break; + default: + INSIST(0); + } - (void)dns_db_addrdataset(db, node, NULL, client->now, rdataset, - 0, NULL); - (void)dns_db_addrdataset(db, node, NULL, client->now, sigrdataset, - 0, NULL); - dns_db_detachnode(db, &node); + /* + * Start with relative version of the full trigger name, + * and trim enough allow the addition of the suffix. + */ + dns_name_init(&prefix, prefix_offsets); + labels = dns_name_countlabels(trig_name); + first = 0; + for (;;) { + dns_name_getlabelsequence(trig_name, first, labels-first-1, + &prefix); + result = dns_name_concatenate(&prefix, suffix, p_name, NULL); + if (result == ISC_R_SUCCESS) + break; + INSIST(result == DNS_R_NAMETOOLONG); + /* + * Trim the trigger name until the combination is not too long. + */ + if (labels-first < 2) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix, + rpz_type, " concatentate()", result); + return (ISC_R_FAILURE); + } + /* + * Complain once about trimming the trigger name. + */ + if (first == 0) { + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix, + rpz_type, " concatentate()", result); + } + ++first; + } + return (ISC_R_SUCCESS); } /* - * Find the secure key that corresponds to rrsig. - * Note: 'keyrdataset' maintains state between successive calls, - * there may be multiple keys with the same keyid. - * Return ISC_FALSE if we have exhausted all the possible keys. + * Look in policy zone rpz for a policy of rpz_type by p_name. + * The self-name (usually the client qname or an NS name) is compared with + * the target of a CNAME policy for the old style passthru encoding. + * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep, + * *rdatasetp, and *policyp. + * The target DNS type, qtype, chooses the best rdataset for *rdatasetp. + * The caller must decide if the found policy is most suitable, including + * better than a previously found policy. + * If it is best, the caller records it in client->query.rpz_st->m. */ -static isc_boolean_t -get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, - dns_rdataset_t *keyrdataset, dst_key_t **keyp) +static isc_result_t +rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, + dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, + dns_rpz_policy_t *policyp) { + dns_fixedname_t foundf; + dns_name_t *found; isc_result_t result; - dns_dbnode_t *node = NULL; - isc_boolean_t secure = ISC_FALSE; dns_clientinfomethods_t cm; dns_clientinfo_t ci; - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + REQUIRE(nodep != NULL); - if (!dns_rdataset_isassociated(keyrdataset)) { - result = dns_db_findnodeext(db, &rrsig->signer, ISC_FALSE, - &cm, &ci, &node); - if (result != ISC_R_SUCCESS) - return (ISC_FALSE); + CTRACE(ISC_LOG_DEBUG(3), "rpz_find_p"); - result = dns_db_findrdataset(db, node, NULL, - dns_rdatatype_dnskey, 0, - client->now, keyrdataset, NULL); - dns_db_detachnode(db, &node); - if (result != ISC_R_SUCCESS) - return (ISC_FALSE); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); - if (keyrdataset->trust != dns_trust_secure) - return (ISC_FALSE); + /* + * Try to find either a CNAME or the type of record demanded by the + * request from the policy zone. + */ + rpz_clean(zonep, dbp, nodep, rdatasetp); + result = rpz_ready(client, rdatasetp); + if (result != ISC_R_SUCCESS) { + CTRACE(ISC_LOG_ERROR, "rpz_ready() failed"); + return (DNS_R_SERVFAIL); + } + *versionp = NULL; + result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp); + if (result != ISC_R_SUCCESS) + return (DNS_R_NXDOMAIN); + dns_fixedname_init(&foundf); + found = dns_fixedname_name(&foundf); - result = dns_rdataset_first(keyrdataset); - } else - result = dns_rdataset_next(keyrdataset); + result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0, + client->now, nodep, found, &cm, &ci, + *rdatasetp, NULL); + /* + * Choose the best rdataset if we found something. + */ + if (result == ISC_R_SUCCESS) { + dns_rdatasetiter_t *rdsiter; - for ( ; result == ISC_R_SUCCESS; - result = dns_rdataset_next(keyrdataset)) { - dns_rdata_t rdata = DNS_RDATA_INIT; - isc_buffer_t b; + rdsiter = NULL; + result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, + &rdsiter); + if (result != ISC_R_SUCCESS) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, + rpz_type, " allrdatasets()", result); + CTRACE(ISC_LOG_ERROR, + "rpz_find_p: allrdatasets failed"); + return (DNS_R_SERVFAIL); + } + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) { + dns_rdatasetiter_current(rdsiter, *rdatasetp); + if ((*rdatasetp)->type == dns_rdatatype_cname || + (*rdatasetp)->type == qtype) + break; + dns_rdataset_disassociate(*rdatasetp); + } + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_SUCCESS) { + if (result != ISC_R_NOMORE) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, + p_name, rpz_type, + " rdatasetiter", result); + CTRACE(ISC_LOG_ERROR, + "rpz_find_p: rdatasetiter_destroy " + "failed"); + return (DNS_R_SERVFAIL); + } + /* + * Ask again to get the right DNS_R_DNAME/NXRRSET/... + * result if there is neither a CNAME nor target type. + */ + if (dns_rdataset_isassociated(*rdatasetp)) + dns_rdataset_disassociate(*rdatasetp); + dns_db_detachnode(*dbp, nodep); - dns_rdataset_current(keyrdataset, &rdata); - isc_buffer_init(&b, rdata.data, rdata.length); - isc_buffer_add(&b, rdata.length); - result = dst_key_fromdns(&rrsig->signer, rdata.rdclass, &b, - client->mctx, keyp); - if (result != ISC_R_SUCCESS) - continue; - if (rrsig->algorithm == (dns_secalg_t)dst_key_alg(*keyp) && - rrsig->keyid == (dns_keytag_t)dst_key_id(*keyp) && - dst_key_iszonekey(*keyp)) { - secure = ISC_TRUE; - break; + if (qtype == dns_rdatatype_rrsig || + qtype == dns_rdatatype_sig) + result = DNS_R_NXRRSET; + else + result = dns_db_findext(*dbp, p_name, *versionp, + qtype, 0, client->now, + nodep, found, &cm, &ci, + *rdatasetp, NULL); } - dst_key_free(keyp); } - return (secure); + switch (result) { + case ISC_R_SUCCESS: + if ((*rdatasetp)->type != dns_rdatatype_cname) { + *policyp = DNS_RPZ_POLICY_RECORD; + } else { + *policyp = dns_rpz_decode_cname(rpz, *rdatasetp, + self_name); + if ((*policyp == DNS_RPZ_POLICY_RECORD || + *policyp == DNS_RPZ_POLICY_WILDCNAME) && + qtype != dns_rdatatype_cname && + qtype != dns_rdatatype_any) + return (DNS_R_CNAME); + } + return (ISC_R_SUCCESS); + case DNS_R_NXRRSET: + *policyp = DNS_RPZ_POLICY_NODATA; + return (result); + case DNS_R_DNAME: + /* + * DNAME policy RRs have very few if any uses that are not + * 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_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. + */ + case DNS_R_NXDOMAIN: + case DNS_R_EMPTYNAME: + return (DNS_R_NXDOMAIN); + default: + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, + "", result); + CTRACE(ISC_LOG_ERROR, + "rpz_find_p: unexpected result"); + return (DNS_R_SERVFAIL); + } } -static isc_boolean_t -verify(dst_key_t *key, dns_name_t *name, dns_rdataset_t *rdataset, - dns_rdata_t *rdata, ns_client_t *client) +static void +rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix, + isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp, + dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, + dns_dbversion_t *version) { - isc_result_t result; - dns_fixedname_t fixed; - isc_boolean_t ignore = ISC_FALSE; - - dns_fixedname_init(&fixed); + dns_rdataset_t *trdataset = NULL; -again: - result = dns_dnssec_verify3(name, rdataset, key, ignore, - client->view->maxbits, client->mctx, - rdata, NULL); - if (result == DNS_R_SIGEXPIRED && client->view->acceptexpired) { - ignore = ISC_TRUE; - goto again; + rpz_match_clear(st); + st->m.rpz = rpz; + st->m.type = rpz_type; + st->m.policy = policy; + dns_name_copy(p_name, st->p_name, NULL); + st->m.prefix = prefix; + st->m.result = result; + SAVE(st->m.zone, *zonep); + SAVE(st->m.db, *dbp); + SAVE(st->m.node, *nodep); + if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { + /* + * Save the replacement rdataset from the policy + * and make the previous replacement rdataset scratch. + */ + SAVE(trdataset, st->m.rdataset); + SAVE(st->m.rdataset, *rdatasetp); + SAVE(*rdatasetp, trdataset); + st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl); + } else { + st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl); } - if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) - return (ISC_TRUE); - return (ISC_FALSE); + SAVE(st->m.version, version); } /* - * Validate the rdataset if possible with available records. + * Check this address in every eligible policy zone. */ -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) +static isc_result_t +rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) { + dns_rpz_zones_t *rpzs; + dns_rpz_st_t *st; + dns_rpz_zone_t *rpz; + dns_rpz_prefix_t prefix; + dns_rpz_num_t rpz_num; + dns_fixedname_t ip_namef, p_namef; + dns_name_t *ip_name, *p_name; + dns_zone_t *p_zone; + dns_db_t *p_db; + dns_dbversion_t *p_version; + dns_dbnode_t *p_node; + dns_rpz_policy_t policy; isc_result_t result; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_rrsig_t rrsig; - dst_key_t *key = NULL; - dns_rdataset_t keyrdataset; - - if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) - return (ISC_FALSE); - for (result = dns_rdataset_first(sigrdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(sigrdataset)) { + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip"); - dns_rdata_reset(&rdata); - dns_rdataset_current(sigrdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &rrsig, NULL); - if (result != ISC_R_SUCCESS) - return (ISC_FALSE); - if (!dns_resolver_algorithm_supported(client->view->resolver, - name, rrsig.algorithm)) - continue; - if (!dns_name_issubdomain(name, &rrsig.signer)) - continue; - dns_rdataset_init(&keyrdataset); - do { - if (!get_key(client, db, &rrsig, &keyrdataset, &key)) - break; - if (verify(key, name, rdataset, &rdata, client)) { - dst_key_free(&key); - dns_rdataset_disassociate(&keyrdataset); - mark_secure(client, db, name, &rrsig, - rdataset, sigrdataset); - return (ISC_TRUE); - } - dst_key_free(&key); - } while (1); - if (dns_rdataset_isassociated(&keyrdataset)) - dns_rdataset_disassociate(&keyrdataset); - } - return (ISC_FALSE); -} + dns_fixedname_init(&ip_namef); + ip_name = dns_fixedname_name(&ip_namef); -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; + p_zone = NULL; + p_db = NULL; + p_node = NULL; - 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; - } + rpzs = client->view->rpzs; + st = client->query.rpz_st; + while (zbits != 0) { + rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr, + ip_name, &prefix); + if (rpz_num == DNS_RPZ_INVALID_NUM) + break; + zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1); - /* - * 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; + /* + * Do not try applying policy zones that cannot replace a + * previously found policy zone. + * Stop looking if the next best choice cannot + * replace what we already have. + */ + rpz = rpzs->zones[rpz_num]; + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.rpz->num < rpz->num) + break; + if (st->m.rpz->num == rpz->num && + (st->m.type < rpz_type || + st->m.prefix > prefix)) + break; } - } 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. + * Get the policy for a prefix at least as long + * as the prefix of the entry we had before. */ - dbuf = NULL; - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); + dns_fixedname_init(&p_namef); + p_name = dns_fixedname_name(&p_namef); + result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name); + if (result != ISC_R_SUCCESS) + continue; + result = rpz_find_p(client, ip_name, qtype, + p_name, rpz, rpz_type, + &p_zone, &p_db, &p_version, &p_node, + p_rdatasetp, &policy); + switch (result) { + case DNS_R_NXDOMAIN: + /* + * Continue after a policy record that is missing + * contrary to the summary data. The summary + * data can out of date during races with and among + * policy zone updates. + */ + CTRACE(ISC_LOG_ERROR, + "rpz_rewrite_ip: mismatched summary data; " + "continuing"); + continue; + case DNS_R_SERVFAIL: + rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + default: + /* + * Forget this policy if it is not preferable + * to the previously found policy. + * If this policy is not good, then stop looking + * because none of the later policy zones would work. + * + * With more than one applicable policy, prefer + * the earliest configured policy, + * client-IP over QNAME over IP over NSDNAME over NSIP, + * the longest prefix + * the lexically smallest address. + * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num. + * We can compare new and current p_name because + * both are of the same type and in the same zone. + * The tests above eliminate other reasons to + * reject this policy. If this policy can't work, + * then neither can later zones. + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS && + rpz->num == st->m.rpz->num && + (st->m.type == rpz_type && + st->m.prefix == prefix && + 0 > dns_name_rdatacompare(st->p_name, p_name))) + break; - if (node != NULL) - dns_db_detachnode(db, &node); - dns_db_detach(&db); + /* + * Stop checking after saving an enabled hit in this + * policy zone. The radix tree in the policy zone + * ensures that we found the longest match. + */ + if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { + CTRACE(ISC_LOG_DEBUG(3), + "rpz_rewrite_ip: rpz_save_p"); + rpz_save_p(st, rpz, rpz_type, + policy, p_name, prefix, result, + &p_zone, &p_db, &p_node, + p_rdatasetp, p_version); + break; + } - RESTORE(db, zdb); - RESTORE(fname, zfname); - RESTORE(rdataset, zrdataset); - RESTORE(sigrdataset, zsigrdataset); + /* + * Log DNS_RPZ_POLICY_DISABLED zones + * and try the next eligible policy zone. + */ + rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, + p_zone, p_name, NULL, rpz_num); + } } - /* - * 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; + rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); + return (ISC_R_SUCCESS); +} - 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; +/* + * Check the IP addresses in the A or AAAA rrsets for name against + * all eligible rpz_type (IP or NSIP) response policy rewrite rules. + */ +static isc_result_t +rpz_rewrite_ip_rrset(ns_client_t *client, + dns_name_t *name, dns_rdatatype_t qtype, + dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type, + dns_db_t **ip_dbp, dns_dbversion_t *ip_version, + dns_rdataset_t **ip_rdatasetp, + dns_rdataset_t **p_rdatasetp, isc_boolean_t resuming) +{ + dns_rpz_zbits_t zbits; + isc_netaddr_t netaddr; + struct in_addr ina; + struct in6_addr in6a; + isc_result_t result; - /* - * 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; + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrset"); + + zbits = rpz_get_zbits(client, ip_type, rpz_type); + if (zbits == 0) + return (ISC_R_SUCCESS); /* - * If the client doesn't want DNSSEC we can discard the sigrdataset - * now. + * Get the A or AAAA rdataset. */ - if (!WANTDNSSEC(client)) - query_putrdataset(client, &sigrdataset); - query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, - DNS_SECTION_AUTHORITY); + result = rpz_rrset_find(client, name, ip_type, rpz_type, ip_dbp, + ip_version, ip_rdatasetp, resuming); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_GLUE: + case DNS_R_ZONECUT: + break; + case DNS_R_EMPTYNAME: + case DNS_R_EMPTYWILD: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + case ISC_R_NOTFOUND: + return (ISC_R_SUCCESS); + case DNS_R_DELEGATION: + case DNS_R_DUPLICATE: + case DNS_R_DROP: + return (result); + case DNS_R_CNAME: + case DNS_R_DNAME: + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type, + " NS address rewrite rrset", result); + return (ISC_R_SUCCESS); + default: + if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { + client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, + rpz_type, " NS address rewrite rrset", + result); + } + CTRACE(ISC_LOG_ERROR, + "rpz_rewrite_ip_rrset: unexpected result"); + return (DNS_R_SERVFAIL); + } - 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); + /* + * Check all of the IP addresses in the rdataset. + */ + for (result = dns_rdataset_first(*ip_rdatasetp); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(*ip_rdatasetp)) { + + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(*ip_rdatasetp, &rdata); + switch (rdata.type) { + case dns_rdatatype_a: + INSIST(rdata.length == 4); + memmove(&ina.s_addr, rdata.data, 4); + isc_netaddr_fromin(&netaddr, &ina); + break; + case dns_rdatatype_aaaa: + INSIST(rdata.length == 16); + memmove(in6a.s6_addr, rdata.data, 16); + isc_netaddr_fromin6(&netaddr, &in6a); + break; + default: + continue; + } + + result = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type, + zbits, p_rdatasetp); + if (result != ISC_R_SUCCESS) + return (result); } -} -static void -fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) { - if (*rdataset == NULL) - *rdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(*rdataset)) - dns_rdataset_disassociate(*rdataset); + return (ISC_R_SUCCESS); } -static void -fixfname(ns_client_t *client, dns_name_t **fname, isc_buffer_t **dbuf, - isc_buffer_t *nbuf) +/* + * Look for IP addresses in A and AAAA rdatasets + * that trigger all eligible IP or NSIP policy rules. + */ +static isc_result_t +rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rdataset_t **ip_rdatasetp, isc_boolean_t resuming) { - if (*fname == NULL) { - *dbuf = query_getnamebuf(client); - if (*dbuf == NULL) - return; - *fname = query_newname(client, *dbuf, nbuf); + dns_rpz_st_t *st; + dns_dbversion_t *ip_version; + dns_db_t *ip_db; + dns_rdataset_t *p_rdataset; + isc_result_t result; + + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrsets"); + + st = client->query.rpz_st; + ip_version = NULL; + ip_db = NULL; + p_rdataset = NULL; + if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && + (qtype == dns_rdatatype_a || + qtype == dns_rdatatype_any || + rpz_type == DNS_RPZ_TYPE_NSIP)) { + /* + * Rewrite based on an IPv4 address that will appear + * in the ANSWER section or if we are checking IP addresses. + */ + result = rpz_rewrite_ip_rrset(client, name, qtype, + rpz_type, dns_rdatatype_a, + &ip_db, ip_version, ip_rdatasetp, + &p_rdataset, resuming); + if (result == ISC_R_SUCCESS) + st->state |= DNS_RPZ_DONE_IPv4; + } else { + result = ISC_R_SUCCESS; + } + if (result == ISC_R_SUCCESS && + (qtype == dns_rdatatype_aaaa || + qtype == dns_rdatatype_any || + rpz_type == DNS_RPZ_TYPE_NSIP)) { + /* + * Rewrite based on IPv6 addresses that will appear + * in the ANSWER section or if we are checking IP addresses. + */ + result = rpz_rewrite_ip_rrset(client, name, qtype, + rpz_type, dns_rdatatype_aaaa, + &ip_db, ip_version, ip_rdatasetp, + &p_rdataset, resuming); } + if (ip_db != NULL) + dns_db_detach(&ip_db); + query_putrdataset(client, &p_rdataset); + return (result); } -static void -query_addds(ns_client_t *client, dns_db_t *db, dns_dbnode_t *node, - dns_dbversion_t *version, dns_name_t *name) +/* + * Try to rewrite a request for a qtype rdataset based on the trigger name + * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME). + * Record the results including the replacement rdataset if any + * in client->query.rpz_st. + * *rdatasetp is a scratch rdataset. + */ +static isc_result_t +rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name, + dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t allowed_zbits, dns_rdataset_t **rdatasetp) { - dns_fixedname_t fixed; - dns_name_t *fname = NULL; - dns_name_t *rname; - dns_rdataset_t *rdataset, *sigrdataset; - isc_buffer_t *dbuf, b; + dns_rpz_zones_t *rpzs; + dns_rpz_zone_t *rpz; + dns_rpz_st_t *st; + dns_fixedname_t p_namef; + dns_name_t *p_name; + dns_rpz_zbits_t zbits; + dns_rpz_num_t rpz_num; + dns_zone_t *p_zone; + dns_db_t *p_db; + dns_dbversion_t *p_version; + dns_dbnode_t *p_node; + dns_rpz_policy_t policy; isc_result_t result; - unsigned int count; - CTRACE(ISC_LOG_DEBUG(3), "query_addds"); - rname = NULL; - rdataset = NULL; - sigrdataset = NULL; + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name"); - /* - * We'll need some resources... - */ - rdataset = query_newrdataset(client); - sigrdataset = query_newrdataset(client); - if (rdataset == NULL || sigrdataset == NULL) - goto cleanup; + zbits = rpz_get_zbits(client, qtype, rpz_type); + zbits &= allowed_zbits; + if (zbits == 0) + return (ISC_R_SUCCESS); - /* - * 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; + rpzs = client->view->rpzs; /* - * 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(). + * Use the summary database to find the bit mask of policy zones + * with policies for this trigger name. We do this even if there + * is only one eligible policy zone so that wildcard triggers + * are matched correctly, and not into their parent. */ - result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY); - if (result != ISC_R_SUCCESS) - goto cleanup; + zbits = dns_rpz_find_name(rpzs, rpz_type, zbits, trig_name); + if (zbits == 0) + return (ISC_R_SUCCESS); - 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; + dns_fixedname_init(&p_namef); + p_name = dns_fixedname_name(&p_namef); - ISC_LIST_APPEND(rname->list, rdataset, link); - ISC_LIST_APPEND(rname->list, sigrdataset, link); - rdataset = NULL; - sigrdataset = NULL; - return; + p_zone = NULL; + p_db = NULL; + p_node = NULL; + + st = client->query.rpz_st; - addnsec3: - if (!dns_db_iszone(db)) - goto cleanup; /* - * Add the NSEC3 which proves the DS does not exist. + * Check the trigger name in every policy zone that the summary data + * says has a hit for the trigger name. + * Most of the time there are no eligible zones and the summary data + * keeps us from getting this far. + * We check the most eligible zone first and so usually check only + * one policy zone. */ - 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)) + for (rpz_num = 0; zbits != 0; ++rpz_num, zbits >>= 1) { + if ((zbits & 1) == 0) + continue; + + /* + * Do not check policy zones that cannot replace a previously + * found policy. + */ + rpz = rpzs->zones[rpz_num]; + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.rpz->num < rpz->num) + break; + if (st->m.rpz->num == rpz->num && + st->m.type < rpz_type) + break; + } + + /* + * Get the next policy zone's record for this trigger name. + */ + result = rpz_get_p_name(client, p_name, rpz, rpz_type, + trig_name); + if (result != ISC_R_SUCCESS) + continue; + result = rpz_find_p(client, trig_name, qtype, p_name, + rpz, rpz_type, + &p_zone, &p_db, &p_version, &p_node, + rdatasetp, &policy); + switch (result) { + case DNS_R_NXDOMAIN: + /* + * Continue after a missing policy record + * contrary to the summary data. The summary + * data can out of date during races with and among + * policy zone updates. + */ + CTRACE(ISC_LOG_ERROR, + "rpz_rewrite_name: mismatched summary data; " + "continuing"); + continue; + case DNS_R_SERVFAIL: + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + default: + /* + * With more than one applicable policy, prefer + * the earliest configured policy, + * client-IP over QNAME over IP over NSDNAME over NSIP, + * and the smallest name. + * We known st->m.rpz->num >= rpz->num and either + * st->m.rpz->num > rpz->num or st->m.type >= rpz_type + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS && + rpz->num == st->m.rpz->num && + (st->m.type < rpz_type || + (st->m.type == rpz_type && + 0 >= dns_name_compare(p_name, st->p_name)))) + continue; +#if 0 + /* + * This code would block a customer reported information + * leak of rpz rules by rewriting requests in the + * rpz-ip, rpz-nsip, rpz-nsdname,and rpz-passthru TLDs. + * Without this code, a bad guy could request + * 24.0.3.2.10.rpz-ip. to find the policy rule for + * 10.2.3.0/14. It is an insignificant leak and this + * code is not worth its cost, because the bad guy + * could publish "evil.com A 10.2.3.4" and request + * evil.com to get the same information. + * Keep code with "#if 0" in case customer demand + * is irresistible. + * + * We have the less frequent case of a triggered + * policy. Check that we have not trigger on one + * of the pretend RPZ TLDs. + * This test would make it impossible to rewrite + * names in TLDs that start with "rpz-" should + * ICANN ever allow such TLDs. + */ + unsigned int labels; + labels = dns_name_countlabels(trig_name); + if (labels >= 2) { + dns_label_t label; + + dns_name_getlabel(trig_name, labels-2, &label); + if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 && + strncasecmp((const char *)label.base+1, + DNS_RPZ_PREFIX, + sizeof(DNS_RPZ_PREFIX)-1) == 0) + continue; + } +#endif + if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { + CTRACE(ISC_LOG_DEBUG(3), + "rpz_rewrite_name: rpz_save_p"); + rpz_save_p(st, rpz, rpz_type, + policy, p_name, 0, result, + &p_zone, &p_db, &p_node, + rdatasetp, p_version); + /* + * After a hit, higher numbered policy zones + * are irrelevant + */ + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + return (ISC_R_SUCCESS); + } + /* + * Log DNS_RPZ_POLICY_DISABLED zones + * and try the next eligible policy zone. + */ + rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, + p_zone, p_name, NULL, rpz_num); + break; + } + } + + rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + return (ISC_R_SUCCESS); +} + +static void +rpz_rewrite_ns_skip(ns_client_t *client, dns_name_t *nsname, + isc_result_t result, int level, const char *str) +{ + dns_rpz_st_t *st; + + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ns_skip"); + + st = client->query.rpz_st; + + if (str != NULL) + rpz_log_fail(client, level, nsname, DNS_RPZ_TYPE_NSIP, + str, result); + if (st->r.ns_rdataset != NULL && + dns_rdataset_isassociated(st->r.ns_rdataset)) + dns_rdataset_disassociate(st->r.ns_rdataset); + + st->r.label--; +} + +/* + * Look for response policy zone QNAME, NSIP, and NSDNAME rewriting. + */ +static isc_result_t +rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, + isc_result_t qresult, isc_boolean_t resuming, + dns_rdataset_t *ordataset, dns_rdataset_t *osigset) +{ + dns_rpz_zones_t *rpzs; + dns_rpz_st_t *st; + dns_rdataset_t *rdataset; + dns_fixedname_t nsnamef; + dns_name_t *nsname; + int qresult_type; + dns_rpz_zbits_t zbits; + isc_result_t result = ISC_R_SUCCESS; + dns_rpz_have_t have; + dns_rpz_popt_t popt; + int rpz_ver; + + CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite"); + + rpzs = client->view->rpzs; + st = client->query.rpz_st; + + if (rpzs == NULL || + (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0)) + return (DNS_R_DISALLOWED); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + if (rpzs->p.num_zones == 0 || + (!RECURSIONOK(client) && rpzs->p.no_rd_ok == 0) || + !rpz_ck_dnssec(client, qresult, ordataset, osigset)) + { + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + return (DNS_R_DISALLOWED); + } + have = rpzs->have; + popt = rpzs->p; + rpz_ver = rpzs->rpz_ver; + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + + if (st == NULL) { + st = isc_mem_get(client->mctx, sizeof(*st)); + if (st == NULL) + return (ISC_R_NOMEMORY); + st->state = 0; + } + if (st->state == 0) { + st->state |= DNS_RPZ_ACTIVE; + memset(&st->m, 0, sizeof(st->m)); + st->m.type = DNS_RPZ_TYPE_BAD; + st->m.policy = DNS_RPZ_POLICY_MISS; + st->m.ttl = ~0; + memset(&st->r, 0, sizeof(st->r)); + memset(&st->q, 0, sizeof(st->q)); + dns_fixedname_init(&st->_p_namef); + dns_fixedname_init(&st->_r_namef); + dns_fixedname_init(&st->_fnamef); + st->p_name = dns_fixedname_name(&st->_p_namef); + st->r_name = dns_fixedname_name(&st->_r_namef); + st->fname = dns_fixedname_name(&st->_fnamef); + st->have = have; + st->popt = popt; + st->rpz_ver = rpz_ver; + client->query.rpz_st = st; + } + + /* + * There is nothing to rewrite if the main query failed. + */ + switch (qresult) { + case ISC_R_SUCCESS: + case DNS_R_GLUE: + case DNS_R_ZONECUT: + qresult_type = 0; + break; + case DNS_R_EMPTYNAME: + case DNS_R_NXRRSET: + case DNS_R_NXDOMAIN: + case DNS_R_EMPTYWILD: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_CNAME: + case DNS_R_DNAME: + qresult_type = 1; + break; + case DNS_R_DELEGATION: + case ISC_R_NOTFOUND: + /* + * If recursion is on, do only tentative rewriting. + * If recursion is off, this the normal and only time we + * can rewrite. + */ + if (RECURSIONOK(client)) + qresult_type = 2; + else + qresult_type = 1; + break; + case ISC_R_FAILURE: + case ISC_R_TIMEDOUT: + case DNS_R_BROKENCHAIN: + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname, + DNS_RPZ_TYPE_QNAME, + " stop on qresult in rpz_rewrite()", qresult); + return (ISC_R_SUCCESS); + default: + rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname, + DNS_RPZ_TYPE_QNAME, + " stop on unrecognized qresult in rpz_rewrite()", + qresult); + return (ISC_R_SUCCESS); + } + + rdataset = NULL; + + if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) != + (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) { + isc_netaddr_t netaddr; + dns_rpz_zbits_t allowed; + + if (qresult_type == 2) { + /* + * This request needs recursion that has not been done. + * Get bits for the policy zones that do not need + * to wait for the results of recursion. + */ + allowed = st->have.qname_skip_recurse; + if (allowed == 0) + return (ISC_R_SUCCESS); + } else { + allowed = DNS_RPZ_ALL_ZBITS; + } + + /* + * Check once for triggers for the client IP address. + */ + if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) { + zbits = rpz_get_zbits(client, dns_rdatatype_none, + DNS_RPZ_TYPE_CLIENT_IP); + zbits &= allowed; + if (zbits != 0) { + isc_netaddr_fromsockaddr(&netaddr, + &client->peeraddr); + result = rpz_rewrite_ip(client, &netaddr, qtype, + DNS_RPZ_TYPE_CLIENT_IP, + zbits, &rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + } + + /* + * Check triggers for the query name if this is the first time + * for the current qname. + * There is a first time for each name in a CNAME chain + */ + if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { + result = rpz_rewrite_name(client, client->query.qname, + qtype, DNS_RPZ_TYPE_QNAME, + allowed, &rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * Check IPv4 addresses in A RRs next. + * Reset to the start of the NS names. + */ + st->r.label = dns_name_countlabels(client->query.qname); + st->state &= ~(DNS_RPZ_DONE_QNAME_IP | + DNS_RPZ_DONE_IPv4); + + } + + /* + * Quit if this was an attempt to find a qname or + * client-IP trigger before recursion. + * We will be back if no pre-recursion triggers hit. + * For example, consider 2 policy zones, both with qname and + * IP address triggers. If the qname misses the 1st zone, + * then we cannot know whether a hit for the qname in the + * 2nd zone matters until after recursing to get the A RRs and + * testing them in the first zone. + * Do not bother saving the work from this attempt, + * because recusion is so slow. + */ + if (qresult_type == 2) + goto cleanup; + + /* + * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP + * is reset at the end of dealing with each CNAME. + */ + st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME); + } + + /* + * Check known IP addresses for the query name if the database + * lookup resulted in some addresses (qresult_type == 0) + * and if we have not already checked them. + * Any recursion required for the query has already happened. + * Do not check addresses that will not be in the ANSWER section. + */ + if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == 0 && + rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) { + result = rpz_rewrite_ip_rrsets(client, + client->query.qname, qtype, + DNS_RPZ_TYPE_IP, + &rdataset, resuming); + if (result != ISC_R_SUCCESS) + goto cleanup; + /* + * We are finished checking the IP addresses for the qname. + * Start with IPv4 if we will check NS IP addesses. + */ + st->state |= DNS_RPZ_DONE_QNAME_IP; + st->state &= ~DNS_RPZ_DONE_IPv4; + } + + /* + * Stop looking for rules if there are none of the other kinds + * that could override what we already have. + */ + if (rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSDNAME) == 0 && + rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSIP) == 0) { + result = ISC_R_SUCCESS; goto cleanup; - query_addrrset(client, &fname, &rdataset, &sigrdataset, dbuf, - DNS_SECTION_AUTHORITY); + } + + dns_fixedname_init(&nsnamef); + dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); + while (st->r.label > st->popt.min_ns_labels) { + /* + * Get NS rrset for each domain in the current qname. + */ + if (st->r.label == dns_name_countlabels(client->query.qname)) { + nsname = client->query.qname; + } else { + nsname = dns_fixedname_name(&nsnamef); + dns_name_split(client->query.qname, st->r.label, + NULL, nsname); + } + if (st->r.ns_rdataset == NULL || + !dns_rdataset_isassociated(st->r.ns_rdataset)) + { + dns_db_t *db = NULL; + result = rpz_rrset_find(client, nsname, + dns_rdatatype_ns, + DNS_RPZ_TYPE_NSDNAME, + &db, NULL, &st->r.ns_rdataset, + resuming); + if (db != NULL) + dns_db_detach(&db); + if (st->m.policy == DNS_RPZ_POLICY_ERROR) + goto cleanup; + switch (result) { + case ISC_R_SUCCESS: + result = dns_rdataset_first(st->r.ns_rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + st->state &= ~(DNS_RPZ_DONE_NSDNAME | + DNS_RPZ_DONE_IPv4); + break; + case DNS_R_DELEGATION: + case DNS_R_DUPLICATE: + case DNS_R_DROP: + goto cleanup; + case DNS_R_EMPTYNAME: + case DNS_R_NXRRSET: + case DNS_R_EMPTYWILD: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + case ISC_R_NOTFOUND: + case DNS_R_CNAME: + case DNS_R_DNAME: + rpz_rewrite_ns_skip(client, nsname, result, + 0, NULL); + continue; + case ISC_R_TIMEDOUT: + case DNS_R_BROKENCHAIN: + case ISC_R_FAILURE: + rpz_rewrite_ns_skip(client, nsname, result, + DNS_RPZ_DEBUG_LEVEL3, + " NS rpz_rrset_find() "); + continue; + default: + rpz_rewrite_ns_skip(client, nsname, result, + DNS_RPZ_INFO_LEVEL, + " unrecognized NS" + " rpz_rrset_find() "); + continue; + } + } + /* + * Check all NS names. + */ + do { + dns_rdata_ns_t ns; + dns_rdata_t nsrdata = DNS_RDATA_INIT; + + dns_rdataset_current(st->r.ns_rdataset, &nsrdata); + result = dns_rdata_tostruct(&nsrdata, &ns, NULL); + dns_rdata_reset(&nsrdata); + if (result != ISC_R_SUCCESS) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, + nsname, DNS_RPZ_TYPE_NSIP, + " rdata_tostruct()", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + goto cleanup; + } + /* + * Do nothing about "NS ." + */ + if (dns_name_equal(&ns.name, dns_rootname)) { + dns_rdata_freestruct(&ns); + result = dns_rdataset_next(st->r.ns_rdataset); + continue; + } + /* + * Check this NS name if we did not handle it + * during a previous recursion. + */ + if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) { + result = rpz_rewrite_name(client, &ns.name, + qtype, + DNS_RPZ_TYPE_NSDNAME, + DNS_RPZ_ALL_ZBITS, + &rdataset); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ns); + goto cleanup; + } + st->state |= DNS_RPZ_DONE_NSDNAME; + } + /* + * Check all IP addresses for this NS name. + */ + result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype, + DNS_RPZ_TYPE_NSIP, + &rdataset, resuming); + dns_rdata_freestruct(&ns); + if (result != ISC_R_SUCCESS) + goto cleanup; + st->state &= ~(DNS_RPZ_DONE_NSDNAME | + DNS_RPZ_DONE_IPv4); + result = dns_rdataset_next(st->r.ns_rdataset); + } while (result == ISC_R_SUCCESS); + dns_rdataset_disassociate(st->r.ns_rdataset); + st->r.label--; + + if (rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSDNAME) == 0 && + rpz_get_zbits(client, dns_rdatatype_any, + DNS_RPZ_TYPE_NSIP) == 0) + break; + } + /* - * Did we find the closest provable encloser instead? - * If so add the nearest to the closest provable encloser. + * Use the best hit, if any. */ - 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); + result = ISC_R_SUCCESS; + +cleanup: + if (st->m.policy != DNS_RPZ_POLICY_MISS && + st->m.policy != DNS_RPZ_POLICY_ERROR && + st->m.rpz->policy != DNS_RPZ_POLICY_GIVEN) + st->m.policy = st->m.rpz->policy; + if (st->m.policy == DNS_RPZ_POLICY_MISS || + st->m.policy == DNS_RPZ_POLICY_PASSTHRU || + st->m.policy == DNS_RPZ_POLICY_ERROR) { + if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU && + result != DNS_R_DELEGATION) + rpz_log_rewrite(client, ISC_FALSE, st->m.policy, + st->m.type, st->m.zone, st->p_name, + NULL, st->m.rpz->num); + rpz_match_clear(st); + } + if (st->m.policy == DNS_RPZ_POLICY_ERROR) { + CTRACE(ISC_LOG_ERROR, "SERVFAIL due to RPZ policy"); + st->m.type = DNS_RPZ_TYPE_BAD; + result = DNS_R_SERVFAIL; } + query_putrdataset(client, &rdataset); + if ((st->state & DNS_RPZ_RECURSING) == 0) + rpz_clean(NULL, &st->r.db, NULL, &st->r.ns_rdataset); - cleanup: - if (rdataset != NULL) - query_putrdataset(client, &rdataset); - if (sigrdataset != NULL) - query_putrdataset(client, &sigrdataset); - if (fname != NULL) - query_releasename(client, &fname); + return (result); } -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) +/* + * See if response policy zone rewriting is allowed by a lack of interest + * by the client in DNSSEC or a lack of signatures. + */ +static isc_boolean_t +rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { - 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; + dns_fixedname_t fixed; + dns_name_t *found; + dns_rdataset_t trdataset; + dns_rdatatype_t type; 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; + CTRACE(ISC_LOG_DEBUG(3), "rpz_ck_dnssec"); - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) + return (ISC_TRUE); /* - * 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 + * We do not know if there are signatures if we have not recursed + * for them. */ - options = client->query.dboptions | DNS_DBFIND_NOWILD; - dns_fixedname_init(&wfixed); - wname = dns_fixedname_name(&wfixed); - again: - have_wname = ISC_FALSE; + if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) + return (ISC_FALSE); + + if (sigrdataset == NULL) + return (ISC_TRUE); + if (dns_rdataset_isassociated(sigrdataset)) + return (ISC_FALSE); + /* - * We'll need some resources... + * We are happy to rewrite nothing. */ - 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; + if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) + return (ISC_TRUE); + /* + * Do not rewrite if there is any sign of signatures. + */ + if (rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3 || + rdataset->type == dns_rdatatype_rrsig) + return (ISC_FALSE); - 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); + /* + * Look for a signature in a negative cache rdataset. + */ + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) == 0) + return (ISC_TRUE); + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + dns_rdataset_init(&trdataset); + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_ncache_current(rdataset, found, &trdataset); + type = trdataset.type; + dns_rdataset_disassociate(&trdataset); + if (type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3 || + type == dns_rdatatype_rrsig) + return (ISC_FALSE); + } + return (ISC_TRUE); +} - 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); +/* + * Extract a network address from the RDATA of an A or AAAA + * record. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTIMPLEMENTED The rdata is not a known address type. + */ +static isc_result_t +rdata_tonetaddr(const dns_rdata_t *rdata, isc_netaddr_t *netaddr) { + struct in_addr ina; + struct in6_addr in6a; - /* - * 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); - } + switch (rdata->type) { + case dns_rdatatype_a: + INSIST(rdata->length == 4); + memmove(&ina.s_addr, rdata->data, 4); + isc_netaddr_fromin(netaddr, &ina); + return (ISC_R_SUCCESS); + case dns_rdatatype_aaaa: + INSIST(rdata->length == 16); + memmove(in6a.s6_addr, rdata->data, 16); + isc_netaddr_fromin6(netaddr, &in6a); + return (ISC_R_SUCCESS); + default: + return (ISC_R_NOTIMPLEMENTED); + } +} - if (rdataset == NULL) - rdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); +#define NS_NAME_INIT(A,B) \ + { \ + DNS_NAME_MAGIC, \ + A, sizeof(A), sizeof(B), \ + DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, \ + B, NULL, { (void *)-1, (void *)-1}, \ + {NULL, NULL} \ + } - if (sigrdataset == NULL) - sigrdataset = query_newrdataset(client); - else if (dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); +static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 }; +static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 }; +static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 }; - 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); +static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA"; - 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); +static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA"; - if (ispositive) - goto cleanup; +static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA"; - /* - * 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); - } +static dns_name_t rfc1918names[] = { + NS_NAME_INIT(inaddr10, inaddr10_offsets), + NS_NAME_INIT(inaddr16172, inaddr172_offsets), + NS_NAME_INIT(inaddr17172, inaddr172_offsets), + NS_NAME_INIT(inaddr18172, inaddr172_offsets), + NS_NAME_INIT(inaddr19172, inaddr172_offsets), + NS_NAME_INIT(inaddr20172, inaddr172_offsets), + NS_NAME_INIT(inaddr21172, inaddr172_offsets), + NS_NAME_INIT(inaddr22172, inaddr172_offsets), + NS_NAME_INIT(inaddr23172, inaddr172_offsets), + NS_NAME_INIT(inaddr24172, inaddr172_offsets), + NS_NAME_INIT(inaddr25172, inaddr172_offsets), + NS_NAME_INIT(inaddr26172, inaddr172_offsets), + NS_NAME_INIT(inaddr27172, inaddr172_offsets), + NS_NAME_INIT(inaddr28172, inaddr172_offsets), + NS_NAME_INIT(inaddr29172, inaddr172_offsets), + NS_NAME_INIT(inaddr30172, inaddr172_offsets), + NS_NAME_INIT(inaddr31172, inaddr172_offsets), + NS_NAME_INIT(inaddr168192, inaddr192_offsets) +}; - 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); +static unsigned char prisoner_data[] = "\010prisoner\004iana\003org"; +static unsigned char hostmaster_data[] = "\012hostmaster\014root-servers\003org"; - 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; +static unsigned char prisoner_offsets[] = { 0, 9, 14, 18 }; +static unsigned char hostmaster_offsets[] = { 0, 11, 24, 28 }; - 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); +static dns_name_t prisoner = NS_NAME_INIT(prisoner_data, prisoner_offsets); +static dns_name_t hostmaster = NS_NAME_INIT(hostmaster_data, hostmaster_offsets); - 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; +static void +warn_rfc1918(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) { + unsigned int i; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + dns_rdataset_t found; + isc_result_t result; - 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; + for (i = 0; i < (sizeof(rfc1918names)/sizeof(*rfc1918names)); i++) { + if (dns_name_issubdomain(fname, &rfc1918names[i])) { + dns_rdataset_init(&found); + result = dns_ncache_getrdataset(rdataset, + &rfc1918names[i], + dns_rdatatype_soa, + &found); + if (result != ISC_R_SUCCESS) + return; + + result = dns_rdataset_first(&found); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(&found, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (dns_name_equal(&soa.origin, &prisoner) && + dns_name_equal(&soa.contact, &hostmaster)) { + char buf[DNS_NAME_FORMATSIZE]; + dns_name_format(fname, buf, sizeof(buf)); + ns_client_log(client, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_QUERY, + ISC_LOG_WARNING, + "RFC 1918 response from " + "Internet for %s", buf); + } + dns_rdataset_disassociate(&found); + return; } } - 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) +query_findclosestnsec3(dns_name_t *qname, dns_db_t *db, + dns_dbversion_t *version, ns_client_t *client, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_name_t *fname, isc_boolean_t exact, + dns_name_t *found) { - 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; + unsigned char salt[256]; + size_t salt_length; + isc_uint16_t iterations; isc_result_t result; + unsigned int dboptions; + dns_fixedname_t fixed; + dns_hash_t hash; + dns_name_t name; + unsigned int skip = 0, labels; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_boolean_t optout; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; - 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); + salt_length = sizeof(salt); + result = dns_db_getnsec3parameters(db, version, &hash, NULL, + &iterations, salt, &salt_length); 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; + dns_name_init(&name, NULL); + dns_name_clone(qname, &name); + labels = dns_name_countlabels(&name); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); /* - * Resume a query after recursion. + * Map unknown algorithm to known value. */ + if (hash == DNS_NSEC3_UNKNOWNALG) + hash = 1; - 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); + again: + dns_fixedname_init(&fixed); + result = dns_nsec3_hashname(&fixed, NULL, NULL, &name, + dns_db_origin(db), hash, + iterations, salt, salt_length); + if (result != ISC_R_SUCCESS) + return; - client->query.attributes &= ~NS_QUERYATTR_RECURSING; - fetch = devent->fetch; - devent->fetch = NULL; + dboptions = client->query.dboptions | DNS_DBFIND_FORCENSEC3; + result = dns_db_findext(db, dns_fixedname_name(&fixed), version, + dns_rdatatype_nsec3, dboptions, client->now, + NULL, fname, &cm, &ci, rdataset, sigrdataset); - /* - * 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); - } + if (result == DNS_R_NXDOMAIN) { + if (!dns_rdataset_isassociated(rdataset)) { + return; } - } + result = dns_rdataset_first(rdataset); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + dns_rdata_tostruct(&rdata, &nsec3, NULL); + dns_rdata_reset(&rdata); + optout = ISC_TF((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); + if (found != NULL && optout && + dns_name_issubdomain(&name, dns_db_origin(db))) + { + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(sigrdataset)) + dns_rdataset_disassociate(sigrdataset); + skip++; + dns_name_getlabelsequence(qname, skip, labels - skip, + &name); + ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, + NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(3), + "looking for closest provable encloser"); + goto again; + } + if (exact) + ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, + NS_LOGMODULE_QUERY, ISC_LOG_WARNING, + "expected a exact match NSEC3, got " + "a covering record"); - dns_resolver_destroyfetch(&fetch); + } else if (result != ISC_R_SUCCESS) { + return; + } else if (!exact) + ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, + NS_LOGMODULE_QUERY, ISC_LOG_WARNING, + "expected covering NSEC3, got an exact match"); + if (found == qname) { + if (skip != 0U) + dns_name_getlabelsequence(qname, skip, labels - skip, + found); + } else if (found != NULL) + dns_name_copy(&name, found, NULL); + return; } -static void -prefetch_done(isc_task_t *task, isc_event_t *event) { - dns_fetchevent_t *devent = (dns_fetchevent_t *)event; - ns_client_t *client; - - UNUSED(task); - - REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); - client = devent->ev_arg; - REQUIRE(NS_CLIENT_VALID(client)); - REQUIRE(task == client->task); - - LOCK(&client->query.fetchlock); - if (client->query.prefetch != NULL) { - INSIST(devent->fetch == client->query.prefetch); - client->query.prefetch = NULL; - } - UNLOCK(&client->query.fetchlock); - if (devent->fetch != NULL) - dns_resolver_destroyfetch(&devent->fetch); - if (devent->node != NULL) - dns_db_detachnode(devent->db, &devent->node); - if (devent->db != NULL) - dns_db_detach(&devent->db); - query_putrdataset(client, &devent->rdataset); - isc_event_free(&event); - ns_client_detach(&client); +#ifdef ALLOW_FILTER_AAAA +static isc_boolean_t +is_v4_client(ns_client_t *client) { + if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) + return (ISC_TRUE); + if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) + return (ISC_TRUE); + return (ISC_FALSE); } -static void -query_prefetch(ns_client_t *client, dns_name_t *qname, - dns_rdataset_t *rdataset) -{ +static isc_boolean_t +is_v6_client(ns_client_t *client) { + if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && + !IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) + return (ISC_TRUE); + return (ISC_FALSE); +} +#endif + +static isc_uint32_t +dns64_ttl(dns_db_t *db, dns_dbversion_t *version) { + dns_dbnode_t *node = NULL; + dns_rdata_soa_t soa; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; isc_result_t result; - isc_sockaddr_t *peeraddr; - dns_rdataset_t *tmprdataset; - ns_client_t *dummy = NULL; - unsigned int options; + isc_uint32_t ttl = ISC_UINT32_MAX; - if (client->query.prefetch != NULL || - client->view->prefetch_trigger == 0U || - rdataset->ttl > client->view->prefetch_trigger || - (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0) - return; + dns_rdataset_init(&rdataset); - if (client->recursionquota == NULL) { - result = isc_quota_attach(&ns_g_server->recursionquota, - &client->recursionquota); - if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) - result = ns_client_replace(client); - if (result != ISC_R_SUCCESS) - return; - isc_stats_increment(ns_g_server->nsstats, - dns_nsstatscounter_recursclients); - } + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) + goto cleanup; - tmprdataset = query_newrdataset(client); - if (tmprdataset == NULL) - return; - if (!TCP(client)) - peeraddr = &client->peeraddr; - else - peeraddr = NULL; - ns_client_attach(client, &dummy); - options = client->query.fetchoptions | DNS_FETCHOPT_PREFETCH; - result = dns_resolver_createfetch3(client->view->resolver, - qname, rdataset->type, NULL, NULL, - NULL, peeraddr, client->message->id, - options, 0, NULL, client->task, - prefetch_done, client, - tmprdataset, NULL, - &client->query.prefetch); - if (result != ISC_R_SUCCESS) { - query_putrdataset(client, &tmprdataset); - ns_client_detach(&dummy); - } - dns_rdataset_clearprefetch(rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + ttl = ISC_MIN(rdataset.ttl, soa.minimum); + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) + dns_rdataset_disassociate(&rdataset); + if (node != NULL) + dns_db_detachnode(db, &node); + return (ttl); } -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_boolean_t +dns64_aaaaok(ns_client_t *client, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { - isc_result_t result; - dns_rdataset_t *rdataset, *sigrdataset; - isc_sockaddr_t *peeraddr; + isc_netaddr_t netaddr; + dns_dns64_t *dns64 = ISC_LIST_HEAD(client->view->dns64); + unsigned int flags = 0; + unsigned int i, count; + isc_boolean_t *aaaaok; - if (!resuming) - inc_stats(client, dns_nsstatscounter_recursion); + INSIST(client->query.dns64_aaaaok == NULL); + INSIST(client->query.dns64_aaaaoklen == 0); + INSIST(client->query.dns64_aaaa == NULL); + INSIST(client->query.dns64_sigaaaa == NULL); - /* - * 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); + if (dns64 == NULL) + return (ISC_TRUE); - isc_stats_increment(ns_g_server->nsstats, - dns_nsstatscounter_recursclients); + if (RECURSIONOK(client)) + flags |= DNS_DNS64_RECURSIVE; - 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); - } + if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) + flags |= DNS_DNS64_DNSSEC; - /* - * Invoke the resolver. - */ - REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); - REQUIRE(client->query.fetch == NULL); + count = dns_rdataset_count(rdataset); + aaaaok = isc_mem_get(client->mctx, sizeof(isc_boolean_t) * count); - 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); + isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); + if (dns_dns64_aaaaok(dns64, &netaddr, client->signer, + &ns_g_server->aclenv, flags, rdataset, + aaaaok, count)) { + for (i = 0; i < count; i++) { + if (aaaaok != NULL && !aaaaok[i]) { + SAVE(client->query.dns64_aaaaok, aaaaok); + client->query.dns64_aaaaoklen = count; + break; + } } - } 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); + if (aaaaok != NULL) + isc_mem_put(client->mctx, aaaaok, + sizeof(isc_boolean_t) * count); + return (ISC_TRUE); } + if (aaaaok != NULL) + isc_mem_put(client->mctx, aaaaok, + sizeof(isc_boolean_t) * count); + return (ISC_FALSE); +} + +/* + * Look for the name and type in the redirection zone. If found update + * the arguments as appropriate. Return ISC_TRUE if a update was + * performed. + * + * Only perform the update if the client is in the allow query acl and + * returning the update would not cause a DNSSEC validation failure. + */ +static isc_result_t +redirect(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, + dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, + dns_rdatatype_t qtype) +{ + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_fixedname_t fixed; + dns_name_t *found; + dns_rdataset_t trdataset; + isc_result_t result; + dns_rdatatype_t type; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + ns_dbversion_t *dbversion; - return (result); -} + CTRACE(ISC_LOG_DEBUG(3), "redirect"); -static inline void -rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep, - dns_rdataset_t **rdatasetp) -{ - if (nodep != NULL && *nodep != NULL) { - REQUIRE(dbp != NULL && *dbp != NULL); - dns_db_detachnode(*dbp, nodep); - } - if (dbp != NULL && *dbp != NULL) - dns_db_detach(dbp); - if (zonep != NULL && *zonep != NULL) - dns_zone_detach(zonep); - if (rdatasetp != NULL && *rdatasetp != NULL && - dns_rdataset_isassociated(*rdatasetp)) - dns_rdataset_disassociate(*rdatasetp); -} + if (client->view->redirect == NULL) + return (ISC_R_NOTFOUND); -static inline void -rpz_match_clear(dns_rpz_st_t *st) { - rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); - st->m.version = NULL; -} + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + dns_rdataset_init(&trdataset); -static inline isc_result_t -rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp) { - REQUIRE(rdatasetp != NULL); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); - CTRACE(ISC_LOG_DEBUG(3), "rpz_ready"); + if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) + return (ISC_R_NOTFOUND); - if (*rdatasetp == NULL) { - *rdatasetp = query_newrdataset(client); - if (*rdatasetp == NULL) { - CTRACE(ISC_LOG_ERROR, - "rpz_ready: query_newrdataset failed"); - return (DNS_R_SERVFAIL); + if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { + if (rdataset->trust == dns_trust_secure) + return (ISC_R_NOTFOUND); + if (rdataset->trust == dns_trust_ultimate && + (rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3)) + return (ISC_R_NOTFOUND); + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_ncache_current(rdataset, found, &trdataset); + type = trdataset.type; + dns_rdataset_disassociate(&trdataset); + if (type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3 || + type == dns_rdatatype_rrsig) + return (ISC_R_NOTFOUND); + } } - } else if (dns_rdataset_isassociated(*rdatasetp)) { - dns_rdataset_disassociate(*rdatasetp); } - return (ISC_R_SUCCESS); -} -static void -rpz_st_clear(ns_client_t *client) { - dns_rpz_st_t *st = client->query.rpz_st; + result = ns_client_checkaclsilent(client, NULL, + dns_zone_getqueryacl(client->view->redirect), + ISC_TRUE); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); - CTRACE(ISC_LOG_DEBUG(3), "rpz_st_clear"); + result = dns_zone_getdb(client->view->redirect, &db); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); - if (st->m.rdataset != NULL) - query_putrdataset(client, &st->m.rdataset); - rpz_match_clear(st); + dbversion = query_findversion(client, db); + if (dbversion == NULL) { + dns_db_detach(&db); + return (ISC_R_NOTFOUND); + } - rpz_clean(NULL, &st->r.db, NULL, NULL); - if (st->r.ns_rdataset != NULL) - query_putrdataset(client, &st->r.ns_rdataset); - if (st->r.r_rdataset != NULL) - query_putrdataset(client, &st->r.r_rdataset); + /* + * Lookup the requested data in the redirect zone. + */ + result = dns_db_findext(db, client->query.qname, dbversion->version, + qtype, DNS_DBFIND_NOZONECUT, client->now, + &node, found, &cm, &ci, &trdataset, NULL); + if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(&trdataset)) + dns_rdataset_disassociate(&trdataset); + goto nxrrset; + } else if (result != ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&trdataset)) + dns_rdataset_disassociate(&trdataset); + if (node != NULL) + dns_db_detachnode(db, &node); + dns_db_detach(&db); + return (ISC_R_NOTFOUND); + } - rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL); - if (st->q.rdataset != NULL) - query_putrdataset(client, &st->q.rdataset); - if (st->q.sigrdataset != NULL) - query_putrdataset(client, &st->q.sigrdataset); - st->state = 0; - st->m.type = DNS_RPZ_TYPE_BAD; - st->m.policy = DNS_RPZ_POLICY_MISS; + CTRACE(ISC_LOG_DEBUG(3), "redirect: found data: done"); + dns_name_copy(found, name, NULL); + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_clone(&trdataset, rdataset); + dns_rdataset_disassociate(&trdataset); + } + nxrrset: + if (*nodep != NULL) + dns_db_detachnode(*dbp, nodep); + dns_db_detach(dbp); + dns_db_attachnode(db, node, nodep); + dns_db_attach(db, dbp); + dns_db_detachnode(db, &node); + dns_db_detach(&db); + *versionp = dbversion->version; + + client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | + NS_QUERYATTR_NOADDITIONAL); + + return (result); } -static dns_rpz_zbits_t -rpz_get_zbits(ns_client_t *client, - dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type) +static isc_result_t +redirect2(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, + dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, + dns_rdatatype_t qtype, isc_boolean_t *is_zonep) { - dns_rpz_st_t *st; - dns_rpz_zbits_t zbits; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_fixedname_t fixed; + dns_fixedname_t fixedredirect; + dns_name_t *found, *redirectname; + dns_rdataset_t trdataset; + isc_result_t result; + dns_rdatatype_t type; + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + dns_dbversion_t *version = NULL; + dns_zone_t *zone = NULL; + isc_boolean_t is_zone; + unsigned int options; - REQUIRE(client != NULL); - REQUIRE(client->query.rpz_st != NULL); + CTRACE(ISC_LOG_DEBUG(3), "redirect2"); - st = client->query.rpz_st; + if (client->view->redirectzone == NULL) + return (ISC_R_NOTFOUND); - switch (rpz_type) { - case DNS_RPZ_TYPE_CLIENT_IP: - zbits = st->have.client_ip; - break; - case DNS_RPZ_TYPE_QNAME: - zbits = st->have.qname; - break; - case DNS_RPZ_TYPE_IP: - if (ip_type == dns_rdatatype_a) { - zbits = st->have.ipv4; - } else if (ip_type == dns_rdatatype_aaaa) { - zbits = st->have.ipv6; - } else { - zbits = st->have.ip; - } - break; - case DNS_RPZ_TYPE_NSDNAME: - zbits = st->have.nsdname; - break; - case DNS_RPZ_TYPE_NSIP: - if (ip_type == dns_rdatatype_a) { - zbits = st->have.nsipv4; - } else if (ip_type == dns_rdatatype_aaaa) { - zbits = st->have.nsipv6; - } else { - zbits = st->have.nsip; + if (dns_name_issubdomain(name, client->view->redirectzone)) + return (ISC_R_NOTFOUND); + + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); + dns_rdataset_init(&trdataset); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); + + if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) + return (ISC_R_NOTFOUND); + + if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { + if (rdataset->trust == dns_trust_secure) + return (ISC_R_NOTFOUND); + if (rdataset->trust == dns_trust_ultimate && + (rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3)) + return (ISC_R_NOTFOUND); + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_ncache_current(rdataset, found, &trdataset); + type = trdataset.type; + dns_rdataset_disassociate(&trdataset); + if (type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3 || + type == dns_rdatatype_rrsig) + return (ISC_R_NOTFOUND); + } } - break; - default: - INSIST(0); - break; } + dns_fixedname_init(&fixedredirect); + redirectname = dns_fixedname_name(&fixedredirect); + if (dns_name_countlabels(name) > 1U) { + dns_name_t prefix; + unsigned int labels = dns_name_countlabels(name) - 1; + + dns_name_init(&prefix, NULL); + dns_name_getlabelsequence(name, 0, labels, &prefix); + result = dns_name_concatenate(&prefix, + client->view->redirectzone, + redirectname, NULL); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + } else + dns_name_copy(redirectname, client->view->redirectzone, NULL); + + options = 0; + result = query_getdb(client, redirectname, qtype, options, &zone, + &db, &version, &is_zone); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + if (zone != NULL) + dns_zone_detach(&zone); + /* - * Choose - * the earliest configured policy zone (rpz->num) - * QNAME over IP over NSDNAME over NSIP (rpz_type) - * the smallest name, - * the longest IP address prefix, - * the lexically smallest address. + * Lookup the requested data in the redirect zone. */ - if (st->m.policy != DNS_RPZ_POLICY_MISS) { - if (st->m.type >= rpz_type) { - zbits &= DNS_RPZ_ZMASK(st->m.rpz->num); - } else{ - zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1; + result = dns_db_findext(db, redirectname, version, + qtype, 0, client->now, + &node, found, &cm, &ci, &trdataset, NULL); + if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(&trdataset)) + dns_rdataset_disassociate(&trdataset); + goto nxrrset; + } else if (result == ISC_R_NOTFOUND || result == DNS_R_DELEGATION) { + /* + * Cleanup. + */ + if (dns_rdataset_isassociated(&trdataset)) + dns_rdataset_disassociate(&trdataset); + if (node != NULL) + dns_db_detachnode(db, &node); + dns_db_detach(&db); + /* + * Don't loop forever if the lookup failed last time. + */ + if (!REDIRECT(client)) { + result = query_recurse(client, qtype, redirectname, + NULL, NULL, ISC_TRUE); + if (result == ISC_R_SUCCESS) { + client->query.attributes |= + NS_QUERYATTR_RECURSING; + client->query.attributes |= + NS_QUERYATTR_REDIRECT; + return (DNS_R_CONTINUE); + } } + return (ISC_R_NOTFOUND); + } else if (result != ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&trdataset)) + dns_rdataset_disassociate(&trdataset); + if (node != NULL) + dns_db_detachnode(db, &node); + dns_db_detach(&db); + return (ISC_R_NOTFOUND); } + CTRACE(ISC_LOG_DEBUG(3), "redirect2: found data: done"); /* - * If the client wants recursion, allow only compatible policies. + * Adjust the found name to not include the redirectzone suffix. */ - if (!RECURSIONOK(client)) - zbits &= st->popt.no_rd_ok; + dns_name_split(found, dns_name_countlabels(client->view->redirectzone), + found, NULL); + /* + * Make the name absolute. + */ + result = dns_name_concatenate(found, dns_rootname, found, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); - return (zbits); + dns_name_copy(found, name, NULL); + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_clone(&trdataset, rdataset); + dns_rdataset_disassociate(&trdataset); + } + nxrrset: + if (*nodep != NULL) + dns_db_detachnode(*dbp, nodep); + dns_db_detach(dbp); + dns_db_attachnode(db, node, nodep); + dns_db_attach(db, dbp); + dns_db_detachnode(db, &node); + dns_db_detach(&db); + *is_zonep = is_zone; + *versionp = version; + + client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | + NS_QUERYATTR_NOADDITIONAL); + + return (result); } +/*% + * Initialize query context 'qctx'. Run by query_setup() when + * first handling a client query, and by query_resume() when + * returning from recursion. + */ static void -query_rpzfetch(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t type) { - isc_result_t result; - isc_sockaddr_t *peeraddr; - dns_rdataset_t *tmprdataset; - ns_client_t *dummy = NULL; - unsigned int options; +qctx_init(ns_client_t *client, dns_fetchevent_t *event, + dns_rdatatype_t qtype, query_ctx_t *qctx) +{ + REQUIRE(qctx != NULL); + REQUIRE(client != NULL); - if (client->query.prefetch != NULL) - return; + /* 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; +} - if (client->recursionquota == NULL) { - result = isc_quota_attach(&ns_g_server->recursionquota, - &client->recursionquota); - if (result == ISC_R_SUCCESS && !client->mortal && !TCP(client)) - result = ns_client_replace(client); - if (result != ISC_R_SUCCESS) - return; - isc_stats_increment(ns_g_server->nsstats, - dns_nsstatscounter_recursclients); +/*% + * 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); } - - tmprdataset = query_newrdataset(client); - if (tmprdataset == NULL) - return; - if (!TCP(client)) - peeraddr = &client->peeraddr; - else - peeraddr = NULL; - ns_client_attach(client, &dummy); - options = client->query.fetchoptions; - result = dns_resolver_createfetch3(client->view->resolver, qname, type, - NULL, NULL, NULL, peeraddr, - client->message->id, options, 0, - NULL, client->task, prefetch_done, - client, tmprdataset, NULL, - &client->query.prefetch); - if (result != ISC_R_SUCCESS) { - query_putrdataset(client, &tmprdataset); - ns_client_detach(&dummy); + 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); } } -/* - * Get an NS, A, or AAAA rrset related to the response for the client - * to check the contents of that rrset for hits by eligible policy zones. +/*% + * Free any allocated memory associated with qctx. */ -static isc_result_t -rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, - dns_rpz_type_t rpz_type, dns_db_t **dbp, - dns_dbversion_t *version, dns_rdataset_t **rdatasetp, - isc_boolean_t resuming) -{ - dns_rpz_st_t *st; - isc_boolean_t is_zone; - dns_dbnode_t *node; - dns_fixedname_t fixed; - dns_name_t *found; - isc_result_t result; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; +static void +qctx_freedata(query_ctx_t *qctx) { + if (qctx->rdataset != NULL) { + query_putrdataset(qctx->client, &qctx->rdataset); + } - CTRACE(ISC_LOG_DEBUG(3), "rpz_rrset_find"); + if (qctx->sigrdataset != NULL) { + query_putrdataset(qctx->client, &qctx->sigrdataset); + } - st = client->query.rpz_st; - if ((st->state & DNS_RPZ_RECURSING) != 0) { - INSIST(st->r.r_type == type); - INSIST(dns_name_equal(name, st->r_name)); - INSIST(*rdatasetp == NULL || - !dns_rdataset_isassociated(*rdatasetp)); - INSIST(*dbp == NULL); - st->state &= ~DNS_RPZ_RECURSING; - *dbp = st->r.db; - st->r.db = NULL; - if (*rdatasetp != NULL) - query_putrdataset(client, rdatasetp); - *rdatasetp = st->r.r_rdataset; - st->r.r_rdataset = NULL; - result = st->r.r_result; - if (result == DNS_R_DELEGATION) { - CTRACE(ISC_LOG_ERROR, "RPZ recursing"); - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, - rpz_type, " rpz_rrset_find(1)", result); - st->m.policy = DNS_RPZ_POLICY_ERROR; - result = DNS_R_SERVFAIL; - } - return (result); + if (qctx->fname != NULL) { + query_releasename(qctx->client, &qctx->fname); } - result = rpz_ready(client, rdatasetp); - if (result != ISC_R_SUCCESS) { - st->m.policy = DNS_RPZ_POLICY_ERROR; - return (result); + if (qctx->db != NULL) { + dns_db_detach(&qctx->db); } - if (*dbp != NULL) { - is_zone = ISC_FALSE; - } else { - dns_zone_t *zone; - version = NULL; - zone = NULL; - result = query_getdb(client, name, type, 0, &zone, dbp, - &version, &is_zone); - if (result != ISC_R_SUCCESS) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, - rpz_type, " rpz_rrset_find(2)", result); - st->m.policy = DNS_RPZ_POLICY_ERROR; - if (zone != NULL) - dns_zone_detach(&zone); - return (result); - } - if (zone != NULL) - dns_zone_detach(&zone); + if (qctx->zone != NULL) { + dns_zone_detach(&qctx->zone); } - node = NULL; - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); - result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK, - client->now, &node, found, - &cm, &ci, *rdatasetp, NULL); - if (result == DNS_R_DELEGATION && is_zone && USECACHE(client)) { - /* - * Try the cache if we're authoritative for an - * ancestor but not the domain itself. - */ - rpz_clean(NULL, dbp, &node, rdatasetp); - version = NULL; - dns_db_attach(client->view->cachedb, dbp); - result = dns_db_findext(*dbp, name, version, dns_rdatatype_ns, - 0, client->now, &node, found, - &cm, &ci, *rdatasetp, NULL); + 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); } - rpz_clean(NULL, dbp, &node, NULL); - if (result == DNS_R_DELEGATION) { - rpz_clean(NULL, NULL, NULL, rdatasetp); - /* - * Recurse for NS rrset or A or AAAA rrset for an NS. - * Do not recurse for addresses for the query name. - */ - if (rpz_type == DNS_RPZ_TYPE_IP) { - result = DNS_R_NXRRSET; - } else if (!client->view->rpzs->p.nsip_wait_recurse) { - query_rpzfetch(client, name, type); - result = DNS_R_NXRRSET; - } else { - dns_name_copy(name, st->r_name, NULL); - result = query_recurse(client, type, st->r_name, - NULL, NULL, resuming); - if (result == ISC_R_SUCCESS) { - st->state |= DNS_RPZ_RECURSING; - result = DNS_R_DELEGATION; - } - } + + if (qctx->event != NULL) { + isc_event_free(ISC_EVENT_PTR(&qctx->event)); } - return (result); +} + +/*% + * 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]; + + if (qctx->client->query.origqname != NULL) + dns_name_format(qctx->client->query.origqname, qbuf, + sizeof(qbuf)); + else + snprintf(qbuf, sizeof(qbuf), ""); + + snprintf(mbuf, sizeof(mbuf) - 1, + "client attr:0x%x, query attr:0x%X, restarts:%d, " + "origqname:%s, timer:%d, authdb:%d, referral:%d", + 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 } /* - * Compute a policy owner name, p_name, in a policy zone given the needed - * policy type and the trigger name. + * 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 -rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, - dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, - dns_name_t *trig_name) -{ - dns_offsets_t prefix_offsets; - dns_name_t prefix, *suffix; - unsigned int first, labels; +query_setup(ns_client_t *client, dns_rdatatype_t qtype) { isc_result_t result; + query_ctx_t qctx; - CTRACE(ISC_LOG_DEBUG(3), "rpz_get_p_name"); + qctx_init(client, NULL, qtype, &qctx); + query_trace(&qctx); /* - * The policy owner name consists of a suffix depending on the type - * and policy zone and a prefix that is the longest possible string - * from the trigger name that keesp the resulting policy owner name - * from being too long. + * Check SERVFAIL cache */ - switch (rpz_type) { - case DNS_RPZ_TYPE_CLIENT_IP: - suffix = &rpz->client_ip; - break; - case DNS_RPZ_TYPE_QNAME: - suffix = &rpz->origin; - break; - case DNS_RPZ_TYPE_IP: - suffix = &rpz->ip; - break; - case DNS_RPZ_TYPE_NSDNAME: - suffix = &rpz->nsdname; - break; - case DNS_RPZ_TYPE_NSIP: - suffix = &rpz->nsip; - break; - default: - INSIST(0); + result = query_sfcache(&qctx); + if (result != ISC_R_COMPLETE) { + return (result); } /* - * Start with relative version of the full trigger name, - * and trim enough allow the addition of the suffix. + * If it's a SIG query, we'll iterate the node. */ - dns_name_init(&prefix, prefix_offsets); - labels = dns_name_countlabels(trig_name); - first = 0; - for (;;) { - dns_name_getlabelsequence(trig_name, first, labels-first-1, - &prefix); - result = dns_name_concatenate(&prefix, suffix, p_name, NULL); - if (result == ISC_R_SUCCESS) - break; - INSIST(result == DNS_R_NAMETOOLONG); - /* - * Trim the trigger name until the combination is not too long. - */ - if (labels-first < 2) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix, - rpz_type, " concatentate()", result); - return (ISC_R_FAILURE); - } - /* - * Complain once about trimming the trigger name. - */ - if (first == 0) { - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix, - rpz_type, " concatentate()", result); - } - ++first; + if (qctx.qtype == dns_rdatatype_rrsig || + qctx.qtype == dns_rdatatype_sig) + { + qctx.type = dns_rdatatype_any; + } else { + qctx.type = qctx.qtype; } - return (ISC_R_SUCCESS); + + return (query_start(&qctx)); } -/* - * Look in policy zone rpz for a policy of rpz_type by p_name. - * The self-name (usually the client qname or an NS name) is compared with - * the target of a CNAME policy for the old style passthru encoding. - * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep, - * *rdatasetp, and *policyp. - * The target DNS type, qtype, chooses the best rdataset for *rdatasetp. - * The caller must decide if the found policy is most suitable, including - * better than a previously found policy. - * If it is best, the caller records it in client->query.rpz_st->m. +/*% + * 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 -rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, - dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, - dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, - dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, - dns_rpz_policy_t *policyp) -{ - dns_fixedname_t foundf; - dns_name_t *found; +query_start(query_ctx_t *qctx) { isc_result_t result; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - - REQUIRE(nodep != NULL); - - CTRACE(ISC_LOG_DEBUG(3), "rpz_find_p"); + 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_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + 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(qctx, DNS_R_REFUSED); + return (query_done(qctx)); + } /* - * Try to find either a CNAME or the type of record demanded by the - * request from the policy zone. + * First we must find the right database. */ - rpz_clean(zonep, dbp, nodep, rdatasetp); - result = rpz_ready(client, rdatasetp); - if (result != ISC_R_SUCCESS) { - CTRACE(ISC_LOG_ERROR, "rpz_ready() failed"); - return (DNS_R_SERVFAIL); + 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; } - *versionp = NULL; - result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp); - if (result != ISC_R_SUCCESS) - return (DNS_R_NXDOMAIN); - dns_fixedname_init(&foundf); - found = dns_fixedname_name(&foundf); - result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0, - client->now, nodep, found, &cm, &ci, - *rdatasetp, NULL); - /* - * Choose the best rdataset if we found something. - */ - if (result == ISC_R_SUCCESS) { - dns_rdatasetiter_t *rdsiter; + 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 + * authoritative for the child zone. + */ + dns_db_t *tdb = NULL; + dns_zone_t *tzone = NULL; + dns_dbversion_t *tversion = NULL; + isc_result_t tresult; - rdsiter = NULL; - result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, - &rdsiter); - if (result != ISC_R_SUCCESS) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, - rpz_type, " allrdatasets()", result); - CTRACE(ISC_LOG_ERROR, - "rpz_find_p: allrdatasets failed"); - return (DNS_R_SERVFAIL); - } - for (result = dns_rdatasetiter_first(rdsiter); - result == ISC_R_SUCCESS; - result = dns_rdatasetiter_next(rdsiter)) { - dns_rdatasetiter_current(rdsiter, *rdatasetp); - if ((*rdatasetp)->type == dns_rdatatype_cname || - (*rdatasetp)->type == qtype) - break; - dns_rdataset_disassociate(*rdatasetp); + tresult = query_getzonedb(qctx->client, + qctx->client->query.qname, + qctx->qtype, + DNS_GETDB_PARTIAL, + &tzone, &tdb, &tversion); + if (tresult == ISC_R_SUCCESS) { + 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) { + dns_db_detach(&tdb); + } + if (tzone != NULL) { + dns_zone_detach(&tzone); + } } - dns_rdatasetiter_destroy(&rdsiter); - if (result != ISC_R_SUCCESS) { - if (result != ISC_R_NOMORE) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - p_name, rpz_type, - " rdatasetiter", result); - CTRACE(ISC_LOG_ERROR, - "rpz_find_p: rdatasetiter_destroy " - "failed"); - return (DNS_R_SERVFAIL); + } + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_REFUSED) { + if (WANTRECURSION(qctx->client)) { + inc_stats(qctx->client, + dns_nsstatscounter_recurserej); + } else { + inc_stats(qctx->client, + dns_nsstatscounter_authrej); } - /* - * Ask again to get the right DNS_R_DNAME/NXRRSET/... - * result if there is neither a CNAME nor target type. - */ - if (dns_rdataset_isassociated(*rdatasetp)) - dns_rdataset_disassociate(*rdatasetp); - dns_db_detachnode(*dbp, nodep); + if (!PARTIALANSWER(qctx->client)) { + QUERY_ERROR(qctx, DNS_R_REFUSED); + } + } else { + CCTRACE(ISC_LOG_ERROR, + "query_start: query_getdb failed"); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + } + return (query_done(qctx)); + } - if (qtype == dns_rdatatype_rrsig || - qtype == dns_rdatatype_sig) - result = DNS_R_NXRRSET; - else - result = dns_db_findext(*dbp, p_name, *versionp, - qtype, 0, client->now, - nodep, found, &cm, &ci, - *rdatasetp, NULL); + 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; } } - switch (result) { - case ISC_R_SUCCESS: - if ((*rdatasetp)->type != dns_rdatatype_cname) { - *policyp = DNS_RPZ_POLICY_RECORD; + + 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(qctx->zone, + &qctx->client->query.authzone); + } + dns_db_attach(qctx->db, &qctx->client->query.authdb); + } + qctx->client->query.authdbset = ISC_TRUE; + + /* Track TCP vs UDP stats per zone */ + if (TCP(qctx->client)) { + inc_stats(qctx->client, dns_nsstatscounter_tcp); } else { - *policyp = dns_rpz_decode_cname(rpz, *rdatasetp, - self_name); - if ((*policyp == DNS_RPZ_POLICY_RECORD || - *policyp == DNS_RPZ_POLICY_WILDCNAME) && - qtype != dns_rdatatype_cname && - qtype != dns_rdatatype_any) - return (DNS_R_CNAME); + inc_stats(qctx->client, dns_nsstatscounter_udp); } - return (ISC_R_SUCCESS); - case DNS_R_NXRRSET: - *policyp = DNS_RPZ_POLICY_NODATA; - return (result); - case DNS_R_DNAME: - /* - * DNAME policy RRs have very few if any uses that are not - * 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 - * 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. - */ - case DNS_R_NXDOMAIN: - case DNS_R_EMPTYNAME: - return (DNS_R_NXDOMAIN); - default: - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, - "", result); - CTRACE(ISC_LOG_ERROR, - "rpz_find_p: unexpected result"); - return (DNS_R_SERVFAIL); } -} -static void -rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, - dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix, - isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp, - dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, - dns_dbversion_t *version) -{ - dns_rdataset_t *trdataset = NULL; + 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... + */ + 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)); + } + + qctx->fname = query_newname(qctx->client, qctx->dbuf, &b); + qctx->rdataset = query_newrdataset(qctx->client); - rpz_match_clear(st); - st->m.rpz = rpz; - st->m.type = rpz_type; - st->m.policy = policy; - dns_name_copy(p_name, st->p_name, NULL); - st->m.prefix = prefix; - st->m.result = result; - SAVE(st->m.zone, *zonep); - SAVE(st->m.db, *dbp); - SAVE(st->m.node, *nodep); - if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { - /* - * Save the replacement rdataset from the policy - * and make the previous replacement rdataset scratch. - */ - SAVE(trdataset, st->m.rdataset); - SAVE(st->m.rdataset, *rdatasetp); - SAVE(*rdatasetp, trdataset); - st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl); - } else { - st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl); + 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)); } - SAVE(st->m.version, version); + + 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. + */ + 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); + + if (!qctx->is_zone) { + dns_cache_updatestats(qctx->client->view->cache, result); + } + + return (query_gotanswer(qctx, result)); } /* - * Check this address in every eligible policy zone. + * 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 isc_result_t -rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, - dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, - dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) -{ - dns_rpz_zones_t *rpzs; - dns_rpz_st_t *st; - dns_rpz_zone_t *rpz; - dns_rpz_prefix_t prefix; - dns_rpz_num_t rpz_num; - dns_fixedname_t ip_namef, p_namef; - dns_name_t *ip_name, *p_name; - dns_zone_t *p_zone; - dns_db_t *p_db; - dns_dbversion_t *p_version; - dns_dbnode_t *p_node; - dns_rpz_policy_t policy; +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; - CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip"); - - dns_fixedname_init(&ip_namef); - ip_name = dns_fixedname_name(&ip_namef); - - p_zone = NULL; - p_db = NULL; - p_node = NULL; + UNUSED(task); - rpzs = client->view->rpzs; - st = client->query.rpz_st; - while (zbits != 0) { - rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr, - ip_name, &prefix); - if (rpz_num == DNS_RPZ_INVALID_NUM) - break; - zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1); + 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) { /* - * Do not try applying policy zones that cannot replace a - * previously found policy zone. - * Stop looking if the next best choice cannot - * replace what we already have. + * This is the fetch we've been waiting for. */ - rpz = rpzs->zones[rpz_num]; - if (st->m.policy != DNS_RPZ_POLICY_MISS) { - if (st->m.rpz->num < rpz->num) - break; - if (st->m.rpz->num == rpz->num && - (st->m.type < rpz_type || - st->m.prefix > prefix)) - break; - } + 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); + } /* - * Get the policy for a prefix at least as long - * as the prefix of the entry we had before. + * This may destroy the client. */ - dns_fixedname_init(&p_namef); - p_name = dns_fixedname_name(&p_namef); - result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name); - if (result != ISC_R_SUCCESS) - continue; - result = rpz_find_p(client, ip_name, qtype, - p_name, rpz, rpz_type, - &p_zone, &p_db, &p_version, &p_node, - p_rdatasetp, &policy); - switch (result) { - case DNS_R_NXDOMAIN: - /* - * Continue after a policy record that is missing - * contrary to the summary data. The summary - * data can out of date during races with and among - * policy zone updates. - */ - CTRACE(ISC_LOG_ERROR, - "rpz_rewrite_ip: mismatched summary data; " - "continuing"); - continue; - case DNS_R_SERVFAIL: - rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); - st->m.policy = DNS_RPZ_POLICY_ERROR; - return (DNS_R_SERVFAIL); - default: - /* - * Forget this policy if it is not preferable - * to the previously found policy. - * If this policy is not good, then stop looking - * because none of the later policy zones would work. - * - * With more than one applicable policy, prefer - * the earliest configured policy, - * client-IP over QNAME over IP over NSDNAME over NSIP, - * the longest prefix - * the lexically smallest address. - * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num. - * We can compare new and current p_name because - * both are of the same type and in the same zone. - * The tests above eliminate other reasons to - * reject this policy. If this policy can't work, - * then neither can later zones. - */ - if (st->m.policy != DNS_RPZ_POLICY_MISS && - rpz->num == st->m.rpz->num && - (st->m.type == rpz_type && - st->m.prefix == prefix && - 0 > dns_name_rdatacompare(st->p_name, p_name))) - break; + ns_client_detach(&client); + } else { + query_ctx_t qctx; - /* - * Stop checking after saving an enabled hit in this - * policy zone. The radix tree in the policy zone - * ensures that we found the longest match. - */ - if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { - CTRACE(ISC_LOG_DEBUG(3), - "rpz_rewrite_ip: rpz_save_p"); - rpz_save_p(st, rpz, rpz_type, - policy, p_name, prefix, result, - &p_zone, &p_db, &p_node, - p_rdatasetp, p_version); - break; - } + qctx_init(client, devent, 0, &qctx); + query_trace(&qctx); - /* - * Log DNS_RPZ_POLICY_DISABLED zones - * and try the next eligible policy zone. - */ - rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, - p_zone, p_name, NULL, rpz_num); + 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); + } } } - rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); - return (ISC_R_SUCCESS); + dns_resolver_destroyfetch(&fetch); } -/* - * Check the IP addresses in the A or AAAA rrsets for name against - * all eligible rpz_type (IP or NSIP) response policy rewrite rules. +/*% + * 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 -rpz_rewrite_ip_rrset(ns_client_t *client, - dns_name_t *name, dns_rdatatype_t qtype, - dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type, - dns_db_t **ip_dbp, dns_dbversion_t *ip_version, - dns_rdataset_t **ip_rdatasetp, - dns_rdataset_t **p_rdatasetp, isc_boolean_t resuming) -{ - dns_rpz_zbits_t zbits; - isc_netaddr_t netaddr; - struct in_addr ina; - struct in6_addr in6a; +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; - CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrset"); - - zbits = rpz_get_zbits(client, ip_type, rpz_type); - if (zbits == 0) - return (ISC_R_SUCCESS); + if (!resuming) + inc_stats(client, dns_nsstatscounter_recursion); /* - * Get the A or AAAA rdataset. + * 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). */ - result = rpz_rrset_find(client, name, ip_type, rpz_type, ip_dbp, - ip_version, ip_rdatasetp, resuming); - switch (result) { - case ISC_R_SUCCESS: - case DNS_R_GLUE: - case DNS_R_ZONECUT: - break; - case DNS_R_EMPTYNAME: - case DNS_R_EMPTYWILD: - case DNS_R_NXDOMAIN: - case DNS_R_NCACHENXDOMAIN: - case DNS_R_NXRRSET: - case DNS_R_NCACHENXRRSET: - case ISC_R_NOTFOUND: - return (ISC_R_SUCCESS); - case DNS_R_DELEGATION: - case DNS_R_DUPLICATE: - case DNS_R_DROP: - return (result); - case DNS_R_CNAME: - case DNS_R_DNAME: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type, - " NS address rewrite rrset", result); - return (ISC_R_SUCCESS); - default: - if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { - client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, - rpz_type, " NS address rewrite rrset", - result); + 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); } - CTRACE(ISC_LOG_ERROR, - "rpz_rewrite_ip_rrset: unexpected result"); - return (DNS_R_SERVFAIL); + 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); } /* - * Check all of the IP addresses in the rdataset. + * Invoke the resolver. */ - for (result = dns_rdataset_first(*ip_rdatasetp); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(*ip_rdatasetp)) { + REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns); + REQUIRE(client->query.fetch == NULL); - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(*ip_rdatasetp, &rdata); - switch (rdata.type) { - case dns_rdatatype_a: - INSIST(rdata.length == 4); - memmove(&ina.s_addr, rdata.data, 4); - isc_netaddr_fromin(&netaddr, &ina); - break; - case dns_rdatatype_aaaa: - INSIST(rdata.length == 16); - memmove(in6a.s6_addr, rdata.data, 16); - isc_netaddr_fromin6(&netaddr, &in6a); - break; - default: - continue; + 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; + } - result = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type, - zbits, p_rdatasetp); - if (result != ISC_R_SUCCESS) - return (result); + if (client->query.timerset == ISC_FALSE) { + ns_client_settimeout(client, 60); } - return (ISC_R_SUCCESS); + 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); } -/* - * Look for IP addresses in A and AAAA rdatasets - * that trigger all eligible IP or NSIP policy rules. +/*% + * Restores the query context after resuming from recursion, and + * continues the query processing if needed. */ static isc_result_t -rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name, - dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, - dns_rdataset_t **ip_rdatasetp, isc_boolean_t resuming) -{ - dns_rpz_st_t *st; - dns_dbversion_t *ip_version; - dns_db_t *ip_db; - dns_rdataset_t *p_rdataset; +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 - CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrsets"); + qctx->want_restart = ISC_FALSE; - st = client->query.rpz_st; - ip_version = NULL; - ip_db = NULL; - p_rdataset = NULL; - if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && - (qtype == dns_rdatatype_a || - qtype == dns_rdatatype_any || - rpz_type == DNS_RPZ_TYPE_NSIP)) { + 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)) { /* - * Rewrite based on an IPv4 address that will appear - * in the ANSWER section or if we are checking IP addresses. + * Restore saved state. */ - result = rpz_rewrite_ip_rrset(client, name, qtype, - rpz_type, dns_rdatatype_a, - &ip_db, ip_version, ip_rdatasetp, - &p_rdataset, resuming); - if (result == ISC_R_SUCCESS) - st->state |= DNS_RPZ_DONE_IPv4; + 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 { - result = ISC_R_SUCCESS; + 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; } - if (result == ISC_R_SUCCESS && - (qtype == dns_rdatatype_aaaa || - qtype == dns_rdatatype_any || - rpz_type == DNS_RPZ_TYPE_NSIP)) { + 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) + { /* - * Rewrite based on IPv6 addresses that will appear - * in the ANSWER section or if we are checking IP addresses. + * Has response policy changed out from under us? */ - result = rpz_rewrite_ip_rrset(client, name, qtype, - rpz_type, dns_rdatatype_aaaa, - &ip_db, ip_version, ip_rdatasetp, - &p_rdataset, resuming); + 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); } - if (ip_db != NULL) - dns_db_detach(&ip_db); - query_putrdataset(client, &p_rdataset); - return (result); -} -/* - * Try to rewrite a request for a qtype rdataset based on the trigger name - * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME). - * Record the results including the replacement rdataset if any - * in client->query.rpz_st. - * *rdatasetp is a scratch rdataset. - */ -static isc_result_t -rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name, - dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, - dns_rpz_zbits_t allowed_zbits, dns_rdataset_t **rdatasetp) -{ - dns_rpz_zones_t *rpzs; - dns_rpz_zone_t *rpz; - dns_rpz_st_t *st; - dns_fixedname_t p_namef; - dns_name_t *p_name; - dns_rpz_zbits_t zbits; - dns_rpz_num_t rpz_num; - dns_zone_t *p_zone; - dns_db_t *p_db; - dns_dbversion_t *p_version; - dns_dbnode_t *p_node; - dns_rpz_policy_t policy; - isc_result_t result; + 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; + } - CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name"); + qctx->resuming = ISC_TRUE; - zbits = rpz_get_zbits(client, qtype, rpz_type); - zbits &= allowed_zbits; - if (zbits == 0) - return (ISC_R_SUCCESS); + return (query_gotanswer(qctx, result)); +} - rpzs = client->view->rpzs; +/*% + * 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; /* - * Use the summary database to find the bit mask of policy zones - * with policies for this trigger name. We do this even if there - * is only one eligible policy zone so that wildcard triggers - * are matched correctly, and not into their parent. + * The SERVFAIL cache doesn't apply to authoritative queries. */ - zbits = dns_rpz_find_name(rpzs, rpz_type, zbits, trig_name); - if (zbits == 0) - return (ISC_R_SUCCESS); + if (!RECURSIONOK(qctx->client)) { + return (ISC_R_COMPLETE); + } - dns_fixedname_init(&p_namef); - p_name = dns_fixedname_name(&p_namef); + 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]; - p_zone = NULL; - p_db = NULL; - p_node = NULL; + 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"); + } - st = client->query.rpz_st; + 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) { /* - * Check the trigger name in every policy zone that the summary data - * says has a hit for the trigger name. - * Most of the time there are no eligible zones and the summary data - * keeps us from getting this far. - * We check the most eligible zone first and so usually check only - * one policy zone. + * Rate limit these responses to this client. + * Do not delay counting and handling obvious referrals, + * since those won't come here again. + * Delay handling delegations for which we are certain to recurse and + * return here (DNS_R_DELEGATION, not a child of one of our + * own zones, and recursion enabled) + * Don't mess with responses rewritten by RPZ + * Count each response at most once. */ - for (rpz_num = 0; zbits != 0; ++rpz_num, zbits >>= 1) { - if ((zbits & 1) == 0) - continue; + 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; - /* - * Do not check policy zones that cannot replace a previously - * found policy. - */ - rpz = rpzs->zones[rpz_num]; - if (st->m.policy != DNS_RPZ_POLICY_MISS) { - if (st->m.rpz->num < rpz->num) - break; - if (st->m.rpz->num == rpz->num && - st->m.type < rpz_type) - break; - } + qctx->client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; - /* - * Get the next policy zone's record for this trigger name. - */ - result = rpz_get_p_name(client, p_name, rpz, rpz_type, - trig_name); - if (result != ISC_R_SUCCESS) - continue; - result = rpz_find_p(client, trig_name, qtype, p_name, - rpz, rpz_type, - &p_zone, &p_db, &p_version, &p_node, - rdatasetp, &policy); - switch (result) { - case DNS_R_NXDOMAIN: + wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP); + constname = qctx->fname; + if (result == DNS_R_NXDOMAIN) { /* - * Continue after a missing policy record - * contrary to the summary data. The summary - * data can out of date during races with and among - * policy zone updates. + * Use the database origin name to rate limit NXDOMAIN */ - CTRACE(ISC_LOG_ERROR, - "rpz_rewrite_name: mismatched summary data; " - "continuing"); - continue; - case DNS_R_SERVFAIL: - rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); - st->m.policy = DNS_RPZ_POLICY_ERROR; - return (DNS_R_SERVFAIL); - default: + if (qctx->db != NULL) + constname = dns_db_origin(qctx->db); + resp_result = result; + } else if (result == DNS_R_NCACHENXDOMAIN && + qctx->rdataset != NULL && + dns_rdataset_isassociated(qctx->rdataset) && + (qctx->rdataset->attributes & + DNS_RDATASETATTR_NEGATIVE) != 0) { /* - * With more than one applicable policy, prefer - * the earliest configured policy, - * client-IP over QNAME over IP over NSDNAME over NSIP, - * and the smallest name. - * We known st->m.rpz->num >= rpz->num and either - * st->m.rpz->num > rpz->num or st->m.type >= rpz_type + * Try to use owner name in the negative cache SOA. */ - if (st->m.policy != DNS_RPZ_POLICY_MISS && - rpz->num == st->m.rpz->num && - (st->m.type < rpz_type || - (st->m.type == rpz_type && - 0 >= dns_name_compare(p_name, st->p_name)))) - continue; -#if 0 + dns_fixedname_init(&fixed); + dns_rdataset_init(&nc_rdataset); + for (nc_result = dns_rdataset_first(qctx->rdataset); + nc_result == ISC_R_SUCCESS; + 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) { + dns_rdataset_disassociate(&nc_rdataset); + constname = dns_fixedname_name(&fixed); + break; + } + dns_rdataset_disassociate(&nc_rdataset); + } + resp_result = DNS_R_NXDOMAIN; + } else if (result == DNS_R_NXRRSET || + result == DNS_R_EMPTYNAME) { + resp_result = DNS_R_NXRRSET; + } else if (result == DNS_R_DELEGATION) { + resp_result = result; + } else if (result == ISC_R_NOTFOUND) { /* - * This code would block a customer reported information - * leak of rpz rules by rewriting requests in the - * rpz-ip, rpz-nsip, rpz-nsdname,and rpz-passthru TLDs. - * Without this code, a bad guy could request - * 24.0.3.2.10.rpz-ip. to find the policy rule for - * 10.2.3.0/14. It is an insignificant leak and this - * code is not worth its cost, because the bad guy - * could publish "evil.com A 10.2.3.4" and request - * evil.com to get the same information. - * Keep code with "#if 0" in case customer demand - * is irresistible. + * Handle referral to ".", including when recursion + * is off or not requested and the hints have not + * been loaded or we have "additional-from-cache no". + */ + constname = dns_rootname; + resp_result = DNS_R_DELEGATION; + } else { + resp_result = ISC_R_SUCCESS; + } + + 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) { + /* + * Log dropped or slipped responses in the query + * category so that requests are not silently lost. + * Starts of rate-limited bursts are logged in + * DNS_LOGCATEGORY_RRL. * - * We have the less frequent case of a triggered - * policy. Check that we have not trigger on one - * of the pretend RPZ TLDs. - * This test would make it impossible to rewrite - * names in TLDs that start with "rpz-" should - * ICANN ever allow such TLDs. + * Dropped responses are counted with dropped queries + * in QryDropped while slipped responses are counted + * with other truncated responses in RespTruncated. */ - unsigned int labels; - labels = dns_name_countlabels(trig_name); - if (labels >= 2) { - dns_label_t label; + if (wouldlog) { + ns_client_log(qctx->client, DNS_LOGCATEGORY_RRL, + NS_LOGMODULE_QUERY, + DNS_RRL_LOG_DROP, + "%s", log_buf); + } + + 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(qctx->client, + dns_nsstatscounter_ratedropped); + QUERY_ERROR(qctx, DNS_R_DROP); + } else { + /* + * These will also be counted in + * dns_nsstatscounter_truncatedresp + */ + inc_stats(qctx->client, + dns_nsstatscounter_rateslipped); + if (WANTCOOKIE(qctx->client)) { + qctx->client->message->flags &= + ~DNS_MESSAGEFLAG_AA; + qctx->client->message->flags &= + ~DNS_MESSAGEFLAG_AD; + qctx->client->message->rcode = + dns_rcode_badcookie; + } else { + qctx->client->message->flags |= + DNS_MESSAGEFLAG_TC; + if (resp_result == + DNS_R_NXDOMAIN) + { + qctx->client->message->rcode = + dns_rcode_nxdomain; + } + } + } + return (DNS_R_DROP); + } + } + } else if (!TCP(qctx->client) && + qctx->client->view->requireservercookie && + WANTCOOKIE(qctx->client) && !HAVECOOKIE(qctx->client)) + { + 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);; + } - dns_name_getlabel(trig_name, labels-2, &label); - if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 && - strncasecmp((const char *)label.base+1, - DNS_RPZ_PREFIX, - sizeof(DNS_RPZ_PREFIX)-1) == 0) - continue; - } -#endif - if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { - CTRACE(ISC_LOG_DEBUG(3), - "rpz_rewrite_name: rpz_save_p"); - rpz_save_p(st, rpz, rpz_type, - policy, p_name, 0, result, - &p_zone, &p_db, &p_node, - rdatasetp, p_version); + 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) { /* - * After a hit, higher numbered policy zones - * are irrelevant + * We will add all of the rdatasets of + * the node by iterating later, + * and set the TTL then. */ - rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); - return (ISC_R_SUCCESS); + 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: /* - * Log DNS_RPZ_POLICY_DISABLED zones - * and try the next eligible policy zone. + * Add overridding CNAME from a named.conf + * response-policy statement */ - rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, - p_zone, p_name, NULL, rpz_num); - break; + 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); } - rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); - return (ISC_R_SUCCESS); + return (result); } -static void -rpz_rewrite_ns_skip(ns_client_t *client, dns_name_t *nsname, - isc_result_t result, int level, const char *str) -{ - dns_rpz_st_t *st; +/*% + * 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), "rpz_rewrite_ns_skip"); + CTRACE(ISC_LOG_DEBUG(3), "query_rpzcname"); - st = client->query.rpz_st; + 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); + } - if (str != NULL) - rpz_log_fail(client, level, nsname, DNS_RPZ_TYPE_NSIP, - str, result); - if (st->r.ns_rdataset != NULL && - dns_rdataset_isassociated(st->r.ns_rdataset)) - dns_rdataset_disassociate(st->r.ns_rdataset); + 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); + } - st->r.label--; + 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); } -/* - * Look for response policy zone QNAME, NSIP, and NSDNAME rewriting. +/*% + * 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 -rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, - isc_result_t qresult, isc_boolean_t resuming, - dns_rdataset_t *ordataset, dns_rdataset_t *osigset) -{ - dns_rpz_zones_t *rpzs; - dns_rpz_st_t *st; - dns_rdataset_t *rdataset; - dns_fixedname_t nsnamef; - dns_name_t *nsname; - int qresult_type; - dns_rpz_zbits_t zbits; - isc_result_t result = ISC_R_SUCCESS; - dns_rpz_have_t have; - dns_rpz_popt_t popt; - int rpz_ver; - - CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite"); - - rpzs = client->view->rpzs; - st = client->query.rpz_st; +query_gotanswer(query_ctx_t *qctx, isc_result_t result) { + char errmsg[256]; - if (rpzs == NULL || - (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0)) - return (DNS_R_DISALLOWED); + CCTRACE(ISC_LOG_DEBUG(3), "query_gotanswer"); - RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); - if (rpzs->p.num_zones == 0 || - (!RECURSIONOK(client) && rpzs->p.no_rd_ok == 0) || - !rpz_ck_dnssec(client, qresult, ordataset, osigset)) - { - RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); - return (DNS_R_DISALLOWED); + if (query_checkrrl(qctx, result) != ISC_R_SUCCESS) { + return (query_done(qctx)); } - have = rpzs->have; - popt = rpzs->p; - rpz_ver = rpzs->rpz_ver; - RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); - if (st == NULL) { - st = isc_mem_get(client->mctx, sizeof(*st)); - if (st == NULL) - return (ISC_R_NOMEMORY); - st->state = 0; - } - if (st->state == 0) { - st->state |= DNS_RPZ_ACTIVE; - memset(&st->m, 0, sizeof(st->m)); - st->m.type = DNS_RPZ_TYPE_BAD; - st->m.policy = DNS_RPZ_POLICY_MISS; - st->m.ttl = ~0; - memset(&st->r, 0, sizeof(st->r)); - memset(&st->q, 0, sizeof(st->q)); - dns_fixedname_init(&st->_p_namef); - dns_fixedname_init(&st->_r_namef); - dns_fixedname_init(&st->_fnamef); - st->p_name = dns_fixedname_name(&st->_p_namef); - st->r_name = dns_fixedname_name(&st->_r_namef); - st->fname = dns_fixedname_name(&st->_fnamef); - st->have = have; - st->popt = popt; - st->rpz_ver = rpz_ver; - client->query.rpz_st = st; + 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)); } - /* - * There is nothing to rewrite if the main query failed. - */ - switch (qresult) { + switch (result) { case ISC_R_SUCCESS: + return (query_prepresponse(qctx)); + case DNS_R_GLUE: case DNS_R_ZONECUT: - qresult_type = 0; - break; - case DNS_R_EMPTYNAME: - case DNS_R_NXRRSET: - case DNS_R_NXDOMAIN: - case DNS_R_EMPTYWILD: - case DNS_R_NCACHENXDOMAIN: - case DNS_R_NCACHENXRRSET: - case DNS_R_CNAME: - case DNS_R_DNAME: - qresult_type = 1; - break; - case DNS_R_DELEGATION: - case ISC_R_NOTFOUND: - /* - * If recursion is on, do only tentative rewriting. - * If recursion is off, this the normal and only time we - * can rewrite. - */ - if (RECURSIONOK(client)) - qresult_type = 2; - else - qresult_type = 1; - break; - case ISC_R_FAILURE: - case ISC_R_TIMEDOUT: - case DNS_R_BROKENCHAIN: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname, - DNS_RPZ_TYPE_QNAME, - " stop on qresult in rpz_rewrite()", qresult); - return (ISC_R_SUCCESS); - default: - rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname, - DNS_RPZ_TYPE_QNAME, - " stop on unrecognized qresult in rpz_rewrite()", - qresult); - return (ISC_R_SUCCESS); - } - - rdataset = NULL; + INSIST(qctx->is_zone); + qctx->authoritative = ISC_FALSE; + return (query_prepresponse(qctx)); - if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) != - (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) { - isc_netaddr_t netaddr; - dns_rpz_zbits_t allowed; + case ISC_R_NOTFOUND: + return (query_notfound(qctx)); - if (qresult_type == 2) { - /* - * This request needs recursion that has not been done. - * Get bits for the policy zones that do not need - * to wait for the results of recursion. - */ - allowed = st->have.qname_skip_recurse; - if (allowed == 0) - return (ISC_R_SUCCESS); - } else { - allowed = DNS_RPZ_ALL_ZBITS; - } + case DNS_R_DELEGATION: + return (query_delegation(qctx)); - /* - * Check once for triggers for the client IP address. - */ - if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) { - zbits = rpz_get_zbits(client, dns_rdatatype_none, - DNS_RPZ_TYPE_CLIENT_IP); - zbits &= allowed; - if (zbits != 0) { - isc_netaddr_fromsockaddr(&netaddr, - &client->peeraddr); - result = rpz_rewrite_ip(client, &netaddr, qtype, - DNS_RPZ_TYPE_CLIENT_IP, - zbits, &rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - } - } + case DNS_R_EMPTYNAME: + return (query_nodata(qctx, DNS_R_EMPTYNAME)); + case DNS_R_NXRRSET: + return (query_nodata(qctx, DNS_R_NXRRSET)); - /* - * Check triggers for the query name if this is the first time - * for the current qname. - * There is a first time for each name in a CNAME chain - */ - if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { - result = rpz_rewrite_name(client, client->query.qname, - qtype, DNS_RPZ_TYPE_QNAME, - allowed, &rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; + case DNS_R_EMPTYWILD: + return (query_nxdomain(qctx, ISC_TRUE)); - /* - * Check IPv4 addresses in A RRs next. - * Reset to the start of the NS names. - */ - st->r.label = dns_name_countlabels(client->query.qname); - st->state &= ~(DNS_RPZ_DONE_QNAME_IP | - DNS_RPZ_DONE_IPv4); + case DNS_R_NXDOMAIN: + return (query_nxdomain(qctx, ISC_FALSE)); - } + case DNS_R_NCACHENXDOMAIN: + result = query_redirect(qctx); + if (result != ISC_R_COMPLETE) + return (result); + return (query_ncache(qctx, DNS_R_NCACHENXDOMAIN)); - /* - * Quit if this was an attempt to find a qname or - * client-IP trigger before recursion. - * We will be back if no pre-recursion triggers hit. - * For example, consider 2 policy zones, both with qname and - * IP address triggers. If the qname misses the 1st zone, - * then we cannot know whether a hit for the qname in the - * 2nd zone matters until after recursing to get the A RRs and - * testing them in the first zone. - * Do not bother saving the work from this attempt, - * because recusion is so slow. - */ - if (qresult_type == 2) - goto cleanup; + case DNS_R_NCACHENXRRSET: + return (query_ncache(qctx, DNS_R_NCACHENXRRSET)); + + case DNS_R_CNAME: + return (query_cname(qctx)); + + case DNS_R_DNAME: + return (query_dname(qctx)); + default: /* - * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP - * is reset at the end of dealing with each CNAME. + * Something has gone wrong. */ - st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME); + snprintf(errmsg, sizeof(errmsg) - 1, + "query_gotanswer: unexpected error: %s", + isc_result_totext(result)); + CCTRACE(ISC_LOG_ERROR, errmsg); + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); } +} - /* - * Check known IP addresses for the query name if the database - * lookup resulted in some addresses (qresult_type == 0) - * and if we have not already checked them. - * Any recursion required for the query has already happened. - * Do not check addresses that will not be in the ANSWER section. - */ - if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == 0 && - rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) { - result = rpz_rewrite_ip_rrsets(client, - client->query.qname, qtype, - DNS_RPZ_TYPE_IP, - &rdataset, resuming); - if (result != ISC_R_SUCCESS) - goto cleanup; - /* - * We are finished checking the IP addresses for the qname. - * Start with IPv4 if we will check NS IP addesses. - */ - st->state |= DNS_RPZ_DONE_QNAME_IP; - st->state &= ~DNS_RPZ_DONE_IPv4; +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; } - /* - * Stop looking for rules if there are none of the other kinds - * that could override what we already have. - */ - if (rpz_get_zbits(client, dns_rdatatype_any, - DNS_RPZ_TYPE_NSDNAME) == 0 && - rpz_get_zbits(client, dns_rdatatype_any, - DNS_RPZ_TYPE_NSIP) == 0) { - result = ISC_R_SUCCESS; + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { goto cleanup; } - dns_fixedname_init(&nsnamef); - dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); - while (st->r.label > st->popt.min_ns_labels) { - /* - * Get NS rrset for each domain in the current qname. - */ - if (st->r.label == dns_name_countlabels(client->query.qname)) { - nsname = client->query.qname; - } else { - nsname = dns_fixedname_name(&nsnamef); - dns_name_split(client->query.qname, st->r.label, - NULL, nsname); - } - if (st->r.ns_rdataset == NULL || - !dns_rdataset_isassociated(st->r.ns_rdataset)) - { - dns_db_t *db = NULL; - result = rpz_rrset_find(client, nsname, - dns_rdatatype_ns, - DNS_RPZ_TYPE_NSDNAME, - &db, NULL, &st->r.ns_rdataset, - resuming); - if (db != NULL) - dns_db_detach(&db); - if (st->m.policy == DNS_RPZ_POLICY_ERROR) - goto cleanup; - switch (result) { - case ISC_R_SUCCESS: - result = dns_rdataset_first(st->r.ns_rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; - st->state &= ~(DNS_RPZ_DONE_NSDNAME | - DNS_RPZ_DONE_IPv4); - break; - case DNS_R_DELEGATION: - case DNS_R_DUPLICATE: - case DNS_R_DROP: - goto cleanup; - case DNS_R_EMPTYNAME: - case DNS_R_NXRRSET: - case DNS_R_EMPTYWILD: - case DNS_R_NXDOMAIN: - case DNS_R_NCACHENXDOMAIN: - case DNS_R_NCACHENXRRSET: - case ISC_R_NOTFOUND: - case DNS_R_CNAME: - case DNS_R_DNAME: - rpz_rewrite_ns_skip(client, nsname, result, - 0, NULL); - continue; - case ISC_R_TIMEDOUT: - case DNS_R_BROKENCHAIN: - case ISC_R_FAILURE: - rpz_rewrite_ns_skip(client, nsname, result, - DNS_RPZ_DEBUG_LEVEL3, - " NS rpz_rrset_find() "); - continue; - default: - rpz_rewrite_ns_skip(client, nsname, result, - DNS_RPZ_INFO_LEVEL, - " unrecognized NS" - " rpz_rrset_find() "); - continue; - } - } - /* - * Check all NS names. - */ - do { - dns_rdata_ns_t ns; - dns_rdata_t nsrdata = DNS_RDATA_INIT; + fname = query_newname(client, dbuf, &b); + neg = query_newrdataset(client); + negsig = query_newrdataset(client); + if (fname == NULL || neg == NULL || negsig == NULL) { + goto cleanup; + } - dns_rdataset_current(st->r.ns_rdataset, &nsrdata); - result = dns_rdata_tostruct(&nsrdata, &ns, NULL); - dns_rdata_reset(&nsrdata); - if (result != ISC_R_SUCCESS) { - rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, - nsname, DNS_RPZ_TYPE_NSIP, - " rdata_tostruct()", result); - st->m.policy = DNS_RPZ_POLICY_ERROR; - goto cleanup; - } - /* - * Do nothing about "NS ." - */ - if (dns_name_equal(&ns.name, dns_rootname)) { - dns_rdata_freestruct(&ns); - result = dns_rdataset_next(st->r.ns_rdataset); - continue; - } - /* - * Check this NS name if we did not handle it - * during a previous recursion. - */ - if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) { - result = rpz_rewrite_name(client, &ns.name, - qtype, - DNS_RPZ_TYPE_NSDNAME, - DNS_RPZ_ALL_ZBITS, - &rdataset); - if (result != ISC_R_SUCCESS) { - dns_rdata_freestruct(&ns); - goto cleanup; - } - st->state |= DNS_RPZ_DONE_NSDNAME; - } - /* - * Check all IP addresses for this NS name. - */ - result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype, - DNS_RPZ_TYPE_NSIP, - &rdataset, resuming); - dns_rdata_freestruct(&ns); - if (result != ISC_R_SUCCESS) - goto cleanup; - st->state &= ~(DNS_RPZ_DONE_NSDNAME | - DNS_RPZ_DONE_IPv4); - result = dns_rdataset_next(st->r.ns_rdataset); - } while (result == ISC_R_SUCCESS); - dns_rdataset_disassociate(st->r.ns_rdataset); - st->r.label--; + result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (rpz_get_zbits(client, dns_rdatatype_any, - DNS_RPZ_TYPE_NSDNAME) == 0 && - rpz_get_zbits(client, dns_rdatatype_any, - DNS_RPZ_TYPE_NSIP) == 0) - break; + query_addrrset(client, &fname, &neg, &negsig, dbuf, + DNS_SECTION_AUTHORITY); + + if ((qctx->noqname->attributes & DNS_RDATASETATTR_CLOSEST) == 0) { + goto cleanup; } - /* - * Use the best hit, if any. - */ - result = ISC_R_SUCCESS; + if (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + } -cleanup: - if (st->m.policy != DNS_RPZ_POLICY_MISS && - st->m.policy != DNS_RPZ_POLICY_ERROR && - st->m.rpz->policy != DNS_RPZ_POLICY_GIVEN) - st->m.policy = st->m.rpz->policy; - if (st->m.policy == DNS_RPZ_POLICY_MISS || - st->m.policy == DNS_RPZ_POLICY_PASSTHRU || - st->m.policy == DNS_RPZ_POLICY_ERROR) { - if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU && - result != DNS_R_DELEGATION) - rpz_log_rewrite(client, ISC_FALSE, st->m.policy, - st->m.type, st->m.zone, st->p_name, - NULL, st->m.rpz->num); - rpz_match_clear(st); + if (neg == NULL) { + neg = query_newrdataset(client); + } else if (dns_rdataset_isassociated(neg)) { + dns_rdataset_disassociate(neg); } - if (st->m.policy == DNS_RPZ_POLICY_ERROR) { - CTRACE(ISC_LOG_ERROR, "SERVFAIL due to RPZ policy"); - st->m.type = DNS_RPZ_TYPE_BAD; - result = DNS_R_SERVFAIL; + + if (negsig == NULL) { + negsig = query_newrdataset(client); + } else if (dns_rdataset_isassociated(negsig)) { + dns_rdataset_disassociate(negsig); } - query_putrdataset(client, &rdataset); - if ((st->state & DNS_RPZ_RECURSING) == 0) - rpz_clean(NULL, &st->r.db, NULL, &st->r.ns_rdataset); - return (result); + 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); + } } -/* - * See if response policy zone rewriting is allowed by a lack of interest - * by the client in DNSSEC or a lack of signatures. +/*% + * Build the response for a query for type ANY. */ -static isc_boolean_t -rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, - dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) -{ - dns_fixedname_t fixed; - dns_name_t *found; - dns_rdataset_t trdataset; - dns_rdatatype_t type; +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; - - CTRACE(ISC_LOG_DEBUG(3), "rpz_ck_dnssec"); - - if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) - return (ISC_TRUE); + dns_rdatatype_t onetype = 0; /* type to use for minimal-any */ +#ifdef ALLOW_FILTER_AAAA + isc_boolean_t have_aaaa, have_a, have_sig; /* - * We do not know if there are signatures if we have not recursed - * for them. + * 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. */ - if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) - return (ISC_FALSE); + have_aaaa = ISC_FALSE; + have_a = !qctx->authoritative; + have_sig = ISC_FALSE; +#endif - if (sigrdataset == NULL) - return (ISC_TRUE); - if (dns_rdataset_isassociated(sigrdataset)) - return (ISC_FALSE); + 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)); + } /* - * We are happy to rewrite nothing. - */ - if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) - return (ISC_TRUE); - /* - * Do not rewrite if there is any sign of signatures. + * 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! */ - if (rdataset->type == dns_rdatatype_nsec || - rdataset->type == dns_rdatatype_nsec3 || - rdataset->type == dns_rdatatype_rrsig) - return (ISC_FALSE); + 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 /* - * Look for a signature in a negative cache rdataset. - */ - if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) == 0) - return (ISC_TRUE); - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - dns_rdataset_init(&trdataset); - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - dns_ncache_current(rdataset, found, &trdataset); - type = trdataset.type; - dns_rdataset_disassociate(&trdataset); - if (type == dns_rdatatype_nsec || - type == dns_rdatatype_nsec3 || - type == dns_rdatatype_rrsig) - return (ISC_FALSE); + * 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; + } } - return (ISC_TRUE); + + 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)); } /* - * 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 + * Set the expire time, if requested, when answering from a + * slave or master zone. */ -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; +static void +query_getexpire(query_ctx_t *qctx) { + dns_zone_t *raw = NULL, *mayberaw; - CTRACE(ISC_LOG_DEBUG(3), "rpz_add_cname"); + if (qctx->zone == NULL || !qctx->is_zone || + qctx->qtype != dns_rdatatype_soa || + qctx->client->query.restarts != 0 || + (qctx->client->attributes & NS_CLIENTATTR_WANTEXPIRE) == 0) + { + return; + } - 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); + 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); - } - 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 + dns_rdataset_current(qctx->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); -#define QUERY_ERROR(r) \ -do { \ - eresult = r; \ - want_restart = ISC_FALSE; \ - line = __LINE__; \ -} while (0) + qctx->client->expire = soa.expire; + qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE; + } -#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) + if (raw != NULL) { + dns_zone_detach(&raw); + } +} +#ifdef ALLOW_FILTER_AAAA /* - * Extract a network address from the RDATA of an A or AAAA - * record. + * Optionally hide AAAAs from IPv4 clients if there is an A. * - * Returns: - * ISC_R_SUCCESS - * ISC_R_NOTIMPLEMENTED The rdata is not a known address type. + * 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 -rdata_tonetaddr(const dns_rdata_t *rdata, isc_netaddr_t *netaddr) { - struct in_addr ina; - struct in6_addr in6a; +query_filter_aaaa(query_ctx_t *qctx) { + isc_result_t result; - switch (rdata->type) { - case dns_rdatatype_a: - INSIST(rdata->length == 4); - memmove(&ina.s_addr, rdata->data, 4); - isc_netaddr_fromin(netaddr, &ina); - return (ISC_R_SUCCESS); - case dns_rdatatype_aaaa: - INSIST(rdata->length == 16); - memmove(in6a.s6_addr, rdata->data, 16); - isc_netaddr_fromin6(netaddr, &in6a); - return (ISC_R_SUCCESS); - default: - return (ISC_R_NOTIMPLEMENTED); + 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); } -} -/* - * 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 (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); - if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) - return (INT_MAX); - return (ns_sortlist_addrorder2(&netaddr, arg)); -} + /* + * 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; -/* - * 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; + } 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)); + } - if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) - return (INT_MAX); - return (ns_sortlist_addrorder1(&netaddr, arg)); + return (ISC_R_COMPLETE); } +#endif -/* - * Find the sortlist statement that applies to 'client' and set up - * the sortlist info in in client->message appropriately. +/*% + * Build a repsonse for a "normal" query, for a type other than ANY, + * for which we have an answer (either positive or negative). */ -static void -setup_query_sortlist(ns_client_t *client) { - isc_netaddr_t netaddr; - dns_rdatasetorderfunc_t order = NULL; - const void *order_arg = NULL; +static isc_result_t +query_respond(query_ctx_t *qctx) { + dns_rdataset_t **sigrdatasetp = NULL; + isc_result_t result; - 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; + /* + * 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)); } - 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; +#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); - CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof"); + 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; - fname = NULL; - neg = NULL; - negsig = NULL; + return (query_lookup(qctx)); + } - 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; + if (qctx->sigrdataset != NULL) { + sigrdatasetp = &qctx->sigrdataset; + } - result = dns_rdataset_getnoqname(rdataset, fname, neg, negsig); - RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) { + qctx->noqname = qctx->rdataset; + } else { + qctx->noqname = NULL; + } - query_addrrset(client, &fname, &neg, &negsig, dbuf, - DNS_SECTION_AUTHORITY); + /* + * 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; + } - if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) == 0) - goto cleanup; + /* + * Set expire time + */ + query_getexpire(qctx); - if (fname == NULL) { - dbuf = query_getnamebuf(client); - if (dbuf == NULL) - goto cleanup; - fname = query_newname(client, dbuf, &b); + 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); } - 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); + query_addnoqnameproof(qctx); - cleanup: - if (neg != NULL) - query_putrdataset(client, &neg); - if (negsig != NULL) - query_putrdataset(client, &negsig); - if (fname != NULL) - query_releasename(client, &fname); + /* + * 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 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; +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; - 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; + /*% + * 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); } -} -#define NS_NAME_INIT(A,B) \ - { \ - DNS_NAME_MAGIC, \ - A, sizeof(A), sizeof(B), \ - DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, \ - B, NULL, { (void *)-1, (void *)-1}, \ - {NULL, NULL} \ + if (qctx->rdataset->trust != dns_trust_secure) { + client->query.attributes &= ~NS_QUERYATTR_SECURE; } -static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 }; -static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 }; -static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 }; + isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); -static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA"; + 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; -static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA"; -static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA"; + 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); -static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA"; + if (RECURSIONOK(client)) + flags |= DNS_DNS64_RECURSIVE; -static dns_name_t rfc1918names[] = { - NS_NAME_INIT(inaddr10, inaddr10_offsets), - NS_NAME_INIT(inaddr16172, inaddr172_offsets), - NS_NAME_INIT(inaddr17172, inaddr172_offsets), - NS_NAME_INIT(inaddr18172, inaddr172_offsets), - NS_NAME_INIT(inaddr19172, inaddr172_offsets), - NS_NAME_INIT(inaddr20172, inaddr172_offsets), - NS_NAME_INIT(inaddr21172, inaddr172_offsets), - NS_NAME_INIT(inaddr22172, inaddr172_offsets), - NS_NAME_INIT(inaddr23172, inaddr172_offsets), - NS_NAME_INIT(inaddr24172, inaddr172_offsets), - NS_NAME_INIT(inaddr25172, inaddr172_offsets), - NS_NAME_INIT(inaddr26172, inaddr172_offsets), - NS_NAME_INIT(inaddr27172, inaddr172_offsets), - NS_NAME_INIT(inaddr28172, inaddr172_offsets), - NS_NAME_INIT(inaddr29172, inaddr172_offsets), - NS_NAME_INIT(inaddr30172, inaddr172_offsets), - NS_NAME_INIT(inaddr31172, inaddr172_offsets), - NS_NAME_INIT(inaddr168192, inaddr192_offsets) -}; + /* + * 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; -static unsigned char prisoner_data[] = "\010prisoner\004iana\003org"; -static unsigned char hostmaster_data[] = "\012hostmaster\014root-servers\003org"; + if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) + goto cleanup; -static unsigned char prisoner_offsets[] = { 0, 9, 14, 18 }; -static unsigned char hostmaster_offsets[] = { 0, 11, 24, 28 }; + 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; -static dns_name_t prisoner = NS_NAME_INIT(prisoner_data, prisoner_offsets); -static dns_name_t hostmaster = NS_NAME_INIT(hostmaster_data, hostmaster_offsets); + cleanup: + if (buffer != NULL) + isc_buffer_free(&buffer); -static void -warn_rfc1918(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) { - unsigned int i; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_soa_t soa; - dns_rdataset_t found; - isc_result_t result; + if (dns64_rdata != NULL) + dns_message_puttemprdata(client->message, &dns64_rdata); - for (i = 0; i < (sizeof(rfc1918names)/sizeof(*rfc1918names)); i++) { - if (dns_name_issubdomain(fname, &rfc1918names[i])) { - dns_rdataset_init(&found); - result = dns_ncache_getrdataset(rdataset, - &rfc1918names[i], - dns_rdatatype_soa, - &found); - if (result != ISC_R_SUCCESS) - return; + if (dns64_rdataset != NULL) + dns_message_puttemprdataset(client->message, &dns64_rdataset); - result = dns_rdataset_first(&found); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdataset_current(&found, &rdata); - result = dns_rdata_tostruct(&rdata, &soa, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (dns_name_equal(&soa.origin, &prisoner) && - dns_name_equal(&soa.contact, &hostmaster)) { - char buf[DNS_NAME_FORMATSIZE]; - dns_name_format(fname, buf, sizeof(buf)); - ns_client_log(client, DNS_LOGCATEGORY_SECURITY, - NS_LOGMODULE_QUERY, - ISC_LOG_WARNING, - "RFC 1918 response from " - "Internet for %s", buf); - } - dns_rdataset_disassociate(&found); - return; + 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_findclosestnsec3(dns_name_t *qname, dns_db_t *db, - dns_dbversion_t *version, ns_client_t *client, - dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, - dns_name_t *fname, isc_boolean_t exact, - dns_name_t *found) -{ - unsigned char salt[256]; - size_t salt_length; - isc_uint16_t iterations; - isc_result_t result; - unsigned int dboptions; - dns_fixedname_t fixed; - dns_hash_t hash; - dns_name_t name; - unsigned int skip = 0, labels; - dns_rdata_nsec3_t nsec3; +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; - isc_boolean_t optout; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; + dns_rdatalist_t *myrdatalist; + dns_rdataset_t *myrdataset; + isc_buffer_t *buffer; + isc_region_t r; + isc_result_t result; + unsigned int i; - salt_length = sizeof(salt); - result = dns_db_getnsec3parameters(db, version, &hash, NULL, - &iterations, salt, &salt_length); - if (result != ISC_R_SUCCESS) - return; + CTRACE(ISC_LOG_DEBUG(3), "query_filter64"); - dns_name_init(&name, NULL); - dns_name_clone(qname, &name); - labels = dns_name_countlabels(&name); - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + INSIST(client->query.dns64_aaaaok != NULL); + INSIST(client->query.dns64_aaaaoklen == + dns_rdataset_count(qctx->rdataset)); - /* - * Map unknown algorithm to known value. - */ - if (hash == DNS_NSEC3_UNKNOWNALG) - hash = 1; + 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; + } - again: - dns_fixedname_init(&fixed); - result = dns_nsec3_hashname(&fixed, NULL, NULL, &name, - dns_db_origin(db), hash, - iterations, salt, salt_length); + 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) - return; + 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; - dboptions = client->query.dboptions | DNS_DBFIND_FORCENSEC3; - result = dns_db_findext(db, dns_fixedname_name(&fixed), version, - dns_rdatatype_nsec3, dboptions, client->now, - NULL, fname, &cm, &ci, rdataset, sigrdataset); + dns_rdatalist_init(myrdatalist); + myrdatalist->rdclass = dns_rdataclass_in; + myrdatalist->type = dns_rdatatype_aaaa; + myrdatalist->ttl = qctx->rdataset->ttl; - if (result == DNS_R_NXDOMAIN) { - if (!dns_rdataset_isassociated(rdataset)) { - return; - } - result = dns_rdataset_first(rdataset); - INSIST(result == ISC_R_SUCCESS); - dns_rdataset_current(rdataset, &rdata); - dns_rdata_tostruct(&rdata, &nsec3, NULL); + 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); - optout = ISC_TF((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); - if (found != NULL && optout && - dns_name_issubdomain(&name, dns_db_origin(db))) - { - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(sigrdataset)) - dns_rdataset_disassociate(sigrdataset); - skip++; - dns_name_getlabelsequence(qname, skip, labels - skip, - &name); - ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, - NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(3), - "looking for closest provable encloser"); - goto again; - } - if (exact) - ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, - NS_LOGMODULE_QUERY, ISC_LOG_WARNING, - "expected a exact match NSEC3, got " - "a covering record"); + } + if (result != ISC_R_NOMORE) + goto cleanup; - } else if (result != ISC_R_SUCCESS) { - return; - } else if (!exact) - ns_client_log(client, DNS_LOGCATEGORY_DNSSEC, - NS_LOGMODULE_QUERY, ISC_LOG_WARNING, - "expected covering NSEC3, got an exact match"); - if (found == qname) { - if (skip != 0U) - dns_name_getlabelsequence(qname, skip, labels - skip, - found); - } else if (found != NULL) - dns_name_copy(&name, found, NULL); - return; -} + 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); -#ifdef ALLOW_FILTER_AAAA -static isc_boolean_t -is_v4_client(ns_client_t *client) { - if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) - return (ISC_TRUE); - if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && - IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) - return (ISC_TRUE); - return (ISC_FALSE); -} + if (myrdata != NULL) + dns_message_puttemprdata(client->message, &myrdata); -static isc_boolean_t -is_v6_client(ns_client_t *client) { - if (isc_sockaddr_pf(&client->peeraddr) == AF_INET6 && - !IN6_IS_ADDR_V4MAPPED(&client->peeraddr.type.sin6.sin6_addr)) - return (ISC_TRUE); - return (ISC_FALSE); + 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"); } -#endif -static isc_uint32_t -dns64_ttl(dns_db_t *db, dns_dbversion_t *version) { - dns_dbnode_t *node = NULL; - dns_rdata_soa_t soa; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_t rdataset; +/*% + * 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; - isc_uint32_t ttl = ISC_UINT32_MAX; - dns_rdataset_init(&rdataset); + INSIST(!qctx->is_zone); - result = dns_db_getoriginnode(db, &node); - if (result != ISC_R_SUCCESS) - goto cleanup; + if (qctx->db != NULL) + dns_db_detach(&qctx->db); - result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, - 0, 0, &rdataset, NULL); - if (result != ISC_R_SUCCESS) - goto cleanup; - result = dns_rdataset_first(&rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup; + /* + * 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_rdataset_current(&rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &soa, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - ttl = ISC_MIN(rdataset.ttl, soa.minimum); + 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); -cleanup: - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - return (ttl); + /* + * 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)); } -static isc_boolean_t -dns64_aaaaok(ns_client_t *client, dns_rdataset_t *rdataset, - dns_rdataset_t *sigrdataset) -{ - isc_netaddr_t netaddr; - dns_dns64_t *dns64 = ISC_LIST_HEAD(client->view->dns64); - unsigned int flags = 0; - unsigned int i, count; - isc_boolean_t *aaaaok; +/*% + * 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; - INSIST(client->query.dns64_aaaaok == NULL); - INSIST(client->query.dns64_aaaaoklen == 0); - INSIST(client->query.dns64_aaaa == NULL); - INSIST(client->query.dns64_sigaaaa == 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; - if (dns64 == NULL) - return (ISC_TRUE); + return (query_lookup(qctx)); + } - if (RECURSIONOK(client)) - flags |= DNS_DNS64_RECURSIVE; + /* + * 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 (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) - flags |= DNS_DNS64_DNSSEC; + /* + * 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; - count = dns_rdataset_count(rdataset); - aaaaok = isc_mem_get(client->mctx, sizeof(isc_boolean_t) * count); + /* + * Add a DS if needed + */ + query_addds(qctx); - isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); - if (dns_dns64_aaaaok(dns64, &netaddr, client->signer, - &ns_g_server->aclenv, flags, rdataset, - aaaaok, count)) { - for (i = 0; i < count; i++) { - if (aaaaok != NULL && !aaaaok[i]) { - SAVE(client->query.dns64_aaaaok, aaaaok); - client->query.dns64_aaaaoklen = count; - break; - } - } - if (aaaaok != NULL) - isc_mem_put(client->mctx, aaaaok, - sizeof(isc_boolean_t) * count); - return (ISC_TRUE); - } - if (aaaaok != NULL) - isc_mem_put(client->mctx, aaaaok, - sizeof(isc_boolean_t) * count); - return (ISC_FALSE); + return (query_done(qctx)); } -/* - * Look for the name and type in the redirection zone. If found update - * the arguments as appropriate. Return ISC_TRUE if a update was - * performed. +/*% + * Handle delegation responses, including root referrals. * - * Only perform the update if the client is in the allow query acl and - * returning the update would not cause a DNSSEC validation failure. + * 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 -redirect(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, - dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, - dns_rdatatype_t qtype) -{ - dns_db_t *db = NULL; - dns_dbnode_t *node = NULL; - dns_fixedname_t fixed; - dns_name_t *found; - dns_rdataset_t trdataset; +query_delegation(query_ctx_t *qctx) { isc_result_t result; - dns_rdatatype_t type; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - ns_dbversion_t *dbversion; - - CTRACE(ISC_LOG_DEBUG(3), "redirect"); + dns_rdataset_t **sigrdatasetp = NULL; - if (client->view->redirect == NULL) - return (ISC_R_NOTFOUND); + qctx->authoritative = ISC_FALSE; - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - dns_rdataset_init(&trdataset); + if (qctx->is_zone) { + return (query_zone_delegation(qctx)); + } - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + 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); - if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) - return (ISC_R_NOTFOUND); + /* + * 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); - if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { - if (rdataset->trust == dns_trust_secure) - return (ISC_R_NOTFOUND); - if (rdataset->trust == dns_trust_ultimate && - (rdataset->type == dns_rdatatype_nsec || - rdataset->type == dns_rdatatype_nsec3)) - return (ISC_R_NOTFOUND); - if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - dns_ncache_current(rdataset, found, &trdataset); - type = trdataset.type; - dns_rdataset_disassociate(&trdataset); - if (type == dns_rdatatype_nsec || - type == dns_rdatatype_nsec3 || - type == dns_rdatatype_rrsig) - return (ISC_R_NOTFOUND); - } - } + /* + * 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(). + */ } - result = ns_client_checkaclsilent(client, NULL, - dns_zone_getqueryacl(client->view->redirect), - ISC_TRUE); - if (result != ISC_R_SUCCESS) - return (ISC_R_NOTFOUND); + 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; - result = dns_zone_getdb(client->view->redirect, &db); - if (result != ISC_R_SUCCESS) - return (ISC_R_NOTFOUND); + INSIST(!REDIRECT(qctx->client)); - dbversion = query_findversion(client, db); - if (dbversion == NULL) { - dns_db_detach(&db); - return (ISC_R_NOTFOUND); + 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)); } /* - * Lookup the requested data in the redirect zone. + * 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. */ - result = dns_db_findext(db, client->query.qname, dbversion->version, - qtype, DNS_DBFIND_NOZONECUT, client->now, - &node, found, &cm, &ci, &trdataset, NULL); - if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(&trdataset)) - dns_rdataset_disassociate(&trdataset); - goto nxrrset; - } else if (result != ISC_R_SUCCESS) { - if (dns_rdataset_isassociated(&trdataset)) - dns_rdataset_disassociate(&trdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - dns_db_detach(&db); - return (ISC_R_NOTFOUND); - } - - CTRACE(ISC_LOG_DEBUG(3), "redirect: found data: done"); - dns_name_copy(found, name, NULL); - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(&trdataset)) { - dns_rdataset_clone(&trdataset, rdataset); - dns_rdataset_disassociate(&trdataset); + qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; + if (qctx->sigrdataset != NULL) { + sigrdatasetp = &qctx->sigrdataset; } - nxrrset: - if (*nodep != NULL) - dns_db_detachnode(*dbp, nodep); - dns_db_detach(dbp); - dns_db_attachnode(db, node, nodep); - dns_db_attach(db, dbp); - dns_db_detachnode(db, &node); - dns_db_detach(&db); - *versionp = dbversion->version; + 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; - client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | - NS_QUERYATTR_NOADDITIONAL); + /* + * Add a DS if needed + */ + query_addds(qctx); - return (result); + return (query_done(qctx)); } -static isc_result_t -redirect2(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset, - dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp, - dns_rdatatype_t qtype, isc_boolean_t *is_zonep) -{ - dns_db_t *db = NULL; - dns_dbnode_t *node = NULL; +/*% + * 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_fixedname_t fixedredirect; - dns_name_t *found, *redirectname; - dns_rdataset_t trdataset; + 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; - dns_rdatatype_t type; - dns_clientinfomethods_t cm; - dns_clientinfo_t ci; - dns_dbversion_t *version = NULL; - dns_zone_t *zone = NULL; - isc_boolean_t is_zone; - unsigned int options; + unsigned int count; - CTRACE(ISC_LOG_DEBUG(3), "redirect2"); + CTRACE(ISC_LOG_DEBUG(3), "query_addds"); - if (client->view->redirectzone == NULL) - return (ISC_R_NOTFOUND); + /* + * DS not needed. + */ + if (!WANTDNSSEC(client)) { + return; + } - if (dns_name_issubdomain(name, client->view->redirectzone)) - return (ISC_R_NOTFOUND); + /* + * We'll need some resources... + */ + rdataset = query_newrdataset(client); + sigrdataset = query_newrdataset(client); + if (rdataset == NULL || sigrdataset == NULL) + goto cleanup; - dns_fixedname_init(&fixed); - found = dns_fixedname_name(&fixed); - dns_rdataset_init(&trdataset); + /* + * 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; - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + /* + * 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; - if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp)) - return (ISC_R_NOTFOUND); + 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; - if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) { - if (rdataset->trust == dns_trust_secure) - return (ISC_R_NOTFOUND); - if (rdataset->trust == dns_trust_ultimate && - (rdataset->type == dns_rdatatype_nsec || - rdataset->type == dns_rdatatype_nsec3)) - return (ISC_R_NOTFOUND); - if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { - for (result = dns_rdataset_first(rdataset); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(rdataset)) { - dns_ncache_current(rdataset, found, &trdataset); - type = trdataset.type; - dns_rdataset_disassociate(&trdataset); - if (type == dns_rdatatype_nsec || - type == dns_rdatatype_nsec3 || - type == dns_rdatatype_rrsig) - return (ISC_R_NOTFOUND); + 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); + } - dns_fixedname_init(&fixedredirect); - redirectname = dns_fixedname_name(&fixedredirect); - if (dns_name_countlabels(name) > 1U) { - dns_name_t prefix; - unsigned int labels = dns_name_countlabels(name) - 1; + 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)); + } - dns_name_init(&prefix, NULL); - dns_name_getlabelsequence(name, 0, labels, &prefix); - result = dns_name_concatenate(&prefix, - client->view->redirectzone, - redirectname, NULL); - if (result != ISC_R_SUCCESS) - return (ISC_R_NOTFOUND); - } else - dns_name_copy(redirectname, client->view->redirectzone, NULL); + 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; + } + } - options = 0; - result = query_getdb(client, redirectname, qtype, options, &zone, - &db, &version, &is_zone); - if (result != ISC_R_SUCCESS) - return (ISC_R_NOTFOUND); - if (zone != NULL) - dns_zone_detach(&zone); + 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; /* - * Lookup the requested data in the redirect zone. + * Look for a NSEC3 record if we don't have a NSEC record. */ - result = dns_db_findext(db, redirectname, version, - qtype, 0, client->now, - &node, found, &cm, &ci, &trdataset, NULL); - if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) { - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(&trdataset)) - dns_rdataset_disassociate(&trdataset); - goto nxrrset; - } else if (result == ISC_R_NOTFOUND || result == DNS_R_DELEGATION) { + 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)) { /* - * Cleanup. + * 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. */ - if (dns_rdataset_isassociated(&trdataset)) - dns_rdataset_disassociate(&trdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - dns_db_detach(&db); + query_keepname(qctx->client, qctx->fname, qctx->dbuf); + } else if (qctx->fname != NULL) { /* - * Don't loop forever if the lookup failed last time. + * We're not going to use fname, and need to release + * our hold on the name buffer so query_addsoa() + * may use it. */ - if (!REDIRECT(client)) { - result = query_recurse(client, qtype, redirectname, - NULL, NULL, ISC_TRUE); - if (result == ISC_R_SUCCESS) { - client->query.attributes |= - NS_QUERYATTR_RECURSING; - client->query.attributes |= - NS_QUERYATTR_REDIRECT; - return (DNS_R_CONTINUE); - } - } - return (ISC_R_NOTFOUND); - } else if (result != ISC_R_SUCCESS) { - if (dns_rdataset_isassociated(&trdataset)) - dns_rdataset_disassociate(&trdataset); - if (node != NULL) - dns_db_detachnode(db, &node); - dns_db_detach(&db); - return (ISC_R_NOTFOUND); + query_releasename(qctx->client, &qctx->fname); } - CTRACE(ISC_LOG_DEBUG(3), "redirect2: found data: done"); /* - * Adjust the found name to not include the redirectzone suffix. + * Add SOA to the additional section if generated by a RPZ + * rewrite. */ - dns_name_split(found, dns_name_countlabels(client->view->redirectzone), - found, NULL); + 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)); + } + /* - * Make the name absolute. + * Add NSEC record if we found one. */ - result = dns_name_concatenate(found, dns_rootname, found, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (WANTDNSSEC(qctx->client) && + dns_rdataset_isassociated(qctx->rdataset)) + { + query_addnxrrsetnsec(qctx); + } - dns_name_copy(found, name, NULL); - if (dns_rdataset_isassociated(rdataset)) - dns_rdataset_disassociate(rdataset); - if (dns_rdataset_isassociated(&trdataset)) { - dns_rdataset_clone(&trdataset, rdataset); - dns_rdataset_disassociate(&trdataset); + 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; } - nxrrset: - if (*nodep != NULL) - dns_db_detachnode(*dbp, nodep); - dns_db_detach(dbp); - dns_db_attachnode(db, node, nodep); - dns_db_attach(db, dbp); - dns_db_detachnode(db, &node); - dns_db_detach(&db); - *is_zonep = is_zone; - *versionp = version; - client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | - NS_QUERYATTR_NOADDITIONAL); + if (qctx->sigrdataset == NULL || + !dns_rdataset_isassociated(qctx->sigrdataset)) + { + return; + } - return (result); -} + if (dns_rdataset_first(qctx->sigrdataset) != ISC_R_SUCCESS) { + return; + } -/* - * 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. - */ -static isc_result_t -query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) -{ - 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; -#ifdef WANT_QUERYTRACE - char mbuf[BUFSIZ]; - char qbuf[DNS_NAME_FORMATSIZE]; - char tbuf[DNS_RDATATYPE_FORMATSIZE]; -#endif - dns_name_t *rpzqname; + 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; + } - CTRACE(ISC_LOG_DEBUG(3), "query_find"); + query_addwildcardproof(qctx, ISC_TRUE, ISC_FALSE); /* - * One-time initialization. - * - * It's especially important to initialize anything that the cleanup - * code might cleanup. + * We'll need some resources... */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { + return; + } - 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; + fname = query_newname(client, dbuf, &b); + if (fname == NULL) { + return; + } - dns_clientinfomethods_init(&cm, ns_client_sourceip); - dns_clientinfo_init(&ci, client, NULL); + 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); +} -#ifdef WANT_QUERYTRACE - if (client->query.origqname != NULL) - dns_name_format(client->query.origqname, qbuf, - sizeof(qbuf)); - else - snprintf(qbuf, sizeof(qbuf), ""); +/*% + * 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; - 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); -#endif + INSIST(qctx->is_zone || REDIRECT(qctx->client)); + + if (!empty_wild) { + result = query_redirect(qctx); + if (result != ISC_R_COMPLETE) + return (result); + } - if (event != NULL) { + 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 returning from recursion. Restore the query context - * and resume. + * We're not going to use fname, and need to release + * our hold on the name buffer so query_addsoa() + * may use it. */ - want_restart = ISC_FALSE; + query_releasename(qctx->client, &qctx->fname); + } - 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 + /* + * 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)); + } - 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; + 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); + } - /* - * 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); + /* + * Set message rcode. + */ + if (empty_wild) + qctx->client->message->rcode = dns_rcode_noerror; + else + qctx->client->message->rcode = dns_rcode_nxdomain; - if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) - type = dns_rdatatype_any; - else - type = qtype; + return (query_done(qctx)); +} - 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; - } +/* + * 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; - 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; - } - } + 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; + } - /* - * 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; + 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); } - resuming = ISC_TRUE; - goto resume; } + 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; + /* - * 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. + * If we have a zero ttl from the cache refetch it. */ - if (RECURSIONOK(client)) { - flags = 0; -#ifdef ENABLE_AFL - if (ns_g_fuzz_type == ns_fuzz_resolver) { - failcache = ISC_FALSE; + 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 { - failcache = dns_badcache_find(client->view->failcache, - client->query.qname, qtype, - &flags, &client->tnow); + RECURSE_ERROR(qctx, result); } -#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; - } + return (query_done(qctx)); } /* - * If it's a SIG query, we'll iterate the node. + * 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). */ - if (qtype == dns_rdatatype_rrsig || qtype == dns_rdatatype_sig) - type = dns_rdatatype_any; - else - type = qtype; + trdataset = qctx->rdataset; - 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; - - if (client->view->checknames && - !dns_rdata_checkowner(client->query.qname, - client->message->rdclass, - qtype, ISC_FALSE)) { - char namebuf[DNS_NAME_FORMATSIZE]; - char typename[DNS_RDATATYPE_FORMATSIZE]; - char classname[DNS_RDATACLASS_FORMATSIZE]; + /* + * Add the CNAME to the answer section. + */ + if (qctx->sigrdataset != NULL) + sigrdatasetp = &qctx->sigrdataset; - 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, - NS_LOGMODULE_QUERY, ISC_LOG_ERROR, - "check-names failure %s/%s/%s", namebuf, - typename, classname); - QUERY_ERROR(DNS_R_REFUSED); - goto cleanup; + 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); + /* - * First we must find the right database. + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. */ - 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)) - { - /* - * If the query type is DS, look to see if we are - * authoritative for the child zone. - */ - dns_db_t *tdb = NULL; - dns_zone_t *tzone = NULL; - dns_dbversion_t *tversion = NULL; + qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; - tresult = query_getzonedb(client, client->query.qname, 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; - result = ISC_R_SUCCESS; - } else { - if (tdb != NULL) - dns_db_detach(&tdb); - if (tzone != NULL) - dns_zone_detach(&tzone); - } + /* + * 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) { - if (result == DNS_R_REFUSED) { - if (WANTRECURSION(client)) { - inc_stats(client, - dns_nsstatscounter_recurserej); - } else - inc_stats(client, dns_nsstatscounter_authrej); - if (!PARTIALANSWER(client)) - QUERY_ERROR(DNS_R_REFUSED); - } else { - CTRACE(ISC_LOG_ERROR, - "query_find: query_getdb failed"); - QUERY_ERROR(DNS_R_SERVFAIL); - } - goto cleanup; + dns_message_puttempname(qctx->client->message, &tname); + 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; + 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)); } - if (event == NULL && client->query.restarts == 0) { - if (is_zone) { - if (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_db_attach(db, &client->query.authdb); - } - client->query.authdbset = ISC_TRUE; + dns_rdata_freestruct(&cname); + ns_client_qnamereplace(qctx->client, tname); + qctx->want_restart = ISC_TRUE; + if (!WANTRECURSION(qctx->client)) + qctx->options |= DNS_GETDB_NOLOG; - /* Track TCP vs UDP stats per zone */ - if (TCP(client)) - inc_stats(client, dns_nsstatscounter_tcp); - else - inc_stats(client, dns_nsstatscounter_udp); - } + 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; - db_find: - CTRACE(ISC_LOG_DEBUG(3), "query_find: db_find"); /* - * We'll need some resources... + * 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. */ - 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; - } - 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; - } - 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; - } + 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); + /* - * Now look for an answer in the database. If this is a dns64 - * AAAA lookup on a rpz database adjust the qname. + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. */ - if (dns64 && rpz) - rpzqname = client->query.rpz_st->p_name; - else - rpzqname = client->query.qname; + qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; - result = dns_db_findext(db, rpzqname, version, type, - client->query.dboptions, client->now, - &node, fname, &cm, &ci, rdataset, sigrdataset); /* - * Fixup fname and sigrdataset. + * Get the target name of the DNAME. */ - if (dns64 && rpz) { - isc_result_t rresult; + tname = NULL; + result = dns_message_gettempname(qctx->client->message, &tname); + if (result != ISC_R_SUCCESS) + return (query_done(qctx)); - 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); + result = dns_rdataset_first(trdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(qctx->client->message, &tname); + return (query_done(qctx)); } - if (!is_zone) - dns_cache_updatestats(client->view->cache, result); + 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)); + } - resume: - CTRACE(ISC_LOG_DEBUG(3), "query_find: resume"); + dns_name_clone(&dname.dname, tname); + dns_rdata_freestruct(&dname); /* - * Rate limit these responses to this client. - * Do not delay counting and handling obvious referrals, - * since those won't come here again. - * Delay handling delegations for which we are certain to recurse and - * return here (DNS_R_DELEGATION, not a child of one of our - * own zones, and recursion enabled) - * Don't mess with responses rewritten by RPZ - * Count each response at most once. + * Construct the new qname consisting of + * . */ - 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) - { - dns_rdataset_t nc_rdataset; - isc_boolean_t wouldlog; - 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; + 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); - client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; + return (ISC_R_SUCCESS); +} - wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP); - constname = fname; - if (result == DNS_R_NXDOMAIN) { - /* - * Use the database origin name to rate limit NXDOMAIN - */ - if (db != NULL) - constname = dns_db_origin(db); - resp_result = result; - } else if (result == DNS_R_NCACHENXDOMAIN && - rdataset != NULL && - dns_rdataset_isassociated(rdataset) && - (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); - nc_result == ISC_R_SUCCESS; - nc_result = dns_rdataset_next(rdataset)) { - dns_ncache_current(rdataset, - dns_fixedname_name(&fixed), - &nc_rdataset); - if (nc_rdataset.type == dns_rdatatype_soa) { - dns_rdataset_disassociate(&nc_rdataset); - constname = dns_fixedname_name(&fixed); - break; - } - dns_rdataset_disassociate(&nc_rdataset); - } - resp_result = DNS_R_NXDOMAIN; - } else if (result == DNS_R_NXRRSET || - result == DNS_R_EMPTYNAME) { - resp_result = DNS_R_NXRRSET; - } else if (result == DNS_R_DELEGATION) { - resp_result = result; - } else if (result == ISC_R_NOTFOUND) { - /* - * Handle referral to ".", including when recursion - * is off or not requested and the hints have not - * been loaded or we have "additional-from-cache no". - */ - constname = dns_rootname; - resp_result = DNS_R_DELEGATION; - } 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, - wouldlog, log_buf, sizeof(log_buf)); - if (rrl_result != DNS_RRL_RESULT_OK) { - /* - * Log dropped or slipped responses in the query - * category so that requests are not silently lost. - * Starts of rate-limited bursts are logged in - * DNS_LOGCATEGORY_RRL. - * - * Dropped responses are counted with dropped queries - * in QryDropped while slipped responses are counted - * with other truncated responses in RespTruncated. - */ - if (wouldlog) { - ns_client_log(client, DNS_LOGCATEGORY_RRL, - NS_LOGMODULE_QUERY, - DNS_RRL_LOG_DROP, - "%s", log_buf); - } - if (!client->view->rrl->log_only) { - if (rrl_result == DNS_RRL_RESULT_DROP) { - /* - * These will also be counted in - * dns_nsstatscounter_dropped - */ - inc_stats(client, - dns_nsstatscounter_ratedropped); - QUERY_ERROR(DNS_R_DROP); - } else { - /* - * These will also be counted in - * dns_nsstatscounter_truncatedresp - */ - inc_stats(client, - dns_nsstatscounter_rateslipped); - if (WANTCOOKIE(client)) { - client->message->flags &= - ~DNS_MESSAGEFLAG_AA; - client->message->flags &= - ~DNS_MESSAGEFLAG_AD; - client->message->rcode = - dns_rcode_badcookie; - } else { - client->message->flags |= - DNS_MESSAGEFLAG_TC; - if (resp_result == - DNS_R_NXDOMAIN) - client->message->rcode = - dns_rcode_nxdomain; - } - } - goto cleanup; - } - } - } 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; +/*% + * 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; } - if (!RECURSING(client) && - !dns_name_equal(client->query.qname, dns_rootname)) +#ifdef ALLOW_FILTER_AAAA + /* + * The filter-aaaa-on-v4 option should suppress AAAAs for IPv4 + * clients if there is an A; filter-aaaa-on-v6 option does the same + * for IPv6 clients. + */ + qctx->client->filter_aaaa = dns_aaaa_ok; + if (qctx->client->view->v4_aaaa != dns_aaaa_ok || + qctx->client->view->v6_aaaa != dns_aaaa_ok) { - 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; + isc_result_t result; + result = ns_client_checkaclsilent(qctx->client, NULL, + qctx->client->view->aaaa_acl, + ISC_TRUE); + if (result == ISC_R_SUCCESS && + 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 && + qctx->client->view->v6_aaaa != dns_aaaa_ok && + is_v6_client(qctx->client)) + qctx->client->filter_aaaa = qctx->client->view->v6_aaaa; + } - 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); - } +#endif - /* - * 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); - } + if (qctx->type == dns_rdatatype_any) { + return (query_respond_any(qctx)); + } else { + return (query_respond(qctx)); } +} - norpz: - switch (result) { - case ISC_R_SUCCESS: - /* - * This case is handled in the main line below. - */ - break; - case DNS_R_GLUE: - case DNS_R_ZONECUT: - /* - * These cases are handled in the main line below. - */ - INSIST(is_zone); - authoritative = ISC_FALSE; - break; - 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); +/*% + * 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; - 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); + CTRACE(ISC_LOG_DEBUG(3), "query_addsoa"); + /* + * Initialization. + */ + eresult = ISC_R_SUCCESS; + name = NULL; + rdataset = NULL; + node = NULL; - /* - * 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); + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, 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. - */ - } + /* + * 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); + } - 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)); - } + /* + * 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; } - goto cleanup; + } - case DNS_R_EMPTYNAME: - case DNS_R_NXRRSET: - iszone_nxrrset: - INSIST(is_zone); + /* + * 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; -#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; - } + 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) { + /* + * 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 { /* - * Look for a NSEC3 record if we don't have a NSEC record. + * Extract the SOA MINIMUM. */ - nxrrset_rrsig: - if (redirected) + 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 (!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); + if (override_ttl != ISC_UINT32_MAX && + override_ttl < rdataset->ttl) + { + rdataset->ttl = override_ttl; + if (sigrdataset != NULL) + sigrdataset->ttl = override_ttl; } /* - * Add SOA to the additional section if generated by a RPZ - * rewrite. + * Add the SOA and its SIG to the response, with the + * TTLs adjusted per RFC2308 section 3. */ - associated = dns_rdataset_isassociated(rdataset); - section = nxrewrite ? DNS_SECTION_ADDITIONAL : - DNS_SECTION_AUTHORITY; + if (rdataset->ttl > soa.minimum) + rdataset->ttl = soa.minimum; + if (sigrdataset != NULL && sigrdataset->ttl > soa.minimum) + sigrdataset->ttl = soa.minimum; - result = query_addsoa(client, db, version, ISC_UINT32_MAX, - associated, section); - if (result != ISC_R_SUCCESS) { - QUERY_ERROR(result); - goto cleanup; - } + if (sigrdataset != NULL) + sigrdatasetp = &sigrdataset; + else + sigrdatasetp = NULL; - /* - * 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; + if (section == DNS_SECTION_ADDITIONAL) + rdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + query_addrrset(client, &name, &rdataset, + sigrdatasetp, NULL, section); + } - case DNS_R_EMPTYWILD: - empty_wild = ISC_TRUE; - /* FALLTHROUGH */ + 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); - 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); - } + return (eresult); +} - /* - * 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; - } +/*% + * 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; - 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); - } + CTRACE(ISC_LOG_DEBUG(3), "query_addns"); - /* - * Set message rcode. - */ - if (empty_wild) - client->message->rcode = dns_rcode_noerror; - else - client->message->rcode = dns_rcode_nxdomain; + /* + * 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; + } - 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; + 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; } - 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 */ + } - 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; + /* + * 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"); /* - * Look for RFC 1918 leakage from Internet. + * This is bad. We tried to get the NS rdataset at the zone + * top and it didn't work! */ - 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); + 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; -#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. - */ + CTRACE(ISC_LOG_DEBUG(3), "query_addbestns"); - /* - * 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; - } + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, client, NULL); - /* - * 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; - } + /* + * 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; - 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); + 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; + } - 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); + /* + * 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; } + } - /* - * 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); + /* + * 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; } - 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; + 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; } - 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); + } 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; } - dns_name_clone(&dname.dname, tname); - dns_rdata_freestruct(&dname); + } + + if (use_zone) { + query_releasename(client, &fname); /* - * Construct the new qname consisting of - * . + * 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. */ - 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; + dbuf = NULL; + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) { + query_putrdataset(client, &sigrdataset); } - fname = query_newname(client, dbuf, &b); - if (fname == NULL) { - dns_message_puttempname(client->message, &tname); - goto cleanup; + + if (node != NULL) { + dns_db_detachnode(db, &node); } - result = dns_name_concatenate(prefix, tname, fname, NULL); - dns_message_puttempname(client->message, &tname); + dns_db_detach(&db); - /* - * 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; + RESTORE(db, zdb); + RESTORE(fname, zfname); + RESTORE(rdataset, zrdataset); + RESTORE(sigrdataset, zsigrdataset); + } - 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", - isc_result_totext(result)); - CTRACE(ISC_LOG_ERROR, errmsg); - QUERY_ERROR(DNS_R_SERVFAIL); + /* + * 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 (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 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 +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); -#ifdef ALLOW_FILTER_AAAA /* - * The filter-aaaa-on-v4 option should suppress AAAAs for IPv4 - * clients if there is an A; filter-aaaa-on-v6 option does the same - * for IPv6 clients. + * 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. */ - client->filter_aaaa = dns_aaaa_ok; - if (client->view->v4_aaaa != dns_aaaa_ok || - client->view->v6_aaaa != dns_aaaa_ok) - { - result = ns_client_checkaclsilent(client, NULL, - 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; - else if (result == ISC_R_SUCCESS && - client->view->v6_aaaa != dns_aaaa_ok && - is_v6_client(client)) - client->filter_aaaa = client->view->v6_aaaa; + if (qctx->need_wildcardproof) { + name = dns_fixedname_name(&qctx->wildcardname); + } else { + name = client->query.qname; } -#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; + /* + * 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; - /* - * 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); - 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)) { /* - * 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! + * No NSEC proof available, return NSEC3 proofs instead. */ - 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 + dns_fixedname_init(&cfixed); + cname = dns_fixedname_name(&cfixed); /* - * Filter AAAAs if there is an A and there is no signature - * or we are supposed to break DNSSEC. + * Find the closest encloser. */ - 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) { + dns_name_copy(name, cname, NULL); + while (result == DNS_R_NXDOMAIN) { + labels = dns_name_countlabels(cname) - 1; /* - * No matching rdatasets found in cache. If we were - * searching for RRSIG/SIG, that's probably okay; - * otherwise this is an error condition. + * Sanity check. */ - 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; + 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); } - } else { - /* - * This is the "normal" case -- an ordinary question to which - * we know the answer. - */ - /* - * If we have a zero ttl from the cache refetch it. + * Add closest (provable) encloser NSEC3. */ - 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); + 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); -#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. + * Replace resources which were consumed by query_addrrset. */ - if (client->filter_aaaa == dns_aaaa_break_dnssec || - (client->filter_aaaa == dns_aaaa_filter && - (!WANTDNSSEC(client) || sigrdataset == NULL || - !dns_rdataset_isassociated(sigrdataset)))) - { - 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); + if (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) goto cleanup; - } + fname = query_newname(client, dbuf, &b); } -#endif - /* - * Check to see if the AAAA RRset has non-excluded addresses - * in it. If not look for a A RRset. - */ - 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 == NULL) + rdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); - if (sigrdataset != NULL) - sigrdatasetp = &sigrdataset; - else - sigrdatasetp = NULL; - if (NOQNAME(rdataset) && WANTDNSSEC(client)) - noqname = rdataset; - else - noqname = NULL; + 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; /* - * BIND 8 priming queries need the additional section. + * Add no qname proof. */ - if (is_zone && qtype == dns_rdatatype_ns && - dns_name_equal(client->query.qname, dns_rootname)) - client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; + 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; /* - * Return the time to expire for slave and master zones. + * Replace resources which were consumed by query_addrrset. */ - 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 (fname == NULL) { + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); } - if (dns64) { - qtype = type = dns_rdatatype_aaaa; - result = query_dns64(client, &fname, rdataset, - sigrdataset, dbuf, - DNS_SECTION_ANSWER); + if (rdataset == NULL) + rdataset = query_newrdataset(client); + else if (dns_rdataset_isassociated(rdataset)) 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); + 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; /* - * We shouldn't ever fail to add 'rdataset' - * because it's already in the answer. + * Add the no wildcard proof. */ - INSIST(rdataset == NULL); + 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); +} - addauth: - CTRACE(ISC_LOG_DEBUG(3), "query_find: addauth"); +/*% + * 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 (!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); + 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); } } @@ -8813,122 +9500,220 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * 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"); + 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; + } + + 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. */ - 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); - 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); + 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; } - if (event != NULL) - isc_event_free(ISC_EVENT_PTR(&event)); + + qctx_clean(qctx); + qctx_freedata(qctx); /* - * AA bit. + * Clear the AA bit if we're not authoritative. */ - if (client->query.restarts == 0 && !authoritative) { - /* - * We're not authoritative, so we must ensure the AA bit - * isn't set. - */ - client->message->flags &= ~DNS_MESSAGEFLAG_AA; + if (qctx->client->query.restarts == 0 && !qctx->authoritative) { + qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA; } /* - * Restart the query? + * Do we need to restart the query (e.g. for CNAME chaining)? */ - if (want_restart && client->query.restarts < MAX_RESTARTS) { - client->query.restarts++; - goto restart; + if (qctx->want_restart && qctx->client->query.restarts < MAX_RESTARTS) { + qctx->client->query.restarts++; + return (query_start(qctx)); } - if (eresult != ISC_R_SUCCESS && - (!PARTIALANSWER(client) || WANTRECURSION(client) - || eresult == DNS_R_DROP)) { - if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) { + 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); + ns_client_detach(&qctx->client); + return (qctx->result); + } + + /* + * If we're recursing then just return; the query will + * resume when recursion ends. + */ + if (RECURSING(qctx->client)) { + return (qctx->result); + } - if (client->message->rcode == dns_rcode_nxdomain && - client->view->auth_nxdomain == ISC_TRUE) - client->message->flags |= DNS_MESSAGEFLAG_AA; + /* + * 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 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; + if (qctx->client->message->rcode == dns_rcode_nxdomain && + qctx->client->view->auth_nxdomain == ISC_TRUE) + { + qctx->client->message->flags |= DNS_MESSAGEFLAG_AA; + } - query_send(client); - ns_client_detach(&client); + /* + * 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; } - CTRACE(ISC_LOG_DEBUG(3), "query_find: done"); - return (eresult); + 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 5ccef960e67..5cf7679c9e8 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 6f7ecf213c9..ec5f38936d4 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 071dac02eca..bb8c6eb596e 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