]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
4573. [func] Query logic has been substantially refactored (e.g. query_find function...
authorwpk <wpk@isc.org>
Wed, 8 Feb 2017 21:15:01 +0000 (22:15 +0100)
committerwpk <wpk@isc.org>
Wed, 8 Feb 2017 21:15:01 +0000 (22:15 +0100)
CHANGES
bin/named/query.c
doc/arm/notes.xml
lib/isc/include/isc/result.h
lib/isc/result.c

diff --git a/CHANGES b/CHANGES
index 404f3856f43a99a24c8fa1018f46f2daaa6ddbce..a696770b443ba3636e1eabdcec6cd25854ee6b94 100644 (file)
--- 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
index d66c28dc16216d03ab8623a32748d2562c5990dc..aca54a7db3aed490b2e9b459ef38d707901ab25c 100644 (file)
@@ -46,6 +46,7 @@
 #include <dns/result.h>
 #include <dns/stats.h>
 #include <dns/tkey.h>
+#include <dns/types.h>
 #include <dns/view.h>
 #include <dns/zone.h>
 #include <dns/zt.h>
 #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), "<unset>");
+
+       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] = "<unset>";
+                       char fbuf[DNS_NAME_FORMATSIZE] = "<unset>";
+                       if (qctx->rpz_st->r_name != NULL)
+                               dns_name_format(qctx->rpz_st->r_name,
+                                               qbuf, sizeof(qbuf));
+                       else
+                               snprintf(qbuf, sizeof(qbuf),
+                                        "<unset>");
+                       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), "<unset>");
+/*%
+ * 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] = "<unset>";
-                               char fbuf[DNS_NAME_FORMATSIZE] = "<unset>";
-                               if (rpz_st->r_name != NULL)
-                                       dns_name_format(rpz_st->r_name,
-                                                       qbuf, sizeof(qbuf));
-                               else
-                                       snprintf(qbuf, sizeof(qbuf),
-                                                "<unset>");
-                               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
+        * <found name prefix>.<dname target>
         */
-       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
+        *   <old qname> <dname ttl> CNAME <new qname>
+        *          with <dname trust value>
+        *
+        * 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
-                * <found name prefix>.<dname target>
+                * 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
-                *   <old qname> <dname ttl> CNAME <new qname>
-                *          with <dname trust value>
-                *
-                * 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);
 }
index 5ccef960e6738b42fde4d94d2e3827dadc95a702..5cf7679c9e827ede4cb6e4602a7eac00debdf551 100644 (file)
 
   <section xml:id="relnotes_features"><info><title>New Features</title></info>
     <itemizedlist>
+      <listitem>
+        <para>
+         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]
+       </para>
+      </listitem>
       <listitem>
        <para>
          <command>dnstap</command> logfiles can now be configured to
index 6f7ecf213c956d8c9e93cc66c9af3697a926748f..ec5f38936d4ecfe91ad11d7323f19b82a8bf37ec 100644 (file)
 #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
 
index 071dac02eca26db16ad9d04f876f42cecb610572..bb8c6eb596eedab0027d88eb3de995d6f5a34433 100644 (file)
@@ -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