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