/* * services/rpz.c - rpz service * * Copyright (c) 2019, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable RPZ service. */ #include "config.h" #include "services/rpz.h" #include "util/config_file.h" #include "sldns/wire2str.h" #include "sldns/str2wire.h" #include "util/data/dname.h" #include "util/net_help.h" #include "util/log.h" #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 "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"; } } /** 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; else return RPZ_INVALID_ACTION; } /** string for RPZ trigger enum */ static const char* rpz_trigger_to_string(enum rpz_trigger r) { switch(r) { 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"; } } /** * Get the label that is just before the root label. * @param dname: dname to work on * @param maxdnamelen: maximum length of the dname * @return: pointer to TLD label, NULL if not found or invalid dname */ static uint8_t* get_tld_label(uint8_t* dname, size_t maxdnamelen) { uint8_t* prevlab = dname; size_t dnamelen = 0; /* one byte needed for label length */ if(dnamelen+1 > maxdnamelen) return NULL; /* only root label */ if(*dname == 0) return NULL; while(*dname) { dnamelen += ((size_t)*dname)+1; if(dnamelen+1 > maxdnamelen) return NULL; dname = dname+((size_t)*dname)+1; if(*dname != 0) prevlab = dname; } 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 * @param rdatawl: RDATA with 2 bytes length * @param rdatalen: the length of rdatawl (including its 2 bytes length) * @return: the RPZ action */ static enum rpz_action rpz_rr_to_action(uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { char* endptr; uint8_t* rdata; int rdatalabs; uint8_t* tldlab = NULL; 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 RPZ_INVALID_ACTION; case LDNS_RR_TYPE_CNAME: break; default: return RPZ_LOCAL_DATA_ACTION; } /* use CNAME target to determine RPZ action */ log_assert(rr_type == LDNS_RR_TYPE_CNAME); if(rdatalen < 3) return RPZ_INVALID_ACTION; rdata = rdatawl + 2; /* 2 bytes of rdata length */ if(dname_valid(rdata, rdatalen-2) != rdatalen-2) return RPZ_INVALID_ACTION; rdatalabs = dname_count_labels(rdata); if(rdatalabs == 1) return RPZ_NXDOMAIN_ACTION; else if(rdatalabs == 2) { if(dname_subdomain_c(rdata, (uint8_t*)&"\001*\000")) return RPZ_NODATA_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\014rpz-passthru\000")) return RPZ_PASSTHRU_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\010rpz-drop\000")) return RPZ_DROP_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\014rpz-tcp-only\000")) return RPZ_TCP_ONLY_ACTION; } /* all other TLDs starting with "rpz-" are invalid */ tldlab = get_tld_label(rdata, rdatalen-2); if(tldlab && dname_lab_startswith(tldlab, "rpz-", &endptr)) return RPZ_INVALID_ACTION; /* no special label found */ return RPZ_LOCAL_DATA_ACTION; } 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_LOCAL_DATA_ACTION: /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect; case RPZ_TCP_ONLY_ACTION: return local_zone_truncate; case RPZ_INVALID_ACTION: /* fallthrough */ default: return local_zone_invalid; } } 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_CNAME_OVERRIDE_ACTION: return respip_redirect; case RPZ_TCP_ONLY_ACTION: return respip_truncate; case RPZ_INVALID_ACTION: /* fallthrough */ default: return respip_invalid; } } 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_truncate: return RPZ_TCP_ONLY_ACTION; case local_zone_invalid: /* fallthrough */ default: return RPZ_INVALID_ACTION; } } 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_truncate: return RPZ_TCP_ONLY_ACTION; case respip_invalid: /* fallthrough */ default: return RPZ_INVALID_ACTION; } } /** * Get RPZ trigger for dname * @param dname: dname containing RPZ trigger * @param dname_len: length of the dname * @return: RPZ trigger enum */ static enum rpz_trigger rpz_dname_to_trigger(uint8_t* dname, size_t dname_len) { uint8_t* tldlab; char* endptr; if(dname_valid(dname, dname_len) != dname_len) return RPZ_INVALID_TRIGGER; tldlab = get_tld_label(dname, dname_len); if(!tldlab || !dname_lab_startswith(tldlab, "rpz-", &endptr)) return RPZ_QNAME_TRIGGER; if(dname_subdomain_c(tldlab, (uint8_t*)&"\015rpz-client-ip\000")) return RPZ_CLIENT_IP_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\006rpz-ip\000")) return RPZ_RESPONSE_IP_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\013rpz-nsdname\000")) return RPZ_NSDNAME_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\010rpz-nsip\000")) return RPZ_NSIP_TRIGGER; return RPZ_QNAME_TRIGGER; } 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); free(r); } int 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; } void 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 */ static struct ub_packed_rrset_key* new_cname_override(struct regional* region, uint8_t* ct, size_t ctlen) { struct ub_packed_rrset_key* rrset; struct packed_rrset_data* pd; uint16_t rdlength = htons(ctlen); rrset = (struct ub_packed_rrset_key*)regional_alloc_zero(region, sizeof(*rrset)); if(!rrset) { log_err("out of memory"); return NULL; } rrset->entry.key = rrset; pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); if(!pd) { log_err("out of memory"); return NULL; } pd->trust = rrset_trust_prim_noglue; pd->security = sec_status_insecure; pd->count = 1; pd->rr_len = regional_alloc_zero(region, sizeof(*pd->rr_len)); pd->rr_ttl = regional_alloc_zero(region, sizeof(*pd->rr_ttl)); pd->rr_data = regional_alloc_zero(region, sizeof(*pd->rr_data)); if(!pd->rr_len || !pd->rr_ttl || !pd->rr_data) { log_err("out of memory"); return NULL; } pd->rr_len[0] = ctlen+2; pd->rr_ttl[0] = 3600; pd->rr_data[0] = regional_alloc_zero(region, 2 /* rdlength */ + ctlen); if(!pd->rr_data[0]) { log_err("out of memory"); return NULL; } memmove(pd->rr_data[0], &rdlength, 2); memmove(pd->rr_data[0]+2, ct, ctlen); rrset->entry.data = pd; rrset->rk.type = htons(LDNS_RR_TYPE_CNAME); rrset->rk.rrset_class = htons(LDNS_RR_CLASS_IN); return rrset; } struct rpz* rpz_create(struct config_auth* p) { struct rpz* r = calloc(1, sizeof(*r)); if(!r) goto err; r->region = regional_create_custom(sizeof(struct regional)); if(!r->region) { goto err; } 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) { r->action_override = rpz_config_to_action(p->rpz_action_override); } else r->action_override = RPZ_NO_OVERRIDE_ACTION; if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { uint8_t nm[LDNS_MAX_DOMAINLEN+1]; size_t nmlen = sizeof(nm); if(!p->rpz_cname) { log_err("rpz: override with cname action found, but no " "rpz-cname-override configured"); goto err; } if(sldns_str2wire_dname_buf(p->rpz_cname, nm, &nmlen) != 0) { log_err("rpz: cannot parse cname override: %s", p->rpz_cname); goto err; } r->cname_override = new_cname_override(r->region, nm, nmlen); if(!r->cname_override) { goto err; } } 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"); goto err; } } return r; 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) regional_destroy(r->region); free(r); } return NULL; } /** * Remove RPZ zone name from dname * Copy dname to newdname, without the originlen number of trailing bytes */ static size_t strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen, uint8_t* newdname, size_t maxnewdnamelen) { size_t newdnamelen; if(dnamelen < originlen) return 0; newdnamelen = dnamelen - originlen; if(newdnamelen+1 > maxnewdnamelen) return 0; memmove(newdname, dname, newdnamelen); newdname[newdnamelen] = 0; return newdnamelen + 1; /* + 1 for root label */ } static void 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); int newzone = 0; 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 || rrtype == LDNS_RR_TYPE_DNSKEY || rrtype == LDNS_RR_TYPE_RRSIG || rrtype == LDNS_RR_TYPE_NSEC || rrtype == LDNS_RR_TYPE_NSEC3PARAM || rrtype == LDNS_RR_TYPE_NSEC3 || rrtype == LDNS_RR_TYPE_DS) { free(dname); return; /* no need to log these types as unsupported */ } dname_str(dname, str); verbose(VERB_ALGO, "rpz: qname trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); free(dname); return; } lock_rw_wrlock(&lz->lock); /* exact match */ 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(&lz->lock); return; } 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(&lz->lock); return; } if(z == NULL) { tp = rpz_action_to_localzone_type(a); 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) { 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(&lz->lock); return; } lock_rw_wrlock(&z->lock); local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype, rrclass, ttl, rdata, rdata_len, rrstr); lock_rw_unlock(&z->lock); free(rrstr); } if(!newzone) { free(dname); } lock_rw_unlock(&lz->lock); } 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) { if(a == RPZ_INVALID_ACTION) { verbose(VERB_ALGO, "rpz: skipping invalid action"); free(dname); return; } 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; } 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; 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 == NULL) { log_err("malloc error while inserting rpz ipaddr based trigger"); lock_rw_unlock(&set->lock); return 0; } 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(&set->lock); node->action = respa; if(a == RPZ_LOCAL_DATA_ACTION) { respip_enter_rr(set->region, node, rrtype, rrclass, ttl, rdata, rdata_len, rrstr, ""); } lock_rw_unlock(&node->lock); free(rrstr); return 1; } static inline struct clientip_synthesized_rr* rpz_clientip_ensure_entry(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, int net) { int insert_ok; struct clientip_synthesized_rr* node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, addr, addrlen, net); 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; } 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; } 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; } 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; } 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; } 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; } 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; } rrset->rrset->rk.dname_len = 1; return rrset; } 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) { 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; } rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass); if(raddr->data == NULL) { return 0; } return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, rdata_len, ttl, ""); } 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; 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(&zones->lock); } return NULL; } wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, ce, ce_len); lock_rw_unlock(&z->lock); if(!zones_keep_lock) { if(wr) { lock_rw_wrlock(&zones->lock); } else { lock_rw_rdlock(&zones->lock); } } z = local_zones_find_le(zones, wc, ce_len+2, ce_labs+1, qclass, &exact); if(!z || !exact) { 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); } return z; } /** Find entry for RR type in the list of rrsets for the clientip. */ static struct local_rrset* rpz_find_synthesized_rrset(uint16_t 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; } /** * 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: length of policydname * @param rr_type: RR type of RR to remove * @param rdata: rdata of RR to remove * @param rdatalen: length of rdata * @return: 1 if zone must be removed after RR deletion */ static int rpz_data_delete_rr(struct local_zone* z, uint8_t* policydname, size_t policydnamelen, uint16_t rr_type, uint8_t* rdata, size_t rdatalen) { struct local_data* ld; struct packed_rrset_data* d; size_t index; ld = local_zone_find_data(z, policydname, policydnamelen, dname_count_labels(policydname)); if(ld) { struct local_rrset* prev=NULL, *p=ld->rrsets; while(p && ntohs(p->rrset->rk.type) != rr_type) { prev = p; p = p->next; } if(!p) return 0; d = (struct packed_rrset_data*)p->rrset->entry.data; if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { if(d->count == 1) { /* no memory recycling for zone deletions ... */ if(prev) prev->next = p->next; else ld->rrsets = p->next; } if(d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } } } if(ld && ld->rrsets) return 0; return 1; } /** * Remove RR from RPZ's respip set * @param raddr: respip node * @param rr_type: RR type of RR to remove * @param rdata: rdata of RR to remove * @param rdatalen: length of rdata * @return: 1 if zone must be removed after RR deletion */ static int rpz_rrset_delete_rr(struct resp_addr* raddr, uint16_t rr_type, uint8_t* rdata, size_t rdatalen) { size_t index; struct packed_rrset_data* d; if(!raddr->data) return 1; d = raddr->data->entry.data; if(ntohs(raddr->data->rk.type) != rr_type) { return 0; } if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { if(d->count == 1) { /* regional alloc'd */ raddr->data->entry.data = NULL; raddr->data = NULL; return 1; } if(d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } } return 0; } /** Remove RR from rpz localzones structure */ static void rpz_remove_local_zones_trigger(struct local_zones* zones, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { struct local_zone* z; int delete_zone = 1; z = rpz_find_zone(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, " "RPZ domain not found"); return; } if(a == RPZ_LOCAL_DATA_ACTION) delete_zone = rpz_data_delete_rr(z, dname, dnamelen, rr_type, rdatawl, rdatalen); else if(a != localzone_type_to_rpz_action(z->type)) { lock_rw_unlock(&z->lock); lock_rw_unlock(&zones->lock); return; } lock_rw_unlock(&z->lock); if(delete_zone) { local_zones_del_zone(zones, z); } lock_rw_unlock(&zones->lock); } /** Remove RR from RPZ's local-zone */ static void rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { rpz_remove_local_zones_trigger(r->local_zones, dname, dnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } static void rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct resp_addr* node; struct sockaddr_storage addr; socklen_t addrlen; int net, af; int delete_respip = 1; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; 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, " "RPZ domain not found"); lock_rw_unlock(&r->respip_set->lock); return; } lock_rw_wrlock(&node->lock); if(a == RPZ_LOCAL_DATA_ACTION) { /* remove RR, signal whether RR can be removed */ delete_respip = rpz_rrset_delete_rr(node, rr_type, rdatawl, rdatalen); } lock_rw_unlock(&node->lock); if(delete_respip) respip_sockaddr_delete(r->respip_set, node); lock_rw_unlock(&r->respip_set->lock); } /** find and remove type from list of local_rrset entries*/ static void del_local_rrset_from_list(struct local_rrset** list_head, uint16_t dtype) { struct local_rrset* prev=NULL, *p=*list_head; while(p && ntohs(p->rrset->rk.type) != dtype) { prev = p; p = p->next; } if(!p) return; /* rrset type not found */ /* unlink it */ if(prev) prev->next = p->next; else *list_head = p->next; /* no memory recycling for zone deletions ... */ } /** Delete client-ip trigger RR from its RRset and perhaps also the rrset * from the linked list. Returns if the local data is empty and the node can * be deleted too, or not. */ static int rpz_remove_clientip_rr(struct clientip_synthesized_rr* node, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct local_rrset* rrset; struct packed_rrset_data* d; size_t index; rrset = rpz_find_synthesized_rrset(rr_type, node); if(rrset == NULL) return 0; /* type not found, ignore */ d = (struct packed_rrset_data*)rrset->rrset->entry.data; if(!packed_rrset_find_rr(d, rdatawl, rdatalen, &index)) return 0; /* RR not found, ignore */ if(d->count == 1) { /* regional alloc'd */ /* delete the type entry from the list */ del_local_rrset_from_list(&node->data, rr_type); /* if the list is empty, the node can be removed too */ if(node->data == NULL) return 1; } else if (d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } return 0; } /** remove trigger RR from clientip_syntheized set tree. */ static void rpz_clientip_remove_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, int net, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct clientip_synthesized_rr* node; int delete_node = 1; lock_rw_wrlock(&set->lock); node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, addr, addrlen, net); if(node == NULL) { /* netblock not found */ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ address, netblock not found"); lock_rw_unlock(&set->lock); return; } lock_rw_wrlock(&node->lock); if(a == RPZ_LOCAL_DATA_ACTION) { /* remove RR, signal whether entry can be removed */ delete_node = rpz_remove_clientip_rr(node, rr_type, rdatawl, rdatalen); } else if(a != node->action) { /* ignore the RR with different action specification */ delete_node = 0; } if(delete_node) { rbtree_delete(&set->entries, node->node.node.key); } lock_rw_unlock(&set->lock); lock_rw_unlock(&node->lock); if(delete_node) { lock_rw_destroy(&node->lock); } } /** Remove clientip trigger RR from RPZ. */ static void rpz_remove_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) return; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; rpz_clientip_remove_trigger_rr(r->client_set, &addr, addrlen, net, a, rr_type, rdatawl, rdatalen); } /** Remove nsip trigger RR from RPZ. */ static void rpz_remove_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) return; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; rpz_clientip_remove_trigger_rr(r->ns_set, &addr, addrlen, net, a, rr_type, rdatawl, rdatalen); } /** Remove nsdname trigger RR from RPZ. */ static void rpz_remove_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { uint8_t* dname_stripped = NULL; size_t dnamelen_stripped = 0; if(a == RPZ_INVALID_ACTION) return; if(!rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, &dnamelen_stripped)) return; rpz_remove_local_zones_trigger(r->nsdname_zones, dname_stripped, dnamelen_stripped, a, rr_type, rr_class, rdatawl, rdatalen); free(dname_stripped); } void rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { size_t policydnamelen; 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; } if(!dname_subdomain_c(dname, azname)) { /* not subdomain of the RPZ zone. */ return; } if(!(policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1))) return; a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); if(a == RPZ_INVALID_ACTION) { free(policydname); return; } if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, policydname, LDNS_MAX_DOMAINLEN + 1))) { free(policydname); return; } t = rpz_dname_to_trigger(policydname, policydnamelen); if(t == RPZ_INVALID_TRIGGER) { /* skipping invalid trigger */ free(policydname); return; } if(t == RPZ_QNAME_TRIGGER) { rpz_remove_qname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } else if(t == RPZ_RESPONSE_IP_TRIGGER) { rpz_remove_response_ip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_CLIENT_IP_TRIGGER) { rpz_remove_clientip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_NSIP_TRIGGER) { rpz_remove_nsip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_NSDNAME_TRIGGER) { rpz_remove_nsdname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } /* else it was an unsupported trigger, also skipped. */ free(policydname); } /** 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(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], portstr[32]; char dnamestr[LDNS_MAX_DOMAINLEN+1]; 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->client_addr, repinfo->client_addrlen, ip, sizeof(ip)); port = ntohs(((struct sockaddr_in*)&repinfo->client_addr)->sin_port); } else if(ms && ms->mesh_info && ms->mesh_info->reply_list) { addr_to_str(&ms->mesh_info->reply_list->query_reply.client_addr, ms->mesh_info->reply_list->query_reply.client_addrlen, ip, sizeof(ip)); port = ntohs(((struct sockaddr_in*)&ms->mesh_info->reply_list->query_reply.client_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); } 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 local_zone* z = 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) { 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->client_addr, repinfo->client_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(node != NULL) { lock_rw_unlock(&node->lock); node = NULL; } } if(z || node) { break; } /* not found in this auth_zone */ lock_rw_unlock(&a->lock); } lock_rw_unlock(&az->rpz_lock); *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; } /** 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, LDNS_EDE_NONE); 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, LDNS_EDE_NONE); 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, LDNS_EDE_NONE); 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 == 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 == 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("qname", z->name, NULL, RPZ_CNAME_OVERRIDE_ACTION, qinfo, repinfo, NULL, r->log_name); } stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; 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("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 !qinfo->local_alias; } ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, lzt); 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->tcp_required = 1; 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; ms->rpz_passthru = 1; 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->tcp_required = 1; 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; ms->rpz_passthru = 1; 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->rpz_passthru) { verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); return NULL; } 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; if(raddr != NULL) { if(z) { lock_rw_unlock(&z->lock); } return rpz_apply_nsip_trigger(ms, r, raddr, a); } return rpz_apply_nsdname_trigger(ms, r, z, &match, a); } 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->rpz_passthru) { verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); return 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->tcp_required = 1; 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; ms->rpz_passthru = 1; 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; } if(r->log) log_rpz_apply("qname", (z?z->name:NULL), NULL, localzone_type_to_rpz_action(lzt), &is->qchase, NULL, ms, r->log_name); 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* passthru) { 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(client_action == RPZ_PASSTHRU_ACTION) { *passthru = 1; } 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, int* passthru) { 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, passthru); 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(r->action_override == RPZ_PASSTHRU_ACTION || lzt == local_zone_always_transparent /* RPZ_PASSTHRU_ACTION */) { *passthru = 1; } 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); return ret; } void rpz_enable(struct rpz* r) { if(!r) return; r->disabled = 0; } void rpz_disable(struct rpz* r) { if(!r) return; r->disabled = 1; }