aboutsummaryrefslogtreecommitdiff
path: root/contrib/fastrpz.patch
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/fastrpz.patch')
-rw-r--r--contrib/fastrpz.patch3552
1 files changed, 3552 insertions, 0 deletions
diff --git a/contrib/fastrpz.patch b/contrib/fastrpz.patch
new file mode 100644
index 000000000000..aa8c1ece0e20
--- /dev/null
+++ b/contrib/fastrpz.patch
@@ -0,0 +1,3552 @@
+===================================================================
+RCS file: ./RCS/Makefile.in,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./Makefile.in
+--- ./Makefile.in
++++ ./Makefile.in
+@@ -23,6 +23,8 @@
+ CHECKLOCK_OBJ=@CHECKLOCK_OBJ@
+ DNSTAP_SRC=@DNSTAP_SRC@
+ DNSTAP_OBJ=@DNSTAP_OBJ@
++FASTRPZ_SRC=@FASTRPZ_SRC@
++FASTRPZ_OBJ=@FASTRPZ_OBJ@
+ DNSCRYPT_SRC=@DNSCRYPT_SRC@
+ DNSCRYPT_OBJ=@DNSCRYPT_OBJ@
+ WITH_PYTHONMODULE=@WITH_PYTHONMODULE@
+@@ -125,7 +127,7 @@
+ edns-subnet/edns-subnet.c edns-subnet/subnetmod.c \
+ edns-subnet/addrtree.c edns-subnet/subnet-whitelist.c \
+ cachedb/cachedb.c respip/respip.c $(CHECKLOCK_SRC) \
+-$(DNSTAP_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC)
++$(DNSTAP_SRC) $(FASTRPZ_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC)
+ COMMON_OBJ_WITHOUT_NETCALL=dns.lo infra.lo rrset.lo dname.lo msgencode.lo \
+ as112.lo msgparse.lo msgreply.lo packed_rrset.lo iterator.lo iter_delegpt.lo \
+ iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
+@@ -137,7 +139,7 @@
+ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \
+ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo cachedb.lo \
+ $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \
+-$(IPSECMOD_OBJ)
++$(FASTRPZ_OBJ) $(DNSCRYPT_OBJ)
+ COMMON_OBJ_WITHOUT_NETCALL+=respip.lo
+ COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \
+ outside_network.lo
+@@ -398,6 +401,11 @@
+ $(srcdir)/util/config_file.h $(srcdir)/util/log.h \
+ $(srcdir)/util/netevent.h
+
++# fastrpz
++rpz.lo rpz.o: $(srcdir)/fastrpz/rpz.c config.h fastrpz/rpz.h fastrpz/librpz.h \
++ $(srcdir)/util/config_file.h $(srcdir)/daemon/daemon.h \
++ $(srcdir)/util/log.h
++
+ # Python Module
+ pythonmod.lo pythonmod.o: $(srcdir)/pythonmod/pythonmod.c config.h \
+ pythonmod/interface.h \
+===================================================================
+RCS file: ./RCS/config.h.in,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./config.h.in
+--- ./config.h.in
++++ ./config.h.in
+@@ -1199,4 +1199,11 @@
+ /** the version of unbound-control that this software implements */
+ #define UNBOUND_CONTROL_VERSION 1
+
+-
++/* have __attribute__s used in librpz.h */
++#undef LIBRPZ_HAVE_ATTR
++/** fastrpz librpz.so */
++#undef FASTRPZ_LIBRPZ_PATH
++/** 0=no fastrpz 1=static link 2=dlopen() */
++#undef FASTRPZ_LIB_OPEN
++/** turn on fastrpz response policy zones */
++#undef ENABLE_FASTRPZ
+===================================================================
+RCS file: ./RCS/configure.ac,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./configure.ac
+--- ./configure.ac
++++ ./configure.ac
+@@ -6,6 +6,7 @@
+ sinclude(acx_python.m4)
+ sinclude(ac_pkg_swig.m4)
+ sinclude(dnstap/dnstap.m4)
++sinclude(fastrpz/rpz.m4)
+ sinclude(dnscrypt/dnscrypt.m4)
+
+ # must be numbers. ac_defun because of later processing
+@@ -1352,6 +1353,9 @@
+ ;;
+ esac
+
++# check for Fastrpz with fastrpz/rpz.m4
++ck_FASTRPZ
++
+ AC_MSG_CHECKING([if ${MAKE:-make} supports $< with implicit rule in scope])
+ # on openBSD, the implicit rule make $< work.
+ # on Solaris, it does not work ($? is changed sources, $^ lists dependencies).
+===================================================================
+RCS file: ./daemon/RCS/daemon.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./daemon/daemon.c
+--- ./daemon/daemon.c
++++ ./daemon/daemon.c
+@@ -89,6 +89,9 @@
+ #include "sldns/keyraw.h"
+ #include "respip/respip.h"
+ #include <signal.h>
++#ifdef ENABLE_FASTRPZ
++#include "fastrpz/rpz.h"
++#endif
+
+ #ifdef HAVE_SYSTEMD
+ #include <systemd/sd-daemon.h>
+@@ -451,6 +454,14 @@
+ fatal_exit("dnstap enabled in config but not built with dnstap support");
+ #endif
+ }
++ if(daemon->cfg->rpz_enable) {
++#ifdef ENABLE_FASTRPZ
++ rpz_init(&daemon->rpz_clist, &daemon->rpz_client, daemon->cfg);
++#else
++ fatal_exit("fastrpz enabled in config"
++ " but not built with fastrpz");
++#endif
++ }
+ for(i=0; i<daemon->num; i++) {
+ if(!(daemon->workers[i] = worker_create(daemon, i,
+ shufport+numport*i/daemon->num,
+@@ -691,6 +702,9 @@
+ #ifdef USE_DNSTAP
+ dt_delete(daemon->dtenv);
+ #endif
++#ifdef ENABLE_FASTRPZ
++ rpz_delete(&daemon->rpz_clist, &daemon->rpz_client);
++#endif
+ daemon->cfg = NULL;
+ }
+
+===================================================================
+RCS file: ./daemon/RCS/daemon.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./daemon/daemon.h
+--- ./daemon/daemon.h
++++ ./daemon/daemon.h
+@@ -134,6 +134,11 @@
+ /** the dnscrypt environment */
+ struct dnsc_env* dnscenv;
+ #endif
++#ifdef ENABLE_FASTRPZ
++ /** global opaque rpz handles */
++ struct librpz_clist *rpz_clist;
++ struct librpz_client *rpz_client;
++#endif
+ };
+
+ /**
+===================================================================
+RCS file: ./daemon/RCS/worker.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./daemon/worker.c
+--- ./daemon/worker.c
++++ ./daemon/worker.c
+@@ -73,6 +73,9 @@
+ #include "libunbound/context.h"
+ #include "libunbound/libworker.h"
+ #include "sldns/sbuffer.h"
++#ifdef ENABLE_FASTRPZ
++#include "fastrpz/rpz.h"
++#endif
+ #include "sldns/wire2str.h"
+ #include "util/shm_side/shm_main.h"
+ #include "dnscrypt/dnscrypt.h"
+@@ -526,8 +529,27 @@
+ /* not secure */
+ secure = 0;
+ break;
++#ifdef ENABLE_FASTRPZ
++ case sec_status_rpz_rewritten:
++ case sec_status_rpz_drop:
++ fatal_exit("impossible cached RPZ sec_status");
++ break;
++#endif
+ }
+ }
++#ifdef ENABLE_FASTRPZ
++ if(repinfo->rpz) {
++ /* Scan the cached answer for RPZ hits.
++ * ret=1 use cache entry
++ * ret=-1 rewritten response already sent or dropped
++ * ret=0 deny a cached entry exists
++ */
++ int ret = rpz_worker_cache(worker, msg->rep, qinfo,
++ id, flags, edns, repinfo);
++ if(ret != 1)
++ return ret;
++ }
++#endif
+ /* return this delegation from the cache */
+ edns->edns_version = EDNS_ADVERTISED_VERSION;
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+@@ -688,6 +710,23 @@
+ secure = 0;
+ }
+ } else secure = 0;
++#ifdef ENABLE_FASTRPZ
++ if(repinfo->rpz) {
++ /* Scan the cached answer for RPZ hits.
++ * ret=1 use cache entry
++ * ret=-1 rewritten response already sent or dropped
++ * ret=0 deny a cached entry exists
++ */
++ int ret = rpz_worker_cache(worker, rep, qinfo, id, flags, edns,
++ repinfo);
++ if(ret != 1) {
++ rrset_array_unlock_touch(worker->env.rrset_cache,
++ worker->scratchpad, rep->ref,
++ rep->rrset_count);
++ return ret;
++ }
++ }
++#endif
+
+ edns->edns_version = EDNS_ADVERTISED_VERSION;
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+@@ -1267,6 +1306,15 @@
+ log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from",
+ &repinfo->addr, repinfo->addrlen);
+ goto send_reply;
++#ifdef ENABLE_FASTRPZ
++ } else {
++ /* Start to rewrite for response policy zones.
++ * This can hit a qname trigger and be done. */
++ if(rpz_start(worker, &qinfo, repinfo, &edns)) {
++ regional_free_all(worker->scratchpad);
++ return 0;
++ }
++#endif
+ }
+
+ /* If we've found a local alias, replace the qname with the alias
+@@ -1315,12 +1363,21 @@
+ h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2));
+ if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) {
+ /* answer from cache - we have acquired a readlock on it */
+- if(answer_from_cache(worker, &qinfo,
++ ret = answer_from_cache(worker, &qinfo,
+ cinfo, &need_drop, &alias_rrset, &partial_rep,
+ (struct reply_info*)e->data,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+ sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
+- &edns)) {
++ &edns);
++#ifdef ENABLE_FASTRPZ
++ if(ret < 0) {
++ /* RPZ already dropped or sent a response. */
++ lock_rw_unlock(&e->lock);
++ regional_free_all(worker->scratchpad);
++ return 0;
++ }
++#endif
++ if(ret) {
+ /* prefetch it if the prefetch TTL expired.
+ * Note that if there is more than one pass
+ * its qname must be that used for cache
+@@ -1371,11 +1428,19 @@
+ lock_rw_unlock(&e->lock);
+ }
+ if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
+- if(answer_norec_from_cache(worker, &qinfo,
++ ret = answer_norec_from_cache(worker, &qinfo,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+ sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
+- &edns)) {
++ &edns);
++ if(ret) {
+ regional_free_all(worker->scratchpad);
++#ifdef ENABLE_FASTRPZ
++ if(ret < 0) {
++ /* RPZ already dropped
++ * or sent a response. */
++ return 0;
++ }
++#endif
+ goto send_reply;
+ }
+ verbose(VERB_ALGO, "answer norec from cache -- "
+===================================================================
+RCS file: ./doc/RCS/unbound.conf.5.in,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./doc/unbound.conf.5.in
+--- ./doc/unbound.conf.5.in
++++ ./doc/unbound.conf.5.in
+@@ -1446,6 +1446,81 @@
+ .B dns64\-synthall: \fI<yes or no>\fR
+ Debug option, default no. If enabled, synthesize all AAAA records
+ despite the presence of actual AAAA records.
++.SS "Response Policy Zone Rewriting"
++.LP
++Response policy zone rewriting is controlled with the
++.B rpz
++clause.
++It must contain a
++.B rpz\-enable:
++option, and one or more
++.B rpz\-zone:
++options.
++It will usually also contain
++.B rpz\-option:
++clauses with general rewriting options or specifying dnsrpzd parameters.
++Beneath the surface, the text in
++.B rpz\-zone: \fI<"domain">\fR
++is converted to \fI"zone domain\\n"\fR and added to the configuration string
++given to
++\fIlibrpz\fR(3).
++The text in
++.B rpz-option \fI<"text">\fR
++is also added to that configuration string.
++.LP
++If using chroot, then the chroot directory must contain the \fIdnsrpzd\fR(3)
++command and the shared libraries that it uses.
++Those can be found with the \fIldd\fR(1) command.
++.LP
++Resolver zone and rewriting options and response policy zone triggers and
++actions are described in \fIlibrpz\fR(3).
++The separate control file that specifies the policy zones maintained by
++the dnsrpzd daemon is described in \fIdnsrpzd\fR(8).
++.LP
++Many installations need a local whitelist that exempts local
++domains from rewriting.
++Whitelist records can be in zones transferred by dnsrpzd from
++authorities or in a local zone file.
++.TP
++.B rpz-enable: \fI<yes or no>
++enables Fastrpz.
++If not enabled, the other options in the
++.B rpz:
++clause are ignored.
++.TP
++.B rpz-zone: \fI<"zone and options">
++specifies a policy zone and optional per-zone rewriting parameters.
++.TP
++.B rpz-option: \fI<"option">
++specifies general Fastrpz options.
++.LP
++Fastrpz is available only on POSIX compliant UNIX-like systems with the
++\fImmap\fR(2) system call.
++.LP
++Fastrpz in Unbound differs from rpz and fastrpz in BIND by
++.RS 3
++.HP 4
++RPZ-CLIENT-IP triggers can only be used in the first policy zone
++specified with
++.B rpz-zone:
++.HP
++Policy zone rewriting is disabled by the DO bit in DNS requests
++even when no DNSSEC signatures are supplied by authorities.
++.HP
++Unbound local zones are not subject to rpz rewriting.
++.HP
++Like Fastrpz with BIND but unlike classic BIND rpz,
++the ADDITIONAL sections of rewritten responses contain the SOA record from
++the policy zone used to rewrite the response.
++.RE
++.P
++.nf
++# example Fastrpz settings for use with chroot on Freebsd
++rpz:
++ rpz-zone: "rpz.example.org"
++ rpz-zone: "other.rpz.example.org ip-as-ns yes"
++ rpz-option: "dnsrpzd ./dnsrpzd"
++.fi
+ .SS "DNSCrypt Options"
+ .LP
+ The
+===================================================================
+RCS file: ./fastrpz/RCS/librpz.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./fastrpz/librpz.h
+--- ./fastrpz/librpz.h
++++ ./fastrpz/librpz.h
+@@ -0,0 +1,957 @@
++/*
++ * Define the interface from a DNS resolver to the Response Policy Zone
++ * library, librpz.
++ *
++ * This file should be included only the interface functions between the
++ * resolver and librpz to avoid name space pollution.
++ *
++ * Copyright (c) 2016-2017 Farsight Security, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ *
++ * Fastrpz version 1.2.10
++ */
++
++#ifndef LIBRPZ_H
++#define LIBRPZ_H
++
++#include <arpa/nameser.h>
++#include <netinet/in.h>
++#include <stdarg.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <sys/types.h>
++
++
++/*
++ * Allow either ordinary or dlopen() linking.
++ */
++#ifdef LIBRPZ_INTERNAL
++#define LIBDEF(t,s) extern t s;
++#define LIBDEF_F(f) LIBDEF(librpz_##f##_t, librpz_##f)
++#else
++#define LIBDEF(t,s)
++#define LIBDEF_F(f)
++#endif
++
++/*
++ * Response Policy Zone triggers.
++ * Comparisons of trigger precedences require
++ * LIBRPZ_TRIG_CLIENT_IP < LIBRPZ_TRIG_QNAME < LIBRPZ_TRIG_IP
++ * < LIBRPZ_TRIG_NSDNAME < LIBRPZ_TRIG_NSIP}
++ */
++typedef enum {
++ LIBRPZ_TRIG_BAD =0,
++ LIBRPZ_TRIG_CLIENT_IP =1,
++ LIBRPZ_TRIG_QNAME =2,
++ LIBRPZ_TRIG_IP =3,
++ LIBRPZ_TRIG_NSDNAME =4,
++ LIBRPZ_TRIG_NSIP =5
++} librpz_trig_t;
++#define LIBRPZ_TRIG_SIZE 3 /* sizeof librpz_trig_t in bits */
++typedef uint8_t librpz_tbit_t; /* one bit for each of the TRIGS_NUM
++ * trigger types */
++
++
++/*
++ * Response Policy Zone Actions or policies
++ */
++typedef enum {
++ LIBRPZ_POLICY_UNDEFINED =0, /* an empty entry or no decision yet */
++ LIBRPZ_POLICY_DELETED =1, /* placeholder for a deleted policy */
++
++ LIBRPZ_POLICY_PASSTHRU =2, /* 'passthru': do not rewrite */
++ LIBRPZ_POLICY_DROP =3, /* 'drop': do not respond */
++ LIBRPZ_POLICY_TCP_ONLY =4, /* 'tcp-only': answer UDP with TC=1 */
++ LIBRPZ_POLICY_NXDOMAIN =5, /* 'nxdomain': answer with NXDOMAIN */
++ LIBRPZ_POLICY_NODATA =6, /* 'nodata': answer with ANCOUNT=0 */
++ LIBRPZ_POLICY_RECORD =7, /* rewrite with the policy's RR */
++
++ /* only in client configurations to override the zone */
++ LIBRPZ_POLICY_GIVEN, /* 'given': what policy record says */
++ LIBRPZ_POLICY_DISABLED, /* at most log */
++ LIBRPZ_POLICY_CNAME, /* answer with 'cname x' */
++} librpz_policy_t;
++#define LIBRPZ_POLICY_BITS 4
++
++/*
++ * Special policies that appear as targets of CNAMEs
++ * NXDOMAIN is signaled by a CNAME with a "." target.
++ * NODATA is signaled by a CNAME with a "*." target.
++ */
++#define LIBRPZ_RPZ_PREFIX "rpz-"
++#define LIBRPZ_RPZ_PASSTHRU LIBRPZ_RPZ_PREFIX"passthru"
++#define LIBRPZ_RPZ_DROP LIBRPZ_RPZ_PREFIX"drop"
++#define LIBRPZ_RPZ_TCP_ONLY LIBRPZ_RPZ_PREFIX"tcp-only"
++
++
++typedef uint16_t librpz_dznum_t; /* dnsrpzd zone # in [0,DZNUM_MAX] */
++typedef uint8_t librpz_cznum_t; /* client zone # in [0,CZNUM_MAX] */
++
++
++/*
++ * CIDR block
++ */
++typedef struct librpz_prefix {
++ union {
++ struct in_addr in;
++ struct in6_addr in6;
++ } addr;
++ uint8_t family;
++ uint8_t len;
++} librpz_prefix_t;
++
++/*
++ * A domain
++ */
++typedef uint8_t librpz_dsize_t;
++typedef struct librpz_domain {
++ librpz_dsize_t size; /* of only .d */
++ uint8_t d[0]; /* variable length wire format */
++} librpz_domain_t;
++
++/*
++ * A maximal domain buffer
++ */
++typedef struct librpz_domain_buf {
++ librpz_dsize_t size;
++ uint8_t d[NS_MAXCDNAME];
++} librpz_domain_buf_t;
++
++/*
++ * A resource record without the owner name.
++ * C compilers say that sizeof(librpz_rr_t)=12 instead of 10.
++ */
++typedef struct {
++ uint16_t type; /* network byte order */
++ uint16_t class; /* network byte order */
++ uint32_t ttl; /* network byte order */
++ uint16_t rdlength; /* network byte order */
++ uint8_t rdata[0]; /* variable length */
++} librpz_rr_t;
++
++/*
++ * The database file might be mapped with different starting addresses
++ * by concurrent clients (resolvers), and so all pointers are offsets.
++ */
++typedef uint32_t librpz_idx_t;
++#define LIBRPZ_IDX_NULL 0
++#define LIBRPZ_IDX_MIN 1
++#define LIBRPZ_IDX_BAD ((librpz_idx_t)-1)
++/**
++ * Partial decoded results of a set of RPZ queries for a single DNS response
++ * or interation through the mapped file.
++ */
++typedef int16_t librpz_result_id_t;
++typedef struct librpz_result {
++ librpz_idx_t next_rr;
++ librpz_result_id_t hit_id; /* trigger ID from resolver */
++ librpz_policy_t zpolicy; /* policy from zone */
++ librpz_policy_t policy; /* adjusted by client configuration */
++ librpz_dznum_t dznum; /* dnsrpzd zone number */
++ librpz_cznum_t cznum; /* librpz client zone number */
++ librpz_trig_t trig:LIBRPZ_TRIG_SIZE;
++ bool log:1; /* log rewrite given librpz_log_level */
++} librpz_result_t;
++
++
++/**
++ * librpz trace or log levels.
++ */
++typedef enum {
++ LIBRPZ_LOG_FATAL =0, /* always print fatal errors */
++ LIBRPZ_LOG_ERROR =1, /* errors have this level */
++ LIBRPZ_LOG_TRACE1 =2, /* big events such as dnsrpzd starts */
++ LIBRPZ_LOG_TRACE2 =3, /* smaller dnsrpzd zone transfers */
++ LIBRPZ_LOG_TRACE3 =4, /* librpz hits */
++ LIBRPZ_LOG_TRACE4 =5, /* librpz lookups */
++ LIBRPZ_LOG_INVALID =999,
++} librpz_log_level_t;
++typedef librpz_log_level_t (librpz_log_level_val_t)(librpz_log_level_t level);
++LIBDEF_F(log_level_val)
++
++/**
++ * Logging function that can be supplied by the resolver.
++ * @param level is one of librpz_log_level_t
++ * @param ctx is for use by the resolver's logging system.
++ * NULL mean a context-free message.
++ */
++typedef void(librpz_log_fnc_t)(librpz_log_level_t level, void *ctx,
++ const char *buf);
++
++/**
++ * Point librpz logging functions to the resolver's choice.
++ */
++typedef void (librpz_set_log_t)(librpz_log_fnc_t *new_log, const char *prog_nm);
++LIBDEF_F(set_log)
++
++
++/**
++ * librpz error messages are put in these buffers.
++ * Use a structure intead of naked char* to let the compiler check the length.
++ * A function defined with "foo(char buf[120])" can be called with
++ * "char sbuf[2]; foo(sbuf)" and suffer a buffer overrun.
++ */
++typedef struct {
++ char c[120];
++} librpz_emsg_t;
++
++
++#ifdef LIBRPZ_HAVE_ATTR
++#define LIBRPZ_UNUSED __attribute__((unused))
++#define LIBRPZ_PF(f,l) __attribute__((format(printf,f,l)))
++#define LIBRPZ_NORET __attribute__((__noreturn__))
++#else
++#define LIBRPZ_UNUSED
++#define LIBRPZ_PF(f,l)
++#define LIBRPZ_NORET
++#endif
++
++#ifdef HAVE_BUILTIN_EXPECT
++#define LIBRPZ_LIKELY(c) __builtin_expect(!!(c), 1)
++#define LIBRPZ_UNLIKELY(c) __builtin_expect(!!(c), 0)
++#else
++#define LIBRPZ_LIKELY(c) (c)
++#define LIBRPZ_UNLIKELY(c) (c)
++#endif
++
++typedef bool (librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg);
++LIBDEF_F(parse_log_opt)
++
++typedef void (librpz_vpemsg_t)(librpz_emsg_t *emsg,
++ const char *p, va_list args);
++LIBDEF_F(vpemsg)
++typedef void (librpz_pemsg_t)(librpz_emsg_t *emsg,
++ const char *p, ...) LIBRPZ_PF(2,3);
++LIBDEF_F(pemsg)
++
++typedef void (librpz_vlog_t)(librpz_log_level_t level, void *ctx,
++ const char *p, va_list args);
++LIBDEF_F(vlog)
++typedef void (librpz_log_t)(librpz_log_level_t level, void *ctx,
++ const char *p, ...) LIBRPZ_PF(3,4);
++LIBDEF_F(log)
++
++typedef void (librpz_fatal_t)(int ex_code,
++ const char *p, ...) LIBRPZ_PF(2,3);
++extern void librpz_fatal(int ex_code,
++ const char *p, ...) LIBRPZ_PF(2,3) LIBRPZ_NORET;
++
++typedef void (librpz_rpz_assert_t)(const char *file, unsigned line,
++ const char *p, ...) LIBRPZ_PF(3,4);
++extern void librpz_rpz_assert(const char *file, unsigned line,
++ const char *p, ...) LIBRPZ_PF(3,4) LIBRPZ_NORET;
++
++typedef void (librpz_rpz_vassert_t)(const char *file, uint line,
++ const char *p, va_list args);
++extern void librpz_rpz_vassert(const char *file, uint line,
++ const char *p, va_list args) LIBRPZ_NORET;
++
++
++/*
++ * As far as clients are concerned, all relative pointers or indexes in a
++ * version of the mapped file except trie node parent pointers remain valid
++ * forever. A client must release a version so that it can be garbage
++ * collected by the file system. When dnsrpzd needs to expand the file,
++ * it copies the old file to a new, larger file. Clients can continue
++ * using the old file.
++ *
++ * Versions can also appear in a single file. Old nodes and trie values
++ * within the file are not destroyed until all clients using the version
++ * that contained the old values release the version.
++ *
++ * A client is marked as using version by connecting to the deamon. It is
++ * marked as using all subsequent versions. A client releases all versions
++ * by closing the connection or a range of versions by updating is slot
++ * in the shared memory version table.
++ *
++ * As far as clients are concerned, there are the following possible librpz
++ * failures:
++ * - malloc() or other fatal internal librpz problems indicated by
++ * a failing return from a librpz function
++ * All operations will fail until client handle is destroyed and
++ * recreated with librpz_client_detach() and librpz_client_create().
++ * - corrupt database detected by librpz code, corrupt database detected
++ * by dnsrpzd, or disconnection from the daemon.
++ * Current operations will fail.
++ *
++ * Clients assume that the file has already been unlinked before
++ * the corrupt flag is set so that they do not race with the server
++ * over the corruption of a single file. A client that finds the
++ * corrupt set knows that dnsrpzd has already crashed with
++ * abort() and is restarting. The client can re-connect to dnsrpzd
++ * and retransmit its configuration, backing off as usual if anything
++ * goes wrong.
++ *
++ * Searchs of the database by a client do not need locks against dnsrpzd or
++ * other clients, but a lock is used to protect changes to the connection
++ * by competing threads in the client. The client provides fuctions
++ * to serialize the conncurrent use of any single client handle.
++ * Functions that do nothing are appropriate for applications that are
++ * not "threaded" or that do not share client handles among threads.
++ * Otherwise, functions must be provided to librpz_clientcreate().
++ * Something like the following works with pthreads:
++ *
++ * static void
++ * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); }
++ *
++ * static void
++ * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); }
++ *
++ * static void
++ * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); }
++ *
++ *
++ *
++ * At every instant, all of the data and pointers in the mapped file are valid.
++ * Changes to trie node or other data are always made so that it and
++ * all pointers in and to it remain valid for a time. Old versions are
++ * eventually discarded.
++ *
++ * Dnsrpzd periodically defines a new version by setting asside all changes
++ * made since the previous version was defined. Subsequent changes
++ * made (only!) by dnsrpzd will be part of the next version.
++ *
++ * To discard an old version, dnsrpzd must know that all clients have stopped
++ * using that version. Clients do that by using part of the mapped file
++ * to tell dnsrpzd the oldest version that each client is using.
++ * Dnsrpzd assigns each connecting client an entry in the cversions array
++ * in the mapped file. The client puts version numbers into that entry
++ * to signal to dnsrpzd which versions that can be discarded.
++ * Dnsrpzd is free, as far as that client is concerned, to discard all
++ * numerically smaller versions. A client can disclaim all versions with
++ * the version number VERSIONS_ALL or 0.
++ *
++ * The race between a client changing its entry and dnsrpzd discarding a
++ * version is resolved by allowing dnsrpzd to discard all versions
++ * smaller or equal to the client's version number. If dnsrpzd is in
++ * the midst of discarding or about to discard version N when the
++ * client asserts N, no harm is done. The client depends only on
++ * the consistency of version N+1.
++ *
++ * This version mechanism depends in part on not being exercised too frequently
++ * Version numbers are 32 bits long and dnsrpzd creates new versions
++ * at most once every 30 seconds.
++ */
++
++
++/*
++ * Lock functions for concurrent use of a single librpz_client_t client handle.
++ */
++typedef void(librpz_mutex_t)(void *mutex);
++
++/*
++ * List of connections to dnsrpzd daemons.
++ */
++typedef struct librpz_clist librpz_clist_t;
++
++/*
++ * Client's handle on dnsrpzd.
++ */
++typedef struct librpz_client librpz_client_t;
++
++/**
++ * Create the list of connections to the dnsrpzd daemon.
++ * @param[out] emsg: error message
++ * @param lock: start exclusive access to the client handle
++ * @param unlock: end exclusive access to the client handle
++ * @param mutex_destroy: release the lock
++ * @param mutex: pointer to the lock for the client handle
++ * @param log_ctx: NULL or resolver's context log messages
++ */
++typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg,
++ librpz_mutex_t *lock,
++ librpz_mutex_t *unlock,
++ librpz_mutex_t *mutex_destroy,
++ void *mutex, void *log_ctx);
++LIBDEF_F(clist_create)
++
++
++/**
++ * Release the list of dnsrpzd connections.
++ */
++typedef void (librpz_clist_detach_t)(librpz_clist_t **clistp);
++LIBDEF_F(clist_detach)
++
++/**
++ * Create a librpz client handle.
++ * @param[out] emsg: error message
++ * @param: list of dnsrpzd connections
++ * @param cstr: string of configuration settings separated by ';' or '\n'
++ * @param use_expired: true to not ignore expired zones
++ * @return client handle or NULL if the handle could not be created
++ */
++typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg,
++ librpz_clist_t *clist,
++ const char *cstr,
++ bool use_expired);
++LIBDEF_F(client_create)
++
++/**
++ * Start (if necessary) dnsrpzd and connect to it.
++ * @param[out] emsg: error message
++ * @param client handle
++ * @param optional: true if it is ok if starting the daemon is not allowed
++ */
++typedef bool (librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client,
++ bool optional);
++LIBDEF_F(connect)
++
++/**
++ * Start to destroy a librpz client handle.
++ * It will not be destroyed until the last set of RPZ queries represented
++ * by a librpz_rsp_t ends.
++ * @param client handle to be released
++ * @return false on error
++ */
++typedef void (librpz_client_detach_t)(librpz_client_t **clientp);
++LIBDEF_F(client_detach)
++
++/**
++ * State for a set of RPZ queries for a single DNS response
++ * or for listing the database.
++ */
++typedef struct librpz_rsp librpz_rsp_t;
++
++/**
++ * Start a set of RPZ queries for a single DNS response.
++ * @param[out] emsg: error message for false return or *rspp=NULL
++ * @param[out] rspp created context or NULL
++ * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value
++ * @param client state
++ * @param have_rd: RD=1 in the DNS request
++ * @param have_do: DO=1 in the DNS request
++ * @return false on error
++ */
++typedef bool (librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
++ int *min_ns_dotsp, librpz_client_t *client,
++ bool have_rd, bool have_do);
++LIBDEF_F(rsp_create)
++
++/**
++ * Finish RPZ work for a DNS response.
++ */
++typedef void (librpz_rsp_detach_t)(librpz_rsp_t **rspp);
++LIBDEF_F(rsp_detach)
++
++/**
++ * Get the final, accumulated result of a set of RPZ queries.
++ * Yield LIBRPZ_POLICY_UNDEFINED if
++ * - there were no hits,
++ * - there was a dispositive hit, be we have not recursed and are required
++ * to recurse so that evil DNS authories will not know we are using RPZ
++ * - we have a hit and have recursed, but later data such as NSIP could
++ * override
++ * @param[out] emsg
++ * @param[out] result describes the hit
++ * or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit
++ * @param[out] result: current policy rewrite values
++ * @param recursed: recursion has now been done even if it was not done
++ * when the hit was found
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result,
++ bool recursed, const librpz_rsp_t *rsp);
++LIBDEF_F(rsp_result)
++
++/**
++ * Might looking for a trigger be worthwhile?
++ * @param trig: look for this type of trigger
++ * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP,
++ * or LIBRPZ_TRIG_NSIP and the IP address is IPv6
++ * @return: true if looking could be worthwhile
++ */
++typedef bool (librpz_have_trig_t)(librpz_trig_t trig, bool ipv6,
++ const librpz_rsp_t *rsp);
++LIBDEF_F(have_trig)
++
++/**
++ * Might looking for NSDNAME and NSIP triggers be worthwhile?
++ * @return: true if looking could be worthwhile
++ */
++typedef bool (librpz_have_ns_trig_t)(const librpz_rsp_t *rsp);
++LIBDEF_F(have_ns_trig)
++
++/**
++ * Convert the found client IP trie key to a CIDR block
++ * @param[out] emsg
++ * @param[out] prefix trigger
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg,
++ librpz_prefix_t *prefix,
++ librpz_rsp_t *rsp);
++LIBDEF_F(rsp_clientip_prefix)
++
++/**
++ * Compute the owner name of the found or result trie key, usually to log it.
++ * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip.
++ * example.com. might be a qname trigger. example.com.rpz-nsdname. could
++ * be an NSDNAME trigger.
++ * @param[out] emsg
++ * @param[out] owner domain
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_domain_t)(librpz_emsg_t *emsg,
++ librpz_domain_buf_t *owner,
++ librpz_rsp_t *rsp);
++LIBDEF_F(rsp_domain)
++
++/**
++ * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of
++ * librpz_rsp_result() or librpz_itr_node() or after a previous use of
++ * librpz_rsp_rr(). The RR is in uncompressed wire format including type,
++ * class, ttl and length in network byte order.
++ * @param[out] emsg
++ * @param[out] typep: optional host byte order record type or ns_t_invalid (0)
++ * @param[out] classp: class such as ns_c_in
++ * @param[out] ttlp: TTL
++ * @param[out] rrp: optionall malloc() buffer containting the next RR or
++ * NULL after the last RR
++ * @param[out] result: current policy rewrite values
++ * @param qname: used construct a wildcard CNAME
++ * @param qname_size
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep,
++ uint16_t *classp, uint32_t *ttlp,
++ librpz_rr_t **rrp, librpz_result_t *result,
++ const uint8_t *qname, size_t qname_size,
++ librpz_rsp_t *rsp);
++LIBDEF_F(rsp_rr)
++
++/**
++ * Get the next RR of the LIBRPZ_POLICY_RECORD result.
++ * @param[out] emsg
++ * @param[out] ttlp: TTL
++ * @param[out] rrp: malloc() buffer with SOA RR without owner name
++ * @param[out] result: current policy rewrite values
++ * @param[out] origin: SOA owner name
++ * @param[out] origin_size
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp,
++ librpz_rr_t **rrp, librpz_domain_buf_t *origin,
++ librpz_result_t *result, librpz_rsp_t *rsp);
++LIBDEF_F(rsp_soa)
++
++/**
++ * Get the SOA serial number for a policy zone to compare with a known value
++ * to check whether a zone tranfer is complete.
++ */
++typedef bool (librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp,
++ const char *domain_nm, librpz_rsp_t *rsp);
++LIBDEF_F(soa_serial)
++
++/**
++ * Save the current policy checking state.
++ * @param[out] emsg
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
++LIBDEF_F(rsp_push)
++#define LIBRPZ_RSP_STACK_DEPTH 3
++
++/**
++ * Restore the previous policy checking state.
++ * @param[out] emsg
++ * @param[out] result: NULL or restored policy rewrite values
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result,
++ librpz_rsp_t *rsp);
++LIBDEF_F(rsp_pop)
++
++/**
++ * Discard the most recently save policy checking state.
++ * @param[out] emsg
++ * @param[out] result: NULL or restored policy rewrite values
++ * @return false on error
++ */
++typedef bool (librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
++LIBDEF_F(rsp_pop_discard)
++
++/**
++ * Disable a zone.
++ * @param[out] emsg
++ * @param znum
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg,
++ librpz_cznum_t znum, librpz_rsp_t *rsp);
++LIBDEF_F(rsp_forget_zone)
++
++/**
++ * Apply RPZ to an IP address.
++ * @param[out] emsg
++ * @param addr: address to check
++ * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4
++ * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP
++ * @param hit_id: caller chosen
++ * @param recursed: recursion has been done
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_ck_ip_t)(librpz_emsg_t *emsg,
++ const void *addr, uint family,
++ librpz_trig_t trig, librpz_result_id_t hit_id,
++ bool recursed, librpz_rsp_t *rsp);
++LIBDEF_F(ck_ip)
++
++/**
++ * Apply RPZ to a wire-format domain.
++ * @param[out] emsg
++ * @param domain in wire format
++ * @param domain_size
++ * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME
++ * @param hit_id: caller chosen
++ * @param recursed: recursion has been done
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return false on error
++ */
++typedef bool (librpz_ck_domain_t)(librpz_emsg_t *emsg,
++ const uint8_t *domain, size_t domain_size,
++ librpz_trig_t trig, librpz_result_id_t hit_id,
++ bool recursed, librpz_rsp_t *rsp);
++LIBDEF_F(ck_domain)
++
++/**
++ * Ask dnsrpzd to refresh a zone.
++ * @param[out] emsg error message
++ * @param librpz_domain_t domain to refresh
++ * @param client context
++ * @return false after error
++ */
++typedef bool (librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain,
++ librpz_rsp_t *rsp);
++LIBDEF_F(zone_refresh)
++
++/**
++ * Get a string describing the the databasse
++ * @param license: include the license
++ * @param cfiles: include the configuration file names
++ * @param listens: include the local notify IP addresses
++ * @param[out] emsg error message if the result is null
++ * @param client context
++ * @return malloc'ed string or NULL after error
++ */
++typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg,
++ bool license, bool cfiles, bool listens,
++ librpz_rsp_t *rsp);
++LIBDEF_F(db_info)
++
++/**
++ * Start a context for listing the nodes and/or zones in the mapped file
++ * @param[out] emsg: error message for false return or *rspp=NULL
++ * @param[out[ rspp created context or NULL
++ * @param client context
++ * @return false after error
++ */
++typedef bool (librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp,
++ librpz_client_t *client);
++LIBDEF_F(itr_start)
++
++/**
++ * Get mapped file memory allocation statistics.
++ * @param[out] emsg: error message
++ * @param rsp state from librpz_itr_start()
++ * @return malloc'ed string or NULL after error
++ */
++typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
++LIBDEF_F(mf_stats)
++
++/**
++ * Get versions currently used by clients.
++ * @param[out] emsg: error message
++ * @param[in,out] rsp: state from librpz_itr_start()
++ * @return malloc'ed string or NULL after error
++ */
++typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp);
++LIBDEF_F(vers_stats)
++
++/**
++ * Allocate a string describing the next zone or "" after the last zone.
++ * @param[out] emsg
++ * @param all_zones to list all instead of only requested zones
++ * @param[in,out] rsp state from librpz_rsp_start()
++ * @return malloc'ed string or NULL after error
++ */
++typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones,
++ librpz_rsp_t *rsp);
++LIBDEF_F(itr_zone)
++
++/**
++ * Describe the next trie node while dumping the database.
++ * @param[out] emsg
++ * @param[out] result describes node
++ * or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node.
++ * @param all_zones to list all instead of only requested zones
++ * @param[in,out] rsp state from librpz_itr_start()
++ * @return: false on error
++ */
++typedef bool (librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result,
++ bool all_zones, librpz_rsp_t *rsp);
++LIBDEF_F(itr_node)
++
++/**
++ * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size
++ */
++typedef const char *(librpz_policy2str_t)(librpz_policy_t policy,
++ char *buf, size_t buf_size);
++#define POLICY2STR_SIZE sizeof("policy xxxxxx")
++LIBDEF_F(policy2str)
++
++/**
++ * Trigger type to string.
++ */
++typedef const char *(librpz_trig2str_t)(librpz_trig_t trig);
++LIBDEF_F(trig2str)
++
++/**
++ * Convert a number of seconds to a zone file duration string
++ */
++typedef const char *(librpz_secs2str_t)(time_t secs,
++ char *buf, size_t buf_size);
++#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s")
++LIBDEF_F(secs2str)
++
++/**
++ * Parse a duration with 's', 'm', 'h', 'd', and 'w' units.
++ */
++typedef bool (librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val,
++ const char *str0);
++LIBDEF_F(str2secs)
++
++/**
++ * Translate selected rtypes to strings
++ */
++typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size);
++#define RTYPE2STR_SIZE sizeof("type xxxxx")
++LIBDEF_F(rtype2str)
++
++/**
++ * Local version of ns_name_ntop() for portability.
++ */
++typedef int (librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz);
++LIBDEF_F(domain_ntop)
++
++/**
++ * Local version of ns_name_pton().
++ */
++typedef int (librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz,
++ size_t *dstlen, bool lower);
++LIBDEF_F(domain_pton2)
++
++typedef union socku socku_t;
++typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp,
++ in_port_t port);
++LIBDEF_F(mk_inet_su)
++
++typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su, const
++ struct in6_addr *addrp,
++ uint32_t scope_id, in_port_t port);
++LIBDEF_F(mk_inet6_su)
++
++typedef bool (librpz_str2su_t)(socku_t *sup, const char *str);
++LIBDEF_F(str2su)
++
++typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su);
++LIBDEF_F(su2str)
++#define SU2STR_SIZE (INET6_ADDRSTRLEN+1+6+1)
++
++
++/**
++ * default path to dnsrpzd
++ */
++const char *librpz_dnsrpzd_path;
++
++
++#undef LIBDEF
++
++/*
++ * This is the dlopen() interface to librpz.
++ */
++typedef const struct {
++ const char *dnsrpzd_path;
++ const char *version;
++ librpz_parse_log_opt_t *parse_log_opt;
++ librpz_log_level_val_t *log_level_val;
++ librpz_set_log_t *set_log;
++ librpz_vpemsg_t *vpemsg;
++ librpz_pemsg_t *pemsg;
++ librpz_vlog_t *vlog;
++ librpz_log_t *log;
++ librpz_fatal_t *fatal LIBRPZ_NORET;
++ librpz_rpz_assert_t *rpz_assert LIBRPZ_NORET;
++ librpz_rpz_vassert_t *rpz_vassert LIBRPZ_NORET;
++ librpz_clist_create_t *clist_create;
++ librpz_clist_detach_t *clist_detach;
++ librpz_client_create_t *client_create;
++ librpz_connect_t *connect;
++ librpz_client_detach_t *client_detach;
++ librpz_rsp_create_t *rsp_create;
++ librpz_rsp_detach_t *rsp_detach;
++ librpz_rsp_result_t *rsp_result;
++ librpz_have_trig_t *have_trig;
++ librpz_have_ns_trig_t *have_ns_trig;
++ librpz_rsp_clientip_prefix_t *rsp_clientip_prefix;
++ librpz_rsp_domain_t *rsp_domain;
++ librpz_rsp_rr_t *rsp_rr;
++ librpz_rsp_soa_t *rsp_soa;
++ librpz_soa_serial_t *soa_serial;
++ librpz_rsp_push_t *rsp_push;
++ librpz_rsp_pop_t *rsp_pop;
++ librpz_rsp_pop_discard_t *rsp_pop_discard;
++ librpz_rsp_forget_zone_t *rsp_forget_zone;
++ librpz_ck_ip_t *ck_ip;
++ librpz_ck_domain_t *ck_domain;
++ librpz_zone_refresh_t *zone_refresh;
++ librpz_db_info_t *db_info;
++ librpz_itr_start_t *itr_start;
++ librpz_mf_stats_t *mf_stats;
++ librpz_vers_stats_t *vers_stats;
++ librpz_itr_zone_t *itr_zone;
++ librpz_itr_node_t *itr_node;
++ librpz_policy2str_t *policy2str;
++ librpz_trig2str_t *trig2str;
++ librpz_secs2str_t *secs2str;
++ librpz_str2secs_t *str2secs;
++ librpz_rtype2str_t *rtype2str;
++ librpz_domain_ntop_t *domain_ntop;
++ librpz_domain_pton2_t *domain_pton2;
++ librpz_mk_inet_su_t *mk_inet_su;
++ librpz_mk_inet6_su_t *mk_inet6_su;
++ librpz_str2su_t *str2su;
++ librpz_su2str_t *su2str;
++} librpz_0_t;
++extern librpz_0_t librpz_def_0;
++
++/*
++ * Future versions can be upward compatible by defining LIBRPZ_DEF as
++ * librpz_X_t.
++ */
++#define LIBRPZ_DEF librpz_def_0
++#define LIBRPZ_DEF_STR "librpz_def_0"
++
++typedef librpz_0_t librpz_t;
++extern librpz_t *librpz;
++
++
++#if LIBRPZ_LIB_OPEN == 2
++#include <dlfcn.h>
++
++/**
++ * link-load librpz
++ * @param[out] emsg: error message
++ * @param[in,out] dl_handle: NULL or pointer to new dlopen handle
++ * @param[in] path: librpz.so path
++ * @return address of interface structure or NULL on failure
++ */
++static inline librpz_t *
++librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path)
++{
++ void *handle;
++ librpz_t *new_librpz;
++
++ emsg->c[0] = '\0';
++
++ /*
++ * Close a previously opened handle on librpz.so.
++ */
++ if (dl_handle != NULL && *dl_handle != NULL) {
++ if (dlclose(*dl_handle) != 0) {
++ snprintf(emsg->c, sizeof(librpz_emsg_t),
++ "dlopen(NULL): %s", dlerror());
++ return (NULL);
++ }
++ *dl_handle = NULL;
++ }
++
++ /*
++ * First try the main executable of the process in case it was
++ * linked to librpz.
++ * Do not worry if we cannot search the main executable of the process.
++ */
++ handle = dlopen(NULL, RTLD_NOW | RTLD_LOCAL);
++ if (handle != NULL) {
++ new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
++ if (new_librpz != NULL) {
++ if (dl_handle != NULL)
++ *dl_handle = handle;
++ return (new_librpz);
++ }
++ if (dlclose(handle) != 0) {
++ snprintf(emsg->c, sizeof(librpz_emsg_t),
++ "dlsym(NULL, "LIBRPZ_DEF_STR"): %s",
++ dlerror());
++ return (NULL);
++ }
++ }
++
++ if (path == NULL || path[0] == '\0') {
++ snprintf(emsg->c, sizeof(librpz_emsg_t),
++ "librpz not linked and no dlopen() path provided");
++ return (NULL);
++ }
++
++ handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
++ if (handle == NULL) {
++ snprintf(emsg->c, sizeof(librpz_emsg_t), "dlopen(%s): %s",
++ path, dlerror());
++ return (NULL);
++ }
++ new_librpz = dlsym(handle, LIBRPZ_DEF_STR);
++ if (new_librpz != NULL) {
++ if (dl_handle != NULL)
++ *dl_handle = handle;
++ return (new_librpz);
++ }
++ snprintf(emsg->c, sizeof(librpz_emsg_t),
++ "dlsym(%s, "LIBRPZ_DEF_STR"): %s",
++ path, dlerror());
++ dlclose(handle);
++ return (NULL);
++}
++
++#elif defined(LIBRPZ_LIB_OPEN)
++
++/*
++ * Statically link to the librpz.so DSO on systems without dlopen()
++ */
++static inline librpz_t *
++librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path)
++{
++ (void)(path);
++
++ if (dl_handle != NULL)
++ *dl_handle = NULL;
++
++#if LIBRPZ_LIB_OPEN == 1
++ emsg->c[0] = '\0';
++ return (&LIBRPZ_DEF);
++#else
++ snprintf(emsg->c, sizeof(librpz_emsg_t),
++ "librpz not available via ./configure");
++ return (NULL);
++#endif /* LIBRPZ_LIB_OPEN */
++}
++#endif /* LIBRPZ_LIB_OPEN */
++
++#endif /* LIBRPZ_H */
+===================================================================
+RCS file: ./fastrpz/RCS/rpz.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./fastrpz/rpz.c
+--- ./fastrpz/rpz.c
++++ ./fastrpz/rpz.c
+@@ -0,0 +1,1357 @@
++/*
++ * fastrpz/rpz.c - interface to the fastrpz response policy zone library
++ *
++ * Optimize no-rewrite cases for speed but optimize rewriting for
++ * simplicity and size.
++ */
++
++#include "config.h"
++
++#ifdef ENABLE_FASTRPZ
++#include "daemon/daemon.h"
++#define LIBRPZ_LIB_OPEN FASTRPZ_LIB_OPEN
++#include "fastrpz/rpz.h"
++#include "daemon/worker.h"
++#include "iterator/iter_delegpt.h"
++#include "iterator/iter_utils.h"
++#include "iterator/iterator.h"
++#include "util/data/dname.h"
++#include "util/data/msgencode.h"
++#include "util/data/msgparse.h"
++#include "util/data/msgreply.h"
++#include "util/log.h"
++#include "util/netevent.h"
++#include "util/net_help.h"
++#include "util/regional.h"
++#include "util/storage/slabhash.h"
++#include "services/cache/dns.h"
++#include "services/cache/rrset.h"
++#include "services/mesh.h"
++#include "sldns/sbuffer.h"
++#include "sldns/rrdef.h"
++
++
++typedef enum state {
++ /* No more rewriting */
++ st_off = 1,
++ /* Send SERVFAIL */
++ st_servfail,
++ /* No dispositive hit yet */
++ st_unknown,
++ /* Let the iterator resolve a CNAME or get a delegation point. */
++ st_iterate,
++ /* Let the iterator resolve NS to check NSIP or NSDNAME triggers. */
++ st_ck_ns,
++ /* We have an answer */
++ st_rewritten,
++} st_t;
++
++
++/* RPZ state pointed to by struct comm_reply */
++typedef struct commreply_rpz {
++ /* librpz state */
++ librpz_rsp_t* rsp;
++ /* ID for log messages */
++ int log_id;
++
++ /* from configuration */
++ int min_ns_dots;
++
++ /* Running in the iterator */
++ bool iterating;
++
++ /* current and previous state and librpz result */
++ st_t st;
++ st_t saved_st[LIBRPZ_RSP_STACK_DEPTH-1];
++ librpz_result_t result;
++
++ /* Stop adding CNAMEs to the prepend list before this owner name. */
++ librpz_domain_buf_t cname_hit;
++ /* It is not the first CNAME */
++ bool cname_hit_2nd;
++ librpz_result_id_t hit_id;
++} commreply_rpz_t;
++
++
++/* Generate an ID for log messages. */
++static int log_id;
++
++librpz_t *librpz;
++
++
++static void LIBRPZ_NORET
++rpz_assert(const char *s)
++{
++ fatal_exit("%s", s);
++ exit(1);
++}
++#define RPZ_ASSERT(c) ((c) ? (void)0 : rpz_assert(#c), (void)0)
++
++/*
++ * librpz client handle locking
++ */
++static void
++lock_destroy(void* mutex)
++{
++ lock_basic_destroy(mutex);
++ free(mutex);
++}
++
++static void
++lock(void* mutex)
++{
++ lock_basic_lock(mutex);
++}
++
++static void
++unlock(void* mutex)
++{
++ lock_basic_unlock(mutex);
++}
++
++
++static void
++log_fnc(librpz_log_level_t level, void* ATTR_UNUSED(ctx), const char* buf)
++{
++ char label_buf[sizeof("rpz ")+8];
++
++ /* Setting librpz_log_level overrides the unbound "verbose" level. */
++ if(level > LIBRPZ_LOG_TRACE1 &&
++ level <= librpz->log_level_val(LIBRPZ_LOG_INVALID))
++ level = LIBRPZ_LOG_TRACE1;
++
++ switch(level) {
++ case LIBRPZ_LOG_FATAL:
++ case LIBRPZ_LOG_ERROR: /* errors */
++ default:
++ log_err("rpz: %s", buf);
++ break;
++
++ case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */
++ verbose(VERB_OPS, "rpz: %s", buf);
++ break;
++
++ case LIBRPZ_LOG_TRACE2: /* smaller dnsrpzd zone transfers */
++ verbose(VERB_DETAIL, "rpz: %s", buf);
++ break;
++
++ case LIBRPZ_LOG_TRACE3: /* librpz hits */
++ verbose(VERB_QUERY, "rpz: %s", buf);
++ break;
++
++ case LIBRPZ_LOG_TRACE4: /* librpz lookups */
++ verbose(VERB_CLIENT, "rpz: %s", buf);
++ break;
++ }
++}
++
++
++/* Release the librpz version. */
++static void
++rpz_off(commreply_rpz_t* rpz, st_t st)
++{
++ if(!rpz)
++ return;
++ rpz->st = st;
++ librpz->rsp_detach(&rpz->rsp);
++}
++
++
++static void LIBRPZ_PF(2,3)
++log_fail(commreply_rpz_t* rpz, const char* p, ...)
++{
++ va_list args;
++
++ if(rpz->st == st_servfail)
++ return;
++
++ va_start(args, p);
++ librpz->vlog(LIBRPZ_LOG_ERROR, rpz, p, args);
++ va_end(args);
++ if(!rpz)
++ return;
++ rpz_off(rpz, st_servfail);
++}
++
++
++/* Announce a rewrite. */
++static void
++log_rewrite(uint8_t* qname, librpz_policy_t policy, const char* msg,
++ commreply_rpz_t* rpz)
++{
++ char policy_buf[POLICY2STR_SIZE];
++ char qname_nm[LDNS_MAX_DOMAINLEN+1];
++ librpz_domain_buf_t tdomain;
++ char tdomain_nm[LDNS_MAX_DOMAINLEN+1];
++ librpz_emsg_t emsg;
++
++ if(rpz->st == st_servfail || !rpz->result.log)
++ return;
++ if(librpz->log_level_val(LIBRPZ_LOG_INVALID) < LIBRPZ_LOG_TRACE1)
++ return;
++
++ dname_str(qname, qname_nm);
++
++ if(!librpz->rsp_domain(&emsg, &tdomain, rpz->rsp)) {
++ librpz->log(LIBRPZ_LOG_ERROR, rpz, "%s", emsg.c);
++ return;
++ }
++ dname_str(tdomain.d, tdomain_nm);
++
++ librpz->log(LIBRPZ_LOG_TRACE3, rpz, "%srewriting %s via %s %s to %s",
++ msg, qname_nm, tdomain_nm,
++ librpz->trig2str(rpz->result.trig),
++ librpz->policy2str(policy, policy_buf,
++ sizeof(policy_buf)));
++}
++
++
++/* Connect to and start dnsrpzd if necessary for the unbound daemon.
++ * Require "rpz-conf: path" to specify the rpz configuration file.
++ * The unbound server directory name is the default rpz working
++ * directory. If unbound uses chroot, then the dnsrpzd working
++ * directory must be in the chroot tree.
++ * The database and socket are closed and re-opened.
++ */
++void
++rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient,
++ const struct config_file* cfg)
++{
++ lock_basic_type* mutex;
++ librpz_emsg_t emsg;
++
++ if(!librpz) {
++ librpz = librpz_lib_open(&emsg, NULL, FASTRPZ_LIBRPZ_PATH);
++ if(!librpz)
++ fatal_exit("rpz: %s", emsg.c);
++ }
++
++ librpz->set_log(&log_fnc, NULL);
++
++ if(!cfg->rpz_cstr)
++ fatal_exit("rpz: rpz-zone: not set");
++
++ librpz->client_detach(pclient);
++ librpz->clist_detach(pclist);
++
++ mutex = malloc(sizeof(*mutex));
++ if(!mutex)
++ fatal_exit("rpz: no memory for lock");
++ lock_basic_init(mutex);
++
++ *pclist = librpz->clist_create(&emsg, &lock, &unlock, &lock_destroy,
++ mutex, NULL);
++ if(!pclist)
++ fatal_exit("rpz: %s", emsg.c);
++
++ *pclient = librpz->client_create(&emsg, *pclist, cfg->rpz_cstr, false);
++ if(!*pclient)
++ fatal_exit("rpz: %s", emsg.c);
++
++ if(!librpz->connect(&emsg, *pclient, true))
++ fatal_exit("rpz: %s", emsg.c);
++
++ verbose(VERB_OPS, "rpz: librpz version %s", librpz->version);
++}
++
++
++/* Stop using librpz on behalf of a worker thread. */
++void
++rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient)
++{
++ if(librpz) {
++ librpz->client_detach(pclient);
++ librpz->clist_detach(pclist);
++ }
++}
++
++
++/* Release the librpz resources held for a DNS client request. */
++void
++rpz_end(struct comm_reply* commreply)
++{
++ if(!commreply->rpz)
++ return;
++ rpz_off(commreply->rpz, commreply->rpz->st);
++ free(commreply->rpz);
++ commreply->rpz = NULL;
++}
++
++
++static bool
++push_st(commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ if(rpz->st == st_off || rpz->st == st_servfail) {
++ librpz->log(LIBRPZ_LOG_ERROR, rpz,
++ "state %d in push_st()", rpz->st);
++ return false;
++ }
++ if(!librpz->rsp_push(&emsg, rpz->rsp))
++ log_fail(rpz, "%s", emsg.c);
++ memmove(&rpz->saved_st[1], &rpz->saved_st[0],
++ sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
++ rpz->saved_st[0] = rpz->st;
++ return rpz->st != st_servfail;
++}
++
++
++static bool
++pop_st(commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ if(rpz->rsp && !librpz->rsp_pop(&emsg, &rpz->result, rpz->rsp))
++ log_fail(rpz, "%s", emsg.c);
++ if(rpz->st != st_servfail)
++ rpz->st = rpz->saved_st[0];
++ memmove(&rpz->saved_st[0], &rpz->saved_st[1],
++ sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
++ return rpz->st != st_servfail;
++}
++
++static bool
++pop_discard_st(commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ if(rpz->rsp && !librpz->rsp_pop_discard(&emsg, rpz->rsp))
++ log_fail(rpz, "%s", emsg.c);
++ memmove(&rpz->saved_st[0], &rpz->saved_st[1],
++ sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0]));
++ return rpz->st != st_servfail;
++}
++
++/* Check a rewrite attempt for errors and a disabled zone. */
++static bool /* true=repeat the check */
++ck_after(uint8_t* qname, bool recursed, librpz_trig_t trig,
++ commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ if(rpz->st == st_servfail)
++ return false;
++
++ if(!librpz->rsp_result(&emsg, &rpz->result, recursed, rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ return false;
++ }
++
++ if(rpz->result.policy == LIBRPZ_POLICY_DISABLED) {
++ /* Log the hit on the disabled zone, do not try the zone again,
++ * and restore the state from before the check to forget the hit
++ * before trying again. */
++ log_rewrite(qname, rpz->result.zpolicy, "disabled ", rpz);
++ if(!librpz->rsp_forget_zone(&emsg, rpz->result.cznum, rpz->rsp))
++ log_fail(rpz, "%s", emsg.c);
++ return pop_st(rpz);
++ }
++
++ /* Complain about and forget client-IP address hit that is not
++ * dispositive. Client-IP triggers have the highest priority
++ * within a policy zone, but can be overridden by any hit in a policy
++ * earlier in the client's (resolver's) list of zones, including
++ * policies that cannot be hit until after recursion. If we allowed
++ * client-IP triggers in secondary zones, then than two DNS requests
++ * that differ only in DNS client-IP addresses could properly
++ * have differing results. The Unbound iterator treats identical
++ * DNS requests the same regardless of DNS client-IP address.
++ * struct query_info would need to be modified to have an optional
++ * librpz_prefix_t containing the prefix of the client-IP address hit
++ * from librpz->rsp_clientip_prefix(). Adding to struct query_info
++ * would require finding and changing the many and obscure places
++ * including the Unbound tests to memset(0) the struct query_info
++ * that they create. */
++ if(trig == LIBRPZ_TRIG_CLIENT_IP) {
++ if(rpz->result.cznum != 0) {
++ log_rewrite(qname, rpz->result.policy,
++ "ignore secondary ", rpz);
++ if(!pop_st(rpz))
++ log_fail(rpz, "%s", emsg.c);
++ return (false);
++ }
++ }
++
++ /* Forget the state from before the check and keep the new state
++ * if we do not have a hit on a disabled policy zone. */
++ pop_discard_st(rpz);
++ return false;
++}
++
++
++/* Get the next RR from the policy record. */
++static bool
++next_rr(librpz_rr_t** rrp, const uint8_t* qname, size_t qname_len,
++ commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ if(!librpz->rsp_rr(&emsg, NULL, NULL, NULL, rrp, &rpz->result,
++ qname, qname_len, rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ *rrp = NULL;
++ return false;
++ }
++ return true;
++}
++
++
++static bool /* false=fatal error to be logged */
++add_rr(struct sldns_buffer* pkt, const uint8_t* owner, size_t owner_len,
++ librpz_rr_t* rr, commreply_rpz_t* rpz)
++{
++ size_t rdlength;
++
++ rdlength = ntohs(rr->rdlength);
++
++ if(!sldns_buffer_available(pkt, owner_len + 10 + rdlength)) {
++ log_fail(rpz, "comm_reply buffer exhausted");
++ free(rr);
++ return false;
++ }
++ sldns_buffer_write(pkt, owner, owner_len);
++ /* sizeof(librpz_rr_t)=12 instead of 10 */
++ sldns_buffer_write(pkt, rr, 10 + rdlength);
++ return true;
++}
++
++
++/* Convert a fake incoming DNS message to an Unbound struct dns_msg */
++static void
++pkt2dns_msg(struct dns_msg** dnsmsg, struct sldns_buffer* pkt,
++ commreply_rpz_t* rpz, struct regional* region)
++{
++ struct msg_parse* msgparse;
++
++ msgparse = regional_alloc(region, sizeof(*msgparse));
++ if(!msgparse) {
++ log_fail(rpz, "out of memory for msgparse");
++ *dnsmsg = NULL;
++ return;
++ }
++ memset(msgparse, 0, sizeof(*msgparse));
++ if(parse_packet(pkt, msgparse, region) != LDNS_RCODE_NOERROR) {
++ log_fail(rpz, "packet parse error");
++ *dnsmsg = NULL;
++ return;
++ }
++ *dnsmsg = dns_alloc_msg(pkt, msgparse, region);
++ if(!*dnsmsg) {
++ log_fail(rpz, "dns_alloc_msg() failed");
++ *dnsmsg = NULL;
++ return;
++ }
++ (*dnsmsg)->rep->security = sec_status_rpz_rewritten;
++}
++
++
++static bool /* false=SERVFAIL */
++ck_ip_rrset(const void* vdata, int family, librpz_trig_t trig,
++ uint8_t* qname, commreply_rpz_t* rpz)
++{
++ const struct packed_rrset_data* data;
++ uint rr_n;
++ size_t len;
++ librpz_emsg_t emsg;
++
++ data = vdata;
++
++ /* Loop to ignore disabled zones. */
++ do {
++ if(!push_st(rpz))
++ return false;
++ for(rr_n = 0; rr_n < data->count; ++rr_n) {
++ len = data->rr_len[rr_n];
++ /* Skip bogus including negative placeholding rdata. */
++ if((family == AF_INET &&
++ len != sizeof(struct in_addr)+2) ||
++ (family == AF_INET6 &&
++ len != sizeof(struct in6_addr)+2))
++ continue;
++ if(!librpz->ck_ip(&emsg, data->rr_data[rr_n]+2,
++ family, trig, rpz->hit_id, true,
++ rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ return false;
++ }
++ }
++ } while(ck_after(qname, true, trig, rpz));
++ return rpz->st != st_servfail;
++}
++
++
++static bool /* false=SERVFAIL */
++ck_dname(uint8_t* dname, size_t dname_size, librpz_trig_t trig,
++ uint8_t* qname, bool recursed, commreply_rpz_t* rpz)
++{
++ librpz_emsg_t emsg;
++
++ /* Refuse to check the root. */
++ if(dname_is_root(dname))
++ return rpz->st != st_servfail;
++
++ /* Loop to ignore disabled zones. */
++ do {
++ if(!push_st(rpz))
++ return false;
++ if(!librpz->ck_domain(&emsg, dname, dname_size, trig,
++ rpz->hit_id, recursed, rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ return false;
++ }
++ } while(ck_after(qname, recursed, trig, rpz));
++
++ return rpz->st != st_servfail;
++}
++
++
++/* Check the IPv4 or IPv6 addresses for one NS name. */
++static bool /* false=st_servfail */
++ck_1nsip(uint8_t* nsname, size_t nsname_size, int family, int qtype,
++ bool* have_ns, commreply_rpz_t* rpz, struct module_env* env)
++{
++ struct ub_packed_rrset_key* akey;
++
++ akey = rrset_cache_lookup(env->rrset_cache, nsname, nsname_size,
++ qtype, LDNS_RR_CLASS_IN, 0, 0, 0);
++ if(akey) {
++ *have_ns = true;
++
++ if(!ck_ip_rrset(akey->entry.data, family, LIBRPZ_TRIG_NSIP,
++ nsname, rpz)) {
++ lock_rw_unlock(&akey->entry.lock);
++ return false;
++ }
++ lock_rw_unlock(&akey->entry.lock);
++ }
++ return true;
++}
++
++
++static bool /* false=st_servfail */
++ck_qname(uint8_t* qname, size_t qname_len,
++ bool recursed, /* recursion done */
++ bool wait_ns, /* willing to iterate for NS data */
++ commreply_rpz_t* rpz, struct module_env* env)
++{
++ uint8_t* dname;
++ size_t dname_size;
++ int cur_lab;
++ struct ub_packed_rrset_key* nskey;
++ const struct packed_rrset_data* nsdata;
++ uint8_t* nsname;
++ size_t nsname_size;
++ uint rr_n;
++ bool have_ns, tried_ns;
++
++ if(!ck_dname(qname, qname_len, LIBRPZ_TRIG_QNAME, qname, false, rpz))
++ return false;
++
++ /* Do not waste time looking for NSDNAME and NSIP hits when there
++ * are no currently relevant triggers. */
++ if(!librpz->have_ns_trig(rpz->rsp))
++ return true;
++
++ have_ns = false;
++ tried_ns = false;
++ dname = qname;
++ dname_size = qname_len;
++ for(cur_lab = dname_count_labels(dname) - 2;
++ cur_lab > rpz->min_ns_dots;
++ --cur_lab) {
++ tried_ns = true;
++ dname_remove_label(&dname, &dname_size);
++ nskey = rrset_cache_lookup(env->rrset_cache, dname, dname_size,
++ LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN,
++ 0, 0, 0);
++ if(!nskey)
++ continue;
++
++ nsdata = (const struct packed_rrset_data*)nskey->entry.data;
++ for(rr_n = 0;
++ rr_n < nsdata->count && rpz->st == st_unknown;
++ ++rr_n) {
++ nsname = nsdata->rr_data[rr_n]+2;
++ nsname_size = nsdata->rr_len[rr_n];
++ if(nsname_size <= 2)
++ continue;
++ nsname_size -= 2;
++ if(!ck_dname(nsname, nsname_size, LIBRPZ_TRIG_NSDNAME,
++ qname, recursed, rpz))
++ return false;
++ if(!ck_1nsip(nsname, nsname_size, AF_INET,
++ LDNS_RR_TYPE_A, &have_ns, rpz, env))
++ return false;
++ if(!ck_1nsip(nsname, nsname_size, AF_INET6,
++ LDNS_RR_TYPE_AAAA, &have_ns, rpz, env))
++ return false;
++ }
++ lock_rw_unlock(&nskey->entry.lock);
++ }
++
++ /* If we failed to find NS records, then stop building the response
++ * before a CNAME with this owner name. */
++ if(!have_ns && tried_ns && (!recursed || wait_ns)) {
++ rpz->cname_hit.size = qname_len;
++ RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d));
++ memcpy(rpz->cname_hit.d, qname, qname_len);
++ rpz->result.hit_id = rpz->hit_id;
++ rpz->st = st_ck_ns;
++ }
++ return true;
++}
++
++
++/*
++ * Are we ready to rewrite the response?
++ */
++static bool /* true=send rewritten response */
++ck_result(uint8_t* qname, bool recursed,
++ commreply_rpz_t* rpz, const struct comm_point* commpoint)
++{
++ librpz_emsg_t emsg;
++
++ switch(rpz->st) {
++ case st_off:
++ case st_servfail:
++ case st_rewritten:
++ return false;
++ case st_unknown:
++ break;
++ case st_iterate:
++ return false;
++ case st_ck_ns:
++ /* An NSDNAME or NSIP check failed for lack of cached data. */
++ return false;
++#pragma clang diagnostic push
++#pragma clang diagnostic ignored "-Wunreachable-code"
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
++ rpz->st);
++#pragma clang diagnostic pop
++ }
++
++ /* Wait for a trigger. */
++ if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED) {
++ if(recursed &&
++ rpz->result.zpolicy != LIBRPZ_POLICY_UNDEFINED &&
++ !librpz->rsp_result(&emsg, &rpz->result, true, rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ return false;
++ }
++ if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED)
++ return false;
++ }
++
++ if(rpz->result.policy == LIBRPZ_POLICY_PASSTHRU) {
++ log_rewrite(qname, rpz->result.policy, "", rpz);
++ rpz_off(rpz, st_off);
++ return false;
++ }
++
++ /* The TCP-only policy answers UDP requests with truncated responses. */
++ if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY &&
++ commpoint->type == comm_tcp) {
++ rpz_off(rpz, st_off);
++ return false;
++ }
++
++ return true;
++}
++
++
++/*
++ * Convert an RPZ hit to a struct dns_msg
++ */
++static void
++get_result_msg(struct dns_msg** dnsmsg, struct query_info* qinfo,
++ uint16_t id, uint16_t flags, bool recursed, commreply_rpz_t* rpz,
++ struct comm_point* commpoint, struct regional* region)
++{
++ librpz_rr_t* rr;
++ librpz_domain_buf_t origin;
++ struct sldns_buffer* pkt;
++ uint16_t num_rrs;
++ librpz_emsg_t emsg;
++
++ *dnsmsg = NULL;
++ if(!ck_result(qinfo->qname, recursed, rpz, commpoint))
++ return;
++
++ rpz->st = st_rewritten;
++
++ if(rpz->result.policy == LIBRPZ_POLICY_DROP) {
++ log_rewrite(qinfo->qname, rpz->result.policy, "", rpz);
++ /* Make a fake cached message to carry
++ * sec_status_rpz_drop and be dropped. */
++ error_encode(commpoint->buffer, LDNS_RCODE_NOERROR,
++ qinfo, id, flags, NULL);
++ pkt2dns_msg(dnsmsg, commpoint->buffer, rpz, region);
++ (*dnsmsg)->rep->security = sec_status_rpz_drop;
++ return;
++ }
++
++ /* Create a DNS message of the RPZ data.
++ * In many cases that message could be sent directly to the DNS client,
++ * but sometimes iteration must be used to resolve a CNAME.
++ * This need not be fast, because rewriting responses should be rare.
++ * Therefore, use the simpler but slower tactic of generating a
++ * parsed version of the message. */
++
++ flags &= ~BIT_AA;
++ flags |= BIT_QR | BIT_RA;
++ rr = NULL;
++
++ /* The TCP-only policy answers UDP requests with truncated responses. */
++ if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY) {
++ flags |= BIT_TC;
++
++ } else if(rpz->result.policy == LIBRPZ_POLICY_NXDOMAIN) {
++ flags |= LDNS_RCODE_NXDOMAIN;
++
++ } else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) {
++ if(!rpz->iterating &&
++ qinfo->qtype != LDNS_RR_TYPE_CNAME) {
++ /* The new DNS message would be a CNAME and
++ * the external request was not for a CNAME.
++ * The worker must punt to the iterator so that
++ * the iterator can resolve the CNAME. */
++ rpz->st = st_iterate;
++ return;
++ }
++ next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
++
++ } else if(rpz->result.policy == LIBRPZ_POLICY_RECORD ||
++ rpz->result.policy == LIBRPZ_POLICY_NODATA) {
++ next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
++ /* Punt to the iterator if the new DNS message would
++ * be a CNAME that must be resolved. */
++ if(!rpz->iterating &&
++ qinfo->qtype != LDNS_RR_TYPE_CNAME &&
++ rr && rr->type == ntohs(LDNS_RR_TYPE_CNAME)) {
++ free(rr);
++ rpz->st = st_iterate;
++ return;
++ }
++ }
++ log_rewrite(qinfo->qname, rpz->result.policy, "", rpz);
++
++ /* Make a buffer containing a DNS message with the RPZ data. */
++ pkt = commpoint->buffer;
++ sldns_buffer_clear(pkt);
++ if(sldns_buffer_remaining(pkt) < LDNS_HEADER_SIZE) {
++ log_fail(rpz, "comm_reply buffer too small for header");
++ if(rr)
++ free(rr);
++ return;
++ }
++
++ /* Install ID, flags, QDCOUNT=1, ANCOUNT=# of RPZ RRs, NSCOUNT=0,
++ * and ARCOUNT=1 for the RPZ SOA. */
++ sldns_buffer_write_u16(pkt, id);
++ sldns_buffer_write_u16(pkt, flags);
++ sldns_buffer_write_u16(pkt, 1); /* QDCOUNT */
++ sldns_buffer_write_u16(pkt, 0); /* ANCOUNT will be set later */
++ sldns_buffer_write_u16(pkt, 0); /* NSCOUNT */
++ sldns_buffer_write_u16(pkt, 1); /* ARCOUNT */
++
++ /* Install the question with the LDNS_RR_CLASS_RPZ bit to
++ * to distinguish this supposed cache entry from the real deal. */
++ sldns_buffer_write(pkt, qinfo->qname, qinfo->qname_len);
++ sldns_buffer_write_u16(pkt, qinfo->qtype);
++ sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_IN);
++
++ /* Install the RPZ RRs in the answer section */
++ num_rrs = 0;
++ while(rr) {
++ /* Include only the requested RRs. */
++ if(qinfo->qtype == LDNS_RR_TYPE_ANY ||
++ rr->type == htons(qinfo->qtype) ||
++ rr->type == htons(LDNS_RR_TYPE_CNAME)) {
++ if(!add_rr(pkt, qinfo->qname, qinfo->qname_len,
++ rr, rpz))
++ return;
++
++ ++num_rrs;
++ }
++ free(rr);
++
++ next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
++ }
++ /* Finish ANCOUNT. */
++ if(num_rrs != 0)
++ sldns_buffer_write_u16_at(pkt, 6, num_rrs);
++
++ /* All rewritten responses have an identifying SOA record in the
++ * additional section. */
++ if(!librpz->rsp_soa(&emsg, NULL, &rr, &origin,
++ &rpz->result, rpz->rsp)) {
++ log_fail(rpz, "no soa");
++ return;
++ }
++ if(!add_rr(pkt, origin.d, origin.size, rr, rpz))
++ return;
++ free(rr);
++
++ /* Create a dns_msg representation of the fake incoming message. */
++ sldns_buffer_flip(pkt);
++ pkt2dns_msg(dnsmsg, pkt, rpz, region);
++}
++
++
++/* Check the RRs in the ANSWER section of a reply_info. */
++static void
++ck_reply(struct reply_info* reply, uint8_t* qname, bool wait_ns,
++ commreply_rpz_t* rpz, struct module_env* env)
++{
++ struct ub_packed_rrset_key* rrset;
++ enum sldns_enum_rr_type type;
++ uint rrset_n;
++
++ /* Check the RRs in the ANSWER section. */
++ rpz->cname_hit.size = 0;
++ rpz->cname_hit_2nd = false;
++ for(rrset_n = 0; rrset_n < reply->an_numrrsets; ++rrset_n) {
++ /* Check all of the RRs before deciding. */
++ if(rpz->st != st_unknown)
++ return;
++
++ rrset = reply->rrsets[rrset_n];
++ if(ntohs(rrset->rk.rrset_class) != LDNS_RR_CLASS_IN)
++ continue;
++ type = ntohs(rrset->rk.type);
++
++ if(type == LDNS_RR_TYPE_A) {
++ if(!ck_ip_rrset(rrset->entry.data, AF_INET,
++ LIBRPZ_TRIG_IP, qname, rpz))
++ break;
++
++ } else if(type == LDNS_RR_TYPE_AAAA) {
++ if(!ck_ip_rrset(rrset->entry.data, AF_INET6,
++ LIBRPZ_TRIG_IP, qname, rpz))
++ break;
++
++ } else if(type == LDNS_RR_TYPE_CNAME) {
++ /* Check CNAME owners unless we already have a hit. */
++ ++rpz->hit_id;
++ if(!ck_qname(rrset->rk.dname, rrset->rk.dname_len,
++ true, wait_ns, rpz, env))
++ break;
++
++ /* Do not worry about the CNAME if it did not hit,
++ * but note the miss so that it can be prepended
++ * if we do hit. */
++ if(rpz->result.hit_id != rpz->hit_id) {
++ rpz->cname_hit_2nd = true;
++ continue;
++ }
++
++ /* Stop after hitting a CNAME.
++ * The iterator must be used to include CNAMEs before
++ * the CNAME that hit in the rewritten response. */
++ rpz->cname_hit.size = rrset->rk.dname_len;
++ RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d));
++ memcpy(rpz->cname_hit.d, rrset->rk.dname,
++ rpz->cname_hit.size);
++ break;
++ }
++ }
++}
++
++
++static void
++worker_servfail(struct worker* worker, struct query_info* qinfo,
++ uint16_t id, uint16_t flags, struct comm_reply* commreply)
++{
++ error_encode(commreply->c->buffer, LDNS_RCODE_SERVFAIL,
++ qinfo, id, flags, NULL);
++ regional_free_all(worker->scratchpad);
++ comm_point_send_reply(commreply);
++}
++
++
++/* Send an RPZ answer before the iterator has started.
++ * @return: 1=continue normal unbound processing
++ * 0=punt to the iterator
++ * -1=rewritten response already sent or dropped. */
++static int
++worker_send(struct dns_msg* dnsmsg, struct worker* worker,
++ struct query_info* qinfo, uint16_t id, uint16_t flags,
++ struct edns_data* edns, struct comm_reply* commreply)
++{
++ switch (commreply->rpz->st) {
++ case st_off:
++ return 1;
++ case st_servfail:
++ worker_servfail(worker, qinfo, id, flags, commreply);
++ return -1;
++ case st_unknown:
++ return 1;
++ case st_iterate:
++ case st_ck_ns:
++ return 0; /* punt to the iterator */
++ case st_rewritten:
++ break;
++ default:
++ fatal_exit("impossible RPZ state %d in worker_send()",
++ commreply->rpz->st);
++ }
++
++ if(dnsmsg->rep->security == sec_status_rpz_drop) {
++ regional_free_all(worker->scratchpad);
++ comm_point_drop_reply(commreply);
++ return -1;
++ }
++
++ edns->edns_version = EDNS_ADVERTISED_VERSION;
++ edns->udp_size = EDNS_ADVERTISED_SIZE;
++ edns->ext_rcode = 0;
++ edns->bits = 0; /* rewritten response cannot verify. */
++ if(!reply_info_answer_encode(qinfo, dnsmsg->rep,
++ id, flags | BIT_QR,
++ commreply->c->buffer, 0, 1,
++ worker->scratchpad,
++ edns->udp_size, edns, 0, 0)) {
++ worker_servfail(worker, qinfo, id, flags, commreply);
++ } else {
++ regional_free_all(worker->scratchpad);
++ comm_point_send_reply(commreply);
++ }
++ return -1;
++}
++
++
++/* Set commreply to an RPZ context if the response might be rewritten.
++ * Try to answer now with a hit allowed before recursion (iteration). */
++bool /* true=response sent or dropped */
++rpz_start(struct worker* worker, struct query_info* qinfo,
++ struct comm_reply* commreply, struct edns_data* edns)
++{
++ commreply_rpz_t* rpz;
++ uint16_t id, flags;
++ struct dns_msg* dnsmsg;
++ int family;
++ const void* addr;
++ librpz_emsg_t emsg;
++
++ /* Quit if rpz not configured. */
++ if(!worker->daemon->rpz_client)
++ return false;
++
++ /* Rewrite only the Internet class */
++ if(qinfo->qclass != LDNS_RR_CLASS_IN)
++ return false;
++
++ rpz = commreply->rpz;
++ RPZ_ASSERT(!rpz);
++
++ dnsmsg = NULL;
++ id = htons(sldns_buffer_read_u16_at(commreply->c->buffer, 0));
++ flags = sldns_buffer_read_u16_at(commreply->c->buffer, 2);
++
++ rpz = malloc(sizeof(*rpz));
++ if(!rpz) {
++ librpz->log(LIBRPZ_LOG_ERROR, NULL, "no memory for rpz");
++ return 0 > worker_send(dnsmsg, worker, qinfo,
++ id, flags, edns, commreply);
++ }
++ memset(rpz, 0, sizeof(*rpz));
++ rpz->st = st_unknown;
++ commreply->rpz = rpz;
++
++ /* Make a new ID for log messages */
++ rpz->log_id = __sync_add_and_fetch(&log_id, 1);
++
++ /* Get access to the librpz data. */
++ if(!librpz->rsp_create(&emsg, &rpz->rsp, &rpz->min_ns_dots,
++ worker->daemon->rpz_client,
++ (flags & BIT_RD) != 0,
++ (edns->bits & EDNS_DO) != 0)) {
++ log_fail(rpz, "%s", emsg.c);
++ return false;
++ }
++ /* Quit if benign reasons prevent rewriting. */
++ if(!rpz->rsp) {
++ rpz->st = st_off;
++ librpz->log(LIBRPZ_LOG_TRACE1, rpz, "%s", emsg.c);
++ return false;
++ }
++
++ /* Check the client IP address.
++ * Do not use commreply->srctype because it is often 0. */
++ family = ((struct sockaddr*)&commreply->addr)->sa_family;
++ switch(family) {
++ case AF_INET:
++ addr = &((struct sockaddr_in*)&commreply->addr)->sin_addr;
++ break;
++ case AF_INET6:
++ addr = &((struct sockaddr_in6*)&commreply->addr)->sin6_addr;
++ break;
++ default:
++ /* Maybe the client is on a UNIX domain socket. */
++ librpz->log(LIBRPZ_LOG_TRACE2, rpz,
++ "unknown client address family %d", family);
++ addr = NULL;
++ break;
++ }
++ /* Loop to ignore disabled zones. */
++ while(addr) {
++ if(!push_st(rpz))
++ break;
++ if(!librpz->ck_ip(&emsg, addr, family, LIBRPZ_TRIG_CLIENT_IP,
++ rpz->hit_id, true, rpz->rsp)) {
++ log_fail(rpz, "%s", emsg.c);
++ break;
++ }
++ if(!ck_after(qinfo->qname, false, LIBRPZ_TRIG_CLIENT_IP, rpz))
++ break;
++ }
++ if(rpz->st == st_servfail)
++ return 0 > worker_send(dnsmsg, worker, qinfo,
++ id, flags, edns, commreply);
++
++ /* Check the QNAME and possibly replace a client-IP hit. */
++ ck_qname(qinfo->qname, qinfo->qname_len, false, true,
++ rpz, &worker->env);
++
++ get_result_msg(&dnsmsg, qinfo, id, flags, false,
++ rpz, commreply->c, worker->scratchpad);
++ return 0 > worker_send(dnsmsg, worker, qinfo,
++ id, flags, edns, commreply);
++}
++
++
++/* Check a cached reply before iteration.
++ * @return: 1=use cache entry
++ * 0=deny a cached entry exists in order to punt to the iterator
++ * -1=rewritten response already sent or dropped */
++int
++rpz_worker_cache(struct worker* worker, struct reply_info* reply,
++ struct query_info* qinfo, uint16_t id, uint16_t flags,
++ struct edns_data* edns, struct comm_reply* commreply)
++{
++ commreply_rpz_t* rpz;
++ struct dns_msg* dnsmsg;
++ st_t new_st;
++ librpz_rr_t* rr;
++
++ dnsmsg = NULL;
++
++ rpz = commreply->rpz;
++ switch(rpz->st) {
++ case st_off:
++ return 1; /* Send the cache entry. */
++ case st_servfail:
++ return worker_send(dnsmsg, worker, qinfo, id, flags,
++ edns, commreply);
++ case st_unknown:
++ break;
++ case st_iterate:
++ case st_ck_ns:
++ return 0; /* Punt to the iterator. */
++ case st_rewritten:
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
++ rpz->st);
++ }
++
++ /* Check the RRs in the ANSWER section. */
++ if(!push_st(rpz))
++ return worker_send(dnsmsg, worker, qinfo, id, flags, edns,
++ commreply);
++
++ ck_reply(reply, qinfo->qname, true, rpz, &worker->env);
++ if(!ck_result(qinfo->qname, true, rpz, commreply->c))
++ return worker_send(dnsmsg, worker, qinfo, id, flags, edns,
++ commreply);
++
++ if(rpz->cname_hit.size != 0) {
++ /* Punt to the iterator if leading CNAMEs must be
++ * included in the rewritten response. */
++ rpz->cname_hit.size = 0;
++ new_st = st_iterate;
++
++ } else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) {
++ /* Punt if the rewritten response is to a CNAME. */
++ new_st = st_iterate;
++
++ } else {
++ if(rpz->result.policy == LIBRPZ_POLICY_RECORD) {
++ next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz);
++ if(rr) {
++ /* Punt we are rewriting to a CNAME. */
++ if(rr->type == ntohs(LDNS_RR_TYPE_CNAME)) {
++ free(rr);
++ rpz->st = st_iterate;
++ } else {
++ free(rr);
++ }
++ }
++ }
++ get_result_msg(&dnsmsg, qinfo, id, flags, true,
++ rpz, commreply->c, worker->scratchpad);
++ new_st = rpz->st;
++ }
++
++ switch(new_st) {
++ case st_off:
++ case st_servfail:
++ break;
++ case st_unknown:
++ pop_discard_st(rpz);
++ break;
++ case st_iterate:
++ case st_ck_ns:
++ if(pop_st(rpz))
++ rpz->st = new_st;
++ break;
++ case st_rewritten:
++ pop_discard_st(rpz);
++ break;
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_worker_cache()",
++ rpz->st);
++ }
++
++ return worker_send(dnsmsg, worker, qinfo, id, flags, edns, commreply);
++}
++
++
++/* Check a cache hit or miss for the iterator.
++ * A cache miss can already have a QNAME hit that was ignored before checking
++ * the iterator because of "QNAME-WAIT-RECURSE yes".
++ * Cache hits are treated like responses from authorities. */
++bool /* false=SERVFAIL */
++rpz_iter_cache(struct dns_msg** msg, enum response_type* type,
++ struct module_qstate* qstate, struct iter_qstate* iq)
++{
++ struct comm_reply* commreply;
++ commreply_rpz_t* rpz;
++ struct dns_msg* dnsmsg;
++
++ commreply = &qstate->mesh_info->reply_list->query_reply;
++ rpz = commreply->rpz;
++
++ rpz->iterating = true;
++
++ switch(rpz->st) {
++ case st_off:
++ iq->rpz_rewritten = 1; /* RPZ has nothing to say. */
++ return true;
++ case st_servfail:
++ return false;
++ case st_unknown:
++ break;
++ case st_iterate:
++ case st_ck_ns:
++ rpz->st = st_unknown;
++ if(!ck_qname(iq->qchase.qname, iq->qchase.qname_len,
++ *msg != NULL, true, rpz, qstate->env))
++ return false;
++ /* If we must recurse regardless and if NSIP/NSDNAME
++ * checking failed, then delay in the hope that
++ * recursion will also get NS data. */
++ if(rpz->st == st_ck_ns)
++ return true;
++ break;
++ case st_rewritten:
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_iter_cache()",
++ rpz->st);
++ }
++
++ push_st(rpz);
++
++ /* Check the cache hit. */
++ if(*msg)
++ ck_reply((*msg)->rep, iq->qchase.qname, true, rpz, qstate->env);
++
++ /* The DNS ID does not matter, because the generated dns_msg
++ * is nominally from an authority and not to the DNS client. */
++ get_result_msg(&dnsmsg, &iq->qchase, 1, qstate->query_flags, true,
++ rpz, commreply->c, qstate->region);
++
++ switch(rpz->st) {
++ case st_off:
++ iq->rpz_rewritten = 1; /* RPZ has nothing to say. */
++ return true;
++ case st_servfail:
++ return false;
++ case st_unknown:
++ /* RPZ has nothing to say yet. Maybe there will be a hit
++ * later in the CNAME chain. */
++ return pop_discard_st(rpz);
++ case st_ck_ns:
++ /* Try to get NS data for a CNAME found by ck_reply() */
++ *type = RESPONSE_TYPE_CNAME;
++ return pop_discard_st(rpz);
++ case st_iterate:
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_iter_cache()",
++ rpz->st);
++ case st_rewritten:
++ break;
++ }
++
++ if(*msg && rpz->cname_hit.size != 0 && rpz->cname_hit_2nd) {
++ /* We hit a CNAME owner in the cached msg after not hitting one
++ * or more CNAME owners. We need to add those leading CNAMEs
++ * to the prepend list. Tell the iterator to treat the cached
++ * message as a RESPONSE_TYPE_CNAME even if it contains answers.
++ * handle_cname_response() will stop prepending CNAMEs before
++ * the triggering CNAME. handle_cname_response() will cause
++ * a restart to resolve the target of the preceding CNAME,
++ * which is the same as the hit CNAME owner. */
++ rpz->st = st_unknown;
++ *type = RESPONSE_TYPE_CNAME;
++ return pop_discard_st(rpz);
++ }
++
++ *msg = dnsmsg;
++ iq->rpz_security = dnsmsg->rep->security;
++
++ if(dnsmsg && dnsmsg->rep->an_numrrsets != 0 &&
++ dnsmsg->rep->rrsets[0]->rk.type == htons(LDNS_RR_TYPE_CNAME)) {
++ /* The cached msg triggered a rule that rewrites to a
++ * CNAME that must be resolved.
++ * We have a replacement dns_msg with that CNAME and also
++ * an SOA RR in the ADDITIONAL section that the iterator
++ * will lose as it adds the CNAME to the prepend list.
++ * Save the SOA RR in iq->rpz_soa. */
++ iq->rpz_soa = dnsmsg->rep->rrsets[1];
++ iq->rpz_rewritten = 1;
++ *type = RESPONSE_TYPE_CNAME;
++ return true;
++ }
++
++ /* Otherwise we have rewritten to zero or more non-CNAME RRs.
++ * (DNAMEs are not supported.)
++ * Tell the iterator to send the rewritten message. */
++ *type = RESPONSE_TYPE_ANSWER;
++ iq->rpz_rewritten = 1;
++ return true;
++}
++
++
++/* Check a RESPONSE_TYPE_ANSWER response from an authority in the iterator. */
++rpz_iter_resp_t
++rpz_iter_resp(struct module_qstate* qstate, struct iter_qstate* iq,
++ struct dns_msg** resp, bool* is_cname)
++{
++ struct comm_reply* commreply;
++ commreply_rpz_t* rpz;
++ struct reply_info* rep;
++
++ *is_cname = false;
++
++ commreply = &qstate->mesh_info->reply_list->query_reply;
++ rpz = commreply->rpz;
++ switch(rpz->st) {
++ case st_off:
++ case st_servfail:
++ case st_iterate:
++ case st_rewritten:
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_iter_resp()",
++ rpz->st);
++ case st_ck_ns:
++ case st_unknown:
++ break;
++ }
++
++ /* We know !iq->rpz_rewritten and so the response was after a simple
++ * cache miss when the original QNAME did not trigger a response
++ * or after a CNAME whose owner name did hit but was then forgotten
++ * with pop_st().
++ * In either case, it is necessary to check the QNAME here.
++ * Checking the QNAME will not lose a better hit. */
++ rpz->st = st_unknown;
++ ck_qname(iq->qchase.qname, iq->qchase.qname_len, true, false,
++ rpz, qstate->env);
++
++ /* Check the RRs in the ANSWER section. */
++ if(!push_st(rpz))
++ return rpz_iter_resp_fail;
++ ck_reply(iq->response->rep, iq->qchase.qname, false, rpz, qstate->env);
++ get_result_msg(resp, &qstate->qinfo, 1, qstate->query_flags, true,
++ rpz, commreply->c, qstate->region);
++ switch(rpz->st) {
++ case st_off:
++ iq->rpz_rewritten = 1; /* Do not come back. */
++ return rpz_iter_resp_done;
++ case st_servfail: /* Send SERVFAIL */
++ return rpz_iter_resp_fail;
++ case st_unknown:
++ case st_ck_ns:
++ return rpz_iter_resp_done; /* continue without change */
++ case st_iterate:
++ default:
++ fatal_exit("impossible RPZ state %d in rpz_iter_resp()",
++ rpz->st);
++ case st_rewritten:
++ /* Tell the iterator to use handle_cname_response() to
++ * prepend any preceding CNAMEs.
++ * We have a replacement dns_msg that also has an SOA RR in the
++ * ADDITIONAL section that the iterator will lose if it is a
++ * CNAME. Save that SOA in that case. */
++ rep = (*resp)->rep;
++ if(rep->an_numrrsets != 0 &&
++ rep->rrsets[0]->rk.type == ntohs(LDNS_RR_TYPE_CNAME)) {
++ *is_cname = true;
++ iq->rpz_soa = rep->rrsets[1];
++ }
++ return rpz_iter_resp_rewrite;
++ }
++}
++
++
++/* Tell handle_cname_response() to stop adding to the answer prepend list
++ * after adding CNAME with a target that hits a QNAME trigger.
++ * Do not change any RPZ state, but expect the call of handle_cname_response()
++ * to try to resolve the CNAME and hit the same QNAME trigger and rewrite
++ * the response. */
++rpz_cname_t
++rpz_cname(struct module_qstate* qstate,
++ uint8_t* oname, size_t oname_size)
++{
++ struct mesh_reply* reply_list;
++ struct comm_reply* commreply;
++ commreply_rpz_t* rpz;
++ rpz_cname_t ret;
++
++ /* Quit if RPZ is off */
++ reply_list = qstate->mesh_info->reply_list;
++ if(!reply_list)
++ return rpz_cname_prepend;
++ commreply = &reply_list->query_reply;
++ rpz = commreply->rpz;
++
++ if(!rpz || rpz->st == st_off)
++ return rpz_cname_prepend;
++
++ /* Stop on a 2nd or later CNAME for rpz_iter_resp(). */
++ if(rpz->cname_hit.size != 0) {
++ if(!query_dname_compare(rpz->cname_hit.d, oname))
++ return rpz_cname_stop;
++ return rpz_cname_prepend;
++ }
++
++ if(rpz->st != st_unknown)
++ fatal_exit("impossible RPZ state %d in rpz_cname()", rpz->st);
++
++ ret = rpz_cname_prepend;
++ if(!push_st(rpz))
++ return rpz_cname_fail;
++ /* Stop before prepending a CNAME that would preempt a
++ * rewritten response or before a possible NSDNAME or NSIP trigger. */
++ ++rpz->hit_id;
++ ck_qname(oname, oname_size, true, true, rpz, qstate->env);
++ if(rpz->st != st_unknown)
++ ret = rpz_cname_stop;
++ if(!pop_st(rpz))
++ return rpz_cname_fail;
++ return ret;
++}
++
++#endif /* ENABLE_FASTRPZ */
+===================================================================
+RCS file: ./fastrpz/RCS/rpz.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./fastrpz/rpz.h
+--- ./fastrpz/rpz.h
++++ ./fastrpz/rpz.h
+@@ -0,0 +1,138 @@
++/*
++ * fastrpz/rpz.h - interface to the fastrpz response policy zone library
++ *
++ * Copyright (c) 2016 Farsight Security, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#ifndef UNBOUND_FASTRPZ_RPZ_H
++#define UNBOUND_FASTRPZ_RPZ_H
++
++#ifndef PACKAGE_VERSION
++/* Ensure that config.h has been included to correctly set ENABLE_FASTRPZ */
++#include "config.h"
++#endif
++
++#ifdef ENABLE_FASTRPZ
++
++#include "librpz.h"
++
++#include "daemon/daemon.h"
++#include "util/config_file.h"
++
++struct comm_point; /* forward references */
++struct comm_reply;
++struct dns_msg;
++struct edns_data;
++struct iter_qstate;
++struct query_info;
++struct reply_info;
++enum response_type; /* iterator/iter_utils.h */
++
++
++struct commreply_rpz;
++
++/**
++ * Connect to the librpz database.
++ * @param pclist: future pointer to opaque librpz client data
++ * @param pclient: future pointer to opaque librpz client data
++ * @param cfg: parsed unbound configuration
++ */
++void rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient,
++ const struct config_file* cfg);
++
++/**
++ * Disconnect from the librpz database
++ * @param client: opaque librpz client data
++ */
++void rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient);
++
++/**
++ * Start working on a DNS request and check for client IP address triggers.
++ * @param worker: the DNS request context
++ * @param qinfo: the DNS question
++ * @param[in,out] commreply: the answer
++ * @param c: where to send the response
++ * @param[in,out] edns for the DO flag
++ * @return true if response already sent or dropped
++ */
++bool rpz_start(struct worker* worker, struct query_info* qinfo,
++ struct comm_reply* commreply, struct edns_data* edns);
++
++/**
++ * Release resources held for a DNS request
++ * @param rspp: pointer to pointer to rpz client context.
++ */
++void rpz_end(struct comm_reply* comm_rep);
++
++/**
++ * Check a cached reply for RPZ hits before iteration
++ * @param worker: the DNS request context
++ * @param casheresp: cache reply
++ * @param qinfo: the DNS question
++ * @param id from the DNS request
++ * @param flags from the DNS request
++ * @param[in,out] edns for the DO flag
++ * @param[in,out] commreply: RPZ state
++ * @return 1=use cache entry, -1=rewritten response already sent or dropped,
++ * 0=deny a cached entry exists
++ */
++int rpz_worker_cache(struct worker* worker, struct reply_info* cacheresp,
++ struct query_info* qinfo, uint16_t id, uint16_t flags,
++ struct edns_data* edns, struct comm_reply* commreply);
++
++/**
++ * Check for an existing RPZ CNAME rewrite with "QNAME-WAIT-RECURSE no"
++ * that needs to be resolved before resolving the external request.
++ * @param[out] msg: rewritten CNAME response.
++ * @param qstate: query state.
++ * @param iq: iterator query state.
++ * @return false=send SERVFAIL
++ */
++bool rpz_iter_cache(struct dns_msg** msg, enum response_type* type,
++ struct module_qstate* qstate, struct iter_qstate* iq);
++
++/**
++ * Check a response from an authority in the iterator.
++ * @param[out] type: of the final response
++ * @param qstate: query state.
++ * @param iq: iterator query state.
++ * @param is_cname: true if the rewritten response is a CNAME
++ * @return one of rpz_resp_t
++ */
++typedef enum {
++ rpz_iter_resp_fail, /* Send SERVFAIL. */
++ rpz_iter_resp_rewrite, /* We rewrote the response. */
++ rpz_iter_resp_done, /* Restart to refetch glue. */
++} rpz_iter_resp_t;
++rpz_iter_resp_t rpz_iter_resp(struct module_qstate* qstate,
++ struct iter_qstate* iq, struct dns_msg** resp,
++ bool* is_cname);
++
++/**
++ * Check a CNAME RR
++ * @param qstate: query state.
++ * @param oname: cname owner name
++ * @param oname_size: length of oname
++ * @return: one of rpz_cname_t
++ */
++typedef enum {
++ rpz_cname_fail, /* send SERVFAIL */
++ rpz_cname_prepend, /* prepend CNAME as usual */
++ rpz_cname_stop, /* stop before prepending this CNAME */
++} rpz_cname_t;
++rpz_cname_t rpz_cname(struct module_qstate* qstate,
++ uint8_t* oname, size_t oname_size);
++
++#endif /* ENABLE_FASTRPZ */
++#endif /* UNBOUND_FASTRPZ_RPZ_H */
+===================================================================
+RCS file: ./fastrpz/RCS/rpz.m4,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./fastrpz/rpz.m4
+--- ./fastrpz/rpz.m4
++++ ./fastrpz/rpz.m4
+@@ -0,0 +1,64 @@
++# fastrpz/rpz.m4
++
++# ck_FASTRPZ
++# --------------------------------------------------------------------------
++# check for Fastrpz
++# --enable-fastrpz enable Fastrpz response policy zones
++# --enable-fastrpz-dl Fastrpz delayed link [default=have dlopen]
++# --with-fastrpz-dir directory containing librpz.so
++#
++# Fastrpz can be compiled into Unbound everywhere with a reasonably
++# modern C compiler. It is enabled on systems with dlopen() and librpz.so.
++
++AC_DEFUN([ck_FASTRPZ],
++[
++ fastrpz_avail=yes
++ AC_MSG_CHECKING([for librpz __attribute__s])
++ AC_TRY_COMPILE(,[
++ extern void f(char *p __attribute__((unused)), ...)
++ __attribute__((format(printf,1,2))) __attribute__((__noreturn__));],
++ librpz_have_attr=yes
++ AC_DEFINE([LIBRPZ_HAVE_ATTR], 1, [have __attribute__s used in librpz.h])
++ AC_MSG_RESULT([yes]),
++ librpz_have_attr=no
++ AC_MSG_RESULT([no]))
++
++ AC_SEARCH_LIBS(dlopen, dl)
++ librpz_dl=yes
++ AC_CHECK_FUNCS(dlopen dlclose dlsym,,librpz_dl=no)
++ AC_ARG_ENABLE([fastrpz-dl],
++ [ --enable-fastrpz-dl Fastrpz delayed link [[default=$librpz_dl]]],
++ [enable_librpz_dl="$enableval"],
++ [enable_librpz_dl="$librpz_dl"])
++ AC_ARG_WITH([fastrpz-dir],
++ [ --with-fastrpz-dir directory containing librpz.so],
++ [librpz_path="$withval/librpz.so"], [librpz_path="librpz.so"])
++ AC_DEFINE_UNQUOTED([FASTRPZ_LIBRPZ_PATH], ["$librpz_path"],
++ [fastrpz librpz.so])
++ if test "x$enable_librpz_dl" = "xyes"; then
++ fastrpz_lib_open=2
++ else
++ fastrpz_lib_open=1
++ # Add librpz.so to linked libraries if we are not using dlopen()
++ AC_SEARCH_LIBS([librpz_client_create], [rpz], [],
++ [fastrpz_lib_open=0
++ fastrpz_avail=no])
++ fi
++ AC_DEFINE_UNQUOTED([FASTRPZ_LIB_OPEN], [$fastrpz_lib_open],
++ [0=no fastrpz 1=static link 2=dlopen()])
++
++ AC_ARG_ENABLE([fastrpz],
++ AS_HELP_STRING([--enable-fastrpz],[enable Fastrpz response policy zones]),
++ [enable_fastrpz=$enableval],[enable_fastrpz=$fastrpz_avail])
++ if test "x$enable_fastrpz" = xyes; then
++ AC_DEFINE([ENABLE_FASTRPZ], [1], [Enable fastrpz])
++ if test "x$fastrpz_lib_open" = "x0"; then
++ AC_MSG_ERROR([[dlopen and librpz.so needed for fastrpz]])
++ fi
++ # used in Makefile.in
++ AC_SUBST([FASTRPZ_SRC], [fastrpz/rpz.c])
++ AC_SUBST([FASTRPZ_OBJ], [rpz.lo])
++ elif test "x$fastrpz_avail" = "x0"; then
++ AC_MSG_WARN([[dlopen and librpz.so needed for fastrpz]])
++ fi
++])
+===================================================================
+RCS file: ./iterator/RCS/iterator.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./iterator/iterator.c
+--- ./iterator/iterator.c
++++ ./iterator/iterator.c
+@@ -67,6 +67,9 @@
+ #include "sldns/str2wire.h"
+ #include "sldns/parseutil.h"
+ #include "sldns/sbuffer.h"
++#ifdef ENABLE_FASTRPZ
++#include "fastrpz/rpz.h"
++#endif
+
+ int
+ iter_init(struct module_env* env, int id)
+@@ -487,6 +490,23 @@
+ if(ntohs(r->rk.type) == LDNS_RR_TYPE_CNAME &&
+ query_dname_compare(*mname, r->rk.dname) == 0 &&
+ !iter_find_rrset_in_prepend_answer(iq, r)) {
++#ifdef ENABLE_FASTRPZ
++ /* Stop adding CNAME rrsets to the prepend list
++ * before defining an RPZ hit. */
++ if(!iq->rpz_rewritten) {
++ switch (rpz_cname(qstate, *mname, *mname_len)) {
++ case rpz_cname_fail:
++ /* send SERVFAIL */
++ return 0;
++ case rpz_cname_prepend:
++ /* save the CNAME. */
++ break;
++ case rpz_cname_stop:
++ /* Pause before adding the CNAME. */
++ goto stop_short;
++ }
++ }
++#endif
+ /* Add this relevant CNAME rrset to the prepend list.*/
+ if(!iter_add_prepend_answer(qstate, iq, r))
+ return 0;
+@@ -495,6 +515,9 @@
+
+ /* Other rrsets in the section are ignored. */
+ }
++#ifdef ENABLE_FASTRPZ
++stop_short: ;
++#endif
+ /* add authority rrsets to authority prepend, for wildcarded CNAMEs */
+ for(i=msg->rep->an_numrrsets; i<msg->rep->an_numrrsets +
+ msg->rep->ns_numrrsets; i++) {
+@@ -996,6 +1019,7 @@
+ uint8_t* delname;
+ size_t delnamelen;
+ struct dns_msg* msg = NULL;
++ enum response_type type;
+
+ log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo);
+ /* check effort */
+@@ -1056,8 +1080,7 @@
+ }
+ if(msg) {
+ /* handle positive cache response */
+- enum response_type type = response_type_from_cache(msg,
+- &iq->qchase);
++ type = response_type_from_cache(msg, &iq->qchase);
+ if(verbosity >= VERB_ALGO) {
+ log_dns_msg("msg from cache lookup", &msg->qinfo,
+ msg->rep);
+@@ -1065,7 +1088,22 @@
+ (int)msg->rep->ttl,
+ (int)msg->rep->prefetch_ttl);
+ }
++#ifdef ENABLE_FASTRPZ
++ }
++ /* Check for an RPZ hit in the cached DNS message or an existing
++ * RPZ CNAME rewrite that can be resolved now after a hit on the QNAME
++ * or client IP address. This can involve a creating a fake cache
++ * hit. It can also involve overriding an RESPONSE_TYPE_ANSWER
++ * result from response_type_from_cache(). Or it can ignore
++ * the cached result to refetch glue. */
++ if(!iq->rpz_rewritten &&
++ qstate->mesh_info->reply_list &&
++ qstate->mesh_info->reply_list->query_reply.rpz &&
++ !rpz_iter_cache(&msg, &type, qstate, iq))
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+
++ if(msg) {
++#endif
+ if(type == RESPONSE_TYPE_CNAME) {
+ uint8_t* sname = 0;
+ size_t slen = 0;
+@@ -2321,6 +2359,62 @@
+ sock_list_insert(&qstate->reply_origin,
+ &qstate->reply->addr, qstate->reply->addrlen,
+ qstate->region);
++#ifdef ENABLE_FASTRPZ
++ /* Check the response for an RPZ hit. The response has already
++ * been saved in the cache. This should have the same effect
++ * as finding that response in the cache.
++ * We have already used rpz_iter_cache() at least once. */
++ if(!iq->rpz_rewritten &&
++ qstate->mesh_info->reply_list &&
++ qstate->mesh_info->reply_list->query_reply.rpz) {
++ struct dns_msg* resp;
++ bool is_cname;
++ uint8_t* sname;
++ size_t slen;
++
++ switch (rpz_iter_resp(qstate, iq, &resp, &is_cname)) {
++ case rpz_iter_resp_fail:
++ return error_response(qstate, id,
++ LDNS_RCODE_SERVFAIL);
++ case rpz_iter_resp_rewrite:
++ /* Prepend any initial CNAMEs from the original
++ * response up to a hit. */
++ if(!handle_cname_response(qstate, iq,
++ iq->response,
++ &sname, &slen))
++ return error_response(qstate, id,
++ LDNS_RCODE_SERVFAIL);
++ if (resp) {
++ iq->response = resp;
++ iq->rpz_security = resp->rep->security;
++ iq->rpz_rewritten = 1;
++
++ /* Send the rewritten record if it
++ * is not a CNAME. */
++ if(!is_cname)
++ break;
++
++ /* Prepend the new CNAME
++ * and restart to resolve it. */
++ if(!handle_cname_response(qstate, iq,
++ resp, &sname, &slen))
++ return error_response(qstate, id,
++ LDNS_RCODE_SERVFAIL);
++ }
++ iq->qchase.qname = sname;
++ iq->qchase.qname_len = slen;
++ iq->dp = NULL;
++ iq->refetch_glue = 0;
++ iq->query_restart_count++;
++ iq->sent_count = 0;
++ iq->state = INIT_REQUEST_STATE;
++ return 1;
++
++ case rpz_iter_resp_done:
++ break;
++ }
++ }
++#endif
+ if(iq->minimisation_state != DONOT_MINIMISE_STATE) {
+ if(FLAGS_GET_RCODE(iq->response->rep->flags) !=
+ LDNS_RCODE_NOERROR) {
+@@ -3022,12 +3116,44 @@
+ * but only if we did recursion. The nonrecursion referral
+ * from cache does not need to be stored in the msg cache. */
+ if(!qstate->no_cache_store && qstate->query_flags&BIT_RD) {
++#ifdef ENABLE_FASTRPZ
++ /* Do not save RPZ rewritten messages. */
++ if(!iq->rpz_rewritten)
++#endif
+ iter_dns_store(qstate->env, &qstate->qinfo,
+ iq->response->rep, 0, qstate->prefetch_leeway,
+ iq->dp&&iq->dp->has_parent_side_NS,
+ qstate->region, qstate->query_flags);
+ }
+ }
++#ifdef ENABLE_FASTRPZ
++ if(iq->rpz_rewritten) {
++ /* Restore RPZ marks on a rewritten response. The marks
++ * are lost if the rewrite is to a CNAME. */
++ iq->response->rep->security = iq->rpz_security;
++
++ /* Append the RPZ SOA to rewritten CNAME chains. */
++ if(iq->rpz_soa) {
++ struct ub_packed_rrset_key** sets;
++ uint n;
++
++ n = iq->response->rep->rrset_count;
++ sets = regional_alloc(qstate->region,
++ (1+n) * sizeof(*sets));
++ if(!sets) {
++ log_err("append RPZ SOA: out of memory");
++ return error_response(qstate, id,
++ LDNS_RCODE_SERVFAIL);
++ }
++ memcpy(sets, iq->response->rep->rrsets,
++ n * sizeof(struct ub_packed_rrset_key*));
++ sets[n] = iq->rpz_soa;
++ iq->response->rep->rrsets = sets;
++ ++iq->response->rep->rrset_count;
++ ++iq->response->rep->ar_numrrsets;
++ }
++ }
++#endif
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+ qstate->return_msg = iq->response;
+ return 0;
+===================================================================
+RCS file: ./iterator/RCS/iterator.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./iterator/iterator.h
+--- ./iterator/iterator.h
++++ ./iterator/iterator.h
+@@ -381,6 +381,16 @@
+ */
+ int minimise_count;
+
++
++#ifdef ENABLE_FASTRPZ
++ /** The response has been rewritten by RPZ. */
++ int rpz_rewritten;
++ /** RPZ SOA RR for the ADDITIONAL section */
++ struct ub_packed_rrset_key* rpz_soa;
++ /** sec_status_rpz_rewritten or sec_status_rpz_drop if rewritten. */
++ enum sec_status rpz_security;
++#endif
++
+ /**
+ * Count number of time-outs. Used to prevent resolving failures when
+ * the QNAME minimisation QTYPE is blocked. */
+===================================================================
+RCS file: ./services/cache/RCS/dns.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./services/cache/dns.c
+--- ./services/cache/dns.c
++++ ./services/cache/dns.c
+@@ -838,6 +838,14 @@
+ struct regional* region, uint16_t flags)
+ {
+ struct reply_info* rep = NULL;
++
++#ifdef ENABLE_FASTRPZ
++ /* Never save RPZ rewritten data. */
++ if (msgrep->security == sec_status_rpz_drop ||
++ msgrep->security == sec_status_rpz_rewritten)
++ return 1;
++#endif
++
+ /* alloc, malloc properly (not in region, like msg is) */
+ rep = reply_info_copy(msgrep, env->alloc, NULL);
+ if(!rep)
+===================================================================
+RCS file: ./services/RCS/mesh.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./services/mesh.c
+--- ./services/mesh.c
++++ ./services/mesh.c
+@@ -59,6 +59,9 @@
+ #include "sldns/wire2str.h"
+ #include "services/localzone.h"
+ #include "util/data/dname.h"
++#ifdef ENABLE_FASTRPZ
++#include "fastrpz/rpz.h"
++#endif
+ #include "respip/respip.h"
+
+ /** subtract timers and the values do not overflow or become negative */
+@@ -1011,6 +1014,13 @@
+ else secure = 0;
+ if(!rep && rcode == LDNS_RCODE_NOERROR)
+ rcode = LDNS_RCODE_SERVFAIL;
++#ifdef ENABLE_FASTRPZ
++ /* Drop the response here for LIBRPZ_POLICY_DROP after iteration. */
++ if(rep && rep->security == sec_status_rpz_drop) {
++ log_query_info(VERB_QUERY, "rpz drop", &m->s.qinfo);
++ secure = 0;
++ } else
++#endif
+ /* send the reply */
+ /* We don't reuse the encoded answer if either the previous or current
+ * response has a local alias. We could compare the alias records
+@@ -1160,6 +1170,7 @@
+ key.s.is_valrec = valrec;
+ key.s.qinfo = *qinfo;
+ key.s.query_flags = qflags;
++ key.reply_list = NULL;
+ /* We are searching for a similar mesh state when we DO want to
+ * aggregate the state. Thus unique is set to NULL. (default when we
+ * desire aggregation).*/
+@@ -1206,6 +1217,10 @@
+ if(!r)
+ return 0;
+ r->query_reply = *rep;
++#ifdef ENABLE_FASTRPZ
++ /* The new reply structure owns the RPZ state. */
++ rep->rpz = NULL;
++#endif
+ r->edns = *edns;
+ if(edns->opt_list) {
+ r->edns.opt_list = edns_opt_copy_region(edns->opt_list,
+===================================================================
+RCS file: ./util/RCS/config_file.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/config_file.c
+--- ./util/config_file.c
++++ ./util/config_file.c
+@@ -1167,6 +1167,8 @@
+ free(cfg->dnstap_socket_path);
+ free(cfg->dnstap_identity);
+ free(cfg->dnstap_version);
++ if (cfg->rpz_cstr)
++ free(cfg->rpz_cstr);
+ config_deldblstrlist(cfg->ratelimit_for_domain);
+ config_deldblstrlist(cfg->ratelimit_below_domain);
+ free(cfg);
+===================================================================
+RCS file: ./util/RCS/config_file.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/config_file.h
+--- ./util/config_file.h
++++ ./util/config_file.h
+@@ -416,6 +416,11 @@
+ /** true to disable DNSSEC lameness check in iterator */
+ int disable_dnssec_lame_check;
+
++ /** true to enable RPZ */
++ int rpz_enable;
++ /** RPZ configuration */
++ char* rpz_cstr;
++
+ /** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */
+ int ip_ratelimit;
+ /** number of slabs for ip_ratelimit cache */
+===================================================================
+RCS file: ./util/RCS/configlexer.lex,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/configlexer.lex
+--- ./util/configlexer.lex
++++ ./util/configlexer.lex
+@@ -395,6 +395,10 @@
+ YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES) }
+ dnstap-log-forwarder-response-messages{COLON} {
+ YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) }
++rpz{COLON} { YDVAR(0, VAR_RPZ) }
++rpz-enable{COLON} { YDVAR(1, VAR_RPZ_ENABLE) }
++rpz-zone{COLON} { YDVAR(1, VAR_RPZ_ZONE) }
++rpz-option{COLON} { YDVAR(1, VAR_RPZ_OPTION) }
+ disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) }
+ ip-ratelimit{COLON} { YDVAR(1, VAR_IP_RATELIMIT) }
+ ratelimit{COLON} { YDVAR(1, VAR_RATELIMIT) }
+===================================================================
+RCS file: ./util/RCS/configparser.y,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/configparser.y
+--- ./util/configparser.y
++++ ./util/configparser.y
+@@ -124,6 +124,7 @@
+ %token VAR_DNSTAP_LOG_CLIENT_RESPONSE_MESSAGES
+ %token VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES
+ %token VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES
++%token VAR_RPZ VAR_RPZ_ENABLE VAR_RPZ_ZONE VAR_RPZ_OPTION
+ %token VAR_RESPONSE_IP_TAG VAR_RESPONSE_IP VAR_RESPONSE_IP_DATA
+ %token VAR_HARDEN_ALGO_DOWNGRADE VAR_IP_TRANSPARENT
+ %token VAR_DISABLE_DNSSEC_LAME_CHECK
+@@ -150,7 +151,7 @@
+ toplevelvar: serverstart contents_server | stubstart contents_stub |
+ forwardstart contents_forward | pythonstart contents_py |
+ rcstart contents_rc | dtstart contents_dt | viewstart
+- contents_view |
++ contents_view | rpzstart contents_rpz |
+ dnscstart contents_dnsc
+ ;
+
+@@ -2160,6 +2161,50 @@
+ (strcmp($2, "yes")==0);
+ }
+ ;
++rpzstart: VAR_RPZ
++ {
++ OUTYY(("\nP(rpz:)\n"));
++ }
++ ;
++contents_rpz: contents_rpz content_rpz
++ | ;
++content_rpz: rpz_enable | rpz_zone | rpz_option
++ ;
++rpz_enable: VAR_RPZ_ENABLE STRING_ARG
++ {
++ OUTYY(("P(rpz_enable:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->rpz_enable = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
++rpz_zone: VAR_RPZ_ZONE STRING_ARG
++ {
++ char *new_cstr, *old_cstr;
++
++ OUTYY(("P(rpz_zone:%s)\n", $2));
++ old_cstr = cfg_parser->cfg->rpz_cstr;
++ asprintf(&new_cstr, "%s\nzone %s", old_cstr?old_cstr:"", $2);
++ if(!new_cstr)
++ yyerror("out of memory");
++ free(old_cstr);
++ cfg_parser->cfg->rpz_cstr = new_cstr;
++ }
++ ;
++rpz_option: VAR_RPZ_OPTION STRING_ARG
++ {
++ char *new_cstr, *old_cstr;
++
++ OUTYY(("P(rpz_option:%s)\n", $2));
++ old_cstr = cfg_parser->cfg->rpz_cstr;
++ asprintf(&new_cstr, "%s\n%s", old_cstr ? old_cstr : "", $2);
++ if(!new_cstr)
++ yyerror("out of memory");
++ free(old_cstr);
++ cfg_parser->cfg->rpz_cstr = new_cstr;
++ }
++ ;
+ pythonstart: VAR_PYTHON
+ {
+ OUTYY(("\nP(python:)\n"));
+===================================================================
+RCS file: ./util/data/RCS/msgencode.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/data/msgencode.c
+--- ./util/data/msgencode.c
++++ ./util/data/msgencode.c
+@@ -585,6 +585,35 @@
+ return RETVAL_OK;
+ }
+
++#ifdef ENABLE_FASTRPZ
++/* Insert the RPZ SOA even with MINIMAL_RESPONSES */
++static int
++insert_rpz_soa(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
++ sldns_buffer* pkt, size_t rrsets_before, time_t timenow,
++ struct regional* region, struct compress_tree_node** tree,
++ size_t rr_offset)
++{
++ int r;
++ size_t i, setstart;
++
++ *num_rrs = 0;
++ for(i=0; i<num_rrsets; i++) {
++ if (rep->rrsets[rrsets_before+i]->rk.type != LDNS_RR_TYPE_SOA)
++ continue;
++ setstart = sldns_buffer_position(pkt);
++ if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i],
++ pkt, num_rrs, timenow, region,
++ 1, 0, tree, LDNS_SECTION_ADDITIONAL,
++ LDNS_RR_TYPE_ANY, 0, rr_offset))
++ != RETVAL_OK) {
++ sldns_buffer_set_position(pkt, setstart);
++ return r;
++ }
++ }
++ return RETVAL_OK;
++}
++
++#endif
+ /** store query section in wireformat buffer, return RETVAL */
+ static int
+ insert_query(struct query_info* qinfo, struct compress_tree_node** tree,
+@@ -748,6 +777,19 @@
+ return 0;
+ }
+ sldns_buffer_write_u16_at(buffer, 10, arcount);
++#ifdef ENABLE_FASTRPZ
++ } else if(rep->security == sec_status_rpz_rewritten) {
++ /* Insert the RPZ SOA for rpz even with MINIMAL_RESPONSES */
++ r = insert_rpz_soa(rep, rep->ar_numrrsets, &arcount, buffer,
++ rep->an_numrrsets + rep->ns_numrrsets,
++ timenow, region, &tree, rr_offset);
++ if(r!= RETVAL_OK) {
++ if(r != RETVAL_TRUNC)
++ return 0;
++ /* no need to set TC bit, this is the additional */
++ sldns_buffer_write_u16_at(buffer, 10, arcount);
++ }
++#endif
+ }
+ sldns_buffer_flip(buffer);
+ return 1;
+===================================================================
+RCS file: ./util/data/RCS/packed_rrset.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/data/packed_rrset.c
+--- ./util/data/packed_rrset.c
++++ ./util/data/packed_rrset.c
+@@ -254,6 +254,10 @@
+ case sec_status_indeterminate: return "sec_status_indeterminate";
+ case sec_status_insecure: return "sec_status_insecure";
+ case sec_status_secure: return "sec_status_secure";
++#ifdef ENABLE_FASTRPZ
++ case sec_status_rpz_rewritten: return "sec_status_rpz_rewritten";
++ case sec_status_rpz_drop: return "sec_status_rpz_drop";
++#endif
+ }
+ return "unknown_sec_status_value";
+ }
+===================================================================
+RCS file: ./util/data/RCS/packed_rrset.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/data/packed_rrset.h
+--- ./util/data/packed_rrset.h
++++ ./util/data/packed_rrset.h
+@@ -189,7 +189,15 @@
+ sec_status_insecure,
+ /** SECURE means that the object (RRset or message) validated
+ * according to local policy. */
+- sec_status_secure
++ sec_status_secure,
++#ifdef ENABLE_FASTRPZ
++ /** RPZ_REWRITTEN means that the response has been rewritten by
++ * rpz and so cannot be verified. */
++ sec_status_rpz_rewritten,
++ /** RPZ_DROP means that the response has been rewritten by rpz
++ * as silence. */
++ sec_status_rpz_drop
++#endif
+ };
+
+ /**
+===================================================================
+RCS file: ./util/RCS/netevent.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/netevent.c
+--- ./util/netevent.c
++++ ./util/netevent.c
+@@ -54,6 +54,9 @@
+ #ifdef HAVE_OPENSSL_ERR_H
+ #include <openssl/err.h>
+ #endif
++#ifdef ENABLE_FASTRPZ
++#include "fastrpz/rpz.h"
++#endif
+
+ /* -------- Start of local definitions -------- */
+ /** if CMSG_ALIGN is not defined on this platform, a workaround */
+@@ -579,6 +582,9 @@
+ struct cmsghdr* cmsg;
+ #endif /* S_SPLINT_S */
+
++#ifdef ENABLE_FASTRPZ
++ rep.rpz = NULL;
++#endif
+ rep.c = (struct comm_point*)arg;
+ log_assert(rep.c->type == comm_udp);
+
+@@ -668,6 +674,9 @@
+ int i;
+ struct sldns_buffer *buffer;
+
++#ifdef ENABLE_FASTRPZ
++ rep.rpz = NULL;
++#endif
+ rep.c = (struct comm_point*)arg;
+ log_assert(rep.c->type == comm_udp);
+
+@@ -711,6 +720,9 @@
+ (void)comm_point_send_udp_msg(rep.c, buffer,
+ (struct sockaddr*)&rep.addr, rep.addrlen);
+ }
++#ifdef ENABLE_FASTRPZ
++ rpz_end(&rep);
++#endif
+ if(rep.c->fd != fd) /* commpoint closed to -1 or reused for
+ another UDP port. Note rep.c cannot be reused with TCP fd. */
+ break;
+@@ -2145,6 +2157,9 @@
+ comm_point_start_listening(repinfo->c, -1,
+ repinfo->c->tcp_timeout_msec);
+ }
++#ifdef ENABLE_FASTRPZ
++ rpz_end(repinfo);
++#endif
+ }
+
+ void
+@@ -2154,6 +2169,9 @@
+ return;
+ log_assert(repinfo && repinfo->c);
+ log_assert(repinfo->c->type != comm_tcp_accept);
++#ifdef ENABLE_FASTRPZ
++ rpz_end(repinfo);
++#endif
+ if(repinfo->c->type == comm_udp)
+ return;
+ reclaim_tcp_handler(repinfo->c);
+@@ -2173,6 +2191,9 @@
+ {
+ verbose(VERB_ALGO, "comm point start listening %d",
+ c->fd==-1?newfd:c->fd);
++#ifdef ENABLE_FASTRPZ
++ rpz_end(&c->repinfo);
++#endif
+ if(c->type == comm_tcp_accept && !c->tcp_free) {
+ /* no use to start listening no free slots. */
+ return;
+===================================================================
+RCS file: ./util/RCS/netevent.h,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./util/netevent.h
+--- ./util/netevent.h
++++ ./util/netevent.h
+@@ -117,6 +117,10 @@
+ /** return type 0 (none), 4(IP4), 6(IP6) */
+ int srctype;
+ /* DnsCrypt context */
++#ifdef ENABLE_FASTRPZ
++ /** per-request RPZ state */
++ struct commreply_rpz* rpz;
++#endif
+ #ifdef USE_DNSCRYPT
+ uint8_t client_nonce[crypto_box_HALF_NONCEBYTES];
+ uint8_t nmkey[crypto_box_BEFORENMBYTES];
+===================================================================
+RCS file: ./validator/RCS/validator.c,v
+retrieving revision 1.1
+diff -u --unidirectional-new-file -r1.1 ./validator/validator.c
+--- ./validator/validator.c
++++ ./validator/validator.c
+@@ -2552,6 +2552,12 @@
+ default:
+ /* NSEC proof did not work, try next */
+ break;
++#ifdef ENABLE_FASTRPZ
++ case sec_status_rpz_rewritten:
++ case sec_status_rpz_drop:
++ fatal_exit("impossible RPZ sec_status");
++ break;
++#endif
+ }
+
+ sec = nsec3_prove_nods(qstate->env, ve,
+@@ -2584,6 +2590,12 @@
+ default:
+ /* NSEC3 proof did not work */
+ break;
++#ifdef ENABLE_FASTRPZ
++ case sec_status_rpz_rewritten:
++ case sec_status_rpz_drop:
++ fatal_exit("impossible RPZ sec_status");
++ break;
++#endif
+ }
+
+ /* Apparently, no available NSEC/NSEC3 proved NODATA, so