diff options
Diffstat (limited to 'external/unbound/respip/respip.c')
-rw-r--r-- | external/unbound/respip/respip.c | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/external/unbound/respip/respip.c b/external/unbound/respip/respip.c new file mode 100644 index 000000000..d71325111 --- /dev/null +++ b/external/unbound/respip/respip.c @@ -0,0 +1,1179 @@ +/* + * respip/respip.c - filtering response IP module + */ + +/** + * \file + * + * This file contains a module that inspects a result of recursive resolution + * to see if any IP address record should trigger a special action. + * If applicable these actions can modify the original response. + */ +#include "config.h" + +#include "services/localzone.h" +#include "services/cache/dns.h" +#include "sldns/str2wire.h" +#include "util/config_file.h" +#include "util/fptr_wlist.h" +#include "util/module.h" +#include "util/net_help.h" +#include "util/regional.h" +#include "util/data/msgreply.h" +#include "util/storage/dnstree.h" +#include "respip/respip.h" +#include "services/view.h" +#include "sldns/rrdef.h" + +/** + * Conceptual set of IP addresses for response AAAA or A records that should + * trigger special actions. + */ +struct respip_set { + struct regional* region; + struct rbtree_type ip_tree; + char* const* tagname; /* shallow copy of tag names, for logging */ + int num_tags; /* number of tagname entries */ +}; + +/** An address span with response control information */ +struct resp_addr { + /** node in address tree */ + struct addr_tree_node node; + /** tag bitlist */ + uint8_t* taglist; + /** length of the taglist (in bytes) */ + size_t taglen; + /** action for this address span */ + enum respip_action action; + /** "local data" for this node */ + struct ub_packed_rrset_key* data; +}; + +/** Subset of resp_addr.node, used for inform-variant logging */ +struct respip_addr_info { + struct sockaddr_storage addr; + socklen_t addrlen; + int net; +}; + +/** Query state regarding the response-ip module. */ +enum respip_state { + /** + * The general state. Unless CNAME chasing takes place, all processing + * is completed in this state without any other asynchronous event. + */ + RESPIP_INIT = 0, + + /** + * A subquery for CNAME chasing is completed. + */ + RESPIP_SUBQUERY_FINISHED +}; + +/** Per query state for the response-ip module. */ +struct respip_qstate { + enum respip_state state; +}; + +struct respip_set* +respip_set_create(void) +{ + struct respip_set* set = calloc(1, sizeof(*set)); + if(!set) + return NULL; + set->region = regional_create(); + if(!set->region) { + free(set); + return NULL; + } + addr_tree_init(&set->ip_tree); + return set; +} + +void +respip_set_delete(struct respip_set* set) +{ + if(!set) + return; + regional_destroy(set->region); + free(set); +} + +struct rbtree_type* +respip_set_get_tree(struct respip_set* set) +{ + if(!set) + return NULL; + return &set->ip_tree; +} + +/** returns the node in the address tree for the specified netblock string; + * non-existent node will be created if 'create' is true */ +static struct resp_addr* +respip_find_or_create(struct respip_set* set, const char* ipstr, int create) +{ + struct resp_addr* node; + struct sockaddr_storage addr; + int net; + socklen_t addrlen; + + if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) { + log_err("cannot parse netblock: '%s'", ipstr); + return NULL; + } + node = (struct resp_addr*)addr_tree_find(&set->ip_tree, &addr, addrlen, net); + if(!node && create) { + node = regional_alloc_zero(set->region, sizeof(*node)); + if(!node) { + log_err("out of memory"); + return NULL; + } + node->action = respip_none; + if(!addr_tree_insert(&set->ip_tree, &node->node, &addr, + addrlen, net)) { + /* We know we didn't find it, so this should be + * impossible. */ + log_warn("unexpected: duplicate address: %s", ipstr); + } + } + return node; +} + +static int +respip_tag_cfg(struct respip_set* set, const char* ipstr, + const uint8_t* taglist, size_t taglen) +{ + struct resp_addr* node; + + if(!(node=respip_find_or_create(set, ipstr, 1))) + return 0; + if(node->taglist) { + log_warn("duplicate response-address-tag for '%s', overridden.", + ipstr); + } + node->taglist = regional_alloc_init(set->region, taglist, taglen); + if(!node->taglist) { + log_err("out of memory"); + return 0; + } + node->taglen = taglen; + return 1; +} + +/** set action for the node specified by the netblock string */ +static int +respip_action_cfg(struct respip_set* set, const char* ipstr, + const char* actnstr) +{ + struct resp_addr* node; + enum respip_action action; + + if(!(node=respip_find_or_create(set, ipstr, 1))) + return 0; + if(node->action != respip_none) { + log_warn("duplicate response-ip action for '%s', overridden.", + ipstr); + } + if(strcmp(actnstr, "deny") == 0) + action = respip_deny; + else if(strcmp(actnstr, "redirect") == 0) + action = respip_redirect; + else if(strcmp(actnstr, "inform") == 0) + action = respip_inform; + else if(strcmp(actnstr, "inform_deny") == 0) + action = respip_inform_deny; + else if(strcmp(actnstr, "always_transparent") == 0) + action = respip_always_transparent; + else if(strcmp(actnstr, "always_refuse") == 0) + action = respip_always_refuse; + else if(strcmp(actnstr, "always_nxdomain") == 0) + action = respip_always_nxdomain; + else { + log_err("unknown response-ip action %s", actnstr); + return 0; + } + node->action = action; + return 1; +} + +/** allocate and initialize an rrset structure; this function is based + * on new_local_rrset() from the localzone.c module */ +static struct ub_packed_rrset_key* +new_rrset(struct regional* region, uint16_t rrtype, uint16_t rrclass) +{ + struct packed_rrset_data* pd; + struct ub_packed_rrset_key* rrset = regional_alloc_zero( + region, sizeof(*rrset)); + if(!rrset) { + log_err("out of memory"); + return NULL; + } + rrset->entry.key = rrset; + pd = 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; + rrset->entry.data = pd; + rrset->rk.dname = regional_alloc_zero(region, 1); + if(!rrset->rk.dname) { + log_err("out of memory"); + return NULL; + } + rrset->rk.dname_len = 1; + rrset->rk.type = htons(rrtype); + rrset->rk.rrset_class = htons(rrclass); + return rrset; +} + +/** enter local data as resource records into a response-ip node */ +static int +respip_enter_rr(struct regional* region, struct resp_addr* raddr, + const char* rrstr, const char* netblock) +{ + uint8_t* nm; + uint16_t rrtype = 0, rrclass = 0; + time_t ttl = 0; + uint8_t rr[LDNS_RR_BUF_SIZE]; + uint8_t* rdata = NULL; + size_t rdata_len = 0; + char buf[65536]; + char bufshort[64]; + struct packed_rrset_data* pd; + struct sockaddr* sa; + int ret; + if(raddr->action != respip_redirect) { + log_err("cannot parse response-ip-data %s: response-ip " + "action for %s is not redirect", rrstr, netblock); + return 0; + } + ret = snprintf(buf, sizeof(buf), ". %s", rrstr); + if(ret < 0 || ret >= (int)sizeof(buf)) { + strlcpy(bufshort, rrstr, sizeof(bufshort)); + log_err("bad response-ip-data: %s...", bufshort); + return 0; + } + if(!rrstr_get_rr_content(buf, &nm, &rrtype, &rrclass, &ttl, rr, sizeof(rr), + &rdata, &rdata_len)) { + log_err("bad response-ip-data: %s", rrstr); + return 0; + } + sa = (struct sockaddr*)&raddr->node.addr; + if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data) { + log_err("CNAME response-ip data (%s) can not co-exist with other " + "response-ip data for netblock %s", rrstr, netblock); + return 0; + } else if (raddr->data && + raddr->data->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + log_err("response-ip data (%s) can not be added; CNAME response-ip " + "data already in place for netblock %s", rrstr, netblock); + return 0; + } else if((rrtype != LDNS_RR_TYPE_CNAME) && + ((sa->sa_family == AF_INET && rrtype != LDNS_RR_TYPE_A) || + (sa->sa_family == AF_INET6 && rrtype != LDNS_RR_TYPE_AAAA))) { + log_err("response-ip data %s record type does not correspond " + "to netblock %s address family", rrstr, netblock); + return 0; + } + + if(!raddr->data) { + raddr->data = new_rrset(region, rrtype, rrclass); + if(!raddr->data) + return 0; + } + pd = raddr->data->entry.data; + return rrset_insert_rr(region, pd, rdata, rdata_len, ttl, rrstr); +} + +static int +respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr) +{ + struct resp_addr* node; + + node=respip_find_or_create(set, ipstr, 0); + if(!node || node->action == respip_none) { + log_err("cannot parse response-ip-data %s: " + "response-ip node for %s not found", rrstr, ipstr); + return 0; + } + return respip_enter_rr(set->region, node, rrstr, ipstr); +} + +static int +respip_set_apply_cfg(struct respip_set* set, char* const* tagname, int num_tags, + struct config_strbytelist* respip_tags, + struct config_str2list* respip_actions, + struct config_str2list* respip_data) +{ + struct config_strbytelist* p; + struct config_str2list* pa; + struct config_str2list* pd; + + set->tagname = tagname; + set->num_tags = num_tags; + + p = respip_tags; + while(p) { + struct config_strbytelist* np = p->next; + + log_assert(p->str && p->str2); + if(!respip_tag_cfg(set, p->str, p->str2, p->str2len)) { + config_del_strbytelist(p); + return 0; + } + free(p->str); + free(p->str2); + free(p); + p = np; + } + + pa = respip_actions; + while(pa) { + struct config_str2list* np = pa->next; + log_assert(pa->str && pa->str2); + if(!respip_action_cfg(set, pa->str, pa->str2)) { + config_deldblstrlist(pa); + return 0; + } + free(pa->str); + free(pa->str2); + free(pa); + pa = np; + } + + pd = respip_data; + while(pd) { + struct config_str2list* np = pd->next; + log_assert(pd->str && pd->str2); + if(!respip_data_cfg(set, pd->str, pd->str2)) { + config_deldblstrlist(pd); + return 0; + } + free(pd->str); + free(pd->str2); + free(pd); + pd = np; + } + + return 1; +} + +int +respip_global_apply_cfg(struct respip_set* set, struct config_file* cfg) +{ + int ret = respip_set_apply_cfg(set, cfg->tagname, cfg->num_tags, + cfg->respip_tags, cfg->respip_actions, cfg->respip_data); + cfg->respip_data = NULL; + cfg->respip_actions = NULL; + cfg->respip_tags = NULL; + return ret; +} + +/** Iterate through raw view data and apply the view-specific respip + * configuration; at this point we should have already seen all the views, + * so if any of the views that respip data refer to does not exist, that's + * an error. This additional iteration through view configuration data + * is expected to not have significant performance impact (or rather, its + * performance impact is not expected to be prohibitive in the configuration + * processing phase). + */ +int +respip_views_apply_cfg(struct views* vs, struct config_file* cfg, + int* have_view_respip_cfg) +{ + struct config_view* cv; + struct view* v; + int ret; + + for(cv = cfg->views; cv; cv = cv->next) { + + /** if no respip config for this view then there's + * nothing to do; note that even though respip data must go + * with respip action, we're checking for both here because + * we want to catch the case where the respip action is missing + * while the data is present */ + if(!cv->respip_actions && !cv->respip_data) + continue; + + if(!(v = views_find_view(vs, cv->name, 1))) { + log_err("view '%s' unexpectedly missing", cv->name); + return 0; + } + if(!v->respip_set) { + v->respip_set = respip_set_create(); + if(!v->respip_set) { + log_err("out of memory"); + lock_rw_unlock(&v->lock); + return 0; + } + } + ret = respip_set_apply_cfg(v->respip_set, NULL, 0, NULL, + cv->respip_actions, cv->respip_data); + lock_rw_unlock(&v->lock); + if(!ret) { + log_err("Error while applying respip configuration " + "for view '%s'", cv->name); + return 0; + } + *have_view_respip_cfg = (*have_view_respip_cfg || + v->respip_set->ip_tree.count); + cv->respip_actions = NULL; + cv->respip_data = NULL; + } + return 1; +} + +/** + * make a deep copy of 'key' in 'region'. + * This is largely derived from packed_rrset_copy_region() and + * packed_rrset_ptr_fixup(), but differs in the following points: + * + * - It doesn't assume all data in 'key' are in a contiguous memory region. + * Although that would be the case in most cases, 'key' can be passed from + * a lower-level module and it might not build the rrset to meet the + * assumption. In fact, an rrset specified as response-ip-data or generated + * in local_data_find_tag_datas() breaks the assumption. So it would be + * safer not to naively rely on the assumption. On the other hand, this + * function ensures the copied rrset data are in a contiguous region so + * that it won't cause a disruption even if an upper layer module naively + * assumes the memory layout. + * - It doesn't copy RRSIGs (if any) in 'key'. The rrset will be used in + * a reply that was already faked, so it doesn't make much sense to provide + * partial sigs even if they are valid themselves. + * - It doesn't adjust TTLs as it basically has to be a verbatim copy of 'key' + * just allocated in 'region' (the assumption is necessary TTL adjustment + * has been already done in 'key'). + * + * This function returns the copied rrset key on success, and NULL on memory + * allocation failure. + */ +struct ub_packed_rrset_key* +copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region) +{ + struct ub_packed_rrset_key* ck = regional_alloc(region, + sizeof(struct ub_packed_rrset_key)); + struct packed_rrset_data* d; + struct packed_rrset_data* data = key->entry.data; + size_t dsize, i; + uint8_t* nextrdata; + + /* derived from packed_rrset_copy_region(), but don't use + * packed_rrset_sizeof() and do exclude RRSIGs */ + if(!ck) + return NULL; + ck->id = key->id; + memset(&ck->entry, 0, sizeof(ck->entry)); + ck->entry.hash = key->entry.hash; + ck->entry.key = ck; + ck->rk = key->rk; + ck->rk.dname = regional_alloc_init(region, key->rk.dname, + key->rk.dname_len); + if(!ck->rk.dname) + return NULL; + + dsize = sizeof(struct packed_rrset_data) + data->count * + (sizeof(size_t)+sizeof(uint8_t*)+sizeof(time_t)); + for(i=0; i<data->count; i++) + dsize += data->rr_len[i]; + d = regional_alloc(region, dsize); + if(!d) + return NULL; + *d = *data; + d->rrsig_count = 0; + ck->entry.data = d; + + /* derived from packed_rrset_ptr_fixup() with copying the data */ + d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); + d->rr_data = (uint8_t**)&(d->rr_len[d->count]); + d->rr_ttl = (time_t*)&(d->rr_data[d->count]); + nextrdata = (uint8_t*)&(d->rr_ttl[d->count]); + for(i=0; i<d->count; i++) { + d->rr_len[i] = data->rr_len[i]; + d->rr_ttl[i] = data->rr_ttl[i]; + d->rr_data[i] = nextrdata; + memcpy(d->rr_data[i], data->rr_data[i], data->rr_len[i]); + nextrdata += d->rr_len[i]; + } + + return ck; +} + +int +respip_init(struct module_env* env, int id) +{ + (void)env; + (void)id; + return 1; +} + +void +respip_deinit(struct module_env* env, int id) +{ + (void)env; + (void)id; +} + +/** Convert a packed AAAA or A RRset to sockaddr. */ +static int +rdata2sockaddr(const struct packed_rrset_data* rd, uint16_t rtype, size_t i, + struct sockaddr_storage* ss, socklen_t* addrlenp) +{ + /* unbound can accept and cache odd-length AAAA/A records, so we have + * to validate the length. */ + if(rtype == LDNS_RR_TYPE_A && rd->rr_len[i] == 6) { + struct sockaddr_in* sa4 = (struct sockaddr_in*)ss; + + memset(sa4, 0, sizeof(*sa4)); + sa4->sin_family = AF_INET; + memcpy(&sa4->sin_addr, rd->rr_data[i] + 2, + sizeof(sa4->sin_addr)); + *addrlenp = sizeof(*sa4); + return 1; + } else if(rtype == LDNS_RR_TYPE_AAAA && rd->rr_len[i] == 18) { + struct sockaddr_in6* sa6 = (struct sockaddr_in6*)ss; + + memset(sa6, 0, sizeof(*sa6)); + sa6->sin6_family = AF_INET6; + memcpy(&sa6->sin6_addr, rd->rr_data[i] + 2, + sizeof(sa6->sin6_addr)); + *addrlenp = sizeof(*sa6); + return 1; + } + return 0; +} + +/** + * Search the given 'iptree' for response address information that matches + * any of the IP addresses in an AAAA or A in the answer section of the + * response (stored in 'rep'). If found, a pointer to the matched resp_addr + * structure will be returned, and '*rrset_id' is set to the index in + * rep->rrsets for the RRset that contains the matching IP address record + * (the index is normally 0, but can be larger than that if this is a CNAME + * chain or type-ANY response). + */ +static const struct resp_addr* +respip_addr_lookup(const struct reply_info *rep, struct rbtree_type* iptree, + size_t* rrset_id) +{ + size_t i; + struct resp_addr* ra; + struct sockaddr_storage ss; + socklen_t addrlen; + + for(i=0; i<rep->an_numrrsets; i++) { + size_t j; + const struct packed_rrset_data* rd; + uint16_t rtype = ntohs(rep->rrsets[i]->rk.type); + + if(rtype != LDNS_RR_TYPE_A && rtype != LDNS_RR_TYPE_AAAA) + continue; + rd = rep->rrsets[i]->entry.data; + for(j = 0; j < rd->count; j++) { + if(!rdata2sockaddr(rd, rtype, j, &ss, &addrlen)) + continue; + ra = (struct resp_addr*)addr_tree_lookup(iptree, &ss, + addrlen); + if(ra) { + *rrset_id = i; + return ra; + } + } + } + + return NULL; +} + +/* + * Create a new reply_info based on 'rep'. The new info is based on + * the passed 'rep', but ignores any rrsets except for the first 'an_numrrsets' + * RRsets in the answer section. These answer rrsets are copied to the + * new info, up to 'copy_rrsets' rrsets (which must not be larger than + * 'an_numrrsets'). If an_numrrsets > copy_rrsets, the remaining rrsets array + * entries will be kept empty so the caller can fill them later. When rrsets + * are copied, they are shallow copied. The caller must ensure that the + * copied rrsets are valid throughout its lifetime and must provide appropriate + * mutex if it can be shared by multiple threads. + */ +static struct reply_info * +make_new_reply_info(const struct reply_info* rep, struct regional* region, + size_t an_numrrsets, size_t copy_rrsets) +{ + struct reply_info* new_rep; + size_t i; + + /* create a base struct. we specify 'insecure' security status as + * the modified response won't be DNSSEC-valid. In our faked response + * the authority and additional sections will be empty (except possible + * EDNS0 OPT RR in the additional section appended on sending it out), + * so the total number of RRsets is an_numrrsets. */ + new_rep = construct_reply_info_base(region, rep->flags, + rep->qdcount, rep->ttl, rep->prefetch_ttl, an_numrrsets, + 0, 0, an_numrrsets, sec_status_insecure); + if(!new_rep) + return NULL; + if(!reply_info_alloc_rrset_keys(new_rep, NULL, region)) + return NULL; + for(i=0; i<copy_rrsets; i++) + new_rep->rrsets[i] = rep->rrsets[i]; + + return new_rep; +} + +/** + * See if response-ip or tag data should override the original answer rrset + * (which is rep->rrsets[rrset_id]) and if so override it. + * This is (mostly) equivalent to localzone.c:local_data_answer() but for + * response-ip actions. + * Note that this function distinguishes error conditions from "success but + * not overridden". This is because we want to avoid accidentally applying + * the "no data" action in case of error. + * @param raddr: address span that requires an action + * @param action: action to apply + * @param qtype: original query type + * @param rep: original reply message + * @param rrset_id: the rrset ID in 'rep' to which the action should apply + * @param new_repp: see respip_rewrite_reply + * @param tag: if >= 0 the tag ID used to determine the action and data + * @param tag_datas: data corresponding to 'tag'. + * @param tag_datas_size: size of 'tag_datas' + * @param tagname: array of tag names, used for logging + * @param num_tags: size of 'tagname', used for logging + * @param redirect_rrsetp: ptr to redirect record + * @param region: region for building new reply + * @return 1 if overridden, 0 if not overridden, -1 on error. + */ +static int +respip_data_answer(const struct resp_addr* raddr, enum respip_action action, + uint16_t qtype, const struct reply_info* rep, + size_t rrset_id, struct reply_info** new_repp, int tag, + struct config_strlist** tag_datas, size_t tag_datas_size, + char* const* tagname, int num_tags, + struct ub_packed_rrset_key** redirect_rrsetp, struct regional* region) +{ + struct ub_packed_rrset_key* rp = raddr->data; + struct reply_info* new_rep; + *redirect_rrsetp = NULL; + + if(action == respip_redirect && tag != -1 && + (size_t)tag<tag_datas_size && tag_datas[tag]) { + struct query_info dataqinfo; + struct ub_packed_rrset_key r; + + /* Extract parameters of the original answer rrset that can be + * rewritten below, in the form of query_info. Note that these + * can be different from the info of the original query if the + * rrset is a CNAME target.*/ + memset(&dataqinfo, 0, sizeof(dataqinfo)); + dataqinfo.qname = rep->rrsets[rrset_id]->rk.dname; + dataqinfo.qname_len = rep->rrsets[rrset_id]->rk.dname_len; + dataqinfo.qtype = ntohs(rep->rrsets[rrset_id]->rk.type); + dataqinfo.qclass = ntohs(rep->rrsets[rrset_id]->rk.rrset_class); + + memset(&r, 0, sizeof(r)); + if(local_data_find_tag_datas(&dataqinfo, tag_datas[tag], &r, + region)) { + verbose(VERB_ALGO, + "response-ip redirect with tag data [%d] %s", + tag, (tag<num_tags?tagname[tag]:"null")); + /* use copy_rrset() to 'normalize' memory layout */ + rp = copy_rrset(&r, region); + if(!rp) + return -1; + } + } + if(!rp) + return 0; + + /* If we are using response-ip-data, we need to make a copy of rrset + * to replace the rrset's dname. Note that, unlike local data, we + * rename the dname for other actions than redirect. This is because + * response-ip-data isn't associated to any specific name. */ + if(rp == raddr->data) { + rp = copy_rrset(rp, region); + if(!rp) + return -1; + rp->rk.dname = rep->rrsets[rrset_id]->rk.dname; + rp->rk.dname_len = rep->rrsets[rrset_id]->rk.dname_len; + } + + /* Build a new reply with redirect rrset. We keep any preceding CNAMEs + * and replace the address rrset that triggers the action. If it's + * type ANY query, however, no other answer records should be kept + * (note that it can't be a CNAME chain in this case due to + * sanitizing). */ + if(qtype == LDNS_RR_TYPE_ANY) + rrset_id = 0; + new_rep = make_new_reply_info(rep, region, rrset_id + 1, rrset_id); + if(!new_rep) + return -1; + rp->rk.flags |= PACKED_RRSET_FIXEDTTL; /* avoid adjusting TTL */ + new_rep->rrsets[rrset_id] = rp; + + *redirect_rrsetp = rp; + *new_repp = new_rep; + return 1; +} + +/** + * apply response ip action in case where no action data is provided. + * this is similar to localzone.c:lz_zone_answer() but simplified due to + * the characteristics of response ip: + * - 'deny' variants will be handled at the caller side + * - no specific processing for 'transparent' variants: unlike local zones, + * there is no such a case of 'no data but name existing'. so all variants + * just mean 'transparent if no data'. + * @param qtype: query type + * @param action: found action + * @param rep: + * @param new_repp + * @param rrset_id + * @param region: region for building new reply + * @return 1 on success, 0 on error. + */ +static int +respip_nodata_answer(uint16_t qtype, enum respip_action action, + const struct reply_info *rep, size_t rrset_id, + struct reply_info** new_repp, struct regional* region) +{ + struct reply_info* new_rep; + + if(action == respip_refuse || action == respip_always_refuse) { + new_rep = make_new_reply_info(rep, region, 0, 0); + if(!new_rep) + return 0; + FLAGS_SET_RCODE(new_rep->flags, LDNS_RCODE_REFUSED); + *new_repp = new_rep; + return 1; + } else if(action == respip_static || action == respip_redirect || + action == respip_always_nxdomain) { + /* Since we don't know about other types of the owner name, + * we generally return NOERROR/NODATA unless an NXDOMAIN action + * is explicitly specified. */ + int rcode = (action == respip_always_nxdomain)? + LDNS_RCODE_NXDOMAIN:LDNS_RCODE_NOERROR; + + /* We should empty the answer section except for any preceding + * CNAMEs (in that case rrset_id > 0). Type-ANY case is + * special as noted in respip_data_answer(). */ + if(qtype == LDNS_RR_TYPE_ANY) + rrset_id = 0; + new_rep = make_new_reply_info(rep, region, rrset_id, rrset_id); + if(!new_rep) + return 0; + FLAGS_SET_RCODE(new_rep->flags, rcode); + *new_repp = new_rep; + return 1; + } + + return 1; +} + +/** Populate action info structure with the results of response-ip action + * processing, iff as the result of response-ip processing we are actually + * taking some action. Only action is set if action_only is true. + * Returns true on success, false on failure. + */ +static int +populate_action_info(struct respip_action_info* actinfo, + enum respip_action action, const struct resp_addr* raddr, + const struct ub_packed_rrset_key* ATTR_UNUSED(rrset), + int ATTR_UNUSED(tag), const struct respip_set* ATTR_UNUSED(ipset), + int ATTR_UNUSED(action_only), struct regional* region) +{ + if(action == respip_none || !raddr) + return 1; + actinfo->action = action; + + /* for inform variants, make a copy of the matched address block for + * later logging. We make a copy to proactively avoid disruption if + * and when we allow a dynamic update to the respip tree. */ + if(action == respip_inform || action == respip_inform_deny) { + struct respip_addr_info* a = + regional_alloc_zero(region, sizeof(*a)); + if(!a) { + log_err("out of memory"); + return 0; + } + a->addr = raddr->node.addr; + a->addrlen = raddr->node.addrlen; + a->net = raddr->node.net; + actinfo->addrinfo = a; + } + + return 1; +} + +int +respip_rewrite_reply(const struct query_info* qinfo, + const struct respip_client_info* cinfo, const struct reply_info* rep, + struct reply_info** new_repp, struct respip_action_info* actinfo, + struct ub_packed_rrset_key** alias_rrset, int search_only, + struct regional* region) +{ + const uint8_t* ctaglist; + size_t ctaglen; + const uint8_t* tag_actions; + size_t tag_actions_size; + struct config_strlist** tag_datas; + size_t tag_datas_size; + struct view* view = NULL; + struct respip_set* ipset = NULL; + size_t rrset_id = 0; + enum respip_action action = respip_none; + int tag = -1; + const struct resp_addr* raddr = NULL; + int ret = 1; + struct ub_packed_rrset_key* redirect_rrset = NULL; + + if(!cinfo) + goto done; + ctaglist = cinfo->taglist; + ctaglen = cinfo->taglen; + tag_actions = cinfo->tag_actions; + tag_actions_size = cinfo->tag_actions_size; + tag_datas = cinfo->tag_datas; + tag_datas_size = cinfo->tag_datas_size; + view = cinfo->view; + ipset = cinfo->respip_set; + + /** Try to use response-ip config from the view first; use + * global response-ip config if we don't have the view or we don't + * have the matching per-view config (and the view allows the use + * of global data in this case). + * Note that we lock the view even if we only use view members that + * currently don't change after creation. This is for safety for + * future possible changes as the view documentation seems to expect + * any of its member can change in the view's lifetime. + * Note also that we assume 'view' is valid in this function, which + * should be safe (see unbound bug #1191) */ + if(view) { + lock_rw_rdlock(&view->lock); + if(view->respip_set) { + if((raddr = respip_addr_lookup(rep, + &view->respip_set->ip_tree, &rrset_id))) { + /** for per-view respip directives the action + * can only be direct (i.e. not tag-based) */ + action = raddr->action; + } + } + if(!raddr && !view->isfirst) + goto done; + } + if(!raddr && ipset && (raddr = respip_addr_lookup(rep, &ipset->ip_tree, + &rrset_id))) { + action = (enum respip_action)local_data_find_tag_action( + raddr->taglist, raddr->taglen, ctaglist, ctaglen, + tag_actions, tag_actions_size, + (enum localzone_type)raddr->action, &tag, + ipset->tagname, ipset->num_tags); + } + if(raddr && !search_only) { + int result = 0; + + /* first, see if we have response-ip or tag action for the + * action except for 'always' variants. */ + if(action != respip_always_refuse + && action != respip_always_transparent + && action != respip_always_nxdomain + && (result = respip_data_answer(raddr, action, + qinfo->qtype, rep, rrset_id, new_repp, tag, tag_datas, + tag_datas_size, ipset->tagname, ipset->num_tags, + &redirect_rrset, region)) < 0) { + ret = 0; + goto done; + } + + /* if no action data applied, take action specific to the + * action without data. */ + if(!result && !respip_nodata_answer(qinfo->qtype, action, rep, + rrset_id, new_repp, region)) { + ret = 0; + goto done; + } + } + done: + if(view) { + lock_rw_unlock(&view->lock); + } + if(ret) { + /* If we're redirecting the original answer to a + * CNAME, record the CNAME rrset so the caller can take + * the appropriate action. Note that we don't check the + * action type; it should normally be 'redirect', but it + * can be of other type when a data-dependent tag action + * uses redirect response-ip data. + */ + if(redirect_rrset && + redirect_rrset->rk.type == ntohs(LDNS_RR_TYPE_CNAME) && + qinfo->qtype != LDNS_RR_TYPE_ANY) + *alias_rrset = redirect_rrset; + /* on success, populate respip result structure */ + ret = populate_action_info(actinfo, action, raddr, + redirect_rrset, tag, ipset, search_only, region); + } + return ret; +} + +static int +generate_cname_request(struct module_qstate* qstate, + struct ub_packed_rrset_key* alias_rrset) +{ + struct module_qstate* subq = NULL; + struct query_info subqi; + + memset(&subqi, 0, sizeof(subqi)); + get_cname_target(alias_rrset, &subqi.qname, &subqi.qname_len); + if(!subqi.qname) + return 0; /* unexpected: not a valid CNAME RDATA */ + subqi.qtype = qstate->qinfo.qtype; + subqi.qclass = qstate->qinfo.qclass; + fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub)); + return (*qstate->env->attach_sub)(qstate, &subqi, BIT_RD, 0, 0, &subq); +} + +void +respip_operate(struct module_qstate* qstate, enum module_ev event, int id, + struct outbound_entry* outbound) +{ + struct respip_qstate* rq = (struct respip_qstate*)qstate->minfo[id]; + + log_query_info(VERB_QUERY, "respip operate: query", &qstate->qinfo); + (void)outbound; + + if(event == module_event_new || event == module_event_pass) { + if(!rq) { + rq = regional_alloc_zero(qstate->region, sizeof(*rq)); + if(!rq) + goto servfail; + rq->state = RESPIP_INIT; + qstate->minfo[id] = rq; + } + if(rq->state == RESPIP_SUBQUERY_FINISHED) { + qstate->ext_state[id] = module_finished; + return; + } + verbose(VERB_ALGO, "respip: pass to next module"); + qstate->ext_state[id] = module_wait_module; + } else if(event == module_event_moddone) { + /* If the reply may be subject to response-ip rewriting + * according to the query type, check the actions. If a + * rewrite is necessary, we'll replace the reply in qstate + * with the new one. */ + enum module_ext_state next_state = module_finished; + + if((qstate->qinfo.qtype == LDNS_RR_TYPE_A || + qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA || + qstate->qinfo.qtype == LDNS_RR_TYPE_ANY) && + qstate->return_msg && qstate->return_msg->rep) { + struct respip_action_info actinfo = {respip_none, NULL}; + struct reply_info* new_rep = qstate->return_msg->rep; + struct ub_packed_rrset_key* alias_rrset = NULL; + + if(!respip_rewrite_reply(&qstate->qinfo, + qstate->client_info, qstate->return_msg->rep, + &new_rep, &actinfo, &alias_rrset, 0, + qstate->region)) { + goto servfail; + } + if(actinfo.action != respip_none) { + /* save action info for logging on a + * per-front-end-query basis */ + if(!(qstate->respip_action_info = + regional_alloc_init(qstate->region, + &actinfo, sizeof(actinfo)))) + { + log_err("out of memory"); + goto servfail; + } + } else { + qstate->respip_action_info = NULL; + } + if (new_rep == qstate->return_msg->rep && + (actinfo.action == respip_deny || + actinfo.action == respip_inform_deny)) { + /* for deny-variant actions (unless response-ip + * data is applied), mark the query state so + * the response will be dropped for all + * clients. */ + qstate->is_drop = 1; + } else if(alias_rrset) { + if(!generate_cname_request(qstate, alias_rrset)) + goto servfail; + next_state = module_wait_subquery; + } + qstate->return_msg->rep = new_rep; + } + qstate->ext_state[id] = next_state; + } else + qstate->ext_state[id] = module_finished; + + return; + + servfail: + qstate->return_rcode = LDNS_RCODE_SERVFAIL; + qstate->return_msg = NULL; +} + +int +respip_merge_cname(struct reply_info* base_rep, + const struct query_info* qinfo, const struct reply_info* tgt_rep, + const struct respip_client_info* cinfo, int must_validate, + struct reply_info** new_repp, struct regional* region) +{ + struct reply_info* new_rep; + struct reply_info* tmp_rep = NULL; /* just a placeholder */ + struct ub_packed_rrset_key* alias_rrset = NULL; /* ditto */ + uint16_t tgt_rcode; + size_t i, j; + struct respip_action_info actinfo = {respip_none, NULL}; + + /* If the query for the CNAME target would result in an unusual rcode, + * we generally translate it as a failure for the base query + * (which would then be translated into SERVFAIL). The only exception + * is NXDOMAIN and YXDOMAIN, which are passed to the end client(s). + * The YXDOMAIN case would be rare but still possible (when + * DNSSEC-validated DNAME has been cached but synthesizing CNAME + * can't be generated due to length limitation) */ + tgt_rcode = FLAGS_GET_RCODE(tgt_rep->flags); + if((tgt_rcode != LDNS_RCODE_NOERROR && + tgt_rcode != LDNS_RCODE_NXDOMAIN && + tgt_rcode != LDNS_RCODE_YXDOMAIN) || + (must_validate && tgt_rep->security <= sec_status_bogus)) { + return 0; + } + + /* see if the target reply would be subject to a response-ip action. */ + if(!respip_rewrite_reply(qinfo, cinfo, tgt_rep, &tmp_rep, &actinfo, + &alias_rrset, 1, region)) + return 0; + if(actinfo.action != respip_none) { + log_info("CNAME target of redirect response-ip action would " + "be subject to response-ip action, too; stripped"); + *new_repp = base_rep; + return 1; + } + + /* Append target reply to the base. Since we cannot assume + * tgt_rep->rrsets is valid throughout the lifetime of new_rep + * or it can be safely shared by multiple threads, we need to make a + * deep copy. */ + new_rep = make_new_reply_info(base_rep, region, + base_rep->an_numrrsets + tgt_rep->an_numrrsets, + base_rep->an_numrrsets); + if(!new_rep) + return 0; + for(i=0,j=base_rep->an_numrrsets; i<tgt_rep->an_numrrsets; i++,j++) { + new_rep->rrsets[j] = copy_rrset(tgt_rep->rrsets[i], region); + if(!new_rep->rrsets[j]) + return 0; + } + + FLAGS_SET_RCODE(new_rep->flags, tgt_rcode); + *new_repp = new_rep; + return 1; +} + +void +respip_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super) +{ + struct respip_qstate* rq = (struct respip_qstate*)super->minfo[id]; + struct reply_info* new_rep = NULL; + + rq->state = RESPIP_SUBQUERY_FINISHED; + + /* respip subquery should have always been created with a valid reply + * in super. */ + log_assert(super->return_msg && super->return_msg->rep); + + /* return_msg can be NULL when, e.g., the sub query resulted in + * SERVFAIL, in which case we regard it as a failure of the original + * query. Other checks are probably redundant, but we check them + * for safety. */ + if(!qstate->return_msg || !qstate->return_msg->rep || + qstate->return_rcode != LDNS_RCODE_NOERROR) + goto fail; + + if(!respip_merge_cname(super->return_msg->rep, &qstate->qinfo, + qstate->return_msg->rep, super->client_info, + super->env->need_to_validate, &new_rep, super->region)) + goto fail; + super->return_msg->rep = new_rep; + return; + + fail: + super->return_rcode = LDNS_RCODE_SERVFAIL; + super->return_msg = NULL; + return; +} + +void +respip_clear(struct module_qstate* qstate, int id) +{ + qstate->minfo[id] = NULL; +} + +size_t +respip_get_mem(struct module_env* env, int id) +{ + (void)env; + (void)id; + return 0; +} + +/** + * The response-ip function block + */ +static struct module_func_block respip_block = { + "respip", + &respip_init, &respip_deinit, &respip_operate, &respip_inform_super, + &respip_clear, &respip_get_mem +}; + +struct module_func_block* +respip_get_funcblock(void) +{ + return &respip_block; +} + +enum respip_action +resp_addr_get_action(const struct resp_addr* addr) +{ + return addr ? addr->action : respip_none; +} + +struct ub_packed_rrset_key* +resp_addr_get_rrset(struct resp_addr* addr) +{ + return addr ? addr->data : NULL; +} + +int +respip_set_is_empty(const struct respip_set* set) +{ + return set ? set->ip_tree.count == 0 : 1; +} + +void +respip_inform_print(struct respip_addr_info* respip_addr, uint8_t* qname, + uint16_t qtype, uint16_t qclass, struct local_rrset* local_alias, + struct comm_reply* repinfo) +{ + char srcip[128], respip[128], txt[512]; + unsigned port; + + if(local_alias) + qname = local_alias->rrset->rk.dname; + port = (unsigned)((repinfo->addr.ss_family == AF_INET) ? + ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port) : + ntohs(((struct sockaddr_in6*)&repinfo->addr)->sin6_port)); + addr_to_str(&repinfo->addr, repinfo->addrlen, srcip, sizeof(srcip)); + addr_to_str(&respip_addr->addr, respip_addr->addrlen, + respip, sizeof(respip)); + snprintf(txt, sizeof(txt), "%s/%d inform %s@%u", respip, + respip_addr->net, srcip, port); + log_nametypeclass(0, txt, qname, qtype, qclass); +} |