From 83152a15cdc99ac1e0df21c06e440e1660f29e5f Mon Sep 17 00:00:00 2001 From: sthen Date: Wed, 23 Feb 2022 11:57:23 +0000 Subject: [PATCH] update to unbound 1.15.0, ok florian@ --- usr.sbin/unbound/README-Travis.md | 2 +- usr.sbin/unbound/dnstap/dtstream.c | 31 +- .../unbound/dnstap/unbound-dnstap-socket.c | 18 +- usr.sbin/unbound/doc/README.tests | 8 + usr.sbin/unbound/edns-subnet/edns-subnet.h | 2 +- usr.sbin/unbound/ipsecmod/ipsecmod.h | 2 +- usr.sbin/unbound/services/outbound_list.h | 2 +- usr.sbin/unbound/services/rpz.c | 1927 ++++++++++++++--- usr.sbin/unbound/services/rpz.h | 56 +- usr.sbin/unbound/testcode/asynclook.c | 2 +- usr.sbin/unbound/testcode/dohclient.c | 6 +- usr.sbin/unbound/testcode/fake_event.c | 46 +- usr.sbin/unbound/testcode/lock_verify.c | 33 + usr.sbin/unbound/testcode/perf.c | 2 +- usr.sbin/unbound/testcode/petal.c | 13 +- usr.sbin/unbound/testcode/streamtcp.c | 2 +- usr.sbin/unbound/testcode/testbound.c | 11 + usr.sbin/unbound/testcode/unitmain.c | 2 +- usr.sbin/unbound/util/edns.c | 50 - usr.sbin/unbound/util/edns.h | 12 - usr.sbin/unbound/util/regional.c | 2 + 21 files changed, 1814 insertions(+), 415 deletions(-) diff --git a/usr.sbin/unbound/README-Travis.md b/usr.sbin/unbound/README-Travis.md index 3ce22cc20f5..ca64456124a 100644 --- a/usr.sbin/unbound/README-Travis.md +++ b/usr.sbin/unbound/README-Travis.md @@ -241,7 +241,7 @@ If you are working from a developer machine you probably already have the necess The fourth step builds OpenSSL and Expat. OpenSSL and Expat are built for iOS using the scripts `contrib/ios/install_openssl.sh` and `contrib/ios/install_expat.sh`. The scripts download, configure and install the latest release version of the libraries. The libraries are configured with `--prefix="$IOS_PREFIX"` so the headers are placed in `$IOS_PREFIX/include` directory, and the libraries are placed in the `$IOS_PREFIX/lib` directory. -`IOS_PREFIX` is the value `$HOME/$IOS_SDK-$IOS_CPU`. The scheme handles both iOS SDKs and cpu architectures so the pair recieves a unique installation directory. The libraries will be installed in `$HOME/iPhoneOS-armv7s`, `$HOME/iPhoneOS-arm64`, `$HOME/iPhoneSimulator-i386`, etc. For Autotools projects, the appropriate `PKG_CONFIG_PATH` is exported. +`IOS_PREFIX` is the value `$HOME/$IOS_SDK-$IOS_CPU`. The scheme handles both iOS SDKs and cpu architectures so the pair receives a unique installation directory. The libraries will be installed in `$HOME/iPhoneOS-armv7s`, `$HOME/iPhoneOS-arm64`, `$HOME/iPhoneSimulator-i386`, etc. For Autotools projects, the appropriate `PKG_CONFIG_PATH` is exported. `PKG_CONFIG_PATH` is an important variable. It is the userland equivalent to sysroot, and allows Autotools to find non-system headers and libraries for an architecture. Typical `PKG_CONFIG_PATH` are `$HOME/iPhoneOS-armv7s/lib/pkgconfig` and `$HOME/iPhoneOS-arm64/lib/pkgconfig`. diff --git a/usr.sbin/unbound/dnstap/dtstream.c b/usr.sbin/unbound/dnstap/dtstream.c index f1ace3c3402..a1dd9703ea9 100644 --- a/usr.sbin/unbound/dnstap/dtstream.c +++ b/usr.sbin/unbound/dnstap/dtstream.c @@ -188,9 +188,9 @@ mq_wakeup_cb(void* arg) /** start timer to wakeup dtio because there is content in the queue */ static void -dt_msg_queue_start_timer(struct dt_msg_queue* mq) +dt_msg_queue_start_timer(struct dt_msg_queue* mq, int wakeupnow) { - struct timeval tv; + struct timeval tv = {0}; /* Start a timer to process messages to be logged. * If we woke up the dtio thread for every message, the wakeup * messages take up too much processing power. If the queue @@ -204,19 +204,26 @@ dt_msg_queue_start_timer(struct dt_msg_queue* mq) /* do not start the timer if a timer already exists, perhaps * in another worker. So this variable is protected by a lock in - * dtio */ + * dtio. */ + + /* If we need to wakeupnow, 0 the timer to force the callback. */ lock_basic_lock(&mq->dtio->wakeup_timer_lock); if(mq->dtio->wakeup_timer_enabled) { + if(wakeupnow) { + comm_timer_set(mq->wakeup_timer, &tv); + } lock_basic_unlock(&mq->dtio->wakeup_timer_lock); return; } mq->dtio->wakeup_timer_enabled = 1; /* we are going to start one */ - lock_basic_unlock(&mq->dtio->wakeup_timer_lock); /* start the timer, in mq, in the event base of our worker */ - tv.tv_sec = 1; - tv.tv_usec = 0; + if(!wakeupnow) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } comm_timer_set(mq->wakeup_timer, &tv); + lock_basic_unlock(&mq->dtio->wakeup_timer_lock); } void @@ -251,7 +258,7 @@ dt_msg_queue_submit(struct dt_msg_queue* mq, void* buf, size_t len) entry->buf = buf; entry->len = len; - /* aqcuire lock */ + /* acquire lock */ lock_basic_lock(&mq->lock); /* if list was empty, start timer for (eventual) wakeup */ if(mq->first == NULL) @@ -283,10 +290,8 @@ dt_msg_queue_submit(struct dt_msg_queue* mq, void* buf, size_t len) /* release lock */ lock_basic_unlock(&mq->lock); - if(wakeupnow) { - dtio_wakeup(mq->dtio); - } else if(wakeupstarttimer) { - dt_msg_queue_start_timer(mq); + if(wakeupnow || wakeupstarttimer) { + dt_msg_queue_start_timer(mq, wakeupnow); } } @@ -930,7 +935,7 @@ static int dtio_write_more_of_data(struct dt_io_thread* dtio) return 1; } -/** write more of the current messsage. false if incomplete, true if +/** write more of the current message. false if incomplete, true if * the message is done */ static int dtio_write_more(struct dt_io_thread* dtio) { @@ -1181,7 +1186,7 @@ static int dtio_read_accept_frame(struct dt_io_thread* dtio) goto close_connection; return 1; } else { - /* unknow content type */ + /* unknown content type */ verbose(VERB_ALGO, "dnstap: ACCEPT frame " "contains unknown content type, " "closing connection"); diff --git a/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c b/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c index 3de8ab3f089..63292fbca36 100644 --- a/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c +++ b/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c @@ -1264,9 +1264,9 @@ int main(int argc, char** argv) memset(&tls_list, 0, sizeof(tls_list)); /* lock debug start (if any) */ + checklock_start(); log_ident_set("unbound-dnstap-socket"); log_init(0, 0, 0); - checklock_start(); #ifdef SIGPIPE if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) { @@ -1413,10 +1413,12 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) struct outbound_entry* worker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), - size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), - char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q)) + size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), + int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; @@ -1445,10 +1447,12 @@ worker_alloc_cleanup(void* ATTR_UNUSED(arg)) struct outbound_entry* libworker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), - size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), - char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q)) + size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), + int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; diff --git a/usr.sbin/unbound/doc/README.tests b/usr.sbin/unbound/doc/README.tests index 5385e2b2221..376f01717fd 100644 --- a/usr.sbin/unbound/doc/README.tests +++ b/usr.sbin/unbound/doc/README.tests @@ -15,6 +15,14 @@ You need to have the following programs installed and in your PATH. * xxd and nc (optional) - for (malformed) packet transmission. The optional programs are detected and can be omitted. +You can also use prepared Dockerfile to run tests inside docker based on latest gcc image: +* build container: docker build -t unbound-tester -f contrib/Dockerfile.tests . +* run container: docker run -it --mount type=bind,source="$(pwd)",target=/usr/src/unbound --rm unbound-tester +* configure environment: ./configure +* run test: make test +* run long tests: make longtest +It is worth to mention that you need to enable [ipv6 in your docker daemon configuration](https://docs.docker.com/config/daemon/ipv6/) because some tests need ipv6 network stack. + testdata/ contains the data for tests. testcode/ contains scripts and c code for the tests. diff --git a/usr.sbin/unbound/edns-subnet/edns-subnet.h b/usr.sbin/unbound/edns-subnet/edns-subnet.h index 4b306080ad6..6ab541de948 100644 --- a/usr.sbin/unbound/edns-subnet/edns-subnet.h +++ b/usr.sbin/unbound/edns-subnet/edns-subnet.h @@ -59,7 +59,7 @@ struct ecs_data { /** * copy the first n BITS from src to dst iff both src and dst - * are large enough, return 0 on succes + * are large enough, return 0 on success */ int copy_clear(uint8_t* dst, size_t dstlen, uint8_t* src, size_t srclen, size_t n); diff --git a/usr.sbin/unbound/ipsecmod/ipsecmod.h b/usr.sbin/unbound/ipsecmod/ipsecmod.h index e00816d4bf9..272f473c2ea 100644 --- a/usr.sbin/unbound/ipsecmod/ipsecmod.h +++ b/usr.sbin/unbound/ipsecmod/ipsecmod.h @@ -37,7 +37,7 @@ * \file * * This file contains a module that facilitates opportunistic IPsec. It does so - * by also quering for the IPSECKEY for A/AAAA queries and calling a + * by also querying for the IPSECKEY for A/AAAA queries and calling a * configurable hook (eg. signaling an IKE daemon) before replying. */ diff --git a/usr.sbin/unbound/services/outbound_list.h b/usr.sbin/unbound/services/outbound_list.h index ad59e42d192..73c137d5099 100644 --- a/usr.sbin/unbound/services/outbound_list.h +++ b/usr.sbin/unbound/services/outbound_list.h @@ -79,7 +79,7 @@ void outbound_list_init(struct outbound_list* list); * Clear the user owner outbound list structure. * Deletes serviced queries. * @param list: the list structure. It is cleared, but the list struct itself - * is callers responsability to delete. + * is callers responsibility to delete. */ void outbound_list_clear(struct outbound_list* list); diff --git a/usr.sbin/unbound/services/rpz.c b/usr.sbin/unbound/services/rpz.c index 3a1ec00d7d3..322e9d1393c 100644 --- a/usr.sbin/unbound/services/rpz.c +++ b/usr.sbin/unbound/services/rpz.c @@ -50,45 +50,50 @@ #include "util/data/dname.h" #include "util/locks.h" #include "util/regional.h" +#include "util/data/msgencode.h" +#include "services/cache/dns.h" +#include "iterator/iterator.h" +#include "iterator/iter_delegpt.h" +#include "daemon/worker.h" + +typedef struct resp_addr rpz_aclnode_type; + +struct matched_delegation_point { + uint8_t* dname; + size_t dname_len; +}; /** string for RPZ action enum */ const char* rpz_action_to_string(enum rpz_action a) { switch(a) { - case RPZ_NXDOMAIN_ACTION: return "nxdomain"; - case RPZ_NODATA_ACTION: return "nodata"; - case RPZ_PASSTHRU_ACTION: return "passthru"; - case RPZ_DROP_ACTION: return "drop"; - case RPZ_TCP_ONLY_ACTION: return "tcp_only"; - case RPZ_INVALID_ACTION: return "invalid"; - case RPZ_LOCAL_DATA_ACTION: return "local_data"; - case RPZ_DISABLED_ACTION: return "disabled"; - case RPZ_CNAME_OVERRIDE_ACTION: return "cname_override"; - case RPZ_NO_OVERRIDE_ACTION: return "no_override"; + case RPZ_NXDOMAIN_ACTION: return "rpz-nxdomain"; + case RPZ_NODATA_ACTION: return "rpz-nodata"; + case RPZ_PASSTHRU_ACTION: return "rpz-passthru"; + case RPZ_DROP_ACTION: return "rpz-drop"; + case RPZ_TCP_ONLY_ACTION: return "rpz-tcp-only"; + case RPZ_INVALID_ACTION: return "rpz-invalid"; + case RPZ_LOCAL_DATA_ACTION: return "rpz-local-data"; + case RPZ_DISABLED_ACTION: return "rpz-disabled"; + case RPZ_CNAME_OVERRIDE_ACTION: return "rpz-cname-override"; + case RPZ_NO_OVERRIDE_ACTION: return "rpz-no-override"; + default: return "rpz-unknown-action"; } - return "unknown"; } /** RPZ action enum for config string */ static enum rpz_action rpz_config_to_action(char* a) { - if(strcmp(a, "nxdomain") == 0) - return RPZ_NXDOMAIN_ACTION; - else if(strcmp(a, "nodata") == 0) - return RPZ_NODATA_ACTION; - else if(strcmp(a, "passthru") == 0) - return RPZ_PASSTHRU_ACTION; - else if(strcmp(a, "drop") == 0) - return RPZ_DROP_ACTION; - else if(strcmp(a, "tcp_only") == 0) - return RPZ_TCP_ONLY_ACTION; - else if(strcmp(a, "cname") == 0) - return RPZ_CNAME_OVERRIDE_ACTION; - else if(strcmp(a, "disabled") == 0) - return RPZ_DISABLED_ACTION; - return RPZ_INVALID_ACTION; + if(strcmp(a, "nxdomain") == 0) return RPZ_NXDOMAIN_ACTION; + else if(strcmp(a, "nodata") == 0) return RPZ_NODATA_ACTION; + else if(strcmp(a, "passthru") == 0) return RPZ_PASSTHRU_ACTION; + else if(strcmp(a, "drop") == 0) return RPZ_DROP_ACTION; + else if(strcmp(a, "tcp_only") == 0) return RPZ_TCP_ONLY_ACTION; + else if(strcmp(a, "cname") == 0) return RPZ_CNAME_OVERRIDE_ACTION; + else if(strcmp(a, "disabled") == 0) return RPZ_DISABLED_ACTION; + else return RPZ_INVALID_ACTION; } /** string for RPZ trigger enum */ @@ -96,14 +101,14 @@ static const char* rpz_trigger_to_string(enum rpz_trigger r) { switch(r) { - case RPZ_QNAME_TRIGGER: return "qname"; - case RPZ_CLIENT_IP_TRIGGER: return "client_ip"; - case RPZ_RESPONSE_IP_TRIGGER: return "response_ip"; - case RPZ_NSDNAME_TRIGGER: return "nsdname"; - case RPZ_NSIP_TRIGGER: return "nsip"; - case RPZ_INVALID_TRIGGER: return "invalid"; + case RPZ_QNAME_TRIGGER: return "rpz-qname"; + case RPZ_CLIENT_IP_TRIGGER: return "rpz-client-ip"; + case RPZ_RESPONSE_IP_TRIGGER: return "rpz-response-ip"; + case RPZ_NSDNAME_TRIGGER: return "rpz-nsdname"; + case RPZ_NSIP_TRIGGER: return "rpz-nsip"; + case RPZ_INVALID_TRIGGER: return "rpz-invalid"; + default: return "rpz-unknown-trigger"; } - return "unknown"; } /** @@ -137,6 +142,31 @@ get_tld_label(uint8_t* dname, size_t maxdnamelen) return prevlab; } +/** + * The RR types that are to be ignored. + * DNSSEC RRs at the apex, and SOA and NS are ignored. + */ +static int +rpz_type_ignored(uint16_t rr_type) +{ + switch(rr_type) { + case LDNS_RR_TYPE_SOA: + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_DNAME: + /* all DNSSEC-related RRs must be ignored */ + case LDNS_RR_TYPE_DNSKEY: + case LDNS_RR_TYPE_DS: + case LDNS_RR_TYPE_RRSIG: + case LDNS_RR_TYPE_NSEC: + case LDNS_RR_TYPE_NSEC3: + case LDNS_RR_TYPE_NSEC3PARAM: + return 1; + default: + break; + } + return 0; +} + /** * Classify RPZ action for RR type/rdata * @param rr_type: the RR type @@ -208,15 +238,15 @@ static enum localzone_type rpz_action_to_localzone_type(enum rpz_action a) { switch(a) { - case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; - case RPZ_NODATA_ACTION: return local_zone_always_nodata; - case RPZ_DROP_ACTION: return local_zone_always_deny; - case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; + case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; + case RPZ_NODATA_ACTION: return local_zone_always_nodata; + case RPZ_DROP_ACTION: return local_zone_always_deny; + case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect; - case RPZ_INVALID_ACTION: /* fallthrough */ - case RPZ_TCP_ONLY_ACTION: /* fallthrough */ - default: return local_zone_invalid; + case RPZ_TCP_ONLY_ACTION: return local_zone_truncate; + case RPZ_INVALID_ACTION: /* fallthrough */ + default: return local_zone_invalid; } } @@ -224,15 +254,15 @@ enum respip_action rpz_action_to_respip_action(enum rpz_action a) { switch(a) { - case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; - case RPZ_NODATA_ACTION: return respip_always_nodata; - case RPZ_DROP_ACTION: return respip_always_deny; - case RPZ_PASSTHRU_ACTION: return respip_always_transparent; - case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ + case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; + case RPZ_NODATA_ACTION: return respip_always_nodata; + case RPZ_DROP_ACTION: return respip_always_deny; + case RPZ_PASSTHRU_ACTION: return respip_always_transparent; + case RPZ_LOCAL_DATA_ACTION: /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return respip_redirect; - case RPZ_INVALID_ACTION: /* fallthrough */ - case RPZ_TCP_ONLY_ACTION: /* fallthrough */ - default: return respip_invalid; + case RPZ_TCP_ONLY_ACTION: return respip_truncate; + case RPZ_INVALID_ACTION: /* fallthrough */ + default: return respip_invalid; } } @@ -240,14 +270,14 @@ static enum rpz_action localzone_type_to_rpz_action(enum localzone_type lzt) { switch(lzt) { - case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; - case local_zone_always_nodata: return RPZ_NODATA_ACTION; - case local_zone_always_deny: return RPZ_DROP_ACTION; - case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; - case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; - case local_zone_invalid: - default: - return RPZ_INVALID_ACTION; + case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case local_zone_always_nodata: return RPZ_NODATA_ACTION; + case local_zone_always_deny: return RPZ_DROP_ACTION; + case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; + case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; + case local_zone_truncate: return RPZ_TCP_ONLY_ACTION; + case local_zone_invalid: /* fallthrough */ + default: return RPZ_INVALID_ACTION; } } @@ -255,14 +285,14 @@ enum rpz_action respip_action_to_rpz_action(enum respip_action a) { switch(a) { - case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; - case respip_always_nodata: return RPZ_NODATA_ACTION; - case respip_always_deny: return RPZ_DROP_ACTION; - case respip_always_transparent: return RPZ_PASSTHRU_ACTION; - case respip_redirect: return RPZ_LOCAL_DATA_ACTION; - case respip_invalid: - default: - return RPZ_INVALID_ACTION; + case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; + case respip_always_nodata: return RPZ_NODATA_ACTION; + case respip_always_deny: return RPZ_DROP_ACTION; + case respip_always_transparent: return RPZ_PASSTHRU_ACTION; + case respip_redirect: return RPZ_LOCAL_DATA_ACTION; + case respip_truncate: return RPZ_TCP_ONLY_ACTION; + case respip_invalid: /* fallthrough */ + default: return RPZ_INVALID_ACTION; } } @@ -298,12 +328,55 @@ rpz_dname_to_trigger(uint8_t* dname, size_t dname_len) return RPZ_QNAME_TRIGGER; } -void rpz_delete(struct rpz* r) +static inline struct clientip_synthesized_rrset* +rpz_clientip_synthesized_set_create(void) +{ + struct clientip_synthesized_rrset* set = calloc(1, sizeof(*set)); + if(set == NULL) { + return NULL; + } + set->region = regional_create(); + if(set->region == NULL) { + free(set); + return NULL; + } + addr_tree_init(&set->entries); + lock_rw_init(&set->lock); + return set; +} + +static void +rpz_clientip_synthesized_rr_delete(rbnode_type* n, void* ATTR_UNUSED(arg)) +{ + struct clientip_synthesized_rr* r = (struct clientip_synthesized_rr*)n->key; + lock_rw_destroy(&r->lock); +#ifdef THREADS_DISABLED + (void)r; +#endif +} + +static inline void +rpz_clientip_synthesized_set_delete(struct clientip_synthesized_rrset* set) +{ + if(set == NULL) { + return; + } + lock_rw_destroy(&set->lock); + traverse_postorder(&set->entries, rpz_clientip_synthesized_rr_delete, NULL); + regional_destroy(set->region); + free(set); +} + +void +rpz_delete(struct rpz* r) { if(!r) return; local_zones_delete(r->local_zones); + local_zones_delete(r->nsdname_zones); respip_set_delete(r->respip_set); + rpz_clientip_synthesized_set_delete(r->client_set); + rpz_clientip_synthesized_set_delete(r->ns_set); regional_destroy(r->region); free(r->taglist); free(r->log_name); @@ -315,13 +388,31 @@ rpz_clear(struct rpz* r) { /* must hold write lock on auth_zone */ local_zones_delete(r->local_zones); + r->local_zones = NULL; + local_zones_delete(r->nsdname_zones); + r->nsdname_zones = NULL; respip_set_delete(r->respip_set); + r->respip_set = NULL; + rpz_clientip_synthesized_set_delete(r->client_set); + r->client_set = NULL; + rpz_clientip_synthesized_set_delete(r->ns_set); + r->ns_set = NULL; if(!(r->local_zones = local_zones_create())){ return 0; } + r->nsdname_zones = local_zones_create(); + if(r->nsdname_zones == NULL) { + return 0; + } if(!(r->respip_set = respip_set_create())) { return 0; } + if(!(r->client_set = rpz_clientip_synthesized_set_create())) { + return 0; + } + if(!(r->ns_set = rpz_clientip_synthesized_set_create())) { + return 0; + } return 1; } @@ -331,6 +422,14 @@ rpz_finish_config(struct rpz* r) lock_rw_wrlock(&r->respip_set->lock); addr_tree_init_parents(&r->respip_set->ip_tree); lock_rw_unlock(&r->respip_set->lock); + + lock_rw_wrlock(&r->client_set->lock); + addr_tree_init_parents(&r->client_set->entries); + lock_rw_unlock(&r->client_set->lock); + + lock_rw_wrlock(&r->ns_set->lock); + addr_tree_init_parents(&r->ns_set->entries); + lock_rw_unlock(&r->ns_set->lock); } /** new rrset containing CNAME override, does not yet contain a dname */ @@ -394,9 +493,26 @@ rpz_create(struct config_auth* p) if(!(r->local_zones = local_zones_create())){ goto err; } + + r->nsdname_zones = local_zones_create(); + if(r->local_zones == NULL){ + goto err; + } + if(!(r->respip_set = respip_set_create())) { goto err; } + + r->client_set = rpz_clientip_synthesized_set_create(); + if(r->client_set == NULL) { + goto err; + } + + r->ns_set = rpz_clientip_synthesized_set_create(); + if(r->ns_set == NULL) { + goto err; + } + r->taglistlen = p->rpz_taglistlen; r->taglist = memdup(p->rpz_taglist, r->taglistlen); if(p->rpz_action_override) { @@ -426,6 +542,7 @@ rpz_create(struct config_auth* p) } } r->log = p->rpz_log; + r->signal_nxdomain_ra = p->rpz_signal_nxdomain_ra; if(p->rpz_log_name) { if(!(r->log_name = strdup(p->rpz_log_name))) { log_err("malloc failure on RPZ log_name strdup"); @@ -437,8 +554,14 @@ err: if(r) { if(r->local_zones) local_zones_delete(r->local_zones); + if(r->nsdname_zones) + local_zones_delete(r->nsdname_zones); if(r->respip_set) respip_set_delete(r->respip_set); + if(r->client_set != NULL) + rpz_clientip_synthesized_set_delete(r->client_set); + if(r->ns_set != NULL) + rpz_clientip_synthesized_set_delete(r->ns_set); if(r->taglist) free(r->taglist); if(r->region) @@ -467,19 +590,17 @@ strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen, return newdnamelen + 1; /* + 1 for root label */ } -/** Insert RR into RPZ's local-zone */ static void -rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, - enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, - uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, + size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, + uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct local_zone* z; enum localzone_type tp = local_zone_always_transparent; int dnamelabs = dname_count_labels(dname); - char* rrstr; int newzone = 0; - if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION) { + if(a == RPZ_INVALID_ACTION) { char str[255+1]; if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS || rrtype == LDNS_RR_TYPE_DNAME || @@ -499,237 +620,542 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return; } - lock_rw_wrlock(&r->local_zones->lock); + lock_rw_wrlock(&lz->lock); /* exact match */ - z = local_zones_find(r->local_zones, dname, dnamelen, dnamelabs, - LDNS_RR_CLASS_IN); - if(z && a != RPZ_LOCAL_DATA_ACTION) { - rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ qname " - "trigger"); + z = local_zones_find(lz, dname, dnamelen, dnamelabs, LDNS_RR_CLASS_IN); + if(z != NULL && a != RPZ_LOCAL_DATA_ACTION) { + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz nsdname trigger"); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } - verbose(VERB_ALGO, "RPZ: skipping duplicate record: '%s'", - rrstr); + if(rrstr[0]) + rrstr[strlen(rrstr)-1]=0; /* remove newline */ + verbose(VERB_ALGO, "rpz: skipping duplicate record: '%s'", rrstr); free(rrstr); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } - if(!z) { + if(z == NULL) { tp = rpz_action_to_localzone_type(a); - if(!(z = local_zones_add_zone(r->local_zones, dname, dnamelen, - dnamelabs, rrclass, tp))) { - log_warn("RPZ create failed"); - lock_rw_unlock(&r->local_zones->lock); + z = local_zones_add_zone(lz, dname, dnamelen, + dnamelabs, rrclass, tp); + if(z == NULL) { + log_warn("rpz: create failed"); + lock_rw_unlock(&lz->lock); /* dname will be free'd in failed local_zone_create() */ return; } newzone = 1; } if(a == RPZ_LOCAL_DATA_ACTION) { - rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ qname " - "trigger"); + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz nsdname trigger"); free(dname); - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&lz->lock); return; } lock_rw_wrlock(&z->lock); - local_zone_enter_rr(z, dname, dnamelen, dnamelabs, - rrtype, rrclass, ttl, rdata, rdata_len, rrstr); + local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr); lock_rw_unlock(&z->lock); free(rrstr); } - if(!newzone) + if(!newzone) { free(dname); - lock_rw_unlock(&r->local_zones->lock); - return; + } + lock_rw_unlock(&lz->lock); } -/** Insert RR into RPZ's respip_set */ -static int -rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, +static void +rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len) +{ + char buf[LDNS_MAX_DOMAINLEN+1]; + (void)dname_len; + dname_str(dname, buf); + verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf); +} + +static void +rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { - struct resp_addr* node; - struct sockaddr_storage addr; - socklen_t addrlen; - int net, af; - char* rrstr; - enum respip_action respa = rpz_action_to_respip_action(a); + if(a == RPZ_INVALID_ACTION) { + verbose(VERB_ALGO, "rpz: skipping invalid action"); + free(dname); + return; + } - if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION || - respa == respip_invalid) { - char str[255+1]; - dname_str(dname, str); - verbose(VERB_ALGO, "RPZ: respip trigger, %s skipping unsupported action: %s", - str, rpz_action_to_string(a)); + rpz_insert_local_zones_trigger(r->local_zones, dname, dnamelen, a, rrtype, + rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +static int +rpz_strip_nsdname_suffix(uint8_t* dname, size_t maxdnamelen, + uint8_t** stripdname, size_t* stripdnamelen) +{ + uint8_t* tldstart = get_tld_label(dname, maxdnamelen); + uint8_t swap; + if(tldstart == NULL) { + if(dname == NULL) { + *stripdname = NULL; + *stripdnamelen = 0; + return 0; + } + *stripdname = memdup(dname, maxdnamelen); + if(!*stripdname) { + *stripdnamelen = 0; + log_err("malloc failure for rpz strip suffix"); + return 0; + } + *stripdnamelen = maxdnamelen; + return 1; + } + /* shorten the domain name briefly, + * then we allocate a new name with the correct length */ + swap = *tldstart; + *tldstart = 0; + (void)dname_count_size_labels(dname, stripdnamelen); + *stripdname = memdup(dname, *stripdnamelen); + *tldstart = swap; + if(!*stripdname) { + *stripdnamelen = 0; + log_err("malloc failure for rpz strip suffix"); return 0; } + return 1; +} - if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) - return 0; +static void +rpz_insert_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + uint8_t* dname_stripped = NULL; + size_t dnamelen_stripped = 0; - lock_rw_wrlock(&r->respip_set->lock); + rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, + &dnamelen_stripped); + if(a == RPZ_INVALID_ACTION) { + verbose(VERB_ALGO, "rpz: skipping invalid action"); + free(dname_stripped); + return; + } + + /* dname_stripped is consumed or freed by the insert routine */ + rpz_insert_local_zones_trigger(r->nsdname_zones, dname_stripped, + dnamelen_stripped, a, rrtype, rrclass, ttl, rdata, rdata_len, + rr, rr_len); +} + +static int +rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype, + uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, + uint8_t* rr, size_t rr_len) +{ + struct resp_addr* node; + char* rrstr; + enum respip_action respa = rpz_action_to_respip_action(a); + + lock_rw_wrlock(&set->lock); rrstr = sldns_wire2str_rr(rr, rr_len); - if(!rrstr) { - log_err("malloc error while inserting RPZ respip trigger"); - lock_rw_unlock(&r->respip_set->lock); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz ipaddr based trigger"); + lock_rw_unlock(&set->lock); return 0; } - if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen, - net, 1, rrstr))) { - lock_rw_unlock(&r->respip_set->lock); + + node = respip_sockaddr_find_or_create(set, addr, addrlen, net, 1, rrstr); + if(node == NULL) { + lock_rw_unlock(&set->lock); free(rrstr); return 0; } lock_rw_wrlock(&node->lock); - lock_rw_unlock(&r->respip_set->lock); + lock_rw_unlock(&set->lock); + node->action = respa; if(a == RPZ_LOCAL_DATA_ACTION) { - respip_enter_rr(r->respip_set->region, node, rrtype, - rrclass, ttl, rdata, rdata_len, rrstr, ""); + respip_enter_rr(set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len, rrstr, ""); } + lock_rw_unlock(&node->lock); free(rrstr); return 1; } -int -rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, - size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, - uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len) +static inline struct clientip_synthesized_rr* +rpz_clientip_ensure_entry(struct clientip_synthesized_rrset* set, + struct sockaddr_storage* addr, socklen_t addrlen, int net) { - size_t policydnamelen; - /* name is free'd in local_zone delete */ - enum rpz_trigger t; - enum rpz_action a; - uint8_t* policydname; + int insert_ok; + struct clientip_synthesized_rr* node = + (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, + addr, addrlen, net); - if(!dname_subdomain_c(dname, azname)) { - char* dname_str = sldns_wire2str_dname(dname, dnamelen); - char* azname_str = sldns_wire2str_dname(azname, aznamelen); - if(dname_str && azname_str) { - log_err("RPZ: name of record (%s) to insert into RPZ is not a " - "subdomain of the configured name of the RPZ zone (%s)", - dname_str, azname_str); - } else { - log_err("RPZ: name of record to insert into RPZ is not a " - "subdomain of the configured name of the RPZ zone"); - } - free(dname_str); - free(azname_str); - return 0; + if(node != NULL) { return node; } + + /* node does not yet exist => allocate one */ + node = regional_alloc_zero(set->region, sizeof(*node)); + if(node == NULL) { + log_err("out of memory"); + return NULL; } - log_assert(dnamelen >= aznamelen); - if(!(policydname = calloc(1, (dnamelen-aznamelen)+1))) { - log_err("malloc error while inserting RPZ RR"); - return 0; + lock_rw_init(&node->lock); + node->action = RPZ_INVALID_ACTION; + insert_ok = addr_tree_insert(&set->entries, &node->node, + addr, addrlen, net); + if (!insert_ok) { + log_warn("rpz: unexpected: unable to insert clientip address node"); + /* we can not free the just allocated node. + * theoretically a memleak */ + return NULL; } - a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); - if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, - policydname, (dnamelen-aznamelen)+1))) { - free(policydname); - return 0; + return node; +} + +static void +rpz_report_rrset_error(const char* msg, uint8_t* rr, size_t rr_len) { + char* rrstr = sldns_wire2str_rr(rr, rr_len); + if(rrstr == NULL) { + log_err("malloc error while inserting rpz clientip based record"); + return; } - t = rpz_dname_to_trigger(policydname, policydnamelen); - if(t == RPZ_INVALID_TRIGGER) { - free(policydname); - verbose(VERB_ALGO, "RPZ: skipping invalid trigger"); - return 1; + log_err("rpz: unexpected: unable to insert %s: %s", msg, rrstr); + free(rrstr); +} + +/* from localzone.c; difference is we don't have a dname */ +static struct local_rrset* +rpz_clientip_new_rrset(struct regional* region, + struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t rrclass) +{ + struct packed_rrset_data* pd; + struct local_rrset* rrset = (struct local_rrset*) + regional_alloc_zero(region, sizeof(*rrset)); + if(rrset == NULL) { + log_err("out of memory"); + return NULL; } - if(t == RPZ_QNAME_TRIGGER) { - rpz_insert_qname_trigger(r, policydname, policydnamelen, - a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, - rr_len); + rrset->next = raddr->data; + raddr->data = rrset; + rrset->rrset = (struct ub_packed_rrset_key*) + regional_alloc_zero(region, sizeof(*rrset->rrset)); + if(rrset->rrset == NULL) { + log_err("out of memory"); + return NULL; } - else if(t == RPZ_RESPONSE_IP_TRIGGER) { - rpz_insert_response_ip_trigger(r, policydname, policydnamelen, - a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, - rr_len); - free(policydname); + rrset->rrset->entry.key = rrset->rrset; + pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); + if(pd == NULL) { + log_err("out of memory"); + return NULL; } - else { - free(policydname); - verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s", - rpz_trigger_to_string(t)); + pd->trust = rrset_trust_prim_noglue; + pd->security = sec_status_insecure; + rrset->rrset->entry.data = pd; + rrset->rrset->rk.type = htons(rrtype); + rrset->rrset->rk.rrset_class = htons(rrclass); + rrset->rrset->rk.dname = regional_alloc_zero(region, 1); + if(rrset->rrset->rk.dname == NULL) { + log_err("out of memory"); + return NULL; } - return 1; + rrset->rrset->rk.dname_len = 1; + return rrset; } -/** - * Find RPZ local-zone by qname. - * @param r: rpz containing local-zone tree - * @param qname: qname - * @param qname_len: length of qname - * @param qclass: qclass - * @param only_exact: if 1 only excact (non wildcard) matches are returned - * @param wr: get write lock for local-zone if 1, read lock if 0 - * @param zones_keep_lock: if set do not release the r->local_zones lock, this - * makes the caller of this function responsible for releasing the lock. - * @return: NULL or local-zone holding rd or wr lock - */ -static struct local_zone* -rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, - int only_exact, int wr, int zones_keep_lock) +static int +rpz_clientip_enter_rr(struct regional* region, struct clientip_synthesized_rr* raddr, + uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, + size_t rdata_len) { - uint8_t* ce; - size_t ce_len; - int ce_labs; - uint8_t wc[LDNS_MAX_DOMAINLEN+1]; - int exact; - struct local_zone* z = NULL; - if(wr) { - lock_rw_wrlock(&r->local_zones->lock); - } else { - lock_rw_rdlock(&r->local_zones->lock); - } - z = local_zones_find_le(r->local_zones, qname, qname_len, - dname_count_labels(qname), - LDNS_RR_CLASS_IN, &exact); - if(!z || (only_exact && !exact)) { - lock_rw_unlock(&r->local_zones->lock); - return NULL; - } - if(wr) { - lock_rw_wrlock(&z->lock); - } else { - lock_rw_rdlock(&z->lock); + struct local_rrset* rrset; + if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data != NULL) { + log_err("CNAME response-ip data can not co-exist with other " + "client-ip data"); + return 0; } - if(!zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + + rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass); + if(raddr->data == NULL) { + return 0; } - if(exact) - return z; + return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, rdata_len, ttl, ""); +} - /* No exact match found, lookup wildcard. closest encloser must - * be the shared parent between the qname and the best local - * zone match, append '*' to that and do another lookup. */ +static int +rpz_clientip_insert_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype, + uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, + uint8_t* rr, size_t rr_len) +{ + struct clientip_synthesized_rr* node; - ce = dname_get_shared_topdomain(z->name, qname); - if(!ce /* should not happen */) { - lock_rw_unlock(&z->lock); - if(zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); - } - return NULL; + lock_rw_wrlock(&set->lock); + + node = rpz_clientip_ensure_entry(set, addr, addrlen, net); + if(node == NULL) { + lock_rw_unlock(&set->lock); + rpz_report_rrset_error("client ip address", rr, rr_len); + return 0; + } + + lock_rw_wrlock(&node->lock); + lock_rw_unlock(&set->lock); + + node->action = a; + if(a == RPZ_LOCAL_DATA_ACTION) { + if(!rpz_clientip_enter_rr(set->region, node, rrtype, + rrclass, ttl, rdata, rdata_len)) { + verbose(VERB_ALGO, "rpz: unable to insert clientip rr"); + lock_rw_unlock(&node->lock); + return 0; + } + + } + + lock_rw_unlock(&node->lock); + + return 1; +} + +static int +rpz_insert_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse client ip"); + return 0; + } + + return rpz_clientip_insert_trigger_rr(r->client_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +static int +rpz_insert_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse ns ip"); + return 0; + } + + return rpz_clientip_insert_trigger_rr(r->ns_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +/** Insert RR into RPZ's respip_set */ +static int +rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + + if(a == RPZ_INVALID_ACTION) { + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "rpz: unable to parse response ip"); + return 0; + } + + if(a == RPZ_INVALID_ACTION || + rpz_action_to_respip_action(a) == respip_invalid) { + char str[255+1]; + dname_str(dname, str); + verbose(VERB_ALGO, "RPZ: respip trigger, %s skipping unsupported action: %s", + str, rpz_action_to_string(a)); + return 0; + } + + return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +int +rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, + size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, + uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len) +{ + size_t policydnamelen; + /* name is free'd in local_zone delete */ + enum rpz_trigger t; + enum rpz_action a; + uint8_t* policydname; + + if(rpz_type_ignored(rr_type)) { + /* this rpz action is not valid, eg. this is the SOA or NS RR */ + return 1; + } + if(!dname_subdomain_c(dname, azname)) { + char* dname_str = sldns_wire2str_dname(dname, dnamelen); + char* azname_str = sldns_wire2str_dname(azname, aznamelen); + if(dname_str && azname_str) { + log_err("rpz: name of record (%s) to insert into RPZ is not a " + "subdomain of the configured name of the RPZ zone (%s)", + dname_str, azname_str); + } else { + log_err("rpz: name of record to insert into RPZ is not a " + "subdomain of the configured name of the RPZ zone"); + } + free(dname_str); + free(azname_str); + return 0; + } + + log_assert(dnamelen >= aznamelen); + if(!(policydname = calloc(1, (dnamelen-aznamelen)+1))) { + log_err("malloc error while inserting RPZ RR"); + return 0; + } + + a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); + if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, + policydname, (dnamelen-aznamelen)+1))) { + free(policydname); + return 0; + } + t = rpz_dname_to_trigger(policydname, policydnamelen); + if(t == RPZ_INVALID_TRIGGER) { + free(policydname); + verbose(VERB_ALGO, "rpz: skipping invalid trigger"); + return 1; + } + if(t == RPZ_QNAME_TRIGGER) { + /* policydname will be consumed, no free */ + rpz_insert_qname_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + } else if(t == RPZ_RESPONSE_IP_TRIGGER) { + rpz_insert_response_ip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else if(t == RPZ_CLIENT_IP_TRIGGER) { + rpz_insert_clientip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else if(t == RPZ_NSIP_TRIGGER) { + rpz_insert_nsip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else if(t == RPZ_NSDNAME_TRIGGER) { + rpz_insert_nsdname_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else { + free(policydname); + verbose(VERB_ALGO, "rpz: skipping unsupported trigger: %s", + rpz_trigger_to_string(t)); + } + return 1; +} + +/** + * Find RPZ local-zone by qname. + * @param zones: local-zone tree + * @param qname: qname + * @param qname_len: length of qname + * @param qclass: qclass + * @param only_exact: if 1 only exact (non wildcard) matches are returned + * @param wr: get write lock for local-zone if 1, read lock if 0 + * @param zones_keep_lock: if set do not release the r->local_zones lock, this + * makes the caller of this function responsible for releasing the lock. + * @return: NULL or local-zone holding rd or wr lock + */ +static struct local_zone* +rpz_find_zone(struct local_zones* zones, uint8_t* qname, size_t qname_len, uint16_t qclass, + int only_exact, int wr, int zones_keep_lock) +{ + uint8_t* ce; + size_t ce_len; + int ce_labs; + uint8_t wc[LDNS_MAX_DOMAINLEN+1]; + int exact; + struct local_zone* z = NULL; + + if(wr) { + lock_rw_wrlock(&zones->lock); + } else { + lock_rw_rdlock(&zones->lock); + } + z = local_zones_find_le(zones, qname, qname_len, + dname_count_labels(qname), + LDNS_RR_CLASS_IN, &exact); + if(!z || (only_exact && !exact)) { + if(!zones_keep_lock) { + lock_rw_unlock(&zones->lock); + } + return NULL; + } + if(wr) { + lock_rw_wrlock(&z->lock); + } else { + lock_rw_rdlock(&z->lock); + } + if(!zones_keep_lock) { + lock_rw_unlock(&zones->lock); + } + + if(exact) + return z; + + /* No exact match found, lookup wildcard. closest encloser must + * be the shared parent between the qname and the best local + * zone match, append '*' to that and do another lookup. */ + + ce = dname_get_shared_topdomain(z->name, qname); + if(!ce /* should not happen */) { + lock_rw_unlock(&z->lock); + if(zones_keep_lock) { + lock_rw_unlock(&zones->lock); + } + return NULL; } ce_labs = dname_count_size_labels(ce, &ce_len); if(ce_len+2 > sizeof(wc)) { lock_rw_unlock(&z->lock); if(zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } return NULL; } @@ -740,15 +1166,15 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, if(!zones_keep_lock) { if(wr) { - lock_rw_wrlock(&r->local_zones->lock); + lock_rw_wrlock(&zones->lock); } else { - lock_rw_rdlock(&r->local_zones->lock); + lock_rw_rdlock(&zones->lock); } } - z = local_zones_find_le(r->local_zones, wc, + z = local_zones_find_le(zones, wc, ce_len+2, ce_labs+1, qclass, &exact); if(!z || !exact) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); return NULL; } if(wr) { @@ -757,7 +1183,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, lock_rw_rdlock(&z->lock); } if(!zones_keep_lock) { - lock_rw_unlock(&r->local_zones->lock); + lock_rw_unlock(&zones->lock); } return z; } @@ -766,7 +1192,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass, * Remove RR from RPZ's local-data * @param z: local-zone for RPZ, holding write lock * @param policydname: dname of RR to remove - * @param policydnamelen: lenth of policydname + * @param policydnamelen: length of policydname * @param rr_type: RR type of RR to remove * @param rdata: rdata of RR to remove * @param rdatalen: length of rdata @@ -852,10 +1278,10 @@ rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, { struct local_zone* z; int delete_zone = 1; - z = rpz_find_zone(r, dname, dnamelen, rr_class, + z = rpz_find_zone(r->local_zones, dname, dnamelen, rr_class, 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/); if(!z) { - verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); return; } @@ -891,7 +1317,7 @@ rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, lock_rw_wrlock(&r->respip_set->lock); if(!(node = (struct resp_addr*)addr_tree_find( &r->respip_set->ip_tree, &addr, addrlen, net))) { - verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, " + verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); lock_rw_unlock(&r->respip_set->lock); return; @@ -944,118 +1370,1009 @@ rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen, /** print log information for an applied RPZ policy. Based on local-zone's * lz_inform_print(). + * The repinfo contains the reply address. If it is NULL, the module + * state is used to report the first IP address (if any). + * The dname is used, for the applied rpz, if NULL, addrnode is used. */ static void -log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo, - struct comm_reply* repinfo, char* log_name) +log_rpz_apply(char* trigger, uint8_t* dname, struct addr_tree_node* addrnode, + enum rpz_action a, struct query_info* qinfo, + struct comm_reply* repinfo, struct module_qstate* ms, char* log_name) { - char ip[128], txt[512]; + char ip[128], txt[512], portstr[32]; char dnamestr[LDNS_MAX_DOMAINLEN+1]; - uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); - dname_str(dname, dnamestr); - addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); - if(log_name) - snprintf(txt, sizeof(txt), "RPZ applied [%s] %s %s %s@%u", - log_name, dnamestr, rpz_action_to_string(a), ip, - (unsigned)port); - else - snprintf(txt, sizeof(txt), "RPZ applied %s %s %s@%u", - dnamestr, rpz_action_to_string(a), ip, (unsigned)port); + uint16_t port = 0; + if(dname) { + dname_str(dname, dnamestr); + } else if(addrnode) { + char addrbuf[128]; + addr_to_str(&addrnode->addr, addrnode->addrlen, addrbuf, sizeof(addrbuf)); + snprintf(dnamestr, sizeof(dnamestr), "%s/%d", addrbuf, addrnode->net); + } else { + dnamestr[0]=0; + } + if(repinfo) { + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); + port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); + } else if(ms && ms->mesh_info && ms->mesh_info->reply_list) { + addr_to_str(&ms->mesh_info->reply_list->query_reply.addr, ms->mesh_info->reply_list->query_reply.addrlen, ip, sizeof(ip)); + port = ntohs(((struct sockaddr_in*)&ms->mesh_info->reply_list->query_reply.addr)->sin_port); + } else { + ip[0]=0; + port = 0; + } + snprintf(portstr, sizeof(portstr), "@%u", (unsigned)port); + snprintf(txt, sizeof(txt), "rpz: applied %s%s%s%s%s%s %s %s%s", + (log_name?"[":""), (log_name?log_name:""), (log_name?"] ":""), + (strcmp(trigger,"qname")==0?"":trigger), + (strcmp(trigger,"qname")==0?"":" "), + dnamestr, rpz_action_to_string(a), + (ip[0]?ip:""), (ip[0]?portstr:"")); log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } -int -rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, - struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, - struct regional* temp, struct comm_reply* repinfo, - uint8_t* taglist, size_t taglen, struct ub_server_stats* stats) +static struct clientip_synthesized_rr* +rpz_ipbased_trigger_lookup(struct clientip_synthesized_rrset* set, + struct sockaddr_storage* addr, socklen_t addrlen, char* triggername) +{ + struct clientip_synthesized_rr* raddr = NULL; + enum rpz_action action = RPZ_INVALID_ACTION; + + lock_rw_rdlock(&set->lock); + + raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(&set->entries, + addr, addrlen); + if(raddr != NULL) { + lock_rw_rdlock(&raddr->lock); + action = raddr->action; + if(verbosity >= VERB_ALGO) { + char ip[256], net[256]; + addr_to_str(addr, addrlen, ip, sizeof(ip)); + addr_to_str(&raddr->node.addr, raddr->node.addrlen, + net, sizeof(net)); + verbose(VERB_ALGO, "rpz: trigger %s %s/%d on %s action=%s", + triggername, net, raddr->node.net, ip, rpz_action_to_string(action)); + } + } + lock_rw_unlock(&set->lock); + + return raddr; +} + +static inline +struct clientip_synthesized_rr* +rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo, + struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, + struct ub_server_stats* stats, + /* output parameters */ + struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out) { + struct clientip_synthesized_rr* node = NULL; + struct auth_zone* a = NULL; struct rpz* r = NULL; - struct auth_zone* a; - int ret; - enum localzone_type lzt; struct local_zone* z = NULL; - struct local_data* ld = NULL; + lock_rw_rdlock(&az->rpz_lock); + for(a = az->rpz_first; a; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; - if(!r->disabled && (!r->taglist || taglist_intersect(r->taglist, - r->taglistlen, taglist, taglen))) { - z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len, - qinfo->qclass, 0, 0, 0); - if(z && r->action_override == RPZ_DISABLED_ACTION) { - if(r->log) - log_rpz_apply(z->name, - r->action_override, - qinfo, repinfo, r->log_name); - /* TODO only register stats when stats_extended? - * */ - stats->rpz_action[r->action_override]++; + if(r->disabled) { + lock_rw_unlock(&a->lock); + continue; + } + if(r->taglist && !taglist_intersect(r->taglist, + r->taglistlen, taglist, taglen)) { + lock_rw_unlock(&a->lock); + continue; + } + z = rpz_find_zone(r->local_zones, qinfo->qname, qinfo->qname_len, + qinfo->qclass, 0, 0, 0); + node = rpz_ipbased_trigger_lookup(r->client_set, &repinfo->addr, repinfo->addrlen, "clientip"); + if((z || node) && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply((node?"clientip":"qname"), + (z?z->name:NULL), + (node?&node->node:NULL), + r->action_override, + qinfo, repinfo, NULL, r->log_name); + stats->rpz_action[r->action_override]++; + if(z != NULL) { lock_rw_unlock(&z->lock); z = NULL; } - if(z) - break; + if(node != NULL) { + lock_rw_unlock(&node->lock); + node = NULL; + } + } + if(z || node) { + break; } - lock_rw_unlock(&a->lock); /* not found in this auth_zone */ + /* not found in this auth_zone */ + lock_rw_unlock(&a->lock); } + lock_rw_unlock(&az->rpz_lock); - if(!z) - return 0; /* not holding auth_zone.lock anymore */ - log_assert(r); - if(r->action_override == RPZ_NO_OVERRIDE_ACTION) - lzt = z->type; - else - lzt = rpz_action_to_localzone_type(r->action_override); + *r_out = r; + *a_out = a; + *z_out = z; + + return node; +} + +static inline int +rpz_is_udp_query(struct comm_reply* repinfo) { + return repinfo != NULL + ? (repinfo->c != NULL + ? repinfo->c->type == comm_udp + : 0) + : 0; +} + +/** encode answer consisting of 1 rrset */ +static int +rpz_local_encode(struct module_env* env, struct query_info* qinfo, + struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, + struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, + int rcode, struct ub_packed_rrset_key* soa_rrset) +{ + struct reply_info rep; + uint16_t udpsize; + struct ub_packed_rrset_key* rrsetlist[3]; + + memset(&rep, 0, sizeof(rep)); + rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode); + rep.qdcount = 1; + rep.rrset_count = ansec; + rep.rrsets = rrsetlist; + if(ansec > 0) { + rep.an_numrrsets = 1; + rep.rrsets[0] = rrset; + rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0]; + } + if(soa_rrset != NULL) { + rep.ar_numrrsets = 1; + rep.rrsets[rep.rrset_count] = soa_rrset; + rep.rrset_count ++; + if(rep.ttl < ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]) { + rep.ttl = ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]; + } + } + + udpsize = edns->udp_size; + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, + repinfo, temp, env->now_tv) || + !reply_info_answer_encode(qinfo, &rep, + *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { + error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, + *(uint16_t*)sldns_buffer_begin(buf), + sldns_buffer_read_u16_at(buf, 2), edns); + } + + return 1; +} + +static struct local_rrset* +rpz_find_synthesized_rrset(int qtype, struct clientip_synthesized_rr* data) { + struct local_rrset* cursor = data->data; + while( cursor != NULL) { + struct packed_rrset_key* packed_rrset = &cursor->rrset->rk; + if(htons(qtype) == packed_rrset->type) { + return cursor; + } + cursor = cursor->next; + } + return NULL; +} +/** allocate SOA record ubrrsetkey in region */ +static struct ub_packed_rrset_key* +make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa, + struct regional* temp) +{ + struct ub_packed_rrset_key csoa; + if(!soa) + return NULL; + memset(&csoa, 0, sizeof(csoa)); + csoa.entry.key = &csoa; + csoa.rk.rrset_class = htons(LDNS_RR_CLASS_IN); + csoa.rk.type = htons(LDNS_RR_TYPE_SOA); + csoa.rk.flags |= PACKED_RRSET_FIXEDTTL + | PACKED_RRSET_RPZ; + csoa.rk.dname = auth_zone->name; + csoa.rk.dname_len = auth_zone->namelen; + csoa.entry.hash = rrset_key_hash(&csoa.rk); + csoa.entry.data = soa->data; + return respip_copy_rrset(&csoa, temp); +} + +static void +rpz_apply_clientip_localdata_action(struct clientip_synthesized_rr* raddr, + struct module_env* env, struct query_info* qinfo, + struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, + struct regional* temp, struct auth_zone* auth_zone) +{ + struct local_rrset* rrset; + enum rpz_action action = RPZ_INVALID_ACTION; + struct ub_packed_rrset_key* rp = NULL; + struct ub_packed_rrset_key* rsoa = NULL; + int rcode = LDNS_RCODE_NOERROR|BIT_AA; + int rrset_count = 1; + + /* prepare synthesized answer for client */ + action = raddr->action; + if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL ) { + verbose(VERB_ALGO, "rpz: bug: local-data action but no local data"); + return; + } + + /* check query type / rr type */ + rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: unable to find local-data for query"); + rrset_count = 0; + goto nodata; + } + + rp = respip_copy_rrset(rrset->rrset, temp); + if(!rp) { + verbose(VERB_ALGO, "rpz: local data action: out of memory"); + return; + } + + rp->rk.flags |= PACKED_RRSET_FIXEDTTL | PACKED_RRSET_RPZ; + rp->rk.dname = qinfo->qname; + rp->rk.dname_len = qinfo->qname_len; + rp->entry.hash = rrset_key_hash(&rp->rk); +nodata: + if(auth_zone) { + struct auth_rrset* soa = NULL; + soa = auth_zone_get_soa_rrset(auth_zone); + if(soa) { + rsoa = make_soa_ubrrset(auth_zone, soa, temp); + if(!rsoa) { + verbose(VERB_ALGO, "rpz: local data action soa: out of memory"); + return; + } + } + } + + rpz_local_encode(env, qinfo, edns, repinfo, buf, temp, rp, + rrset_count, rcode, rsoa); +} + +/** add additional section SOA record to the reply. + * Since this gets fed into the normal iterator answer creation, it + * gets minimal-responses applied to it, that can remove the additional SOA + * again. */ +static int +rpz_add_soa(struct reply_info* rep, struct module_qstate* ms, + struct auth_zone* az) +{ + struct auth_rrset* soa = NULL; + struct ub_packed_rrset_key* rsoa = NULL; + struct ub_packed_rrset_key** prevrrsets; + if(!az) return 1; + soa = auth_zone_get_soa_rrset(az); + if(!soa) return 1; + if(!rep) return 0; + rsoa = make_soa_ubrrset(az, soa, ms->region); + if(!rsoa) return 0; + prevrrsets = rep->rrsets; + rep->rrsets = regional_alloc_zero(ms->region, + sizeof(*rep->rrsets)*(rep->rrset_count+1)); + if(!rep->rrsets) + return 0; + if(prevrrsets && rep->rrset_count > 0) + memcpy(rep->rrsets, prevrrsets, rep->rrset_count*sizeof(*rep->rrsets)); + rep->rrset_count++; + rep->ar_numrrsets++; + rep->rrsets[rep->rrset_count-1] = rsoa; + return 1; +} + +static inline struct dns_msg* +rpz_dns_msg_new(struct regional* region) +{ + struct dns_msg* msg = + (struct dns_msg*)regional_alloc(region, + sizeof(struct dns_msg)); + if(msg == NULL) { return NULL; } + memset(msg, 0, sizeof(struct dns_msg)); + + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nodata(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, + struct query_info* qinfo, struct auth_zone* az) +{ + struct dns_msg* msg = rpz_dns_msg_new(ms->region); + if(msg == NULL) { return msg; } + msg->qinfo = *qinfo; + msg->rep = construct_reply_info_base(ms->region, + LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 0, /* an */ + 0, /* ns */ + 0, /* ar */ + 0, /* total */ + sec_status_insecure); + if(msg->rep) + msg->rep->authoritative = 1; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nxdomain(struct rpz* r, struct module_qstate* ms, + struct query_info* qinfo, struct auth_zone* az) +{ + struct dns_msg* msg = rpz_dns_msg_new(ms->region); + uint16_t flags; + if(msg == NULL) { return msg; } + msg->qinfo = *qinfo; + flags = LDNS_RCODE_NXDOMAIN | BIT_QR | BIT_AA | BIT_RA; + if(r->signal_nxdomain_ra) + flags &= ~BIT_RA; + msg->rep = construct_reply_info_base(ms->region, + flags, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 0, /* an */ + 0, /* ns */ + 0, /* ar */ + 0, /* total */ + sec_status_insecure); + if(msg->rep) + msg->rep->authoritative = 1; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_localdata_from_rrset(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, + struct query_info* qi, struct local_rrset* rrset, struct auth_zone* az) +{ + struct dns_msg* msg = NULL; + struct reply_info* new_reply_info; + struct ub_packed_rrset_key* rp; + + + msg = rpz_dns_msg_new(ms->region); + if(msg == NULL) { return NULL; } + + new_reply_info = construct_reply_info_base(ms->region, + LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA, + 1, /* qd */ + 0, /* ttl */ + 0, /* prettl */ + 0, /* expttl */ + 1, /* an */ + 0, /* ns */ + 0, /* ar */ + 1, /* total */ + sec_status_insecure); + if(new_reply_info == NULL) { + log_err("out of memory"); + return NULL; + } + new_reply_info->authoritative = 1; + rp = respip_copy_rrset(rrset->rrset, ms->region); + if(rp == NULL) { + log_err("out of memory"); + return NULL; + } + rp->rk.dname = qi->qname; + rp->rk.dname_len = qi->qname_len; + /* this rrset is from the rpz data, or synthesized. + * It is not actually from the network, so we flag it with this + * flags as a fake RRset. If later the cache is used to look up + * rrsets, then the fake ones are not returned (if you look without + * the flag). For like CNAME lookups from the iterator or A, AAAA + * lookups for nameserver targets, it would use the without flag + * actual data. So that the actual network data and fake data + * are kept track of separately. */ + rp->rk.flags |= PACKED_RRSET_RPZ; + new_reply_info->rrsets[0] = rp; + msg->rep = new_reply_info; + if(!rpz_add_soa(msg->rep, ms, az)) + return NULL; + return msg; +} + +static inline struct dns_msg* +rpz_synthesize_nsip_localdata(struct rpz* r, struct module_qstate* ms, + struct clientip_synthesized_rr* data, struct auth_zone* az) +{ + struct query_info* qi = &ms->qinfo; + struct local_rrset* rrset; + + rrset = rpz_find_synthesized_rrset(qi->qtype, data); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: nsip: no matching local data found"); + return NULL; + } + + return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az); +} + +/* copy'n'paste from localzone.c */ +static struct local_rrset* +local_data_find_type(struct local_data* data, uint16_t type, int alias_ok) +{ + struct local_rrset* p; + type = htons(type); + for(p = data->rrsets; p; p = p->next) { + if(p->rrset->rk.type == type) + return p; + if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) + return p; + } + return NULL; +} + +/* based on localzone.c:local_data_answer() */ +static inline struct dns_msg* +rpz_synthesize_nsdname_localdata(struct rpz* r, struct module_qstate* ms, + struct local_zone* z, struct matched_delegation_point const* match, + struct auth_zone* az) +{ + struct local_data key; + struct local_data* ld; + struct local_rrset* rrset; + + if(match->dname == NULL) { return NULL; } + + key.node.key = &key; + key.name = match->dname; + key.namelen = match->dname_len; + key.namelabs = dname_count_labels(match->dname); + + rpz_log_dname("nsdname local data", key.name, key.namelen); + + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + if(ld == NULL) { + verbose(VERB_ALGO, "rpz: nsdname: impossible: qname not found"); + return NULL; + } + + rrset = local_data_find_type(ld, ms->qinfo.qtype, 1); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: nsdname: no matching local data found"); + return NULL; + } + + return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az); +} + +/* like local_data_answer for qname triggers after a cname */ +static struct dns_msg* +rpz_synthesize_qname_localdata_msg(struct rpz* r, struct module_qstate* ms, + struct query_info* qinfo, struct local_zone* z, struct auth_zone* az) +{ + struct local_data key; + struct local_data* ld; + struct local_rrset* rrset; + key.node.key = &key; + key.name = qinfo->qname; + key.namelen = qinfo->qname_len; + key.namelabs = dname_count_labels(qinfo->qname); + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + if(ld == NULL) { + verbose(VERB_ALGO, "rpz: qname after cname: name not found"); + return NULL; + } + rrset = local_data_find_type(ld, qinfo->qtype, 1); + if(rrset == NULL) { + verbose(VERB_ALGO, "rpz: qname after cname: type not found"); + return NULL; + } + return rpz_synthesize_localdata_from_rrset(r, ms, qinfo, rrset, az); +} + +static int +rpz_synthesize_qname_localdata(struct module_env* env, struct rpz* r, + struct local_zone* z, enum localzone_type lzt, struct query_info* qinfo, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, + struct comm_reply* repinfo, struct ub_server_stats* stats) +{ + struct local_data* ld = NULL; + int ret = 0; if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { - qinfo->local_alias = - regional_alloc_zero(temp, sizeof(struct local_rrset)); - if(!qinfo->local_alias) { - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); + qinfo->local_alias = regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(qinfo->local_alias == NULL) { return 0; /* out of memory */ } - qinfo->local_alias->rrset = - regional_alloc_init(temp, r->cname_override, - sizeof(*r->cname_override)); - if(!qinfo->local_alias->rrset) { - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); + qinfo->local_alias->rrset = regional_alloc_init(temp, r->cname_override, + sizeof(*r->cname_override)); + if(qinfo->local_alias->rrset == NULL) { return 0; /* out of memory */ } qinfo->local_alias->rrset->rk.dname = qinfo->qname; qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; - if(r->log) - log_rpz_apply(z->name, RPZ_CNAME_OVERRIDE_ACTION, - qinfo, repinfo, r->log_name); + if(r->log) { + log_rpz_apply("qname", z->name, NULL, RPZ_CNAME_OVERRIDE_ACTION, + qinfo, repinfo, NULL, r->log_name); + } stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); return 0; } if(lzt == local_zone_redirect && local_data_answer(z, env, qinfo, edns, repinfo, buf, temp, dname_count_labels(qinfo->qname), &ld, lzt, -1, NULL, 0, NULL, 0)) { - if(r->log) - log_rpz_apply(z->name, + if(r->log) { + log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), qinfo, - repinfo, r->log_name); + repinfo, NULL, r->log_name); + } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; - lock_rw_unlock(&z->lock); - lock_rw_unlock(&a->lock); return !qinfo->local_alias; } ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, lzt); - if(r->log) - log_rpz_apply(z->name, localzone_type_to_rpz_action(lzt), - qinfo, repinfo, r->log_name); + if(r->signal_nxdomain_ra && LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) + == LDNS_RCODE_NXDOMAIN) + LDNS_RA_CLR(sldns_buffer_begin(buf)); + if(r->log) { + log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), + qinfo, repinfo, NULL, r->log_name); + } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; + return ret; +} + +static struct clientip_synthesized_rr* +rpz_delegation_point_ipbased_trigger_lookup(struct rpz* rpz, struct iter_qstate* is) +{ + struct delegpt_addr* cursor; + struct clientip_synthesized_rr* action = NULL; + if(is->dp == NULL) { return NULL; } + for(cursor = is->dp->target_list; + cursor != NULL; + cursor = cursor->next_target) { + if(cursor->bogus) { continue; } + action = rpz_ipbased_trigger_lookup(rpz->ns_set, &cursor->addr, + cursor->addrlen, "nsip"); + if(action != NULL) { return action; } + } + return NULL; +} + +static struct dns_msg* +rpz_apply_nsip_trigger(struct module_qstate* ms, struct rpz* r, + struct clientip_synthesized_rr* raddr, struct auth_zone* az) +{ + enum rpz_action action = raddr->action; + struct dns_msg* ret = NULL; + + if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { + verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", + rpz_action_to_string(r->action_override), rpz_action_to_string(action)); + action = r->action_override; + } + + if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL) { + verbose(VERB_ALGO, "rpz: bug: nsip local data action but no local data"); + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + goto done; + } + + switch(action) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_nsip_localdata(r, ms, raddr, az); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(action)); + ret = NULL; + } + +done: + if(r->log) + log_rpz_apply("nsip", NULL, &raddr->node, + action, &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[action]++; + lock_rw_unlock(&raddr->lock); + return ret; +} + +static struct dns_msg* +rpz_apply_nsdname_trigger(struct module_qstate* ms, struct rpz* r, + struct local_zone* z, struct matched_delegation_point const* match, + struct auth_zone* az) +{ + struct dns_msg* ret = NULL; + enum rpz_action action = localzone_type_to_rpz_action(z->type); + + if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { + verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", + rpz_action_to_string(r->action_override), rpz_action_to_string(action)); + action = r->action_override; + } + + switch(action) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_nsdname_localdata(r, ms, z, match, az); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(action)); + ret = NULL; + } + + if(r->log) + log_rpz_apply("nsdname", match->dname, NULL, + action, &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[action]++; + lock_rw_unlock(&z->lock); + return ret; +} + +static struct local_zone* +rpz_delegation_point_zone_lookup(struct delegpt* dp, struct local_zones* zones, + uint16_t qclass, + /* output parameter */ + struct matched_delegation_point* match) +{ + struct delegpt_ns* nameserver; + struct local_zone* z = NULL; + + /* the rpz specs match the nameserver names (NS records), not the + * name of the delegation point itself, to the nsdname triggers */ + for(nameserver = dp->nslist; + nameserver != NULL; + nameserver = nameserver->next) { + z = rpz_find_zone(zones, nameserver->name, nameserver->namelen, + qclass, 0, 0, 0); + if(z != NULL) { + match->dname = nameserver->name; + match->dname_len = nameserver->namelen; + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(match->dname, nm); + dname_str(z->name, zn); + if(strcmp(nm, zn) != 0) + verbose(VERB_ALGO, "rpz: trigger nsdname %s on %s action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); + else + verbose(VERB_ALGO, "rpz: trigger nsdname %s action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); + } + break; + } + } + + return z; +} + +struct dns_msg* +rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* is) +{ + struct auth_zones* az; + struct auth_zone* a; + struct clientip_synthesized_rr* raddr = NULL; + struct rpz* r = NULL; + struct local_zone* z = NULL; + struct matched_delegation_point match = {0}; + + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } + + az = ms->env->auth_zones; + + verbose(VERB_ALGO, "rpz: iterator module callback: have_rpz=%d", az->rpz_first != NULL); + + lock_rw_rdlock(&az->rpz_lock); + + /* precedence of RPZ works, loosely, like this: + * CNAMEs in order of the CNAME chain. rpzs in the order they are + * configured. In an RPZ: first client-IP addr, then QNAME, then + * response IP, then NSDNAME, then NSIP. Longest match first. Smallest + * one from a set. */ + /* we use the precedence rules for the topics and triggers that + * are pertinent at this stage of the resolve processing */ + for(a = az->rpz_first; a != NULL; a = a->rpz_az_next) { + lock_rw_rdlock(&a->lock); + r = a->rpz; + if(r->disabled) { + lock_rw_unlock(&a->lock); + continue; + } + + /* the nsdname has precedence over the nsip triggers */ + z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones, + ms->qinfo.qclass, &match); + if(z != NULL) { + lock_rw_unlock(&a->lock); + break; + } + + raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is); + if(raddr != NULL) { + lock_rw_unlock(&a->lock); + break; + } + lock_rw_unlock(&a->lock); + } + + lock_rw_unlock(&az->rpz_lock); + + if(raddr == NULL && z == NULL) { return NULL; } + else if(raddr != NULL) { + if(z) { + lock_rw_unlock(&z->lock); + } + return rpz_apply_nsip_trigger(ms, r, raddr, a); + } else if(z != NULL) { + if(raddr) { + lock_rw_unlock(&raddr->lock); + } + return rpz_apply_nsdname_trigger(ms, r, z, &match, a); + } else { return NULL; } +} + +struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, + struct iter_qstate* is) +{ + struct auth_zones* az; + struct auth_zone* a = NULL; + struct rpz* r = NULL; + struct local_zone* z = NULL; + enum localzone_type lzt; + struct dns_msg* ret = NULL; + + if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } + az = ms->env->auth_zones; + + lock_rw_rdlock(&az->rpz_lock); + + for(a = az->rpz_first; a; a = a->rpz_az_next) { + lock_rw_rdlock(&a->lock); + r = a->rpz; + if(r->disabled) { + lock_rw_unlock(&a->lock); + continue; + } + z = rpz_find_zone(r->local_zones, is->qchase.qname, + is->qchase.qname_len, is->qchase.qclass, 0, 0, 0); + if(z && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply("qname", z->name, NULL, + r->action_override, + &ms->qinfo, NULL, ms, r->log_name); + if(ms->env->worker) + ms->env->worker->stats.rpz_action[r->action_override]++; + lock_rw_unlock(&z->lock); + z = NULL; + } + if(z) { + break; + } + /* not found in this auth_zone */ + lock_rw_unlock(&a->lock); + } + lock_rw_unlock(&az->rpz_lock); + + if(z == NULL) + return NULL; + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { + lzt = z->type; + } else { + lzt = rpz_action_to_localzone_type(r->action_override); + } + + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(is->qchase.qname, nm); + dname_str(z->name, zn); + if(strcmp(zn, nm) != 0) + verbose(VERB_ALGO, "rpz: qname trigger after cname %s on %s, with action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + else + verbose(VERB_ALGO, "rpz: qname trigger after cname %s, with action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + } + switch(localzone_type_to_rpz_action(lzt)) { + case RPZ_NXDOMAIN_ACTION: + ret = rpz_synthesize_nxdomain(r, ms, &is->qchase, a); + break; + case RPZ_NODATA_ACTION: + ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); + break; + case RPZ_TCP_ONLY_ACTION: + /* basically a passthru here but the tcp-only will be + * honored before the query gets sent. */ + ms->respip_action_info->action = respip_truncate; + ret = NULL; + break; + case RPZ_DROP_ACTION: + ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); + ms->is_drop = 1; + break; + case RPZ_LOCAL_DATA_ACTION: + ret = rpz_synthesize_qname_localdata_msg(r, ms, &is->qchase, z, a); + if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); } + break; + case RPZ_PASSTHRU_ACTION: + ret = NULL; + break; + default: + verbose(VERB_ALGO, "rpz: qname trigger after cname: bug: unhandled or invalid action: '%s'", + rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + ret = NULL; + } + lock_rw_unlock(&z->lock); + lock_rw_unlock(&a->lock); + return ret; +} + +static int +rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, + uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, + sldns_buffer* buf, struct regional* temp, + /* output parameters */ + struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out) +{ + int ret = 0; + enum rpz_action client_action; + struct clientip_synthesized_rr* node = rpz_resolve_client_action_and_zone( + az, qinfo, repinfo, taglist, taglen, stats, z_out, a_out, r_out); + + client_action = ((node == NULL) ? RPZ_INVALID_ACTION : node->action); + + if(*z_out == NULL || (client_action != RPZ_INVALID_ACTION && + client_action != RPZ_PASSTHRU_ACTION)) { + if(client_action == RPZ_PASSTHRU_ACTION + || client_action == RPZ_INVALID_ACTION + || (client_action == RPZ_TCP_ONLY_ACTION + && !rpz_is_udp_query(repinfo))) { + ret = 0; + goto done; + } + stats->rpz_action[client_action]++; + if(client_action == RPZ_LOCAL_DATA_ACTION) { + rpz_apply_clientip_localdata_action(node, env, qinfo, + edns, repinfo, buf, temp, *a_out); + } else { + if(*r_out && (*r_out)->log) + log_rpz_apply( + (node?"clientip":"qname"), + ((*z_out)?(*z_out)->name:NULL), + (node?&node->node:NULL), + client_action, qinfo, repinfo, NULL, + (*r_out)->log_name); + local_zones_zone_answer(*z_out /*likely NULL, no zone*/, env, qinfo, edns, + repinfo, buf, temp, 0 /* no local data used */, + rpz_action_to_localzone_type(client_action)); + if(*r_out && (*r_out)->signal_nxdomain_ra && + LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) + == LDNS_RCODE_NXDOMAIN) + LDNS_RA_CLR(sldns_buffer_begin(buf)); + } + ret = 1; + goto done; + } + ret = -1; +done: + if(node != NULL) { + lock_rw_unlock(&node->lock); + } + return ret; +} + +int +rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, struct ub_server_stats* stats) +{ + struct rpz* r = NULL; + struct auth_zone* a = NULL; + struct local_zone* z = NULL; + int ret; + enum localzone_type lzt; + + int clientip_trigger = rpz_apply_maybe_clientip_trigger(az, env, qinfo, + edns, repinfo, taglist, taglen, stats, buf, temp, &z, &a, &r); + if(clientip_trigger >= 0) { + if(a) { + lock_rw_unlock(&a->lock); + } + if(z) { + lock_rw_unlock(&z->lock); + } + return clientip_trigger; + } + + if(z == NULL) { + if(a) { + lock_rw_unlock(&a->lock); + } + return 0; + } + + log_assert(r); + + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { + lzt = z->type; + } else { + lzt = rpz_action_to_localzone_type(r->action_override); + } + + if(verbosity >= VERB_ALGO) { + char nm[255+1], zn[255+1]; + dname_str(qinfo->qname, nm); + dname_str(z->name, zn); + if(strcmp(zn, nm) != 0) + verbose(VERB_ALGO, "rpz: qname trigger %s on %s with action=%s", + zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + else + verbose(VERB_ALGO, "rpz: qname trigger %s with action=%s", + nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); + } + + ret = rpz_synthesize_qname_localdata(env, r, z, lzt, qinfo, edns, buf, temp, + repinfo, stats); + lock_rw_unlock(&z->lock); lock_rw_unlock(&a->lock); diff --git a/usr.sbin/unbound/services/rpz.h b/usr.sbin/unbound/services/rpz.h index d5996a6cfa2..c29d30dff50 100644 --- a/usr.sbin/unbound/services/rpz.h +++ b/usr.sbin/unbound/services/rpz.h @@ -50,6 +50,7 @@ #include "sldns/sbuffer.h" #include "daemon/stats.h" #include "respip/respip.h" +struct iter_qstate; /** * RPZ triggers, only the QNAME trigger is currently supported in Unbound. @@ -83,6 +84,27 @@ enum rpz_action { RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/ }; +struct clientip_synthesized_rrset{ + struct regional* region; + struct rbtree_type entries; + lock_rw_type lock; /* lock on the respip tree */ +}; + +struct clientip_synthesized_rr { + /** node in address tree */ + struct addr_tree_node node; + /** lock on the node item */ + lock_rw_type lock; + /** tag bitlist */ + uint8_t* taglist; + /** length of the taglist (in bytes) */ + size_t taglen; + /** action for this address span */ + enum rpz_action action; + /** "local data" for this node */ + struct local_rrset* data; +}; + /** * RPZ containing policies. Pointed to from corresponding auth-zone. Part of a * linked list to keep configuration order. Iterating or changing the linked @@ -92,12 +114,17 @@ enum rpz_action { struct rpz { struct local_zones* local_zones; struct respip_set* respip_set; + struct clientip_synthesized_rrset* client_set; + struct clientip_synthesized_rrset* ns_set; + struct local_zones* nsdname_zones; uint8_t* taglist; size_t taglistlen; enum rpz_action action_override; struct ub_packed_rrset_key* cname_override; int log; char* log_name; + /** signal NXDOMAIN blocked with unset RA flag */ + int signal_nxdomain_ra; struct regional* region; int disabled; }; @@ -147,15 +174,38 @@ void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, * @param temp: scratchpad * @param repinfo: reply info * @param taglist: taglist to lookup. - * @param taglen: lenth of taglist. + * @param taglen: length of taglist. * @param stats: worker stats struct * @return: 1 if client answer is ready, 0 to continue resolving */ -int rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, +int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats); +/** + * Callback to process when the iterator module is about to send queries. + * Checks for nsip and nsdname triggers. + * @param qstate: the query state. + * @param iq: iterator module query state. + * @return NULL if nothing is done. Or a new message with the contents from + * the rpz, based on the delegation point. It is allocated in the + * qstate region. + */ +struct dns_msg* rpz_callback_from_iterator_module(struct module_qstate* qstate, + struct iter_qstate* iq); + +/** + * Callback to process when the iterator module has followed a cname. + * There can be a qname trigger for the new query name. + * @param qstate: the query state. + * @param iq: iterator module query state. + * @return NULL if nothing is done. Or a new message with the contents from + * the rpz, based on the iq.qchase. It is allocated in the qstate region. + */ +struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* qstate, + struct iter_qstate* iq); + /** * Delete RPZ * @param r: RPZ struct to delete @@ -186,7 +236,7 @@ enum rpz_action respip_action_to_rpz_action(enum respip_action a); /** - * Prepare RPZ after procesing feed content. + * Prepare RPZ after processing feed content. * @param r: RPZ to use */ void rpz_finish_config(struct rpz* r); diff --git a/usr.sbin/unbound/testcode/asynclook.c b/usr.sbin/unbound/testcode/asynclook.c index 660f72a7df8..fffd14ed598 100644 --- a/usr.sbin/unbound/testcode/asynclook.c +++ b/usr.sbin/unbound/testcode/asynclook.c @@ -406,10 +406,10 @@ int main(int argc, char** argv) struct lookinfo* lookups; int i, r, cancel=0, blocking=0, ext=0; + checklock_start(); /* init log now because solaris thr_key_create() is not threadsafe */ log_init(0,0,0); /* lock debug start (if any) */ - checklock_start(); /* create context */ ctx = ub_ctx_create(); diff --git a/usr.sbin/unbound/testcode/dohclient.c b/usr.sbin/unbound/testcode/dohclient.c index 93d84a83550..d35f5a82c66 100644 --- a/usr.sbin/unbound/testcode/dohclient.c +++ b/usr.sbin/unbound/testcode/dohclient.c @@ -358,7 +358,7 @@ static int http2_data_chunk_recv_cb(nghttp2_session* ATTR_UNUSED(session), } if(sldns_buffer_remaining(h2_stream->buf) < len) { - log_err("received data chunck does not fit into buffer"); + log_err("received data chunk does not fit into buffer"); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -531,7 +531,7 @@ run(struct http2_session* h2_session, int port, int no_tls, int count, char** q) h2_session->block_select = 1; - /* hande query */ + /* handle query */ for(i=0; ibase; struct fake_pending* pend = (struct fake_pending*)calloc(1, @@ -1222,11 +1223,37 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, if(1) { struct edns_data edns; struct edns_string_addr* client_string_addr; + struct edns_option* backed_up_opt_list = + qstate->edns_opts_back_out; + struct edns_option* per_upstream_opt_list = NULL; + /* If we have an already populated EDNS option list make a copy + * since we may now add upstream specific EDNS options. */ + if(qstate->edns_opts_back_out) { + per_upstream_opt_list = edns_opt_copy_region( + qstate->edns_opts_back_out, qstate->region); + if(!per_upstream_opt_list) { + free(pend); + fatal_exit("out of memory"); + } + qstate->edns_opts_back_out = per_upstream_opt_list; + } if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen, zone, zonelen, qstate, qstate->region)) { free(pend); return NULL; } + /* Restore the option list; we can explicitly use the copied + * one from now on. */ + per_upstream_opt_list = qstate->edns_opts_back_out; + qstate->edns_opts_back_out = backed_up_opt_list; + if((client_string_addr = edns_string_addr_lookup( + &env->edns_strings->client_strings, + addr, addrlen))) { + edns_opt_list_append(&per_upstream_opt_list, + env->edns_strings->client_string_opcode, + client_string_addr->string_len, + client_string_addr->string, qstate->region); + } /* add edns */ edns.edns_present = 1; edns.ext_rcode = 0; @@ -1236,15 +1263,9 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, if(dnssec) edns.bits = EDNS_DO; edns.padding_block_size = 0; - if((client_string_addr = edns_string_addr_lookup( - &env->edns_strings->client_strings, - addr, addrlen))) { - edns_opt_list_append(&qstate->edns_opts_back_out, - env->edns_strings->client_string_opcode, - client_string_addr->string_len, - client_string_addr->string, qstate->region); - } - edns.opt_list = qstate->edns_opts_back_out; + edns.opt_list_in = NULL; + edns.opt_list_out = per_upstream_opt_list; + edns.opt_list_inplace_cb_out = NULL; attach_edns_record(pend->buffer, &edns); } memcpy(&pend->addr, addr, addrlen); @@ -1422,6 +1443,11 @@ void pending_udp_timer_cb(void *ATTR_UNUSED(arg)) log_assert(0); } +void serviced_timer_cb(void *ATTR_UNUSED(arg)) +{ + log_assert(0); +} + void pending_udp_timer_delay_cb(void *ATTR_UNUSED(arg)) { log_assert(0); diff --git a/usr.sbin/unbound/testcode/lock_verify.c b/usr.sbin/unbound/testcode/lock_verify.c index 666a7029d64..b0cffe292ae 100644 --- a/usr.sbin/unbound/testcode/lock_verify.c +++ b/usr.sbin/unbound/testcode/lock_verify.c @@ -387,6 +387,37 @@ static void check_order(rbtree_type* all_locks) fprintf(stderr, "\n"); } +/** delete lock ref */ +static void dellockref(rbnode_type* node, void* ATTR_UNUSED(arg)) +{ + struct lock_ref* o = (struct lock_ref*)node; + if(!o) return; + free(o->file); + free(o); +} + +/** delete lock node */ +static void delnode(rbnode_type* node, void* ATTR_UNUSED(arg)) +{ + struct order_lock* o = (struct order_lock*)node; + if(!o) return; + free(o->create_file); + if(o->smaller) { + traverse_postorder(o->smaller, &dellockref, NULL); + free(o->smaller); + } + free(o); +} + +/** delete allocated memory */ +static void locks_free(rbtree_type* all_locks) +{ + if(!all_locks) + return; + traverse_postorder(all_locks, &delnode, NULL); + free(all_locks); +} + /** main program to verify all traces passed */ int main(int argc, char* argv[]) @@ -403,6 +434,7 @@ main(int argc, char* argv[]) usage(); return 1; } + checklock_start(); log_init(NULL, 0, NULL); log_ident_set("lock-verify"); /* init */ @@ -421,6 +453,7 @@ main(int argc, char* argv[]) printf("checked %d locks in %d seconds with %d errors.\n", (int)all_locks->count, (int)(time(NULL)-starttime), errors_detected); + locks_free(all_locks); if(errors_detected) return 1; return 0; } diff --git a/usr.sbin/unbound/testcode/perf.c b/usr.sbin/unbound/testcode/perf.c index b13eca6d394..55d6483c7cf 100644 --- a/usr.sbin/unbound/testcode/perf.c +++ b/usr.sbin/unbound/testcode/perf.c @@ -575,9 +575,9 @@ int main(int argc, char* argv[]) memset(&info, 0, sizeof(info)); info.io_num = 16; + checklock_start(); log_init(NULL, 0, NULL); log_ident_set("perf"); - checklock_start(); #ifdef USE_WINSOCK if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) fatal_exit("WSAStartup failed: %s", wsa_strerror(r)); diff --git a/usr.sbin/unbound/testcode/petal.c b/usr.sbin/unbound/testcode/petal.c index a1a37615518..63d3d452ee3 100644 --- a/usr.sbin/unbound/testcode/petal.c +++ b/usr.sbin/unbound/testcode/petal.c @@ -220,8 +220,11 @@ read_http_headers(SSL* ssl, char* file, size_t flen, char* host, size_t hlen, host[0] = 0; while(read_ssl_line(ssl, buf, sizeof(buf))) { if(verb>=2) printf("read: %s\n", buf); - if(buf[0] == 0) + if(buf[0] == 0) { + int e = ERR_peek_error(); + printf("error string: %s\n", ERR_reason_error_string(e)); return 1; + } if(!process_one_header(buf, file, flen, host, hlen, vs)) return 0; } @@ -241,8 +244,11 @@ setup_ctx(char* key, char* cert) #ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL SSL_CTX_set_security_level(ctx, 0); /* for keys in tests */ #endif - if(!SSL_CTX_use_certificate_chain_file(ctx, cert)) + if(!SSL_CTX_use_certificate_chain_file(ctx, cert)) { + int e = ERR_peek_error(); + printf("error string: %s\n", ERR_reason_error_string(e)); print_exit("cannot read cert"); + } if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) print_exit("cannot read key"); if(!SSL_CTX_check_private_key(ctx)) @@ -576,10 +582,9 @@ do_service(char* addr, int port, char* key, char* cert) { SSL_CTX* sslctx = setup_ctx(key, cert); int fd = setup_fd(addr, port); - int go = 1; if(fd == -1) print_exit("could not setup sockets"); if(verb) {printf("petal start\n"); fflush(stdout);} - while(go) { + while(1) { struct sockaddr_storage from; socklen_t flen = (socklen_t)sizeof(from); int s; diff --git a/usr.sbin/unbound/testcode/streamtcp.c b/usr.sbin/unbound/testcode/streamtcp.c index 2bd076ee5db..ecc83c1cff3 100644 --- a/usr.sbin/unbound/testcode/streamtcp.c +++ b/usr.sbin/unbound/testcode/streamtcp.c @@ -437,8 +437,8 @@ int main(int argc, char** argv) #endif /* lock debug start (if any) */ - log_init(0, 0, 0); checklock_start(); + log_init(0, 0, 0); #ifdef SIGPIPE if(signal(SIGPIPE, &sigh) == SIG_ERR) { diff --git a/usr.sbin/unbound/testcode/testbound.c b/usr.sbin/unbound/testcode/testbound.c index c9290014287..ec627cc8deb 100644 --- a/usr.sbin/unbound/testcode/testbound.c +++ b/usr.sbin/unbound/testcode/testbound.c @@ -374,6 +374,7 @@ main(int argc, char* argv[]) (void)unsetenv("NOTIFY_SOCKET"); #endif /* HAVE_SYSTEMD */ + checklock_start(); log_init(NULL, 0, NULL); /* determine commandline options for the daemon */ pass_argc = 1; @@ -604,3 +605,13 @@ int squelch_err_ssl_handshake(unsigned long ATTR_UNUSED(err)) { return 0; } + +void listen_setup_locks(void) +{ + /* nothing */ +} + +void listen_desetup_locks(void) +{ + /* nothing */ +} diff --git a/usr.sbin/unbound/testcode/unitmain.c b/usr.sbin/unbound/testcode/unitmain.c index c18be7be3a0..16aa8845021 100644 --- a/usr.sbin/unbound/testcode/unitmain.c +++ b/usr.sbin/unbound/testcode/unitmain.c @@ -861,6 +861,7 @@ void ecdsa_evp_workaround_init(void); int main(int argc, char* argv[]) { + checklock_start(); log_init(NULL, 0, NULL); if(argc != 1) { printf("usage: %s\n", argv[0]); @@ -888,7 +889,6 @@ main(int argc, char* argv[]) if(NSS_NoDB_Init(".") != SECSuccess) fatal_exit("could not init NSS"); #endif /* HAVE_SSL or HAVE_NSS*/ - checklock_start(); authzone_test(); neg_test(); rnd_test(); diff --git a/usr.sbin/unbound/util/edns.c b/usr.sbin/unbound/util/edns.c index 84308449c7f..f55dcb97e75 100644 --- a/usr.sbin/unbound/util/edns.c +++ b/usr.sbin/unbound/util/edns.c @@ -128,53 +128,3 @@ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr, return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen); } -static int edns_keepalive(struct edns_data* edns_out, struct edns_data* edns_in, - struct comm_point* c, struct regional* region) -{ - if(c->type == comm_udp) - return 1; - - /* To respond with a Keepalive option, the client connection - * must have received one message with a TCP Keepalive EDNS option, - * and that option must have 0 length data. Subsequent messages - * sent on that connection will have a TCP Keepalive option. - */ - if(c->tcp_keepalive || - edns_opt_list_find(edns_in->opt_list, LDNS_EDNS_KEEPALIVE)) { - int keepalive = c->tcp_timeout_msec / 100; - uint8_t data[2]; - data[0] = (uint8_t)((keepalive >> 8) & 0xff); - data[1] = (uint8_t)(keepalive & 0xff); - if(!edns_opt_list_append(&edns_out->opt_list, LDNS_EDNS_KEEPALIVE, - sizeof(data), data, region)) - return 0; - c->tcp_keepalive = 1; - } - return 1; -} - -int apply_edns_options(struct edns_data* edns_out, struct edns_data* edns_in, - struct config_file* cfg, struct comm_point* c, struct regional* region) -{ - if(cfg->do_tcp_keepalive && - !edns_keepalive(edns_out, edns_in, c, region)) - return 0; - - if (cfg->nsid && edns_opt_list_find(edns_in->opt_list, LDNS_EDNS_NSID) - && !edns_opt_list_append(&edns_out->opt_list, - LDNS_EDNS_NSID, cfg->nsid_len, cfg->nsid, region)) - return 0; - - if(!cfg->pad_responses || c->type != comm_tcp || !c->ssl - || !edns_opt_list_find(edns_in->opt_list, LDNS_EDNS_PADDING)) { - ; /* pass */ - } - - else if(!edns_opt_list_append(&edns_out->opt_list, LDNS_EDNS_PADDING - , 0, NULL, region)) - return 0; - else - edns_out->padding_block_size = cfg->pad_responses_block_size; - - return 1; -} diff --git a/usr.sbin/unbound/util/edns.h b/usr.sbin/unbound/util/edns.h index 11742eb5b72..d9ded0b84dc 100644 --- a/usr.sbin/unbound/util/edns.h +++ b/usr.sbin/unbound/util/edns.h @@ -106,16 +106,4 @@ struct edns_string_addr* edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr, socklen_t addrlen); -/** - * Apply common EDNS options. - * - * @param edns_out: initialised edns information with outbound edns. - * @param edns_in: initialised edns information with received edns. - * @param cfg: configuration. - * @param c: comm channel. - * @param region: the region to allocate the edns options in. - */ -int apply_edns_options(struct edns_data* edns_out, struct edns_data* edns_in, - struct config_file* cfg, struct comm_point* c, struct regional* region); - #endif diff --git a/usr.sbin/unbound/util/regional.c b/usr.sbin/unbound/util/regional.c index bd67ecf50af..93e911c5ec1 100644 --- a/usr.sbin/unbound/util/regional.c +++ b/usr.sbin/unbound/util/regional.c @@ -103,6 +103,8 @@ regional_create_custom_large_object(size_t size, size_t large_object_size) struct regional* regional_create_custom(size_t size) { + if(size < sizeof(struct regional)) + size = sizeof(struct regional); return regional_create_custom_large_object(size, REGIONAL_LARGE_OBJECT_SIZE); } -- 2.20.1