/*	$NetBSD: rpz.c,v 1.1.4.2 2024/02/29 11:38:42 martin Exp $	*/

/*
 * 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.
 */

/*! \file */

#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>

#include <isc/buffer.h>
#include <isc/mem.h>
#include <isc/net.h>
#include <isc/netaddr.h>
#include <isc/print.h>
#include <isc/rwlock.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/util.h>

#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/dnsrps.h>
#include <dns/events.h>
#include <dns/fixedname.h>
#include <dns/log.h>
#include <dns/rbt.h>
#include <dns/rdata.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/result.h>
#include <dns/rpz.h>
#include <dns/view.h>

/*
 * Parallel radix trees for databases of response policy IP addresses
 *
 * The radix or patricia trees are somewhat specialized to handle response
 * policy addresses by representing the two sets of IP addresses and name
 * server IP addresses in a single tree.  One set of IP addresses is
 * for rpz-ip policies or policies triggered by addresses in A or
 * AAAA records in responses.
 * The second set is for rpz-nsip policies or policies triggered by addresses
 * in A or AAAA records for NS records that are authorities for responses.
 *
 * Each leaf indicates that an IP address is listed in the IP address or the
 * name server IP address policy sub-zone (or both) of the corresponding
 * response policy zone.  The policy data such as a CNAME or an A record
 * is kept in the policy zone.  After an IP address has been found in a radix
 * tree, the node in the policy zone's database is found by converting
 * the IP address to a domain name in a canonical form.
 *
 *
 * The response policy zone canonical form of an IPv6 address is one of:
 *	prefix.W.W.W.W.W.W.W.W
 *	prefix.WORDS.zz
 *	prefix.WORDS.zz.WORDS
 *	prefix.zz.WORDS
 *  where
 *	prefix	is the prefix length of the IPv6 address between 1 and 128
 *	W	is a number between 0 and 65535
 *	WORDS	is one or more numbers W separated with "."
 *	zz	corresponds to :: in the standard IPv6 text representation
 *
 * The canonical form of IPv4 addresses is:
 *	prefix.B.B.B.B
 *  where
 *	prefix	is the prefix length of the address between 1 and 32
 *	B	is a number between 0 and 255
 *
 * Names for IPv4 addresses are distinguished from IPv6 addresses by having
 * 5 labels all of which are numbers, and a prefix between 1 and 32.
 */

/*
 * Nodes hashtable calculation parameters
 */
#define DNS_RPZ_HTSIZE_MAX 24
#define DNS_RPZ_HTSIZE_DIV 3

/*
 * Maximum number of nodes to process per quantum
 */
#define DNS_RPZ_QUANTUM 1024

static void
dns_rpz_update_from_db(dns_rpz_zone_t *rpz);

static void
dns_rpz_update_taskaction(isc_task_t *task, isc_event_t *event);

/*
 * Use a private definition of IPv6 addresses because s6_addr32 is not
 * always defined and our IPv6 addresses are in non-standard byte order
 */
typedef uint32_t dns_rpz_cidr_word_t;
#define DNS_RPZ_CIDR_WORD_BITS ((int)sizeof(dns_rpz_cidr_word_t) * 8)
#define DNS_RPZ_CIDR_KEY_BITS  ((int)sizeof(dns_rpz_cidr_key_t) * 8)
#define DNS_RPZ_CIDR_WORDS     (128 / DNS_RPZ_CIDR_WORD_BITS)
typedef struct {
	dns_rpz_cidr_word_t w[DNS_RPZ_CIDR_WORDS];
} dns_rpz_cidr_key_t;

#define ADDR_V4MAPPED 0xffff
#define KEY_IS_IPV4(prefix, ip)                                  \
	((prefix) >= 96 && (ip)->w[0] == 0 && (ip)->w[1] == 0 && \
	 (ip)->w[2] == ADDR_V4MAPPED)

#define DNS_RPZ_WORD_MASK(b)                   \
	((b) == 0 ? (dns_rpz_cidr_word_t)(-1)  \
		  : ((dns_rpz_cidr_word_t)(-1) \
		     << (DNS_RPZ_CIDR_WORD_BITS - (b))))

/*
 * Get bit #n from the array of words of an IP address.
 */
#define DNS_RPZ_IP_BIT(ip, n)                          \
	(1 & ((ip)->w[(n) / DNS_RPZ_CIDR_WORD_BITS] >> \
	      (DNS_RPZ_CIDR_WORD_BITS - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS))))

/*
 * A triplet of arrays of bits flagging the existence of
 * client-IP, IP, and NSIP policy triggers.
 */
typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t;
struct dns_rpz_addr_zbits {
	dns_rpz_zbits_t client_ip;
	dns_rpz_zbits_t ip;
	dns_rpz_zbits_t nsip;
};

/*
 * A CIDR or radix tree node.
 */
struct dns_rpz_cidr_node {
	dns_rpz_cidr_node_t *parent;
	dns_rpz_cidr_node_t *child[2];
	dns_rpz_cidr_key_t ip;
	dns_rpz_prefix_t prefix;
	dns_rpz_addr_zbits_t set;
	dns_rpz_addr_zbits_t sum;
};

/*
 * A pair of arrays of bits flagging the existence of
 * QNAME and NSDNAME policy triggers.
 */
typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t;
struct dns_rpz_nm_zbits {
	dns_rpz_zbits_t qname;
	dns_rpz_zbits_t ns;
};

/*
 * The data in a RBT node has two pairs of bits for policy zones.
 * One pair is for the corresponding name of the node such as example.com
 * and the other pair is for a wildcard child such as *.example.com.
 */
typedef struct dns_rpz_nm_data dns_rpz_nm_data_t;
struct dns_rpz_nm_data {
	dns_rpz_nm_zbits_t set;
	dns_rpz_nm_zbits_t wild;
};

static void
rpz_detach(dns_rpz_zone_t **rpzp);

static void
rpz_detach_rpzs(dns_rpz_zones_t **rpzsp);

#if 0
/*
 * Catch a name while debugging.
 */
static void
catch_name(const dns_name_t *src_name, const char *tgt, const char *str) {
	dns_fixedname_t tgt_namef;
	dns_name_t *tgt_name;

	tgt_name = dns_fixedname_initname(&tgt_namef);
	dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL);
	if (dns_name_equal(src_name, tgt_name)) {
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
			      "rpz hit failed: %s %s", str, tgt);
	}
}
#endif /* if 0 */

const char *
dns_rpz_type2str(dns_rpz_type_t type) {
	switch (type) {
	case DNS_RPZ_TYPE_CLIENT_IP:
		return ("CLIENT-IP");
	case DNS_RPZ_TYPE_QNAME:
		return ("QNAME");
	case DNS_RPZ_TYPE_IP:
		return ("IP");
	case DNS_RPZ_TYPE_NSIP:
		return ("NSIP");
	case DNS_RPZ_TYPE_NSDNAME:
		return ("NSDNAME");
	case DNS_RPZ_TYPE_BAD:
		break;
	}
	FATAL_ERROR(__FILE__, __LINE__, "impossible rpz type %d", type);
	return ("impossible");
}

dns_rpz_policy_t
dns_rpz_str2policy(const char *str) {
	static struct {
		const char *str;
		dns_rpz_policy_t policy;
	} tbl[] = {
		{ "given", DNS_RPZ_POLICY_GIVEN },
		{ "disabled", DNS_RPZ_POLICY_DISABLED },
		{ "passthru", DNS_RPZ_POLICY_PASSTHRU },
		{ "drop", DNS_RPZ_POLICY_DROP },
		{ "tcp-only", DNS_RPZ_POLICY_TCP_ONLY },
		{ "nxdomain", DNS_RPZ_POLICY_NXDOMAIN },
		{ "nodata", DNS_RPZ_POLICY_NODATA },
		{ "cname", DNS_RPZ_POLICY_CNAME },
		{ "no-op", DNS_RPZ_POLICY_PASSTHRU }, /* old passthru */
	};
	unsigned int n;

	if (str == NULL) {
		return (DNS_RPZ_POLICY_ERROR);
	}
	for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n) {
		if (!strcasecmp(tbl[n].str, str)) {
			return (tbl[n].policy);
		}
	}
	return (DNS_RPZ_POLICY_ERROR);
}

const char *
dns_rpz_policy2str(dns_rpz_policy_t policy) {
	const char *str;

	switch (policy) {
	case DNS_RPZ_POLICY_PASSTHRU:
		str = "PASSTHRU";
		break;
	case DNS_RPZ_POLICY_DROP:
		str = "DROP";
		break;
	case DNS_RPZ_POLICY_TCP_ONLY:
		str = "TCP-ONLY";
		break;
	case DNS_RPZ_POLICY_NXDOMAIN:
		str = "NXDOMAIN";
		break;
	case DNS_RPZ_POLICY_NODATA:
		str = "NODATA";
		break;
	case DNS_RPZ_POLICY_RECORD:
		str = "Local-Data";
		break;
	case DNS_RPZ_POLICY_CNAME:
	case DNS_RPZ_POLICY_WILDCNAME:
		str = "CNAME";
		break;
	case DNS_RPZ_POLICY_MISS:
		str = "MISS";
		break;
	case DNS_RPZ_POLICY_DNS64:
		str = "DNS64";
		break;
	case DNS_RPZ_POLICY_ERROR:
		str = "ERROR";
		break;
	default:
		UNREACHABLE();
	}
	return (str);
}

/*
 * Return the bit number of the highest set bit in 'zbit'.
 * (for example, 0x01 returns 0, 0xFF returns 7, etc.)
 */
static int
zbit_to_num(dns_rpz_zbits_t zbit) {
	dns_rpz_num_t rpz_num;

	REQUIRE(zbit != 0);
	rpz_num = 0;
	if ((zbit & 0xffffffff00000000ULL) != 0) {
		zbit >>= 32;
		rpz_num += 32;
	}
	if ((zbit & 0xffff0000) != 0) {
		zbit >>= 16;
		rpz_num += 16;
	}
	if ((zbit & 0xff00) != 0) {
		zbit >>= 8;
		rpz_num += 8;
	}
	if ((zbit & 0xf0) != 0) {
		zbit >>= 4;
		rpz_num += 4;
	}
	if ((zbit & 0xc) != 0) {
		zbit >>= 2;
		rpz_num += 2;
	}
	if ((zbit & 2) != 0) {
		++rpz_num;
	}
	return (rpz_num);
}

/*
 * Make a set of bit masks given one or more bits and their type.
 */
static void
make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits,
	      dns_rpz_type_t type) {
	switch (type) {
	case DNS_RPZ_TYPE_CLIENT_IP:
		tgt_set->client_ip = zbits;
		tgt_set->ip = 0;
		tgt_set->nsip = 0;
		break;
	case DNS_RPZ_TYPE_IP:
		tgt_set->client_ip = 0;
		tgt_set->ip = zbits;
		tgt_set->nsip = 0;
		break;
	case DNS_RPZ_TYPE_NSIP:
		tgt_set->client_ip = 0;
		tgt_set->ip = 0;
		tgt_set->nsip = zbits;
		break;
	default:
		UNREACHABLE();
	}
}

static void
make_nm_set(dns_rpz_nm_zbits_t *tgt_set, dns_rpz_num_t rpz_num,
	    dns_rpz_type_t type) {
	switch (type) {
	case DNS_RPZ_TYPE_QNAME:
		tgt_set->qname = DNS_RPZ_ZBIT(rpz_num);
		tgt_set->ns = 0;
		break;
	case DNS_RPZ_TYPE_NSDNAME:
		tgt_set->qname = 0;
		tgt_set->ns = DNS_RPZ_ZBIT(rpz_num);
		break;
	default:
		UNREACHABLE();
	}
}

/*
 * Mark a node and all of its parents as having client-IP, IP, or NSIP data
 */
static void
set_sum_pair(dns_rpz_cidr_node_t *cnode) {
	dns_rpz_cidr_node_t *child;
	dns_rpz_addr_zbits_t sum;

	do {
		sum = cnode->set;

		child = cnode->child[0];
		if (child != NULL) {
			sum.client_ip |= child->sum.client_ip;
			sum.ip |= child->sum.ip;
			sum.nsip |= child->sum.nsip;
		}

		child = cnode->child[1];
		if (child != NULL) {
			sum.client_ip |= child->sum.client_ip;
			sum.ip |= child->sum.ip;
			sum.nsip |= child->sum.nsip;
		}

		if (cnode->sum.client_ip == sum.client_ip &&
		    cnode->sum.ip == sum.ip && cnode->sum.nsip == sum.nsip)
		{
			break;
		}
		cnode->sum = sum;
		cnode = cnode->parent;
	} while (cnode != NULL);
}

/* Caller must hold rpzs->maint_lock */
static void
fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) {
	dns_rpz_zbits_t mask;

	/*
	 * qname_wait_recurse and qname_skip_recurse are used to
	 * implement the "qname-wait-recurse" config option.
	 *
	 * When "qname-wait-recurse" is yes, no processing happens without
	 * recursion. In this case, qname_wait_recurse is true, and
	 * qname_skip_recurse (a bit field indicating which policy zones
	 * can be processed without recursion) is set to all 0's by
	 * fix_qname_skip_recurse().
	 *
	 * When "qname-wait-recurse" is no, qname_skip_recurse may be
	 * set to a non-zero value by fix_qname_skip_recurse(). The mask
	 * has to have bits set for the policy zones for which
	 * processing may continue without recursion, and bits cleared
	 * for the rest.
	 *
	 * (1) The ARM says:
	 *
	 *   The "qname-wait-recurse no" option overrides that default
	 *   behavior when recursion cannot change a non-error
	 *   response. The option does not affect QNAME or client-IP
	 *   triggers in policy zones listed after other zones
	 *   containing IP, NSIP and NSDNAME triggers, because those may
	 *   depend on the A, AAAA, and NS records that would be found
	 *   during recursive resolution.
	 *
	 * Let's consider the following:
	 *
	 *     zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
	 *		    rpzs->have.nsdname |
	 *		    rpzs->have.nsipv4 | rpzs->have.nsipv6);
	 *
	 * zbits_req now contains bits set for zones which require
	 * recursion.
	 *
	 * But going by the description in the ARM, if the first policy
	 * zone requires recursion, then all zones after that (higher
	 * order bits) have to wait as well.  If the Nth zone requires
	 * recursion, then (N+1)th zone onwards all need to wait.
	 *
	 * So mapping this, examples:
	 *
	 * zbits_req = 0b000  mask = 0xffffffff (no zones have to wait for
	 *					 recursion)
	 * zbits_req = 0b001  mask = 0x00000000 (all zones have to wait)
	 * zbits_req = 0b010  mask = 0x00000001 (the first zone doesn't have to
	 *					 wait, second zone onwards need
	 *					 to wait)
	 * zbits_req = 0b011  mask = 0x00000000 (all zones have to wait)
	 * zbits_req = 0b100  mask = 0x00000011 (the 1st and 2nd zones don't
	 *					 have to wait, third zone
	 *					 onwards need to wait)
	 *
	 * More generally, we have to count the number of trailing 0
	 * bits in zbits_req and only these can be processed without
	 * recursion. All the rest need to wait.
	 *
	 * (2) The ARM says that "qname-wait-recurse no" option
	 * overrides the default behavior when recursion cannot change a
	 * non-error response. So, in the order of listing of policy
	 * zones, within the first policy zone where recursion may be
	 * required, we should first allow CLIENT-IP and QNAME policy
	 * records to be attempted without recursion.
	 */

	/*
	 * Get a mask covering all policy zones that are not subordinate to
	 * other policy zones containing triggers that require that the
	 * qname be resolved before they can be checked.
	 */
	rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6;
	rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6;
	rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6;

	if (rpzs->p.qname_wait_recurse) {
		mask = 0;
	} else {
		dns_rpz_zbits_t zbits_req;
		dns_rpz_zbits_t zbits_notreq;
		dns_rpz_zbits_t mask2;
		dns_rpz_zbits_t req_mask;

		/*
		 * Get the masks of zones with policies that
		 * do/don't require recursion
		 */

		zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 |
			     rpzs->have.nsdname | rpzs->have.nsipv4 |
			     rpzs->have.nsipv6);
		zbits_notreq = (rpzs->have.client_ip | rpzs->have.qname);

		if (zbits_req == 0) {
			mask = DNS_RPZ_ALL_ZBITS;
			goto set;
		}

		/*
		 * req_mask is a mask covering used bits in
		 * zbits_req. (For instance, 0b1 => 0b1, 0b101 => 0b111,
		 * 0b11010101 => 0b11111111).
		 */
		req_mask = zbits_req;
		req_mask |= req_mask >> 1;
		req_mask |= req_mask >> 2;
		req_mask |= req_mask >> 4;
		req_mask |= req_mask >> 8;
		req_mask |= req_mask >> 16;
		req_mask |= req_mask >> 32;

		/*
		 * There's no point in skipping recursion for a later
		 * zone if it is required in a previous zone.
		 */
		if ((zbits_notreq & req_mask) == 0) {
			mask = 0;
			goto set;
		}

		/*
		 * This bit arithmetic creates a mask of zones in which
		 * it is okay to skip recursion. After the first zone
		 * that has to wait for recursion, all the others have
		 * to wait as well, so we want to create a mask in which
		 * all the trailing zeroes in zbits_req are are 1, and
		 * more significant bits are 0. (For instance,
		 * 0x0700 => 0x00ff, 0x0007 => 0x0000)
		 */
		mask = ~(zbits_req | ((~zbits_req) + 1));

		/*
		 * As mentioned in (2) above, the zone corresponding to
		 * the least significant zero could have its CLIENT-IP
		 * and QNAME policies checked before recursion, if it
		 * has any of those policies.  So if it does, we
		 * can set its 0 to 1.
		 *
		 * Locate the least significant 0 bit in the mask (for
		 * instance, 0xff => 0x100)...
		 */
		mask2 = (mask << 1) & ~mask;

		/*
		 * Also set the bit for zone 0, because if it's in
		 * zbits_notreq then it's definitely okay to attempt to
		 * skip recursion for zone 0...
		 */
		mask2 |= 1;

		/* Clear any bits *not* in zbits_notreq... */
		mask2 &= zbits_notreq;

		/* And merge the result into the skip-recursion mask */
		mask |= mask2;
	}

set:
	isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB,
		      DNS_RPZ_DEBUG_QUIET,
		      "computed RPZ qname_skip_recurse mask=0x%" PRIx64,
		      (uint64_t)mask);
	rpzs->have.qname_skip_recurse = mask;
}

static void
adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
		dns_rpz_type_t rpz_type, const dns_rpz_cidr_key_t *tgt_ip,
		dns_rpz_prefix_t tgt_prefix, bool inc) {
	dns_rpz_trigger_counter_t *cnt = NULL;
	dns_rpz_zbits_t *have = NULL;

	switch (rpz_type) {
	case DNS_RPZ_TYPE_CLIENT_IP:
		REQUIRE(tgt_ip != NULL);
		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
			cnt = &rpzs->triggers[rpz_num].client_ipv4;
			have = &rpzs->have.client_ipv4;
		} else {
			cnt = &rpzs->triggers[rpz_num].client_ipv6;
			have = &rpzs->have.client_ipv6;
		}
		break;
	case DNS_RPZ_TYPE_QNAME:
		cnt = &rpzs->triggers[rpz_num].qname;
		have = &rpzs->have.qname;
		break;
	case DNS_RPZ_TYPE_IP:
		REQUIRE(tgt_ip != NULL);
		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
			cnt = &rpzs->triggers[rpz_num].ipv4;
			have = &rpzs->have.ipv4;
		} else {
			cnt = &rpzs->triggers[rpz_num].ipv6;
			have = &rpzs->have.ipv6;
		}
		break;
	case DNS_RPZ_TYPE_NSDNAME:
		cnt = &rpzs->triggers[rpz_num].nsdname;
		have = &rpzs->have.nsdname;
		break;
	case DNS_RPZ_TYPE_NSIP:
		REQUIRE(tgt_ip != NULL);
		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
			cnt = &rpzs->triggers[rpz_num].nsipv4;
			have = &rpzs->have.nsipv4;
		} else {
			cnt = &rpzs->triggers[rpz_num].nsipv6;
			have = &rpzs->have.nsipv6;
		}
		break;
	default:
		UNREACHABLE();
	}

	if (inc) {
		if (++*cnt == 1U) {
			*have |= DNS_RPZ_ZBIT(rpz_num);
			fix_qname_skip_recurse(rpzs);
		}
	} else {
		REQUIRE(*cnt != 0U);
		if (--*cnt == 0U) {
			*have &= ~DNS_RPZ_ZBIT(rpz_num);
			fix_qname_skip_recurse(rpzs);
		}
	}
}

static dns_rpz_cidr_node_t *
new_node(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *ip,
	 dns_rpz_prefix_t prefix, const dns_rpz_cidr_node_t *child) {
	dns_rpz_cidr_node_t *node;
	int i, words, wlen;

	node = isc_mem_get(rpzs->mctx, sizeof(*node));
	memset(node, 0, sizeof(*node));

	if (child != NULL) {
		node->sum = child->sum;
	}

	node->prefix = prefix;
	words = prefix / DNS_RPZ_CIDR_WORD_BITS;
	wlen = prefix % DNS_RPZ_CIDR_WORD_BITS;
	i = 0;
	while (i < words) {
		node->ip.w[i] = ip->w[i];
		++i;
	}
	if (wlen != 0) {
		node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen);
		++i;
	}
	while (i < DNS_RPZ_CIDR_WORDS) {
		node->ip.w[i++] = 0;
	}

	return (node);
}

static void
badname(int level, const dns_name_t *name, const char *str1, const char *str2) {
	char namebuf[DNS_NAME_FORMATSIZE];

	/*
	 * bin/tests/system/rpz/tests.sh looks for "invalid rpz".
	 */
	if (level < DNS_RPZ_DEBUG_QUIET && isc_log_wouldlog(dns_lctx, level)) {
		dns_name_format(name, namebuf, sizeof(namebuf));
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, level,
			      "invalid rpz IP address \"%s\"%s%s", namebuf,
			      str1, str2);
	}
}

/*
 * Convert an IP address from radix tree binary (host byte order) to
 * to its canonical response policy domain name without the origin of the
 * policy zone.
 *
 * Generate a name for an IPv6 address that fits RFC 5952, except that our
 * reversed format requires that when the length of the consecutive 16-bit
 * 0 fields are equal (e.g., 1.0.0.1.0.0.db8.2001 corresponding to
 * 2001:db8:0:0:1:0:0:1), we shorted the last instead of the first
 * (e.g., 1.0.0.1.zz.db8.2001 corresponding to 2001:db8::1:0:0:1).
 */
static isc_result_t
ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
	const dns_name_t *base_name, dns_name_t *ip_name) {
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif /* ifndef INET6_ADDRSTRLEN */
	int w[DNS_RPZ_CIDR_WORDS * 2];
	char str[1 + 8 + 1 + INET6_ADDRSTRLEN + 1];
	isc_buffer_t buffer;
	isc_result_t result;
	int best_first, best_len, cur_first, cur_len;
	int i, n, len;

	if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
		len = snprintf(str, sizeof(str), "%u.%u.%u.%u.%u",
			       tgt_prefix - 96U, tgt_ip->w[3] & 0xffU,
			       (tgt_ip->w[3] >> 8) & 0xffU,
			       (tgt_ip->w[3] >> 16) & 0xffU,
			       (tgt_ip->w[3] >> 24) & 0xffU);
		if (len < 0 || (size_t)len >= sizeof(str)) {
			return (ISC_R_FAILURE);
		}
	} else {
		len = snprintf(str, sizeof(str), "%d", tgt_prefix);
		if (len < 0 || (size_t)len >= sizeof(str)) {
			return (ISC_R_FAILURE);
		}

		for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) {
			w[i * 2 + 1] =
				((tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] >> 16) &
				 0xffff);
			w[i * 2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] &
				   0xffff;
		}
		/*
		 * Find the start and length of the first longest sequence
		 * of zeros in the address.
		 */
		best_first = -1;
		best_len = 0;
		cur_first = -1;
		cur_len = 0;
		for (n = 0; n <= 7; ++n) {
			if (w[n] != 0) {
				cur_len = 0;
				cur_first = -1;
			} else {
				++cur_len;
				if (cur_first < 0) {
					cur_first = n;
				} else if (cur_len >= best_len) {
					best_first = cur_first;
					best_len = cur_len;
				}
			}
		}

		for (n = 0; n <= 7; ++n) {
			INSIST(len > 0 && (size_t)len < sizeof(str));
			if (n == best_first) {
				i = snprintf(str + len, sizeof(str) - len,
					     ".zz");
				n += best_len - 1;
			} else {
				i = snprintf(str + len, sizeof(str) - len,
					     ".%x", w[n]);
			}
			if (i < 0 || (size_t)i >= (size_t)(sizeof(str) - len)) {
				return (ISC_R_FAILURE);
			}
			len += i;
		}
	}

	isc_buffer_init(&buffer, str, sizeof(str));
	isc_buffer_add(&buffer, len);
	result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL);
	return (result);
}

/*
 * Determine the type of a name in a response policy zone.
 */
static dns_rpz_type_t
type_from_name(const dns_rpz_zones_t *rpzs, dns_rpz_zone_t *rpz,
	       const dns_name_t *name) {
	if (dns_name_issubdomain(name, &rpz->ip)) {
		return (DNS_RPZ_TYPE_IP);
	}

	if (dns_name_issubdomain(name, &rpz->client_ip)) {
		return (DNS_RPZ_TYPE_CLIENT_IP);
	}

	if ((rpzs->p.nsip_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
	    dns_name_issubdomain(name, &rpz->nsip))
	{
		return (DNS_RPZ_TYPE_NSIP);
	}

	if ((rpzs->p.nsdname_on & DNS_RPZ_ZBIT(rpz->num)) != 0 &&
	    dns_name_issubdomain(name, &rpz->nsdname))
	{
		return (DNS_RPZ_TYPE_NSDNAME);
	}

	return (DNS_RPZ_TYPE_QNAME);
}

/*
 * Convert an IP address from canonical response policy domain name form
 * to radix tree binary (host byte order) for adding or deleting IP or NSIP
 * data.
 */
static isc_result_t
name2ipkey(int log_level, const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
	   dns_rpz_type_t rpz_type, const dns_name_t *src_name,
	   dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t *tgt_prefix,
	   dns_rpz_addr_zbits_t *new_set) {
	dns_rpz_zone_t *rpz;
	char ip_str[DNS_NAME_FORMATSIZE], ip2_str[DNS_NAME_FORMATSIZE];
	dns_offsets_t ip_name_offsets;
	dns_fixedname_t ip_name2f;
	dns_name_t ip_name, *ip_name2;
	const char *prefix_str, *cp, *end;
	char *cp2;
	int ip_labels;
	dns_rpz_prefix_t prefix;
	unsigned long prefix_num, l;
	isc_result_t result;
	int i;

	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
	rpz = rpzs->zones[rpz_num];
	REQUIRE(rpz != NULL);

	make_addr_set(new_set, DNS_RPZ_ZBIT(rpz_num), rpz_type);

	ip_labels = dns_name_countlabels(src_name);
	if (rpz_type == DNS_RPZ_TYPE_QNAME) {
		ip_labels -= dns_name_countlabels(&rpz->origin);
	} else {
		ip_labels -= dns_name_countlabels(&rpz->nsdname);
	}
	if (ip_labels < 2) {
		badname(log_level, src_name, "; too short", "");
		return (ISC_R_FAILURE);
	}
	dns_name_init(&ip_name, ip_name_offsets);
	dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name);

	/*
	 * Get text for the IP address
	 */
	dns_name_format(&ip_name, ip_str, sizeof(ip_str));
	end = &ip_str[strlen(ip_str) + 1];
	prefix_str = ip_str;

	prefix_num = strtoul(prefix_str, &cp2, 10);
	if (*cp2 != '.') {
		badname(log_level, src_name, "; invalid leading prefix length",
			"");
		return (ISC_R_FAILURE);
	}
	/*
	 * Patch in trailing nul character to print just the length
	 * label (for various cases below).
	 */
	*cp2 = '\0';
	if (prefix_num < 1U || prefix_num > 128U) {
		badname(log_level, src_name, "; invalid prefix length of ",
			prefix_str);
		return (ISC_R_FAILURE);
	}
	cp = cp2 + 1;

	if (--ip_labels == 4 && !strchr(cp, 'z')) {
		/*
		 * Convert an IPv4 address
		 * from the form "prefix.z.y.x.w"
		 */
		if (prefix_num > 32U) {
			badname(log_level, src_name,
				"; invalid IPv4 prefix length of ", prefix_str);
			return (ISC_R_FAILURE);
		}
		prefix_num += 96;
		*tgt_prefix = (dns_rpz_prefix_t)prefix_num;
		tgt_ip->w[0] = 0;
		tgt_ip->w[1] = 0;
		tgt_ip->w[2] = ADDR_V4MAPPED;
		tgt_ip->w[3] = 0;
		for (i = 0; i < 32; i += 8) {
			l = strtoul(cp, &cp2, 10);
			if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) {
				if (*cp2 == '.') {
					*cp2 = '\0';
				}
				badname(log_level, src_name,
					"; invalid IPv4 octet ", cp);
				return (ISC_R_FAILURE);
			}
			tgt_ip->w[3] |= l << i;
			cp = cp2 + 1;
		}
	} else {
		/*
		 * Convert a text IPv6 address.
		 */
		*tgt_prefix = (dns_rpz_prefix_t)prefix_num;
		for (i = 0; ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2;
		     ip_labels--)
		{
			if (cp[0] == 'z' && cp[1] == 'z' &&
			    (cp[2] == '.' || cp[2] == '\0') && i <= 6)
			{
				do {
					if ((i & 1) == 0) {
						tgt_ip->w[3 - i / 2] = 0;
					}
					++i;
				} while (ip_labels + i <= 8);
				cp += 3;
			} else {
				l = strtoul(cp, &cp2, 16);
				if (l > 0xffffu ||
				    (*cp2 != '.' && *cp2 != '\0'))
				{
					if (*cp2 == '.') {
						*cp2 = '\0';
					}
					badname(log_level, src_name,
						"; invalid IPv6 word ", cp);
					return (ISC_R_FAILURE);
				}
				if ((i & 1) == 0) {
					tgt_ip->w[3 - i / 2] = l;
				} else {
					tgt_ip->w[3 - i / 2] |= l << 16;
				}
				i++;
				cp = cp2 + 1;
			}
		}
	}
	if (cp != end) {
		badname(log_level, src_name, "", "");
		return (ISC_R_FAILURE);
	}

	/*
	 * Check for 1s after the prefix length.
	 */
	prefix = (dns_rpz_prefix_t)prefix_num;
	while (prefix < DNS_RPZ_CIDR_KEY_BITS) {
		dns_rpz_cidr_word_t aword;

		i = prefix % DNS_RPZ_CIDR_WORD_BITS;
		aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS];
		if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) {
			badname(log_level, src_name,
				"; too small prefix length of ", prefix_str);
			return (ISC_R_FAILURE);
		}
		prefix -= i;
		prefix += DNS_RPZ_CIDR_WORD_BITS;
	}

	/*
	 * Complain about bad names but be generous and accept them.
	 */
	if (log_level < DNS_RPZ_DEBUG_QUIET &&
	    isc_log_wouldlog(dns_lctx, log_level))
	{
		/*
		 * Convert the address back to a canonical domain name
		 * to ensure that the original name is in canonical form.
		 */
		ip_name2 = dns_fixedname_initname(&ip_name2f);
		result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL,
				 ip_name2);
		if (result != ISC_R_SUCCESS ||
		    !dns_name_equal(&ip_name, ip_name2))
		{
			dns_name_format(ip_name2, ip2_str, sizeof(ip2_str));
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
				      DNS_LOGMODULE_RBTDB, log_level,
				      "rpz IP address \"%s\""
				      " is not the canonical \"%s\"",
				      ip_str, ip2_str);
		}
	}

	return (ISC_R_SUCCESS);
}

/*
 * Get trigger name and data bits for adding or deleting summary NSDNAME
 * or QNAME data.
 */
static void
name2data(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
	  const dns_name_t *src_name, dns_name_t *trig_name,
	  dns_rpz_nm_data_t *new_data) {
	dns_rpz_zone_t *rpz;
	dns_offsets_t tmp_name_offsets;
	dns_name_t tmp_name;
	unsigned int prefix_len, n;

	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
	rpz = rpzs->zones[rpz_num];
	REQUIRE(rpz != NULL);

	/*
	 * Handle wildcards by putting only the parent into the
	 * summary RBT.  The summary database only causes a check of the
	 * real policy zone where wildcards will be handled.
	 */
	if (dns_name_iswildcard(src_name)) {
		prefix_len = 1;
		memset(&new_data->set, 0, sizeof(new_data->set));
		make_nm_set(&new_data->wild, rpz_num, rpz_type);
	} else {
		prefix_len = 0;
		make_nm_set(&new_data->set, rpz_num, rpz_type);
		memset(&new_data->wild, 0, sizeof(new_data->wild));
	}

	dns_name_init(&tmp_name, tmp_name_offsets);
	n = dns_name_countlabels(src_name);
	n -= prefix_len;
	if (rpz_type == DNS_RPZ_TYPE_QNAME) {
		n -= dns_name_countlabels(&rpz->origin);
	} else {
		n -= dns_name_countlabels(&rpz->nsdname);
	}
	dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name);
	(void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL);
}

#ifndef HAVE_BUILTIN_CLZ
/**
 * \brief Count Leading Zeros: Find the location of the left-most set
 * bit.
 */
static unsigned int
clz(dns_rpz_cidr_word_t w) {
	unsigned int bit;

	bit = DNS_RPZ_CIDR_WORD_BITS - 1;

	if ((w & 0xffff0000) != 0) {
		w >>= 16;
		bit -= 16;
	}

	if ((w & 0xff00) != 0) {
		w >>= 8;
		bit -= 8;
	}

	if ((w & 0xf0) != 0) {
		w >>= 4;
		bit -= 4;
	}

	if ((w & 0xc) != 0) {
		w >>= 2;
		bit -= 2;
	}

	if ((w & 2) != 0) {
		--bit;
	}

	return (bit);
}
#endif /* ifndef HAVE_BUILTIN_CLZ */

/*
 * Find the first differing bit in two keys (IP addresses).
 */
static int
diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1,
	  const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) {
	dns_rpz_cidr_word_t delta;
	dns_rpz_prefix_t maxbit, bit;
	int i;

	bit = 0;
	maxbit = ISC_MIN(prefix1, prefix2);

	/*
	 * find the first differing words
	 */
	for (i = 0; bit < maxbit; i++, bit += DNS_RPZ_CIDR_WORD_BITS) {
		delta = key1->w[i] ^ key2->w[i];
		if (ISC_UNLIKELY(delta != 0)) {
#ifdef HAVE_BUILTIN_CLZ
			bit += __builtin_clz(delta);
#else  /* ifdef HAVE_BUILTIN_CLZ */
			bit += clz(delta);
#endif /* ifdef HAVE_BUILTIN_CLZ */
			break;
		}
	}
	return (ISC_MIN(bit, maxbit));
}

/*
 * Given a hit while searching the radix trees,
 * clear all bits for higher numbered zones.
 */
static dns_rpz_zbits_t
trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) {
	dns_rpz_zbits_t x;

	/*
	 * Isolate the first or smallest numbered hit bit.
	 * Make a mask of that bit and all smaller numbered bits.
	 */
	x = zbits & found;
	x &= (~x + 1);
	x = (x << 1) - 1;
	zbits &= x;
	return (zbits);
}

/*
 * Search a radix tree for an IP address for ordinary lookup
 *	or for a CIDR block adding or deleting an entry
 *
 * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND,
 *	    and *found=longest match node
 *	or with create==true, ISC_R_EXISTS or ISC_R_NOMEMORY
 */
static isc_result_t
search(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *tgt_ip,
       dns_rpz_prefix_t tgt_prefix, const dns_rpz_addr_zbits_t *tgt_set,
       bool create, dns_rpz_cidr_node_t **found) {
	dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling;
	dns_rpz_addr_zbits_t set;
	int cur_num, child_num;
	dns_rpz_prefix_t dbit;
	isc_result_t find_result;

	set = *tgt_set;
	find_result = ISC_R_NOTFOUND;
	*found = NULL;
	cur = rpzs->cidr;
	parent = NULL;
	cur_num = 0;
	for (;;) {
		if (cur == NULL) {
			/*
			 * No child so we cannot go down.
			 * Quit with whatever we already found
			 * or add the target as a child of the current parent.
			 */
			if (!create) {
				return (find_result);
			}
			child = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
			if (child == NULL) {
				return (ISC_R_NOMEMORY);
			}
			if (parent == NULL) {
				rpzs->cidr = child;
			} else {
				parent->child[cur_num] = child;
			}
			child->parent = parent;
			child->set.client_ip |= tgt_set->client_ip;
			child->set.ip |= tgt_set->ip;
			child->set.nsip |= tgt_set->nsip;
			set_sum_pair(child);
			*found = child;
			return (ISC_R_SUCCESS);
		}

		if ((cur->sum.client_ip & set.client_ip) == 0 &&
		    (cur->sum.ip & set.ip) == 0 &&
		    (cur->sum.nsip & set.nsip) == 0)
		{
			/*
			 * This node has no relevant data
			 * and is in none of the target trees.
			 * Pretend it does not exist if we are not adding.
			 *
			 * If we are adding, continue down to eventually add
			 * a node and mark/put this node in the correct tree.
			 */
			if (!create) {
				return (find_result);
			}
		}

		dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix);
		/*
		 * dbit <= tgt_prefix and dbit <= cur->prefix always.
		 * We are finished searching if we matched all of the target.
		 */
		if (dbit == tgt_prefix) {
			if (tgt_prefix == cur->prefix) {
				/*
				 * The node's key matches the target exactly.
				 */
				if ((cur->set.client_ip & set.client_ip) != 0 ||
				    (cur->set.ip & set.ip) != 0 ||
				    (cur->set.nsip & set.nsip) != 0)
				{
					/*
					 * It is the answer if it has data.
					 */
					*found = cur;
					if (create) {
						find_result = ISC_R_EXISTS;
					} else {
						find_result = ISC_R_SUCCESS;
					}
				} else if (create) {
					/*
					 * The node lacked relevant data,
					 * but will have it now.
					 */
					cur->set.client_ip |=
						tgt_set->client_ip;
					cur->set.ip |= tgt_set->ip;
					cur->set.nsip |= tgt_set->nsip;
					set_sum_pair(cur);
					*found = cur;
					find_result = ISC_R_SUCCESS;
				}
				return (find_result);
			}

			/*
			 * We know tgt_prefix < cur->prefix which means that
			 * the target is shorter than the current node.
			 * Add the target as the current node's parent.
			 */
			if (!create) {
				return (find_result);
			}

			new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur);
			if (new_parent == NULL) {
				return (ISC_R_NOMEMORY);
			}
			new_parent->parent = parent;
			if (parent == NULL) {
				rpzs->cidr = new_parent;
			} else {
				parent->child[cur_num] = new_parent;
			}
			child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix);
			new_parent->child[child_num] = cur;
			cur->parent = new_parent;
			new_parent->set = *tgt_set;
			set_sum_pair(new_parent);
			*found = new_parent;
			return (ISC_R_SUCCESS);
		}

		if (dbit == cur->prefix) {
			if ((cur->set.client_ip & set.client_ip) != 0 ||
			    (cur->set.ip & set.ip) != 0 ||
			    (cur->set.nsip & set.nsip) != 0)
			{
				/*
				 * We have a partial match between of all of the
				 * current node but only part of the target.
				 * Continue searching for other hits in the
				 * same or lower numbered trees.
				 */
				find_result = DNS_R_PARTIALMATCH;
				*found = cur;
				set.client_ip = trim_zbits(set.client_ip,
							   cur->set.client_ip);
				set.ip = trim_zbits(set.ip, cur->set.ip);
				set.nsip = trim_zbits(set.nsip, cur->set.nsip);
			}
			parent = cur;
			cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
			cur = cur->child[cur_num];
			continue;
		}

		/*
		 * dbit < tgt_prefix and dbit < cur->prefix,
		 * so we failed to match both the target and the current node.
		 * Insert a fork of a parent above the current node and
		 * add the target as a sibling of the current node
		 */
		if (!create) {
			return (find_result);
		}

		sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
		if (sibling == NULL) {
			return (ISC_R_NOMEMORY);
		}
		new_parent = new_node(rpzs, tgt_ip, dbit, cur);
		if (new_parent == NULL) {
			isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling));
			return (ISC_R_NOMEMORY);
		}
		new_parent->parent = parent;
		if (parent == NULL) {
			rpzs->cidr = new_parent;
		} else {
			parent->child[cur_num] = new_parent;
		}
		child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
		new_parent->child[child_num] = sibling;
		new_parent->child[1 - child_num] = cur;
		cur->parent = new_parent;
		sibling->parent = new_parent;
		sibling->set = *tgt_set;
		set_sum_pair(sibling);
		*found = sibling;
		return (ISC_R_SUCCESS);
	}
}

/*
 * Add an IP address to the radix tree.
 */
static isc_result_t
add_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
	 const dns_name_t *src_name) {
	dns_rpz_cidr_key_t tgt_ip;
	dns_rpz_prefix_t tgt_prefix;
	dns_rpz_addr_zbits_t set;
	dns_rpz_cidr_node_t *found;
	isc_result_t result;

	result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, rpz_type,
			    src_name, &tgt_ip, &tgt_prefix, &set);
	/*
	 * Log complaints about bad owner names but let the zone load.
	 */
	if (result != ISC_R_SUCCESS) {
		return (ISC_R_SUCCESS);
	}

	result = search(rpzs, &tgt_ip, tgt_prefix, &set, true, &found);
	if (result != ISC_R_SUCCESS) {
		char namebuf[DNS_NAME_FORMATSIZE];

		/*
		 * Do not worry if the radix tree already exists,
		 * because diff_apply() likes to add nodes before deleting.
		 */
		if (result == ISC_R_EXISTS) {
			return (ISC_R_SUCCESS);
		}

		/*
		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
		 */
		dns_name_format(src_name, namebuf, sizeof(namebuf));
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
			      "rpz add_cidr(%s) failed: %s", namebuf,
			      isc_result_totext(result));
		return (result);
	}

	adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, true);
	return (result);
}

static isc_result_t
add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name,
       const dns_rpz_nm_data_t *new_data) {
	dns_rbtnode_t *nmnode;
	dns_rpz_nm_data_t *nm_data;
	isc_result_t result;

	nmnode = NULL;
	result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode);
	switch (result) {
	case ISC_R_SUCCESS:
	case ISC_R_EXISTS:
		nm_data = nmnode->data;
		if (nm_data == NULL) {
			nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data));
			*nm_data = *new_data;
			nmnode->data = nm_data;
			return (ISC_R_SUCCESS);
		}
		break;
	default:
		return (result);
	}

	/*
	 * Do not count bits that are already present
	 */
	if ((nm_data->set.qname & new_data->set.qname) != 0 ||
	    (nm_data->set.ns & new_data->set.ns) != 0 ||
	    (nm_data->wild.qname & new_data->wild.qname) != 0 ||
	    (nm_data->wild.ns & new_data->wild.ns) != 0)
	{
		return (ISC_R_EXISTS);
	}

	nm_data->set.qname |= new_data->set.qname;
	nm_data->set.ns |= new_data->set.ns;
	nm_data->wild.qname |= new_data->wild.qname;
	nm_data->wild.ns |= new_data->wild.ns;
	return (ISC_R_SUCCESS);
}

static isc_result_t
add_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
	 const dns_name_t *src_name) {
	dns_rpz_nm_data_t new_data;
	dns_fixedname_t trig_namef;
	dns_name_t *trig_name;
	isc_result_t result;

	/*
	 * We need a summary database of names even with 1 policy zone,
	 * because wildcard triggers are handled differently.
	 */

	trig_name = dns_fixedname_initname(&trig_namef);
	name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data);

	result = add_nm(rpzs, trig_name, &new_data);

	/*
	 * Do not worry if the node already exists,
	 * because diff_apply() likes to add nodes before deleting.
	 */
	if (result == ISC_R_EXISTS) {
		return (ISC_R_SUCCESS);
	}
	if (result == ISC_R_SUCCESS) {
		adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, true);
	}
	return (result);
}

/*
 * Callback to free the data for a node in the summary RBT database.
 */
static void
rpz_node_deleter(void *nm_data, void *mctx) {
	isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t));
}

/*
 * Get ready for a new set of policy zones for a view.
 */
isc_result_t
dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, char *rps_cstr, size_t rps_cstr_size,
		  isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
		  isc_timermgr_t *timermgr) {
	dns_rpz_zones_t *zones;
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(rpzsp != NULL && *rpzsp == NULL);

	zones = isc_mem_get(mctx, sizeof(*zones));
	memset(zones, 0, sizeof(*zones));

	isc_rwlock_init(&zones->search_lock, 0, 0);
	isc_mutex_init(&zones->maint_lock);
	isc_refcount_init(&zones->refs, 1);
	isc_refcount_init(&zones->irefs, 1);

	zones->rps_cstr = rps_cstr;
	zones->rps_cstr_size = rps_cstr_size;
#ifdef USE_DNSRPS
	if (rps_cstr != NULL) {
		result = dns_dnsrps_view_init(zones, rps_cstr);
	}
#else  /* ifdef USE_DNSRPS */
	INSIST(!zones->p.dnsrps_enabled);
#endif /* ifdef USE_DNSRPS */
	if (result == ISC_R_SUCCESS && !zones->p.dnsrps_enabled) {
		result = dns_rbt_create(mctx, rpz_node_deleter, mctx,
					&zones->rbt);
	}

	if (result != ISC_R_SUCCESS) {
		goto cleanup_rbt;
	}

	result = isc_task_create(taskmgr, 0, &zones->updater);
	if (result != ISC_R_SUCCESS) {
		goto cleanup_task;
	}

	isc_mem_attach(mctx, &zones->mctx);
	zones->timermgr = timermgr;
	zones->taskmgr = taskmgr;

	*rpzsp = zones;
	return (ISC_R_SUCCESS);

cleanup_task:
	dns_rbt_destroy(&zones->rbt);

cleanup_rbt:
	isc_refcount_decrementz(&zones->irefs);
	isc_refcount_destroy(&zones->irefs);
	isc_refcount_decrementz(&zones->refs);
	isc_refcount_destroy(&zones->refs);
	isc_mutex_destroy(&zones->maint_lock);
	isc_rwlock_destroy(&zones->search_lock);
	isc_mem_put(mctx, zones, sizeof(*zones));

	return (result);
}

isc_result_t
dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) {
	dns_rpz_zone_t *zone;
	isc_result_t result;

	REQUIRE(rpzp != NULL && *rpzp == NULL);
	REQUIRE(rpzs != NULL);
	if (rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) {
		return (ISC_R_NOSPACE);
	}

	zone = isc_mem_get(rpzs->mctx, sizeof(*zone));

	memset(zone, 0, sizeof(*zone));
	isc_refcount_init(&zone->refs, 1);

	result = isc_timer_create(rpzs->timermgr, isc_timertype_inactive, NULL,
				  NULL, rpzs->updater,
				  dns_rpz_update_taskaction, zone,
				  &zone->updatetimer);
	if (result != ISC_R_SUCCESS) {
		goto cleanup_timer;
	}

	/*
	 * This will never be used, but costs us nothing and
	 * simplifies update_from_db
	 */

	isc_ht_init(&zone->nodes, rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE);

	dns_name_init(&zone->origin, NULL);
	dns_name_init(&zone->client_ip, NULL);
	dns_name_init(&zone->ip, NULL);
	dns_name_init(&zone->nsdname, NULL);
	dns_name_init(&zone->nsip, NULL);
	dns_name_init(&zone->passthru, NULL);
	dns_name_init(&zone->drop, NULL);
	dns_name_init(&zone->tcp_only, NULL);
	dns_name_init(&zone->cname, NULL);

	isc_time_settoepoch(&zone->lastupdated);
	zone->updatepending = false;
	zone->updaterunning = false;
	zone->db = NULL;
	zone->dbversion = NULL;
	zone->updb = NULL;
	zone->updbversion = NULL;
	zone->updbit = NULL;
	isc_refcount_increment(&rpzs->irefs);
	zone->rpzs = rpzs;
	zone->db_registered = false;
	zone->addsoa = true;
	ISC_EVENT_INIT(&zone->updateevent, sizeof(zone->updateevent), 0, NULL,
		       0, NULL, NULL, NULL, NULL, NULL);

	zone->num = rpzs->p.num_zones++;
	rpzs->zones[zone->num] = zone;

	*rpzp = zone;

	return (ISC_R_SUCCESS);

cleanup_timer:
	isc_refcount_decrementz(&zone->refs);
	isc_refcount_destroy(&zone->refs);

	isc_mem_put(rpzs->mctx, zone, sizeof(*zone));

	return (result);
}

isc_result_t
dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg) {
	dns_rpz_zone_t *zone = (dns_rpz_zone_t *)fn_arg;
	isc_time_t now;
	uint64_t tdiff;
	isc_result_t result = ISC_R_SUCCESS;
	char dname[DNS_NAME_FORMATSIZE];

	REQUIRE(DNS_DB_VALID(db));
	REQUIRE(zone != NULL);

	LOCK(&zone->rpzs->maint_lock);

	/* New zone came as AXFR */
	if (zone->db != NULL && zone->db != db) {
		/* We need to clean up the old DB */
		if (zone->dbversion != NULL) {
			dns_db_closeversion(zone->db, &zone->dbversion, false);
		}
		dns_db_updatenotify_unregister(zone->db,
					       dns_rpz_dbupdate_callback, zone);
		dns_db_detach(&zone->db);
	}

	if (zone->db == NULL) {
		RUNTIME_CHECK(zone->dbversion == NULL);
		dns_db_attach(db, &zone->db);
	}

	if (!zone->updatepending && !zone->updaterunning) {
		zone->updatepending = true;
		isc_time_now(&now);
		tdiff = isc_time_microdiff(&now, &zone->lastupdated) / 1000000;
		if (tdiff < zone->min_update_interval) {
			uint64_t defer = zone->min_update_interval - tdiff;
			isc_interval_t interval;
			dns_name_format(&zone->origin, dname,
					DNS_NAME_FORMATSIZE);
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
				      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
				      "rpz: %s: new zone version came "
				      "too soon, deferring update for "
				      "%" PRIu64 " seconds",
				      dname, defer);
			isc_interval_set(&interval, (unsigned int)defer, 0);
			dns_db_currentversion(zone->db, &zone->dbversion);
			result = isc_timer_reset(zone->updatetimer,
						 isc_timertype_once, NULL,
						 &interval, true);
			if (result != ISC_R_SUCCESS) {
				goto cleanup;
			}
		} else {
			isc_event_t *event;

			dns_db_currentversion(zone->db, &zone->dbversion);
			INSIST(!ISC_LINK_LINKED(&zone->updateevent, ev_link));
			ISC_EVENT_INIT(&zone->updateevent,
				       sizeof(zone->updateevent), 0, NULL,
				       DNS_EVENT_RPZUPDATED,
				       dns_rpz_update_taskaction, zone, zone,
				       NULL, NULL);
			event = &zone->updateevent;
			isc_task_send(zone->rpzs->updater, &event);
		}
	} else {
		zone->updatepending = true;
		dns_name_format(&zone->origin, dname, DNS_NAME_FORMATSIZE);
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3),
			      "rpz: %s: update already queued or running",
			      dname);
		if (zone->dbversion != NULL) {
			dns_db_closeversion(zone->db, &zone->dbversion, false);
		}
		dns_db_currentversion(zone->db, &zone->dbversion);
	}

cleanup:
	UNLOCK(&zone->rpzs->maint_lock);

	return (result);
}

static void
dns_rpz_update_taskaction(isc_task_t *task, isc_event_t *event) {
	isc_result_t result;
	dns_rpz_zone_t *zone;

	REQUIRE(event != NULL);
	REQUIRE(event->ev_arg != NULL);

	UNUSED(task);
	zone = (dns_rpz_zone_t *)event->ev_arg;
	isc_event_free(&event);
	LOCK(&zone->rpzs->maint_lock);
	zone->updatepending = false;
	zone->updaterunning = true;
	dns_rpz_update_from_db(zone);
	result = isc_timer_reset(zone->updatetimer, isc_timertype_inactive,
				 NULL, NULL, true);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);
	result = isc_time_now(&zone->lastupdated);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);
	UNLOCK(&zone->rpzs->maint_lock);
}

static isc_result_t
setup_update(dns_rpz_zone_t *rpz) {
	isc_result_t result;
	char domain[DNS_NAME_FORMATSIZE];
	unsigned int nodecount;
	uint32_t hashsize;

	dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
		      ISC_LOG_INFO, "rpz: %s: reload start", domain);

	nodecount = dns_db_nodecount(rpz->updb);
	hashsize = 1;
	while (nodecount != 0 &&
	       hashsize <= (DNS_RPZ_HTSIZE_MAX + DNS_RPZ_HTSIZE_DIV))
	{
		hashsize++;
		nodecount >>= 1;
	}

	if (hashsize > DNS_RPZ_HTSIZE_DIV) {
		hashsize -= DNS_RPZ_HTSIZE_DIV;
	}

	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER,
		      ISC_LOG_DEBUG(1), "rpz: %s: using hashtable size %d",
		      domain, hashsize);

	isc_ht_init(&rpz->newnodes, rpz->rpzs->mctx, hashsize,
		    ISC_HT_CASE_SENSITIVE);

	result = dns_db_createiterator(rpz->updb, DNS_DB_NONSEC3, &rpz->updbit);
	if (result != ISC_R_SUCCESS) {
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
			      "rpz: %s: failed to create DB iterator - %s",
			      domain, isc_result_totext(result));
		goto cleanup;
	}

	result = dns_dbiterator_first(rpz->updbit);
	if (result != ISC_R_SUCCESS) {
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
			      "rpz: %s: failed to get db iterator - %s", domain,
			      isc_result_totext(result));
		goto cleanup;
	}

	result = dns_dbiterator_pause(rpz->updbit);
	if (result != ISC_R_SUCCESS) {
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
			      "rpz: %s: failed to pause db iterator - %s",
			      domain, isc_result_totext(result));
		goto cleanup;
	}

cleanup:
	if (result != ISC_R_SUCCESS) {
		if (rpz->updbit != NULL) {
			dns_dbiterator_destroy(&rpz->updbit);
		}
		if (rpz->newnodes != NULL) {
			isc_ht_destroy(&rpz->newnodes);
		}
		dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
	}

	return (result);
}

static void
finish_update(dns_rpz_zone_t *rpz) {
	LOCK(&rpz->rpzs->maint_lock);
	rpz->updaterunning = false;

	/*
	 * If there's an update pending, schedule it.
	 */
	if (rpz->updatepending) {
		if (rpz->min_update_interval > 0) {
			uint64_t defer = rpz->min_update_interval;
			char dname[DNS_NAME_FORMATSIZE];
			isc_interval_t interval;

			dns_name_format(&rpz->origin, dname,
					DNS_NAME_FORMATSIZE);
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
				      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
				      "rpz: %s: new zone version came "
				      "too soon, deferring update for "
				      "%" PRIu64 " seconds",
				      dname, defer);
			isc_interval_set(&interval, (unsigned int)defer, 0);
			isc_timer_reset(rpz->updatetimer, isc_timertype_once,
					NULL, &interval, true);
		} else {
			isc_event_t *event = NULL;
			INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
			ISC_EVENT_INIT(&rpz->updateevent,
				       sizeof(rpz->updateevent), 0, NULL,
				       DNS_EVENT_RPZUPDATED,
				       dns_rpz_update_taskaction, rpz, rpz,
				       NULL, NULL);
			event = &rpz->updateevent;
			isc_task_send(rpz->rpzs->updater, &event);
		}
	}
	UNLOCK(&rpz->rpzs->maint_lock);
}

static void
cleanup_quantum(isc_task_t *task, isc_event_t *event) {
	isc_result_t result = ISC_R_SUCCESS;
	char domain[DNS_NAME_FORMATSIZE];
	dns_rpz_zone_t *rpz = NULL;
	isc_ht_iter_t *iter = NULL;
	dns_fixedname_t fname;
	dns_name_t *name = NULL;
	int count = 0;

	UNUSED(task);

	REQUIRE(event != NULL);
	REQUIRE(event->ev_sender != NULL);

	rpz = (dns_rpz_zone_t *)event->ev_sender;
	iter = (isc_ht_iter_t *)event->ev_arg;
	isc_event_free(&event);

	if (iter == NULL) {
		/*
		 * Iterate over old ht with existing nodes deleted to
		 * delete deleted nodes from RPZ
		 */
		isc_ht_iter_create(rpz->nodes, &iter);
	}

	name = dns_fixedname_initname(&fname);

	LOCK(&rpz->rpzs->maint_lock);

	/* Check that we aren't shutting down. */
	if (rpz->rpzs->zones[rpz->num] == NULL) {
		UNLOCK(&rpz->rpzs->maint_lock);
		goto cleanup;
	}

	for (result = isc_ht_iter_first(iter);
	     result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM;
	     result = isc_ht_iter_delcurrent_next(iter))
	{
		isc_region_t region;
		unsigned char *key = NULL;
		size_t keysize;

		isc_ht_iter_currentkey(iter, &key, &keysize);
		region.base = key;
		region.length = (unsigned int)keysize;
		dns_name_fromregion(name, &region);
		dns_rpz_delete(rpz->rpzs, rpz->num, name);
	}

	if (result == ISC_R_SUCCESS) {
		isc_event_t *nevent = NULL;

		/*
		 * We finished a quantum; trigger the next one and return.
		 */

		INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
		ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
			       NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum,
			       iter, rpz, NULL, NULL);
		nevent = &rpz->updateevent;
		isc_task_send(rpz->rpzs->updater, &nevent);
		UNLOCK(&rpz->rpzs->maint_lock);
		return;
	} else if (result == ISC_R_NOMORE) {
		isc_ht_t *tmpht = NULL;

		/*
		 * Done with cleanup of deleted nodes; finalize
		 * the update.
		 */
		tmpht = rpz->nodes;
		rpz->nodes = rpz->newnodes;
		rpz->newnodes = tmpht;

		UNLOCK(&rpz->rpzs->maint_lock);
		finish_update(rpz);
		dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
			      DNS_LOGMODULE_MASTER, ISC_LOG_INFO,
			      "rpz: %s: reload done", domain);
	} else {
		UNLOCK(&rpz->rpzs->maint_lock);
	}

	/*
	 * If we're here, we're finished or something went wrong.
	 */
cleanup:
	if (iter != NULL) {
		isc_ht_iter_destroy(&iter);
	}
	if (rpz->newnodes != NULL) {
		isc_ht_destroy(&rpz->newnodes);
	}
	dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
	dns_db_detach(&rpz->updb);
	rpz_detach(&rpz);
}

static void
update_quantum(isc_task_t *task, isc_event_t *event) {
	isc_result_t result = ISC_R_SUCCESS;
	dns_dbnode_t *node = NULL;
	dns_rpz_zone_t *rpz = NULL;
	char domain[DNS_NAME_FORMATSIZE];
	dns_fixedname_t fixname;
	dns_name_t *name = NULL;
	isc_event_t *nevent = NULL;
	int count = 0;

	UNUSED(task);

	REQUIRE(event != NULL);
	REQUIRE(event->ev_arg != NULL);

	rpz = (dns_rpz_zone_t *)event->ev_arg;
	isc_event_free(&event);

	REQUIRE(rpz->updbit != NULL);
	REQUIRE(rpz->newnodes != NULL);

	name = dns_fixedname_initname(&fixname);

	dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE);

	LOCK(&rpz->rpzs->maint_lock);

	/* Check that we aren't shutting down. */
	if (rpz->rpzs->zones[rpz->num] == NULL) {
		UNLOCK(&rpz->rpzs->maint_lock);
		goto cleanup;
	}

	while (result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM) {
		char namebuf[DNS_NAME_FORMATSIZE];
		dns_rdatasetiter_t *rdsiter = NULL;

		result = dns_dbiterator_current(rpz->updbit, &node, name);
		if (result != ISC_R_SUCCESS) {
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
				      "rpz: %s: failed to get dbiterator - %s",
				      domain, isc_result_totext(result));
			dns_db_detachnode(rpz->updb, &node);
			break;
		}

		result = dns_db_allrdatasets(rpz->updb, node, rpz->updbversion,
					     0, 0, &rdsiter);
		if (result != ISC_R_SUCCESS) {
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
				      "rpz: %s: failed to fetch "
				      "rrdatasets - %s",
				      domain, isc_result_totext(result));
			dns_db_detachnode(rpz->updb, &node);
			break;
		}

		result = dns_rdatasetiter_first(rdsiter);
		dns_rdatasetiter_destroy(&rdsiter);
		if (result != ISC_R_SUCCESS) { /* empty non-terminal */
			if (result != ISC_R_NOMORE) {
				isc_log_write(
					dns_lctx, DNS_LOGCATEGORY_GENERAL,
					DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
					"rpz: %s: error %s while creating "
					"rdatasetiter",
					domain, isc_result_totext(result));
			}
			dns_db_detachnode(rpz->updb, &node);
			result = dns_dbiterator_next(rpz->updbit);
			continue;
		}

		dns_name_downcase(name, name, NULL);
		result = isc_ht_add(rpz->newnodes, name->ndata, name->length,
				    rpz);
		if (result != ISC_R_SUCCESS) {
			dns_name_format(name, namebuf, sizeof(namebuf));
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
				      DNS_LOGMODULE_MASTER, ISC_LOG_ERROR,
				      "rpz: %s, adding node %s to HT error %s",
				      domain, namebuf,
				      isc_result_totext(result));
			dns_db_detachnode(rpz->updb, &node);
			result = dns_dbiterator_next(rpz->updbit);
			continue;
		}

		result = isc_ht_find(rpz->nodes, name->ndata, name->length,
				     NULL);
		if (result == ISC_R_SUCCESS) {
			isc_ht_delete(rpz->nodes, name->ndata, name->length);
		} else { /* not found */
			result = dns_rpz_add(rpz->rpzs, rpz->num, name);
			if (result != ISC_R_SUCCESS) {
				dns_name_format(name, namebuf, sizeof(namebuf));
				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
					      DNS_LOGMODULE_MASTER,
					      ISC_LOG_ERROR,
					      "rpz: %s: adding node %s "
					      "to RPZ error %s",
					      domain, namebuf,
					      isc_result_totext(result));
			} else {
				dns_name_format(name, namebuf, sizeof(namebuf));
				isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
					      DNS_LOGMODULE_MASTER,
					      ISC_LOG_DEBUG(3),
					      "rpz: %s: adding node %s", domain,
					      namebuf);
			}
		}

		dns_db_detachnode(rpz->updb, &node);
		result = dns_dbiterator_next(rpz->updbit);
	}

	if (result == ISC_R_SUCCESS) {
		/*
		 * Pause the iterator so that the DB is not locked.
		 */
		dns_dbiterator_pause(rpz->updbit);

		/*
		 * We finished a quantum; trigger the next one and return.
		 */
		INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
		ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
			       NULL, DNS_EVENT_RPZUPDATED, update_quantum, rpz,
			       rpz, NULL, NULL);
		nevent = &rpz->updateevent;
		isc_task_send(rpz->rpzs->updater, &nevent);
		UNLOCK(&rpz->rpzs->maint_lock);
		return;
	} else if (result == ISC_R_NOMORE) {
		/*
		 * Done with the new database; now we just need to
		 * clean up the old.
		 */
		dns_dbiterator_destroy(&rpz->updbit);

		INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
		ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0,
			       NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum,
			       NULL, rpz, NULL, NULL);
		nevent = &rpz->updateevent;
		isc_task_send(rpz->rpzs->updater, &nevent);
		UNLOCK(&rpz->rpzs->maint_lock);
		return;
	}

	/*
	 * If we're here, something went wrong, so clean up.
	 */
	UNLOCK(&rpz->rpzs->maint_lock);

cleanup:
	if (rpz->updbit != NULL) {
		dns_dbiterator_destroy(&rpz->updbit);
	}
	if (rpz->newnodes != NULL) {
		isc_ht_destroy(&rpz->newnodes);
	}
	dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
	dns_db_detach(&rpz->updb);
	rpz_detach(&rpz);
}

static void
dns_rpz_update_from_db(dns_rpz_zone_t *rpz) {
	isc_result_t result;
	isc_event_t *event;

	REQUIRE(rpz != NULL);
	REQUIRE(DNS_DB_VALID(rpz->db));
	REQUIRE(rpz->updb == NULL);
	REQUIRE(rpz->updbversion == NULL);
	REQUIRE(rpz->updbit == NULL);
	REQUIRE(rpz->newnodes == NULL);

	isc_refcount_increment(&rpz->refs);
	dns_db_attach(rpz->db, &rpz->updb);
	rpz->updbversion = rpz->dbversion;
	rpz->dbversion = NULL;

	result = setup_update(rpz);
	if (result != ISC_R_SUCCESS) {
		goto cleanup;
	}

	event = &rpz->updateevent;
	INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link));
	ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, NULL,
		       DNS_EVENT_RPZUPDATED, update_quantum, rpz, rpz, NULL,
		       NULL);
	isc_task_send(rpz->rpzs->updater, &event);
	return;

cleanup:
	if (rpz->updbit != NULL) {
		dns_dbiterator_destroy(&rpz->updbit);
	}
	if (rpz->newnodes != NULL) {
		isc_ht_destroy(&rpz->newnodes);
	}
	dns_db_closeversion(rpz->updb, &rpz->updbversion, false);
	dns_db_detach(&rpz->updb);
	rpz_detach(&rpz);
}

/*
 * Free the radix tree of a response policy database.
 */
static void
cidr_free(dns_rpz_zones_t *rpzs) {
	dns_rpz_cidr_node_t *cur, *child, *parent;

	cur = rpzs->cidr;
	while (cur != NULL) {
		/* Depth first. */
		child = cur->child[0];
		if (child != NULL) {
			cur = child;
			continue;
		}
		child = cur->child[1];
		if (child != NULL) {
			cur = child;
			continue;
		}

		/* Delete this leaf and go up. */
		parent = cur->parent;
		if (parent == NULL) {
			rpzs->cidr = NULL;
		} else {
			parent->child[parent->child[1] == cur] = NULL;
		}
		isc_mem_put(rpzs->mctx, cur, sizeof(*cur));
		cur = parent;
	}
}

/*
 * Discard a response policy zone blob
 * before discarding the overall rpz structure.
 */
static void
rpz_detach(dns_rpz_zone_t **rpzp) {
	dns_rpz_zone_t *rpz;
	dns_rpz_zones_t *rpzs;

	REQUIRE(rpzp != NULL && *rpzp != NULL);

	rpz = *rpzp;
	*rpzp = NULL;

	if (isc_refcount_decrement(&rpz->refs) == 1) {
		isc_refcount_destroy(&rpz->refs);

		rpzs = rpz->rpzs;
		rpz->rpzs = NULL;

		if (dns_name_dynamic(&rpz->origin)) {
			dns_name_free(&rpz->origin, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->client_ip)) {
			dns_name_free(&rpz->client_ip, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->ip)) {
			dns_name_free(&rpz->ip, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->nsdname)) {
			dns_name_free(&rpz->nsdname, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->nsip)) {
			dns_name_free(&rpz->nsip, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->passthru)) {
			dns_name_free(&rpz->passthru, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->drop)) {
			dns_name_free(&rpz->drop, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->tcp_only)) {
			dns_name_free(&rpz->tcp_only, rpzs->mctx);
		}
		if (dns_name_dynamic(&rpz->cname)) {
			dns_name_free(&rpz->cname, rpzs->mctx);
		}
		if (rpz->db != NULL) {
			if (rpz->dbversion != NULL) {
				dns_db_closeversion(rpz->db, &rpz->dbversion,
						    false);
			}
			dns_db_updatenotify_unregister(
				rpz->db, dns_rpz_dbupdate_callback, rpz);
			dns_db_detach(&rpz->db);
		}
		if (rpz->updaterunning) {
			isc_task_purgeevent(rpzs->updater, &rpz->updateevent);
			if (rpz->updbit != NULL) {
				dns_dbiterator_destroy(&rpz->updbit);
			}
			if (rpz->newnodes != NULL) {
				isc_ht_destroy(&rpz->newnodes);
			}
			if (rpz->updb != NULL) {
				if (rpz->updbversion != NULL) {
					dns_db_closeversion(rpz->updb,
							    &rpz->updbversion,
							    false);
				}
				dns_db_detach(&rpz->updb);
			}
		}

		isc_timer_reset(rpz->updatetimer, isc_timertype_inactive, NULL,
				NULL, true);
		isc_timer_destroy(&rpz->updatetimer);

		isc_ht_destroy(&rpz->nodes);

		isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz));
		rpz_detach_rpzs(&rpzs);
	}
}

void
dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) {
	REQUIRE(rpzsp != NULL && *rpzsp == NULL);
	isc_refcount_increment(&rpzs->refs);
	*rpzsp = rpzs;
}

/*
 * Forget a view's policy zones.
 */
void
dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) {
	REQUIRE(rpzsp != NULL && *rpzsp != NULL);
	dns_rpz_zones_t *rpzs = *rpzsp;
	*rpzsp = NULL;

	if (isc_refcount_decrement(&rpzs->refs) == 1) {
		LOCK(&rpzs->maint_lock);
		/*
		 * Forget the last of view's rpz machinery after
		 * the last reference.
		 */
		for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES;
		     ++rpz_num)
		{
			dns_rpz_zone_t *rpz = rpzs->zones[rpz_num];
			rpzs->zones[rpz_num] = NULL;
			if (rpz != NULL) {
				rpz_detach(&rpz);
			}
		}
		UNLOCK(&rpzs->maint_lock);
		rpz_detach_rpzs(&rpzs);
	}
}

static void
rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) {
	REQUIRE(rpzsp != NULL && *rpzsp != NULL);
	dns_rpz_zones_t *rpzs = *rpzsp;
	*rpzsp = NULL;

	if (isc_refcount_decrement(&rpzs->irefs) == 1) {
		if (rpzs->rps_cstr_size != 0) {
#ifdef USE_DNSRPS
			librpz->client_detach(&rpzs->rps_client);
#endif /* ifdef USE_DNSRPS */
			isc_mem_put(rpzs->mctx, rpzs->rps_cstr,
				    rpzs->rps_cstr_size);
		}

		cidr_free(rpzs);
		if (rpzs->rbt != NULL) {
			dns_rbt_destroy(&rpzs->rbt);
		}
		isc_task_destroy(&rpzs->updater);
		isc_mutex_destroy(&rpzs->maint_lock);
		isc_rwlock_destroy(&rpzs->search_lock);
		isc_refcount_destroy(&rpzs->refs);
		isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs));
	}
}

/*
 * Deprecated and removed.
 */
isc_result_t
dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, dns_rpz_zones_t *rpzs,
		  dns_rpz_num_t rpz_num) {
	UNUSED(load_rpzsp);
	UNUSED(rpzs);
	UNUSED(rpz_num);

	return (ISC_R_NOTIMPLEMENTED);
}

/*
 * Deprecated and removed.
 */
isc_result_t
dns_rpz_ready(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp,
	      dns_rpz_num_t rpz_num) {
	UNUSED(rpzs);
	UNUSED(load_rpzsp);
	UNUSED(rpz_num);

	return (ISC_R_NOTIMPLEMENTED);
}

/*
 * Add an IP address to the radix tree or a name to the summary database.
 */
isc_result_t
dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
	    const dns_name_t *src_name) {
	dns_rpz_zone_t *rpz;
	dns_rpz_type_t rpz_type;
	isc_result_t result = ISC_R_FAILURE;

	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
	rpz = rpzs->zones[rpz_num];
	REQUIRE(rpz != NULL);
	RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);

	rpz_type = type_from_name(rpzs, rpz, src_name);

	switch (rpz_type) {
	case DNS_RPZ_TYPE_QNAME:
	case DNS_RPZ_TYPE_NSDNAME:
		result = add_name(rpzs, rpz_num, rpz_type, src_name);
		break;
	case DNS_RPZ_TYPE_CLIENT_IP:
	case DNS_RPZ_TYPE_IP:
	case DNS_RPZ_TYPE_NSIP:
		result = add_cidr(rpzs, rpz_num, rpz_type, src_name);
		break;
	case DNS_RPZ_TYPE_BAD:
		break;
	}
	RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);

	return (result);
}

/*
 * Remove an IP address from the radix tree.
 */
static void
del_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
	 const dns_name_t *src_name) {
	isc_result_t result;
	dns_rpz_cidr_key_t tgt_ip;
	dns_rpz_prefix_t tgt_prefix;
	dns_rpz_addr_zbits_t tgt_set;
	dns_rpz_cidr_node_t *tgt, *parent, *child;

	/*
	 * Do not worry about invalid rpz IP address names.  If we
	 * are here, then something relevant was added and so was
	 * valid.  Invalid names here are usually internal RBTDB nodes.
	 */
	result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpzs, rpz_num, rpz_type,
			    src_name, &tgt_ip, &tgt_prefix, &tgt_set);
	if (result != ISC_R_SUCCESS) {
		return;
	}

	result = search(rpzs, &tgt_ip, tgt_prefix, &tgt_set, false, &tgt);
	if (result != ISC_R_SUCCESS) {
		INSIST(result == ISC_R_NOTFOUND ||
		       result == DNS_R_PARTIALMATCH);
		/*
		 * Do not worry about missing summary RBT nodes that probably
		 * correspond to RBTDB nodes that were implicit RBT nodes
		 * that were later added for (often empty) wildcards
		 * and then to the RBTDB deferred cleanup list.
		 */
		return;
	}

	/*
	 * Mark the node and its parents to reflect the deleted IP address.
	 * Do not count bits that are already clear for internal RBTDB nodes.
	 */
	tgt_set.client_ip &= tgt->set.client_ip;
	tgt_set.ip &= tgt->set.ip;
	tgt_set.nsip &= tgt->set.nsip;
	tgt->set.client_ip &= ~tgt_set.client_ip;
	tgt->set.ip &= ~tgt_set.ip;
	tgt->set.nsip &= ~tgt_set.nsip;
	set_sum_pair(tgt);

	adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, false);

	/*
	 * We might need to delete 2 nodes.
	 */
	do {
		/*
		 * The node is now useless if it has no data of its own
		 * and 0 or 1 children.  We are finished if it is not useless.
		 */
		if ((child = tgt->child[0]) != NULL) {
			if (tgt->child[1] != NULL) {
				break;
			}
		} else {
			child = tgt->child[1];
		}
		if (tgt->set.client_ip != 0 || tgt->set.ip != 0 ||
		    tgt->set.nsip != 0)
		{
			break;
		}

		/*
		 * Replace the pointer to this node in the parent with
		 * the remaining child or NULL.
		 */
		parent = tgt->parent;
		if (parent == NULL) {
			rpzs->cidr = child;
		} else {
			parent->child[parent->child[1] == tgt] = child;
		}
		/*
		 * If the child exists fix up its parent pointer.
		 */
		if (child != NULL) {
			child->parent = parent;
		}
		isc_mem_put(rpzs->mctx, tgt, sizeof(*tgt));

		tgt = parent;
	} while (tgt != NULL);
}

static void
del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_rpz_type_t rpz_type,
	 const dns_name_t *src_name) {
	char namebuf[DNS_NAME_FORMATSIZE];
	dns_fixedname_t trig_namef;
	dns_name_t *trig_name;
	dns_rbtnode_t *nmnode;
	dns_rpz_nm_data_t *nm_data, del_data;
	isc_result_t result;
	bool exists;

	/*
	 * We need a summary database of names even with 1 policy zone,
	 * because wildcard triggers are handled differently.
	 */

	trig_name = dns_fixedname_initname(&trig_namef);
	name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data);

	nmnode = NULL;
	result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, 0,
				  NULL, NULL);
	if (result != ISC_R_SUCCESS) {
		/*
		 * Do not worry about missing summary RBT nodes that probably
		 * correspond to RBTDB nodes that were implicit RBT nodes
		 * that were later added for (often empty) wildcards
		 * and then to the RBTDB deferred cleanup list.
		 */
		if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) {
			return;
		}
		dns_name_format(src_name, namebuf, sizeof(namebuf));
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
			      "rpz del_name(%s) node search failed: %s",
			      namebuf, isc_result_totext(result));
		return;
	}

	nm_data = nmnode->data;
	INSIST(nm_data != NULL);

	/*
	 * Do not count bits that next existed for RBT nodes that would we
	 * would not have found in a summary for a single RBTDB tree.
	 */
	del_data.set.qname &= nm_data->set.qname;
	del_data.set.ns &= nm_data->set.ns;
	del_data.wild.qname &= nm_data->wild.qname;
	del_data.wild.ns &= nm_data->wild.ns;

	exists = (del_data.set.qname != 0 || del_data.set.ns != 0 ||
		  del_data.wild.qname != 0 || del_data.wild.ns != 0);

	nm_data->set.qname &= ~del_data.set.qname;
	nm_data->set.ns &= ~del_data.set.ns;
	nm_data->wild.qname &= ~del_data.wild.qname;
	nm_data->wild.ns &= ~del_data.wild.ns;

	if (nm_data->set.qname == 0 && nm_data->set.ns == 0 &&
	    nm_data->wild.qname == 0 && nm_data->wild.ns == 0)
	{
		result = dns_rbt_deletenode(rpzs->rbt, nmnode, false);
		if (result != ISC_R_SUCCESS) {
			/*
			 * bin/tests/system/rpz/tests.sh looks for
			 * "rpz.*failed".
			 */
			dns_name_format(src_name, namebuf, sizeof(namebuf));
			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
				      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
				      "rpz del_name(%s) node delete failed: %s",
				      namebuf, isc_result_totext(result));
		}
	}

	if (exists) {
		adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, false);
	}
}

/*
 * Remove an IP address from the radix tree or a name from the summary database.
 */
void
dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
	       const dns_name_t *src_name) {
	dns_rpz_zone_t *rpz;
	dns_rpz_type_t rpz_type;

	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
	rpz = rpzs->zones[rpz_num];
	REQUIRE(rpz != NULL);

	RWLOCK(&rpzs->search_lock, isc_rwlocktype_write);

	rpz_type = type_from_name(rpzs, rpz, src_name);

	switch (rpz_type) {
	case DNS_RPZ_TYPE_QNAME:
	case DNS_RPZ_TYPE_NSDNAME:
		del_name(rpzs, rpz_num, rpz_type, src_name);
		break;
	case DNS_RPZ_TYPE_CLIENT_IP:
	case DNS_RPZ_TYPE_IP:
	case DNS_RPZ_TYPE_NSIP:
		del_cidr(rpzs, rpz_num, rpz_type, src_name);
		break;
	case DNS_RPZ_TYPE_BAD:
		break;
	}

	RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write);
}

/*
 * Search the summary radix tree to get a relative owner name in a
 * policy zone relevant to a triggering IP address.
 *	rpz_type and zbits limit the search for IP address netaddr
 *	return the policy zone's number or DNS_RPZ_INVALID_NUM
 *	ip_name is the relative owner name found and
 *	*prefixp is its prefix length.
 */
dns_rpz_num_t
dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
		dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
		dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) {
	dns_rpz_cidr_key_t tgt_ip;
	dns_rpz_addr_zbits_t tgt_set;
	dns_rpz_cidr_node_t *found;
	isc_result_t result;
	dns_rpz_num_t rpz_num = 0;
	dns_rpz_have_t have;
	int i;

	RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
	have = rpzs->have;
	RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);

	/*
	 * Convert IP address to CIDR tree key.
	 */
	if (netaddr->family == AF_INET) {
		tgt_ip.w[0] = 0;
		tgt_ip.w[1] = 0;
		tgt_ip.w[2] = ADDR_V4MAPPED;
		tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr);
		switch (rpz_type) {
		case DNS_RPZ_TYPE_CLIENT_IP:
			zbits &= have.client_ipv4;
			break;
		case DNS_RPZ_TYPE_IP:
			zbits &= have.ipv4;
			break;
		case DNS_RPZ_TYPE_NSIP:
			zbits &= have.nsipv4;
			break;
		default:
			UNREACHABLE();
		}
	} else if (netaddr->family == AF_INET6) {
		dns_rpz_cidr_key_t src_ip6;

		/*
		 * Given the int aligned struct in_addr member of netaddr->type
		 * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *,
		 * but some people object.
		 */
		memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w));
		for (i = 0; i < 4; i++) {
			tgt_ip.w[i] = ntohl(src_ip6.w[i]);
		}
		switch (rpz_type) {
		case DNS_RPZ_TYPE_CLIENT_IP:
			zbits &= have.client_ipv6;
			break;
		case DNS_RPZ_TYPE_IP:
			zbits &= have.ipv6;
			break;
		case DNS_RPZ_TYPE_NSIP:
			zbits &= have.nsipv6;
			break;
		default:
			UNREACHABLE();
		}
	} else {
		return (DNS_RPZ_INVALID_NUM);
	}

	if (zbits == 0) {
		return (DNS_RPZ_INVALID_NUM);
	}
	make_addr_set(&tgt_set, zbits, rpz_type);

	RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
	result = search(rpzs, &tgt_ip, 128, &tgt_set, false, &found);
	if (result == ISC_R_NOTFOUND) {
		/*
		 * There are no eligible zones for this IP address.
		 */
		RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
		return (DNS_RPZ_INVALID_NUM);
	}

	/*
	 * Construct the trigger name for the longest matching trigger
	 * in the first eligible zone with a match.
	 */
	*prefixp = found->prefix;
	switch (rpz_type) {
	case DNS_RPZ_TYPE_CLIENT_IP:
		rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip);
		break;
	case DNS_RPZ_TYPE_IP:
		rpz_num = zbit_to_num(found->set.ip & tgt_set.ip);
		break;
	case DNS_RPZ_TYPE_NSIP:
		rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip);
		break;
	default:
		UNREACHABLE();
	}
	result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name);
	RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
	if (result != ISC_R_SUCCESS) {
		/*
		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
		 */
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
			      "rpz ip2name() failed: %s",
			      isc_result_totext(result));
		return (DNS_RPZ_INVALID_NUM);
	}
	return (rpz_num);
}

/*
 * Search the summary radix tree for policy zones with triggers matching
 * a name.
 */
dns_rpz_zbits_t
dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
		  dns_rpz_zbits_t zbits, dns_name_t *trig_name) {
	char namebuf[DNS_NAME_FORMATSIZE];
	dns_rbtnode_t *nmnode;
	const dns_rpz_nm_data_t *nm_data;
	dns_rpz_zbits_t found_zbits;
	dns_rbtnodechain_t chain;
	isc_result_t result;
	int i;

	if (zbits == 0) {
		return (0);
	}

	found_zbits = 0;

	dns_rbtnodechain_init(&chain);

	RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);

	nmnode = NULL;
	result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, &chain,
				  DNS_RBTFIND_EMPTYDATA, NULL, NULL);

	switch (result) {
	case ISC_R_SUCCESS:
		nm_data = nmnode->data;
		if (nm_data != NULL) {
			if (rpz_type == DNS_RPZ_TYPE_QNAME) {
				found_zbits = nm_data->set.qname;
			} else {
				found_zbits = nm_data->set.ns;
			}
		}
		FALLTHROUGH;

	case DNS_R_PARTIALMATCH:
		i = chain.level_matches;
		nmnode = chain.levels[chain.level_matches];

		/*
		 * Whenever an exact match is found by dns_rbt_findnode(),
		 * the highest level node in the chain will not be put into
		 * chain->levels[] array, but instead the chain->end
		 * pointer will be adjusted to point to that node.
		 *
		 * Suppose we have the following entries in a rpz zone:
		 *   example.com     CNAME rpz-passthru.
		 *   *.example.com   CNAME rpz-passthru.
		 *
		 * A query for www.example.com would result in the
		 * following chain object returned by dns_rbt_findnode():
		 *   chain->level_count = 2
		 *   chain->level_matches = 2
		 *   chain->levels[0] = .
		 *   chain->levels[1] = example.com
		 *   chain->levels[2] = NULL
		 *   chain->end = www
		 *
		 * Since exact matches only care for testing rpz set bits,
		 * we need to test for rpz wild bits through iterating the
		 * nodechain, and that includes testing the rpz wild bits
		 * in the highest level node found. In the case of an exact
		 * match, chain->levels[chain->level_matches] will be NULL,
		 * to address that we must use chain->end as the start
		 * point, then iterate over the remaining levels in the
		 * chain.
		 */
		if (nmnode == NULL) {
			--i;
			nmnode = chain.end;
		}

		while (nmnode != NULL) {
			nm_data = nmnode->data;
			if (nm_data != NULL) {
				if (rpz_type == DNS_RPZ_TYPE_QNAME) {
					found_zbits |= nm_data->wild.qname;
				} else {
					found_zbits |= nm_data->wild.ns;
				}
			}

			if (i >= 0) {
				nmnode = chain.levels[i];
				--i;
			} else {
				break;
			}
		}
		break;

	case ISC_R_NOTFOUND:
		break;

	default:
		/*
		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
		 */
		dns_name_format(trig_name, namebuf, sizeof(namebuf));
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
			      "dns_rpz_find_name(%s) failed: %s", namebuf,
			      isc_result_totext(result));
		break;
	}

	RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);

	dns_rbtnodechain_invalidate(&chain);

	return (zbits & found_zbits);
}

/*
 * Translate CNAME rdata to a QNAME response policy action.
 */
dns_rpz_policy_t
dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
		     dns_name_t *selfname) {
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdata_cname_t cname;
	isc_result_t result;

	result = dns_rdataset_first(rdataset);
	INSIST(result == ISC_R_SUCCESS);
	dns_rdataset_current(rdataset, &rdata);
	result = dns_rdata_tostruct(&rdata, &cname, NULL);
	INSIST(result == ISC_R_SUCCESS);
	dns_rdata_reset(&rdata);

	/*
	 * CNAME . means NXDOMAIN
	 */
	if (dns_name_equal(&cname.cname, dns_rootname)) {
		return (DNS_RPZ_POLICY_NXDOMAIN);
	}

	if (dns_name_iswildcard(&cname.cname)) {
		/*
		 * CNAME *. means NODATA
		 */
		if (dns_name_countlabels(&cname.cname) == 2) {
			return (DNS_RPZ_POLICY_NODATA);
		}

		/*
		 * A qname of www.evil.com and a policy of
		 *	*.evil.com    CNAME   *.garden.net
		 * gives a result of
		 *	evil.com    CNAME   evil.com.garden.net
		 */
		if (dns_name_countlabels(&cname.cname) > 2) {
			return (DNS_RPZ_POLICY_WILDCNAME);
		}
	}

	/*
	 * CNAME rpz-tcp-only. means "send truncated UDP responses."
	 */
	if (dns_name_equal(&cname.cname, &rpz->tcp_only)) {
		return (DNS_RPZ_POLICY_TCP_ONLY);
	}

	/*
	 * CNAME rpz-drop. means "do not respond."
	 */
	if (dns_name_equal(&cname.cname, &rpz->drop)) {
		return (DNS_RPZ_POLICY_DROP);
	}

	/*
	 * CNAME rpz-passthru. means "do not rewrite."
	 */
	if (dns_name_equal(&cname.cname, &rpz->passthru)) {
		return (DNS_RPZ_POLICY_PASSTHRU);
	}

	/*
	 * 128.1.0.127.rpz-ip CNAME  128.1.0.0.127. is obsolete PASSTHRU
	 */
	if (selfname != NULL && dns_name_equal(&cname.cname, selfname)) {
		return (DNS_RPZ_POLICY_PASSTHRU);
	}

	/*
	 * Any other rdata gives a response consisting of the rdata.
	 */
	return (DNS_RPZ_POLICY_RECORD);
}
