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; }
/** * 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 || action == respip_inform_redirect) { /* 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; }
/** * Given a response event, remove suspect RRsets from the response. * "Suspect" rrsets are potentially poison. Note that this routine expects * the response to be in a "normalized" state -- that is, all "irrelevant" * RRsets have already been removed, CNAMEs are in order, etc. * * @param pkt: packet. * @param msg: msg to normalize. * @param qinfo: the question originally asked. * @param zonename: name of server zone. * @param env: module environment with config and cache. * @param ie: iterator environment with private address data. * @return 0 on error. */ static int scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct module_env* env, struct iter_env* ie) { int del_addi = 0; /* if additional-holding rrsets are deleted, we do not trust the normalized additional-A-AAAA any more */ struct rrset_parse* rrset, *prev; prev = NULL; rrset = msg->rrset_first; /* the first DNAME is allowed to stay. It needs checking before * it can be used from the cache. After normalization, an initial * DNAME will have a correctly synthesized CNAME after it. */ if(rrset && rrset->type == LDNS_RR_TYPE_DNAME && rrset->section == LDNS_SECTION_ANSWER && pkt_strict_sub(pkt, qinfo->qname, rrset->dname) && pkt_sub(pkt, rrset->dname, zonename)) { prev = rrset; /* DNAME allowed to stay in answer section */ rrset = rrset->rrset_all_next; } /* remove all records from the answer section that are * not the same domain name as the query domain name. * The answer section should contain rrsets with the same name * as the question. For DNAMEs a CNAME has been synthesized. * Wildcards have the query name in answer section. * ANY queries get query name in answer section. * Remainders of CNAME chains are cut off and resolved by iterator. */ while(rrset && rrset->section == LDNS_SECTION_ANSWER) { if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) { if(has_additional(rrset->type)) del_addi = 1; remove_rrset("sanitize: removing extraneous answer " "RRset:", pkt, msg, prev, &rrset); continue; } prev = rrset; rrset = rrset->rrset_all_next; } /* At this point, we brutally remove ALL rrsets that aren't * children of the originating zone. The idea here is that, * as far as we know, the server that we contacted is ONLY * authoritative for the originating zone. It, of course, MAY * be authoriative for any other zones, and of course, MAY * NOT be authoritative for some subdomains of the originating * zone. */ prev = NULL; rrset = msg->rrset_first; while(rrset) { /* remove private addresses */ if( (rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA) && priv_rrset_bad(ie->priv, pkt, rrset)) { /* set servfail, so the classification becomes * THROWAWAY, instead of LAME or other unwanted */ FLAGS_SET_RCODE(msg->flags, LDNS_RCODE_SERVFAIL); remove_rrset("sanitize: removing public name with " "private address", pkt, msg, prev, &rrset); continue; } /* skip DNAME records -- they will always be followed by a * synthesized CNAME, which will be relevant. * FIXME: should this do something differently with DNAME * rrsets NOT in Section.ANSWER? */ /* But since DNAME records are also subdomains of the zone, * same check can be used */ if(!pkt_sub(pkt, rrset->dname, zonename)) { if(msg->an_rrsets == 0 && rrset->type == LDNS_RR_TYPE_NS && rrset->section == LDNS_SECTION_AUTHORITY && FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && !soa_in_auth(msg) && sub_of_pkt(pkt, zonename, rrset->dname)) { /* noerror, nodata and this NS rrset is above * the zone. This is LAME! * Leave in the NS for lame classification. */ /* remove everything from the additional * (we dont want its glue that was approved * during the normalize action) */ del_addi = 1; } else if(!env->cfg->harden_glue) { /* store in cache! Since it is relevant * (from normalize) it will be picked up * from the cache to be used later */ store_rrset(pkt, msg, env, rrset); remove_rrset("sanitize: storing potential " "poison RRset:", pkt, msg, prev, &rrset); continue; } else { if(has_additional(rrset->type)) del_addi = 1; remove_rrset("sanitize: removing potential " "poison RRset:", pkt, msg, prev, &rrset); continue; } } if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) { remove_rrset("sanitize: removing potential " "poison reference RRset:", pkt, msg, prev, &rrset); continue; } /* check if right hand side of NSEC is within zone */ if(rrset->type == LDNS_RR_TYPE_NSEC && sanitize_nsec_is_overreach(rrset, zonename)) { remove_rrset("sanitize: removing overreaching NSEC " "RRset:", pkt, msg, prev, &rrset); continue; } prev = rrset; rrset = rrset->rrset_all_next; } return 1; }
/** * This routine normalizes a response. This includes removing "irrelevant" * records from the answer and additional sections and (re)synthesizing * CNAMEs from DNAMEs, if present. * * @param pkt: packet. * @param msg: msg to normalize. * @param qinfo: original query. * @param region: where to allocate synthesized CNAMEs. * @return 0 on error. */ static int scrub_normalize(ldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, struct regional* region) { uint8_t* sname = qinfo->qname; size_t snamelen = qinfo->qname_len; struct rrset_parse* rrset, *prev, *nsset=NULL; if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN) return 1; /* For the ANSWER section, remove all "irrelevant" records and add * synthesized CNAMEs from DNAMEs * This will strip out-of-order CNAMEs as well. */ /* walk through the parse packet rrset list, keep track of previous * for insert and delete ease, and examine every RRset */ prev = NULL; rrset = msg->rrset_first; while(rrset && rrset->section == LDNS_SECTION_ANSWER) { if(rrset->type == LDNS_RR_TYPE_DNAME && pkt_strict_sub(pkt, sname, rrset->dname)) { /* check if next rrset is correct CNAME. else, * synthesize a CNAME */ struct rrset_parse* nx = rrset->rrset_all_next; uint8_t alias[LDNS_MAX_DOMAINLEN+1]; size_t aliaslen = 0; if(rrset->rr_count != 1) { verbose(VERB_ALGO, "Found DNAME rrset with " "size > 1: %u", (unsigned)rrset->rr_count); return 0; } if(!synth_cname(sname, snamelen, rrset, alias, &aliaslen, pkt)) { verbose(VERB_ALGO, "synthesized CNAME " "too long"); return 0; } /* internally we have CNAME'd/DNAME'd chains ending * in nxdomain with NOERROR rcode, change rcode * to reflect this (if needed) */ FLAGS_SET_RCODE(msg->flags, LDNS_RCODE_NOERROR); if(nx && nx->type == LDNS_RR_TYPE_CNAME && dname_pkt_compare(pkt, sname, nx->dname) == 0) { /* check next cname */ uint8_t* t = NULL; size_t tlen = 0; if(!parse_get_cname_target(rrset, &t, &tlen)) return 0; if(dname_pkt_compare(pkt, alias, t) == 0) { /* it's OK and better capitalized */ prev = rrset; rrset = nx; continue; } /* synth ourselves */ } /* synth a CNAME rrset */ prev = synth_cname_rrset(&sname, &snamelen, alias, aliaslen, region, msg, rrset, rrset, nx, pkt); if(!prev) { log_err("out of memory synthesizing CNAME"); return 0; } /* FIXME: resolve the conflict between synthesized * CNAME ttls and the cache. */ rrset = nx; continue; } /* The only records in the ANSWER section not allowed to */ if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) { remove_rrset("normalize: removing irrelevant RRset:", pkt, msg, prev, &rrset); continue; } /* Follow the CNAME chain. */ if(rrset->type == LDNS_RR_TYPE_CNAME) { if(!parse_get_cname_target(rrset, &sname, &snamelen)) return 0; prev = rrset; rrset = rrset->rrset_all_next; /* internally we have CNAME'd/DNAME'd chains ending * in nxdomain with NOERROR rcode, change rcode * to reflect this (if needed) */ FLAGS_SET_RCODE(msg->flags, LDNS_RCODE_NOERROR); continue; } /* Otherwise, make sure that the RRset matches the qtype. */ if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != rrset->type) { remove_rrset("normalize: removing irrelevant RRset:", pkt, msg, prev, &rrset); continue; } /* Mark the additional names from relevant rrset as OK. */ /* only for RRsets that match the query name, other ones * will be removed by sanitize, so no additional for them */ if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) mark_additional_rrset(pkt, msg, rrset); prev = rrset; rrset = rrset->rrset_all_next; } /* Mark additional names from AUTHORITY */ while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) { if(rrset->type==LDNS_RR_TYPE_DNAME || rrset->type==LDNS_RR_TYPE_CNAME || rrset->type==LDNS_RR_TYPE_A || rrset->type==LDNS_RR_TYPE_AAAA) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } /* only one NS set allowed in authority section */ if(rrset->type==LDNS_RR_TYPE_NS) { /* NS set must be pertinent to the query */ if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } if(nsset == NULL) { nsset = rrset; } else { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } } mark_additional_rrset(pkt, msg, rrset); prev = rrset; rrset = rrset->rrset_all_next; } /* For each record in the additional section, remove it if it is an * address record and not in the collection of additional names * found in ANSWER and AUTHORITY. */ /* These records have not been marked OK previously */ while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) { /* FIXME: what about other types? */ if(rrset->type==LDNS_RR_TYPE_A || rrset->type==LDNS_RR_TYPE_AAAA) { if((rrset->flags & RRSET_SCRUB_OK)) { /* remove flag to clean up flags variable */ rrset->flags &= ~RRSET_SCRUB_OK; } else { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } } if(rrset->type==LDNS_RR_TYPE_DNAME || rrset->type==LDNS_RR_TYPE_CNAME || rrset->type==LDNS_RR_TYPE_NS) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } prev = rrset; rrset = rrset->rrset_all_next; } return 1; }