/** * Assemble the rrsets in the anchors, ready for use by validator. * @param anchors: trust anchor storage. * @return: false on error. */ static int anchors_assemble_rrsets(struct val_anchors* anchors) { struct trust_anchor* ta; struct trust_anchor* next; size_t nods, nokey; lock_basic_lock(&anchors->lock); ta=(struct trust_anchor*)rbtree_first(anchors->tree); while((rbnode_type*)ta != RBTREE_NULL) { next = (struct trust_anchor*)rbtree_next(&ta->node); lock_basic_lock(&ta->lock); if(ta->autr || (ta->numDS == 0 && ta->numDNSKEY == 0)) { lock_basic_unlock(&ta->lock); ta = next; /* skip */ continue; } if(!anchors_assemble(ta)) { log_err("out of memory"); lock_basic_unlock(&ta->lock); lock_basic_unlock(&anchors->lock); return 0; } nods = anchors_ds_unsupported(ta); nokey = anchors_dnskey_unsupported(ta); if(nods) { log_nametypeclass(0, "warning: unsupported " "algorithm for trust anchor", ta->name, LDNS_RR_TYPE_DS, ta->dclass); } if(nokey) { log_nametypeclass(0, "warning: unsupported " "algorithm for trust anchor", ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); } if(nods == ta->numDS && nokey == ta->numDNSKEY) { char b[257]; dname_str(ta->name, b); log_warn("trust anchor %s has no supported algorithms," " the anchor is ignored (check if you need to" " upgrade unbound and " #ifdef HAVE_LIBRESSL "libressl" #else "openssl" #endif ")", b); (void)rbtree_delete(anchors->tree, &ta->node); lock_basic_unlock(&ta->lock); if(anchors->dlv_anchor == ta) anchors->dlv_anchor = NULL; anchors_delfunc(&ta->node, NULL); ta = next; continue; } lock_basic_unlock(&ta->lock); ta = next; } lock_basic_unlock(&anchors->lock); return 1; }
enum sec_status val_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys, char** reason) { enum sec_status sec; struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> entry.data; if(d->security == sec_status_secure) { /* re-verify all other statuses, because keyset may change*/ log_nametypeclass(VERB_ALGO, "verify rrset cached", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); return d->security; } /* check in the cache if verification has already been done */ rrset_check_sec_status(env->rrset_cache, rrset, *env->now); if(d->security == sec_status_secure) { log_nametypeclass(VERB_ALGO, "verify rrset from cache", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); return d->security; } log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); sec = dnskeyset_verify_rrset(env, ve, rrset, keys, reason); verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); regional_free_all(env->scratch); /* update rrset security status * only improves security status * and bogus is set only once, even if we rechecked the status */ if(sec > d->security) { d->security = sec; if(sec == sec_status_secure) d->trust = rrset_trust_validated; else if(sec == sec_status_bogus) { size_t i; /* update ttl for rrset to fixed value. */ d->ttl = ve->bogus_ttl; for(i=0; i<d->count+d->rrsig_count; i++) d->rr_ttl[i] = ve->bogus_ttl; /* leave RR specific TTL: not used for determine * if RRset timed out and clients see proper value. */ lock_basic_lock(&ve->bogus_lock); ve->num_rrset_bogus++; lock_basic_unlock(&ve->bogus_lock); } /* if status updated - store in cache for reuse */ rrset_update_sec_status(env->rrset_cache, rrset, *env->now); } return sec; }
/** find and add A and AAAA records for missing nameservers in delegpt */ int cache_fill_missing(struct module_env* env, uint16_t qclass, struct regional* region, struct delegpt* dp) { struct delegpt_ns* ns; struct msgreply_entry* neg; struct ub_packed_rrset_key* akey; time_t now = *env->now; for(ns = dp->nslist; ns; ns = ns->next) { akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(akey) { if(!delegpt_add_rrset_A(dp, region, akey, ns->lame)) { lock_rw_unlock(&akey->entry.lock); return 0; } log_nametypeclass(VERB_ALGO, "found in cache", ns->name, LDNS_RR_TYPE_A, qclass); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); if(akey) { if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame)) { lock_rw_unlock(&akey->entry.lock); return 0; } log_nametypeclass(VERB_ALGO, "found in cache", ns->name, LDNS_RR_TYPE_AAAA, qclass); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } } return 1; }
void iter_scrub_ds(struct dns_msg* msg, struct ub_packed_rrset_key* ns, uint8_t* z) { /* Only the DS record for the delegation itself is expected. * We allow DS for everything between the bailiwick and the * zonecut, thus DS records must be at or above the zonecut. * And the DS records must be below the server authority zone. * The answer section is already scrubbed. */ size_t i = msg->rep->an_numrrsets; while(i < (msg->rep->an_numrrsets + msg->rep->ns_numrrsets)) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_DS && (!ns || !dname_subdomain_c(ns->rk.dname, s->rk.dname) || query_dname_compare(z, s->rk.dname) == 0)) { log_nametypeclass(VERB_ALGO, "removing irrelevant DS", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); memmove(msg->rep->rrsets+i, msg->rep->rrsets+i+1, sizeof(struct ub_packed_rrset_key*) * (msg->rep->rrset_count-i-1)); msg->rep->ns_numrrsets--; msg->rep->rrset_count--; /* stay at same i, but new record */ continue; } i++; } }
/** remove rrset, update loop variables */ static void remove_rrset(const char* str, ldns_buffer* pkt, struct msg_parse* msg, struct rrset_parse* prev, struct rrset_parse** rrset) { if(verbosity >= VERB_QUERY && (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, (*rrset)->dname); log_nametypeclass(VERB_QUERY, str, buf, (*rrset)->type, ntohs((*rrset)->rrset_class)); } if(prev) prev->rrset_all_next = (*rrset)->rrset_all_next; else msg->rrset_first = (*rrset)->rrset_all_next; if(msg->rrset_last == *rrset) msg->rrset_last = prev; msg->rrset_count --; switch((*rrset)->section) { case LDNS_SECTION_ANSWER: msg->an_rrsets--; break; case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break; case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break; default: log_assert(0); } msgparse_bucket_remove(msg, *rrset); *rrset = (*rrset)->rrset_all_next; }
void log_rrset_key(enum verbosity_value v, const char* str, struct ub_packed_rrset_key* rrset) { if(verbosity >= v) log_nametypeclass(v, str, rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); }
/** * Add new RR. It converts ldns RR to wire format. * @param anchors: anchor storage. * @param buffer: parsing buffer. * @param rr: the rr (allocated by caller). * @return NULL on error, else the trust anchor. */ static struct trust_anchor* anchor_store_new_rr(struct val_anchors* anchors, ldns_buffer* buffer, ldns_rr* rr) { struct trust_anchor* ta; ldns_rdf* owner = ldns_rr_owner(rr); ldns_status status; ldns_buffer_clear(buffer); ldns_buffer_skip(buffer, 2); /* skip rdatalen */ status = ldns_rr_rdata2buffer_wire(buffer, rr); if(status != LDNS_STATUS_OK) { log_err("error converting trustanchor to wireformat: %s", ldns_get_errorstr_by_id(status)); return NULL; } ldns_buffer_flip(buffer); ldns_buffer_write_u16_at(buffer, 0, ldns_buffer_limit(buffer) - 2); if(!(ta=anchor_store_new_key(anchors, ldns_rdf_data(owner), ldns_rr_get_type(rr), ldns_rr_get_class(rr), ldns_buffer_begin(buffer), ldns_buffer_limit(buffer)))) { return NULL; } log_nametypeclass(VERB_QUERY, "adding trusted key", ldns_rdf_data(owner), ldns_rr_get_type(rr), ldns_rr_get_class(rr)); return ta; }
/** verify and test one rrset against the key rrset */ static void verifytest_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, struct query_info* qinfo) { enum sec_status sec; char* reason = NULL; uint8_t sigalg[ALGO_NEEDS_MAX+1]; if(vsig) { log_nametypeclass(VERB_QUERY, "verify of rrset", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); } setup_sigalg(dnskey, sigalg); /* check all algorithms in the dnskey */ sec = dnskeyset_verify_rrset(env, ve, rrset, dnskey, sigalg, &reason); if(vsig) { printf("verify outcome is: %s %s\n", sec_status_to_string(sec), reason?reason:""); } if(should_be_bogus(rrset, qinfo)) { unit_assert(sec == sec_status_bogus); } else { unit_assert(sec == sec_status_secure); } }
/** * proveClosestEncloser * Given a List of nsec3 RRs, find and prove the closest encloser to qname. * @param env: module environment with temporary region and buffer. * @param flt: the NSEC3 RR filter, contains zone name and RRs. * @param ct: cached hashes table. * @param qinfo: query that is verified for. * @param prove_does_not_exist: If true, then if the closest encloser * turns out to be qname, then null is returned. * If set true, and the return value is true, then you can be * certain that the ce.nc_rrset and ce.nc_rr are set properly. * @param ce: closest encloser information is returned in here. * @return bogus if no closest encloser could be proven. * secure if a closest encloser could be proven, ce is set. * insecure if the closest-encloser candidate turns out to prove * that an insecure delegation exists above the qname. */ static enum sec_status nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, rbtree_t* ct, struct query_info* qinfo, int prove_does_not_exist, struct ce_response* ce) { uint8_t* nc; size_t nc_len; /* robust: clean out ce, in case it gets abused later */ memset(ce, 0, sizeof(*ce)); if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " "not find a candidate for the closest encloser."); return sec_status_bogus; } log_nametypeclass(VERB_ALGO, "ce candidate", ce->ce, 0, 0); if(query_dname_compare(ce->ce, qinfo->qname) == 0) { if(prove_does_not_exist) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " "proved that qname existed, bad"); return sec_status_bogus; } /* otherwise, we need to nothing else to prove that qname * is its own closest encloser. */ return sec_status_secure; } /* If the closest encloser is actually a delegation, then the * response should have been a referral. If it is a DNAME, then * it should have been a DNAME response. */ if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_SOA)) { if(!nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " "closest encloser is insecure delegation"); return sec_status_insecure; } verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " "encloser was a delegation, bad"); return sec_status_bogus; } if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DNAME)) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " "encloser was a DNAME, bad"); return sec_status_bogus; } /* Otherwise, we need to show that the next closer name is covered. */ next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len); if(!find_covering_nsec3(env, flt, ct, nc, nc_len, &ce->nc_rrset, &ce->nc_rr)) { verbose(VERB_ALGO, "nsec3: Could not find proof that the " "candidate encloser was the closest encloser"); return sec_status_bogus; } return sec_status_secure; }
void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, uint8_t* zone_name) { size_t i, need; uint8_t* signer; size_t signer_len; uint16_t dclass; struct val_neg_zone* zone; /* no SOA in this message, find RRSIG over NSEC's signer name. * note the NSEC records are maybe not validated yet */ signer = reply_nsec_signer(rep, &signer_len, &dclass); if(!signer) return; if(!dname_subdomain_c(signer, zone_name)) { /* the signer is not in the bailiwick, throw it out */ return; } log_nametypeclass(VERB_ALGO, "negcache insert referral ", signer, LDNS_RR_TYPE_NS, dclass); /* ask for enough space to store all of it */ need = calc_data_need(rep) + calc_zone_need(signer, signer_len); lock_basic_lock(&neg->lock); neg_make_space(neg, need); /* find or create the zone entry */ zone = neg_find_zone(neg, signer, signer_len, dclass); if(!zone) { if(!(zone = neg_create_zone(neg, signer, signer_len, dclass))) { lock_basic_unlock(&neg->lock); log_err("out of memory adding negative zone"); return; } } val_neg_zone_take_inuse(zone); /* insert the NSECs */ for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC && ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC3) continue; if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, zone->name)) continue; /* insert NSEC into this zone's tree */ neg_insert_data(neg, zone, rep->rrsets[i]); } if(zone->tree.count == 0) { /* remove empty zone if inserts failed */ neg_delete_zone(neg, zone); } lock_basic_unlock(&neg->lock); }
/** print all RRsets in local zone */ static void local_zone_out(struct local_zone* z) { struct local_data* d; struct local_rrset* p; RBTREE_FOR(d, struct local_data*, &z->data) { for(p = d->rrsets; p; p = p->next) { log_nametypeclass(0, "rrset", d->name, ntohs(p->rrset->rk.type), ntohs(p->rrset->rk.rrset_class)); } } }
/** print log information for an inform zone query */ static void lz_inform_print(struct local_zone* z, struct query_info* qinfo, struct comm_reply* repinfo) { char ip[128], txt[512]; char zname[LDNS_MAX_DOMAINLEN+1]; uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); dname_str(z->name, zname); addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); snprintf(txt, sizeof(txt), "%s inform %s@%u", zname, ip, (unsigned)port); log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); }
void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep) { size_t i, need; struct ub_packed_rrset_key* soa; struct val_neg_zone* zone; /* see if secure nsecs inside */ if(!reply_has_nsec(rep)) return; /* find the zone name in message */ soa = reply_find_soa(rep); if(!soa) return; log_nametypeclass(VERB_ALGO, "negcache insert for zone", soa->rk.dname, LDNS_RR_TYPE_SOA, ntohs(soa->rk.rrset_class)); /* ask for enough space to store all of it */ need = calc_data_need(rep) + calc_zone_need(soa->rk.dname, soa->rk.dname_len); lock_basic_lock(&neg->lock); neg_make_space(neg, need); /* find or create the zone entry */ zone = neg_find_zone(neg, soa->rk.dname, soa->rk.dname_len, ntohs(soa->rk.rrset_class)); if(!zone) { if(!(zone = neg_create_zone(neg, soa->rk.dname, soa->rk.dname_len, ntohs(soa->rk.rrset_class)))) { lock_basic_unlock(&neg->lock); log_err("out of memory adding negative zone"); return; } } val_neg_zone_take_inuse(zone); /* insert the NSECs */ for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC) continue; if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, zone->name)) continue; /* insert NSEC into this zone's tree */ neg_insert_data(neg, zone, rep->rrsets[i]); } if(zone->tree.count == 0) { /* remove empty zone if inserts failed */ neg_delete_zone(neg, zone); } lock_basic_unlock(&neg->lock); }
void iter_mark_pside_cycle_targets(struct module_qstate* qstate, struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->done_pside4 && ns->done_pside6) continue; /* see if this ns as target causes dependency cycle */ if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_A, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle", ns->name, LDNS_RR_TYPE_A, qstate->qinfo.qclass); ns->done_pside4 = 1; } if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle", ns->name, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass); ns->done_pside6 = 1; } } }
/** * Add new RR. It converts ldns RR to wire format. * @param anchors: anchor storage. * @param rr: the wirerr. * @param rl: length of rr. * @param dl: length of dname. * @return NULL on error, else the trust anchor. */ static struct trust_anchor* anchor_store_new_rr(struct val_anchors* anchors, uint8_t* rr, size_t rl, size_t dl) { struct trust_anchor* ta; if(!(ta=anchor_store_new_key(anchors, rr, sldns_wirerr_get_type(rr, rl, dl), sldns_wirerr_get_class(rr, rl, dl), sldns_wirerr_get_rdatawl(rr, rl, dl), sldns_wirerr_get_rdatalen(rr, rl, dl)+2))) { return NULL; } log_nametypeclass(VERB_QUERY, "adding trusted key", rr, sldns_wirerr_get_type(rr, rl, dl), sldns_wirerr_get_class(rr, rl, dl)); return ta; }
/** add a random item */ static void add_item(struct val_neg_cache* neg) { struct val_neg_zone* z; struct packed_rrset_data rd; struct ub_packed_rrset_key nsec; size_t rr_len; time_t rr_ttl; uint8_t* rr_data; char* zname = get_random_zone(); char* from, *to; lock_basic_lock(&neg->lock); if(negverbose) log_nametypeclass(0, "add to zone", (uint8_t*)zname, 0, 0); z = neg_find_zone(neg, (uint8_t*)zname, strlen(zname)+1, LDNS_RR_CLASS_IN); if(!z) { z = neg_create_zone(neg, (uint8_t*)zname, strlen(zname)+1, LDNS_RR_CLASS_IN); } unit_assert(z); val_neg_zone_take_inuse(z); /* construct random NSEC item */ get_random_data(&from, &to, zname); /* create nsec and insert it */ memset(&rd, 0, sizeof(rd)); memset(&nsec, 0, sizeof(nsec)); nsec.rk.dname = (uint8_t*)from; nsec.rk.dname_len = strlen(from)+1; nsec.rk.type = htons(LDNS_RR_TYPE_NSEC); nsec.rk.rrset_class = htons(LDNS_RR_CLASS_IN); nsec.entry.data = &rd; rd.security = sec_status_secure; rd.count = 1; rd.rr_len = &rr_len; rr_len = 19; rd.rr_ttl = &rr_ttl; rr_ttl = 0; rd.rr_data = &rr_data; rr_data = (uint8_t*)to; neg_insert_data(neg, z, &nsec); lock_basic_unlock(&neg->lock); }
/** verify from a file */ static void verifytest_file(const char* fname, const char* at_date) { /* * The file contains a list of ldns-testpkts entries. * The first entry must be a query for DNSKEY. * The answer rrset is the keyset that will be used for verification */ struct ub_packed_rrset_key* dnskey; struct regional* region = regional_create(); struct alloc_cache alloc; ldns_buffer* buf = ldns_buffer_new(65535); struct entry* e; struct entry* list = read_datafile(fname); struct module_env env; struct val_env ve; uint32_t now = time(NULL); if(!list) fatal_exit("could not read %s: %s", fname, strerror(errno)); alloc_init(&alloc, NULL, 1); memset(&env, 0, sizeof(env)); memset(&ve, 0, sizeof(ve)); env.scratch = region; env.scratch_buffer = buf; env.now = &now; ve.date_override = cfg_convert_timeval(at_date); unit_assert(region && buf); dnskey = extract_keys(list, &alloc, region, buf); if(vsig) log_nametypeclass(VERB_QUERY, "test dnskey", dnskey->rk.dname, ntohs(dnskey->rk.type), ntohs(dnskey->rk.rrset_class)); /* ready to go! */ for(e = list->next; e; e = e->next) { verifytest_entry(e, &alloc, region, buf, dnskey, &env, &ve); } ub_packed_rrset_parsedelete(dnskey, &alloc); delete_entry(list); regional_destroy(region); alloc_clear(&alloc); ldns_buffer_free(buf); }
/** Do the name error proof */ static enum sec_status nsec3_do_prove_nameerror(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* wc_rrset; int wc_rr; enum sec_status sec; /* First locate and prove the closest encloser to qname. We will * use the variant that fails if the closest encloser turns out * to be qname. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); if(sec != sec_status_secure) { if(sec == sec_status_bogus) verbose(VERB_ALGO, "nsec3 nameerror proof: failed " "to prove a closest encloser"); else verbose(VERB_ALGO, "nsec3 nameerror proof: closest " "nsec3 is an insecure delegation"); return sec; } log_nametypeclass(VERB_ALGO, "nsec3 namerror: proven ce=", ce.ce,0,0); /* At this point, we know that qname does not exist. Now we need * to prove that the wildcard does not exist. */ log_assert(ce.ce); wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr)) { verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " "that the applicable wildcard did not exist."); return sec_status_bogus; } if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nameerror proof: nc has optout"); return sec_status_insecure; } return sec_status_secure; }
/** 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); }
enum sec_status nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey) { rbtree_t ct; struct nsec3_filter flt; if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", flt.zone, 0, 0); return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); }
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); }
void iter_mark_cycle_targets(struct module_qstate* qstate, struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->resolved) continue; /* see if this ns as target causes dependency cycle */ if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass) || causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_A, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle (harden-glue: no may " "fix some of the cycles)", ns->name, LDNS_RR_TYPE_A, qstate->qinfo.qclass); ns->resolved = 1; } } }
int scrub_message(ldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct regional* region, struct module_env* env, struct iter_env* ie) { /* basic sanity checks */ log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, qinfo->qclass); if(msg->qdcount > 1) return 0; if( !(msg->flags&BIT_QR) ) return 0; msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */ /* make sure that a query is echoed back when NOERROR or NXDOMAIN */ /* this is not required for basic operation but is a forgery * resistance (security) feature */ if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR || FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) && msg->qdcount == 0) return 0; /* if a query is echoed back, make sure it is correct. Otherwise, * this may be not a reply to our query. */ if(msg->qdcount == 1) { if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0) return 0; if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass) return 0; } /* normalize the response, this cleans up the additional. */ if(!scrub_normalize(pkt, msg, qinfo, region)) return 0; /* delete all out-of-zone information */ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie)) return 0; return 1; }
int mesh_make_new_space(struct mesh_area* mesh, ldns_buffer* qbuf) { struct mesh_state* m = mesh->jostle_first; /* free space is available */ if(mesh->num_reply_states < mesh->max_reply_states) return 1; /* try to kick out a jostle-list item */ if(m && m->reply_list && m->list_select == mesh_jostle_list) { /* how old is it? */ struct timeval age; timeval_subtract(&age, mesh->env->now_tv, &m->reply_list->start_time); if(timeval_smaller(&mesh->jostle_max, &age)) { /* its a goner */ log_nametypeclass(VERB_ALGO, "query jostled out to " "make space for a new one", m->s.qinfo.qname, m->s.qinfo.qtype, m->s.qinfo.qclass); /* backup the query */ if(qbuf) ldns_buffer_copy(mesh->qbuf_bak, qbuf); /* notify supers */ if(m->super_set.count > 0) { verbose(VERB_ALGO, "notify supers of failure"); m->s.return_msg = NULL; m->s.return_rcode = LDNS_RCODE_SERVFAIL; mesh_walk_supers(mesh, m); } mesh->stats_jostled ++; mesh_state_delete(&m->s); /* restore the query - note that the qinfo ptr to * the querybuffer is then correct again. */ if(qbuf) ldns_buffer_copy(qbuf, mesh->qbuf_bak); return 1; } } /* no space for new item */ return 0; }
int worker_handle_request(struct comm_point* c, void* arg, int error, struct comm_reply* repinfo) { struct worker* worker = (struct worker*)arg; int ret; hashvalue_t h; struct lruhash_entry* e; struct query_info qinfo; struct edns_data edns; enum acl_access acl; int rc = 0; if(error != NETEVENT_NOERROR) { /* some bad tcp query DNS formats give these error calls */ verbose(VERB_ALGO, "handle request called with err=%d", error); return 0; } #ifdef USE_DNSTAP if(worker->dtenv.log_client_query_messages) dt_msg_send_client_query(&worker->dtenv, &repinfo->addr, c->type, c->buffer); #endif acl = acl_list_lookup(worker->daemon->acl, &repinfo->addr, repinfo->addrlen); if((ret=deny_refuse_all(c, acl, worker, repinfo)) != -1) { if(ret == 1) goto send_reply; return ret; } if((ret=worker_check_request(c->buffer, worker)) != 0) { verbose(VERB_ALGO, "worker check request: bad query."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); if(ret != -1) { LDNS_QR_SET(sldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); return 1; } comm_point_drop_reply(repinfo); return 0; } worker->stats.num_queries++; /* see if query is in the cache */ if(!query_info_parse(&qinfo, c->buffer)) { verbose(VERB_ALGO, "worker parse request: formerror."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) { comm_point_drop_reply(repinfo); return 0; } sldns_buffer_rewind(c->buffer); LDNS_QR_SET(sldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), LDNS_RCODE_FORMERR); server_stats_insrcode(&worker->stats, c->buffer); goto send_reply; } if(worker->env.cfg->log_queries) { char ip[128]; addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); log_nametypeclass(0, ip, qinfo.qname, qinfo.qtype, qinfo.qclass); } if(qinfo.qtype == LDNS_RR_TYPE_AXFR || qinfo.qtype == LDNS_RR_TYPE_IXFR) { verbose(VERB_ALGO, "worker request: refused zone transfer."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); sldns_buffer_rewind(c->buffer); LDNS_QR_SET(sldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), LDNS_RCODE_REFUSED); if(worker->stats.extended) { worker->stats.qtype[qinfo.qtype]++; server_stats_insrcode(&worker->stats, c->buffer); } goto send_reply; } if((ret=parse_edns_from_pkt(c->buffer, &edns, worker->scratchpad)) != 0) { struct edns_data reply_edns; verbose(VERB_ALGO, "worker parse edns: formerror."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); memset(&reply_edns, 0, sizeof(reply_edns)); reply_edns.edns_present = 1; reply_edns.udp_size = EDNS_ADVERTISED_SIZE; LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); error_encode(c->buffer, ret, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns); regional_free_all(worker->scratchpad); server_stats_insrcode(&worker->stats, c->buffer); goto send_reply; } if(edns.edns_present && edns.edns_version != 0) { edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4); edns.edns_version = EDNS_ADVERTISED_VERSION; edns.udp_size = EDNS_ADVERTISED_SIZE; edns.bits &= EDNS_DO; edns.opt_list = NULL; verbose(VERB_ALGO, "query with bad edns version."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), NULL); attach_edns_record(c->buffer, &edns); regional_free_all(worker->scratchpad); goto send_reply; }
struct serviced_query* outnet_serviced_query(struct outside_network* outnet, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, int dnssec, int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, comm_point_callback_t* callback, void* callback_arg, sldns_buffer* ATTR_UNUSED(buff)) { struct replay_runtime* runtime = (struct replay_runtime*)outnet->base; struct fake_pending* pend = (struct fake_pending*)calloc(1, sizeof(struct fake_pending)); char z[256]; log_assert(pend); log_nametypeclass(VERB_OPS, "pending serviced query", qname, qtype, qclass); dname_str(zone, z); verbose(VERB_OPS, "pending serviced query zone %s flags%s%s%s%s", z, (flags&BIT_RD)?" RD":"", (flags&BIT_CD)?" CD":"", (flags&~(BIT_RD|BIT_CD))?" MORE":"", (dnssec)?" DO":""); /* create packet with EDNS */ pend->buffer = sldns_buffer_new(512); log_assert(pend->buffer); sldns_buffer_write_u16(pend->buffer, 0); /* id */ sldns_buffer_write_u16(pend->buffer, flags); sldns_buffer_write_u16(pend->buffer, 1); /* qdcount */ sldns_buffer_write_u16(pend->buffer, 0); /* ancount */ sldns_buffer_write_u16(pend->buffer, 0); /* nscount */ sldns_buffer_write_u16(pend->buffer, 0); /* arcount */ sldns_buffer_write(pend->buffer, qname, qnamelen); sldns_buffer_write_u16(pend->buffer, qtype); sldns_buffer_write_u16(pend->buffer, qclass); sldns_buffer_flip(pend->buffer); if(1) { /* add edns */ struct edns_data edns; edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = EDNS_ADVERTISED_VERSION; edns.udp_size = EDNS_ADVERTISED_SIZE; edns.bits = 0; if(dnssec) edns.bits = EDNS_DO; attach_edns_record(pend->buffer, &edns); } memcpy(&pend->addr, addr, addrlen); pend->addrlen = addrlen; pend->zone = memdup(zone, zonelen); pend->zonelen = zonelen; pend->qtype = (int)qtype; log_assert(pend->zone); pend->callback = callback; pend->cb_arg = callback_arg; pend->timeout = UDP_AUTH_QUERY_TIMEOUT; pend->transport = transport_udp; /* pretend UDP */ pend->pkt = NULL; pend->runtime = runtime; pend->serviced = 1; pend->pkt_len = sldns_buffer_limit(pend->buffer); pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len); if(!pend->pkt) fatal_exit("out of memory"); /*log_pkt("pending serviced query: ", pend->pkt, pend->pkt_len);*/ /* see if it matches the current moment */ if(runtime->now && runtime->now->evt_type == repevt_back_query && (runtime->now->addrlen == 0 || sockaddr_cmp( &runtime->now->addr, runtime->now->addrlen, &pend->addr, pend->addrlen) == 0) && find_match(runtime->now->match, pend->pkt, pend->pkt_len, pend->transport)) { log_info("testbound: matched pending to event. " "advance time between events."); log_info("testbound: do STEP %d %s", runtime->now->time_step, repevt_string(runtime->now->evt_type)); advance_moment(runtime); /* still create the pending, because we need it to callback */ } log_info("testbound: created fake pending"); /* add to list */ pend->next = runtime->pending_list; runtime->pending_list = pend; return (struct serviced_query*)pend; }
void val_check_nonsecure(struct val_env* ve, struct reply_info* rep) { size_t i; /* authority */ for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) { if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) ->security != sec_status_secure) { /* because we want to return the authentic original * message when presented with CD-flagged queries, * we need to preserve AUTHORITY section data. * However, this rrset is not signed or signed * with the wrong keys. Validation has tried to * verify this rrset with the keysets of import. * But this rrset did not verify. * Therefore the message is bogus. */ /* check if authority consists of only an NS record * which is bad, and there is an answer section with * data. In that case, delete NS and additional to * be lenient and make a minimal response */ if(rep->an_numrrsets != 0 && rep->ns_numrrsets == 1 && ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS) { verbose(VERB_ALGO, "truncate to minimal"); rep->ns_numrrsets = 0; rep->ar_numrrsets = 0; rep->rrset_count = rep->an_numrrsets; return; } log_nametypeclass(VERB_QUERY, "message is bogus, " "non secure rrset", rep->rrsets[i]->rk.dname, ntohs(rep->rrsets[i]->rk.type), ntohs(rep->rrsets[i]->rk.rrset_class)); rep->security = sec_status_bogus; return; } } /* additional */ if(!ve->clean_additional) return; for(i=rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) { if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) ->security != sec_status_secure) { /* This does not cause message invalidation. It was * simply unsigned data in the additional. The * RRSIG must have been truncated off the message. * * However, we do not want to return possible bogus * data to clients that rely on this service for * their authentication. */ /* remove this unneeded additional rrset */ memmove(rep->rrsets+i, rep->rrsets+i+1, sizeof(struct ub_packed_rrset_key*)* (rep->rrset_count - i - 1)); rep->ar_numrrsets--; rep->rrset_count--; i--; } } }
void neg_insert_data(struct val_neg_cache* neg, struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d; struct val_neg_data* parent; struct val_neg_data* el; uint8_t* nm = nsec->rk.dname; size_t nm_len = nsec->rk.dname_len; int labs = dname_count_labels(nsec->rk.dname); d = (struct packed_rrset_data*)nsec->entry.data; if( !(d->security == sec_status_secure || (d->security == sec_status_unchecked && d->rrsig_count > 0))) return; log_nametypeclass(VERB_ALGO, "negcache rr", nsec->rk.dname, ntohs(nsec->rk.type), ntohs(nsec->rk.rrset_class)); /* find closest enclosing parent data that (still) exists */ parent = neg_closest_data_parent(zone, nm, nm_len, labs); if(parent && query_dname_compare(parent->name, nm) == 0) { /* perfect match already exists */ log_assert(parent->count > 0); el = parent; } else { struct val_neg_data* p, *np; /* create subtree for perfect match */ /* if parent exists, it is in use */ log_assert(!parent || parent->count > 0); el = neg_data_chain(nm, nm_len, labs, parent); if(!el) { log_err("out of memory inserting NSEC negative cache"); return; } el->in_use = 0; /* set on below */ /* insert the list of zones into the tree */ p = el; while(p) { np = p->parent; /* mem use */ neg->use += sizeof(struct val_neg_data) + p->len; /* insert in tree */ p->zone = zone; (void)rbtree_insert(&zone->tree, &p->node); /* last one needs proper parent pointer */ if(np == NULL) p->parent = parent; p = np; } } if(!el->in_use) { struct val_neg_data* p; el->in_use = 1; /* increase usage count of all parents */ for(p=el; p; p = p->parent) { p->count++; } neg_lru_front(neg, el); } else { /* in use, bring to front, lru */ neg_lru_touch(neg, el); } /* if nsec3 store last used parameters */ if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC3) { int h; uint8_t* s; size_t slen, it; if(nsec3_get_params(nsec, 0, &h, &it, &s, &slen) && it <= neg->nsec3_max_iter && (h != zone->nsec3_hash || it != zone->nsec3_iter || slen != zone->nsec3_saltlen || memcmp(zone->nsec3_salt, s, slen) != 0)) { uint8_t* sa = memdup(s, slen); if(sa) { free(zone->nsec3_salt); zone->nsec3_salt = sa; zone->nsec3_saltlen = slen; zone->nsec3_hash = h; zone->nsec3_iter = it; } } } /* wipe out the cache items between NSEC start and end */ wipeout(neg, zone, el, nsec); }
int worker_handle_request(struct comm_point* c, void* arg, int error, struct comm_reply* repinfo) { struct worker* worker = (struct worker*)arg; int ret; hashvalue_t h; struct lruhash_entry* e; struct query_info qinfo; struct edns_data edns; enum acl_access acl; if(error != NETEVENT_NOERROR) { /* some bad tcp query DNS formats give these error calls */ verbose(VERB_ALGO, "handle request called with err=%d", error); return 0; } acl = acl_list_lookup(worker->daemon->acl, &repinfo->addr, repinfo->addrlen); if(acl == acl_deny) { comm_point_drop_reply(repinfo); if(worker->stats.extended) worker->stats.unwanted_queries++; return 0; } else if(acl == acl_refuse) { log_addr(VERB_ALGO, "refused query from", &repinfo->addr, repinfo->addrlen); log_buf(VERB_ALGO, "refuse", c->buffer); if(worker->stats.extended) worker->stats.unwanted_queries++; if(worker_check_request(c->buffer, worker) == -1) { comm_point_drop_reply(repinfo); return 0; /* discard this */ } ldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE); ldns_buffer_write_at(c->buffer, 4, (uint8_t*)"\0\0\0\0\0\0\0\0", 8); LDNS_QR_SET(ldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), LDNS_RCODE_REFUSED); return 1; } if((ret=worker_check_request(c->buffer, worker)) != 0) { verbose(VERB_ALGO, "worker check request: bad query."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); if(ret != -1) { LDNS_QR_SET(ldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), ret); return 1; } comm_point_drop_reply(repinfo); return 0; } worker->stats.num_queries++; /* see if query is in the cache */ if(!query_info_parse(&qinfo, c->buffer)) { verbose(VERB_ALGO, "worker parse request: formerror."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); ldns_buffer_rewind(c->buffer); LDNS_QR_SET(ldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), LDNS_RCODE_FORMERR); server_stats_insrcode(&worker->stats, c->buffer); return 1; } if(worker->env.cfg->log_queries) { char ip[128]; addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); log_nametypeclass(0, ip, qinfo.qname, qinfo.qtype, qinfo.qclass); } if(qinfo.qtype == LDNS_RR_TYPE_AXFR || qinfo.qtype == LDNS_RR_TYPE_IXFR) { verbose(VERB_ALGO, "worker request: refused zone transfer."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); ldns_buffer_rewind(c->buffer); LDNS_QR_SET(ldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), LDNS_RCODE_REFUSED); if(worker->stats.extended) { worker->stats.qtype[qinfo.qtype]++; server_stats_insrcode(&worker->stats, c->buffer); } return 1; } if((ret=parse_edns_from_pkt(c->buffer, &edns)) != 0) { verbose(VERB_ALGO, "worker parse edns: formerror."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); ldns_buffer_rewind(c->buffer); LDNS_QR_SET(ldns_buffer_begin(c->buffer)); LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), ret); server_stats_insrcode(&worker->stats, c->buffer); return 1; } if(edns.edns_present && edns.edns_version != 0) { edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4); edns.edns_version = EDNS_ADVERTISED_VERSION; edns.udp_size = EDNS_ADVERTISED_SIZE; edns.bits &= EDNS_DO; verbose(VERB_ALGO, "query with bad edns version."); log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo, *(uint16_t*)ldns_buffer_begin(c->buffer), ldns_buffer_read_u16_at(c->buffer, 2), NULL); attach_edns_record(c->buffer, &edns); return 1; }
int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, uint16_t qclass, struct rrset_cache* rrset_cache, uint32_t now) { /* lookup closest zone */ struct val_neg_zone* zone; struct val_neg_data* data; int labs; struct ub_packed_rrset_key* nsec; struct packed_rrset_data* d; uint32_t flags; uint8_t* wc; struct query_info qinfo; if(!neg) return 0; log_nametypeclass(VERB_ALGO, "negcache dlvlookup", qname, LDNS_RR_TYPE_DLV, qclass); labs = dname_count_labels(qname); lock_basic_lock(&neg->lock); zone = neg_closest_zone_parent(neg, qname, len, labs, qclass); while(zone && !zone->in_use) zone = zone->parent; if(!zone) { lock_basic_unlock(&neg->lock); return 0; } log_nametypeclass(VERB_ALGO, "negcache zone", zone->name, 0, zone->dclass); /* DLV is defined to use NSEC only */ if(zone->nsec3_hash) { lock_basic_unlock(&neg->lock); return 0; } /* lookup closest data record */ (void)neg_closest_data(zone, qname, len, labs, &data); while(data && !data->in_use) data = data->parent; if(!data) { lock_basic_unlock(&neg->lock); return 0; } log_nametypeclass(VERB_ALGO, "negcache rr", data->name, LDNS_RR_TYPE_NSEC, zone->dclass); /* lookup rrset in rrset cache */ flags = 0; if(query_dname_compare(data->name, zone->name) == 0) flags = PACKED_RRSET_NSEC_AT_APEX; nsec = rrset_cache_lookup(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC, zone->dclass, flags, now, 0); /* check if secure and TTL ok */ if(!nsec) { lock_basic_unlock(&neg->lock); return 0; } d = (struct packed_rrset_data*)nsec->entry.data; if(!d || now > d->ttl) { lock_rw_unlock(&nsec->entry.lock); /* delete data record if expired */ neg_delete_data(neg, data); lock_basic_unlock(&neg->lock); return 0; } if(d->security != sec_status_secure) { lock_rw_unlock(&nsec->entry.lock); neg_delete_data(neg, data); lock_basic_unlock(&neg->lock); return 0; } verbose(VERB_ALGO, "negcache got secure rrset"); /* check NSEC security */ /* check if NSEC proves no DLV type exists */ /* check if NSEC proves NXDOMAIN for qname */ qinfo.qname = qname; qinfo.qtype = LDNS_RR_TYPE_DLV; qinfo.qclass = qclass; if(!nsec_proves_nodata(nsec, &qinfo, &wc) && !val_nsec_proves_name_error(nsec, qname)) { /* the NSEC is not a denial for the DLV */ lock_rw_unlock(&nsec->entry.lock); lock_basic_unlock(&neg->lock); verbose(VERB_ALGO, "negcache not proven"); return 0; } /* so the NSEC was a NODATA proof, or NXDOMAIN proof. */ /* no need to check for wildcard NSEC; no wildcards in DLV repos */ /* no need to lookup SOA record for client; no response message */ lock_rw_unlock(&nsec->entry.lock); /* if OK touch the LRU for neg_data element */ neg_lru_touch(neg, data); lock_basic_unlock(&neg->lock); verbose(VERB_ALGO, "negcache DLV denial proven"); return 1; }