fastrpz.patch revision 356345
1Description: based on the included patch contrib/fastrpz.patch
2Author: fastrpz@farsightsecurity.com
3---
4diff --git a/Makefile.in b/Makefile.in
5index 721c01b6..56bfb560 100644
6--- a/Makefile.in
7+++ b/Makefile.in
8@@ -23,6 +23,8 @@ CHECKLOCK_SRC=testcode/checklocks.c
9 CHECKLOCK_OBJ=@CHECKLOCK_OBJ@
10 DNSTAP_SRC=@DNSTAP_SRC@
11 DNSTAP_OBJ=@DNSTAP_OBJ@
12+FASTRPZ_SRC=@FASTRPZ_SRC@
13+FASTRPZ_OBJ=@FASTRPZ_OBJ@
14 DNSCRYPT_SRC=@DNSCRYPT_SRC@
15 DNSCRYPT_OBJ=@DNSCRYPT_OBJ@
16 WITH_PYTHONMODULE=@WITH_PYTHONMODULE@
17@@ -126,7 +128,7 @@ validator/val_sigcrypt.c validator/val_utils.c dns64/dns64.c \
18 edns-subnet/edns-subnet.c edns-subnet/subnetmod.c \
19 edns-subnet/addrtree.c edns-subnet/subnet-whitelist.c \
20 cachedb/cachedb.c cachedb/redis.c respip/respip.c $(CHECKLOCK_SRC) \
21-$(DNSTAP_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC) $(IPSET_SRC)
22+$(DNSTAP_SRC) $(FASTRPZ_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC) $(IPSET_SRC)
23 COMMON_OBJ_WITHOUT_NETCALL=dns.lo infra.lo rrset.lo dname.lo msgencode.lo \
24 as112.lo msgparse.lo msgreply.lo packed_rrset.lo iterator.lo iter_delegpt.lo \
25 iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
26@@ -139,7 +141,7 @@ autotrust.lo val_anchor.lo \
27 validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \
28 val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo cachedb.lo redis.lo authzone.lo \
29 $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \
30-$(IPSECMOD_OBJ) $(IPSET_OBJ) respip.lo
31+$(FASTRPZ_OBJ) $(IPSECMOD_OBJ) $(IPSET_OBJ) respip.lo
32 COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \
33 outside_network.lo
34 COMMON_OBJ=$(COMMON_OBJ_WITHOUT_UB_EVENT) ub_event.lo
35@@ -409,6 +411,11 @@ dnscrypt.lo dnscrypt.o: $(srcdir)/dnscrypt/dnscrypt.c config.h \
36 	$(srcdir)/util/config_file.h $(srcdir)/util/log.h \
37 	$(srcdir)/util/netevent.h
38 
39+# fastrpz
40+rpz.lo rpz.o: $(srcdir)/fastrpz/rpz.c config.h fastrpz/rpz.h fastrpz/librpz.h \
41+	$(srcdir)/util/config_file.h $(srcdir)/daemon/daemon.h \
42+	$(srcdir)/util/log.h
43+
44 # Python Module
45 pythonmod.lo pythonmod.o: $(srcdir)/pythonmod/pythonmod.c config.h \
46 	pythonmod/interface.h \
47diff --git a/config.h.in b/config.h.in
48index 8c2aa3b9..efaf6450 100644
49--- a/config.h.in
50+++ b/config.h.in
51@@ -1325,4 +1325,11 @@ void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file,
52 /** the version of unbound-control that this software implements */
53 #define UNBOUND_CONTROL_VERSION 1
54 
55-
56+/* have __attribute__s used in librpz.h */
57+#undef LIBRPZ_HAVE_ATTR
58+/** fastrpz librpz.so */
59+#undef FASTRPZ_LIBRPZ_PATH
60+/** 0=no fastrpz  1=static link  2=dlopen() */
61+#undef FASTRPZ_LIB_OPEN
62+/** turn on fastrpz response policy zones */
63+#undef ENABLE_FASTRPZ
64diff --git a/configure.ac b/configure.ac
65index 5276d441..9d74592e 100644
66--- a/configure.ac
67+++ b/configure.ac
68@@ -6,6 +6,7 @@ sinclude(ax_pthread.m4)
69 sinclude(acx_python.m4)
70 sinclude(ac_pkg_swig.m4)
71 sinclude(dnstap/dnstap.m4)
72+sinclude(fastrpz/rpz.m4)
73 sinclude(dnscrypt/dnscrypt.m4)
74 
75 # must be numbers. ac_defun because of later processing
76@@ -1726,6 +1727,9 @@ case "$enable_ipset" in
77 		;;
78 esac
79 
80+# check for Fastrpz with fastrpz/rpz.m4
81+ck_FASTRPZ
82+
83 AC_MSG_CHECKING([if ${MAKE:-make} supports $< with implicit rule in scope])
84 # on openBSD, the implicit rule make $< work.
85 # on Solaris, it does not work ($? is changed sources, $^ lists dependencies).
86diff --git a/daemon/daemon.c b/daemon/daemon.c
87index 0b1200a2..5857c18b 100644
88--- a/daemon/daemon.c
89+++ b/daemon/daemon.c
90@@ -91,6 +91,9 @@
91 #include "sldns/keyraw.h"
92 #include "respip/respip.h"
93 #include <signal.h>
94+#ifdef ENABLE_FASTRPZ
95+#include "fastrpz/rpz.h"
96+#endif
97 
98 #ifdef HAVE_SYSTEMD
99 #include <systemd/sd-daemon.h>
100@@ -458,6 +461,14 @@ daemon_create_workers(struct daemon* daemon)
101 		dt_apply_cfg(daemon->dtenv, daemon->cfg);
102 #else
103 		fatal_exit("dnstap enabled in config but not built with dnstap support");
104+#endif
105+	}
106+	if(daemon->cfg->rpz_enable) {
107+#ifdef ENABLE_FASTRPZ
108+		rpz_init(&daemon->rpz_clist, &daemon->rpz_client, daemon->cfg);
109+#else
110+		fatal_exit("fastrpz enabled in config"
111+			   " but not built with fastrpz");
112 #endif
113 	}
114 	for(i=0; i<daemon->num; i++) {
115@@ -724,6 +735,9 @@ daemon_cleanup(struct daemon* daemon)
116 #ifdef USE_DNSCRYPT
117 	dnsc_delete(daemon->dnscenv);
118 	daemon->dnscenv = NULL;
119+#endif
120+#ifdef ENABLE_FASTRPZ
121+	rpz_delete(&daemon->rpz_clist, &daemon->rpz_client);
122 #endif
123 	daemon->cfg = NULL;
124 }
125diff --git a/daemon/daemon.h b/daemon/daemon.h
126index 5749dbef..64ce230f 100644
127--- a/daemon/daemon.h
128+++ b/daemon/daemon.h
129@@ -136,6 +136,11 @@ struct daemon {
130 	/** the dnscrypt environment */
131 	struct dnsc_env* dnscenv;
132 #endif
133+#ifdef ENABLE_FASTRPZ
134+	/** global opaque rpz handles */
135+	struct librpz_clist *rpz_clist;
136+	struct librpz_client *rpz_client;
137+#endif
138 };
139 
140 /**
141diff --git a/daemon/worker.c b/daemon/worker.c
142index e2ce0e87..f031c656 100644
143--- a/daemon/worker.c
144+++ b/daemon/worker.c
145@@ -75,6 +75,9 @@
146 #include "libunbound/context.h"
147 #include "libunbound/libworker.h"
148 #include "sldns/sbuffer.h"
149+#ifdef ENABLE_FASTRPZ
150+#include "fastrpz/rpz.h"
151+#endif
152 #include "sldns/wire2str.h"
153 #include "util/shm_side/shm_main.h"
154 #include "dnscrypt/dnscrypt.h"
155@@ -533,8 +536,27 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
156 			/* not secure */
157 			secure = 0;
158 			break;
159+#ifdef ENABLE_FASTRPZ
160+		case sec_status_rpz_rewritten:
161+		case sec_status_rpz_drop:
162+			fatal_exit("impossible cached RPZ sec_status");
163+			break;
164+#endif
165 		}
166 	}
167+#ifdef ENABLE_FASTRPZ
168+	if(repinfo->rpz) {
169+		/* Scan the cached answer for RPZ hits.
170+		 * ret=1 use cache entry
171+		 * ret=-1 rewritten response already sent or dropped
172+		 * ret=0 deny a cached entry exists
173+		 */
174+		int ret = rpz_worker_cache(worker, msg->rep, qinfo,
175+					   id, flags, edns, repinfo);
176+		if(ret != 1)
177+			return ret;
178+	}
179+#endif
180 	/* return this delegation from the cache */
181 	edns_bak = *edns;
182 	edns->edns_version = EDNS_ADVERTISED_VERSION;
183@@ -699,6 +721,23 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
184 			secure = 0;
185 		}
186 	} else	secure = 0;
187+#ifdef ENABLE_FASTRPZ
188+	if(repinfo->rpz) {
189+		/* Scan the cached answer for RPZ hits.
190+		 * ret=1 use cache entry
191+		 * ret=-1 rewritten response already sent or dropped
192+		 * ret=0 deny a cached entry exists
193+		 */
194+		int ret = rpz_worker_cache(worker, rep, qinfo, id, flags, edns,
195+					   repinfo);
196+		if(ret != 1) {
197+			rrset_array_unlock_touch(worker->env.rrset_cache,
198+						 worker->scratchpad, rep->ref,
199+						 rep->rrset_count);
200+			return ret;
201+		}
202+        }
203+#endif
204 
205 	edns_bak = *edns;
206 	edns->edns_version = EDNS_ADVERTISED_VERSION;
207@@ -1410,6 +1449,15 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
208 		log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from",
209 			&repinfo->addr, repinfo->addrlen);
210 		goto send_reply;
211+#ifdef ENABLE_FASTRPZ
212+	} else {
213+		/* Start to rewrite for response policy zones.
214+		 * This can hit a qname trigger and be done. */
215+		if(rpz_start(worker, &qinfo, repinfo, &edns)) {
216+			regional_free_all(worker->scratchpad);
217+			return 0;
218+		}
219+#endif
220 	}
221 
222 	/* If we've found a local alias, replace the qname with the alias
223@@ -1458,12 +1506,21 @@ lookup_cache:
224 		h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2));
225 		if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) {
226 			/* answer from cache - we have acquired a readlock on it */
227-			if(answer_from_cache(worker, &qinfo,
228+			ret = answer_from_cache(worker, &qinfo,
229 				cinfo, &need_drop, &alias_rrset, &partial_rep,
230 				(struct reply_info*)e->data,
231 				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
232 				sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
233-				&edns)) {
234+				&edns);
235+#ifdef ENABLE_FASTRPZ
236+			if(ret < 0) {
237+				/* RPZ already dropped or sent a response. */
238+				lock_rw_unlock(&e->lock);
239+				regional_free_all(worker->scratchpad);
240+				return 0;
241+			}
242+#endif
243+			if(ret) {
244 				/* prefetch it if the prefetch TTL expired.
245 				 * Note that if there is more than one pass
246 				 * its qname must be that used for cache
247@@ -1518,11 +1575,19 @@ lookup_cache:
248 			lock_rw_unlock(&e->lock);
249 		}
250 		if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
251-			if(answer_norec_from_cache(worker, &qinfo,
252+			ret = answer_norec_from_cache(worker, &qinfo,
253 				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer), 
254 				sldns_buffer_read_u16_at(c->buffer, 2), repinfo, 
255-				&edns)) {
256+				&edns);
257+			if(ret) {
258 				regional_free_all(worker->scratchpad);
259+#ifdef ENABLE_FASTRPZ
260+				if(ret < 0) {
261+					/* RPZ already dropped
262+					 * or sent a response. */
263+					return 0;
264+				}
265+#endif
266 				goto send_reply;
267 			}
268 			verbose(VERB_ALGO, "answer norec from cache -- "
269diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in
270index 4bdfcd56..69e70627 100644
271--- a/doc/unbound.conf.5.in
272+++ b/doc/unbound.conf.5.in
273@@ -1801,6 +1801,81 @@ List domain for which the AAAA records are ignored and the A record is
274 used by dns64 processing instead.  Can be entered multiple times, list a
275 new domain for which it applies, one per line.  Applies also to names
276 underneath the name given.
277+.SS "Response Policy Zone Rewriting"
278+.LP
279+Response policy zone rewriting is controlled with the
280+.B rpz
281+clause.
282+It must contain a
283+.B rpz\-enable:
284+option, and one or more
285+.B rpz\-zone:
286+options.
287+It will usually also contain
288+.B rpz\-option:
289+clauses with general rewriting options or specifying dnsrpzd parameters.
290+Beneath the surface, the text in
291+.B rpz\-zone: \fI<"domain">\fR
292+is converted to \fI"zone domain\\n"\fR and added to the configuration string
293+given to
294+\fIlibrpz\fR(3).
295+The text in
296+.B rpz-option \fI<"text">\fR
297+is also added to that configuration string.
298+.LP
299+If using chroot, then the chroot directory must contain the \fIdnsrpzd\fR(3)
300+command and the shared libraries that it uses.
301+Those can be found with the \fIldd\fR(1) command.
302+.LP
303+Resolver zone and rewriting options and response policy zone triggers and
304+actions are described in \fIlibrpz\fR(3).
305+The separate control file that specifies the policy zones maintained by
306+the dnsrpzd daemon is described in \fIdnsrpzd\fR(8).
307+.LP
308+Many installations need a local whitelist that exempts local
309+domains from rewriting.
310+Whitelist records can be in zones transferred by dnsrpzd from
311+authorities or in a local zone file.
312+.TP
313+.B rpz-enable: \fI<yes or no>
314+enables Fastrpz.
315+If not enabled, the other options in the
316+.B rpz:
317+clause are ignored.
318+.TP
319+.B rpz-zone: \fI<"zone and options">
320+specifies a policy zone and optional per-zone rewriting parameters.
321+.TP
322+.B rpz-option: \fI<"option">
323+specifies general Fastrpz options.
324+.LP
325+Fastrpz is available only on POSIX compliant UNIX-like systems with the
326+\fImmap\fR(2) system call.
327+.LP
328+Fastrpz in Unbound differs from rpz and fastrpz in BIND by
329+.RS 3
330+.HP 4
331+RPZ-CLIENT-IP triggers can only be used in the first policy zone
332+specified with
333+.B rpz-zone:
334+.HP
335+Policy zone rewriting is disabled by the DO bit in DNS requests
336+even when no DNSSEC signatures are supplied by authorities.
337+.HP
338+Unbound local zones are not subject to rpz rewriting.
339+.HP
340+Like Fastrpz with BIND but unlike classic BIND rpz,
341+the ADDITIONAL sections of rewritten responses contain the SOA record from
342+the policy zone used to rewrite the response.
343+.RE
344+.P
345+.nf
346+# example Fastrpz settings for use with chroot on Freebsd
347+rpz:
348+    rpz-zone: "rpz.example.org"
349+    rpz-zone: "other.rpz.example.org ip-as-ns yes"
350+    rpz-option: "dnsrpzd ./dnsrpzd"
351+.fi
352 .SS "DNSCrypt Options"
353 .LP
354 The
355diff --git a/fastrpz/librpz.h b/fastrpz/librpz.h
356new file mode 100644
357index 00000000..645279d1
358--- /dev/null
359+++ b/fastrpz/librpz.h
360@@ -0,0 +1,957 @@
361+/*
362+ * Define the interface from a DNS resolver to the Response Policy Zone
363+ * library, librpz.
364+ *
365+ * This file should be included only the interface functions between the
366+ * resolver and librpz to avoid name space pollution.
367+ *
368+ * Copyright (c) 2016-2017 Farsight Security, Inc.
369+ *
370+ * Licensed under the Apache License, Version 2.0 (the "License");
371+ * you may not use this file except in compliance with the License.
372+ * You may obtain a copy of the License at
373+ *	http://www.apache.org/licenses/LICENSE-2.0
374+ *
375+ * Unless required by applicable law or agreed to in writing, software
376+ * distributed under the License is distributed on an "AS IS" BASIS,
377+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
378+ * See the License for the specific language governing permissions and
379+ * limitations under the License.
380+ *
381+ * Fastrpz version 1.2.10
382+ */
383+
384+#ifndef LIBRPZ_H
385+#define LIBRPZ_H
386+
387+#include <arpa/nameser.h>
388+#include <netinet/in.h>
389+#include <stdarg.h>
390+#include <stdbool.h>
391+#include <stdio.h>
392+#include <sys/types.h>
393+
394+
395+/*
396+ * Allow either ordinary or dlopen() linking.
397+ */
398+#ifdef LIBRPZ_INTERNAL
399+#define LIBDEF(t,s) extern t s;
400+#define LIBDEF_F(f) LIBDEF(librpz_##f##_t, librpz_##f)
401+#else
402+#define LIBDEF(t,s)
403+#define LIBDEF_F(f)
404+#endif
405+
406+/*
407+ * Response Policy Zone triggers.
408+ *	Comparisons of trigger precedences require
409+ *	LIBRPZ_TRIG_CLIENT_IP < LIBRPZ_TRIG_QNAME < LIBRPZ_TRIG_IP
410+ *	    < LIBRPZ_TRIG_NSDNAME < LIBRPZ_TRIG_NSIP}
411+ */
412+typedef enum {
413+	LIBRPZ_TRIG_BAD		=0,
414+	LIBRPZ_TRIG_CLIENT_IP	=1,
415+	LIBRPZ_TRIG_QNAME	=2,
416+	LIBRPZ_TRIG_IP		=3,
417+	LIBRPZ_TRIG_NSDNAME	=4,
418+	LIBRPZ_TRIG_NSIP	=5
419+} librpz_trig_t;
420+#define LIBRPZ_TRIG_SIZE	3	/* sizeof librpz_trig_t in bits */
421+typedef uint8_t		librpz_tbit_t;  /* one bit for each of the TRIGS_NUM
422+					 * trigger types */
423+
424+
425+/*
426+ * Response Policy Zone Actions or policies
427+ */
428+typedef enum {
429+	LIBRPZ_POLICY_UNDEFINED	=0,	/* an empty entry or no decision yet */
430+	LIBRPZ_POLICY_DELETED	=1,	/* placeholder for a deleted policy */
431+
432+	LIBRPZ_POLICY_PASSTHRU	=2,	/* 'passthru': do not rewrite */
433+	LIBRPZ_POLICY_DROP	=3,	/* 'drop': do not respond */
434+	LIBRPZ_POLICY_TCP_ONLY	=4,	/* 'tcp-only': answer UDP with TC=1 */
435+	LIBRPZ_POLICY_NXDOMAIN	=5,	/* 'nxdomain': answer with NXDOMAIN */
436+	LIBRPZ_POLICY_NODATA	=6,	/* 'nodata': answer with ANCOUNT=0 */
437+	LIBRPZ_POLICY_RECORD	=7,	/* rewrite with the policy's RR */
438+
439+	/* only in client configurations to override the zone */
440+	LIBRPZ_POLICY_GIVEN,		/* 'given': what policy record says */
441+	LIBRPZ_POLICY_DISABLED,		/* at most log */
442+	LIBRPZ_POLICY_CNAME,		/* answer with 'cname x' */
443+} librpz_policy_t;
444+#define LIBRPZ_POLICY_BITS	4
445+
446+/*
447+ * Special policies that appear as targets of CNAMEs
448+ * NXDOMAIN is signaled by a CNAME with a "." target.
449+ * NODATA is signaled by a CNAME with a "*." target.
450+ */
451+#define LIBRPZ_RPZ_PREFIX	"rpz-"
452+#define LIBRPZ_RPZ_PASSTHRU	LIBRPZ_RPZ_PREFIX"passthru"
453+#define LIBRPZ_RPZ_DROP		LIBRPZ_RPZ_PREFIX"drop"
454+#define LIBRPZ_RPZ_TCP_ONLY	LIBRPZ_RPZ_PREFIX"tcp-only"
455+
456+
457+typedef	uint16_t    librpz_dznum_t;	/* dnsrpzd zone # in [0,DZNUM_MAX] */
458+typedef	uint8_t	    librpz_cznum_t;	/* client zone # in [0,CZNUM_MAX] */
459+
460+
461+/*
462+ * CIDR block
463+ */
464+typedef struct librpz_prefix {
465+	union {
466+		struct in_addr	in;
467+		struct in6_addr	in6;
468+	} addr;
469+	uint8_t		    family;
470+	uint8_t		    len;
471+} librpz_prefix_t;
472+
473+/*
474+ * A domain
475+ */
476+typedef uint8_t	librpz_dsize_t;
477+typedef struct librpz_domain {
478+	librpz_dsize_t	    size;	/* of only .d */
479+	uint8_t		    d[0];	/* variable length wire format */
480+} librpz_domain_t;
481+
482+/*
483+ * A maximal domain buffer
484+ */
485+typedef struct librpz_domain_buf {
486+	librpz_dsize_t	    size;
487+	uint8_t		    d[NS_MAXCDNAME];
488+} librpz_domain_buf_t;
489+
490+/*
491+ * A resource record without the owner name.
492+ * C compilers say that sizeof(librpz_rr_t)=12 instead of 10.
493+ */
494+typedef struct {
495+	uint16_t	    type;	/* network byte order */
496+	uint16_t	    class;	/* network byte order */
497+	uint32_t	    ttl;	/* network byte order */
498+	uint16_t	    rdlength;	/* network byte order */
499+	uint8_t		    rdata[0];	/* variable length */
500+} librpz_rr_t;
501+
502+/*
503+ * The database file might be mapped with different starting addresses
504+ * by concurrent clients (resolvers), and so all pointers are offsets.
505+ */
506+typedef uint32_t	librpz_idx_t;
507+#define LIBRPZ_IDX_NULL	0
508+#define LIBRPZ_IDX_MIN	1
509+#define LIBRPZ_IDX_BAD  ((librpz_idx_t)-1)
510+/**
511+ * Partial decoded results of a set of RPZ queries for a single DNS response
512+ * or interation through the mapped file.
513+ */
514+typedef int16_t librpz_result_id_t;
515+typedef struct librpz_result {
516+	librpz_idx_t	    next_rr;
517+	librpz_result_id_t  hit_id;		/* trigger ID from resolver */
518+	librpz_policy_t	    zpolicy;	/* policy from zone */
519+	librpz_policy_t	    policy;	/* adjusted by client configuration */
520+	librpz_dznum_t	    dznum;	/* dnsrpzd zone number */
521+	librpz_cznum_t	    cznum;	/* librpz client zone number */
522+	librpz_trig_t	    trig:LIBRPZ_TRIG_SIZE;
523+	bool		    log:1;	/* log rewrite given librpz_log_level */
524+} librpz_result_t;
525+
526+
527+/**
528+ * librpz trace or log levels.
529+ */
530+typedef enum {
531+	LIBRPZ_LOG_FATAL    =0,		/* always print fatal errors */
532+	LIBRPZ_LOG_ERROR    =1,		/* errors have this level */
533+	LIBRPZ_LOG_TRACE1   =2,		/* big events such as dnsrpzd starts */
534+	LIBRPZ_LOG_TRACE2   =3,		/* smaller dnsrpzd zone transfers */
535+	LIBRPZ_LOG_TRACE3   =4,		/* librpz hits */
536+	LIBRPZ_LOG_TRACE4   =5,		/* librpz lookups */
537+	LIBRPZ_LOG_INVALID   =999,
538+} librpz_log_level_t;
539+typedef librpz_log_level_t (librpz_log_level_val_t)(librpz_log_level_t level);
540+LIBDEF_F(log_level_val)
541+
542+/**
543+ * Logging function that can be supplied by the resolver.
544+ * @param level is one of librpz_log_level_t
545+ * @param ctx is for use by the resolver's logging system.
546+ *	NULL mean a context-free message.
547+ */
548+typedef void(librpz_log_fnc_t)(librpz_log_level_t level, void *ctx,
549+			       const char *buf);
550+
551+/**
552+ * Point librpz logging functions to the resolver's choice.
553+ */
554+typedef void (librpz_set_log_t)(librpz_log_fnc_t *new_log, const char *prog_nm);
555+LIBDEF_F(set_log)
556+
557+
558+/**
559+ * librpz error messages are put in these buffers.
560+ * Use a structure intead of naked char* to let the compiler check the length.
561+ * A function defined with "foo(char buf[120])" can be called with
562+ * "char sbuf[2]; foo(sbuf)" and suffer a buffer overrun.
563+ */
564+typedef struct {
565+	char	c[120];
566+} librpz_emsg_t;
567+
568+
569+#ifdef LIBRPZ_HAVE_ATTR
570+#define LIBRPZ_UNUSED	__attribute__((unused))
571+#define LIBRPZ_PF(f,l)	__attribute__((format(printf,f,l)))
572+#define	LIBRPZ_NORET	__attribute__((__noreturn__))
573+#else
574+#define LIBRPZ_UNUSED
575+#define LIBRPZ_PF(f,l)
576+#define	LIBRPZ_NORET
577+#endif
578+
579+#ifdef HAVE_BUILTIN_EXPECT
580+#define LIBRPZ_LIKELY(c) __builtin_expect(!!(c), 1)
581+#define LIBRPZ_UNLIKELY(c) __builtin_expect(!!(c), 0)
582+#else
583+#define LIBRPZ_LIKELY(c) (c)
584+#define LIBRPZ_UNLIKELY(c) (c)
585+#endif
586+
587+typedef bool (librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg);
588+LIBDEF_F(parse_log_opt)
589+
590+typedef void (librpz_vpemsg_t)(librpz_emsg_t *emsg,
591+			       const char *p, va_list args);
592+LIBDEF_F(vpemsg)
593+typedef void (librpz_pemsg_t)(librpz_emsg_t *emsg,
594+			      const char *p, ...) LIBRPZ_PF(2,3);
595+LIBDEF_F(pemsg)
596+
597+typedef void (librpz_vlog_t)(librpz_log_level_t level, void *ctx,
598+			     const char *p, va_list args);
599+LIBDEF_F(vlog)
600+typedef void (librpz_log_t)(librpz_log_level_t level, void *ctx,
601+			    const char *p, ...) LIBRPZ_PF(3,4);
602+LIBDEF_F(log)
603+
604+typedef void (librpz_fatal_t)(int ex_code,
605+			      const char *p, ...) LIBRPZ_PF(2,3);
606+extern void librpz_fatal(int ex_code,
607+			 const char *p, ...) LIBRPZ_PF(2,3) LIBRPZ_NORET;
608+
609+typedef void (librpz_rpz_assert_t)(const char *file, unsigned line,
610+				   const char *p, ...) LIBRPZ_PF(3,4);
611+extern void librpz_rpz_assert(const char *file, unsigned line,
612+			      const char *p, ...) LIBRPZ_PF(3,4) LIBRPZ_NORET;
613+
614+typedef void (librpz_rpz_vassert_t)(const char *file, uint line,
615+				    const char *p, va_list args);
616+extern void librpz_rpz_vassert(const char *file, uint line,
617+			       const char *p, va_list args) LIBRPZ_NORET;
618+
619+
620+/*
621+ * As far as clients are concerned, all relative pointers or indexes in a
622+ * version of the mapped file except trie node parent pointers remain valid
623+ * forever.  A client must release a version so that it can be garbage
624+ * collected by the file system.  When dnsrpzd needs to expand the file,
625+ * it copies the old file to a new, larger file.  Clients can continue
626+ * using the old file.
627+ *
628+ * Versions can also appear in a single file.  Old nodes and trie values
629+ * within the file are not destroyed until all clients using the version
630+ * that contained the old values release the version.
631+ *
632+ * A client is marked as using version by connecting to the deamon.  It is
633+ * marked as using all subsequent versions.  A client releases all versions
634+ * by closing the connection or a range of versions by updating is slot
635+ * in the shared memory version table.
636+ *
637+ * As far as clients are concerned, there are the following possible librpz
638+ * failures:
639+ *	- malloc() or other fatal internal librpz problems indicated by
640+ *	    a failing return from a librpz function
641+ *	    All operations will fail until client handle is destroyed and
642+ *	    recreated with librpz_client_detach() and librpz_client_create().
643+ *	- corrupt database detected by librpz code, corrupt database detected
644+ *	    by dnsrpzd, or disconnection from the daemon.
645+ *	    Current operations will fail.
646+ *
647+ * Clients assume that the file has already been unlinked before
648+ *	the corrupt flag is set so that they do not race with the server
649+ *	over the corruption of a single file.  A client that finds the
650+ *	corrupt set knows that dnsrpzd has already crashed with
651+ *	abort() and is restarting.  The client can re-connect to dnsrpzd
652+ *	and retransmit its configuration, backing off as usual if anything
653+ *	goes wrong.
654+ *
655+ * Searchs of the database by a client do not need locks against dnsrpzd or
656+ *	other clients, but a lock is used to protect changes to the connection
657+ *	by competing threads in the client.  The client provides fuctions
658+ *	to serialize the conncurrent use of any single client handle.
659+ *	Functions that do nothing are appropriate for applications that are
660+ *	not "threaded" or that do not share client handles among threads.
661+ *	Otherwise, functions must be provided to librpz_clientcreate().
662+ *	Something like the following works with pthreads:
663+ *
664+ * static void
665+ * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); }
666+ *
667+ * static void
668+ * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); }
669+ *
670+ * static void
671+ * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); }
672+ *
673+ *
674+ *
675+ * At every instant, all of the data and pointers in the mapped file are valid.
676+ *	Changes to trie node or other data are always made so that it and
677+ *	all pointers in and to it remain valid for a time.  Old versions are
678+ *	eventually discarded.
679+ *
680+ * Dnsrpzd periodically defines a new version by setting asside all changes
681+ *	made since the previous version was defined.  Subsequent changes
682+ *	made (only!) by dnsrpzd will be part of the next version.
683+ *
684+ * To discard an old version, dnsrpzd must know that all clients have stopped
685+ *	using that version.  Clients do that by using part of the mapped file
686+ *	to tell dnsrpzd the oldest version that each client is using.
687+ *	Dnsrpzd assigns each connecting client an entry in the cversions array
688+ *	in the mapped file.  The client puts version numbers into that entry
689+ *	to signal to dnsrpzd which versions that can be discarded.
690+ *	Dnsrpzd is free, as far as that client is concerned, to discard all
691+ *	numerically smaller versions.  A client can disclaim all versions with
692+ *	the version number VERSIONS_ALL or 0.
693+ *
694+ * The race between a client changing its entry and dnsrpzd discarding a
695+ *	version is resolved by allowing dnsrpzd to discard all versions
696+ *	smaller or equal to the client's version number.  If dnsrpzd is in
697+ *	the midst of discarding or about to discard version N when the
698+ *	client asserts N, no harm is done.  The client depends only on
699+ *	the consistency of version N+1.
700+ *
701+ * This version mechanism depends in part on not being exercised too frequently
702+ *	Version numbers are 32 bits long and dnsrpzd creates new versions
703+ *	at most once every 30 seconds.
704+ */
705+
706+
707+/*
708+ * Lock functions for concurrent use of a single librpz_client_t client handle.
709+ */
710+typedef void(librpz_mutex_t)(void *mutex);
711+
712+/*
713+ * List of connections to dnsrpzd daemons.
714+ */
715+typedef struct librpz_clist librpz_clist_t;
716+
717+/*
718+ * Client's handle on dnsrpzd.
719+ */
720+typedef struct librpz_client librpz_client_t;
721+
722+/**
723+ * Create the list of connections to the dnsrpzd daemon.
724+ * @param[out] emsg: error message
725+ * @param lock: start exclusive access to the client handle
726+ * @param unlock: end exclusive access to the client handle
727+ * @param mutex_destroy: release the lock
728+ * @param mutex: pointer to the lock for the client handle
729+ * @param log_ctx: NULL or resolver's context log messages
730+ */
731+typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg,
732+						librpz_mutex_t *lock,
733+						librpz_mutex_t *unlock,
734+						librpz_mutex_t *mutex_destroy,
735+						void *mutex, void *log_ctx);
736+LIBDEF_F(clist_create)
737+
738+
739+/**
740+ * Release the list of dnsrpzd connections.
741+ */
742+typedef void (librpz_clist_detach_t)(librpz_clist_t **clistp);
743+LIBDEF_F(clist_detach)
744+
745+/**
746+ * Create a librpz client handle.
747+ * @param[out] emsg: error message
748+ * @param: list of dnsrpzd connections
749+ * @param cstr: string of configuration settings separated by ';' or '\n'
750+ * @param use_expired: true to not ignore expired zones
751+ * @return client handle or NULL if the handle could not be created
752+ */
753+typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg,
754+						  librpz_clist_t *clist,
755+						  const char *cstr,
756+						  bool use_expired);
757+LIBDEF_F(client_create)
758+
759+/**
760+ * Start (if necessary) dnsrpzd and connect to it.
761+ * @param[out] emsg: error message
762+ * @param client handle
763+ * @param optional: true if it is ok if starting the daemon is not allowed
764+ */
765+typedef bool (librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client,
766+				bool optional);
767+LIBDEF_F(connect)
768+
769+/**
770+ * Start to destroy a librpz client handle.
771+ * It will not be destroyed until the last set of RPZ queries represented
772+ * by a librpz_rsp_t ends.
773+ * @param client handle to be released
774+ * @return false on error
775+ */
776+typedef void (librpz_client_detach_t)(librpz_client_t **clientp);
777+LIBDEF_F(client_detach)
778+
779+/**
780+ * State for a set of RPZ queries for a single DNS response
781+ * or for listing the database.
782+ */
783+typedef struct librpz_rsp librpz_rsp_t;
784+
785+/**
786+ * Start a set of RPZ queries for a single DNS response.
787+ * @param[out] emsg: error message for false return or *rspp=NULL
788+ * @param[out] rspp created context or NULL
789+ * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value
790+ * @param client state
791+ * @param have_rd: RD=1 in the DNS request
792+ * @param have_do: DO=1 in the DNS request
793+ * @return false on error
794+ */
795+typedef bool (librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
796+				   int *min_ns_dotsp, librpz_client_t *client,
797+				   bool have_rd, bool have_do);
798+LIBDEF_F(rsp_create)
799+
800+/**
801+ * Finish RPZ work for a DNS response.
802+ */
803+typedef void (librpz_rsp_detach_t)(librpz_rsp_t **rspp);
804+LIBDEF_F(rsp_detach)
805+
806+/**
807+ * Get the final, accumulated result of a set of RPZ queries.
808+ * Yield LIBRPZ_POLICY_UNDEFINED if
809+ *  - there were no hits,
810+ *  - there was a dispositive hit, be we have not recursed and are required
811+ *	to recurse so that evil DNS authories will not know we are using RPZ
812+ *  - we have a hit and have recursed, but later data such as NSIP could
813+ *	override
814+ * @param[out] emsg
815+ * @param[out] result describes the hit
816+ *	or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit
817+ * @param[out] result: current policy rewrite values
818+ * @param recursed: recursion has now been done even if it was not done
819+ *	when the hit was found
820+ * @param[in,out] rsp state from librpz_itr_start()
821+ * @return false on error
822+ */
823+typedef bool (librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result,
824+				   bool recursed, const librpz_rsp_t *rsp);
825+LIBDEF_F(rsp_result)
826+
827+/**
828+ * Might looking for a trigger be worthwhile?
829+ * @param trig: look for this type of trigger
830+ * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP,
831+ *	or LIBRPZ_TRIG_NSIP and the IP address is IPv6
832+ * @return: true if looking could be worthwhile
833+ */
834+typedef bool (librpz_have_trig_t)(librpz_trig_t trig, bool ipv6,
835+				  const librpz_rsp_t *rsp);
836+LIBDEF_F(have_trig)
837+
838+/**
839+ * Might looking for NSDNAME and NSIP triggers be worthwhile?
840+ * @return: true if looking could be worthwhile
841+ */
842+typedef bool (librpz_have_ns_trig_t)(const librpz_rsp_t *rsp);
843+LIBDEF_F(have_ns_trig)
844+
845+/**
846+ * Convert the found client IP trie key to a CIDR block
847+ * @param[out] emsg
848+ * @param[out] prefix trigger
849+ * @param[in,out] rsp state from librpz_itr_start()
850+ * @return false on error
851+ */
852+typedef bool (librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg,
853+					    librpz_prefix_t *prefix,
854+					    librpz_rsp_t *rsp);
855+LIBDEF_F(rsp_clientip_prefix)
856+
857+/**
858+ * Compute the owner name of the found or result trie key, usually to log it.
859+ * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip.
860+ * example.com. might be a qname trigger.  example.com.rpz-nsdname. could
861+ * be an NSDNAME trigger.
862+ * @param[out] emsg
863+ * @param[out] owner domain
864+ * @param[in,out] rsp state from librpz_itr_start()
865+ * @return false on error
866+ */
867+typedef bool (librpz_rsp_domain_t)(librpz_emsg_t *emsg,
868+				   librpz_domain_buf_t *owner,
869+				   librpz_rsp_t *rsp);
870+LIBDEF_F(rsp_domain)
871+
872+/**
873+ * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of
874+ * librpz_rsp_result() or librpz_itr_node() or after a previous use of
875+ * librpz_rsp_rr().  The RR is in uncompressed wire format including type,
876+ * class, ttl and length in network byte order.
877+ * @param[out] emsg
878+ * @param[out] typep: optional host byte order record type or ns_t_invalid (0)
879+ * @param[out] classp: class such as ns_c_in
880+ * @param[out] ttlp: TTL
881+ * @param[out] rrp: optionall malloc() buffer containting the next RR or
882+ *	NULL after the last RR
883+ * @param[out] result: current policy rewrite values
884+ * @param qname: used construct a wildcard CNAME
885+ * @param qname_size
886+ * @param[in,out] rsp state from librpz_itr_start()
887+ * @return false on error
888+ */
889+typedef bool (librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep,
890+			       uint16_t *classp, uint32_t *ttlp,
891+			       librpz_rr_t **rrp, librpz_result_t *result,
892+			       const uint8_t *qname, size_t qname_size,
893+			       librpz_rsp_t *rsp);
894+LIBDEF_F(rsp_rr)
895+
896+/**
897+ * Get the next RR of the LIBRPZ_POLICY_RECORD result.
898+ * @param[out] emsg
899+ * @param[out] ttlp: TTL
900+ * @param[out] rrp: malloc() buffer with SOA RR without owner name
901+ * @param[out] result: current policy rewrite values
902+ * @param[out] origin: SOA owner name
903+ * @param[out] origin_size
904+ * @param[in,out] rsp state from librpz_itr_start()
905+ * @return false on error
906+ */
907+typedef bool (librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp,
908+				librpz_rr_t **rrp, librpz_domain_buf_t *origin,
909+				librpz_result_t *result, librpz_rsp_t *rsp);
910+LIBDEF_F(rsp_soa)
911+
912+/**
913+ * Get the SOA serial number for a policy zone to compare with a known value
914+ * to check whether a zone tranfer is complete.
915+ */
916+typedef bool (librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp,
917+				   const char *domain_nm, librpz_rsp_t *rsp);
918+LIBDEF_F(soa_serial)
919+
920+/**
921+ * Save the current policy checking state.
922+ * @param[out] emsg
923+ * @param[in,out] rsp state from librpz_itr_start()
924+ * @return false on error
925+ */
926+typedef bool (librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
927+LIBDEF_F(rsp_push)
928+#define LIBRPZ_RSP_STACK_DEPTH	3
929+
930+/**
931+ * Restore the previous policy checking state.
932+ * @param[out] emsg
933+ * @param[out] result: NULL or restored policy rewrite values
934+ * @param[in,out] rsp state from librpz_itr_start()
935+ * @return false on error
936+ */
937+typedef bool (librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result,
938+				librpz_rsp_t *rsp);
939+LIBDEF_F(rsp_pop)
940+
941+/**
942+ * Discard the most recently save policy checking state.
943+ * @param[out] emsg
944+ * @param[out] result: NULL or restored policy rewrite values
945+ * @return false on error
946+ */
947+typedef bool (librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
948+LIBDEF_F(rsp_pop_discard)
949+
950+/**
951+ * Disable a zone.
952+ * @param[out] emsg
953+ * @param znum
954+ * @param[in,out] rsp state from librpz_itr_start()
955+ * @return false on error
956+ */
957+typedef bool (librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg,
958+					librpz_cznum_t znum, librpz_rsp_t *rsp);
959+LIBDEF_F(rsp_forget_zone)
960+
961+/**
962+ * Apply RPZ to an IP address.
963+ * @param[out] emsg
964+ * @param addr: address to check
965+ * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4
966+ * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP
967+ * @param hit_id: caller chosen
968+ * @param recursed: recursion has been done
969+ * @param[in,out] rsp state from librpz_itr_start()
970+ * @return false on error
971+ */
972+typedef bool (librpz_ck_ip_t)(librpz_emsg_t *emsg,
973+			      const void *addr, uint family,
974+			      librpz_trig_t trig, librpz_result_id_t hit_id,
975+			      bool recursed, librpz_rsp_t *rsp);
976+LIBDEF_F(ck_ip)
977+
978+/**
979+ * Apply RPZ to a wire-format domain.
980+ * @param[out] emsg
981+ * @param domain in wire format
982+ * @param domain_size
983+ * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME
984+ * @param hit_id: caller chosen
985+ * @param recursed: recursion has been done
986+ * @param[in,out] rsp state from librpz_itr_start()
987+ * @return false on error
988+ */
989+typedef bool (librpz_ck_domain_t)(librpz_emsg_t *emsg,
990+				  const uint8_t *domain, size_t domain_size,
991+				  librpz_trig_t trig, librpz_result_id_t hit_id,
992+				  bool recursed, librpz_rsp_t *rsp);
993+LIBDEF_F(ck_domain)
994+
995+/**
996+ * Ask dnsrpzd to refresh a zone.
997+ * @param[out] emsg error message
998+ * @param librpz_domain_t domain to refresh
999+ * @param client context
1000+ * @return false after error
1001+ */
1002+typedef bool (librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain,
1003+				       librpz_rsp_t *rsp);
1004+LIBDEF_F(zone_refresh)
1005+
1006+/**
1007+ * Get a string describing the the databasse
1008+ * @param license: include the license
1009+ * @param cfiles: include the configuration file names
1010+ * @param listens: include the local notify IP addresses
1011+ * @param[out] emsg error message if the result is null
1012+ * @param client context
1013+ * @return malloc'ed string or NULL after error
1014+ */
1015+typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg,
1016+				 bool license, bool cfiles, bool listens,
1017+				 librpz_rsp_t *rsp);
1018+LIBDEF_F(db_info)
1019+
1020+/**
1021+ * Start a context for listing the nodes and/or zones in the mapped file
1022+ * @param[out] emsg: error message for false return or *rspp=NULL
1023+ * @param[out[ rspp created context or NULL
1024+ * @param client context
1025+ * @return false after error
1026+ */
1027+typedef bool (librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
1028+				  librpz_client_t *client);
1029+LIBDEF_F(itr_start)
1030+
1031+/**
1032+ * Get mapped file memory allocation statistics.
1033+ * @param[out] emsg: error message
1034+ * @param rsp state from librpz_itr_start()
1035+ * @return malloc'ed string or NULL after error
1036+ */
1037+typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
1038+LIBDEF_F(mf_stats)
1039+
1040+/**
1041+ * Get versions currently used by clients.
1042+ * @param[out] emsg: error message
1043+ * @param[in,out] rsp: state from librpz_itr_start()
1044+ * @return malloc'ed string or NULL after error
1045+ */
1046+typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
1047+LIBDEF_F(vers_stats)
1048+
1049+/**
1050+ * Allocate a string describing the next zone or "" after the last zone.
1051+ * @param[out] emsg
1052+ * @param all_zones to list all instead of only requested zones
1053+ * @param[in,out] rsp state from librpz_rsp_start()
1054+ * @return malloc'ed string or NULL after error
1055+ */
1056+typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones,
1057+				  librpz_rsp_t *rsp);
1058+LIBDEF_F(itr_zone)
1059+
1060+/**
1061+ * Describe the next trie node while dumping the database.
1062+ * @param[out] emsg
1063+ * @param[out] result describes node
1064+ *	or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node.
1065+ * @param all_zones to list all instead of only requested zones
1066+ * @param[in,out] rsp state from librpz_itr_start()
1067+ * @return: false on error
1068+ */
1069+typedef bool (librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result,
1070+				 bool all_zones, librpz_rsp_t *rsp);
1071+LIBDEF_F(itr_node)
1072+
1073+/**
1074+ * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size
1075+ */
1076+typedef const char *(librpz_policy2str_t)(librpz_policy_t policy,
1077+					  char *buf, size_t buf_size);
1078+#define POLICY2STR_SIZE sizeof("policy xxxxxx")
1079+LIBDEF_F(policy2str)
1080+
1081+/**
1082+ * Trigger type to string.
1083+ */
1084+typedef const char *(librpz_trig2str_t)(librpz_trig_t trig);
1085+LIBDEF_F(trig2str)
1086+
1087+/**
1088+ * Convert a number of seconds to a zone file duration string
1089+ */
1090+typedef const char *(librpz_secs2str_t)(time_t secs,
1091+					char *buf, size_t buf_size);
1092+#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s")
1093+LIBDEF_F(secs2str)
1094+
1095+/**
1096+ * Parse a duration with 's', 'm', 'h', 'd', and 'w' units.
1097+ */
1098+typedef bool (librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val,
1099+				 const char *str0);
1100+LIBDEF_F(str2secs)
1101+
1102+/**
1103+ * Translate selected rtypes to strings
1104+ */
1105+typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size);
1106+#define RTYPE2STR_SIZE sizeof("type xxxxx")
1107+LIBDEF_F(rtype2str)
1108+
1109+/**
1110+ * Local version of ns_name_ntop() for portability.
1111+ */
1112+typedef int (librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz);
1113+LIBDEF_F(domain_ntop)
1114+
1115+/**
1116+ * Local version of ns_name_pton().
1117+ */
1118+typedef int (librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz,
1119+				    size_t *dstlen, bool lower);
1120+LIBDEF_F(domain_pton2)
1121+
1122+typedef union socku socku_t;
1123+typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp,
1124+				     in_port_t port);
1125+LIBDEF_F(mk_inet_su)
1126+
1127+typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su, const
1128+					struct in6_addr *addrp,
1129+					uint32_t scope_id, in_port_t port);
1130+LIBDEF_F(mk_inet6_su)
1131+
1132+typedef bool (librpz_str2su_t)(socku_t *sup, const char *str);
1133+LIBDEF_F(str2su)
1134+
1135+typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su);
1136+LIBDEF_F(su2str)
1137+#define SU2STR_SIZE (INET6_ADDRSTRLEN+1+6+1)
1138+
1139+
1140+/**
1141+ * default path to dnsrpzd
1142+ */
1143+const char *librpz_dnsrpzd_path;
1144+
1145+
1146+#undef LIBDEF
1147+
1148+/*
1149+ * This is the dlopen() interface to librpz.
1150+ */
1151+typedef const struct {
1152+	const char			*dnsrpzd_path;
1153+	const char			*version;
1154+	librpz_parse_log_opt_t		*parse_log_opt;
1155+	librpz_log_level_val_t		*log_level_val;
1156+	librpz_set_log_t		*set_log;
1157+	librpz_vpemsg_t			*vpemsg;
1158+	librpz_pemsg_t			*pemsg;
1159+	librpz_vlog_t			*vlog;
1160+	librpz_log_t			*log;
1161+	librpz_fatal_t			*fatal LIBRPZ_NORET;
1162+	librpz_rpz_assert_t		*rpz_assert LIBRPZ_NORET;
1163+	librpz_rpz_vassert_t		*rpz_vassert LIBRPZ_NORET;
1164+	librpz_clist_create_t		*clist_create;
1165+	librpz_clist_detach_t		*clist_detach;
1166+	librpz_client_create_t		*client_create;
1167+	librpz_connect_t		*connect;
1168+	librpz_client_detach_t		*client_detach;
1169+	librpz_rsp_create_t		*rsp_create;
1170+	librpz_rsp_detach_t		*rsp_detach;
1171+	librpz_rsp_result_t		*rsp_result;
1172+	librpz_have_trig_t		*have_trig;
1173+	librpz_have_ns_trig_t		*have_ns_trig;
1174+	librpz_rsp_clientip_prefix_t	*rsp_clientip_prefix;
1175+	librpz_rsp_domain_t		*rsp_domain;
1176+	librpz_rsp_rr_t			*rsp_rr;
1177+	librpz_rsp_soa_t		*rsp_soa;
1178+	librpz_soa_serial_t		*soa_serial;
1179+	librpz_rsp_push_t		*rsp_push;
1180+	librpz_rsp_pop_t		*rsp_pop;
1181+	librpz_rsp_pop_discard_t	*rsp_pop_discard;
1182+	librpz_rsp_forget_zone_t	*rsp_forget_zone;
1183+	librpz_ck_ip_t			*ck_ip;
1184+	librpz_ck_domain_t		*ck_domain;
1185+	librpz_zone_refresh_t		*zone_refresh;
1186+	librpz_db_info_t		*db_info;
1187+	librpz_itr_start_t		*itr_start;
1188+	librpz_mf_stats_t		*mf_stats;
1189+	librpz_vers_stats_t		*vers_stats;
1190+	librpz_itr_zone_t		*itr_zone;
1191+	librpz_itr_node_t		*itr_node;
1192+	librpz_policy2str_t		*policy2str;
1193+	librpz_trig2str_t		*trig2str;
1194+	librpz_secs2str_t		*secs2str;
1195+	librpz_str2secs_t		*str2secs;
1196+	librpz_rtype2str_t		*rtype2str;
1197+	librpz_domain_ntop_t		*domain_ntop;
1198+	librpz_domain_pton2_t		*domain_pton2;
1199+	librpz_mk_inet_su_t		*mk_inet_su;
1200+	librpz_mk_inet6_su_t		*mk_inet6_su;
1201+	librpz_str2su_t			*str2su;
1202+	librpz_su2str_t			*su2str;
1203+} librpz_0_t;
1204+extern librpz_0_t librpz_def_0;
1205+
1206+/*
1207+ * Future versions can be upward compatible by defining LIBRPZ_DEF as
1208+ * librpz_X_t.
1209+ */
1210+#define LIBRPZ_DEF	librpz_def_0
1211+#define LIBRPZ_DEF_STR	"librpz_def_0"
1212+
1213+typedef librpz_0_t librpz_t;
1214+extern librpz_t *librpz;
1215+
1216+
1217+#if LIBRPZ_LIB_OPEN == 2
1218+#include <dlfcn.h>
1219+
1220+/**
1221+ * link-load librpz
1222+ * @param[out] emsg: error message
1223+ * @param[in,out] dl_handle: NULL or pointer to new dlopen handle
1224+ * @param[in] path: librpz.so path
1225+ * @return address of interface structure or NULL on failure
1226+ */
1227+static inline librpz_t *
1228+librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path)
1229+{
1230+	void *handle;
1231+	librpz_t *new_librpz;
1232+
1233+	emsg->c[0] = '\0';
1234+
1235+	/*
1236+	 * Close a previously opened handle on librpz.so.
1237+	 */
1238+	if (dl_handle != NULL && *dl_handle != NULL) {
1239+		if (dlclose(*dl_handle) != 0) {
1240+			snprintf(emsg->c, sizeof(librpz_emsg_t),
1241+				 "dlopen(NULL): %s", dlerror());
1242+			return (NULL);
1243+		}
1244+		*dl_handle = NULL;
1245+	}
1246+
1247+	/*
1248+	 * First try the main executable of the process in case it was
1249+	 * linked to librpz.
1250+	 * Do not worry if we cannot search the main executable of the process.
1251+	 */
1252+	handle = dlopen(NULL, RTLD_NOW | RTLD_LOCAL);
1253+	if (handle != NULL) {
1254+		new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
1255+		if (new_librpz != NULL) {
1256+			if (dl_handle != NULL)
1257+				*dl_handle = handle;
1258+			return (new_librpz);
1259+		}
1260+		if (dlclose(handle) != 0) {
1261+			snprintf(emsg->c, sizeof(librpz_emsg_t),
1262+				 "dlsym(NULL, "LIBRPZ_DEF_STR"): %s",
1263+				 dlerror());
1264+			return (NULL);
1265+		}
1266+	}
1267+
1268+	if (path == NULL || path[0] == '\0') {
1269+		snprintf(emsg->c, sizeof(librpz_emsg_t),
1270+			 "librpz not linked and no dlopen() path provided");
1271+		return (NULL);
1272+	}
1273+
1274+	handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
1275+	if (handle == NULL) {
1276+		snprintf(emsg->c, sizeof(librpz_emsg_t), "dlopen(%s): %s",
1277+			 path, dlerror());
1278+		return (NULL);
1279+	}
1280+	new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
1281+	if (new_librpz != NULL) {
1282+		if (dl_handle != NULL)
1283+			*dl_handle = handle;
1284+		return (new_librpz);
1285+	}
1286+	snprintf(emsg->c, sizeof(librpz_emsg_t),
1287+		 "dlsym(%s, "LIBRPZ_DEF_STR"): %s",
1288+		 path, dlerror());
1289+	dlclose(handle);
1290+	return (NULL);
1291+}
1292+
1293+#elif defined(LIBRPZ_LIB_OPEN)
1294+
1295+/*
1296+ * Statically link to the librpz.so DSO on systems without dlopen()
1297+ */
1298+static inline librpz_t *
1299+librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path)
1300+{
1301+	(void)(path);
1302+
1303+	if (dl_handle != NULL)
1304+		*dl_handle = NULL;
1305+
1306+#if LIBRPZ_LIB_OPEN == 1
1307+	emsg->c[0] = '\0';
1308+	return (&LIBRPZ_DEF);
1309+#else
1310+	snprintf(emsg->c, sizeof(librpz_emsg_t),
1311+		 "librpz not available via ./configure");
1312+	return (NULL);
1313+#endif /* LIBRPZ_LIB_OPEN */
1314+}
1315+#endif /* LIBRPZ_LIB_OPEN */
1316+
1317+#endif /* LIBRPZ_H */
1318diff --git a/fastrpz/rpz.c b/fastrpz/rpz.c
1319new file mode 100644
1320index 00000000..c5ab7801
1321--- /dev/null
1322+++ b/fastrpz/rpz.c
1323@@ -0,0 +1,1352 @@
1324+/*
1325+ * fastrpz/rpz.c - interface to the fastrpz response policy zone library
1326+ *
1327+ * Optimize no-rewrite cases for speed but optimize rewriting for
1328+ * simplicity and size.
1329+ */
1330+
1331+#include "config.h"
1332+
1333+#ifdef ENABLE_FASTRPZ
1334+#include "daemon/daemon.h"
1335+#define LIBRPZ_LIB_OPEN FASTRPZ_LIB_OPEN
1336+#include "fastrpz/rpz.h"
1337+#include "daemon/worker.h"
1338+#include "iterator/iter_delegpt.h"
1339+#include "iterator/iter_utils.h"
1340+#include "iterator/iterator.h"
1341+#include "util/data/dname.h"
1342+#include "util/data/msgencode.h"
1343+#include "util/data/msgparse.h"
1344+#include "util/data/msgreply.h"
1345+#include "util/log.h"
1346+#include "util/netevent.h"
1347+#include "util/net_help.h"
1348+#include "util/regional.h"
1349+#include "util/storage/slabhash.h"
1350+#include "services/cache/dns.h"
1351+#include "services/cache/rrset.h"
1352+#include "services/mesh.h"
1353+#include "sldns/sbuffer.h"
1354+#include "sldns/rrdef.h"
1355+
1356+
1357+typedef enum state {
1358+	/* No more rewriting */
1359+	st_off = 1,
1360+	/* Send SERVFAIL */
1361+	st_servfail,
1362+	/* No dispositive hit yet */
1363+	st_unknown,
1364+	/* Let the iterator resolve a CNAME or get a delegation point. */
1365+	st_iterate,
1366+	/* Let the iterator resolve NS to check NSIP or NSDNAME triggers. */
1367+	st_ck_ns,
1368+	/* We have an answer */
1369+	st_rewritten,
1370+} st_t;
1371+
1372+
1373+/* RPZ state pointed to by struct comm_reply */
1374+typedef struct commreply_rpz {
1375+	/* librpz state */
1376+	librpz_rsp_t*	rsp;
1377+	/* ID for log messages */
1378+	int		log_id;
1379+
1380+	/* from configuration */
1381+	int		min_ns_dots;
1382+
1383+	/* Running in the iterator */
1384+	bool		iterating;
1385+
1386+	/* current and previous state and librpz result */
1387+	st_t		st;
1388+	st_t		saved_st[LIBRPZ_RSP_STACK_DEPTH-1];
1389+	librpz_result_t result;
1390+
1391+	/* Stop adding CNAMEs to the prepend list before this owner name. */
1392+	librpz_domain_buf_t cname_hit;
1393+	/* It is not the first CNAME */
1394+	bool		cname_hit_2nd;
1395+	librpz_result_id_t hit_id;
1396+} commreply_rpz_t;
1397+
1398+
1399+/* Generate an ID for log messages. */
1400+static int log_id;
1401+
1402+librpz_t *librpz;
1403+
1404+
1405+static void LIBRPZ_NORET
1406+rpz_assert(const char *s)
1407+{
1408+	fatal_exit("%s", s);
1409+	exit(1);
1410+}
1411+#define RPZ_ASSERT(c) ((c) ? (void)0 : rpz_assert(#c), (void)0)
1412+
1413+/*
1414+ * librpz client handle locking
1415+ */
1416+static void
1417+lock_destroy(void* mutex)
1418+{
1419+	lock_basic_destroy(mutex);
1420+	free(mutex);
1421+}
1422+
1423+static void
1424+lock(void* mutex)
1425+{
1426+	lock_basic_lock(mutex);
1427+}
1428+
1429+static void
1430+unlock(void* mutex)
1431+{
1432+	lock_basic_unlock(mutex);
1433+}
1434+
1435+
1436+static void
1437+log_fnc(librpz_log_level_t level, void* ATTR_UNUSED(ctx), const char* buf)
1438+{
1439+	/* Setting librpz_log_level overrides the unbound "verbose" level. */
1440+	if(level > LIBRPZ_LOG_TRACE1 &&
1441+	   level <= librpz->log_level_val(LIBRPZ_LOG_INVALID))
1442+		level = LIBRPZ_LOG_TRACE1;
1443+
1444+	switch(level) {
1445+	case LIBRPZ_LOG_FATAL:
1446+	case LIBRPZ_LOG_ERROR:		/* errors */
1447+	default:
1448+		log_err("rpz: %s", buf);
1449+		break;
1450+
1451+	case LIBRPZ_LOG_TRACE1:		/* big events such as dnsrpzd starts */
1452+		verbose(VERB_OPS, "rpz: %s", buf);
1453+		break;
1454+
1455+	case LIBRPZ_LOG_TRACE2:		/* smaller dnsrpzd zone transfers */
1456+		verbose(VERB_DETAIL, "rpz: %s", buf);
1457+		break;
1458+
1459+	case LIBRPZ_LOG_TRACE3:		/* librpz hits */
1460+		verbose(VERB_QUERY, "rpz: %s", buf);
1461+		break;
1462+
1463+	case LIBRPZ_LOG_TRACE4:		/* librpz lookups */
1464+		verbose(VERB_CLIENT, "rpz: %s", buf);
1465+		break;
1466+	}
1467+}
1468+
1469+
1470+/* Release the librpz version. */
1471+static void
1472+rpz_off(commreply_rpz_t* rpz, st_t st)
1473+{
1474+	if(!rpz)
1475+		return;
1476+	rpz->st = st;
1477+	librpz->rsp_detach(&rpz->rsp);
1478+}
1479+
1480+
1481+static void LIBRPZ_PF(2,3)
1482+log_fail(commreply_rpz_t* rpz, const char* p, ...)
1483+{
1484+	va_list args;
1485+
1486+	if(rpz->st == st_servfail)
1487+		return;
1488+
1489+	va_start(args, p);
1490+	librpz->vlog(LIBRPZ_LOG_ERROR, rpz, p, args);
1491+	va_end(args);
1492+	if(!rpz)
1493+		return;
1494+	rpz_off(rpz, st_servfail);
1495+}
1496+
1497+
1498+/* Announce a rewrite. */
1499+static void
1500+log_rewrite(uint8_t* qname, librpz_policy_t policy, const char* msg,
1501+	    commreply_rpz_t* rpz)
1502+{
1503+	char policy_buf[POLICY2STR_SIZE];
1504+	char qname_nm[LDNS_MAX_DOMAINLEN+1];
1505+	librpz_domain_buf_t tdomain;
1506+	char tdomain_nm[LDNS_MAX_DOMAINLEN+1];
1507+	librpz_emsg_t emsg;
1508+
1509+	if(rpz->st == st_servfail || !rpz->result.log)
1510+		return;
1511+	if(librpz->log_level_val(LIBRPZ_LOG_INVALID) < LIBRPZ_LOG_TRACE1)
1512+		return;
1513+
1514+	dname_str(qname, qname_nm);
1515+
1516+	if(!librpz->rsp_domain(&emsg, &tdomain, rpz->rsp)) {
1517+		librpz->log(LIBRPZ_LOG_ERROR, rpz, "%s", emsg.c);
1518+		return;
1519+	}
1520+	dname_str(tdomain.d, tdomain_nm);
1521+
1522+	librpz->log(LIBRPZ_LOG_TRACE3, rpz, "%srewriting %s via %s %s to %s",
1523+		    msg, qname_nm, tdomain_nm,
1524+		    librpz->trig2str(rpz->result.trig),
1525+		    librpz->policy2str(policy, policy_buf,
1526+				       sizeof(policy_buf)));
1527+}
1528+
1529+
1530+/* Connect to and start dnsrpzd if necessary for the unbound daemon.
1531+ *	Require "rpz-conf: path" to specify the rpz configuration file.
1532+ *	The unbound server directory name is the default rpz working
1533+ *	    directory.  If unbound uses chroot, then the dnsrpzd working
1534+ *	    directory must be in the chroot tree.
1535+ *	The database and socket are closed and re-opened.
1536+ */
1537+void
1538+rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient,
1539+	 const struct config_file* cfg)
1540+{
1541+	lock_basic_type* mutex;
1542+	librpz_emsg_t emsg;
1543+
1544+	if(!librpz) {
1545+		librpz = librpz_lib_open(&emsg, NULL, FASTRPZ_LIBRPZ_PATH);
1546+		if(!librpz)
1547+			fatal_exit("rpz: %s", emsg.c);
1548+	}
1549+
1550+	librpz->set_log(&log_fnc, NULL);
1551+
1552+	if(!cfg->rpz_cstr)
1553+		fatal_exit("rpz: rpz-zone: not set");
1554+
1555+	librpz->client_detach(pclient);
1556+	librpz->clist_detach(pclist);
1557+
1558+	mutex = malloc(sizeof(*mutex));
1559+	if(!mutex)
1560+		fatal_exit("rpz: no memory for lock");
1561+	lock_basic_init(mutex);
1562+
1563+	*pclist = librpz->clist_create(&emsg, &lock, &unlock, &lock_destroy,
1564+				       mutex, NULL);
1565+	if(!pclist)
1566+		fatal_exit("rpz: %s", emsg.c);
1567+
1568+	*pclient = librpz->client_create(&emsg, *pclist, cfg->rpz_cstr, false);
1569+	if(!*pclient)
1570+		fatal_exit("rpz: %s", emsg.c);
1571+
1572+	if(!librpz->connect(&emsg, *pclient, true))
1573+		fatal_exit("rpz: %s", emsg.c);
1574+
1575+	verbose(VERB_OPS, "rpz: librpz version %s", librpz->version);
1576+}
1577+
1578+
1579+/* Stop using librpz on behalf of a worker thread. */
1580+void
1581+rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient)
1582+{
1583+	if(librpz) {
1584+		librpz->client_detach(pclient);
1585+		librpz->clist_detach(pclist);
1586+	}
1587+}
1588+
1589+
1590+/* Release the librpz resources held for a DNS client request. */
1591+void
1592+rpz_end(struct comm_reply* commreply)
1593+{
1594+	if(!commreply->rpz)
1595+		return;
1596+	rpz_off(commreply->rpz, commreply->rpz->st);
1597+	free(commreply->rpz);
1598+	commreply->rpz = NULL;
1599+}
1600+
1601+
1602+static bool
1603+push_st(commreply_rpz_t* rpz)
1604+{
1605+	librpz_emsg_t emsg;
1606+
1607+	if(rpz->st == st_off || rpz->st == st_servfail) {
1608+		librpz->log(LIBRPZ_LOG_ERROR, rpz,
1609+			    "state %d in push_st()", rpz->st);
1610+		return false;
1611+	}
1612+	if(!librpz->rsp_push(&emsg, rpz->rsp))
1613+		log_fail(rpz, "%s", emsg.c);
1614+	memmove(&rpz->saved_st[1], &rpz->saved_st[0],
1615+		sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
1616+	rpz->saved_st[0] = rpz->st;
1617+	return rpz->st != st_servfail;
1618+}
1619+
1620+
1621+static bool
1622+pop_st(commreply_rpz_t* rpz)
1623+{
1624+	librpz_emsg_t emsg;
1625+
1626+	if(rpz->rsp && !librpz->rsp_pop(&emsg, &rpz->result, rpz->rsp))
1627+		log_fail(rpz, "%s", emsg.c);
1628+	if(rpz->st != st_servfail)
1629+		rpz->st = rpz->saved_st[0];
1630+	memmove(&rpz->saved_st[0], &rpz->saved_st[1],
1631+		sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
1632+	return rpz->st != st_servfail;
1633+}
1634+
1635+static bool
1636+pop_discard_st(commreply_rpz_t* rpz)
1637+{
1638+	librpz_emsg_t emsg;
1639+
1640+	if(rpz->rsp && !librpz->rsp_pop_discard(&emsg, rpz->rsp))
1641+		log_fail(rpz, "%s", emsg.c);
1642+	memmove(&rpz->saved_st[0], &rpz->saved_st[1],
1643+		sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
1644+	return rpz->st != st_servfail;
1645+}
1646+
1647+/* Check a rewrite attempt for errors and a disabled zone. */
1648+static bool				/* true=repeat the check */
1649+ck_after(uint8_t* qname, bool recursed, librpz_trig_t trig,
1650+	 commreply_rpz_t* rpz)
1651+{
1652+	librpz_emsg_t emsg;
1653+
1654+	if(rpz->st == st_servfail)
1655+		return false;
1656+
1657+	if(!librpz->rsp_result(&emsg, &rpz->result, recursed, rpz->rsp)) {
1658+		log_fail(rpz, "%s", emsg.c);
1659+		return false;
1660+	}
1661+
1662+	if(rpz->result.policy == LIBRPZ_POLICY_DISABLED) {
1663+		/* Log the hit on the disabled zone, do not try the zone again,
1664+		 * and restore the state from before the check to forget the hit
1665+		 * before trying again. */
1666+		log_rewrite(qname, rpz->result.zpolicy, "disabled ", rpz);
1667+		if(!librpz->rsp_forget_zone(&emsg, rpz->result.cznum, rpz->rsp))
1668+			log_fail(rpz, "%s", emsg.c);
1669+		return pop_st(rpz);
1670+	}
1671+
1672+	/* Complain about and forget client-IP address hit that is not
1673+	 * dispositive.  Client-IP triggers have the highest priority
1674+	 * within a policy zone, but can be overridden by any hit in a policy
1675+	 * earlier in the client's (resolver's) list of zones, including
1676+	 * policies that cannot be hit until after recursion. If we allowed
1677+	 * client-IP triggers in secondary zones, then than two DNS requests
1678+	 * that differ only in DNS client-IP addresses could properly
1679+	 * have differing results.  The Unbound iterator treats identical
1680+	 * DNS requests the same regardless of DNS client-IP address.
1681+	 * struct query_info would need to be modified to have an optional
1682+	 * librpz_prefix_t containing the prefix of the client-IP address hit
1683+	 * from librpz->rsp_clientip_prefix().  Adding to struct query_info
1684+	 * would require finding and changing the many and obscure places
1685+	 * including the Unbound tests to memset(0) the struct query_info
1686+	 * that they create. */
1687+	if(trig == LIBRPZ_TRIG_CLIENT_IP) {
1688+		if(rpz->result.cznum != 0) {
1689+			log_rewrite(qname, rpz->result.policy,
1690+				     "ignore secondary ", rpz);
1691+			if(!pop_st(rpz))
1692+				log_fail(rpz, "%s", emsg.c);
1693+			return (false);
1694+		}
1695+	}
1696+
1697+	/* Forget the state from before the check and keep the new state
1698+	 * if we do not have a hit on a disabled policy zone. */
1699+	pop_discard_st(rpz);
1700+	return false;
1701+}
1702+
1703+
1704+/* Get the next RR from the policy record. */
1705+static bool
1706+next_rr(librpz_rr_t** rrp, const uint8_t* qname, size_t qname_len,
1707+	commreply_rpz_t* rpz)
1708+{
1709+	librpz_emsg_t emsg;
1710+
1711+	if(!librpz->rsp_rr(&emsg, NULL, NULL, NULL, rrp, &rpz->result,
1712+			   qname, qname_len, rpz->rsp)) {
1713+		log_fail(rpz, "%s", emsg.c);
1714+		*rrp = NULL;
1715+		return false;
1716+	}
1717+	return true;
1718+}
1719+
1720+
1721+static bool				/* false=fatal error to be logged */
1722+add_rr(struct sldns_buffer* pkt, const uint8_t* owner, size_t owner_len,
1723+       librpz_rr_t* rr, commreply_rpz_t* rpz)
1724+{
1725+	size_t rdlength;
1726+
1727+	rdlength = ntohs(rr->rdlength);
1728+
1729+	if(!sldns_buffer_available(pkt, owner_len + 10 + rdlength)) {
1730+		log_fail(rpz, "comm_reply buffer exhausted");
1731+		free(rr);
1732+		return false;
1733+	}
1734+	sldns_buffer_write(pkt, owner, owner_len);
1735+	/* sizeof(librpz_rr_t)=12 instead of 10 */
1736+	sldns_buffer_write(pkt, rr, 10 + rdlength);
1737+	return true;
1738+}
1739+
1740+
1741+/* Convert a fake incoming DNS message to an Unbound struct dns_msg */
1742+static void
1743+pkt2dns_msg(struct dns_msg** dnsmsg, struct sldns_buffer* pkt,
1744+	    commreply_rpz_t* rpz, struct regional* region)
1745+{
1746+	struct msg_parse* msgparse;
1747+
1748+	msgparse = regional_alloc(region, sizeof(*msgparse));
1749+	if(!msgparse) {
1750+		log_fail(rpz, "out of memory for msgparse");
1751+		*dnsmsg = NULL;
1752+		return;
1753+	}
1754+	memset(msgparse, 0, sizeof(*msgparse));
1755+	if(parse_packet(pkt, msgparse, region) != LDNS_RCODE_NOERROR) {
1756+		log_fail(rpz, "packet parse error");
1757+		*dnsmsg = NULL;
1758+		return;
1759+	}
1760+	*dnsmsg = dns_alloc_msg(pkt, msgparse, region);
1761+	if(!*dnsmsg) {
1762+		log_fail(rpz, "dns_alloc_msg() failed");
1763+		*dnsmsg = NULL;
1764+		return;
1765+	}
1766+	(*dnsmsg)->rep->security = sec_status_rpz_rewritten;
1767+}
1768+
1769+
1770+static bool				/* false=SERVFAIL */
1771+ck_ip_rrset(const void* vdata, int family, librpz_trig_t trig,
1772+	    uint8_t* qname, commreply_rpz_t* rpz)
1773+{
1774+	const struct packed_rrset_data* data;
1775+	uint rr_n;
1776+	size_t len;
1777+	librpz_emsg_t emsg;
1778+
1779+	data = vdata;
1780+
1781+	/* Loop to ignore disabled zones. */
1782+	do {
1783+		if(!push_st(rpz))
1784+			return false;
1785+		for(rr_n = 0; rr_n < data->count; ++rr_n) {
1786+			len = data->rr_len[rr_n];
1787+			/* Skip bogus including negative placeholding rdata. */
1788+			if((family == AF_INET &&
1789+			    len != sizeof(struct in_addr)+2) ||
1790+			   (family == AF_INET6 &&
1791+			    len != sizeof(struct in6_addr)+2))
1792+				continue;
1793+			if(!librpz->ck_ip(&emsg, data->rr_data[rr_n]+2,
1794+					  family, trig, rpz->hit_id, true,
1795+					  rpz->rsp)) {
1796+				log_fail(rpz, "%s", emsg.c);
1797+				return false;
1798+			}
1799+		}
1800+	} while(ck_after(qname, true, trig, rpz));
1801+	return rpz->st != st_servfail;
1802+}
1803+
1804+
1805+static bool				/* false=SERVFAIL */
1806+ck_dname(uint8_t* dname, size_t dname_size, librpz_trig_t trig,
1807+	 uint8_t* qname, bool recursed, commreply_rpz_t* rpz)
1808+{
1809+	librpz_emsg_t emsg;
1810+
1811+	/* Refuse to check the root. */
1812+	if(dname_is_root(dname))
1813+		return rpz->st != st_servfail;
1814+
1815+	/* Loop to ignore disabled zones. */
1816+	do {
1817+		if(!push_st(rpz))
1818+			return false;
1819+		if(!librpz->ck_domain(&emsg, dname, dname_size, trig,
1820+				      rpz->hit_id, recursed, rpz->rsp)) {
1821+			log_fail(rpz, "%s", emsg.c);
1822+			return false;
1823+		}
1824+	} while(ck_after(qname, recursed, trig, rpz));
1825+
1826+	return rpz->st != st_servfail;
1827+}
1828+
1829+
1830+/* Check the IPv4 or IPv6 addresses for one NS name. */
1831+static bool				/* false=st_servfail */
1832+ck_1nsip(uint8_t* nsname, size_t nsname_size, int family, int qtype,
1833+	 bool* have_ns, commreply_rpz_t* rpz, struct module_env* env)
1834+{
1835+	struct ub_packed_rrset_key* akey;
1836+
1837+	akey = rrset_cache_lookup(env->rrset_cache, nsname, nsname_size,
1838+				  qtype, LDNS_RR_CLASS_IN, 0, 0, 0);
1839+	if(akey) {
1840+		*have_ns = true;
1841+
1842+		if(!ck_ip_rrset(akey->entry.data, family, LIBRPZ_TRIG_NSIP,
1843+				nsname, rpz)) {
1844+			lock_rw_unlock(&akey->entry.lock);
1845+			return false;
1846+		}
1847+		lock_rw_unlock(&akey->entry.lock);
1848+	}
1849+	return true;
1850+}
1851+
1852+
1853+static bool				/* false=st_servfail */
1854+ck_qname(uint8_t* qname, size_t qname_len,
1855+	 bool recursed,			/* recursion done */
1856+	 bool wait_ns,			/* willing to iterate for NS data */
1857+	 commreply_rpz_t* rpz, struct module_env* env)
1858+{
1859+	uint8_t* dname;
1860+	size_t dname_size;
1861+	int cur_lab;
1862+	struct ub_packed_rrset_key* nskey;
1863+	const struct packed_rrset_data* nsdata;
1864+	uint8_t* nsname;
1865+	size_t nsname_size;
1866+	uint rr_n;
1867+	bool have_ns, tried_ns;
1868+
1869+	if(!ck_dname(qname, qname_len, LIBRPZ_TRIG_QNAME, qname, false, rpz))
1870+		return false;
1871+
1872+	/* Do not waste time looking for NSDNAME and NSIP hits when there
1873+	 * are no currently relevant triggers. */
1874+	if(!librpz->have_ns_trig(rpz->rsp))
1875+		return true;
1876+
1877+	have_ns = false;
1878+	tried_ns = false;
1879+	dname = qname;
1880+	dname_size = qname_len;
1881+	for(cur_lab = dname_count_labels(dname) - 2;
1882+	    cur_lab > rpz->min_ns_dots;
1883+	    --cur_lab) {
1884+		tried_ns = true;
1885+		dname_remove_label(&dname, &dname_size);
1886+		nskey = rrset_cache_lookup(env->rrset_cache, dname, dname_size,
1887+					   LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN,
1888+					   0, 0, 0);
1889+		if(!nskey)
1890+			continue;
1891+
1892+		nsdata = (const struct packed_rrset_data*)nskey->entry.data;
1893+		for(rr_n = 0;
1894+		    rr_n < nsdata->count && rpz->st == st_unknown;
1895+		    ++rr_n) {
1896+			nsname = nsdata->rr_data[rr_n]+2;
1897+			nsname_size = nsdata->rr_len[rr_n];
1898+			if(nsname_size <= 2)
1899+				continue;
1900+			nsname_size -= 2;
1901+			if(!ck_dname(nsname, nsname_size, LIBRPZ_TRIG_NSDNAME,
1902+				     qname, recursed, rpz))
1903+				return false;
1904+			if(!ck_1nsip(nsname, nsname_size, AF_INET,
1905+				      LDNS_RR_TYPE_A, &have_ns, rpz, env))
1906+				return false;
1907+			if(!ck_1nsip(nsname, nsname_size, AF_INET6,
1908+				      LDNS_RR_TYPE_AAAA, &have_ns, rpz, env))
1909+				return false;
1910+		}
1911+		lock_rw_unlock(&nskey->entry.lock);
1912+	}
1913+
1914+	/* If we failed to find NS records, then stop building the response
1915+	 * before a CNAME with this owner name. */
1916+	if(!have_ns && tried_ns && (!recursed || wait_ns)) {
1917+		rpz->cname_hit.size = qname_len;
1918+		RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d));
1919+		memcpy(rpz->cname_hit.d, qname, qname_len);
1920+		rpz->result.hit_id = rpz->hit_id;
1921+		rpz->st = st_ck_ns;
1922+	}
1923+	return true;
1924+}
1925+
1926+
1927+/*
1928+ * Are we ready to rewrite the response?
1929+ */
1930+static bool				/* true=send rewritten response */
1931+ck_result(uint8_t* qname, bool recursed,
1932+	  commreply_rpz_t* rpz, const struct comm_point* commpoint)
1933+{
1934+	librpz_emsg_t emsg;
1935+
1936+	switch(rpz->st) {
1937+	case st_off:
1938+	case st_servfail:
1939+	case st_rewritten:
1940+		return false;
1941+	case st_unknown:
1942+		break;
1943+	case st_iterate:
1944+		return false;
1945+	case st_ck_ns:
1946+		/* An NSDNAME or NSIP check failed for lack of cached data. */
1947+		return false;
1948+	default:
1949+		fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
1950+			   rpz->st);
1951+	}
1952+
1953+	/* Wait for a trigger. */
1954+	if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED) {
1955+		if(recursed &&
1956+		    rpz->result.zpolicy != LIBRPZ_POLICY_UNDEFINED &&
1957+		    !librpz->rsp_result(&emsg, &rpz->result, true, rpz->rsp)) {
1958+			log_fail(rpz, "%s", emsg.c);
1959+			return false;
1960+		}
1961+		if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED)
1962+			return false;
1963+	}
1964+
1965+	if(rpz->result.policy == LIBRPZ_POLICY_PASSTHRU) {
1966+		log_rewrite(qname, rpz->result.policy, "", rpz);
1967+		rpz_off(rpz, st_off);
1968+		return false;
1969+	}
1970+
1971+	/* The TCP-only policy answers UDP requests with truncated responses. */
1972+	if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY &&
1973+	   commpoint->type == comm_tcp) {
1974+		rpz_off(rpz, st_off);
1975+		return false;
1976+	}
1977+
1978+	return true;
1979+}
1980+
1981+
1982+/*
1983+ * Convert an RPZ hit to a struct dns_msg
1984+ */
1985+static void
1986+get_result_msg(struct dns_msg** dnsmsg, struct query_info* qinfo,
1987+	       uint16_t id, uint16_t flags, bool recursed, commreply_rpz_t* rpz,
1988+	       struct comm_point* commpoint, struct regional* region)
1989+{
1990+	librpz_rr_t* rr;
1991+	librpz_domain_buf_t origin;
1992+	struct sldns_buffer* pkt;
1993+	uint16_t num_rrs;
1994+	librpz_emsg_t emsg;
1995+
1996+	*dnsmsg = NULL;
1997+	if(!ck_result(qinfo->qname, recursed, rpz, commpoint))
1998+		return;
1999+
2000+	rpz->st = st_rewritten;
2001+
2002+	if(rpz->result.policy == LIBRPZ_POLICY_DROP) {
2003+		log_rewrite(qinfo->qname, rpz->result.policy, "", rpz);
2004+		/* Make a fake cached message to carry
2005+		 * sec_status_rpz_drop and be dropped. */
2006+		error_encode(commpoint->buffer, LDNS_RCODE_NOERROR,
2007+			     qinfo, id, flags, NULL);
2008+		pkt2dns_msg(dnsmsg, commpoint->buffer, rpz, region);
2009+		(*dnsmsg)->rep->security = sec_status_rpz_drop;
2010+		return;
2011+	}
2012+
2013+	/* Create a DNS message of the RPZ data.
2014+	 * In many cases that message could be sent directly to the DNS client,
2015+	 * but sometimes iteration must be used to resolve a CNAME.
2016+	 * This need not be fast, because rewriting responses should be rare.
2017+	 * Therefore, use the simpler but slower tactic of generating a
2018+	 * parsed  version of the message. */
2019+
2020+	flags &= ~BIT_AA;
2021+	flags |= BIT_QR | BIT_RA;
2022+	rr = NULL;
2023+
2024+	/* The TCP-only policy answers UDP requests with truncated responses. */
2025+	if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY) {
2026+		flags |= BIT_TC;
2027+
2028+	} else if(rpz->result.policy == LIBRPZ_POLICY_NXDOMAIN) {
2029+		flags |= LDNS_RCODE_NXDOMAIN;
2030+
2031+	} else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) {
2032+		if(!rpz->iterating &&
2033+		   qinfo->qtype != LDNS_RR_TYPE_CNAME) {
2034+			/* The new DNS message would be a CNAME and
2035+			 * the external request was not for a CNAME.
2036+			 * The worker must punt to the iterator so that
2037+			 * the iterator can resolve the CNAME. */
2038+			rpz->st = st_iterate;
2039+			return;
2040+		}
2041+		next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
2042+
2043+	} else if(rpz->result.policy == LIBRPZ_POLICY_RECORD ||
2044+		  rpz->result.policy == LIBRPZ_POLICY_NODATA) {
2045+		next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
2046+		/* Punt to the iterator if the new DNS message would
2047+		 * be a CNAME that must be resolved. */
2048+		if(!rpz->iterating &&
2049+		   qinfo->qtype != LDNS_RR_TYPE_CNAME &&
2050+		   rr && rr->type == ntohs(LDNS_RR_TYPE_CNAME)) {
2051+			free(rr);
2052+			rpz->st = st_iterate;
2053+			return;
2054+		}
2055+	}
2056+	log_rewrite(qinfo->qname, rpz->result.policy, "", rpz);
2057+
2058+	/* Make a buffer containing a DNS message with the RPZ data. */
2059+	pkt = commpoint->buffer;
2060+	sldns_buffer_clear(pkt);
2061+	if(sldns_buffer_remaining(pkt) < LDNS_HEADER_SIZE) {
2062+		log_fail(rpz, "comm_reply buffer too small for header");
2063+		if(rr)
2064+			free(rr);
2065+		return;
2066+	}
2067+
2068+	/* Install ID, flags, QDCOUNT=1, ANCOUNT=# of RPZ RRs, NSCOUNT=0,
2069+	 * and ARCOUNT=1 for the RPZ SOA. */
2070+	sldns_buffer_write_u16(pkt, id);
2071+	sldns_buffer_write_u16(pkt, flags);
2072+	sldns_buffer_write_u16(pkt, 1);	/* QDCOUNT */
2073+	sldns_buffer_write_u16(pkt, 0);	/* ANCOUNT will be set later */
2074+	sldns_buffer_write_u16(pkt, 0);	/* NSCOUNT */
2075+	sldns_buffer_write_u16(pkt, 1);	/* ARCOUNT */
2076+
2077+	/* Install the question with the LDNS_RR_CLASS_RPZ bit to
2078+	 * to distinguish this supposed cache entry from the real deal. */
2079+	sldns_buffer_write(pkt, qinfo->qname, qinfo->qname_len);
2080+	sldns_buffer_write_u16(pkt, qinfo->qtype);
2081+	sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_IN);
2082+
2083+	/* Install the RPZ RRs in the answer section */
2084+	num_rrs = 0;
2085+	while(rr) {
2086+		/* Include only the requested RRs. */
2087+		if(qinfo->qtype == LDNS_RR_TYPE_ANY ||
2088+		   rr->type == htons(qinfo->qtype) ||
2089+		   rr->type == htons(LDNS_RR_TYPE_CNAME)) {
2090+			if(!add_rr(pkt, qinfo->qname, qinfo->qname_len,
2091+				   rr, rpz))
2092+				return;
2093+
2094+			++num_rrs;
2095+		}
2096+		free(rr);
2097+
2098+		next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
2099+	}
2100+	/* Finish ANCOUNT. */
2101+	if(num_rrs != 0)
2102+		sldns_buffer_write_u16_at(pkt, 6, num_rrs);
2103+
2104+	/* All rewritten responses have an identifying SOA record in the
2105+	 * additional section. */
2106+	if(!librpz->rsp_soa(&emsg, NULL, &rr, &origin,
2107+			    &rpz->result, rpz->rsp)) {
2108+		log_fail(rpz, "no soa");
2109+		return;
2110+	}
2111+	if(!add_rr(pkt, origin.d, origin.size, rr, rpz))
2112+		return;
2113+	free(rr);
2114+
2115+	/* Create a dns_msg representation of the fake incoming message. */
2116+	sldns_buffer_flip(pkt);
2117+	pkt2dns_msg(dnsmsg, pkt, rpz, region);
2118+}
2119+
2120+
2121+/* Check the RRs in the ANSWER section of a reply_info. */
2122+static void
2123+ck_reply(struct reply_info* reply, uint8_t* qname, bool wait_ns,
2124+	 commreply_rpz_t* rpz, struct module_env* env)
2125+{
2126+	struct ub_packed_rrset_key* rrset;
2127+	enum sldns_enum_rr_type type;
2128+	uint rrset_n;
2129+
2130+	/* Check the RRs in the ANSWER section. */
2131+	rpz->cname_hit.size = 0;
2132+	rpz->cname_hit_2nd = false;
2133+	for(rrset_n = 0; rrset_n < reply->an_numrrsets; ++rrset_n) {
2134+		/* Check all of the RRs before deciding. */
2135+		if(rpz->st != st_unknown)
2136+			return;
2137+
2138+		rrset = reply->rrsets[rrset_n];
2139+		if(ntohs(rrset->rk.rrset_class) != LDNS_RR_CLASS_IN)
2140+			continue;
2141+		type = ntohs(rrset->rk.type);
2142+
2143+		if(type == LDNS_RR_TYPE_A) {
2144+			if(!ck_ip_rrset(rrset->entry.data, AF_INET,
2145+					LIBRPZ_TRIG_IP, qname, rpz))
2146+				break;
2147+
2148+		} else if(type == LDNS_RR_TYPE_AAAA) {
2149+			if(!ck_ip_rrset(rrset->entry.data, AF_INET6,
2150+					LIBRPZ_TRIG_IP, qname, rpz))
2151+				break;
2152+
2153+		} else if(type == LDNS_RR_TYPE_CNAME) {
2154+			/* Check CNAME owners unless we already have a hit. */
2155+			++rpz->hit_id;
2156+			if(!ck_qname(rrset->rk.dname, rrset->rk.dname_len,
2157+				     true, wait_ns, rpz, env))
2158+				break;
2159+
2160+			/* Do not worry about the CNAME if it did not hit,
2161+			 * but note the miss so that it can be prepended
2162+			 * if we do hit. */
2163+			if(rpz->result.hit_id != rpz->hit_id) {
2164+				rpz->cname_hit_2nd = true;
2165+				continue;
2166+			}
2167+
2168+			/* Stop after hitting a CNAME.
2169+			 * The iterator must be used to include CNAMEs before
2170+			 * the CNAME that hit in the rewritten response. */
2171+			rpz->cname_hit.size = rrset->rk.dname_len;
2172+			RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d));
2173+			memcpy(rpz->cname_hit.d, rrset->rk.dname,
2174+			       rpz->cname_hit.size);
2175+			break;
2176+		}
2177+	}
2178+}
2179+
2180+
2181+static void
2182+worker_servfail(struct worker* worker, struct query_info* qinfo,
2183+		uint16_t id, uint16_t flags, struct comm_reply* commreply)
2184+{
2185+	error_encode(commreply->c->buffer, LDNS_RCODE_SERVFAIL,
2186+		     qinfo, id, flags, NULL);
2187+	regional_free_all(worker->scratchpad);
2188+	comm_point_send_reply(commreply);
2189+}
2190+
2191+
2192+/* Send an RPZ answer before the iterator has started.
2193+ * @return: 1=continue normal unbound processing
2194+ *	    0=punt to the iterator
2195+ *	    -1=rewritten response already sent or dropped. */
2196+static int
2197+worker_send(struct dns_msg* dnsmsg, struct worker* worker,
2198+	    struct query_info* qinfo, uint16_t id, uint16_t flags,
2199+	    struct edns_data* edns, struct comm_reply* commreply)
2200+{
2201+	switch (commreply->rpz->st) {
2202+	case st_off:
2203+		return 1;
2204+	case st_servfail:
2205+		worker_servfail(worker, qinfo, id, flags, commreply);
2206+		return -1;
2207+	case st_unknown:
2208+		return 1;
2209+	case st_iterate:
2210+	case st_ck_ns:
2211+		return 0;		/* punt to the iterator */
2212+	case st_rewritten:
2213+		break;
2214+	default:
2215+		fatal_exit("impossible RPZ state %d in worker_send()",
2216+			   commreply->rpz->st);
2217+	}
2218+
2219+	if(dnsmsg->rep->security == sec_status_rpz_drop) {
2220+		regional_free_all(worker->scratchpad);
2221+		comm_point_drop_reply(commreply);
2222+		return -1;
2223+	}
2224+
2225+	edns->edns_version = EDNS_ADVERTISED_VERSION;
2226+	edns->udp_size = EDNS_ADVERTISED_SIZE;
2227+	edns->ext_rcode = 0;
2228+	edns->bits = 0;			/* rewritten response cannot verify. */
2229+	if(!reply_info_answer_encode(qinfo, dnsmsg->rep,
2230+				     id, flags | BIT_QR,
2231+				     commreply->c->buffer, 0, 1,
2232+				     worker->scratchpad,
2233+				     edns->udp_size, edns, 0, 0)) {
2234+		worker_servfail(worker, qinfo, id, flags, commreply);
2235+	} else {
2236+		regional_free_all(worker->scratchpad);
2237+		comm_point_send_reply(commreply);
2238+	}
2239+	return -1;
2240+}
2241+
2242+
2243+/* Set commreply to an RPZ context if the response might be rewritten.
2244+ * Try to answer now with a hit allowed before recursion (iteration). */
2245+bool					/* true=response sent or dropped */
2246+rpz_start(struct worker* worker, struct query_info* qinfo,
2247+	  struct comm_reply* commreply, struct edns_data* edns)
2248+{
2249+	commreply_rpz_t* rpz;
2250+	uint16_t id, flags;
2251+	struct dns_msg* dnsmsg;
2252+	int family;
2253+	const void* addr;
2254+	librpz_emsg_t emsg;
2255+
2256+	/* Quit if rpz not configured. */
2257+	if(!worker->daemon->rpz_client)
2258+		return false;
2259+
2260+	/* Rewrite only the Internet class */
2261+	if(qinfo->qclass != LDNS_RR_CLASS_IN)
2262+		return false;
2263+
2264+	rpz = commreply->rpz;
2265+	RPZ_ASSERT(!rpz);
2266+
2267+	dnsmsg = NULL;
2268+	id = htons(sldns_buffer_read_u16_at(commreply->c->buffer, 0));
2269+	flags = sldns_buffer_read_u16_at(commreply->c->buffer, 2);
2270+
2271+	rpz = malloc(sizeof(*rpz));
2272+	if(!rpz) {
2273+		librpz->log(LIBRPZ_LOG_ERROR, NULL, "no memory for rpz");
2274+		return 0 > worker_send(dnsmsg, worker, qinfo,
2275+				       id, flags, edns, commreply);
2276+	}
2277+	memset(rpz, 0, sizeof(*rpz));
2278+	rpz->st = st_unknown;
2279+	commreply->rpz = rpz;
2280+
2281+	/* Make a new ID for log messages */
2282+	rpz->log_id = __sync_add_and_fetch(&log_id, 1);
2283+
2284+	/* Get access to the librpz data. */
2285+	if(!librpz->rsp_create(&emsg, &rpz->rsp, &rpz->min_ns_dots,
2286+			      worker->daemon->rpz_client,
2287+			      (flags & BIT_RD) != 0,
2288+			      (edns->bits & EDNS_DO) != 0)) {
2289+		log_fail(rpz, "%s", emsg.c);
2290+		return false;
2291+	}
2292+	/* Quit if benign reasons prevent rewriting. */
2293+	if(!rpz->rsp) {
2294+		rpz->st = st_off;
2295+		librpz->log(LIBRPZ_LOG_TRACE1, rpz, "%s", emsg.c);
2296+		return false;
2297+	}
2298+
2299+	/* Check the client IP address.
2300+	 * Do not use commreply->srctype because it is often 0. */
2301+	family = ((struct sockaddr*)&commreply->addr)->sa_family;
2302+	switch(family) {
2303+	case AF_INET:
2304+		addr = &((struct sockaddr_in*)&commreply->addr)->sin_addr;
2305+		break;
2306+	case AF_INET6:
2307+		addr = &((struct sockaddr_in6*)&commreply->addr)->sin6_addr;
2308+		break;
2309+	default:
2310+		/* Maybe the client is on a UNIX domain socket. */
2311+		librpz->log(LIBRPZ_LOG_TRACE2, rpz,
2312+			    "unknown client address family %d", family);
2313+		addr = NULL;
2314+		break;
2315+	}
2316+	/* Loop to ignore disabled zones. */
2317+	while(addr) {
2318+		if(!push_st(rpz))
2319+			break;
2320+		if(!librpz->ck_ip(&emsg, addr, family, LIBRPZ_TRIG_CLIENT_IP,
2321+				  rpz->hit_id, true, rpz->rsp)) {
2322+			log_fail(rpz, "%s", emsg.c);
2323+			break;
2324+		}
2325+		if(!ck_after(qinfo->qname, false, LIBRPZ_TRIG_CLIENT_IP, rpz))
2326+			break;
2327+	}
2328+	if(rpz->st == st_servfail)
2329+		return 0 > worker_send(dnsmsg, worker, qinfo,
2330+				       id, flags, edns, commreply);
2331+
2332+	/* Check the QNAME and possibly replace a client-IP hit. */
2333+	ck_qname(qinfo->qname, qinfo->qname_len, false, true,
2334+		 rpz, &worker->env);
2335+
2336+	get_result_msg(&dnsmsg, qinfo, id, flags, false,
2337+		       rpz, commreply->c, worker->scratchpad);
2338+	return 0 > worker_send(dnsmsg, worker, qinfo,
2339+			       id, flags, edns, commreply);
2340+}
2341+
2342+
2343+/* Check a cached reply before iteration.
2344+ * @return: 1=use cache entry
2345+ *	    0=deny a cached entry exists in order to punt to the iterator
2346+ *	    -1=rewritten response already sent or dropped */
2347+int
2348+rpz_worker_cache(struct worker* worker, struct reply_info* reply,
2349+		 struct query_info* qinfo, uint16_t id, uint16_t flags,
2350+		 struct edns_data* edns, struct comm_reply* commreply)
2351+{
2352+	commreply_rpz_t* rpz;
2353+	struct dns_msg* dnsmsg;
2354+	st_t new_st;
2355+	librpz_rr_t* rr;
2356+
2357+	dnsmsg = NULL;
2358+
2359+	rpz = commreply->rpz;
2360+	switch(rpz->st) {
2361+	case st_off:
2362+		return 1;		/* Send the cache entry. */
2363+	case st_servfail:
2364+		return worker_send(dnsmsg, worker, qinfo, id, flags,
2365+				   edns, commreply);
2366+	case st_unknown:
2367+		break;
2368+	case st_iterate:
2369+	case st_ck_ns:
2370+		return 0;		/* Punt to the iterator. */
2371+	case st_rewritten:
2372+	default:
2373+		fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
2374+			   rpz->st);
2375+	}
2376+
2377+	/* Check the RRs in the ANSWER section. */
2378+	if(!push_st(rpz))
2379+		return worker_send(dnsmsg, worker, qinfo, id, flags, edns,
2380+				   commreply);
2381+
2382+	ck_reply(reply, qinfo->qname, true, rpz, &worker->env);
2383+	if(!ck_result(qinfo->qname, true, rpz, commreply->c))
2384+		return worker_send(dnsmsg, worker, qinfo, id, flags, edns,
2385+				   commreply);
2386+
2387+	if(rpz->cname_hit.size != 0) {
2388+		/* Punt to the iterator if leading CNAMEs must be
2389+		 * included in the rewritten response. */
2390+		rpz->cname_hit.size = 0;
2391+		new_st = st_iterate;
2392+
2393+	} else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) {
2394+		/* Punt if the rewritten response is to a CNAME. */
2395+		new_st = st_iterate;
2396+
2397+	} else {
2398+		if(rpz->result.policy == LIBRPZ_POLICY_RECORD) {
2399+			next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
2400+			if(rr) {
2401+				/* Punt we are rewriting to a CNAME. */
2402+				if(rr->type == ntohs(LDNS_RR_TYPE_CNAME)) {
2403+					free(rr);
2404+					rpz->st = st_iterate;
2405+				} else {
2406+					free(rr);
2407+				}
2408+			}
2409+		}
2410+		get_result_msg(&dnsmsg, qinfo, id, flags, true,
2411+			       rpz, commreply->c, worker->scratchpad);
2412+		new_st = rpz->st;
2413+	}
2414+
2415+	switch(new_st) {
2416+	case st_off:
2417+	case st_servfail:
2418+		break;
2419+	case st_unknown:
2420+		pop_discard_st(rpz);
2421+		break;
2422+	case st_iterate:
2423+	case st_ck_ns:
2424+		if(pop_st(rpz))
2425+			rpz->st = new_st;
2426+		break;
2427+	case st_rewritten:
2428+		pop_discard_st(rpz);
2429+		break;
2430+	default:
2431+		fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
2432+			   rpz->st);
2433+	}
2434+
2435+	return worker_send(dnsmsg, worker, qinfo, id, flags, edns, commreply);
2436+}
2437+
2438+
2439+/* Check a cache hit or miss for the iterator.
2440+ * A cache miss can already have a QNAME hit that was ignored before checking
2441+ * the iterator because of "QNAME-WAIT-RECURSE yes".
2442+ * Cache hits are treated like responses from authorities. */
2443+bool					/* false=SERVFAIL */
2444+rpz_iter_cache(struct dns_msg** msg, enum response_type* type,
2445+	       struct module_qstate* qstate, struct iter_qstate* iq)
2446+{
2447+	struct comm_reply* commreply;
2448+	commreply_rpz_t* rpz;
2449+	struct dns_msg* dnsmsg;
2450+
2451+	commreply = &qstate->mesh_info->reply_list->query_reply;
2452+	rpz = commreply->rpz;
2453+
2454+	rpz->iterating = true;
2455+
2456+	switch(rpz->st) {
2457+	case st_off:
2458+		iq->rpz_rewritten = 1;	/* RPZ has nothing to say. */
2459+		return true;
2460+	case st_servfail:
2461+		return false;
2462+	case st_unknown:
2463+		break;
2464+	case st_iterate:
2465+	case st_ck_ns:
2466+		rpz->st = st_unknown;
2467+		if(!ck_qname(iq->qchase.qname, iq->qchase.qname_len,
2468+			     *msg != NULL, true, rpz, qstate->env))
2469+			return false;
2470+		/* If we must recurse regardless and if NSIP/NSDNAME
2471+		 * checking failed, then delay in the hope that
2472+		 * recursion will also get NS data. */
2473+		if(rpz->st == st_ck_ns)
2474+			return true;
2475+		break;
2476+	case st_rewritten:
2477+	default:
2478+		fatal_exit("impossible RPZ state %d in rpz_iter_cache()",
2479+			   rpz->st);
2480+	}
2481+
2482+	push_st(rpz);
2483+
2484+	/* Check the cache hit. */
2485+	if(*msg)
2486+		ck_reply((*msg)->rep, iq->qchase.qname, true, rpz, qstate->env);
2487+
2488+	/* The DNS ID does not matter, because the generated dns_msg
2489+	 * is nominally from an authority and not to the DNS client. */
2490+	get_result_msg(&dnsmsg, &iq->qchase, 1, qstate->query_flags, true,
2491+		       rpz, commreply->c, qstate->region);
2492+
2493+	switch(rpz->st) {
2494+	case st_off:
2495+		iq->rpz_rewritten = 1;	/* RPZ has nothing to say. */
2496+		return true;
2497+	case st_servfail:
2498+		return false;
2499+	case st_unknown:
2500+		/* RPZ has nothing to say yet.  Maybe there will be a hit
2501+		 * later in the CNAME chain. */
2502+		return pop_discard_st(rpz);
2503+	case st_ck_ns:
2504+		/* Try to get NS data for a CNAME found by ck_reply() */
2505+		*type = RESPONSE_TYPE_CNAME;
2506+		return pop_discard_st(rpz);
2507+	case st_iterate:
2508+	default:
2509+		fatal_exit("impossible RPZ state %d in rpz_iter_cache()",
2510+			   rpz->st);
2511+	case st_rewritten:
2512+		break;
2513+	}
2514+
2515+	if(*msg && rpz->cname_hit.size != 0 && rpz->cname_hit_2nd) {
2516+		/* We hit a CNAME owner in the cached msg after not hitting one
2517+		 * or more CNAME owners.  We need to add those leading CNAMEs
2518+		 * to the prepend list.  Tell the iterator to treat the cached
2519+		 * message as a RESPONSE_TYPE_CNAME even if it contains answers.
2520+		 * handle_cname_response() will stop prepending CNAMEs before
2521+		 * the triggering CNAME.  handle_cname_response() will cause
2522+		 * a restart to resolve the target of the preceding CNAME,
2523+		 * which is the same as the hit CNAME owner. */
2524+		rpz->st = st_unknown;
2525+		*type = RESPONSE_TYPE_CNAME;
2526+		return pop_discard_st(rpz);
2527+	}
2528+
2529+	*msg = dnsmsg;
2530+	iq->rpz_security = dnsmsg->rep->security;
2531+
2532+	if(dnsmsg && dnsmsg->rep->an_numrrsets != 0 &&
2533+	   dnsmsg->rep->rrsets[0]->rk.type == htons(LDNS_RR_TYPE_CNAME)) {
2534+		/* The cached msg triggered a rule that rewrites to a
2535+		 * CNAME that must be resolved.
2536+		 * We have a replacement dns_msg with that CNAME and also
2537+		 * an SOA RR in the ADDITIONAL section that the iterator
2538+		 * will lose as it adds the CNAME to the prepend list.
2539+		 * Save the SOA RR in iq->rpz_soa. */
2540+		iq->rpz_soa = dnsmsg->rep->rrsets[1];
2541+		iq->rpz_rewritten = 1;
2542+		*type = RESPONSE_TYPE_CNAME;
2543+		return true;
2544+	}
2545+
2546+	/* Otherwise we have rewritten to zero or more non-CNAME RRs.
2547+	 * (DNAMEs are not supported.)
2548+	 * Tell the iterator to send the rewritten message. */
2549+	*type = RESPONSE_TYPE_ANSWER;
2550+	iq->rpz_rewritten = 1;
2551+	return true;
2552+}
2553+
2554+
2555+/* Check a RESPONSE_TYPE_ANSWER response from an authority in the iterator. */
2556+rpz_iter_resp_t
2557+rpz_iter_resp(struct module_qstate* qstate, struct iter_qstate* iq,
2558+	      struct dns_msg** resp, bool* is_cname)
2559+{
2560+	struct comm_reply* commreply;
2561+	commreply_rpz_t* rpz;
2562+	struct reply_info* rep;
2563+
2564+	*is_cname = false;
2565+
2566+	commreply = &qstate->mesh_info->reply_list->query_reply;
2567+	rpz = commreply->rpz;
2568+	switch(rpz->st) {
2569+	case st_off:
2570+	case st_servfail:
2571+	case st_iterate:
2572+	case st_rewritten:
2573+	default:
2574+		fatal_exit("impossible RPZ state %d in rpz_iter_resp()",
2575+			   rpz->st);
2576+	case st_ck_ns:
2577+	case st_unknown:
2578+		break;
2579+	}
2580+
2581+	/* We know !iq->rpz_rewritten and so the response was after a simple
2582+	 * cache miss when the original QNAME did not trigger a response
2583+	 * or after a CNAME whose owner name did hit but was then forgotten
2584+	 * with pop_st().
2585+	 * In either case, it is necessary to check the QNAME here.
2586+	 * Checking the QNAME will not lose a better hit. */
2587+	rpz->st = st_unknown;
2588+	ck_qname(iq->qchase.qname, iq->qchase.qname_len, true, false,
2589+		 rpz, qstate->env);
2590+
2591+	/* Check the RRs in the ANSWER section. */
2592+	if(!push_st(rpz))
2593+		return rpz_iter_resp_fail;
2594+	ck_reply(iq->response->rep, iq->qchase.qname, false, rpz, qstate->env);
2595+	get_result_msg(resp, &qstate->qinfo, 1, qstate->query_flags, true,
2596+		       rpz, commreply->c, qstate->region);
2597+	switch(rpz->st) {
2598+	case st_off:
2599+		iq->rpz_rewritten = 1;	/* Do not come back. */
2600+		return rpz_iter_resp_done;
2601+	case st_servfail:		/* Send SERVFAIL */
2602+		return rpz_iter_resp_fail;
2603+	case st_unknown:
2604+	case st_ck_ns:
2605+		return rpz_iter_resp_done;  /* continue without change */
2606+	case st_iterate:
2607+	default:
2608+		fatal_exit("impossible RPZ state %d in rpz_iter_resp()",
2609+			   rpz->st);
2610+	case st_rewritten:
2611+		/* Tell the iterator to use handle_cname_response() to
2612+		 * prepend any preceding CNAMEs.
2613+		 * We have a replacement dns_msg that also has an SOA RR in the
2614+		 * ADDITIONAL section that the iterator will lose if it is a
2615+		 * CNAME.  Save that SOA in that case. */
2616+		rep = (*resp)->rep;
2617+		if(rep->an_numrrsets != 0 &&
2618+		   rep->rrsets[0]->rk.type == ntohs(LDNS_RR_TYPE_CNAME)) {
2619+			*is_cname = true;
2620+			iq->rpz_soa = rep->rrsets[1];
2621+		}
2622+		return rpz_iter_resp_rewrite;
2623+	}
2624+}
2625+
2626+
2627+/* Tell handle_cname_response() to stop adding to the answer prepend list
2628+ * after adding CNAME with a target that hits a QNAME trigger.
2629+ * Do not change any RPZ state, but expect the call of handle_cname_response()
2630+ * to try to resolve the CNAME and hit the same QNAME trigger and rewrite
2631+ * the response. */
2632+rpz_cname_t
2633+rpz_cname(struct module_qstate* qstate,
2634+	  uint8_t* oname, size_t oname_size)
2635+{
2636+	struct mesh_reply* reply_list;
2637+	struct comm_reply* commreply;
2638+	commreply_rpz_t* rpz;
2639+	rpz_cname_t ret;
2640+
2641+	/* Quit if RPZ is off */
2642+	reply_list = qstate->mesh_info->reply_list;
2643+	if(!reply_list)
2644+		return rpz_cname_prepend;
2645+	commreply = &reply_list->query_reply;
2646+	rpz = commreply->rpz;
2647+
2648+	if(!rpz || rpz->st == st_off)
2649+		return rpz_cname_prepend;
2650+
2651+	/* Stop on a 2nd or later CNAME for rpz_iter_resp(). */
2652+	if(rpz->cname_hit.size != 0) {
2653+		if(!query_dname_compare(rpz->cname_hit.d, oname))
2654+			return rpz_cname_stop;
2655+		return rpz_cname_prepend;
2656+	}
2657+
2658+	if(rpz->st != st_unknown)
2659+		fatal_exit("impossible RPZ state %d in rpz_cname()", rpz->st);
2660+
2661+	ret = rpz_cname_prepend;
2662+	if(!push_st(rpz))
2663+		return rpz_cname_fail;
2664+	/* Stop before prepending a CNAME that would preempt a
2665+	 * rewritten response or before a possible NSDNAME or NSIP trigger. */
2666+	++rpz->hit_id;
2667+	ck_qname(oname, oname_size, true, true, rpz, qstate->env);
2668+	if(rpz->st != st_unknown)
2669+		ret = rpz_cname_stop;
2670+	if(!pop_st(rpz))
2671+		return rpz_cname_fail;
2672+	return ret;
2673+}
2674+
2675+#endif /* ENABLE_FASTRPZ */
2676diff --git a/fastrpz/rpz.h b/fastrpz/rpz.h
2677new file mode 100644
2678index 00000000..5d7e31c5
2679--- /dev/null
2680+++ b/fastrpz/rpz.h
2681@@ -0,0 +1,138 @@
2682+/*
2683+ * fastrpz/rpz.h - interface to the fastrpz response policy zone library
2684+ *
2685+ * Copyright (c) 2016 Farsight Security, Inc.
2686+ *
2687+ * Licensed under the Apache License, Version 2.0 (the "License");
2688+ * you may not use this file except in compliance with the License.
2689+ * You may obtain a copy of the License at
2690+ *	http://www.apache.org/licenses/LICENSE-2.0
2691+ *
2692+ * Unless required by applicable law or agreed to in writing, software
2693+ * distributed under the License is distributed on an "AS IS" BASIS,
2694+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2695+ * See the License for the specific language governing permissions and
2696+ * limitations under the License.
2697+ */
2698+
2699+#ifndef UNBOUND_FASTRPZ_RPZ_H
2700+#define UNBOUND_FASTRPZ_RPZ_H
2701+
2702+#ifndef PACKAGE_VERSION
2703+/* Ensure that config.h has been included to correctly set ENABLE_FASTRPZ */
2704+#include "config.h"
2705+#endif
2706+
2707+#ifdef ENABLE_FASTRPZ
2708+
2709+#include "librpz.h"
2710+
2711+#include "daemon/daemon.h"
2712+#include "util/config_file.h"
2713+
2714+struct comm_point;			/* forward references */
2715+struct comm_reply;
2716+struct dns_msg;
2717+struct edns_data;
2718+struct iter_qstate;
2719+struct query_info;
2720+struct reply_info;
2721+enum response_type;			/* iterator/iter_utils.h */
2722+
2723+
2724+struct commreply_rpz;
2725+
2726+/**
2727+ * Connect to the librpz database.
2728+ * @param pclist: future pointer to opaque librpz client data
2729+ * @param pclient: future pointer to opaque librpz client data
2730+ * @param cfg: parsed unbound configuration
2731+ */
2732+void rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient,
2733+	      const struct config_file* cfg);
2734+
2735+/**
2736+ * Disconnect from the librpz database
2737+ * @param client: opaque librpz client data
2738+ */
2739+void rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient);
2740+
2741+/**
2742+ * Start working on a DNS request and check for client IP address triggers.
2743+ * @param worker: the DNS request context
2744+ * @param qinfo: the DNS question
2745+ * @param[in,out] commreply: the answer
2746+ * @param c: where to send the response
2747+ * @param[in,out] edns for the DO flag
2748+ * @return true if response already sent or dropped
2749+ */
2750+bool rpz_start(struct worker* worker, struct query_info* qinfo,
2751+	       struct comm_reply* commreply, struct edns_data* edns);
2752+
2753+/**
2754+ * Release resources held for a DNS request
2755+ * @param rspp: pointer to pointer to rpz client context.
2756+ */
2757+void rpz_end(struct comm_reply* comm_rep);
2758+
2759+/**
2760+ * Check a cached reply for RPZ hits before iteration
2761+ * @param worker: the DNS request context
2762+ * @param casheresp: cache reply
2763+ * @param qinfo: the DNS question
2764+ * @param id from the DNS request
2765+ * @param flags from the DNS request
2766+ * @param[in,out] edns for the DO flag
2767+ * @param[in,out] commreply: RPZ state
2768+ * @return 1=use cache entry, -1=rewritten response already sent or dropped,
2769+ *	0=deny a cached entry exists
2770+ */
2771+int rpz_worker_cache(struct worker* worker, struct reply_info* cacheresp,
2772+		     struct query_info* qinfo, uint16_t id, uint16_t flags,
2773+		     struct edns_data* edns, struct comm_reply* commreply);
2774+
2775+/**
2776+ * Check for an existing RPZ CNAME rewrite with "QNAME-WAIT-RECURSE no"
2777+ * that needs to be resolved before resolving the external request.
2778+ * @param[out] msg: rewritten CNAME response.
2779+ * @param qstate: query state.
2780+ * @param iq: iterator query state.
2781+ * @return false=send SERVFAIL
2782+ */
2783+bool rpz_iter_cache(struct dns_msg** msg, enum response_type* type,
2784+		    struct module_qstate* qstate, struct iter_qstate* iq);
2785+
2786+/**
2787+ * Check a response from an authority in the iterator.
2788+ * @param[out] type: of the final response
2789+ * @param qstate: query state.
2790+ * @param iq: iterator query state.
2791+ * @param is_cname: true if the rewritten response is a CNAME
2792+ * @return one of rpz_resp_t
2793+ */
2794+typedef enum {
2795+	rpz_iter_resp_fail,		/* Send SERVFAIL. */
2796+	rpz_iter_resp_rewrite,		/* We rewrote the response. */
2797+	rpz_iter_resp_done,		/* Restart to refetch glue. */
2798+} rpz_iter_resp_t;
2799+rpz_iter_resp_t rpz_iter_resp(struct module_qstate* qstate,
2800+			      struct iter_qstate* iq, struct dns_msg** resp,
2801+			      bool* is_cname);
2802+
2803+/**
2804+ * Check a CNAME RR
2805+ * @param qstate: query state.
2806+ * @param oname: cname owner name
2807+ * @param oname_size: length of oname
2808+ * @return: one of rpz_cname_t
2809+ */
2810+typedef enum {
2811+	rpz_cname_fail,			/* send SERVFAIL */
2812+	rpz_cname_prepend,		/* prepend CNAME as usual */
2813+	rpz_cname_stop,			/* stop before prepending this CNAME */
2814+} rpz_cname_t;
2815+rpz_cname_t rpz_cname(struct module_qstate* qstate,
2816+		      uint8_t* oname, size_t oname_size);
2817+
2818+#endif /* ENABLE_FASTRPZ */
2819+#endif /* UNBOUND_FASTRPZ_RPZ_H */
2820diff --git a/fastrpz/rpz.m4 b/fastrpz/rpz.m4
2821new file mode 100644
2822index 00000000..21235355
2823--- /dev/null
2824+++ b/fastrpz/rpz.m4
2825@@ -0,0 +1,64 @@
2826+# fastrpz/rpz.m4
2827+
2828+# ck_FASTRPZ
2829+# --------------------------------------------------------------------------
2830+# check for Fastrpz
2831+#   --enable-fastrpz	    enable Fastrpz response policy zones
2832+#   --enable-fastrpz-dl	    Fastrpz delayed link [default=have dlopen]
2833+#   --with-fastrpz-dir	    directory containing librpz.so
2834+#
2835+# Fastrpz can be compiled into Unbound everywhere with a reasonably
2836+# modern C compiler.  It is enabled on systems with dlopen() and librpz.so.
2837+
2838+AC_DEFUN([ck_FASTRPZ],
2839+[
2840+  fastrpz_avail=yes
2841+  AC_MSG_CHECKING([for librpz __attribute__s])
2842+    AC_TRY_COMPILE(,[
2843+	extern void f(char *p __attribute__((unused)), ...)
2844+	__attribute__((format(printf,1,2))) __attribute__((__noreturn__));],
2845+      librpz_have_attr=yes
2846+        AC_DEFINE([LIBRPZ_HAVE_ATTR], 1, [have __attribute__s used in librpz.h])
2847+        AC_MSG_RESULT([yes]),
2848+      librpz_have_attr=no
2849+        AC_MSG_RESULT([no]))
2850+
2851+  AC_SEARCH_LIBS(dlopen, dl)
2852+  librpz_dl=yes
2853+  AC_CHECK_FUNCS(dlopen dlclose dlsym,,librpz_dl=no)
2854+  AC_ARG_ENABLE([fastrpz-dl],
2855+    [  --enable-fastrpz-dl	  Fastrpz delayed link [[default=$librpz_dl]]],
2856+    [enable_librpz_dl="$enableval"],
2857+    [enable_librpz_dl="$librpz_dl"])
2858+  AC_ARG_WITH([fastrpz-dir],
2859+    [  --with-fastrpz-dir	  directory containing librpz.so],
2860+    [librpz_path="$withval/librpz.so"], [librpz_path="librpz.so"])
2861+  AC_DEFINE_UNQUOTED([FASTRPZ_LIBRPZ_PATH], ["$librpz_path"],
2862+    [fastrpz librpz.so])
2863+  if test "x$enable_librpz_dl" = "xyes"; then
2864+    fastrpz_lib_open=2
2865+  else
2866+    fastrpz_lib_open=1
2867+    # Add librpz.so to linked libraries if we are not using dlopen()
2868+    AC_SEARCH_LIBS([librpz_client_create], [rpz], [],
2869+      [fastrpz_lib_open=0
2870+        fastrpz_avail=no])
2871+  fi
2872+  AC_DEFINE_UNQUOTED([FASTRPZ_LIB_OPEN], [$fastrpz_lib_open],
2873+    [0=no fastrpz  1=static link  2=dlopen()])
2874+
2875+  AC_ARG_ENABLE([fastrpz],
2876+    AS_HELP_STRING([--enable-fastrpz],[enable Fastrpz response policy zones]),
2877+    [enable_fastrpz=$enableval],[enable_fastrpz=$fastrpz_avail])
2878+  if test "x$enable_fastrpz" = xyes; then
2879+    AC_DEFINE([ENABLE_FASTRPZ], [1], [Enable fastrpz])
2880+    if test "x$fastrpz_lib_open" = "x0"; then
2881+      AC_MSG_ERROR([[dlopen and librpz.so needed for fastrpz]])
2882+    fi
2883+    # used in Makefile.in
2884+    AC_SUBST([FASTRPZ_SRC], [fastrpz/rpz.c])
2885+    AC_SUBST([FASTRPZ_OBJ], [rpz.lo])
2886+  elif test "x$fastrpz_avail" = "x0"; then
2887+    AC_MSG_WARN([[dlopen and librpz.so needed for fastrpz]])
2888+  fi
2889+])
2890diff --git a/iterator/iterator.c b/iterator/iterator.c
2891index 1e0113a8..2fcbf547 100644
2892--- a/iterator/iterator.c
2893+++ b/iterator/iterator.c
2894@@ -68,6 +68,9 @@
2895 #include "sldns/str2wire.h"
2896 #include "sldns/parseutil.h"
2897 #include "sldns/sbuffer.h"
2898+#ifdef ENABLE_FASTRPZ
2899+#include "fastrpz/rpz.h"
2900+#endif
2901 
2902 /* in msec */
2903 int UNKNOWN_SERVER_NICENESS = 376;
2904@@ -555,6 +558,23 @@ handle_cname_response(struct module_qstate* qstate, struct iter_qstate* iq,
2905 		if(ntohs(r->rk.type) == LDNS_RR_TYPE_CNAME &&
2906 			query_dname_compare(*mname, r->rk.dname) == 0 &&
2907 			!iter_find_rrset_in_prepend_answer(iq, r)) {
2908+#ifdef ENABLE_FASTRPZ
2909+			/* Stop adding CNAME rrsets to the prepend list
2910+			 * before defining an RPZ hit. */
2911+			if(!iq->rpz_rewritten) {
2912+				switch (rpz_cname(qstate, *mname, *mname_len)) {
2913+				case rpz_cname_fail:
2914+					/* send SERVFAIL */
2915+					return 0;
2916+				case rpz_cname_prepend:
2917+					/* save the CNAME. */
2918+					break;
2919+				case rpz_cname_stop:
2920+					/* Pause before adding the CNAME. */
2921+					goto stop_short;
2922+				}
2923+			}
2924+#endif
2925 			/* Add this relevant CNAME rrset to the prepend list.*/
2926 			if(!iter_add_prepend_answer(qstate, iq, r))
2927 				return 0;
2928@@ -563,6 +583,9 @@ handle_cname_response(struct module_qstate* qstate, struct iter_qstate* iq,
2929 
2930 		/* Other rrsets in the section are ignored. */
2931 	}
2932+#ifdef ENABLE_FASTRPZ
2933+stop_short: ;
2934+#endif
2935 	/* add authority rrsets to authority prepend, for wildcarded CNAMEs */
2936 	for(i=msg->rep->an_numrrsets; i<msg->rep->an_numrrsets +
2937 		msg->rep->ns_numrrsets; i++) {
2938@@ -1199,6 +1222,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
2939 	uint8_t* delname;
2940 	size_t delnamelen;
2941 	struct dns_msg* msg = NULL;
2942+	enum response_type type;
2943 
2944 	log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo);
2945 	/* check effort */
2946@@ -1285,8 +1309,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
2947 	}
2948 	if(msg) {
2949 		/* handle positive cache response */
2950-		enum response_type type = response_type_from_cache(msg, 
2951-			&iq->qchase);
2952+		type = response_type_from_cache(msg, &iq->qchase);
2953 		if(verbosity >= VERB_ALGO) {
2954 			log_dns_msg("msg from cache lookup", &msg->qinfo, 
2955 				msg->rep);
2956@@ -1294,7 +1317,22 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
2957 				(int)msg->rep->ttl, 
2958 				(int)msg->rep->prefetch_ttl);
2959 		}
2960+#ifdef ENABLE_FASTRPZ
2961+	}
2962+	/* Check for an RPZ hit in the cached DNS message or an existing
2963+	 * RPZ CNAME rewrite that can be resolved now after a hit on the QNAME
2964+	 * or client IP address.  This can involve a creating a fake cache
2965+	 * hit. It can also involve overriding an RESPONSE_TYPE_ANSWER
2966+	 * result from response_type_from_cache().  Or it can ignore
2967+	 * the cached result to refetch glue. */
2968+	if(!iq->rpz_rewritten &&
2969+	   qstate->mesh_info->reply_list &&
2970+	   qstate->mesh_info->reply_list->query_reply.rpz &&
2971+	   !rpz_iter_cache(&msg, &type, qstate, iq))
2972+		return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
2973 
2974+	if(msg) {
2975+#endif
2976 		if(type == RESPONSE_TYPE_CNAME) {
2977 			uint8_t* sname = 0;
2978 			size_t slen = 0;
2979@@ -2718,6 +2756,62 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
2980 			sock_list_insert(&qstate->reply_origin, 
2981 				&qstate->reply->addr, qstate->reply->addrlen, 
2982 				qstate->region);
2983+#ifdef ENABLE_FASTRPZ
2984+		/* Check the response for an RPZ hit. The response has already
2985+		 * been saved in the cache.  This should have the same effect
2986+		 * as finding that response in the cache.
2987+		 * We have already used rpz_iter_cache() at least once. */
2988+		if(!iq->rpz_rewritten &&
2989+		   qstate->mesh_info->reply_list &&
2990+		   qstate->mesh_info->reply_list->query_reply.rpz) {
2991+			struct dns_msg* resp;
2992+			bool is_cname;
2993+			uint8_t* sname;
2994+			size_t slen;
2995+
2996+			switch (rpz_iter_resp(qstate, iq, &resp, &is_cname)) {
2997+			case rpz_iter_resp_fail:
2998+				return error_response(qstate, id,
2999+						      LDNS_RCODE_SERVFAIL);
3000+			case rpz_iter_resp_rewrite:
3001+				/* Prepend any initial CNAMEs from the original
3002+				 * response up to a hit. */
3003+				if(!handle_cname_response(qstate, iq,
3004+							iq->response,
3005+							&sname, &slen))
3006+					return error_response(qstate, id,
3007+							LDNS_RCODE_SERVFAIL);
3008+				if (resp) {
3009+					iq->response = resp;
3010+					iq->rpz_security = resp->rep->security;
3011+					iq->rpz_rewritten = 1;
3012+
3013+					/* Send the rewritten record if it
3014+					 * is not a CNAME. */
3015+					if(!is_cname)
3016+					    break;
3017+
3018+					/* Prepend the new CNAME
3019+					 * and restart to resolve it. */
3020+					if(!handle_cname_response(qstate, iq,
3021+							resp, &sname, &slen))
3022+					    return error_response(qstate, id,
3023+							LDNS_RCODE_SERVFAIL);
3024+				}
3025+				iq->qchase.qname = sname;
3026+				iq->qchase.qname_len = slen;
3027+				iq->dp = NULL;
3028+				iq->refetch_glue = 0;
3029+				iq->query_restart_count++;
3030+				iq->sent_count = 0;
3031+				iq->state = INIT_REQUEST_STATE;
3032+				return 1;
3033+
3034+			case rpz_iter_resp_done:
3035+				break;
3036+			}
3037+		}
3038+#endif
3039 		if(iq->minimisation_state != DONOT_MINIMISE_STATE
3040 			&& !(iq->chase_flags & BIT_RD)) {
3041 			if(FLAGS_GET_RCODE(iq->response->rep->flags) != 
3042@@ -3471,12 +3565,44 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq,
3043 		 * but only if we did recursion. The nonrecursion referral
3044 		 * from cache does not need to be stored in the msg cache. */
3045 		if(!qstate->no_cache_store && qstate->query_flags&BIT_RD) {
3046+#ifdef ENABLE_FASTRPZ
3047+			/* Do not save RPZ rewritten messages. */
3048+			if(!iq->rpz_rewritten)
3049+#endif
3050 			iter_dns_store(qstate->env, &qstate->qinfo, 
3051 				iq->response->rep, 0, qstate->prefetch_leeway,
3052 				iq->dp&&iq->dp->has_parent_side_NS,
3053 				qstate->region, qstate->query_flags);
3054 		}
3055 	}
3056+#ifdef ENABLE_FASTRPZ
3057+	if(iq->rpz_rewritten) {
3058+		/* Restore RPZ marks on a rewritten response.  The marks
3059+		 * are lost if the rewrite is to a CNAME. */
3060+		iq->response->rep->security = iq->rpz_security;
3061+
3062+		/* Append the RPZ SOA to rewritten CNAME chains. */
3063+		if(iq->rpz_soa) {
3064+			struct ub_packed_rrset_key** sets;
3065+			uint n;
3066+
3067+			n = iq->response->rep->rrset_count;
3068+			sets = regional_alloc(qstate->region,
3069+					      (1+n) * sizeof(*sets));
3070+			if(!sets) {
3071+				log_err("append RPZ SOA: out of memory");
3072+				return error_response(qstate, id,
3073+						      LDNS_RCODE_SERVFAIL);
3074+			}
3075+			memcpy(sets, iq->response->rep->rrsets,
3076+			       n * sizeof(struct ub_packed_rrset_key*));
3077+			sets[n] = iq->rpz_soa;
3078+			iq->response->rep->rrsets = sets;
3079+			++iq->response->rep->rrset_count;
3080+			++iq->response->rep->ar_numrrsets;
3081+		}
3082+	}
3083+#endif
3084 	qstate->return_rcode = LDNS_RCODE_NOERROR;
3085 	qstate->return_msg = iq->response;
3086 	return 0;
3087diff --git a/iterator/iterator.h b/iterator/iterator.h
3088index a2f1b570..e1e4a738 100644
3089--- a/iterator/iterator.h
3090+++ b/iterator/iterator.h
3091@@ -386,6 +386,16 @@ struct iter_qstate {
3092 	 */
3093 	int minimise_count;
3094 
3095+
3096+#ifdef ENABLE_FASTRPZ
3097+	/** The response has been rewritten by RPZ. */
3098+	int rpz_rewritten;
3099+	/** RPZ SOA RR for the ADDITIONAL section */
3100+	struct ub_packed_rrset_key* rpz_soa;
3101+	/** sec_status_rpz_rewritten or sec_status_rpz_drop if rewritten. */
3102+	enum sec_status rpz_security;
3103+#endif
3104+
3105 	/**
3106 	 * Count number of time-outs. Used to prevent resolving failures when
3107 	 * the QNAME minimisation QTYPE is blocked. */
3108diff --git a/services/cache/dns.c b/services/cache/dns.c
3109index aa4efec7..5dd3412e 100644
3110--- a/services/cache/dns.c
3111+++ b/services/cache/dns.c
3112@@ -945,6 +945,14 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
3113 	struct regional* region, uint32_t flags)
3114 {
3115 	struct reply_info* rep = NULL;
3116+
3117+#ifdef ENABLE_FASTRPZ
3118+	/* Never save RPZ rewritten data. */
3119+	if (msgrep->security == sec_status_rpz_drop ||
3120+	    msgrep->security == sec_status_rpz_rewritten)
3121+		return 1;
3122+#endif
3123+
3124 	/* alloc, malloc properly (not in region, like msg is) */
3125 	rep = reply_info_copy(msgrep, env->alloc, NULL);
3126 	if(!rep)
3127diff --git a/services/mesh.c b/services/mesh.c
3128index d4f814d5..624a9d95 100644
3129--- a/services/mesh.c
3130+++ b/services/mesh.c
3131@@ -60,6 +60,9 @@
3132 #include "sldns/wire2str.h"
3133 #include "services/localzone.h"
3134 #include "util/data/dname.h"
3135+#ifdef ENABLE_FASTRPZ
3136+#include "fastrpz/rpz.h"
3137+#endif
3138 #include "respip/respip.h"
3139 #include "services/listen_dnsport.h"
3140 
3141@@ -1076,6 +1079,13 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
3142 	else	secure = 0;
3143 	if(!rep && rcode == LDNS_RCODE_NOERROR)
3144 		rcode = LDNS_RCODE_SERVFAIL;
3145+#ifdef ENABLE_FASTRPZ
3146+	/* Drop the response here for LIBRPZ_POLICY_DROP after iteration. */
3147+	if(rep && rep->security == sec_status_rpz_drop) {
3148+		log_query_info(VERB_QUERY, "rpz drop", &m->s.qinfo);
3149+		secure = 0;
3150+	} else
3151+#endif
3152 	/* send the reply */
3153 	/* We don't reuse the encoded answer if either the previous or current
3154 	 * response has a local alias.  We could compare the alias records
3155@@ -1255,6 +1265,7 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh,
3156 	key.s.is_valrec = valrec;
3157 	key.s.qinfo = *qinfo;
3158 	key.s.query_flags = qflags;
3159+	key.reply_list = NULL;
3160 	/* We are searching for a similar mesh state when we DO want to
3161 	 * aggregate the state. Thus unique is set to NULL. (default when we
3162 	 * desire aggregation).*/
3163@@ -1301,6 +1312,10 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
3164 	if(!r)
3165 		return 0;
3166 	r->query_reply = *rep;
3167+#ifdef ENABLE_FASTRPZ
3168+	/* The new reply structure owns the RPZ state. */
3169+	rep->rpz = NULL;
3170+#endif
3171 	r->edns = *edns;
3172 	if(edns->opt_list) {
3173 		r->edns.opt_list = edns_opt_copy_region(edns->opt_list,
3174diff --git a/util/config_file.c b/util/config_file.c
3175index 119b2223..ce43a234 100644
3176--- a/util/config_file.c
3177+++ b/util/config_file.c
3178@@ -1434,6 +1434,8 @@ config_delete(struct config_file* cfg)
3179 	free(cfg->dnstap_socket_path);
3180 	free(cfg->dnstap_identity);
3181 	free(cfg->dnstap_version);
3182+	if (cfg->rpz_cstr)
3183+		free(cfg->rpz_cstr);
3184 	config_deldblstrlist(cfg->ratelimit_for_domain);
3185 	config_deldblstrlist(cfg->ratelimit_below_domain);
3186 	config_delstrlist(cfg->python_script);
3187diff --git a/util/config_file.h b/util/config_file.h
3188index b3ef930a..56173b80 100644
3189--- a/util/config_file.h
3190+++ b/util/config_file.h
3191@@ -494,6 +494,11 @@ struct config_file {
3192 	/** true to disable DNSSEC lameness check in iterator */
3193 	int disable_dnssec_lame_check;
3194 
3195+	/** true to enable RPZ */
3196+	int rpz_enable;
3197+	/** RPZ configuration */
3198+	char* rpz_cstr;
3199+
3200 	/** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */
3201 	int ip_ratelimit;
3202 	/** number of slabs for ip_ratelimit cache */
3203diff --git a/util/configlexer.lex b/util/configlexer.lex
3204index a86ddf55..b56bcfb4 100644
3205--- a/util/configlexer.lex
3206+++ b/util/configlexer.lex
3207@@ -438,6 +438,10 @@ dnstap-log-forwarder-query-messages{COLON}	{
3208 		YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES) }
3209 dnstap-log-forwarder-response-messages{COLON}	{
3210 		YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) }
3211+rpz{COLON}			{ YDVAR(0, VAR_RPZ) }
3212+rpz-enable{COLON}		{ YDVAR(1, VAR_RPZ_ENABLE) }
3213+rpz-zone{COLON}			{ YDVAR(1, VAR_RPZ_ZONE) }
3214+rpz-option{COLON}		{ YDVAR(1, VAR_RPZ_OPTION) }
3215 disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) }
3216 ip-ratelimit{COLON}		{ YDVAR(1, VAR_IP_RATELIMIT) }
3217 ratelimit{COLON}		{ YDVAR(1, VAR_RATELIMIT) }
3218diff --git a/util/configparser.y b/util/configparser.y
3219index 10227a2f..cdbcf7cd 100644
3220--- a/util/configparser.y
3221+++ b/util/configparser.y
3222@@ -125,6 +125,7 @@ extern struct config_parser_state* cfg_parser;
3223 %token VAR_DNSTAP_LOG_CLIENT_RESPONSE_MESSAGES
3224 %token VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES
3225 %token VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES
3226+%token VAR_RPZ VAR_RPZ_ENABLE VAR_RPZ_ZONE VAR_RPZ_OPTION
3227 %token VAR_RESPONSE_IP_TAG VAR_RESPONSE_IP VAR_RESPONSE_IP_DATA
3228 %token VAR_HARDEN_ALGO_DOWNGRADE VAR_IP_TRANSPARENT
3229 %token VAR_DISABLE_DNSSEC_LAME_CHECK
3230@@ -171,7 +172,7 @@ extern struct config_parser_state* cfg_parser;
3231 
3232 %%
3233 toplevelvars: /* empty */ | toplevelvars toplevelvar ;
3234-toplevelvar: serverstart contents_server | stubstart contents_stub |
3235+toplevelvar: serverstart contents_server | stubstart contents_stub | rpzstart contents_rpz |
3236 	forwardstart contents_forward | pythonstart contents_py | 
3237 	rcstart contents_rc | dtstart contents_dt | viewstart contents_view |
3238 	dnscstart contents_dnsc | cachedbstart contents_cachedb |
3239@@ -2726,6 +2727,50 @@ dt_dnstap_log_forwarder_response_messages: VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MES
3240 		free($2);
3241 	}
3242 	;
3243+rpzstart: VAR_RPZ
3244+	{
3245+		OUTYY(("\nP(rpz:)\n"));
3246+	}
3247+	;
3248+contents_rpz: contents_rpz content_rpz
3249+	| ;
3250+content_rpz: rpz_enable | rpz_zone | rpz_option
3251+	;
3252+rpz_enable: VAR_RPZ_ENABLE STRING_ARG
3253+	{
3254+		OUTYY(("P(rpz_enable:%s)\n", $2));
3255+		if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
3256+			yyerror("expected yes or no.");
3257+		else cfg_parser->cfg->rpz_enable = (strcmp($2, "yes")==0);
3258+		free($2);
3259+	}
3260+	;
3261+rpz_zone: VAR_RPZ_ZONE STRING_ARG
3262+	{
3263+		char *new_cstr, *old_cstr;
3264+
3265+		OUTYY(("P(rpz_zone:%s)\n", $2));
3266+		old_cstr = cfg_parser->cfg->rpz_cstr;
3267+		if(asprintf(&new_cstr, "%s\nzone %s", old_cstr?old_cstr:"", $2) == -1) {new_cstr = NULL; yyerror("out of memory");}
3268+		else if(!new_cstr)
3269+			yyerror("out of memory");
3270+		free(old_cstr);
3271+		cfg_parser->cfg->rpz_cstr = new_cstr;
3272+	}
3273+	;
3274+rpz_option: VAR_RPZ_OPTION STRING_ARG
3275+	{
3276+		char *new_cstr, *old_cstr;
3277+
3278+		OUTYY(("P(rpz_option:%s)\n", $2));
3279+		old_cstr = cfg_parser->cfg->rpz_cstr;
3280+		if(asprintf(&new_cstr, "%s\n%s", old_cstr ? old_cstr : "", $2) == -1) {new_cstr = NULL; yyerror("out of memory");}
3281+		else if(!new_cstr)
3282+			yyerror("out of memory");
3283+		free(old_cstr);
3284+		cfg_parser->cfg->rpz_cstr = new_cstr;
3285+	}
3286+	;
3287 pythonstart: VAR_PYTHON
3288 	{ 
3289 		OUTYY(("\nP(python:)\n")); 
3290diff --git a/util/data/msgencode.c b/util/data/msgencode.c
3291index a51a4b9b..475dfce9 100644
3292--- a/util/data/msgencode.c
3293+++ b/util/data/msgencode.c
3294@@ -590,6 +590,35 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
3295 	return RETVAL_OK;
3296 }
3297 
3298+#ifdef ENABLE_FASTRPZ
3299+/* Insert the RPZ SOA even with MINIMAL_RESPONSES */
3300+static int
3301+insert_rpz_soa(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
3302+	       sldns_buffer* pkt, size_t rrsets_before, time_t timenow,
3303+	       struct regional* region, struct compress_tree_node** tree,
3304+	       size_t rr_offset)
3305+{
3306+	int r;
3307+	size_t i, setstart;
3308+
3309+	*num_rrs = 0;
3310+	for(i=0; i<num_rrsets; i++) {
3311+		if (rep->rrsets[rrsets_before+i]->rk.type != LDNS_RR_TYPE_SOA)
3312+			continue;
3313+		setstart = sldns_buffer_position(pkt);
3314+		if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i],
3315+					  pkt, num_rrs, timenow, region,
3316+					  1, 0, tree, LDNS_SECTION_ADDITIONAL,
3317+					  LDNS_RR_TYPE_ANY, 0, rr_offset))
3318+		   != RETVAL_OK) {
3319+			sldns_buffer_set_position(pkt, setstart);
3320+			return r;
3321+		}
3322+	}
3323+	return RETVAL_OK;
3324+}
3325+
3326+#endif
3327 /** store query section in wireformat buffer, return RETVAL */
3328 static int
3329 insert_query(struct query_info* qinfo, struct compress_tree_node** tree, 
3330@@ -777,6 +806,19 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
3331 			}
3332 			sldns_buffer_write_u16_at(buffer, 10, arcount);
3333 		}
3334+#ifdef ENABLE_FASTRPZ
3335+	} else if(rep->security == sec_status_rpz_rewritten) {
3336+		/* Insert the RPZ SOA for rpz even with MINIMAL_RESPONSES */
3337+		r = insert_rpz_soa(rep, rep->ar_numrrsets, &arcount, buffer,
3338+				   rep->an_numrrsets + rep->ns_numrrsets,
3339+				   timenow, region, &tree, rr_offset);
3340+		if(r!= RETVAL_OK) {
3341+			if(r != RETVAL_TRUNC)
3342+				return 0;
3343+			/* no need to set TC bit, this is the additional */
3344+			sldns_buffer_write_u16_at(buffer, 10, arcount);
3345+		}
3346+#endif
3347 	}
3348 	sldns_buffer_flip(buffer);
3349 	return 1;
3350diff --git a/util/data/packed_rrset.c b/util/data/packed_rrset.c
3351index 7b9d5494..e44b2ce5 100644
3352--- a/util/data/packed_rrset.c
3353+++ b/util/data/packed_rrset.c
3354@@ -255,6 +255,10 @@ sec_status_to_string(enum sec_status s)
3355 	case sec_status_insecure: 	return "sec_status_insecure";
3356 	case sec_status_secure_sentinel_fail: 	return "sec_status_secure_sentinel_fail";
3357 	case sec_status_secure: 	return "sec_status_secure";
3358+#ifdef ENABLE_FASTRPZ
3359+	case sec_status_rpz_rewritten: 	return "sec_status_rpz_rewritten";
3360+	case sec_status_rpz_drop: 	return "sec_status_rpz_drop";
3361+#endif
3362 	}
3363 	return "unknown_sec_status_value";
3364 }
3365diff --git a/util/data/packed_rrset.h b/util/data/packed_rrset.h
3366index 3a5335dd..20113217 100644
3367--- a/util/data/packed_rrset.h
3368+++ b/util/data/packed_rrset.h
3369@@ -193,7 +193,15 @@ enum sec_status {
3370 	sec_status_secure_sentinel_fail,
3371 	/** SECURE means that the object (RRset or message) validated 
3372 	 * according to local policy. */
3373-	sec_status_secure
3374+	sec_status_secure,
3375+#ifdef ENABLE_FASTRPZ
3376+	/** RPZ_REWRITTEN means that the response has been rewritten by
3377+	 * rpz and so cannot be verified. */
3378+	sec_status_rpz_rewritten,
3379+	/** RPZ_DROP means that the response has been rewritten by rpz
3380+	 * as silence. */
3381+	sec_status_rpz_drop
3382+#endif
3383 };
3384 
3385 /**
3386diff --git a/util/netevent.c b/util/netevent.c
3387index 980bb8be..d537d288 100644
3388--- a/util/netevent.c
3389+++ b/util/netevent.c
3390@@ -57,6 +57,9 @@
3391 #ifdef HAVE_OPENSSL_ERR_H
3392 #include <openssl/err.h>
3393 #endif
3394+#ifdef ENABLE_FASTRPZ
3395+#include "fastrpz/rpz.h"
3396+#endif
3397 
3398 /* -------- Start of local definitions -------- */
3399 /** if CMSG_ALIGN is not defined on this platform, a workaround */
3400@@ -590,6 +593,9 @@ comm_point_udp_ancil_callback(int fd, short event, void* arg)
3401 	struct cmsghdr* cmsg;
3402 #endif /* S_SPLINT_S */
3403 
3404+#ifdef ENABLE_FASTRPZ
3405+	rep.rpz = NULL;
3406+#endif
3407 	rep.c = (struct comm_point*)arg;
3408 	log_assert(rep.c->type == comm_udp);
3409 
3410@@ -679,6 +685,9 @@ comm_point_udp_callback(int fd, short event, void* arg)
3411 	int i;
3412 	struct sldns_buffer *buffer;
3413 
3414+#ifdef ENABLE_FASTRPZ
3415+	rep.rpz = NULL;
3416+#endif
3417 	rep.c = (struct comm_point*)arg;
3418 	log_assert(rep.c->type == comm_udp);
3419 
3420@@ -722,6 +731,9 @@ comm_point_udp_callback(int fd, short event, void* arg)
3421 			(void)comm_point_send_udp_msg(rep.c, buffer,
3422 				(struct sockaddr*)&rep.addr, rep.addrlen);
3423 		}
3424+#ifdef ENABLE_FASTRPZ
3425+		rpz_end(&rep);
3426+#endif
3427 		if(!rep.c || rep.c->fd != fd) /* commpoint closed to -1 or reused for
3428 		another UDP port. Note rep.c cannot be reused with TCP fd. */
3429 			break;
3430@@ -3184,6 +3196,9 @@ comm_point_send_reply(struct comm_reply *repinfo)
3431 				repinfo->c->tcp_timeout_msec);
3432 		}
3433 	}
3434+#ifdef ENABLE_FASTRPZ
3435+	rpz_end(repinfo);
3436+#endif
3437 }
3438 
3439 void 
3440@@ -3193,6 +3208,9 @@ comm_point_drop_reply(struct comm_reply* repinfo)
3441 		return;
3442 	log_assert(repinfo->c);
3443 	log_assert(repinfo->c->type != comm_tcp_accept);
3444+#ifdef ENABLE_FASTRPZ
3445+	rpz_end(repinfo);
3446+#endif
3447 	if(repinfo->c->type == comm_udp)
3448 		return;
3449 	if(repinfo->c->tcp_req_info)
3450@@ -3214,6 +3232,9 @@ comm_point_start_listening(struct comm_point* c, int newfd, int msec)
3451 {
3452 	verbose(VERB_ALGO, "comm point start listening %d (%d msec)", 
3453 		c->fd==-1?newfd:c->fd, msec);
3454+#ifdef ENABLE_FASTRPZ
3455+	rpz_end(&c->repinfo);
3456+#endif
3457 	if(c->type == comm_tcp_accept && !c->tcp_free) {
3458 		/* no use to start listening no free slots. */
3459 		return;
3460diff --git a/util/netevent.h b/util/netevent.h
3461index d80c72b3..0233292f 100644
3462--- a/util/netevent.h
3463+++ b/util/netevent.h
3464@@ -120,6 +120,10 @@ struct comm_reply {
3465 	/** return type 0 (none), 4(IP4), 6(IP6) */
3466 	int srctype;
3467 	/* DnsCrypt context */
3468+#ifdef ENABLE_FASTRPZ
3469+	/** per-request RPZ state */
3470+	struct commreply_rpz* rpz;
3471+#endif
3472 #ifdef USE_DNSCRYPT
3473 	uint8_t client_nonce[crypto_box_HALF_NONCEBYTES];
3474 	uint8_t nmkey[crypto_box_BEFORENMBYTES];
3475diff --git a/validator/validator.c b/validator/validator.c
3476index 4c560a8e..71de3760 100644
3477--- a/validator/validator.c
3478+++ b/validator/validator.c
3479@@ -2755,6 +2755,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
3480 			default:
3481 				/* NSEC proof did not work, try next */
3482 				break;
3483+#ifdef ENABLE_FASTRPZ
3484+			case sec_status_rpz_rewritten:
3485+			case sec_status_rpz_drop:
3486+				fatal_exit("impossible RPZ sec_status");
3487+				break;
3488+#endif
3489 		}
3490 
3491 		sec = nsec3_prove_nods(qstate->env, ve, 
3492@@ -2788,6 +2794,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
3493 			default:
3494 				/* NSEC3 proof did not work */
3495 				break;
3496+#ifdef ENABLE_FASTRPZ
3497+			case sec_status_rpz_rewritten:
3498+			case sec_status_rpz_drop:
3499+				fatal_exit("impossible RPZ sec_status");
3500+				break;
3501+#endif
3502 		}
3503 
3504 		/* Apparently, no available NSEC/NSEC3 proved NODATA, so 
3505