]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
import libdummyrpz test library for DNSRPS
authorEvan Hunt <each@isc.org>
Mon, 13 Mar 2023 19:47:00 +0000 (12:47 -0700)
committerEvan Hunt <each@isc.org>
Tue, 28 Mar 2023 22:44:31 +0000 (15:44 -0700)
libdummyrpz is a limited version of the fastrpz library for use in
testing the dnsrps API.

bin/tests/system/Makefile.am
bin/tests/system/rpz/dnsrps.c
bin/tests/system/rpz/testlib/Makefile.am [new file with mode: 0644]
bin/tests/system/rpz/testlib/dummylib.c [new file with mode: 0644]
bin/tests/system/rpz/testlib/test-data.c [new file with mode: 0644]
bin/tests/system/rpz/testlib/test-data.h [new file with mode: 0644]
bin/tests/system/rpz/testlib/trpz.h [new file with mode: 0644]
configure.ac
lib/dns/dnsrps.c
lib/dns/include/dns/librpz.h
lib/ns/query.c

index 8ee01e1753b53a8740733d04ab233aa3e134cf80..c4ba3a0266b3c22513c5f9b8d9701a2d735ebe9d 100644 (file)
@@ -11,11 +11,17 @@ dist-hook:
 
 SUBDIRS = dyndb/driver dlzexternal/driver hooks/driver
 
+if DNSRPS
+SUBDIRS += rpz/testlib
+endif
+
 AM_CPPFLAGS +=                 \
-       $(LIBISC_CFLAGS)
+       $(LIBISC_CFLAGS)        \
+       $(LIBDNS_CFLAGS)
 
 LDADD +=                       \
-       $(LIBISC_LIBS)
+       $(LIBISC_LIBS)          \
+       $(LIBDNS_LIBS)
 
 if HAVE_PERL
 
@@ -48,11 +54,13 @@ pipelined_pipequeries_LDADD =       \
 
 rpz_dnsrps_CPPFLAGS =          \
        $(AM_CPPFLAGS)          \
-       $(LIBDNS_CFLAGS)
+       $(LIBDNS_CFLAGS)        \
+       -DLIBRPZ_LIB_OPEN=\"$(abs_builddir)/rpz/testlib/.libs/libdummyrpz.so\"
 
 rpz_dnsrps_LDADD =             \
        $(LDADD)                \
-       $(LIBDNS_LIBS)
+       $(LIBDNS_LIBS)          \
+       -ldl
 
 TESTS =
 
index 7bd7448f56a39b3a5aabfa57ba1e8686aacb6e68..664af2251213f881d31870d60c52092112749c92 100644 (file)
@@ -36,7 +36,6 @@
 #include <isc/util.h>
 
 #ifdef USE_DNSRPS
-#define LIBRPZ_LIB_OPEN DNSRPS_LIB_OPEN
 #include <dns/librpz.h>
 
 librpz_t *librpz;
diff --git a/bin/tests/system/rpz/testlib/Makefile.am b/bin/tests/system/rpz/testlib/Makefile.am
new file mode 100644 (file)
index 0000000..ac66f68
--- /dev/null
@@ -0,0 +1,12 @@
+include $(top_srcdir)/Makefile.top
+
+AM_CPPFLAGS +=                 \
+       $(LIBISC_CFLAGS)        \
+       $(LIBDNS_CFLAGS)
+
+AM_CFLAGS += -Wall -pedantic
+
+check_LTLIBRARIES = libdummyrpz.la
+libdummyrpz_la_SOURCES= dummylib.c test-data.c trpz.h test-data.h
+libdummyrpz_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -rpath $(abs_builddir)
+LDADD += -lpthread -ldl
diff --git a/bin/tests/system/rpz/testlib/dummylib.c b/bin/tests/system/rpz/testlib/dummylib.c
new file mode 100644 (file)
index 0000000..2645c28
--- /dev/null
@@ -0,0 +1,2181 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Limited implementation of the DNSRPS API for testing purposes.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/util.h>
+
+#include <dns/librpz.h>
+
+#include "test-data.h"
+#include "trpz.h"
+
+librpz_log_fnc_t *g_log_fnc = NULL;
+const char *g_prog_nm = NULL;
+bool g_scan_data_file_for_errors = true;
+
+typedef struct {
+       void *mutex_ctx;
+       void *log_ctx;
+       librpz_mutex_t *mutex_lock_fn;
+       librpz_mutex_t *mutex_unlock_fn;
+       librpz_mutex_t *mutex_destroy_fn;
+} trpz_clist_t;
+
+typedef struct {
+       char *cstr;
+       bool uses_expired;
+       trpz_clist_t *pclist;
+       ssize_t *base_zones;
+       size_t nbase_zones;
+} trpz_client_t;
+
+typedef struct {
+       size_t idx; /* value only used for node iteration */
+       trpz_client_t *client;
+       bool have_rd;
+       char zone[256];
+       char domain[256];
+       size_t zidx;
+       trpz_result_t rstack[LIBRPZ_RSP_STACK_DEPTH];
+       size_t stack_idx;
+       trpz_zone_t *all_zones;
+       trpz_result_t *all_nodes;
+       size_t num_zones, num_nodes;
+       ssize_t last_zone;
+} trpz_rsp_t;
+
+librpz_log_level_t g_log_level = LIBRPZ_LOG_TRACE2;
+FILE *g_log_outf = NULL;
+
+static int
+apply_all_updates(trpz_rsp_t *trsp);
+static void
+clear_all_updates(trpz_rsp_t *trsp);
+
+static int
+domain_ntop(const u_char *src, char *dst, size_t dstsiz);
+static int
+domain_pton2(const char *src, u_char *dst, size_t dstsiz, size_t *dstlen,
+            bool lower);
+
+void
+trpz_set_log(librpz_log_fnc_t *new_log, const char *prog_nm);
+void
+trpz_vlog(librpz_log_level_t level, void *ctx, const char *p, va_list args);
+void
+trpz_log(librpz_log_level_t level, void *ctx, const char *p, ...);
+librpz_log_level_t
+trpz_log_level_val(librpz_log_level_t level);
+void
+trpz_vpemsg(librpz_emsg_t *emsg, const char *p, va_list args);
+void
+trpz_pemsg(librpz_emsg_t *emsg, const char *fmt, ...);
+librpz_clist_t *
+trpz_clist_create(librpz_emsg_t *emsg, librpz_mutex_t *lock,
+                 librpz_mutex_t *unlock, librpz_mutex_t *mutex_destroy,
+                 void *mutex_ctx, void *log_ctx);
+void
+trpz_clist_detach(librpz_clist_t **clistp);
+bool
+trpz_connect(librpz_emsg_t *emsg, librpz_client_t *client, bool optional);
+librpz_client_t *
+trpz_client_create(librpz_emsg_t *emsg, librpz_clist_t *clist, const char *cstr,
+                  bool use_expired);
+void
+trpz_client_detach(librpz_client_t **clientp);
+bool
+trpz_rsp_create(librpz_emsg_t *emsg, librpz_rsp_t **rspp, int *min_ns_dotsp,
+               librpz_client_t *client, bool have_rd, bool have_do);
+void
+trpz_rsp_detach(librpz_rsp_t **rspp);
+bool
+trpz_rsp_push(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+bool
+trpz_rsp_pop(librpz_emsg_t *emsg, librpz_result_t *result, librpz_rsp_t *rsp);
+bool
+trpz_rsp_pop_discard(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+bool
+trpz_rsp_domain(librpz_emsg_t *emsg, librpz_domain_buf_t *owner,
+               librpz_rsp_t *rsp);
+bool
+trpz_rsp_result(librpz_emsg_t *emsg, librpz_result_t *result, bool recursed,
+               const librpz_rsp_t *rsp);
+bool
+trpz_rsp_soa(librpz_emsg_t *emsg, uint32_t *ttlp, librpz_rr_t **rrp,
+            librpz_domain_buf_t *origin, librpz_result_t *result,
+            librpz_rsp_t *rsp);
+bool
+trpz_rsp_rr(librpz_emsg_t *emsg, uint16_t *typep, uint16_t *classp,
+           uint32_t *ttlp, librpz_rr_t **rrp, librpz_result_t *result,
+           const uint8_t *qname, size_t qname_size, librpz_rsp_t *rsp);
+bool
+trpz_ck_domain(librpz_emsg_t *emsg, const uint8_t *domain, size_t domain_size,
+              librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed,
+              librpz_rsp_t *rsp);
+bool
+trpz_ck_ip(librpz_emsg_t *emsg, const void *addr, uint family,
+          librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed,
+          librpz_rsp_t *rsp);
+bool
+trpz_rsp_clientip_prefix(librpz_emsg_t *emsg, librpz_prefix_t *prefix,
+                        librpz_rsp_t *rsp);
+bool
+trpz_have_trig(librpz_trig_t trig, bool ipv6, const librpz_rsp_t *rsp);
+bool
+trpz_rsp_forget_zone(librpz_emsg_t *emsg, librpz_cznum_t znum,
+                    librpz_rsp_t *rsp);
+char *
+trpz_vers_stats(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
+bool
+trpz_soa_serial(librpz_emsg_t *emsg, uint32_t *serialp, const char *domain_nm,
+               librpz_rsp_t *rsp);
+const char *
+trpz_policy2str(librpz_policy_t policy, char *buf, size_t buf_size);
+
+#define BASE_ZONE_ANY    -1
+#define BASE_ZONE_INVALID -2
+
+librpz_0_t LIBRPZ_DEF = {
+       .dnsrpzd_path = "test-only",
+       .version = "0.0",
+       .log_level_val = trpz_log_level_val,
+       .set_log = trpz_set_log,
+       .vpemsg = trpz_vpemsg,
+       .pemsg = trpz_pemsg,
+       .vlog = trpz_vlog,
+       .log = trpz_log,
+       .clist_create = trpz_clist_create,
+       .clist_detach = trpz_clist_detach,
+       .client_create = trpz_client_create,
+       .connect = trpz_connect,
+       .client_detach = trpz_client_detach,
+       .rsp_create = trpz_rsp_create,
+       .rsp_detach = trpz_rsp_detach,
+       .rsp_result = trpz_rsp_result,
+       .have_trig = trpz_have_trig,
+       .rsp_domain = trpz_rsp_domain,
+       .rsp_rr = trpz_rsp_rr,
+       .rsp_soa = trpz_rsp_soa,
+       .soa_serial = trpz_soa_serial,
+       .rsp_push = trpz_rsp_push,
+       .rsp_pop = trpz_rsp_pop,
+       .rsp_pop_discard = trpz_rsp_pop_discard,
+       .rsp_forget_zone = trpz_rsp_forget_zone,
+       .ck_ip = trpz_ck_ip,
+       .ck_domain = trpz_ck_domain,
+       .policy2str = trpz_policy2str,
+};
+
+/*
+ * Returns whether or not searching in the specified zone, by index, is
+ * permitted. A client/RSP state can support a variable number of configured
+ * zones.
+ */
+static bool
+has_base_zone(trpz_client_t *cli, ssize_t zone) {
+       size_t n;
+
+       if (cli == NULL || cli->base_zones == NULL || cli->nbase_zones == 0) {
+               return (false);
+       }
+
+       for (n = 0; n < cli->nbase_zones; n++) {
+               if (cli->base_zones[n] == BASE_ZONE_ANY ||
+                   cli->base_zones[n] == zone)
+               {
+                       return (true);
+               }
+       }
+
+       return (false);
+}
+
+static bool
+pack_soa_record(unsigned char *rdatap, size_t rbufsz, size_t *rdlenp,
+               const rpz_soa_t *psoa) {
+       uint32_t *uptr = NULL;
+       size_t needed = (sizeof(uint32_t) * 5) + strlen(psoa->mname) + 2 +
+                       strlen(psoa->rname) + 2;
+       size_t mlen = 0, rlen = 0, used = 0;
+
+       if (needed > rbufsz) {
+               return (false);
+       }
+
+       if (!domain_pton2(psoa->mname, rdatap, rbufsz, &rlen, true)) {
+               return (false);
+       }
+
+       if (!domain_pton2(psoa->rname, rdatap + rlen, rbufsz - rlen, &mlen,
+                         true))
+       {
+               return (false);
+       }
+
+       used = rlen + mlen;
+
+       uptr = (uint32_t *)(rdatap + rlen + mlen);
+       *uptr++ = htonl(psoa->serial);
+       *uptr++ = htonl(psoa->refresh);
+       *uptr++ = htonl(psoa->retry);
+       *uptr++ = htonl(psoa->expire);
+       *uptr++ = htonl(psoa->minimum);
+
+       used += (sizeof(uint32_t) * 5);
+
+       if (rdlenp) {
+               *rdlenp = used;
+       }
+
+       return (true);
+}
+
+static void
+do_log(librpz_log_level_t level, void *ctx, const char *fmt, va_list args) {
+       if (level > g_log_level) {
+               return;
+       }
+
+       if (g_log_fnc != NULL) {
+               char lbuf[8192] = { 0 };
+
+               vsnprintf(lbuf, sizeof(lbuf) - 1, fmt, args);
+               g_log_fnc(level, ctx, lbuf);
+               return;
+       }
+
+       if (g_log_outf == NULL) {
+               return;
+       }
+
+       vfprintf(g_log_outf, fmt, args);
+       fprintf(g_log_outf, "\n");
+       return;
+}
+
+void
+trpz_vlog(librpz_log_level_t level, void *ctx, const char *p, va_list args) {
+       do_log(level, ctx, p, args);
+       return;
+}
+
+void
+trpz_log(librpz_log_level_t level, void *ctx, const char *p, ...) {
+       va_list ap;
+
+       va_start(ap, p);
+       trpz_vlog(level, ctx, p, ap);
+       va_end(ap);
+
+       return;
+}
+
+void
+trpz_set_log(librpz_log_fnc_t *new_log, const char *prog_nm) {
+       assert(new_log != NULL || prog_nm != NULL);
+
+       if (new_log != NULL) {
+               g_log_fnc = new_log;
+       }
+
+       if (prog_nm != NULL) {
+               g_prog_nm = prog_nm;
+       }
+}
+
+librpz_log_level_t
+trpz_log_level_val(librpz_log_level_t level) {
+       if (level >= LIBRPZ_LOG_INVALID) {
+               return (g_log_level);
+       }
+
+       g_log_level = (level < LIBRPZ_LOG_FATAL) ? LIBRPZ_LOG_FATAL : level;
+
+       return (g_log_level);
+}
+
+void
+trpz_vpemsg(librpz_emsg_t *emsg, const char *p, va_list args) {
+       if (emsg == NULL) {
+               return;
+       }
+
+       vsnprintf(emsg->c, sizeof(emsg->c), p, args);
+       emsg->c[sizeof(emsg->c) - 1] = 0;
+       return;
+}
+
+void
+trpz_pemsg(librpz_emsg_t *emsg, const char *fmt, ...) {
+       va_list ap;
+
+       va_start(ap, fmt);
+       trpz_vpemsg(emsg, fmt, ap);
+       va_end(ap);
+
+       return;
+}
+
+/*
+ * Scan the data file for errors and log anything found that's critically
+ * wrong.
+ */
+static void
+scan_data_file_for_errors(void *lctx) {
+       char *updfile = NULL, *fname = NULL, *last = NULL;
+
+       updfile = getenv("DNSRPS_TEST_UPDATE_FILE");
+       if (updfile == NULL) {
+               return;
+       }
+
+       fname = strtok_r(updfile, ":", &last);
+
+       while (fname) {
+               char *errp = NULL;
+               int ret;
+
+               ret = sanity_check_data_file(fname, &errp);
+
+               if ((ret < 0) && errp) {
+                       trpz_log(LIBRPZ_LOG_ERROR, lctx, errp);
+                       free(errp);
+               }
+
+               fname = strtok_r(NULL, ":", &last);
+       }
+
+       return;
+}
+
+librpz_clist_t *
+trpz_clist_create(librpz_emsg_t *emsg, librpz_mutex_t *lock,
+                 librpz_mutex_t *unlock, librpz_mutex_t *mutex_destroy,
+                 void *mutex_ctx, void *log_ctx) {
+       trpz_clist_t *result = NULL;
+
+       result = calloc(1, sizeof(*result));
+       if (result == NULL) {
+               trpz_pemsg(emsg, "calloc: %s", strerror(errno));
+               return (NULL);
+       }
+
+       result->mutex_ctx = mutex_ctx;
+       result->log_ctx = log_ctx;
+       result->mutex_lock_fn = lock;
+       result->mutex_unlock_fn = unlock;
+       result->mutex_destroy_fn = mutex_destroy;
+
+       if (g_scan_data_file_for_errors) {
+               scan_data_file_for_errors(log_ctx);
+       }
+
+       return ((librpz_clist_t *)result);
+}
+
+void
+trpz_clist_detach(librpz_clist_t **clistp) {
+       if (clistp != NULL && *clistp != NULL) {
+               librpz_clist_t *clist = *clistp;
+               *clistp = NULL;
+               free(clist);
+       }
+
+       return;
+}
+
+bool
+trpz_connect(librpz_emsg_t *emsg, librpz_client_t *client, bool optional) {
+       UNUSED(optional);
+
+       if (client == NULL) {
+               trpz_pemsg(emsg, "Can't connect to null client");
+               return (false);
+       }
+
+       return (true);
+}
+
+const char *
+trpz_policy2str(librpz_policy_t policy, char *buf, size_t buf_size) {
+       const char *pname = NULL;
+
+       if (buf == NULL || buf_size == 0) {
+               return (NULL);
+       }
+
+       switch (policy) {
+       case LIBRPZ_POLICY_UNDEFINED:
+               pname = "UNDEFINED";
+               break;
+       case LIBRPZ_POLICY_DELETED:
+               pname = "DELETED";
+               break;
+       case LIBRPZ_POLICY_PASSTHRU:
+               pname = "PASSTHRU";
+               break;
+       case LIBRPZ_POLICY_DROP:
+               pname = "DROP";
+               break;
+       case LIBRPZ_POLICY_TCP_ONLY:
+               pname = "TCP-ONLY";
+               break;
+       case LIBRPZ_POLICY_NXDOMAIN:
+               pname = "NXDOMAIN";
+               break;
+       case LIBRPZ_POLICY_NODATA:
+               pname = "NODATA";
+               break;
+       case LIBRPZ_POLICY_RECORD:
+               pname = "RECORD";
+               break;
+       case LIBRPZ_POLICY_GIVEN:
+               pname = "GIVEN";
+               break;
+       case LIBRPZ_POLICY_DISABLED:
+               pname = "DISABLED";
+               break;
+       case LIBRPZ_POLICY_CNAME:
+               pname = "CNAME";
+               break;
+       default:
+               pname = "UNKNOWN";
+               break;
+       }
+
+       strncpy(buf, pname, buf_size);
+       buf[buf_size - 1] = 0;
+       return (buf);
+}
+
+/*
+ * Get the entire set of zones configured by the config string,
+ * and bind then to the specified RSP state.
+ *
+ * The array of active zone indices is returned by the function on success,
+ * or NULL on failure. The total number of zones is stored in pnzones.
+ */
+static ssize_t *
+get_cstr_zones(const char *cstr, trpz_rsp_t *trsp, size_t *pnzones) {
+       char tmpc[8192] = { 0 };
+       char *tptr = tmpc, *tok = NULL;
+       size_t nzones = 0, cur_idx = 0;
+       ssize_t *result = NULL;
+       unsigned long zflags = 0;
+
+       result = calloc(trsp->num_zones + 1, sizeof(*result));
+       if (result == NULL) {
+               perror("calloc");
+               exit(EXIT_FAILURE);
+       }
+
+       if (cstr == NULL) {
+               result[0] = BASE_ZONE_ANY;
+               *pnzones = 1;
+               return (result);
+       }
+
+       strncpy(tmpc, cstr, sizeof(tmpc) - 1);
+       *pnzones = 0;
+
+       while (tptr != NULL && *tptr != '\0') {
+               tok = strsep(&tptr, ";\n");
+
+               while (isspace(*tok)) {
+                       tok++;
+               }
+
+               if (strncasecmp(tok, "zone ", 5) == 0) {
+                       char zcmd[1024] = { 0 };
+                       char *qend = NULL;
+                       size_t zind = 0, old_zct = trsp->num_zones;
+                       unsigned long zopts = 0;
+
+                       tok += 5;
+
+                       while (isspace(*tok)) {
+                               tok++;
+                       }
+
+                       if (*tok == '"') {
+                               qend = strchr(++tok, '"');
+                               if (qend == NULL) {
+                                       fprintf(stderr, "Error parsing cstr "
+                                                       "contents!\n");
+                                       free(result);
+                                       return (NULL);
+                               }
+
+                               *qend++ = 0;
+
+                               if (tok[strlen(tok) - 1] == '.') {
+                                       tok[strlen(tok) - 1] = 0;
+                               }
+
+                       } else {
+                               qend = tok;
+                       }
+
+                       while (*qend != '\0' && !isspace(*qend)) {
+                               qend++;
+                       }
+
+                       if (*qend != '\0') {
+                               *qend++ = '\0';
+                               zopts = parse_zone_options(qend);
+                       }
+
+                       snprintf(zcmd, sizeof(zcmd) - 1, "zone %s 1", tok);
+
+                       if (apply_update(zcmd, &(trsp->all_nodes),
+                                        &(trsp->num_nodes), &(trsp->all_zones),
+                                        &(trsp->num_zones), 0, zopts,
+                                        NULL) < 0)
+                       {
+                               fprintf(stderr, "Internal error {%s}!\n", zcmd);
+                               free(result);
+                               return (NULL);
+                       }
+
+                       if (trsp->num_zones > old_zct) {
+                               result = realloc(result,
+                                                ((trsp->num_zones + 1) *
+                                                 sizeof(*result)));
+                               if (result == NULL) {
+                                       perror("realloc");
+                                       exit(EXIT_FAILURE);
+                               }
+                       }
+
+                       for (zind = 0; zind < trsp->num_zones; zind++) {
+                               if (!strcmp(trsp->all_zones[zind].name, tok)) {
+                                       break;
+                               }
+                       }
+
+                       if (zind == trsp->num_zones) {
+                               free(result);
+                               return (NULL);
+                       }
+
+                       result[cur_idx++] = zind;
+                       *pnzones = cur_idx;
+                       nzones++;
+               } else {
+                       unsigned long flags;
+
+                       flags = parse_zone_options(tok);
+                       zflags |= (flags & (ZOPT_QNAME_AS_NS | ZOPT_IP_AS_NS |
+                                           ZOPT_RECURSIVE_ONLY |
+                                           ZOPT_NOT_RECURSIVE_ONLY |
+                                           ZOPT_NO_QNAME_WAIT_RECURSE |
+                                           ZOPT_NO_NSIP_WAIT_RECURSE));
+               }
+
+               tok = NULL;
+       }
+
+       if (nzones == 0) {
+               free(result);
+               return (NULL);
+       }
+
+       if (zflags != 0) {
+               size_t n;
+
+               for (n = 0; n < trsp->num_zones; n++) {
+                       if (zflags & ZOPT_QNAME_AS_NS) {
+                               trsp->all_zones[n].qname_as_ns = true;
+                       }
+
+                       if (zflags & ZOPT_IP_AS_NS) {
+                               trsp->all_zones[n].ip_as_ns = true;
+                       }
+
+                       if (zflags & ZOPT_RECURSIVE_ONLY) {
+                               trsp->all_zones[n].not_recursive_only = false;
+                       } else if (zflags & ZOPT_NOT_RECURSIVE_ONLY) {
+                               trsp->all_zones[n].not_recursive_only = true;
+                       }
+
+                       if (zflags & ZOPT_NO_QNAME_WAIT_RECURSE) {
+                               trsp->all_zones[n].no_qname_wait_recurse = true;
+                       }
+
+                       if (zflags & ZOPT_NO_NSIP_WAIT_RECURSE) {
+                               trsp->all_zones[n].no_nsip_wait_recurse = true;
+                       }
+               }
+       }
+
+       return (result);
+}
+
+librpz_client_t *
+trpz_client_create(librpz_emsg_t *emsg, librpz_clist_t *clist, const char *cstr,
+                  bool use_expired) {
+       trpz_client_t *result = NULL;
+
+       if (clist == NULL) {
+               trpz_pemsg(emsg, "clist was NULL\n");
+               return (NULL);
+       }
+
+       result = calloc(1, sizeof(*result));
+       if (result == NULL) {
+               trpz_pemsg(emsg, "calloc: %s", strerror(errno));
+               return (NULL);
+       }
+
+       result->cstr = strdup(cstr);
+       if (result->cstr == NULL) {
+               trpz_pemsg(emsg, "strdup: %s", strerror(errno));
+               free(result);
+               return (NULL);
+       }
+
+       result->uses_expired = use_expired;
+       result->pclist = (trpz_clist_t *)clist;
+
+       return ((librpz_client_t *)result);
+}
+
+void
+trpz_client_detach(librpz_client_t **clientp) {
+       if (clientp != NULL && *clientp != NULL) {
+               librpz_client_t *client = *clientp;
+               *clientp = NULL;
+               free(client);
+       }
+
+       return;
+}
+
+/*
+ * If the DNSRPS_TEST_UPDATE_FILE env variable is set,
+ * load the current list of test nodes from the specified data file.
+ *
+ * Any existing nodes are first destroyed.
+ */
+static int
+apply_all_updates(trpz_rsp_t *trsp) {
+       char *updfile = NULL, *fname = NULL, *last = NULL;
+
+       updfile = getenv("DNSRPS_TEST_UPDATE_FILE");
+       if (updfile == NULL) {
+               return (0);
+       }
+
+       fname = strtok_r(updfile, ":", &last);
+       while (fname != NULL) {
+               char *errp = NULL;
+               int ret;
+
+               ret = load_all_updates(fname, &trsp->all_nodes,
+                                      &trsp->num_nodes, &trsp->all_zones,
+                                      &trsp->num_zones, &errp);
+
+               if (errp) {
+                       fprintf(stderr, "Error loading updates: %s\n", errp);
+                       free(errp);
+               }
+
+               if (ret < 0) {
+                       return (-1);
+               }
+
+               fname = strtok_r(NULL, ":", &last);
+       }
+
+       return (0);
+}
+
+static void
+clear_all_updates(trpz_rsp_t *trsp) {
+       if (trsp == NULL) {
+               return;
+       }
+
+       if (trsp->all_zones != NULL) {
+               free(trsp->all_zones);
+       }
+
+       trsp->all_zones = NULL;
+       trsp->num_zones = 0;
+
+       if (trsp->all_nodes != NULL) {
+               size_t n;
+
+               for (n = 0; n < trsp->num_nodes; n++) {
+                       if (trsp->all_nodes[n].rrs) {
+                               size_t m;
+
+                               for (m = 0; m < trsp->all_nodes[n].nrrs; m++) {
+                                       if (trsp->all_nodes[n].rrs[m].rdata) {
+                                               free(trsp->all_nodes[n]
+                                                            .rrs[m]
+                                                            .rdata);
+                                       }
+                               }
+
+                               free(trsp->all_nodes[n].rrs);
+                       }
+               }
+
+               free(trsp->all_nodes);
+       }
+
+       trsp->all_nodes = NULL;
+       trsp->num_nodes = 0;
+
+       return;
+}
+
+/*
+ * Start a set of RPZ queries for a single DNS response.
+ */
+bool
+trpz_rsp_create(librpz_emsg_t *emsg, librpz_rsp_t **rspp, int *min_ns_dotsp,
+               librpz_client_t *client, bool have_rd, bool have_do) {
+       trpz_client_t *cli = (trpz_client_t *)client;
+       trpz_rsp_t *result = NULL;
+
+       UNUSED(min_ns_dotsp);
+       UNUSED(have_do);
+
+       if (client == NULL) {
+               trpz_pemsg(emsg, "client was NULL");
+               return (false);
+       } else if (rspp == NULL) {
+               trpz_pemsg(emsg, "rspp was NULL");
+               return (false);
+       } else if (cli->cstr == NULL) {
+               trpz_pemsg(emsg, "no valid policy zone specified");
+               return (false);
+       }
+
+       result = calloc(1, sizeof(*result));
+       if (result == NULL) {
+               trpz_pemsg(emsg, "calloc: %s", strerror(errno));
+               return (false);
+       }
+
+       result->idx = 0;
+       result->client = cli;
+       result->have_rd = have_rd;
+       result->stack_idx = 1;
+       result->last_zone = -1;
+
+       *rspp = (librpz_rsp_t *)result;
+
+       clear_all_updates(result);
+       cli->base_zones = get_cstr_zones(cli->cstr, result,
+                                        &(cli->nbase_zones));
+
+       if (cli->base_zones == NULL) {
+               trpz_pemsg(emsg, "no valid policy zone specified");
+               free(*rspp);
+               *rspp = NULL;
+               return (false);
+       }
+
+       if (apply_all_updates(result) < 0) {
+               trpz_pemsg(emsg, "internal error loading test data 1");
+               return (false);
+       }
+
+       return (true);
+}
+
+bool
+trpz_rsp_push(librpz_emsg_t *emsg, librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+
+       UNUSED(emsg);
+
+       if (trsp->stack_idx == 0) {
+               memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0]) * 2);
+               trsp->stack_idx++;
+               return (true);
+       } else if (trsp->stack_idx >= LIBRPZ_RSP_STACK_DEPTH) {
+               return (false);
+       }
+
+       memmove(&(trsp->rstack[1]), &(trsp->rstack[0]),
+               (trsp->stack_idx * sizeof(trsp->rstack[0])));
+       trsp->stack_idx++;
+
+       return (true);
+}
+
+bool
+trpz_rsp_pop(librpz_emsg_t *emsg, librpz_result_t *result, librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+
+       UNUSED(emsg);
+
+       if (trsp->stack_idx <= 1) {
+               return (false);
+       }
+
+       memmove(&(trsp->rstack[0]), &(trsp->rstack[1]),
+               ((trsp->stack_idx - 1) * sizeof(trsp->rstack[0])));
+       memmove(result, &(trsp->rstack[0].result), sizeof(*result));
+       trsp->stack_idx--;
+
+       return (true);
+}
+
+bool
+trpz_rsp_pop_discard(librpz_emsg_t *emsg, librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+
+       UNUSED(emsg);
+
+       if (trsp->stack_idx == 0) {
+               return (false);
+       } else if (trsp->stack_idx == 1) {
+               return (true);
+       }
+
+       if (trsp->stack_idx > 1) {
+               memmove(&(trsp->rstack[1]), &(trsp->rstack[2]),
+                       ((trsp->stack_idx - 2) * sizeof(trsp->rstack[0])));
+       }
+
+       trsp->stack_idx--;
+
+       return (true);
+}
+
+void
+trpz_rsp_detach(librpz_rsp_t **rspp) {
+       if (rspp && *rspp) {
+               trpz_rsp_t *trsp = (trpz_rsp_t *)*rspp;
+
+               clear_all_updates(trsp);
+
+               free(*rspp);
+               *rspp = NULL;
+       }
+
+       return;
+}
+
+bool
+trpz_rsp_domain(librpz_emsg_t *emsg, librpz_domain_buf_t *owner,
+               librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       const char *tstr = "";
+       char tmpname[256] = { 0 };
+       size_t osz = 0;
+       uint32_t n;
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL");
+               return (false);
+       } else if (trsp->stack_idx == 0) {
+               trpz_pemsg(emsg, "domain not found [1]");
+               return (false);
+       } else if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) {
+               trpz_pemsg(emsg, "domain not found [2]");
+               return (false);
+       }
+
+       if (trsp->all_zones[trsp->rstack[0].result.dznum].forgotten) {
+               trpz_pemsg(emsg, "domain not found [3]");
+               memset(owner, 0, sizeof(*owner));
+               return (true);
+       }
+
+       switch (trsp->rstack[0].result.trig) {
+       case LIBRPZ_TRIG_CLIENT_IP:
+               tstr = "rpz-client-ip.";
+               break;
+       case LIBRPZ_TRIG_IP:
+               tstr = "rpz-ip.";
+               break;
+       case LIBRPZ_TRIG_NSDNAME:
+               tstr = "rpz-nsdname.";
+               break;
+       case LIBRPZ_TRIG_NSIP:
+               tstr = "rpz-nsip.";
+               break;
+       default:
+               break;
+       }
+
+       n = snprintf(tmpname, sizeof(tmpname), "%s.%s%s", trsp->rstack[0].dname,
+                    tstr, trsp->all_zones[trsp->rstack[0].result.dznum].name);
+       if (n > sizeof(tmpname)) {
+               trpz_pemsg(emsg, "%s truncated", tmpname);
+               return (false);
+       }
+
+       if (!domain_pton2(tmpname, owner->d, sizeof(owner->d), &osz, true)) {
+               trpz_pemsg(emsg, "unable to read hostname from rsp!");
+               return (false);
+       }
+
+       owner->size = osz;
+       return (true);
+}
+
+bool
+trpz_rsp_result(librpz_emsg_t *emsg, librpz_result_t *result, bool recursed,
+               const librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+
+       UNUSED(recursed);
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL!");
+               return (false);
+       } else if (result == NULL) {
+               trpz_pemsg(emsg, "result was NULL");
+               return (false);
+       }
+
+       if (trsp->stack_idx == 0) {
+               memset(result, 0, sizeof(*result));
+               result->policy = LIBRPZ_POLICY_UNDEFINED;
+       } else {
+               if (trsp->rstack[0].result.policy && trsp->rstack[0].nrrs &&
+                   (trsp->rstack[0].result.policy != LIBRPZ_POLICY_DISABLED))
+               {
+                       trsp->rstack[0].result.next_rr =
+                               trsp->rstack[0].rrs[0].rrn;
+               }
+
+               trsp->rstack[0].rridx = 0;
+
+               memmove(result, &(trsp->rstack[0].result), sizeof(*result));
+
+               if (result->policy && trsp->rstack[0].poverride) {
+                       result->policy = trsp->rstack[0].poverride;
+               }
+       }
+
+       return (true);
+}
+
+bool
+trpz_rsp_soa(librpz_emsg_t *emsg, uint32_t *ttlp, librpz_rr_t **rrp,
+            librpz_domain_buf_t *origin, librpz_result_t *result,
+            librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       librpz_rr_t *rres = NULL;
+       rpz_soa_t tmpsoa;
+       unsigned char *rdbuf = NULL;
+       char tmp_rname[1024] = { 0 };
+       size_t rdlen = 0;
+
+       UNUSED(ttlp);
+
+       if (result == NULL) {
+               trpz_pemsg(emsg, "result was NULL!");
+               return (false);
+       } else if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL!");
+               return (false);
+       }
+
+       if (trsp->zidx >= trsp->num_zones) {
+               trpz_pemsg(emsg, "bad zone");
+               return (false);
+       }
+
+       rdbuf = calloc(1024, 1);
+       if (rdbuf == NULL) {
+               trpz_pemsg(emsg, "calloc: %s", strerror(errno));
+               return (false);
+       }
+
+       rres = (librpz_rr_t *)rdbuf;
+
+       rres->type = htons(6);
+       rres->class = htons(1);
+       rres->ttl = htonl(60);
+
+       memmove(&tmpsoa, &g_soa_record, sizeof(tmpsoa));
+       tmpsoa.serial = trsp->all_zones[result->dznum].serial;
+       tmpsoa.mname = trsp->all_zones[result->dznum].name;
+
+       snprintf(tmp_rname, sizeof(tmp_rname) - 1, "hostmaster.ns.%s",
+                tmpsoa.mname);
+       tmpsoa.rname = tmp_rname;
+
+       if (tmpsoa.serial == 0) {
+               tmpsoa.serial = time(NULL);
+       }
+
+       if (!pack_soa_record(rres->rdata, 1024 - sizeof(*rres), &rdlen,
+                            &tmpsoa))
+       {
+               trpz_pemsg(emsg, "Error packing SOA reply");
+               free(rdbuf);
+               return (false);
+       }
+
+       rres->rdlength = htons(rdlen);
+
+       if (rrp) {
+               *rrp = rres;
+       }
+
+       if (origin) {
+               uint8_t *buf = NULL;
+               int nbytes;
+
+               if ((nbytes = wdns_str_to_name(tmpsoa.mname, &buf, 1)) < 0) {
+                       trpz_pemsg(emsg, "Error packing domain");
+                       free(rdbuf);
+                       return (false);
+               }
+
+               memset(origin, 0, sizeof(*origin));
+               memmove(origin->d, buf, nbytes);
+               origin->size = nbytes;
+               free(buf);
+       }
+
+       return (true);
+}
+
+/*
+ * Compare a query domain against a record, allowing for the possibility of
+ * a wildcard match.
+ */
+static int
+domain_cmp(const char *query, const char *record, bool *wildp) {
+       const char *end = NULL;
+       size_t cmplen;
+
+       end = record + strlen(record);
+
+       cmplen = end - record;
+
+       *wildp = false;
+
+       if (record != NULL && *record == '*' && record[1] == '.') {
+               const char *rptr = record + 2;
+
+               if ((cmplen - 2) < strlen(query)) {
+                       const char *qptr = NULL;
+
+                       qptr = query + strlen(query) - (cmplen - 2);
+
+                       if (strncmp(qptr, rptr, (cmplen - 2)) == 0) {
+                               *wildp = true;
+                               return (0);
+                       }
+               }
+       }
+
+       if (strlen(query) > cmplen) {
+               return (1);
+       } else if (strlen(query) < cmplen) {
+               return (-1);
+       }
+
+       return ((strncmp(record, query, cmplen)));
+}
+
+/*
+ * Count the number of labels in the given domain name.
+ */
+static size_t
+count_labels(const char *domain) {
+       const char *dptr = NULL;
+       size_t result = 1;
+
+       if (domain == NULL || *domain == '\0') {
+               return (0);
+       }
+
+       dptr = domain + strlen(domain);
+
+       while (1) {
+               while ((dptr >= domain) && (*dptr != '.')) {
+                       dptr--;
+               }
+
+               if (dptr <= domain) {
+                       break;
+               }
+
+               result++;
+               dptr--;
+       }
+
+       return (result);
+}
+
+/*
+ * Does the newly found result supercede the old result in precedence?
+ * This function is used to determine whether a match on the result stack
+ * should be overwritten by another match of higher precedence - or whether
+ * the old match should remain, as-is.
+ *
+ * 1. "CNAME or DNAME Chain Position" Precedence Rule
+ * 2. "RPZ Ordering" Precedence Rule [zone order]
+ * 3. "Domain Name Matching" Precedence Rule [QNAME/NSDNAME - label count]
+ * 4. "Trigger Type" Precedence Rule
+ * 5. "Name Order" Precedence Rule [NSDNAME]
+ * 6. "Prefix Length" Precedence Rule
+ * 7. "IP Address Order" Precedence Rule
+ */
+static bool
+result_supercedes(const trpz_result_t *new, const trpz_result_t *old) {
+       size_t nsz, osz;
+
+       if (old == NULL || old->result.policy == 0 || old->dname == NULL ||
+           old->dname[0] == '\0')
+       {
+               return (true);
+       }
+
+       if (new->result.dznum < old->result.dznum) {
+               return (true);
+       } else if (new->result.dznum > old->result.dznum) {
+               return (false);
+       }
+
+       nsz = count_labels(new->dname);
+       osz = count_labels(old->dname);
+
+       /* More matching labels is better. */
+       if (nsz > osz) {
+               return (true);
+       } else if (nsz < osz) {
+               return (false);
+       }
+
+       if (new->result.trig < old->result.trig) {
+               return (true);
+       } else if (new->result.trig > old->result.trig) {
+               return (false);
+       }
+
+       return (true);
+}
+
+static bool
+result_supercedes_address(const trpz_result_t *new, const trpz_result_t *old) {
+       if (old == NULL || old->result.policy == 0 || old->dname == NULL ||
+           old->dname[0] == '\0')
+       {
+               return (true);
+       }
+
+       if (new->result.dznum < old->result.dznum) {
+               return (true);
+       } else if (new->result.dznum > old->result.dznum) {
+               return (false);
+       }
+
+       if (new->result.trig < old->result.trig) {
+               return (true);
+       } else if (new->result.trig > old->result.trig) {
+               return (false);
+       }
+
+       if ((new->flags &NODE_FLAG_IPV6_ADDRESS) &&
+           !(old->flags & NODE_FLAG_IPV6_ADDRESS))
+       {
+               return (true);
+       }
+
+       /*
+        * XXX: this is broken. Needs proper address comparison. For
+        * example, by most specific prefix match.
+        */
+       if (strcmp(old->dname, new->dname) < 0) {
+               return (false);
+       }
+
+       return (true);
+}
+
+bool
+trpz_ck_domain(librpz_emsg_t *emsg, const uint8_t *domain, size_t domain_size,
+              librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed,
+              librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       char dname[256] = { 0 };
+       librpz_trig_t toverride = LIBRPZ_TRIG_BAD;
+       ssize_t fidx = -1, nfidx = -1;
+       bool wild;
+       size_t n;
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL!");
+               return (false);
+       } else if (domain == NULL || domain_size == 0) {
+               trpz_pemsg(emsg, "domain was empty");
+               return (false);
+       } else if (trig != LIBRPZ_TRIG_QNAME && trig != LIBRPZ_TRIG_NSDNAME) {
+               trpz_pemsg(emsg, "invalid trigger type");
+               return (false);
+       } else if (!domain_ntop(domain, dname, sizeof(dname))) {
+               trpz_pemsg(emsg, "domain was invalid");
+               return (false);
+       }
+
+       if (trsp->stack_idx == 0) {
+               trsp->stack_idx = 1;
+       }
+
+       for (n = 0; n < trsp->num_nodes; n++) {
+               int pos = 0;
+
+               if (!trsp->have_rd &&
+                   !trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                            .not_recursive_only)
+               {
+                       continue;
+               }
+
+               if (pos ||
+                   ((trig == trsp->all_nodes[n].result.trig) &&
+                    (!domain_cmp(dname, trsp->all_nodes[n].dname, &wild))))
+               {
+                       if ((trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .no_qname_wait_recurse ||
+                            trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .not_recursive_only ||
+                            recursed) &&
+                           has_base_zone(trsp->client,
+                                         trsp->all_nodes[n].result.dznum) &&
+                           !trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .forgotten)
+                       {
+                               if (fidx < 0) {
+                                       fidx = n;
+                                       toverride = LIBRPZ_TRIG_BAD;
+                               }
+                       }
+                       if (!wild) {
+                               break;
+                       }
+
+                       /*
+                        * Some inelegant special handling for qname_as_ns
+                        * feature.
+                        */
+               } else if (trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                  .qname_as_ns &&
+                          (((trig == LIBRPZ_TRIG_QNAME) &&
+                            (trsp->all_nodes[n].result.trig ==
+                             LIBRPZ_TRIG_NSDNAME)) ||
+                           ((trig == LIBRPZ_TRIG_NSDNAME) &&
+                            (trsp->all_nodes[n].result.trig ==
+                             LIBRPZ_TRIG_QNAME))))
+               {
+                       if (!domain_cmp(dname, trsp->all_nodes[n].dname, &wild))
+                       {
+                               if (recursed &&
+                                   has_base_zone(
+                                           trsp->client,
+                                           trsp->all_nodes[n].result.dznum) &&
+                                   !trsp->all_zones[trsp->all_nodes[n]
+                                                            .result.dznum]
+                                            .forgotten)
+                               {
+                                       if (fidx < 0) {
+                                               fidx = n;
+                                               toverride = trig;
+                                       }
+                               }
+                               if (!wild) {
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if (!trsp->have_rd && (fidx < 0) && (nfidx < 0)) {
+               goto out;
+       }
+
+       if (recursed && (fidx < 0) && trsp->rstack[0].hidden_policy) {
+               if (trsp->rstack[0].result.trig <= trig) {
+                       trsp->rstack[0].result.policy =
+                               trsp->rstack[0].hidden_policy;
+                       trsp->rstack[0].result.zpolicy =
+                               trsp->rstack[0].hidden_policy;
+                       trsp->rstack[0].hidden_policy = LIBRPZ_POLICY_UNDEFINED;
+                       return (true);
+               }
+       }
+
+       if (fidx >= 0) {
+               bool disabled = false;
+
+               if (!result_supercedes(&(trsp->all_nodes[fidx]),
+                                      &(trsp->rstack[0])))
+               {
+                       return (true);
+               }
+
+               strncpy(trsp->domain, dname, sizeof(trsp->domain));
+               memmove(&(trsp->rstack[0]), &(trsp->all_nodes[fidx]),
+                       sizeof(trsp->rstack[0]));
+               trsp->rstack[0].result.hit_id = hit_id;
+               trsp->last_zone = trsp->rstack[0].result.dznum;
+
+               if (trsp->all_zones[trsp->rstack[0].result.dznum].flags) {
+                       unsigned long flags =
+                               trsp->all_zones[trsp->rstack[0].result.dznum]
+                                       .flags;
+                       librpz_policy_t force_policy = 0;
+
+                       if (flags & ZOPT_POLICY_NODATA) {
+                               force_policy = LIBRPZ_POLICY_NODATA;
+                       } else if (flags & ZOPT_POLICY_PASSTHRU) {
+                               force_policy = LIBRPZ_POLICY_PASSTHRU;
+                       } else if (flags & ZOPT_POLICY_NXDOMAIN) {
+                               force_policy = LIBRPZ_POLICY_NXDOMAIN;
+                       } else if (flags & ZOPT_POLICY_DROP) {
+                               force_policy = LIBRPZ_POLICY_DROP;
+                       } else if (flags & ZOPT_POLICY_TCP_ONLY) {
+                               force_policy = LIBRPZ_POLICY_TCP_ONLY;
+                       } else if (flags & ZOPT_POLICY_DISABLED) {
+                               disabled = true;
+                       }
+
+                       if (force_policy) {
+                               trsp->rstack[0].result.policy = force_policy;
+                               trsp->rstack[0].result.zpolicy = force_policy;
+                               trsp->rstack[0].poverride = force_policy;
+                       }
+               }
+
+               if (toverride) {
+                       trsp->rstack[0].result.trig = toverride;
+               }
+
+               if (disabled) {
+                       trsp->rstack[0].poverride = LIBRPZ_POLICY_DISABLED;
+               } else if (!recursed) {
+                       ssize_t m;
+
+                       /*
+                        * If recursed is not set, then an earlier zone of
+                        * higher precedence may not contain a rule of
+                        * trigger types rpz-ip, rpz-nsip, or rpz-nsdname -
+                        * which are by nature post-recursion rules.
+                        */
+                       for (m = trsp->all_nodes[fidx].result.dznum - 1; m >= 0;
+                            m--)
+                       {
+                               if (trsp->all_zones[m]
+                                           .has_triggers[0][LIBRPZ_TRIG_IP] ||
+                                   trsp->all_zones[m]
+                                           .has_triggers[1][LIBRPZ_TRIG_IP] ||
+                                   trsp->all_zones[m]
+                                           .has_triggers[0][LIBRPZ_TRIG_NSIP] ||
+                                   trsp->all_zones[m]
+                                           .has_triggers[1][LIBRPZ_TRIG_NSIP] ||
+                                   trsp->all_zones[m]
+                                           .has_triggers[0]
+                                                        [LIBRPZ_TRIG_NSDNAME])
+                               {
+                                       trsp->rstack[0].result.policy =
+                                               LIBRPZ_POLICY_UNDEFINED;
+                                       break;
+                               }
+                       }
+               }
+
+               return (true);
+       } else if (nfidx >= 0) {
+               strncpy(trsp->domain, dname, sizeof(trsp->domain));
+               memmove(&(trsp->rstack[0]), &(trsp->all_nodes[nfidx]),
+                       sizeof(trsp->rstack[0]));
+               trsp->last_zone = trsp->all_nodes[nfidx].result.dznum;
+               trsp->rstack[0].hidden_policy = trsp->rstack[0].result.policy;
+               trsp->rstack[0].result.hit_id = hit_id;
+               trsp->rstack[0].result.policy = LIBRPZ_POLICY_UNDEFINED;
+               trsp->rstack[0].result.zpolicy = LIBRPZ_POLICY_UNDEFINED;
+               return (true);
+       }
+
+out:
+       if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) {
+               memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0]));
+       }
+
+       return (true);
+}
+
+static void
+rpzify_ipv6_str(char *buf) {
+       char tmpb[512] = { 0 }, *tptr = NULL;
+
+       strncpy(tmpb, buf, sizeof(tmpb) - 1);
+       memset(buf, 0, strlen(buf));
+       tptr = tmpb + strlen(tmpb);
+
+       strcat(buf, "128.");
+
+       while (tptr > tmpb) {
+               if (*tptr == ':') {
+                       strcat(buf, tptr + 1);
+                       strcat(buf, ".");
+               }
+
+               tptr--;
+
+               if ((*tptr == ':') && (tptr[0] == tptr[1])) {
+                       strcat(buf, "zz");
+               }
+
+               if (tptr[1] == ':') {
+                       tptr[1] = 0;
+               }
+       }
+
+       strcat(buf, tptr);
+
+       return;
+}
+
+static uint32_t
+get_mask(unsigned char prefix) {
+       uint32_t result = 0;
+       unsigned char n;
+
+       if (prefix == 0) {
+               return (0);
+       } else if (prefix >= 32) {
+               return (~(0));
+       }
+
+       for (n = 1; n < prefix; n++) {
+               result |= (1 << n);
+       }
+
+       return (result);
+}
+
+/* XXX: this is broken for handling subnet masks in IPv6. */
+static int
+address_cmp(const char *addrstr, const void *addr, uint family,
+           unsigned int *pmask) {
+       char abuf[256] = { 0 };
+       int ipstr[8] = { 0 };
+       unsigned int nmask = 32;
+
+       if (family == AF_INET6) {
+               if (!inet_ntop(AF_INET6, addr, abuf, sizeof(abuf))) {
+                       return (-1);
+               }
+
+               rpzify_ipv6_str(abuf);
+       } else if (family == AF_INET) {
+               char newstr[32] = { 0 };
+               in_addr_t a1, a2;
+
+               if (sscanf(addrstr, "%d.%d.%d.%d.%d", &ipstr[0], &ipstr[1],
+                          &ipstr[2], &ipstr[3], &ipstr[4]) == 5)
+               {
+                       nmask = ipstr[0];
+                       if (nmask > 32) {
+                               return (-1);
+                       }
+               } else if (sscanf(addrstr, "%d.%d.%d.%d", &ipstr[1], &ipstr[2],
+                                         &ipstr[3], &ipstr[4]) != 4)
+               {
+                       perror("bad address format");
+                       return (-1);
+               }
+
+               if (ipstr[1] > 255 || ipstr[2] > 255 || ipstr[3] > 255 ||
+                   ipstr[4] > 255) {
+                       perror("bad address format");
+                       return (-1);
+               }
+
+               sprintf(newstr, "%u.%u.%u.%u", ipstr[4], ipstr[3], ipstr[2],
+                       ipstr[1]);
+
+               a1 = inet_addr(newstr);
+               if (a1 == INADDR_NONE) {
+                       perror("inet_addr");
+                       return (-1);
+               } else {
+                       uint32_t m;
+
+                       memmove(&a2, addr, sizeof(uint32_t));
+                       m = get_mask(nmask);
+
+                       if (pmask) {
+                               *pmask = nmask;
+                       }
+
+                       return (((a1 & m) == (a2 & m)) ? 0 : 1);
+               }
+
+       } else {
+               return (-1);
+       }
+
+       if (strcmp(addrstr, abuf) == 0) {
+               if (pmask) {
+                       *pmask = nmask;
+               }
+
+               return (0);
+       }
+
+       return (1);
+}
+
+bool
+trpz_ck_ip(librpz_emsg_t *emsg, const void *addr, uint family,
+          librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed,
+          librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       size_t n, last_mask = 0;
+       ssize_t fidx = -1, nfidx = -1;
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL!");
+               return (false);
+       } else if (addr == NULL) {
+               trpz_pemsg(emsg, "addr was empty");
+               return (false);
+       } else if (trig != LIBRPZ_TRIG_IP && trig != LIBRPZ_TRIG_CLIENT_IP &&
+                  trig != LIBRPZ_TRIG_NSIP)
+       {
+               trpz_pemsg(emsg, "trigger type not supported for IP");
+               return (false);
+       }
+
+       if (trsp->stack_idx == 0) {
+               trsp->stack_idx = 1;
+       }
+
+       /* The final match is the most specifically-matching netmask. */
+       for (n = 0; n < trsp->num_nodes; n++) {
+               unsigned int mask = 0;
+               bool amatch = false;
+
+               if (!trsp->have_rd &&
+                   !trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                            .not_recursive_only)
+               {
+                       continue;
+               }
+
+               if (trsp->all_zones[trsp->all_nodes[n].result.dznum].ip_as_ns &&
+                   (((trig == LIBRPZ_TRIG_IP) &&
+                     (trsp->all_nodes[n].result.trig == LIBRPZ_TRIG_NSIP)) ||
+                    ((trig == LIBRPZ_TRIG_NSIP) &&
+                     (trsp->all_nodes[n].result.trig == LIBRPZ_TRIG_IP))) &&
+                   !address_cmp(trsp->all_nodes[n].dname, addr, family, &mask))
+               {
+                       amatch = true;
+               } else if (trsp->all_nodes[n].match_trig != trig) {
+                       continue;
+               }
+
+               if (amatch ||
+                   !address_cmp(trsp->all_nodes[n].dname, addr, family, &mask))
+               {
+                       if ((trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .no_qname_wait_recurse ||
+                            trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .not_recursive_only ||
+                            recursed) &&
+                           has_base_zone(trsp->client,
+                                         trsp->all_nodes[n].result.dznum) &&
+                           !trsp->all_zones[trsp->all_nodes[n].result.dznum]
+                                    .forgotten)
+                       {
+                               if (mask > last_mask) {
+                                       last_mask = mask;
+                                       fidx = n;
+                               }
+
+                       } else if ((nfidx < 0) && !recursed &&
+                                  has_base_zone(
+                                          trsp->client,
+                                          trsp->all_nodes[n].result.dznum) &&
+                                  !trsp->all_zones[trsp->all_nodes[n]
+                                                           .result.dznum]
+                                           .forgotten)
+                       {
+                               nfidx = n;
+                       }
+               }
+       }
+
+       if (!trsp->have_rd && (fidx < 0) && (nfidx < 0)) {
+               goto out;
+       }
+
+       if (recursed && (fidx < 0) && trsp->rstack[0].hidden_policy) {
+               if (trsp->rstack[0].result.trig <= trig) {
+                       trsp->rstack[0].result.policy =
+                               trsp->rstack[0].hidden_policy;
+                       trsp->rstack[0].result.zpolicy =
+                               trsp->rstack[0].hidden_policy;
+                       trsp->rstack[0].hidden_policy = LIBRPZ_POLICY_UNDEFINED;
+                       return (true);
+               }
+       }
+
+       if (fidx >= 0) {
+               bool disabled = false;
+
+               if (!result_supercedes_address(&(trsp->all_nodes[fidx]),
+                                              &(trsp->rstack[0])))
+               {
+                       return (true);
+               }
+
+               memmove(&(trsp->rstack[0]), &(trsp->all_nodes[fidx]),
+                       sizeof(trsp->rstack[0]));
+               trsp->rstack[0].result.hit_id = hit_id;
+               trsp->last_zone = trsp->rstack[0].result.dznum;
+
+               if (trsp->all_zones[trsp->rstack[0].result.dznum].flags) {
+                       unsigned long flags =
+                               trsp->all_zones[trsp->rstack[0].result.dznum]
+                                       .flags;
+                       librpz_policy_t force_policy = 0;
+
+                       if (flags & ZOPT_POLICY_NODATA) {
+                               force_policy = LIBRPZ_POLICY_NODATA;
+                       } else if (flags & ZOPT_POLICY_PASSTHRU) {
+                               force_policy = LIBRPZ_POLICY_PASSTHRU;
+                       } else if (flags & ZOPT_POLICY_NXDOMAIN) {
+                               force_policy = LIBRPZ_POLICY_NXDOMAIN;
+                       } else if (flags & ZOPT_POLICY_DROP) {
+                               force_policy = LIBRPZ_POLICY_DROP;
+                       } else if (flags & ZOPT_POLICY_TCP_ONLY) {
+                               force_policy = LIBRPZ_POLICY_TCP_ONLY;
+                       } else if (flags & ZOPT_POLICY_DISABLED) {
+                               disabled = true;
+                       }
+
+                       if (force_policy) {
+                               trsp->rstack[0].result.policy = force_policy;
+                               trsp->rstack[0].result.zpolicy = force_policy;
+                               trsp->rstack[0].poverride = force_policy;
+                       }
+               }
+
+               if (disabled) {
+                       trsp->rstack[0].poverride = LIBRPZ_POLICY_DISABLED;
+               }
+
+               return (true);
+       } else if (nfidx >= 0) {
+               memmove(&(trsp->rstack[0]), &(trsp->all_nodes[nfidx]),
+                       sizeof(trsp->rstack[0]));
+               trsp->last_zone = trsp->rstack[0].result.dznum;
+               trsp->rstack[0].result.hit_id = hit_id;
+               trsp->rstack[0].hidden_policy = trsp->rstack[0].result.policy;
+               trsp->rstack[0].result.policy = LIBRPZ_POLICY_UNDEFINED;
+               trsp->rstack[0].result.zpolicy = LIBRPZ_POLICY_UNDEFINED;
+               return (true);
+       }
+
+out:
+       if (trig == LIBRPZ_TRIG_NSIP) {
+               bool needs_wait = false;
+
+               for (n = 0; n < trsp->num_zones; n++) {
+                       if (trsp->all_zones[n].no_nsip_wait_recurse) {
+                               needs_wait = true;
+                               break;
+                       }
+               }
+
+               if (!needs_wait) {
+                       usleep(100);
+               }
+       }
+
+       if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) {
+               memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0]));
+               trsp->rstack[0].result.trig = trig;
+       }
+
+       return (true);
+}
+
+bool
+trpz_soa_serial(librpz_emsg_t *emsg, uint32_t *serialp, const char *domain_nm,
+               librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       size_t n, dlen;
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL");
+               return (false);
+       } else if (domain_nm == NULL) {
+               trpz_pemsg(emsg, "domain_nm was NULL");
+               return (false);
+       } else if (serialp == NULL) {
+               trpz_pemsg(emsg, "serialp was NULL");
+               return (false);
+       }
+
+       dlen = strlen(domain_nm);
+
+       if (dlen > 0 && domain_nm[dlen - 1] == '.') {
+               dlen--;
+       }
+
+       for (n = 0; n < trsp->num_zones; n++) {
+               if (dlen != strlen(trsp->all_zones[n].name)) {
+                       continue;
+               } else if (!has_base_zone(trsp->client, n)) {
+                       continue;
+               }
+
+               if ((strlen(trsp->all_zones[n].name) == dlen) &&
+                   (!strncmp(trsp->all_zones[n].name, domain_nm, dlen)))
+               {
+                       if (!trsp->all_zones[n].serial) {
+                               trsp->all_zones[n].serial = time(NULL);
+                       }
+
+                       *serialp = trsp->all_zones[n].serial;
+                       return (true);
+               }
+       }
+
+       trpz_pemsg(emsg, "zone not found");
+       return (false);
+}
+
+static int
+domain_ntop(const u_char *src, char *dst, size_t dstsiz) {
+       const unsigned char *sptr = src;
+       char *dptr = dst, *dend = dst + dstsiz;
+
+       if (dst == NULL || dstsiz == 0) {
+               return (0);
+       }
+
+       memset(dst, 0, dstsiz);
+
+       while (*sptr) {
+               if (((dptr + *sptr) > dend)) {
+                       return (0);
+               }
+
+               if (sptr != src) {
+                       *dptr++ = '.';
+               }
+
+               memmove(dptr, sptr + 1, *sptr);
+               dptr += *sptr;
+               sptr += *sptr;
+               sptr++;
+       }
+
+       return (1);
+}
+
+static int
+domain_pton2(const char *src, u_char *dst, size_t dstsiz, size_t *dstlen,
+            bool lower) {
+       unsigned char *dptr = dst;
+       const unsigned char *dend = dst + dstsiz;
+       char *tmps = NULL, *tok = NULL, *tptr = NULL;
+
+       UNUSED(lower);
+
+       if (src == NULL || dst == NULL || dstsiz == 0) {
+               return (false);
+       }
+
+       memset(dst, 0, dstsiz);
+
+       tmps = strdup(src);
+       if (tmps == NULL) {
+               perror("strdup");
+               return (0);
+       }
+
+       tptr = tmps;
+
+       if (dstlen) {
+               *dstlen = 0;
+       }
+
+       while (tptr && *tptr) {
+               tok = strsep(&tptr, ".");
+
+               if (((dptr + strlen(tok) + 1) > dend)) {
+                       return (0);
+               }
+
+               *dptr++ = strlen(tok);
+               memmove(dptr, tok, strlen(tok));
+               dptr += strlen(tok);
+
+               if (dstlen) {
+                       (*dstlen) += (1 + strlen(tok));
+               }
+       }
+
+       if (dptr >= dend) {
+               return (0);
+       }
+
+       *dptr = 0;
+
+       if (dstlen) {
+               (*dstlen)++;
+       }
+
+       free(tmps);
+
+       return (1);
+}
+
+/* XXX: needs IPv6 support. */
+bool
+trpz_rsp_clientip_prefix(librpz_emsg_t *emsg, librpz_prefix_t *prefix,
+                        librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       unsigned int cbytes[5] = { 0 };
+       uint8_t *aptr = NULL;
+
+       if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL");
+               return (false);
+       } else if (prefix == NULL) {
+               trpz_pemsg(emsg, "prefix was NULL");
+               return (false);
+       }
+
+       memset(prefix, 0, sizeof(*prefix));
+
+       if (trsp->rstack[0].result.trig != LIBRPZ_TRIG_CLIENT_IP) {
+               return (true);
+       }
+
+       if (sscanf(trsp->rstack[0].dname, "%u.%u.%u.%u.%u", &cbytes[0],
+                  &cbytes[1], &cbytes[2], &cbytes[3], &cbytes[4]) != 5)
+       {
+               char abuf[64] = { 0 };
+               int family = 0;
+
+               if (sscanf(trsp->rstack[0].dname, "%u.", &cbytes[0]) != 1) {
+                       return (true);
+               }
+
+               if (get_address_info(trsp->rstack[0].dname, &family, abuf, NULL,
+                                    NULL) < 0)
+               {
+                       return (true);
+               } else if (family != AF_INET6) {
+                       return (true);
+               }
+
+               aptr = (uint8_t *)&(prefix->addr.in6);
+               memset(aptr, 0, sizeof(prefix->addr.in6));
+
+               if (inet_pton(AF_INET6, abuf, aptr) != 1) {
+                       return (true);
+               }
+
+               prefix->family = AF_INET6;
+               prefix->len = cbytes[0];
+
+               return (true);
+       }
+
+       prefix->family = AF_INET;
+       prefix->len = cbytes[0];
+
+       if (prefix->len <= 24) {
+               cbytes[1] = 0;
+       }
+
+       if (prefix->len <= 16) {
+               cbytes[2] = 0;
+       }
+
+       if (prefix->len == 86) {
+               cbytes[3] = 0;
+       }
+
+       aptr = (uint8_t *)&(prefix->addr.in);
+       *aptr++ = cbytes[4];
+       *aptr++ = cbytes[3];
+       *aptr++ = cbytes[2];
+       *aptr++ = cbytes[1];
+
+       return (true);
+}
+
+bool
+trpz_have_trig(librpz_trig_t trig, bool ipv6, const librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       size_t ind = ipv6 ? 1 : 0;
+
+       if (rsp == NULL) {
+               return (false);
+       }
+
+       /* No hit, so look in all zones for trigger. */
+       if (trsp->stack_idx == 0 || trsp->rstack[0].result.policy == 0) {
+               ssize_t max_z = (trsp->last_zone >= 0)
+                                       ? trsp->last_zone
+                                       : (ssize_t)trsp->num_zones - 0;
+
+               for (ssize_t n = 0; n < max_z; n++) {
+                       if (!trsp->have_rd &&
+                           !trsp->all_zones[n].not_recursive_only)
+                       {
+                               continue;
+                       } else if (trsp->all_zones[n].has_triggers[ind][trig]) {
+                               return (true);
+                       } else if (trsp->all_zones[n].ip_as_ns &&
+                                  (((trig == LIBRPZ_TRIG_IP) &&
+                                    trsp->all_zones[n]
+                                            .has_triggers[ind]
+                                                         [LIBRPZ_TRIG_NSIP]) ||
+                                   ((trig == LIBRPZ_TRIG_NSIP) &&
+                                    trsp->all_zones[n]
+                                            .has_triggers[ind]
+                                                         [LIBRPZ_TRIG_IP])))
+                       {
+                               return (true);
+                       } else if (trsp->all_zones[n].qname_as_ns &&
+                                  (((trig == LIBRPZ_TRIG_QNAME) &&
+                                    trsp->all_zones[n].has_triggers
+                                            [ind][LIBRPZ_TRIG_NSDNAME]) ||
+                                   ((trig == LIBRPZ_TRIG_NSDNAME) &&
+                                    trsp->all_zones[n]
+                                            .has_triggers[ind]
+                                                         [LIBRPZ_TRIG_QNAME])))
+                       {
+                               return (true);
+                       }
+               }
+
+               return (false);
+       }
+
+       /* Special case of first base zone. */
+       if (trsp->rstack[0].result.dznum == 0 &&
+           (trig > trsp->rstack[0].result.trig))
+       {
+               return (false);
+       }
+
+       /* Otherwise check lower zones (of higher precedence). */
+       for (size_t n = 0; n <= (trsp->rstack[0].result.dznum); n++) {
+               if (!trsp->have_rd && !trsp->all_zones[n].not_recursive_only) {
+                       continue;
+               } else if (trsp->all_zones[n].has_triggers[ind][trig]) {
+                       return (true);
+               } else if (trsp->all_zones[n].ip_as_ns &&
+                          (((trig == LIBRPZ_TRIG_IP) &&
+                            trsp->all_zones[n]
+                                    .has_triggers[ind][LIBRPZ_TRIG_NSIP]) ||
+                           ((trig == LIBRPZ_TRIG_NSIP) &&
+                            trsp->all_zones[n]
+                                    .has_triggers[ind][LIBRPZ_TRIG_IP])))
+               {
+                       return (true);
+               } else if (trsp->all_zones[n].qname_as_ns &&
+                          (((trig == LIBRPZ_TRIG_QNAME) &&
+                            trsp->all_zones[n]
+                                    .has_triggers[ind][LIBRPZ_TRIG_NSDNAME]) ||
+                           ((trig == LIBRPZ_TRIG_NSDNAME) &&
+                            trsp->all_zones[n]
+                                    .has_triggers[ind][LIBRPZ_TRIG_QNAME])))
+               {
+                       return (true);
+               }
+       }
+
+       return (false);
+}
+
+bool
+trpz_rsp_rr(librpz_emsg_t *emsg, uint16_t *typep, uint16_t *classp,
+           uint32_t *ttlp, librpz_rr_t **rrp, librpz_result_t *result,
+           const uint8_t *qname, size_t qname_size, librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+       trpz_result_t *last_result = NULL;
+
+       if (result == NULL) {
+               trpz_pemsg(emsg, "result was NULL");
+               return (false);
+       } else if (rsp == NULL) {
+               trpz_pemsg(emsg, "rsp was NULL");
+               return (false);
+       }
+
+       last_result = &(trsp->rstack[0]);
+
+       if (last_result->rridx < last_result->nrrs) {
+               trpz_rr_t *this_rr = &(last_result->rrs[last_result->rridx]);
+
+               if (classp != NULL) {
+                       *classp = this_rr->class;
+               }
+
+               if (ttlp != NULL) {
+                       *ttlp = 3600;
+               }
+
+               if (typep != NULL) {
+                       *typep = this_rr->type;
+               }
+
+               if (rrp != NULL) {
+                       uint8_t *copy_src = NULL, *nrdata = NULL;
+                       size_t to_copy, needed;
+
+                       copy_src = this_rr->rdata;
+                       to_copy = this_rr->rdlength;
+
+                       /* If there's CNAME wild card expansion */
+                       if (qname != NULL && qname_size != 0 &&
+                           (this_rr->type == ns_t_cname) &&
+                           (this_rr->rdlength > 2))
+                       {
+                               if (this_rr->rdata[0] == 1 &&
+                                   this_rr->rdata[1] == '*')
+                               {
+                                       char tmpexp[256] = { 0 },
+                                            tmpexp2[256] = { 0 }, tmpexp3[256];
+
+                                       wdns_domain_to_str(this_rr->rdata,
+                                                          this_rr->rdlength,
+                                                          tmpexp);
+                                       wdns_domain_to_str(qname, qname_size,
+                                                          tmpexp2);
+
+                                       if (tmpexp2[strlen(tmpexp2) - 1] == '.')
+                                       {
+                                               tmpexp2[strlen(tmpexp2) - 1] =
+                                                       0;
+                                       }
+
+                                       if (strncmp(tmpexp, "*.", 2) == 0) {
+                                               size_t nrd;
+                                               uint32_t n = snprintf(
+                                                       tmpexp3,
+                                                       sizeof(tmpexp3),
+                                                       "%s.%s", tmpexp2,
+                                                       &tmpexp[2]);
+                                               if (n > sizeof(tmpexp3)) {
+                                                       trpz_pemsg(
+                                                               emsg,
+                                                               "%s truncated",
+                                                               tmpexp3);
+                                                       return (false);
+                                               }
+                                               nrd = wdns_str_to_name(
+                                                       tmpexp3, &nrdata, 1);
+                                               to_copy = nrd;
+                                               copy_src = nrdata;
+                                       }
+                               }
+                       }
+
+                       needed = sizeof(**rrp) + to_copy;
+
+                       *rrp = calloc(1, needed);
+                       if (*rrp == NULL) {
+                               trpz_pemsg(emsg, "calloc: %s", strerror(errno));
+                               return (false);
+                       }
+
+                       (*rrp)->type = htons(this_rr->type);
+                       (*rrp)->class = htons(this_rr->class);
+                       (*rrp)->ttl = htonl(this_rr->ttl);
+                       (*rrp)->rdlength = htons(to_copy);
+                       memmove((*rrp)->rdata, copy_src, to_copy);
+               }
+
+               result->next_rr = this_rr->rrn;
+               trsp->rstack[0].result.next_rr = this_rr->rrn;
+               last_result->rridx++;
+       } else {
+               if (typep != NULL) {
+                       *typep = ns_t_invalid;
+               }
+
+               if (rrp != NULL) {
+                       *rrp = NULL;
+               }
+
+               result->next_rr = 0;
+               trsp->rstack[0].result.next_rr = 0;
+       }
+
+       return (true);
+}
+
+bool
+trpz_rsp_forget_zone(librpz_emsg_t *emsg, librpz_cznum_t znum,
+                    librpz_rsp_t *rsp) {
+       trpz_rsp_t *trsp = (trpz_rsp_t *)rsp;
+
+       if (znum >= trsp->num_zones) {
+               trpz_pemsg(emsg, "invalid zone number");
+               return (false);
+       } else if (trsp->all_zones[znum].forgotten) {
+               trpz_pemsg(emsg, "zone already forgotten");
+               return (false);
+       }
+
+       trsp->all_zones[znum].forgotten = true;
+
+       return (true);
+}
diff --git a/bin/tests/system/rpz/testlib/test-data.c b/bin/tests/system/rpz/testlib/test-data.c
new file mode 100644 (file)
index 0000000..5461d3e
--- /dev/null
@@ -0,0 +1,1484 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Limited implementation of the DNSRPS API for testing purposes.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE 1
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/util.h>
+
+#include "test-data.h"
+
+const rpz_soa_t g_soa_record = { "a.rpz-ns.dns-nod.net",
+                                "nod-admin.fsi.io",
+                                12345,
+                                3600,
+                                1200,
+                                604800,
+                                60 };
+
+int
+wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase);
+
+static char *
+str_printf(const char *fmt, ...) {
+       va_list ap;
+       char tbuf[8192], *result = NULL;
+
+       va_start(ap, fmt);
+       vsnprintf(tbuf, sizeof(tbuf) - 1, fmt, ap);
+       tbuf[sizeof(tbuf) - 1] = 0;
+       va_end(ap);
+
+       result = strdup(tbuf);
+       if (result == NULL) {
+               perror("strdup");
+       }
+
+       return (result);
+}
+
+/*
+ * Given a config-ready RPZ IP address, determine its family and normal
+ * canonical representation.
+ */
+int
+get_address_info(const char *astr, int *pfamily, char *pbuf,
+                const char *optname, char **errp) {
+       char tmpc[512] = { 0 };
+       char *tok = NULL, *tptr = tmpc, *last_tok = NULL;
+       size_t lcount = 0, bcount = 0;
+       unsigned int prefix = 0, values[16] = { 0 }, hex_values[16] = { 0 };
+       bool is_ipv6 = false;
+
+       if (!astr || !pfamily || !pbuf) {
+               return (-1);
+       }
+
+       strncpy(tmpc, astr, sizeof(tmpc) - 1);
+
+       while ((tok = strsep(&tptr, "."))) {
+               char *eptr = NULL;
+               unsigned long val;
+
+               lcount++;
+               last_tok = tok;
+
+               errno = 0;
+               val = strtoul(tok, &eptr, 10);
+
+               if (errno != 0 || *eptr != '\0') {
+                       bool bad = false;
+
+                       bcount++;
+                       errno = 0;
+                       eptr = NULL;
+                       val = strtoul(tok, &eptr, 16);
+
+                       if (errno || *eptr != '\0') {
+                               if (strcmp(tok, "zz") == 0) {
+                                       val = ~0;
+                                       is_ipv6 = true;
+                               } else {
+                                       bad = true;
+                               }
+                       }
+
+                       if (!bad && (lcount > 1)) {
+                               hex_values[lcount - 2] = val;
+                       }
+               } else {
+                       if (val > 255) {
+                               bcount++;
+                       }
+
+                       if (lcount == 1) {
+                               prefix = val;
+                       } else if (lcount > 1) {
+                               unsigned int hexval;
+                               values[lcount - 2] = val;
+
+                               /*
+                                * All integer strings are valid hex
+                                * strings, but decimal values are longer,
+                                * so we have to check for overflow when
+                                * reading as hex.
+                                */
+                               errno = 0;
+                               hexval = strtoul(tok, &eptr, 16);
+                               if (errno != 0) {
+                                       return (-1);
+                               }
+                               hex_values[lcount - 2] = hexval;
+                       }
+               }
+       }
+
+       if (last_tok && (strncmp(last_tok, "rpz-", 4) == 0)) {
+               lcount--;
+               bcount--;
+       }
+
+       /* Not acceptable for either address family. */
+       if (lcount > 9) {
+               return (-1);
+       }
+
+       *pfamily = (!is_ipv6 && (lcount == 5)) ? AF_INET : AF_INET6;
+
+       /*
+        * For AF_INET we expect exactly 4 "good" (0<->255) octets and the
+        * subnet mask.
+        */
+       if (*pfamily == AF_INET) {
+               if (prefix > 32) {
+                       if (errp) {
+                               *errp = str_printf(
+                                       "invalid rpz IP address \"%s\"; "
+                                       "invalid prefix length of %u",
+                                       (optname ? optname : astr), prefix);
+                       }
+
+                       return (-1);
+               } else if (bcount > 0) {
+                       return (-1);
+               }
+
+               sprintf(pbuf, "%u.%u.%u.%u", values[3], values[2], values[1],
+                       values[0]);
+       } else {
+               size_t n;
+
+               if (prefix > 128) {
+                       if (errp) {
+                               *errp = str_printf(
+                                       "invalid rpz IP address \"%s\"; "
+                                       "invalid prefix length of %u",
+                                       (optname ? optname : astr), prefix);
+                       }
+
+                       return (-1);
+               }
+
+               *pbuf = 0;
+
+               /*
+                * Walk the values backward. Account for :: and discard
+                * chunks > 2 octets.
+                */
+               for (n = lcount - 1; n > 0; n--) {
+                       if (hex_values[n - 1] == ~0U) {
+                               strcat(pbuf, ":");
+                       } else {
+                               if (hex_values[n - 1] > 0xffff) {
+                                       return (-1);
+                               } else if (n > 1) {
+                                       sprintf(&pbuf[strlen(pbuf)],
+                                               "%x:", hex_values[n - 1]);
+                               } else {
+                                       sprintf(&pbuf[strlen(pbuf)], "%x",
+                                               hex_values[n - 1]);
+                               }
+                       }
+               }
+       }
+
+       return (0);
+}
+
+rpz_soa_t *
+parse_serial(unsigned char *rdata, size_t rdlen) {
+       rpz_soa_t *result = NULL;
+       char dname[WDNS_PRESLEN_NAME];
+       size_t mlen, rlen;
+       uint32_t *uptr = NULL;
+
+       result = calloc(1, sizeof(*result));
+       if (result == NULL) {
+               perror("calloc");
+               return (NULL);
+       }
+
+       mlen = wdns_domain_to_str(rdata, rdlen, dname);
+       result->mname = strdup(dname);
+       rlen = wdns_domain_to_str(rdata + mlen, rdlen - mlen, dname);
+       result->rname = strdup(dname);
+       uptr = (uint32_t *)(rdata + mlen + rlen);
+       result->serial = ntohl(*uptr);
+       uptr++;
+       result->refresh = ntohl(*uptr);
+       uptr++;
+       result->retry = ntohl(*uptr);
+       uptr++;
+       result->expire = ntohl(*uptr);
+       uptr++;
+       result->minimum = ntohl(*uptr);
+
+       return (result);
+}
+
+size_t
+wdns_domain_to_str(const uint8_t *src, size_t src_len, char *dst) {
+       size_t bytes_read = 0;
+       size_t bytes_remaining = src_len;
+       uint8_t oclen;
+
+       if (!src) {
+               return (0);
+       }
+
+       oclen = *src;
+       while (bytes_remaining > 0 && oclen != 0) {
+               src++;
+               bytes_remaining--;
+
+               bytes_read += oclen + 1 /* length octet */;
+
+               while (oclen-- && bytes_remaining > 0) {
+                       uint8_t c = *src++;
+                       bytes_remaining--;
+
+                       if (c == '.' || c == '\\') {
+                               *dst++ = '\\';
+                               *dst++ = c;
+                       } else if (c >= '!' && c <= '~') {
+                               *dst++ = c;
+                       } else {
+                               snprintf(dst, 5, "\\%.3d", c);
+                               dst += 4;
+                       }
+               }
+               *dst++ = '.';
+               oclen = *src;
+       }
+       if (bytes_read == 0) {
+               *dst++ = '.';
+       }
+       bytes_read++;
+
+       *dst = '\0';
+       return ((bytes_read));
+}
+
+/* Add parsed update specification to maintained list of nodes. */
+static trpz_result_t *
+apply_update_to_set(trpz_result_t **results, size_t *pnresults,
+                   trpz_zone_t **pzones, const char *node, size_t zidx,
+                   uint32_t ttl, librpz_trig_t trigger, librpz_policy_t policy,
+                   int *modified, unsigned long flags, char **errp) {
+       size_t n;
+       int family = 0;
+
+       UNUSED(flags);
+
+       *modified = 0;
+
+       switch (trigger) {
+       case LIBRPZ_TRIG_QNAME:
+       case LIBRPZ_TRIG_NSDNAME:
+               (*pzones)[zidx].has_triggers[0][trigger] = 1;
+               break;
+       case LIBRPZ_TRIG_CLIENT_IP:
+       case LIBRPZ_TRIG_IP:
+       case LIBRPZ_TRIG_NSIP: {
+               char abuf[128];
+
+               if (get_address_info(node, &family, abuf, NULL, errp) < 0) {
+                       fprintf(stderr,
+                               "Error in determining IP address type: %s\n",
+                               node);
+                       return (NULL);
+               } else if (family == AF_INET) {
+                       (*pzones)[zidx].has_triggers[0][trigger] = 1;
+               } else {
+                       (*pzones)[zidx].has_triggers[1][trigger] = 1;
+               }
+
+       } break;
+       default:
+               break;
+       }
+
+       for (n = 0; n < *pnresults; n++) {
+               trpz_result_t *rptr = &((*results)[n]);
+
+               if (rptr->result.cznum != zidx) {
+                       continue;
+               }
+
+               if (!strcmp(rptr->dname, node)) {
+                       if (rptr->result.trig == trigger &&
+                           rptr->result.policy == policy && rptr->ttl == ttl)
+                       {
+                               return (rptr);
+                       }
+
+                       rptr->result.trig = trigger;
+                       rptr->result.policy = policy;
+                       rptr->result.zpolicy = policy;
+                       rptr->ttl = ttl;
+                       *modified = 1;
+                       return (rptr);
+               }
+       }
+
+       /* No match. Instead, append. */
+       (*pnresults)++;
+
+       *results = realloc(*results, (*pnresults * sizeof(**results)));
+       if (*results == NULL) {
+               perror("realloc");
+               return (NULL);
+       }
+
+       memset(&((*results)[*pnresults - 1]), 0, sizeof(**results));
+       (*results)[*pnresults - 1].dname = strdup(node);
+       (*results)[*pnresults - 1].ttl = ttl;
+       (*results)[*pnresults - 1].result.trig = trigger;
+       (*results)[*pnresults - 1].result.policy = policy;
+       (*results)[*pnresults - 1].result.zpolicy = policy;
+       (*results)[*pnresults - 1].result.cznum = zidx;
+       (*results)[*pnresults - 1].result.dznum = zidx;
+       (*results)[*pnresults - 1].result.log = 1;
+       (*results)[*pnresults - 1].poverride = policy;
+       (*results)[*pnresults - 1].match_trig = trigger;
+
+       if (family == AF_INET6) {
+               (*results)[*pnresults - 1].flags |= NODE_FLAG_IPV6_ADDRESS;
+       }
+
+       *modified = 1;
+       return ((&((*results)[*pnresults - 1])));
+}
+
+/*
+ * Add a parsed RR value that is maintained in conjunction with record policy
+ * items.
+ */
+static int
+add_other_rr(trpz_result_t *node, const char *rrtype, const char *val,
+            uint32_t ttl, int *modified) {
+       trpz_rr_t nrec = { 0 };
+       size_t n;
+       static unsigned int rrn = 1;
+
+       *modified = 0;
+
+       nrec.class = ns_c_in;
+       nrec.ttl = ttl;
+       nrec.rrn = rrn++;
+
+       if (!strcasecmp(rrtype, "A")) {
+               uint32_t addr;
+
+               if (inet_pton(AF_INET, val, &addr) != 1) {
+                       fprintf(stderr,
+                               "Error determining policy record IPv4 address: "
+                               "%s\n",
+                               val);
+                       return (-1);
+               }
+
+               nrec.type = ns_t_a;
+               nrec.rdlength = sizeof(uint32_t);
+
+               nrec.rdata = malloc(nrec.rdlength);
+               if (nrec.rdata == NULL) {
+                       perror("malloc");
+                       exit(EXIT_FAILURE);
+               }
+
+               memmove(nrec.rdata, &addr, nrec.rdlength);
+       } else if (!strcasecmp(rrtype, "AAAA")) {
+               char addr[16] = { 0 };
+
+               if (inet_pton(AF_INET6, val, addr) != 1) {
+                       fprintf(stderr,
+                               "Error determining policy record IPv6 address: "
+                               "%s\n",
+                               val);
+                       return (-1);
+               }
+
+               nrec.type = ns_t_aaaa;
+               nrec.rdlength = sizeof(addr);
+
+               nrec.rdata = malloc(nrec.rdlength);
+               if (nrec.rdata == NULL) {
+                       perror("malloc");
+                       exit(EXIT_FAILURE);
+               }
+
+               memmove(nrec.rdata, addr, nrec.rdlength);
+       } else if (!strcasecmp(rrtype, "TXT")) {
+               nrec.type = ns_t_txt;
+               nrec.rdlength = 1 + strlen(val);
+
+               nrec.rdata = calloc(nrec.rdlength, 1);
+               if (nrec.rdata == NULL) {
+                       perror("calloc");
+                       exit(EXIT_FAILURE);
+               }
+
+               nrec.rdata[0] = nrec.rdlength - 1;
+               memmove(&(nrec.rdata[1]), val, nrec.rdlength - 1);
+       } else if (!strcasecmp(rrtype, "CNAME")) {
+               int ret;
+
+               nrec.type = ns_t_cname;
+               ret = wdns_str_to_name(val, &(nrec.rdata), 1);
+
+               if (ret <= 0) {
+                       fprintf(stderr,
+                               "Error processing CNAME policy record data "
+                               "(%d)!\n",
+                               ret);
+                       return (-1);
+               }
+
+               nrec.rdlength = ret;
+       } else if (!strcasecmp(rrtype, "DNAME")) {
+               int ret;
+
+               nrec.type = ns_t_dname;
+               ret = wdns_str_to_name(val, &(nrec.rdata), 1);
+
+               if (ret <= 0) {
+                       fprintf(stderr,
+                               "Error processing DNAME policy record data "
+                               "(%d)!\n",
+                               ret);
+                       return (-1);
+               }
+
+               nrec.rdlength = ret;
+       } else {
+               fprintf(stderr,
+                       "Error: unsupported policy record type: \"%s\"\n",
+                       rrtype);
+               return (-1);
+       }
+
+       for (n = 0; n < node->nrrs; n++) {
+               trpz_rr_t *rptr = &(node->rrs[n]);
+
+               /* Same thing. Don't replace. */
+               if (rptr->type == nrec.type && rptr->class == nrec.class &&
+                   rptr->ttl == nrec.ttl && rptr->rdlength == nrec.rdlength &&
+                   !memcmp(rptr->rdata, nrec.rdata, nrec.rdlength))
+               {
+                       return (n + 1);
+               }
+       }
+
+       node->nrrs++;
+
+       node->rrs = realloc(node->rrs, (node->nrrs * sizeof(*(node->rrs))));
+       if (node->rrs == NULL) {
+               perror("realloc");
+               exit(EXIT_FAILURE);
+       }
+
+       memset(&(node->rrs[node->nrrs - 1]), 0, sizeof(node->rrs[0]));
+       node->rrs[node->nrrs - 1] = nrec;
+       *modified = 1;
+
+       return (node->nrrs);
+}
+
+void
+reverse_labels(const char *str, char *pbuf) {
+       const char *sptr = str, *end = NULL;
+
+       if (!sptr || !*sptr) {
+               return;
+       }
+
+       sptr += (strlen(sptr) - 1);
+       end = sptr + 1;
+       *pbuf = 0;
+
+       if (*sptr == '.') {
+               sptr--;
+               end--;
+       }
+
+       while (sptr >= str) {
+               if ((*sptr != '.') && (sptr != str)) {
+                       sptr--;
+                       continue;
+               }
+
+               if (sptr == str) {
+                       strncat(pbuf, sptr, (end - sptr));
+                       break;
+               }
+
+               strncat(pbuf, sptr + 1, (end - (sptr + 1)));
+               strcat(pbuf, ".");
+               end = sptr--;
+       }
+
+       if (pbuf[strlen(pbuf) - 1] == '.') {
+               pbuf[strlen(pbuf) - 1] = 0;
+       }
+
+       return;
+}
+
+/* Parse trailing zone options as specified in a cstr line */
+unsigned long
+parse_zone_options(const char *str) {
+       char tmpstr[8192] = { 0 };
+       char *tok = NULL, *sptr = NULL;
+       unsigned long result = 0;
+
+       if (!str || !*str) {
+               return (0);
+       }
+
+       strncpy(tmpstr, str, sizeof(tmpstr) - 1);
+
+       tok = strtok_r(tmpstr, " ", &sptr);
+
+       while (tok) {
+               if (!strcasecmp(tok, "policy")) {
+                       tok = strtok_r(NULL, " ", &sptr);
+                       if (tok == NULL) {
+                               break;
+                       }
+
+                       if (!strcasecmp(tok, "passthru")) {
+                               result |= ZOPT_POLICY_PASSTHRU;
+                       } else if (!strcasecmp(tok, "drop")) {
+                               result |= ZOPT_POLICY_DROP;
+                       } else if (!strcasecmp(tok, "tcp-only")) {
+                               result |= ZOPT_POLICY_TCP_ONLY;
+                       } else if (!strcasecmp(tok, "nxdomain")) {
+                               result |= ZOPT_POLICY_NXDOMAIN;
+                       } else if (!strcasecmp(tok, "nodata")) {
+                               result |= ZOPT_POLICY_NODATA;
+                       } else if (!strcasecmp(tok, "given")) {
+                               result |= ZOPT_POLICY_GIVEN;
+                       } else if (!strcasecmp(tok, "disabled")) {
+                               result |= ZOPT_POLICY_DISABLED;
+                       } else if (!strcasecmp(tok, "no-op")) {
+                               ;
+                       }
+               } else {
+                       if (!strcasecmp(tok, "max-policy-ttl")) {
+                               tok = strtok_r(NULL, " ", &sptr);
+                               if (tok == NULL) {
+                                       break;
+                               }
+                       } else if (!strcasecmp(tok, "recursive-only")) {
+                               tok = strtok_r(NULL, " ", &sptr);
+                               if (tok == NULL) {
+                                       break;
+                               }
+
+                               if (!strcasecmp(tok, "yes")) {
+                                       result |= ZOPT_RECURSIVE_ONLY;
+                               } else if (!strcasecmp(tok, "no")) {
+                                       result |= ZOPT_NOT_RECURSIVE_ONLY;
+                               }
+
+                       } else {
+                               if (!strcasecmp(tok, "qname-as-ns")) {
+                                       tok = strtok_r(NULL, " ", &sptr);
+                                       if (tok == NULL) {
+                                               break;
+                                       }
+
+                                       if (!strcasecmp(tok, "yes")) {
+                                               result |= ZOPT_QNAME_AS_NS;
+                                       }
+
+                               } else if (!strcasecmp(tok, "ip-as-ns")) {
+                                       tok = strtok_r(NULL, " ", &sptr);
+                                       if (tok == NULL) {
+                                               break;
+                                       }
+
+                                       if (!strcasecmp(tok, "yes")) {
+                                               result |= ZOPT_IP_AS_NS;
+                                       }
+
+                               } else if (!strcasecmp(tok,
+                                                      "qname-wait-recurse"))
+                               {
+                                       tok = strtok_r(NULL, " ", &sptr);
+                                       if (tok == NULL) {
+                                               break;
+                                       }
+
+                                       if (!strcasecmp(tok, "no")) {
+                                               result |=
+                                                       ZOPT_NO_QNAME_WAIT_RECURSE;
+                                       }
+
+                               } else if (!strcasecmp(tok,
+                                                      "nsip-wait-recurse"))
+                               {
+                                       tok = strtok_r(NULL, " ", &sptr);
+                                       if (tok == NULL) {
+                                               break;
+                                       }
+
+                                       if (!strcasecmp(tok, "no")) {
+                                               result |=
+                                                       ZOPT_NO_NSIP_WAIT_RECURSE;
+                                       }
+                               }
+                       }
+               }
+
+               tok = strtok_r(NULL, " ", &sptr);
+       }
+
+       /*        LIBRPZ_POLICY_CNAME,      */
+       return (result);
+}
+
+/*
+ * Parse an update string and attempt to add any relevant data to the node
+ * and policy RR tables.
+ */
+int
+apply_update(const char *updstr, trpz_result_t **presults, size_t *pnresults,
+            trpz_zone_t **pzones, size_t *pnzones, int is_static,
+            unsigned long flags, char **errp) {
+       trpz_result_t *res = NULL;
+       char cmdbuf[64] = { 0 }, nodebuf[256] = { 0 }, rrbuf[32] = { 0 },
+            databuf[256] = { 0 };
+       char *nend = NULL;
+       librpz_policy_t policy = LIBRPZ_POLICY_UNDEFINED;
+       librpz_trig_t trig = LIBRPZ_TRIG_QNAME;
+       unsigned int ttl;
+       ssize_t n, zidx = -1;
+       size_t ndlen = 0, last_matchlen = 0;
+       int nfield, zupd = 0;
+
+       nfield = sscanf(updstr, "%63s %255s %u %31s %255s", cmdbuf, nodebuf,
+                       &ttl, rrbuf, databuf);
+       if (nfield < 1) {
+               return (-1);
+       }
+
+       /*
+        * Special case for handling zone additions; here the 'ttl' field
+        * becomes a serial.
+        */
+       if (!strcasecmp(cmdbuf, "zone")) {
+               trpz_zone_t *zptr = NULL;
+               bool qname_as_ns = false, ip_as_ns = false,
+                    not_recursive_only = false, do_inc = false;
+               bool no_qname_as_ns = false, no_ip_as_ns = false,
+                    recursive_only = false, no_nsip_wait_recurse = false;
+
+               if (nfield < 3) {
+                       return (-1);
+               }
+
+               if (!strcasecmp(rrbuf, "qname_as_ns")) {
+                       qname_as_ns = true;
+               } else if (!strcasecmp(rrbuf, "ip_as_ns")) {
+                       ip_as_ns = true;
+               } else if (!strcasecmp(rrbuf, "not_recursive_only")) {
+                       not_recursive_only = true;
+               } else if (!strcasecmp(rrbuf, "inc")) {
+                       do_inc = true;
+               } else if (!strcasecmp(rrbuf, "no_qname_as_ns")) {
+                       no_qname_as_ns = true;
+               } else if (!strcasecmp(rrbuf, "no_ip_as_ns")) {
+                       no_ip_as_ns = true;
+               } else if (!strcasecmp(rrbuf, "recursive_only")) {
+                       recursive_only = true;
+               } else if (!strcasecmp(rrbuf, "no_nsip_wait_recurse")) {
+                       no_nsip_wait_recurse = true;
+               }
+
+               if (flags & ZOPT_RECURSIVE_ONLY) {
+                       recursive_only = true;
+                       not_recursive_only = false;
+               } else if (flags & ZOPT_NOT_RECURSIVE_ONLY) {
+                       recursive_only = false;
+                       not_recursive_only = true;
+               }
+
+               if (flags & ZOPT_NO_NSIP_WAIT_RECURSE) {
+                       no_nsip_wait_recurse = true;
+               }
+
+               for (n = 0; (size_t)n < *pnzones; n++) {
+                       if (!strcmp((*pzones)[n].name, nodebuf)) {
+                               /*
+                                * Force override of serial. But only if
+                                * serial is non-zero.
+                                */
+                               if (ttl) {
+                                       if (do_inc) {
+                                               (*pzones)[n].serial += ttl;
+                                       } else {
+                                               (*pzones)[n].serial = ttl;
+                                       }
+                               }
+
+                               (*pzones)[n].has_update = 0;
+
+                               if (qname_as_ns) {
+                                       (*pzones)[n].qname_as_ns = true;
+                               }
+
+                               if (ip_as_ns) {
+                                       (*pzones)[n].ip_as_ns = true;
+                               }
+
+                               if (no_qname_as_ns) {
+                                       (*pzones)[n].qname_as_ns = false;
+                               }
+
+                               if (no_ip_as_ns) {
+                                       (*pzones)[n].ip_as_ns = false;
+                               }
+
+                               if (not_recursive_only) {
+                                       (*pzones)[n].not_recursive_only = true;
+                               }
+
+                               if (recursive_only) {
+                                       (*pzones)[n].not_recursive_only = false;
+                               }
+
+                               if (flags & ZOPT_NO_QNAME_WAIT_RECURSE) {
+                                       (*pzones)[n].no_qname_wait_recurse =
+                                               true;
+                               }
+
+                               if (no_nsip_wait_recurse) {
+                                       (*pzones)[n].no_nsip_wait_recurse =
+                                               true;
+                               }
+
+                               return (0);
+                       }
+               }
+
+               (*pnzones)++;
+
+               *pzones = realloc(*pzones, (*pnzones * sizeof(**pzones)));
+               if (*pzones == NULL) {
+                       perror("realloc");
+                       exit(EXIT_FAILURE);
+               }
+
+               zptr = &(*pzones)[*pnzones - 1];
+               *zptr = (trpz_zone_t){
+                       .serial = ttl,
+                       .qname_as_ns = qname_as_ns,
+                       .ip_as_ns = ip_as_ns,
+                       .flags = flags,
+                       .not_recursive_only = not_recursive_only,
+               };
+
+               if (qname_as_ns) {
+                       (*pzones)[n].qname_as_ns = true;
+               }
+
+               if (ip_as_ns) {
+                       (*pzones)[n].ip_as_ns = true;
+               }
+
+               if (flags & ZOPT_NO_QNAME_WAIT_RECURSE) {
+                       (*pzones)[n].no_qname_wait_recurse = true;
+               }
+
+               if (no_nsip_wait_recurse) {
+                       (*pzones)[n].no_nsip_wait_recurse = true;
+               }
+
+               strncpy(zptr->name, nodebuf, LIBRPZ_MAXDOMAIN + 1);
+               if (zptr->name[strlen(zptr->name) - 1] == '.') {
+                       zptr->name[strlen(zptr->name) - 1] = 0;
+               }
+
+               return (0);
+       } else if (nfield != 5) {
+               return (-1);
+       }
+
+       if (strcasecmp(cmdbuf, "add")) {
+               fprintf(stderr, "Warning: only update add action is currently "
+                               "supported!\n");
+               return (-1);
+       }
+
+       if (!strcasecmp(rrbuf, "A")) {
+               policy = LIBRPZ_POLICY_RECORD;
+       } else if (!strcasecmp(rrbuf, "CNAME")) {
+               if (!strcmp(databuf, ".")) {
+                       policy = LIBRPZ_POLICY_NXDOMAIN;
+               } else if (!strcmp(databuf, "*.")) {
+                       policy = LIBRPZ_POLICY_NODATA;
+               } else if (!strcasecmp(databuf, "rpz-passthru.")) {
+                       policy = LIBRPZ_POLICY_PASSTHRU;
+               } else if (!strcasecmp(databuf, "rpz-drop.")) {
+                       policy = LIBRPZ_POLICY_DROP;
+               } else if (!strcasecmp(databuf, "rpz-tcp-only.")) {
+                       policy = LIBRPZ_POLICY_TCP_ONLY;
+               } else {
+                       policy = LIBRPZ_POLICY_RECORD;
+               }
+
+       } else if (!strcasecmp(rrbuf, "TXT")) {
+               char *ftext = NULL;
+
+               ftext = strstr(updstr, databuf);
+               if (ftext == NULL) {
+                       fprintf(stderr, "Error parsing TXT record: \"%s\"\n",
+                               updstr);
+                       return (-1);
+               }
+
+               if (*ftext == '"') {
+                       *ftext++ = 0;
+
+                       if (ftext[strlen(ftext) - 1] == '"') {
+                               ftext[strlen(ftext) - 1] = 0;
+                       }
+               }
+
+               strncpy(databuf, ftext, sizeof(databuf));
+               databuf[sizeof(databuf) - 1] = 0;
+               policy = LIBRPZ_POLICY_RECORD;
+       } else if (!strcasecmp(rrbuf, "DNAME")) {
+               policy = LIBRPZ_POLICY_RECORD;
+       } else if (!strcasecmp(rrbuf, "AAAA")) {
+               policy = LIBRPZ_POLICY_RECORD;
+       } else {
+               fprintf(stderr,
+                       "Warning: target \"%s\" is not currently supported!\n",
+                       rrbuf);
+               return (-1);
+       }
+
+       if (policy == LIBRPZ_POLICY_UNDEFINED) {
+               fprintf(stderr, "Error: could not determine appropriate policy "
+                               "for update!\n");
+               return (-1);
+       }
+
+       for (n = 0; (size_t)n < *pnzones; n++) {
+               const char *zptr = nodebuf;
+               size_t cmplen;
+
+               zptr += strlen(zptr) - 1;
+
+               if (*zptr == '.') {
+                       zptr--;
+               }
+
+               cmplen = strlen((*pzones)[n].name);
+
+               if ((*pzones)[n].name[cmplen - 1] == '.') {
+                       cmplen--;
+               }
+
+               zptr -= (cmplen - 1);
+
+               if ((zptr <= nodebuf) || (*(zptr - 1) != '.')) {
+                       continue;
+               }
+
+               if (!strncmp((*pzones)[n].name, zptr, cmplen)) {
+                       /*
+                        * We don't break immediately after a match because
+                        * there might be a better one yet.
+                        */
+                       if (cmplen > last_matchlen) {
+                               ndlen = strlen(nodebuf) - cmplen;
+
+                               if (nodebuf[strlen(nodebuf) - 1] == '.') {
+                                       ndlen--;
+                               }
+
+                               /*
+                                * Account for the period between the node name
+                                * and zone name.
+                                */
+                               ndlen--;
+                               zidx = n;
+                               last_matchlen = cmplen;
+                       }
+               }
+       }
+
+       if (memmem(nodebuf, ndlen, ".rpz-", 5)) {
+               char *tptr = nodebuf + ndlen - 1;
+               size_t slen;
+
+               while (strncmp(tptr, ".rpz-", 5)) {
+                       tptr--;
+               }
+
+               slen = nodebuf + ndlen - tptr;
+               nend = tptr;
+
+               if (slen == 7 && !memcmp(tptr, ".rpz-ip", 7)) {
+                       trig = LIBRPZ_TRIG_IP;
+               } else if (slen == 9 && !memcmp(tptr, ".rpz-nsip", 9)) {
+                       trig = LIBRPZ_TRIG_NSIP;
+               } else if (slen == 14 && !memcmp(tptr, ".rpz-client-ip", 14)) {
+                       trig = LIBRPZ_TRIG_CLIENT_IP;
+               } else if (slen == 12 && !memcmp(tptr, ".rpz-nsdname", 12)) {
+                       trig = LIBRPZ_TRIG_NSDNAME;
+               } else {
+                       fprintf(stderr, "Warning: unknown suffix \"%s\"\n",
+                               tptr);
+                       nend = NULL;
+               }
+
+               /* We saved the trigger value, so shave that part off. */
+               *tptr = 0;
+       }
+
+       if (zidx == -1) {
+               return (0);
+       }
+
+       nodebuf[ndlen] = 0;
+
+       /*
+        * The original, deprecated PASSTHRU encoding of a CNAME pointing
+        * to the trigger QNAME might still be in use in local, private
+        * policy zones, and  so it is still recognized by RPZ subscriber
+        * implementations as of 2016.
+        */
+       if ((policy == LIBRPZ_POLICY_RECORD) && !strcasecmp(rrbuf, "cname")) {
+               char tmpname[512] = { 0 };
+
+               strncpy(tmpname, databuf, sizeof(tmpname) - 1);
+
+               if ((nodebuf[strlen(nodebuf) - 1] == '.') &&
+                   (tmpname[strlen(tmpname) - 1] != '.'))
+               {
+                       size_t tlen = strlen(tmpname);
+
+                       tmpname[tlen] = '.';
+                       tmpname[tlen + 1] = 0;
+               } else if ((nodebuf[strlen(nodebuf) - 1] != '.') &&
+                          (tmpname[strlen(tmpname) - 1] == '.'))
+               {
+                       tmpname[strlen(tmpname) - 1] = 0;
+               }
+
+               /* A special case of PASSTHRU (with trailing characters) */
+               if (nend != NULL &&
+                   (strlen(databuf) == (size_t)(nend - nodebuf)) &&
+                   !strncmp(databuf, nodebuf, (nend - nodebuf)))
+               {
+                       policy = LIBRPZ_POLICY_PASSTHRU;
+               }
+
+               if (!strcmp(nodebuf, tmpname)) {
+                       policy = LIBRPZ_POLICY_PASSTHRU;
+               }
+       }
+
+       res = apply_update_to_set(presults, pnresults, pzones, nodebuf, zidx,
+                                 ttl, trig, policy, &zupd, flags, errp);
+
+       if (res) {
+               if (zupd && !is_static) {
+                       (*pzones)[zidx].has_update = 1;
+               } else if (is_static) {
+                       res->flags |= NODE_FLAG_STATIC_DATA;
+               }
+
+               if (policy == LIBRPZ_POLICY_RECORD) {
+                       /*
+                        * Policy/RR change does not seem to prompt zone
+                        * serial increment. (has_update)
+                        */
+                       if (add_other_rr(res, rrbuf, databuf, ttl, &zupd) < 0) {
+                               fprintf(stderr,
+                                       "Error: could not add policy record %s "
+                                       "/ %s\n",
+                                       rrbuf, databuf);
+                               return (-1);
+                       }
+               }
+       }
+
+       return (0);
+}
+
+/*
+ * XXX: memory leak. Also, does not properly preserve "static" node entries,
+ * as envisioned.
+ */
+static void
+free_nodes(trpz_result_t **presults, size_t *pnresults) {
+       size_t n, tot;
+
+       if ((!presults || !*presults) && pnresults) {
+               *pnresults = 0;
+       }
+
+       if (!presults || !*presults) {
+               return;
+       }
+
+       tot = *pnresults;
+
+       for (n = tot; n > 0; n--) {
+               trpz_result_t *res = &((*presults)[n - 1]);
+               size_t m;
+
+               if (res->canonical) {
+                       free(res->canonical);
+               }
+
+               if (res->dname) {
+                       free(res->dname);
+               }
+
+               for (m = 0; m < res->nrrs; m++) {
+                       if (res->rrs[m].rdata) {
+                               free(res->rrs[m].rdata);
+                       }
+               }
+
+               if (res->rrs) {
+                       free(res->rrs);
+               }
+       }
+
+       free(*presults);
+       *presults = NULL;
+       *pnresults = 0;
+
+       return;
+}
+
+/*
+ * Perform only sanity checking on a data file's contents.
+ *
+ * Note that this function only really exists to facilitate the logging of error
+ * messages that may be expected to occur upon encounter with certain invalid
+ * node data in unit tests.
+ *
+ * fname is the pathname of the data file to be checked.
+ * errp is a pointer to an error string that may be set if this function fails.
+ * It is the responsibility of the caller to free this pointer if it is returned
+ * non-NULL.
+ *
+ * This function returns 0 on success, or -1 on failure, possibly setting *errp
+ * on failure.
+ */
+int
+sanity_check_data_file(const char *fname, char **errp) {
+       FILE *f = NULL;
+       int result = -1;
+
+       if (errp) {
+               *errp = NULL;
+       }
+
+       f = fopen(fname, "r");
+       if (f == NULL) {
+               fprintf(stderr, "couldn't sanity check %s\n", fname);
+               perror("fopen");
+               return (-1);
+       }
+
+       while (!feof(f)) {
+               char line[1024] = { 0 }, cmdbuf[64] = { 0 },
+                    nodebuf[256] = { 0 }, rrbuf[32] = { 0 },
+                    databuf[256] = { 0 };
+               char *lptr = line;
+               int nfield;
+               unsigned int ttl;
+
+               if (!fgets(line, sizeof(line) - 1, f)) {
+                       break;
+               }
+
+               if (!line[0]) {
+                       continue;
+               }
+
+               if (line[strlen(line) - 1] == '\n') {
+                       line[strlen(line) - 1] = 0;
+               }
+
+               if (!line[0] || line[0] == ';') {
+                       continue;
+               }
+
+               while (*lptr && !isspace(*lptr)) {
+                       lptr++;
+               }
+
+               *lptr++ = 0;
+
+               if (!strcasecmp(line, "server") || !strcasecmp(line, "send") ||
+                   !strcasecmp(line, "wipe") ||
+                   !strcasecmp(line, "rollback") ||
+                   !strcasecmp(line, "restart"))
+               {
+                       continue;
+               } else if (strcasecmp(line, "static") &&
+                          strcasecmp(line, "update"))
+               {
+                       if (errp) {
+                               *errp = str_printf("Found unknown instruction "
+                                                  "directive: \"%s\"\n",
+                                                  line);
+                       }
+
+                       goto out;
+               }
+
+               while (isspace(*lptr)) {
+                       lptr++;
+               }
+
+               nfield = sscanf(lptr, "%63s %255s %u %31s %255s", cmdbuf,
+                               nodebuf, &ttl, rrbuf, databuf);
+
+               /* We don't care about checking zones - only node entries. */
+               if (nfield != 5) {
+                       continue;
+               }
+
+               if (strcasecmp(cmdbuf, "add")) {
+                       continue;
+               }
+
+               if (strcasecmp(rrbuf, "A") && strcasecmp(rrbuf, "CNAME") &&
+                   strcasecmp(rrbuf, "TXT") && strcasecmp(rrbuf, "DNAME") &&
+                   strcasecmp(rrbuf, "AAAA"))
+               {
+                       if (errp) {
+                               *errp = str_printf("Target \"%s\" is not "
+                                                  "currently supported!\n",
+                                                  rrbuf);
+                       }
+
+                       goto out;
+               }
+
+               if (strstr(nodebuf, ".rpz-")) {
+                       char abuf[64], tmpname[512];
+                       char *tptr = nodebuf;
+                       size_t slen;
+                       int family;
+
+                       while (strncmp(tptr, ".rpz-", 5)) {
+                               tptr++;
+                       }
+
+                       slen = nodebuf + strlen(nodebuf) - tptr;
+
+                       if (!(slen >= 8 && !memcmp(tptr, ".rpz-ip.", 8)) &&
+                           !(slen >= 10 && !memcmp(tptr, ".rpz-nsip.", 10)) &&
+                           !(slen >= 15 &&
+                             !memcmp(tptr, ".rpz-client-ip.", 15)) &&
+                           !(slen >= 13 && !memcmp(tptr, ".rpz-nsdname.", 13)))
+                       {
+                               continue;
+                       }
+
+                       strncpy(tmpname, nodebuf, sizeof(tmpname) - 1);
+                       tmpname[sizeof(tmpname) - 1] = 0;
+
+                       *tptr = 0;
+
+                       if (get_address_info(nodebuf, &family, abuf, tmpname,
+                                            errp) < 0)
+                       {
+                               goto out;
+                       }
+               }
+       }
+
+       result = 0;
+
+out:
+       fclose(f);
+
+       return (result);
+}
+
+/* Load a database of nodes from a given filename. */
+int
+load_all_updates(const char *fname, trpz_result_t **presults, size_t *pnresults,
+                trpz_zone_t **pzones, size_t *pnzones, char **errp) {
+       FILE *f = NULL;
+
+       f = fopen(fname, "r");
+       if (f == NULL) {
+               fprintf(stderr, "couldn't load updates from %s\n", fname);
+               perror("fopen");
+               return (-1);
+       }
+
+       while (!feof(f)) {
+               char line[1024] = { 0 };
+               char *lptr = line;
+               int is_static = 0;
+
+               if (!fgets(line, sizeof(line) - 1, f)) {
+                       break;
+               }
+
+               if (!line[0]) {
+                       continue;
+               }
+
+               if (line[strlen(line) - 1] == '\n') {
+                       line[strlen(line) - 1] = 0;
+               }
+
+               if (!line[0]) {
+                       strcpy(line, "send");
+               }
+
+               if (!line[0] || line[0] == ';') {
+                       continue;
+               }
+
+               while (*lptr && !isspace(*lptr)) {
+                       lptr++;
+               }
+
+               *lptr++ = 0;
+
+               if (!strcasecmp(line, "server")) {
+                       continue;
+               } else if (!strcasecmp(line, "send")) {
+                       size_t n;
+
+                       for (n = 0; n < *pnzones; n++) {
+                               if ((*pzones)[n].has_update) {
+                                       (*pzones)[n].serial += 1;
+                                       (*pzones)[n].rollback += 1;
+                                       (*pzones)[n].has_update = 0;
+                               }
+                       }
+
+                       continue;
+               } else if (!strcasecmp(line, "wipe") ||
+                          !strcasecmp(line, "rollback"))
+               {
+                       size_t n;
+                       int rollback;
+
+                       rollback = strcasecmp(line, "rollback") == 0;
+
+                       free_nodes(presults, pnresults);
+
+                       /* Now push forward the serial by # rollback */
+                       for (n = 0; n < *pnzones; n++) {
+                               if (rollback) {
+                                       (*pzones)[n].serial +=
+                                               (*pzones)[n].rollback;
+                                       (*pzones)[n].rollback = 0;
+                               }
+
+                               memset((*pzones)[n].has_triggers, 0,
+                                      sizeof((*pzones)[n].has_triggers));
+                       }
+
+                       continue;
+               } else if (!strcasecmp(line, "static")) {
+                       is_static = 1;
+               } else if (!strcasecmp(line, "restart")) {
+                       size_t n;
+
+                       for (n = 0; n < *pnzones; n++) {
+                               (*pzones)[n].serial = 1;
+                               (*pzones)[n].rollback = 0;
+                       }
+
+                       continue;
+               } else if (strcasecmp(line, "update")) {
+                       fprintf(stderr,
+                               "Warning: skipping unknown instruction "
+                               "directive: \"%s\"\n",
+                               line);
+                       continue;
+               }
+
+               /* Everything here is an update */
+               while (isspace(*lptr)) {
+                       lptr++;
+               }
+
+               if (apply_update(lptr, presults, pnresults, pzones, pnzones,
+                                is_static, 0, errp) == -1)
+               {
+                       fprintf(stderr,
+                               "Error: could not apply update \"%s\"\n", lptr);
+                       return (-1);
+               }
+       }
+
+       fclose(f);
+
+       return (0);
+}
+
+#define WDNS_MAXLEN_NAME 255
+int
+wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase) {
+       const char *p = NULL;
+       size_t label_len;
+       ssize_t slen;
+       uint8_t c, *oclen = NULL, *data = NULL;
+       int res = -1;
+
+       assert(pbuf != NULL);
+
+       p = str;
+       slen = strlen(str);
+
+       if (slen == 1 && *p == '.') {
+               *pbuf = malloc(1);
+               *pbuf[0] = 0;
+               return (1);
+       }
+
+       res = 0;
+       *pbuf = malloc(WDNS_MAXLEN_NAME);
+
+       data = *pbuf;
+       label_len = 0;
+       oclen = data++;
+       res++;
+
+       for (;;) {
+               c = *p++;
+               label_len++;
+
+               if (slen == 0) {
+                       /* end of input */
+                       if (res == WDNS_MAXLEN_NAME) {
+                               res = -1;
+                               goto out;
+                       }
+                       *oclen = --label_len;
+                       *data++ = '\0';
+                       res++;
+                       break;
+               }
+
+               if (res >= WDNS_MAXLEN_NAME) {
+                       res = -1;
+               }
+
+               if (c >= 'A' && c <= 'Z') {
+                       /* an upper case letter; downcase it */
+                       if (downcase) {
+                               c |= 0x20;
+                       }
+                       *data++ = c;
+                       res++;
+               } else if (c == '\\' && !isdigit(*p)) {
+                       /* an escaped character */
+                       if (slen <= 0) {
+                               res = -1;
+                               goto out;
+                       }
+                       *data++ = *p;
+                       res++;
+                       p++;
+                       slen--;
+               } else if (c == '\\' && slen >= 3) {
+                       /* an escaped octet */
+                       char d[4];
+                       char *endptr = NULL;
+                       long int val;
+
+                       d[0] = *p++;
+                       d[1] = *p++;
+                       d[2] = *p++;
+                       d[3] = '\0';
+                       slen -= 3;
+                       if (!isdigit(d[0]) || !isdigit(d[1]) || !isdigit(d[2]))
+                       {
+                               goto out;
+                       }
+                       val = strtol(d, &endptr, 10);
+                       if (endptr != NULL && *endptr == '\0' && val >= 0 &&
+                           val <= 255)
+                       {
+                               uint8_t uval;
+
+                               uval = (uint8_t)val;
+                               *data++ = uval;
+                               res++;
+                       } else {
+                               goto out;
+                       }
+               } else if (c == '\\') {
+                       /* should not occur */
+                       goto out;
+               } else if (c == '.') {
+                       /* end of label */
+                       *oclen = --label_len;
+                       if (label_len == 0) {
+                               goto out;
+                       }
+                       oclen = data++;
+                       if (slen > 1) {
+                               res++;
+                       }
+                       label_len = 0;
+               } else if (c != '\0') {
+                       *data++ = c;
+                       res++;
+               }
+
+               slen--;
+       }
+
+       return (res);
+
+out:
+       free(*pbuf);
+       return (-1);
+}
diff --git a/bin/tests/system/rpz/testlib/test-data.h b/bin/tests/system/rpz/testlib/test-data.h
new file mode 100644 (file)
index 0000000..d8027f8
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Limited implementation of the DNSRPS API for testing purposes.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LIBRPZ_LIB_OPEN 2
+#include <dns/librpz.h>
+
+#include "trpz.h"
+
+#define NODE_FLAG_IPV6_ADDRESS 0x1
+#define NODE_FLAG_STATIC_DATA  0x2
+
+#define ZOPT_POLICY_PASSTHRU 0x0001
+#define ZOPT_POLICY_DROP     0x0002
+#define ZOPT_POLICY_TCP_ONLY 0x0004
+#define ZOPT_POLICY_NXDOMAIN 0x0008
+#define ZOPT_POLICY_NODATA   0x0010
+#define ZOPT_POLICY_GIVEN    0x0020
+#define ZOPT_POLICY_DISABLED 0x0040
+
+#define ZOPT_RECURSIVE_ONLY    0x0100
+#define ZOPT_NOT_RECURSIVE_ONLY 0x0200
+#define ZOPT_QNAME_AS_NS       0x0400
+#define ZOPT_IP_AS_NS          0x0800
+
+#define ZOPT_QNAME_WAIT_RECURSE           0x1000
+#define ZOPT_NO_QNAME_WAIT_RECURSE 0x2000
+#define ZOPT_NO_NSIP_WAIT_RECURSE  0x4000
+
+typedef struct {
+       char name[256];
+       uint32_t serial;
+       int has_update;
+       size_t rollback;
+       int has_triggers[2][LIBRPZ_TRIG_NSIP + 1];
+       bool forgotten;
+       bool qname_as_ns, ip_as_ns;
+       bool not_recursive_only;
+       bool no_qname_wait_recurse, no_nsip_wait_recurse;
+       unsigned long flags;
+} trpz_zone_t;
+
+typedef struct {
+       uint16_t type;
+       uint16_t class;
+       uint32_t ttl;
+       uint16_t rdlength;
+       uint8_t *rdata;
+       unsigned int rrn;
+} trpz_rr_t;
+
+typedef struct {
+       char *canonical;
+       char *dname;
+       librpz_result_t result;
+       uint32_t ttl;
+       trpz_rr_t *rrs;
+       size_t nrrs, rridx;
+       librpz_policy_t poverride, hidden_policy;
+       unsigned long flags;
+       librpz_trig_t match_trig;
+} trpz_result_t;
+
+#define DECL_NODE(canon, name, policy, znum, trig) \
+       { canon, name, { 0, 0, policy, policy, znum, znum, trig, true } },
+
+#define NUM_ZONES_SNAPSHOT1 20
+
+extern const rpz_soa_t g_soa_record;
+
+#define WDNS_PRESLEN_NAME 1025
+extern size_t
+wdns_domain_to_str(const uint8_t *src, size_t src_len, char *dst);
+extern int
+wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase);
+
+extern void
+reverse_labels(const char *str, char *pbuf);
+
+extern rpz_soa_t *
+parse_serial(unsigned char *rdata, size_t rdlen);
+
+extern int
+load_all_updates(const char *fname, trpz_result_t **presults, size_t *pnresults,
+                trpz_zone_t **pzones, size_t *pnzones, char **errp);
+extern int
+apply_update(const char *updstr, trpz_result_t **presults, size_t *pnresults,
+            trpz_zone_t **pzones, size_t *pnzones, int is_static,
+            unsigned long flags, char **errp);
+extern int
+sanity_check_data_file(const char *fname, char **errp);
+
+extern unsigned long
+parse_zone_options(const char *str);
+
+extern int
+get_address_info(const char *astr, int *pfamily, char *pbuf,
+                const char *optname, char **errp);
diff --git a/bin/tests/system/rpz/testlib/trpz.h b/bin/tests/system/rpz/testlib/trpz.h
new file mode 100644 (file)
index 0000000..96ffce0
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * Limited implementation of the DNSRPS API for testing purposes.
+ *
+ * Copyright (c) 2016-2017 Farsight Security, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TRPZ_H
+#define TRPZ_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define TARGET_ZONE "rpz-test.example.com"
+
+/* This should be in the librpz.h include. */
+union socku {
+       struct sockaddr sa;
+       struct sockaddr_in ipv4;
+       struct sockaddr_in6 ipv6;
+       struct sockaddr_un sun;
+};
+
+typedef struct {
+       const char *mname;
+       const char *rname;
+       uint32_t serial;
+       uint32_t refresh;
+       uint32_t retry;
+       uint32_t expire;
+       uint32_t minimum;
+} rpz_soa_t;
+
+#endif
index 7ebbb0d2c596aa40404e367b5c6a1e87cfec2093..5adc5df049825a2d668548bc4c3feb9d222ad67a 100644 (file)
@@ -1430,7 +1430,7 @@ no)
        AC_MSG_RESULT(no)
        ;;
 *)
-       AC_MSG_ERROR("--enable-querytrace requires yes or no (not $enable_querytrace)")
+       AC_MSG_ERROR(["--enable-querytrace requires yes or no (not $enable_querytrace)"])
        ;;
 esac
 
@@ -1522,6 +1522,8 @@ AS_IF([test "$enable_dnsrps" != "no"],[
        AC_DEFINE([USE_DNSRPS], [1], [Enable DNS Response Policy Service API])
       ])
 
+AM_CONDITIONAL([DNSRPS], [test "$enable_dnsrps" != "no"])
+
 AC_CHECK_HEADERS([glob.h])
 
 #
@@ -1590,7 +1592,9 @@ AC_CONFIG_FILES([bin/tests/Makefile
                 bin/tests/system/conf.sh
                 bin/tests/system/dyndb/driver/Makefile
                 bin/tests/system/dlzexternal/driver/Makefile
-                bin/tests/system/hooks/driver/Makefile])
+                bin/tests/system/hooks/driver/Makefile
+                bin/tests/system/rpz/testlib/Makefile
+                 ])
 
 AC_CONFIG_FILES([bin/tests/system/ifconfig.sh],
                [chmod +x bin/tests/system/ifconfig.sh])
index a322c9900bc8829e3d05133531c6579f41521df6..e47ab411eaec6951697b47ba9d52df7cc4161655 100644 (file)
@@ -98,12 +98,6 @@ dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) {
        }
 
        switch (level) {
-       case LIBRPZ_LOG_FATAL:
-       case LIBRPZ_LOG_ERROR: /* errors */
-       default:
-               isc_level = DNS_RPZ_ERROR_LEVEL;
-               break;
-
        case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */
                isc_level = DNS_RPZ_INFO_LEVEL;
                break;
@@ -119,6 +113,12 @@ dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) {
        case LIBRPZ_LOG_TRACE4: /* librpz lookups */
                isc_level = DNS_RPZ_DEBUG_LEVEL3;
                break;
+
+       case LIBRPZ_LOG_FATAL:
+       case LIBRPZ_LOG_ERROR: /* errors */
+       default:
+               isc_level = DNS_RPZ_ERROR_LEVEL;
+               break;
        }
        isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
                      isc_level, "dnsrps: %s", buf);
@@ -319,9 +319,6 @@ dns_dnsrps_2policy(librpz_policy_t rps_policy) {
 dns_rpz_type_t
 dns_dnsrps_trig2type(librpz_trig_t trig) {
        switch (trig) {
-       case LIBRPZ_TRIG_BAD:
-       default:
-               return (DNS_RPZ_TYPE_BAD);
        case LIBRPZ_TRIG_CLIENT_IP:
                return (DNS_RPZ_TYPE_CLIENT_IP);
        case LIBRPZ_TRIG_QNAME:
@@ -332,6 +329,9 @@ dns_dnsrps_trig2type(librpz_trig_t trig) {
                return (DNS_RPZ_TYPE_NSDNAME);
        case LIBRPZ_TRIG_NSIP:
                return (DNS_RPZ_TYPE_NSIP);
+       case LIBRPZ_TRIG_BAD:
+       default:
+               return (DNS_RPZ_TYPE_BAD);
        }
 }
 
@@ -341,9 +341,6 @@ dns_dnsrps_trig2type(librpz_trig_t trig) {
 librpz_trig_t
 dns_dnsrps_type2trig(dns_rpz_type_t type) {
        switch (type) {
-       case DNS_RPZ_TYPE_BAD:
-       default:
-               return (LIBRPZ_TRIG_BAD);
        case DNS_RPZ_TYPE_CLIENT_IP:
                return (LIBRPZ_TRIG_CLIENT_IP);
        case DNS_RPZ_TYPE_QNAME:
@@ -354,6 +351,9 @@ dns_dnsrps_type2trig(dns_rpz_type_t type) {
                return (LIBRPZ_TRIG_NSDNAME);
        case DNS_RPZ_TYPE_NSIP:
                return (LIBRPZ_TRIG_NSIP);
+       case DNS_RPZ_TYPE_BAD:
+       default:
+               return (LIBRPZ_TRIG_BAD);
        }
 }
 
@@ -493,6 +493,16 @@ rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
        REQUIRE(node == &rpsdb->data_node);
 
        switch (rpsdb->result.policy) {
+       case LIBRPZ_POLICY_NXDOMAIN:
+               return (DNS_R_NXDOMAIN);
+
+       case LIBRPZ_POLICY_NODATA:
+               return (DNS_R_NXRRSET);
+
+       case LIBRPZ_POLICY_RECORD:
+       case LIBRPZ_POLICY_CNAME:
+               break;
+
        case LIBRPZ_POLICY_UNDEFINED:
        case LIBRPZ_POLICY_DELETED:
        case LIBRPZ_POLICY_PASSTHRU:
@@ -505,16 +515,6 @@ rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
                            "impossible dnsrps policy %d at %s:%d",
                            rpsdb->result.policy, __FILE__, __LINE__);
                return (DNS_R_SERVFAIL);
-
-       case LIBRPZ_POLICY_NXDOMAIN:
-               return (DNS_R_NXDOMAIN);
-
-       case LIBRPZ_POLICY_NODATA:
-               return (DNS_R_NXRRSET);
-
-       case LIBRPZ_POLICY_RECORD:
-       case LIBRPZ_POLICY_CNAME:
-               break;
        }
 
        if (type == dns_rdatatype_soa) {
index 60496be8eed5d2149faf5d47fc68fde2f67f4142..dd09ac87b1452c7fa8a20810214c8692ff5ecd13 100644 (file)
@@ -55,6 +55,8 @@
 #define LIBDEF_F(f)
 #endif /* ifdef LIBRPZ_INTERNAL */
 
+#define LIBRPZ_MAXDOMAIN 255
+
 /*
  * Response Policy Zone triggers.
  *     Comparisons of trigger precedences require
@@ -125,7 +127,7 @@ typedef struct librpz_prefix {
 typedef uint8_t librpz_dsize_t;
 typedef struct librpz_domain {
        librpz_dsize_t size; /* of only .d */
-       uint8_t        d[0]; /* variable length wire format */
+       uint8_t        d[];  /* variable length wire format */
 } librpz_domain_t;
 
 /*
@@ -133,7 +135,7 @@ typedef struct librpz_domain {
  */
 typedef struct librpz_domain_buf {
        librpz_dsize_t size;
-       uint8_t        d[NS_MAXCDNAME];
+       uint8_t        d[LIBRPZ_MAXDOMAIN];
 } librpz_domain_buf_t;
 
 /*
@@ -145,7 +147,7 @@ typedef struct {
        uint16_t class;    /* network byte order */
        uint32_t ttl;      /* network byte order */
        uint16_t rdlength; /* network byte order */
-       uint8_t  rdata[0]; /* variable length */
+       uint8_t  rdata[];  /* variable length */
 } librpz_rr_t;
 
 /*
@@ -169,8 +171,7 @@ typedef struct librpz_result {
        librpz_dznum_t     dznum;   /* dnsrpzd zone number */
        librpz_cznum_t     cznum;   /* librpz client zone number */
        librpz_trig_t      trig : LIBRPZ_TRIG_SIZE;
-       bool               log : 1; /* log rewrite given librpz_log_level
-                                    * */
+       bool               log : 1; /* log rewrite at given log level */
 } librpz_result_t;
 
 /**
index 2fe7386c3c8d6e6fa228524ead513d066fcf1c99..4ce1c4d7abfc6e39f9c6bde030ef9ab7c78435f4 100644 (file)
@@ -3348,6 +3348,8 @@ dnsrps_ck(librpz_emsg_t *emsg, ns_client_t *client, rpsdb_t *rpsdb,
        isc_region_t region;
        librpz_domain_buf_t pname_buf;
 
+       CTRACE(ISC_LOG_DEBUG(3), "dnsrps_ck");
+
        if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) {
                return (-1);
        }
@@ -3396,18 +3398,18 @@ static bool
 dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st,
             dns_rdatatype_t qtype, dns_rdataset_t **p_rdatasetp,
             bool recursed) {
-       rpsdb_t *rpsdb;
+       rpsdb_t *rpsdb = NULL;
        librpz_domain_buf_t pname_buf;
        isc_region_t region;
-       dns_zone_t *p_zone;
-       dns_db_t *p_db;
-       dns_dbnode_t *p_node;
+       dns_zone_t *p_zone = NULL;
+       dns_db_t *p_db = NULL;
+       dns_dbnode_t *p_node = NULL;
        dns_rpz_policy_t policy;
-       dns_fixedname_t foundf;
-       dns_name_t *found;
        dns_rdatatype_t foundtype, searchtype;
        isc_result_t result;
 
+       CTRACE(ISC_LOG_DEBUG(3), "dnsrps_set_p");
+
        rpsdb = (rpsdb_t *)st->rpsdb;
 
        if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) {
@@ -3437,9 +3439,6 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st,
        region.length = pname_buf.size;
        dns_name_fromregion(st->p_name, &region);
 
-       p_zone = NULL;
-       p_db = NULL;
-       p_node = NULL;
        rpz_ready(client, p_rdatasetp);
        dns_db_attach(st->rpsdb, &p_db);
        policy = dns_dnsrps_2policy(rpsdb->result.policy);
@@ -3453,6 +3452,9 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st,
                result = DNS_R_NXRRSET;
                policy = DNS_RPZ_POLICY_NODATA;
        } else {
+               dns_fixedname_t foundf;
+               dns_name_t *found = NULL;
+
                /*
                 * Get the next (and so first) RR from the policy node.
                 * If it is a CNAME, then look for it regardless of the
@@ -3464,6 +3466,7 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st,
                {
                        return (false);
                }
+
                if (foundtype == dns_rdatatype_cname) {
                        searchtype = dns_rdatatype_cname;
                } else {
@@ -3511,6 +3514,8 @@ dnsrps_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr,
        librpz_emsg_t emsg;
        isc_result_t result;
 
+       CTRACE(ISC_LOG_DEBUG(3), "dnsrps_rewrite_ip");
+
        st = client->query.rpz_st;
        rpsdb = (rpsdb_t *)st->rpsdb;
 
@@ -3567,6 +3572,8 @@ dnsrps_rewrite_name(ns_client_t *client, dns_name_t *trig_name, bool recursed,
        librpz_emsg_t emsg;
        isc_result_t result;
 
+       CTRACE(ISC_LOG_DEBUG(3), "dnsrps_rewrite_name");
+
        st = client->query.rpz_st;
        rpsdb = (rpsdb_t *)st->rpsdb;
 
@@ -4201,6 +4208,7 @@ rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, isc_result_t qresult,
                        if (st->rpsdb != NULL) {
                                dns_db_detach(&st->rpsdb);
                        }
+                       CTRACE(ISC_LOG_DEBUG(3), "dns_dnsrps_rewrite_init");
                        result = dns_dnsrps_rewrite_init(
                                &emsg, st, rpzs, client->query.qname,
                                client->manager->mctx, RECURSIONOK(client));