/** test dname_is_root */ static void dname_test_isroot(void) { unit_show_func("util/data/dname.c", "dname_isroot"); unit_assert(dname_is_root((uint8_t*)"\000")); unit_assert(!dname_is_root((uint8_t*)"\001a\000")); unit_assert(!dname_is_root((uint8_t*)"\005abvcd\003com\000")); /* malformed dname in this test, but should work */ unit_assert(!dname_is_root((uint8_t*)"\077a\000")); unit_assert(dname_is_root((uint8_t*)"\000")); }
int forwards_next_root(struct iter_forwards* fwd, uint16_t* dclass) { struct iter_forward_zone key; rbnode_t* n; struct iter_forward_zone* p; if(*dclass == 0) { /* first root item is first item in tree */ n = rbtree_first(fwd->tree); if(n == RBTREE_NULL) return 0; p = (struct iter_forward_zone*)n; if(dname_is_root(p->name)) { *dclass = p->dclass; return 1; } /* root not first item? search for higher items */ *dclass = p->dclass + 1; return forwards_next_root(fwd, dclass); } /* find class n in tree, we may get a direct hit, or if we don't * this is the last item of the previous class so rbtree_next() takes * us to the next root (if any) */ key.node.key = &key; key.name = (uint8_t*)"\000"; key.namelen = 1; key.namelabs = 0; key.dclass = *dclass; n = NULL; if(rbtree_find_less_equal(fwd->tree, &key, &n)) { /* exact */ return 1; } else { /* smaller element */ if(!n || n == RBTREE_NULL) return 0; /* nothing found */ n = rbtree_next(n); if(n == RBTREE_NULL) return 0; /* no higher */ p = (struct iter_forward_zone*)n; if(dname_is_root(p->name)) { *dclass = p->dclass; return 1; } /* not a root node, return next higher item */ *dclass = p->dclass+1; return forwards_next_root(fwd, dclass); } }
struct key_entry_key* key_cache_obtain(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class, struct regional* region, uint32_t now) { /* keep looking until we find a nonexpired entry */ while(1) { struct key_entry_key* k = key_cache_search(kcache, name, namelen, key_class, 0); if(k) { /* see if TTL is OK */ struct key_entry_data* d = (struct key_entry_data*) k->entry.data; if(now <= d->ttl) { /* copy and return it */ struct key_entry_key* retkey = key_entry_copy_toregion(k, region); lock_rw_unlock(&k->entry.lock); return retkey; } lock_rw_unlock(&k->entry.lock); } /* snip off first label to continue */ if(dname_is_root(name)) break; dname_remove_label(&name, &namelen); } return NULL; }
/** * Calculate space needed for zone and all its parents * @param d: name of zone * @param len: length of name * @return size. */ static size_t calc_zone_need(uint8_t* d, size_t len) { size_t res = sizeof(struct val_neg_zone) + len; while(!dname_is_root(d)) { log_assert(len > 1); /* not root label */ dname_remove_label(&d, &len); res += sizeof(struct val_neg_zone) + len; } return res; }
/** delete empty terminals from tree when final data is deleted */ static void del_empty_term(struct local_zone* z, struct local_data* d, uint8_t* name, size_t len, int labs) { while(d && d->rrsets == NULL && is_terminal(d)) { /* is this empty nonterminal? delete */ /* note, no memory recycling in zone region */ (void)rbtree_delete(&z->data, d); /* go up and to the next label */ if(dname_is_root(name)) return; dname_remove_label(&name, &len); labs--; d = lz_find_node(z, name, len, labs); } }
/** * Calculate space needed for the data and all its parents * @param rep: NSEC entries. * @return size. */ static size_t calc_data_need(struct reply_info* rep) { uint8_t* d; size_t i, len, res = 0; for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) { d = rep->rrsets[i]->rk.dname; len = rep->rrsets[i]->rk.dname_len; res = sizeof(struct val_neg_data) + len; while(!dname_is_root(d)) { log_assert(len > 1); /* not root label */ dname_remove_label(&d, &len); res += sizeof(struct val_neg_data) + len; } } } return res; }
/** check if negative cache is still valid */ static void check_zone_invariants(struct val_neg_cache* neg, struct val_neg_zone* zone) { unit_assert(zone->nsec3_hash == 0); unit_assert(zone->tree.cmp == &val_neg_data_compare); unit_assert(zone->count != 0); if(zone->tree.count == 0) unit_assert(!zone->in_use); else { if(!zone->in_use) { /* details on error */ log_nametypeclass(0, "zone", zone->name, 0, 0); log_err("inuse %d count=%d tree.count=%d", zone->in_use, zone->count, (int)zone->tree.count); if(negverbose) print_neg_cache(neg); } unit_assert(zone->in_use); } if(zone->parent) { unit_assert(zone->parent->count >= zone->count); if(zone->parent->in_use) { unit_assert(zone->parent->count > zone->count); } unit_assert(zone->parent->labs == zone->labs-1); /* and parent must be one label shorter */ unit_assert(zone->name[0] == (zone->len-zone->parent->len-1)); unit_assert(query_dname_compare(zone->name + zone->name[0]+1, zone->parent->name) == 0); } else { /* must be apex */ unit_assert(dname_is_root(zone->name)); } /* tree property: */ unit_assert(zone->count == sum_zone_subtree_inuse(neg, zone)); /* check structure of zone data tree */ checkzonetree(zone); }
/** check point in data tree */ static void check_data(struct val_neg_zone* zone, struct val_neg_data* data) { unit_assert(data->count > 0); if(data->parent) { unit_assert(data->parent->count >= data->count); if(data->parent->in_use) { unit_assert(data->parent->count > data->count); } unit_assert(data->parent->labs == data->labs-1); /* and parent must be one label shorter */ unit_assert(data->name[0] == (data->len-data->parent->len-1)); unit_assert(query_dname_compare(data->name + data->name[0]+1, data->parent->name) == 0); } else { /* must be apex */ unit_assert(dname_is_root(data->name)); } /* tree property: */ unit_assert(data->count == sum_subtree_inuse(zone, data)); }
/** * Initialize the filter structure. * Finds the zone by looking at available NSEC3 records and best match. * (skips the unknown flag and unknown algo NSEC3s). * * @param filter: nsec3 filter structure. * @param list: list of rrsets, an array of them. * @param num: number of rrsets in list. * @param qinfo: * query name to match a zone for. * query type (if DS a higher zone must be chosen) * qclass, to filter NSEC3s with. */ static void filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo) { size_t i; uint8_t* nm; size_t nmlen; filter->zone = NULL; filter->zone_len = 0; filter->list = list; filter->num = num; filter->fclass = qinfo->qclass; for(i=0; i<num; i++) { /* ignore other stuff in the list */ if(ntohs(list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 || ntohs(list[i]->rk.rrset_class) != qinfo->qclass) continue; /* skip unknown flags, algo */ if(!nsec3_rrset_has_known(list[i])) continue; /* since NSEC3s are base32.zonename, we can find the zone * name by stripping off the first label of the record */ nm = list[i]->rk.dname; nmlen = list[i]->rk.dname_len; dname_remove_label(&nm, &nmlen); /* if we find a domain that can prove about the qname, * and if this domain is closer to the qname */ if(dname_subdomain_c(qinfo->qname, nm) && (!filter->zone || dname_subdomain_c(nm, filter->zone))) { /* for a type DS do not accept a zone equal to qname*/ if(qinfo->qtype == LDNS_RR_TYPE_DS && query_dname_compare(qinfo->qname, nm) == 0 && !dname_is_root(qinfo->qname)) continue; filter->zone = nm; filter->zone_len = nmlen; } } }
int print_deleg_lookup(SSL* ssl, struct worker* worker, uint8_t* nm, size_t nmlen, int ATTR_UNUSED(nmlabs)) { /* deep links into the iterator module */ struct delegpt* dp; struct dns_msg* msg; struct regional* region = worker->scratchpad; char b[260]; struct query_info qinfo; struct iter_hints_stub* stub; regional_free_all(region); qinfo.qname = nm; qinfo.qname_len = nmlen; qinfo.qtype = LDNS_RR_TYPE_A; qinfo.qclass = LDNS_RR_CLASS_IN; qinfo.local_alias = NULL; dname_str(nm, b); if(!ssl_printf(ssl, "The following name servers are used for lookup " "of %s\n", b)) return 0; dp = forwards_lookup(worker->env.fwds, nm, qinfo.qclass); if(dp) { if(!ssl_printf(ssl, "forwarding request:\n")) return 0; print_dp_main(ssl, dp, NULL); print_dp_details(ssl, worker, dp); return 1; } while(1) { dp = dns_cache_find_delegation(&worker->env, nm, nmlen, qinfo.qtype, qinfo.qclass, region, &msg, *worker->env.now); if(!dp) { return ssl_printf(ssl, "no delegation from " "cache; goes to configured roots\n"); } /* go up? */ if(iter_dp_is_useless(&qinfo, BIT_RD, dp)) { print_dp_main(ssl, dp, msg); print_dp_details(ssl, worker, dp); if(!ssl_printf(ssl, "cache delegation was " "useless (no IP addresses)\n")) return 0; if(dname_is_root(nm)) { /* goes to root config */ return ssl_printf(ssl, "no delegation from " "cache; goes to configured roots\n"); } else { /* useless, goes up */ nm = dp->name; nmlen = dp->namelen; dname_remove_label(&nm, &nmlen); dname_str(nm, b); if(!ssl_printf(ssl, "going up, lookup %s\n", b)) return 0; continue; } } stub = hints_lookup_stub(worker->env.hints, nm, qinfo.qclass, dp); if(stub) { if(stub->noprime) { if(!ssl_printf(ssl, "The noprime stub servers " "are used:\n")) return 0; } else { if(!ssl_printf(ssl, "The stub is primed " "with servers:\n")) return 0; } print_dp_main(ssl, stub->dp, NULL); print_dp_details(ssl, worker, stub->dp); } else { print_dp_main(ssl, dp, msg); print_dp_details(ssl, worker, dp); } break; } return 1; }
/** Do the nodata proof */ static enum sec_status nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, rbtree_t* ct, struct query_info* qinfo) { struct ce_response ce; uint8_t* wc; size_t wclen; struct ub_packed_rrset_key* rrset; int rr; enum sec_status sec; if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, &rrset, &rr)) { /* cases 1 and 2 */ if(nsec3_has_type(rrset, rr, qinfo->qtype)) { verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " "proved that type existed, bogus"); return sec_status_bogus; } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " "proved that a CNAME existed, bogus"); return sec_status_bogus; } /* * If type DS: filter_init zone find already found a parent * zone, so this nsec3 is from a parent zone. * o can be not a delegation (unusual query for normal name, * no DS anyway, but we can verify that). * o can be a delegation (which is the usual DS check). * o may not have the SOA bit set (only the top of the * zone, which must have been above the name, has that). * Except for the root; which is checked by itself. * * If not type DS: matching nsec3 must not be a delegation. */ if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && !dname_is_root(qinfo->qname)) { verbose(VERB_ALGO, "proveNodata: apex NSEC3 " "abused for no DS proof, bogus"); return sec_status_bogus; } else if(qinfo->qtype != LDNS_RR_TYPE_DS && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "proveNodata: matching " "NSEC3 is insecure delegation"); return sec_status_insecure; } verbose(VERB_ALGO, "proveNodata: matching " "NSEC3 is a delegation, bogus"); return sec_status_bogus; } return sec_status_secure; } /* For cases 3 - 5, we need the proven closest encloser, and it * can't match qname. Although, at this point, we know that it * won't since we just checked that. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); if(sec == sec_status_bogus) { verbose(VERB_ALGO, "proveNodata: did not match qname, " "nor found a proven closest encloser."); return sec_status_bogus; } else if(sec==sec_status_insecure && qinfo->qtype!=LDNS_RR_TYPE_DS){ verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure " "delegation."); return sec_status_insecure; } /* Case 3: removed */ /* Case 4: */ log_assert(ce.ce); wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) { /* found wildcard */ if(nsec3_has_type(rrset, rr, qinfo->qtype)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard had qtype, bogus"); return sec_status_bogus; } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard had a CNAME, bogus"); return sec_status_bogus; } if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard for no DS proof has a SOA, bogus"); return sec_status_bogus; } else if(qinfo->qtype != LDNS_RR_TYPE_DS && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wilcard is a delegation, bogus"); return sec_status_bogus; } /* everything is peachy keen, except for optout spans */ if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard is in optout range, insecure"); return sec_status_insecure; } return sec_status_secure; } /* Case 5: */ /* Due to forwarders, cnames, and other collating effects, we * can see the ordinary unsigned data from a zone beneath an * insecure delegation under an optout here */ if(!ce.nc_rrset) { verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3"); return sec_status_bogus; } /* We need to make sure that the covering NSEC3 is opt-out. */ log_assert(ce.nc_rrset); if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { if(qinfo->qtype == LDNS_RR_TYPE_DS) verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not " "opt-out in an opt-out DS NOERROR/NODATA case."); else verbose(VERB_ALGO, "proveNodata: could not find matching " "NSEC3, nor matching wildcard, nor optout NSEC3 " "-- no more options, bogus."); return sec_status_bogus; } /* RFC5155 section 9.2: if nc has optout then no AD flag set */ return sec_status_insecure; }
struct dns_msg* dns_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, struct regional* region, struct regional* scratch, int no_partial) { struct lruhash_entry* e; struct query_info k; hashvalue_type h; time_t now = *env->now; struct ub_packed_rrset_key* rrset; /* lookup first, this has both NXdomains and ANSWER responses */ k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; k.local_alias = NULL; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(e) { struct msgreply_entry* key = (struct msgreply_entry*)e->key; struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg = tomsg(env, &key->key, data, region, now, scratch); if(msg) { lock_rw_unlock(&e->lock); return msg; } /* could be msg==NULL; due to TTL or not all rrsets available */ lock_rw_unlock(&e->lock); } /* see if a DNAME exists. Checked for first, to enforce that DNAMEs * are more important, the CNAME is resynthesized and thus * consistent with the DNAME */ if(!no_partial && (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, LDNS_RR_TYPE_DNAME, 1))) { /* synthesize a DNAME+CNAME message based on this */ enum sec_status sec_status = sec_status_unchecked; struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k, &sec_status); if(msg) { struct ub_packed_rrset_key* cname_rrset; lock_rw_unlock(&rrset->entry.lock); /* now, after unlocking the DNAME rrset lock, * check the sec_status, and see if we need to look * up the CNAME record associated before it can * be used */ /* normally, only secure DNAMEs allowed from cache*/ if(sec_status == sec_status_secure) return msg; /* but if we have a CNAME cached with this name, then we * have previously already allowed this name to pass. * the next cache lookup is going to fetch that CNAME itself, * but it is better to have the (unsigned)DNAME + CNAME in * that case */ cname_rrset = rrset_cache_lookup( env->rrset_cache, qname, qnamelen, LDNS_RR_TYPE_CNAME, qclass, 0, now, 0); if(cname_rrset) { /* CNAME already synthesized by * synth_dname_msg routine, so we can * straight up return the msg */ lock_rw_unlock(&cname_rrset->entry.lock); return msg; } } else { lock_rw_unlock(&rrset->entry.lock); } } /* see if we have CNAME for this domain, * but not for DS records (which are part of the parent) */ if(!no_partial && qtype != LDNS_RR_TYPE_DS && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, LDNS_RR_TYPE_CNAME, qclass, 0, now, 0))) { uint8_t* wc = NULL; size_t wl; /* if the rrset is not a wildcard expansion, with wcname */ /* because, if we return that CNAME rrset on its own, it is * missing the NSEC or NSEC3 proof */ if(!(val_rrset_wildcard(rrset, &wc, &wl) && wc != NULL)) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } } lock_rw_unlock(&rrset->entry.lock); } /* construct DS, DNSKEY, DLV messages from rrset cache. */ if((qtype == LDNS_RR_TYPE_DS || qtype == LDNS_RR_TYPE_DNSKEY || qtype == LDNS_RR_TYPE_DLV) && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, qtype, qclass, 0, now, 0))) { /* if the rrset is from the additional section, and the * signatures have fallen off, then do not synthesize a msg * instead, allow a full query for signed results to happen. * Forego all rrset data from additional section, because * some signatures may not be present and cause validation * failure. */ struct packed_rrset_data *d = (struct packed_rrset_data*) rrset->entry.data; if(d->trust != rrset_trust_add_noAA && d->trust != rrset_trust_add_AA && (qtype == LDNS_RR_TYPE_DS || (d->trust != rrset_trust_auth_noAA && d->trust != rrset_trust_auth_AA) )) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } } lock_rw_unlock(&rrset->entry.lock); } /* stop downwards cache search on NXDOMAIN. * Empty nonterminals are NOERROR, so an NXDOMAIN for foo * means bla.foo also does not exist. The DNSSEC proofs are * the same. We search upwards for NXDOMAINs. */ if(env->cfg->harden_below_nxdomain) while(!dname_is_root(k.qname)) { dname_remove_label(&k.qname, &k.qname_len); h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(!e && k.qtype != LDNS_RR_TYPE_A && env->cfg->qname_minimisation) { k.qtype = LDNS_RR_TYPE_A; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); } if(e) { struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg; if(FLAGS_GET_RCODE(data->flags) == LDNS_RCODE_NXDOMAIN && data->security == sec_status_secure && (msg=tomsg(env, &k, data, region, now, scratch))){ lock_rw_unlock(&e->lock); msg->qinfo.qname=qname; msg->qinfo.qname_len=qnamelen; /* check that DNSSEC really works out */ msg->rep->security = sec_status_unchecked; return msg; } lock_rw_unlock(&e->lock); } k.qtype = qtype; } /* fill common RR types for ANY response to avoid requery */ if(qtype == LDNS_RR_TYPE_ANY) { return fill_any(env, qname, qnamelen, qtype, qclass, region); } return NULL; }
struct dns_msg* dns_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, struct regional* region, struct regional* scratch) { struct lruhash_entry* e; struct query_info k; hashvalue_t h; time_t now = *env->now; struct ub_packed_rrset_key* rrset; /* lookup first, this has both NXdomains and ANSWER responses */ k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(e) { struct msgreply_entry* key = (struct msgreply_entry*)e->key; struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg = tomsg(env, &key->key, data, region, now, scratch); if(msg) { lock_rw_unlock(&e->lock); return msg; } /* could be msg==NULL; due to TTL or not all rrsets available */ lock_rw_unlock(&e->lock); } /* see if a DNAME exists. Checked for first, to enforce that DNAMEs * are more important, the CNAME is resynthesized and thus * consistent with the DNAME */ if( (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, LDNS_RR_TYPE_DNAME, 1))) { /* synthesize a DNAME+CNAME message based on this */ struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } lock_rw_unlock(&rrset->entry.lock); } /* see if we have CNAME for this domain, * but not for DS records (which are part of the parent) */ if( qtype != LDNS_RR_TYPE_DS && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, LDNS_RR_TYPE_CNAME, qclass, 0, now, 0))) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } lock_rw_unlock(&rrset->entry.lock); } /* construct DS, DNSKEY, DLV messages from rrset cache. */ if((qtype == LDNS_RR_TYPE_DS || qtype == LDNS_RR_TYPE_DNSKEY || qtype == LDNS_RR_TYPE_DLV) && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, qtype, qclass, 0, now, 0))) { /* if the rrset is from the additional section, and the * signatures have fallen off, then do not synthesize a msg * instead, allow a full query for signed results to happen. * Forego all rrset data from additional section, because * some signatures may not be present and cause validation * failure. */ struct packed_rrset_data *d = (struct packed_rrset_data*) rrset->entry.data; if(d->trust != rrset_trust_add_noAA && d->trust != rrset_trust_add_AA && (qtype == LDNS_RR_TYPE_DS || (d->trust != rrset_trust_auth_noAA && d->trust != rrset_trust_auth_AA) )) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } } lock_rw_unlock(&rrset->entry.lock); } /* stop downwards cache search on NXDOMAIN. * Empty nonterminals are NOERROR, so an NXDOMAIN for foo * means bla.foo also does not exist. The DNSSEC proofs are * the same. We search upwards for NXDOMAINs. */ if(env->cfg->harden_below_nxdomain) while(!dname_is_root(k.qname)) { dname_remove_label(&k.qname, &k.qname_len); h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(e) { struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg; if(FLAGS_GET_RCODE(data->flags) == LDNS_RCODE_NXDOMAIN && data->security == sec_status_secure && (msg=tomsg(env, &k, data, region, now, scratch))) { lock_rw_unlock(&e->lock); msg->qinfo.qname=qname; msg->qinfo.qname_len=qnamelen; /* check that DNSSEC really works out */ msg->rep->security = sec_status_unchecked; return msg; } lock_rw_unlock(&e->lock); } } return NULL; }