#include "util/net_help.h"
enum val_classification
-val_classify_response(struct query_info* qinf, struct reply_info* rep)
+val_classify_response(struct query_info* qinf, struct reply_info* rep,
+ size_t skip)
{
int rcode = (int)FLAGS_GET_RCODE(rep->flags);
size_t i;
return VAL_CLASS_NAMEERROR;
log_assert(rcode == LDNS_RCODE_NOERROR);
+ /* next check if the skip into the answer section shows no answer */
+ if(skip>0 && rep->an_numrrsets <= skip)
+ return VAL_CLASS_CNAMENOANSWER;
+
/* Next is NODATA */
if(rep->an_numrrsets == 0)
return VAL_CLASS_NODATA;
/* Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless
* qtype=CNAME, this will yield a CNAME response. */
- for(i=0; i<rep->an_numrrsets; i++) {
+ for(i=skip; i<rep->an_numrrsets; i++) {
if(ntohs(rep->rrsets[i]->rk.type) == qinf->qtype)
return VAL_CLASS_POSITIVE;
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME)
sname, slen);
}
+/**
+ * Find best signer name in this set of rrsigs.
+ * @param rrset: which rrsigs to look through.
+ * @param qinf: the query name that needs validation.
+ * @param signer_name: the best signer_name. Updated if a better one is found.
+ * @param signer_len: length of signer name.
+ * @param matchcount: count of current best name (starts at 0 for no match).
+ * Updated if match is improved.
+ */
+static void
+val_find_best_signer(struct ub_packed_rrset_key* rrset,
+ struct query_info* qinf, uint8_t** signer_name, size_t* signer_len,
+ int* matchcount)
+{
+ struct packed_rrset_data* d = (struct packed_rrset_data*)
+ rrset->entry.data;
+ uint8_t* sign;
+ size_t i;
+ int m;
+ for(i=d->count; i<d->count+d->rrsig_count; i++) {
+ sign = d->rr_data[i]+2+18;
+ /* look at signatures that are valid (long enough),
+ * and have a signer name that is a superdomain of qname,
+ * and then check the number of labels in the shared topdomain
+ * improve the match if possible */
+ if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/
+ dname_subdomain_c(qinf->qname, sign)) {
+ (void)dname_lab_cmp(qinf->qname,
+ dname_count_labels(qinf->qname),
+ sign, dname_count_labels(sign), &m);
+ if(m > *matchcount) {
+ *matchcount = m;
+ *signer_name = sign;
+ (void)dname_count_size_labels(*signer_name,
+ signer_len);
+ }
+ }
+ }
+}
+
void
-val_find_signer(struct query_info* qinf, struct reply_info* rep,
- uint8_t** signer_name, size_t* signer_len)
+val_find_signer(enum val_classification subtype, struct query_info* qinf,
+ struct reply_info* rep, size_t cname_skip, uint8_t** signer_name,
+ size_t* signer_len)
{
- enum val_classification subtype = val_classify_response(qinf, rep);
size_t i;
if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_CNAME
|| subtype == VAL_CLASS_ANY) {
/* check for the answer rrset */
- for(i=0; i<rep->an_numrrsets; i++) {
+ for(i=cname_skip; i<rep->an_numrrsets; i++) {
if(query_dname_compare(qinf->qname,
rep->rrsets[i]->rk.dname) == 0) {
val_find_rrset_signer(rep->rrsets[i],
return;
}
}
+ } else if(subtype == VAL_CLASS_CNAMENOANSWER) {
+ /* find closest superdomain signer name in authority section
+ * NSEC and NSEC3s */
+ int matchcount = 0;
+ *signer_name = NULL;
+ *signer_len = 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
+ || ntohs(rep->rrsets[i]->rk.type) ==
+ LDNS_RR_TYPE_NSEC3) {
+ val_find_best_signer(rep->rrsets[i], qinf,
+ signer_name, signer_len, &matchcount);
+ }
+ }
} else {
verbose(VERB_ALGO, "find_signer: could not find signer name"
" for unknown type response");
*wc = NULL;
return 1;
}
+
+int
+val_chase_cname(struct query_info* qchase, struct reply_info* rep,
+ size_t* cname_skip) {
+ size_t i;
+ /* skip any DNAMEs, go to the CNAME for next part */
+ for(i = *cname_skip; i < rep->an_numrrsets; i++) {
+ if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME &&
+ query_dname_compare(qchase->qname, rep->rrsets[i]->
+ rk.dname) == 0) {
+ qchase->qname = NULL;
+ get_cname_target(rep->rrsets[i], &qchase->qname,
+ &qchase->qname_len);
+ if(!qchase->qname)
+ return 0; /* bad CNAME rdata */
+ (*cname_skip) = i;
+ return 1;
+ }
+ }
+ return 0; /* CNAME classified but no matching CNAME ?! */
+}
+
+/** see if rrset has signer name as one of the rrsig signers */
+static int
+rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len)
+{
+ struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+ entry.data;
+ size_t i;
+ for(i = d->count; i< d->count+d->rrsig_count; i++) {
+ if(d->rr_len[i] > 2+18+len) {
+ /* at least rdatalen + signature + signame (+1 sig)*/
+ if(query_dname_compare(name, d->rr_data[i]+2+18) == 0)
+ {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+void
+val_fill_reply(struct reply_info* chase, struct reply_info* orig,
+ size_t cname_skip, uint8_t* name, size_t len)
+{
+ /* unsigned RRsets are never copied, but should not happen in
+ * secure answers anyway. Except for the synthesized CNAME after
+ * a DNAME. */
+ size_t i;
+ int seen_dname = 0;
+ chase->rrset_count = 0;
+ chase->an_numrrsets = 0;
+ chase->ns_numrrsets = 0;
+ chase->ar_numrrsets = 0;
+ /* ANSWER section */
+ for(i=cname_skip; i<orig->an_numrrsets; i++) {
+ if(seen_dname && ntohs(orig->rrsets[i]->rk.type) ==
+ LDNS_RR_TYPE_CNAME) {
+ chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i];
+ seen_dname = 0;
+ } else if(rrset_has_signer(orig->rrsets[i], name, len)) {
+ chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i];
+ if(ntohs(orig->rrsets[i]->rk.type) ==
+ LDNS_RR_TYPE_DNAME) {
+ seen_dname = 1;
+ }
+ }
+ }
+ /* AUTHORITY section */
+ for(i=orig->an_numrrsets; i<orig->an_numrrsets+orig->ns_numrrsets;
+ i++) {
+ if(rrset_has_signer(orig->rrsets[i], name, len)) {
+ chase->rrsets[chase->an_numrrsets+
+ chase->ns_numrrsets++] = orig->rrsets[i];
+ }
+ }
+ /* ADDITIONAL section */
+ for(i=orig->an_numrrsets+orig->ns_numrrsets; i<orig->rrset_count;
+ i++) {
+ if(rrset_has_signer(orig->rrsets[i], name, len)) {
+ chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+
+ chase->ar_numrrsets++] = orig->rrsets[i];
+ }
+ }
+ chase->rrset_count = chase->an_numrrsets + chase->ns_numrrsets +
+ chase->ar_numrrsets;
+}
+
+void
+val_dump_nonsecure(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) {
+ /* remove this unsigned/bogus/unneeded rrset */
+ memmove(rep->rrsets+i, rep->rrsets+i+1,
+ sizeof(struct ub_packed_rrset_key*)*
+ (rep->rrset_count - i - 1));
+ rep->ns_numrrsets--;
+ rep->rrset_count--;
+ }
+ }
+ /* additional */
+ 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) {
+ /* remove this unsigned/bogus/unneeded 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--;
+ }
+ }
+}
VAL_CLASS_NODATA,
/** A NXDOMAIN response. */
VAL_CLASS_NAMEERROR,
+ /** A CNAME/DNAME chain, and the offset is at the end of it,
+ * but there is no answer here, it can be NAMERROR or NODATA. */
+ VAL_CLASS_CNAMENOANSWER,
/** A response to a qtype=ANY query. */
VAL_CLASS_ANY
};
/**
* Given a response, classify ANSWER responses into a subtype.
- * @param qinf: query info
- * @param rep: response
- * @return A subtype ranging from UNKNOWN to NAMEERROR.
+ * @param qinf: query info. The chased query name.
+ * @param rep: response. The original response.
+ * @param skip: offset into the original response answer section.
+ * @return A subtype, all values possible except UNTYPED .
+ * Once CNAME type is returned you can increase skip.
+ * Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible.
*/
enum val_classification val_classify_response(struct query_info* qinf,
- struct reply_info* rep);
+ struct reply_info* rep, size_t skip);
/**
* Given a response, determine the name of the "signer". This is primarily
* to determine if the response is, in fact, signed at all, and, if so, what
* is the name of the most pertinent keyset.
*
- * @param qinf: query
- * @param rep: response to that
+ * @param subtype: the type from classify.
+ * @param qinf: query, the chased query name.
+ * @param rep: response to that, original response.
+ * @param cname_skip: how many answer rrsets have been skipped due to CNAME
+ * chains being chased around.
* @param signer_name: signer name, if the response is signed
* (even partially), or null if the response isn't signed.
* @param signer_len: length of signer_name of 0 if signer_name is NULL.
*/
-void val_find_signer(struct query_info* qinf, struct reply_info* rep,
- uint8_t** signer_name, size_t* signer_len);
+void val_find_signer(enum val_classification subtype,
+ struct query_info* qinf, struct reply_info* rep,
+ size_t cname_skip, uint8_t** signer_name, size_t* signer_len);
/**
* Verify RRset with keys
*/
int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc);
+/**
+ * Chase the cname to the next query name.
+ * @param qchase: the current query name, updated to next target.
+ * @param rep: original message reply to look at CNAMEs.
+ * @param cname_skip: the skip into the answer section. Updated to skip
+ * DNAME and CNAME to the next part of the answer.
+ * @return false on error (bad rdata).
+ */
+int val_chase_cname(struct query_info* qchase, struct reply_info* rep,
+ size_t* cname_skip);
+
+/**
+ * Fill up the chased reply with the content from the original reply;
+ * as pointers to those rrsets. Select the part after the cname_skip into
+ * the answer section, NS and AR sections that are signed with same signer.
+ *
+ * @param chase: chased reply, filled up.
+ * @param orig: original reply.
+ * @param cname_skip: which part of the answer section to skip.
+ * The skipped part contains CNAME(and DNAME)s that have been chased.
+ * @param name: the signer name to look for.
+ * @param len: length of name.
+ */
+void val_fill_reply(struct reply_info* chase, struct reply_info* orig,
+ size_t cname_skip, uint8_t* name, size_t len);
+
+/**
+ * Remove all unsigned or non-secure status rrsets from NS and AR sections.
+ * So that unsigned data does not get let through to clients, when we have
+ * found the data to be secure.
+ *
+ * @param rep: reply to dump all nonsecure stuff out of.
+ */
+void val_dump_nonsecure(struct reply_info* rep);
+
#endif /* VALIDATOR_VAL_UTILS_H */
vq->orig_msg = qstate->return_msg;
}
vq->qchase = qstate->qinfo;
- vq->chase_reply = vq->orig_msg->rep;
+ /* chase reply will be an edited (sub)set of the orig msg rrset ptrs */
+ vq->chase_reply = region_alloc_init(qstate->region, vq->orig_msg->rep,
+ sizeof(struct reply_info) - sizeof(struct rrset_ref));
+ if(!vq->chase_reply)
+ return NULL;
+ vq->chase_reply->rrsets = region_alloc_init(qstate->region,
+ vq->orig_msg->rep->rrsets, sizeof(struct ub_packed_rrset_key*)
+ * vq->orig_msg->rep->rrset_count);
+ if(!vq->chase_reply->rrsets)
+ return NULL;
+ vq->cname_skip = 0;
return vq;
}
* REFUSED, etc.)
*
* @param qstate: query state.
- * @param vq: validator query state.
+ * @param ret_rc: rcode for this message (if noerror - examine ret_msg).
+ * @param ret_msg: return msg, can be NULL; look at rcode instead.
* @return true if the response could use validation (although this does not
* mean we can actually validate this response).
*/
static int
-needs_validation(struct module_qstate* qstate, struct val_qstate* vq)
+needs_validation(struct module_qstate* qstate, int ret_rc,
+ struct dns_msg* ret_msg)
{
int rcode;
return 0;
}
- /* validate unchecked, and re-validate bogus messages */
- if (vq->orig_msg->rep->security > sec_status_bogus)
- {
- verbose(VERB_ALGO, "response has already been validated");
- return 0;
- }
+ if(ret_rc != LDNS_RCODE_NOERROR || !ret_msg)
+ rcode = ret_rc;
+ else rcode = (int)FLAGS_GET_RCODE(ret_msg->rep->flags);
- rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) {
verbose(VERB_ALGO, "cannot validate non-answer, rcode %s",
ldns_lookup_by_id(ldns_rcodes, rcode)?
ldns_lookup_by_id(ldns_rcodes, rcode)->name:"??");
return 0;
}
+
+ /* validate unchecked, and re-validate bogus messages */
+ if (ret_msg && ret_msg->rep->security > sec_status_bogus)
+ {
+ verbose(VERB_ALGO, "response has already been validated");
+ return 0;
+ }
return 1;
}
/* Since we are here, there must be nothing in the ANSWER section to
* validate. */
/* (Note: CNAME/DNAME responses will not directly get here --
- * instead they are broken down into individual CNAME/DNAME/final answer
- * responses. - TODO this will change though) */
+ * instead, they are chased down into indiviual CNAME validations,
+ * and at the end of the cname chain a POSITIVE, or CNAME_NOANSWER
+ * validation.) */
/* validate the AUTHORITY section */
int has_valid_nsec = 0; /* If true, then the NODATA has been proven.*/
chase_reply->security = sec_status_secure;
}
+/**
+ * Validate CNAME response, or DNAME+CNAME.
+ * This is just like a positive proof, except that this is about a
+ * DNAME+CNAME. Possible wildcard proof.
+ * Also refuses wildcarded DNAMEs.
+ *
+ * Note that by the time this method is called, the process of finding the
+ * trusted DNSKEY rrset that signs this response must already have been
+ * completed.
+ *
+ * @param env: module env for verify.
+ * @param ve: validator env for verify.
+ * @param qchase: query that was made.
+ * @param chase_reply: answer to that query to validate.
+ * @param key_entry: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
+ */
+static void
+validate_cname_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+ struct key_entry_key* key_entry)
+{
+ uint8_t* wc = NULL;
+ int wc_NSEC_ok = 0;
+ int dname_seen = 0;
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
+ enum sec_status sec;
+
+ /* validate the ANSWER section - this will be the CNAME (+DNAME) */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
+ s = chase_reply->rrsets[i];
+ /* Skip the CNAME following a (validated) DNAME.
+ * Because of the normalization routines in the iterator,
+ * there will always be an unsigned CNAME following a DNAME
+ * (unless qtype=DNAME). */
+ if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) {
+ dname_seen = 0;
+ /* CNAME was synthesized by our own iterator */
+ /* since the DNAME verified, mark the CNAME as secure */
+ ((struct packed_rrset_data*)s->entry.data)->security =
+ sec_status_secure;
+ ((struct packed_rrset_data*)s->entry.data)->trust =
+ rrset_trust_validated;
+ continue;
+ }
+
+ /* Verify the answer rrset */
+ sec = val_verify_rrset_entry(env, ve, s, key_entry);
+ /* If the (answer) rrset failed to validate, then this
+ * message is BAD. */
+ if(sec != sec_status_secure) {
+ log_nametypeclass(VERB_ALGO, "Cname response has "
+ "failed ANSWER rrset: ", s->rk.dname,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+ /* Check to see if the rrset is the result of a wildcard
+ * expansion. If so, an additional check will need to be
+ * made in the authority section. */
+ if(!val_rrset_wildcard(s, &wc)) {
+ log_nametypeclass(VERB_ALGO, "Cname response has "
+ "inconsistent wildcard sigs: ", s->rk.dname,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+
+ /* Notice a DNAME that should be followed by an unsigned
+ * CNAME. */
+ if(qchase->qtype != LDNS_RR_TYPE_DNAME &&
+ ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
+ dname_seen = 1;
+ /* we will not follow a wildcarded DNAME because
+ * its synthesized expansion is underdefined */
+ if(dname_is_wild(s->rk.dname)) {
+ log_nametypeclass(VERB_ALGO, "cannot follow "
+ "wildcard DNAME: ", s->rk.dname,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+ }
+ }
+
+ /* validate the AUTHORITY section as well - this will generally be
+ * the NS rrset (which could be missing, no problem) */
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+ s = chase_reply->rrsets[i];
+ sec = val_verify_rrset_entry(env, ve, s, key_entry);
+ /* If anything in the authority section fails to be secure,
+ * we have a bad message. */
+ if(sec != sec_status_secure) {
+ log_nametypeclass(VERB_ALGO, "Cname response has "
+ "failed AUTHORITY rrset: ", s->rk.dname,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+
+ /* If this is a positive wildcard response, and we have a
+ * (just verified) NSEC record, try to use it to 1) prove
+ * that qname doesn't exist and 2) that the correct wildcard
+ * was used. */
+ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) {
+ if(val_nsec_proves_positive_wildcard(s, qchase, wc)) {
+ wc_NSEC_ok = 1;
+ }
+ /* if not, continue looking for proof */
+ }
+
+ /* Otherwise, if this is a positive wildcard response and
+ * we have NSEC3 records */
+ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) {
+ nsec3s_seen = 1;
+ }
+ }
+
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+ /* TODO NSEC3 positive wildcard proof */
+ /* possibly: wc_NSEC_ok = 1; */
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+ * response, fail. */
+ if(wc != NULL && !wc_NSEC_ok) {
+ verbose(VERB_ALGO, "CNAME response was wildcard "
+ "expansion and did not prove original data "
+ "did not exist");
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+
+ verbose(VERB_ALGO, "Successfully validated CNAME response");
+ chase_reply->security = sec_status_secure;
+}
+
+/**
+ * Validate CNAME NOANSWER response, no more data after a CNAME chain.
+ * This can be a NODATA or a NAME ERROR case, but not both at the same time.
+ * We don't know because the rcode has been set to NOERROR by the CNAME.
+ *
+ * Note that by the time this method is called, the process of finding the
+ * trusted DNSKEY rrset that signs this response must already have been
+ * completed.
+ *
+ * @param env: module env for verify.
+ * @param ve: validator env for verify.
+ * @param qchase: query that was made.
+ * @param chase_reply: answer to that query to validate.
+ * @param key_entry: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
+ */
+static void
+validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+ struct key_entry_key* key_entry)
+{
+ /* Since we are here, there must be nothing in the ANSWER section to
+ * validate. */
+
+ /* validate the AUTHORITY section */
+ int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
+ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
+ proven closest encloser. */
+ uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */
+ int nxdomain_valid_nsec = 0; /* if true, namerror has been proven */
+ int nxdomain_valid_wnsec = 0;
+ int nsec3s_seen = 0; /* nsec3s seen */
+ struct ub_packed_rrset_key* s;
+ enum sec_status sec;
+ size_t i;
+
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+ s = chase_reply->rrsets[i];
+ sec = val_verify_rrset_entry(env, ve, s, key_entry);
+ if(sec != sec_status_secure) {
+ log_nametypeclass(VERB_ALGO, "CNAMEnoanswer response has "
+ "failed AUTHORITY rrset: ", s->rk.dname,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+
+ /* If we encounter an NSEC record, try to use it to prove
+ * NODATA.
+ * This needs to handle the ENT NODATA case. */
+ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) {
+ if(nsec_proves_nodata(s, qchase)) {
+ nodata_valid_nsec = 1;
+ if(dname_is_wild(s->rk.dname))
+ wc = s->rk.dname;
+ }
+ if(val_nsec_proves_name_error(s, qchase->qname)) {
+ ce = nsec_closest_encloser(qchase->qname, s);
+ nxdomain_valid_nsec = 1;
+ }
+ if(val_nsec_proves_no_wc(s, qchase->qname,
+ qchase->qname_len))
+ nxdomain_valid_wnsec = 1;
+ } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) {
+ nsec3s_seen = 1;
+ }
+ }
+
+ /* check to see if we have a wildcard NODATA proof. */
+
+ /* The wildcard NODATA is 1 NSEC proving that qname does not exists
+ * (and also proving what the closest encloser is), and 1 NSEC
+ * showing the matching wildcard, which must be *.closest_encloser. */
+ if(wc && !ce)
+ nodata_valid_nsec = 0;
+ else if(wc && ce) {
+ log_assert(dname_is_wild(wc));
+ /* first label wc is \001*, so remove and compare to ce */
+ if(query_dname_compare(wc+2, ce) != 0) {
+ nodata_valid_nsec = 0;
+ }
+ }
+ if(nxdomain_valid_nsec && !nxdomain_valid_wnsec) {
+ /* name error is missing wildcard denial proof */
+ nxdomain_valid_nsec = 0;
+ }
+
+ if(nodata_valid_nsec && nxdomain_valid_nsec) {
+ verbose(VERB_ALGO, "CNAMEnoanswer proves that name exists "
+ "and not exists, bogus");
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
+ /* TODO handle NSEC3 proof here */
+ /* and set nodata_valid_nsec=1; if so */
+ }
+
+ if(!nodata_valid_nsec && !nxdomain_valid_nsec) {
+ verbose(VERB_ALGO, "CNAMEnoanswer response failed to prove "
+ "status with NSEC/NSEC3");
+ if(verbosity >= VERB_ALGO)
+ log_dns_msg("Failed CNAMEnoanswer", qchase, chase_reply);
+ chase_reply->security = sec_status_bogus;
+ return;
+ }
+
+ if(nodata_valid_nsec)
+ verbose(VERB_ALGO, "successfully validated CNAME to a "
+ "NODATA response.");
+ else verbose(VERB_ALGO, "successfully validated CNAME to a "
+ "NAMEERROR response.");
+ chase_reply->security = sec_status_secure;
+}
+
/**
* Process init state for validator.
* Process the INIT state. First tier responses start in the INIT state.
{
uint8_t* lookup_name;
size_t lookup_len;
- if(!needs_validation(qstate, vq)) {
- qstate->return_rcode = LDNS_RCODE_NOERROR;
- qstate->return_msg = vq->orig_msg;
- qstate->ext_state[id] = module_finished;
- return 0;
- }
+ enum val_classification subtype = val_classify_response(&vq->qchase,
+ vq->orig_msg->rep, vq->cname_skip);
+
vq->trust_anchor = anchors_lookup(ve->anchors, vq->qchase.qname,
vq->qchase.qname_len, vq->qchase.qclass);
if(vq->trust_anchor == NULL) {
}
/* Determine the signer/lookup name */
- val_find_signer(&vq->qchase, vq->chase_reply,
- &vq->signer_name, &vq->signer_len);
+ val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep,
+ vq->cname_skip, &vq->signer_name, &vq->signer_len);
if(vq->signer_name == NULL) {
lookup_name = vq->qchase.qname;
lookup_len = vq->qchase.qname_len;
lookup_len = vq->signer_len;
}
+ if(vq->cname_skip > 0 || subtype == VAL_CLASS_CNAME) {
+ /* extract this part of orig_msg into chase_reply for
+ * the eventual VALIDATE stage */
+ val_fill_reply(vq->chase_reply, vq->orig_msg->rep,
+ vq->cname_skip, lookup_name, lookup_len);
+ log_dns_msg("chased extract", &vq->qchase, vq->chase_reply);
+ }
+
vq->key_entry = key_cache_obtain(ve->kcache, lookup_name, lookup_len,
vq->qchase.qclass, qstate->region);
return 1;
}
- subtype = val_classify_response(&vq->qchase, vq->chase_reply);
+ subtype = val_classify_response(&vq->qchase, vq->orig_msg->rep,
+ vq->cname_skip);
switch(subtype) {
case VAL_CLASS_POSITIVE:
verbose(VERB_ALGO, "Validating a positive response");
case VAL_CLASS_CNAME:
verbose(VERB_ALGO, "Validating a cname response");
+ validate_cname_response(qstate->env, ve,
+ &vq->qchase, vq->chase_reply, vq->key_entry);
/*
* TODO special CNAME state or routines
+ * validate CNAME + wildcard NSEC or DNAME.
+ * reject wildcarded DNAMEs.
validate_cname_response(vq->qchase, vq->chase_reply,
vq->key_entry);
*/
break;
+ case VAL_CLASS_CNAMENOANSWER:
+ verbose(VERB_ALGO, "Validating a cname noanswer "
+ "response");
+ validate_cname_noanswer_response(qstate->env, ve,
+ &vq->qchase, vq->chase_reply, vq->key_entry);
+ /*
+ * TODO special CNAME state or routines
+ * see if nodata or noerror can be proven.
+ * but not both.
+ validate_cnamenoanswer_response(vq->qchase,
+ vq->chase_reply, vq->key_entry);
+ */
+ break;
+
case VAL_CLASS_ANY:
verbose(VERB_ALGO, "Validating a positive ANY "
"response");
processFinished(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id)
{
- /* TODO CNAME query restarts */
+ enum val_classification subtype = val_classify_response(&vq->qchase,
+ vq->orig_msg->rep, vq->cname_skip);
+
+ /* store overall validation result in orig_msg */
+ if(vq->cname_skip == 0)
+ vq->orig_msg->rep->security = vq->chase_reply->security;
+ else {
+ /* use the lowest security status as end result. */
+ if(vq->chase_reply->security < vq->orig_msg->rep->security)
+ vq->orig_msg->rep->security =
+ vq->chase_reply->security;
+ }
+
+ if(vq->chase_reply->security != sec_status_bogus &&
+ subtype == VAL_CLASS_CNAME) {
+ /* chase the CNAME; process next part of the message */
+ if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep,
+ &vq->cname_skip)) {
+ verbose(VERB_ALGO, "validator: failed to chase CNAME");
+ vq->orig_msg->rep->security = sec_status_bogus;
+ } else {
+ /* restart process for new qchase at cname_skip */
+ log_query_info(VERB_DETAIL, "validator: chased to",
+ &vq->qchase);
+ vq->chase_reply->security = sec_status_unchecked;
+ vq->state = VAL_INIT_STATE;
+ return 1;
+ }
+ }
+
+ if(vq->orig_msg->rep->security == sec_status_secure) {
+ /* Do not store the validated status of the dropped RRsets.
+ * (only secure is reused). These rrsets are apparantly
+ * added on maliciously, or are unsigned additional data */
+ val_dump_nonsecure(vq->orig_msg->rep);
+ }
/* if the result is bogus - set message ttl to bogus ttl to avoid
* endless bogus revalidation */
- if(vq->chase_reply->security == sec_status_bogus) {
- vq->chase_reply->ttl = time(0) + ve->bogus_ttl;
+ if(vq->orig_msg->rep->security == sec_status_bogus) {
+ vq->orig_msg->rep->ttl = time(0) + ve->bogus_ttl;
}
/* store results in cache */
- if(!dns_cache_store(qstate->env, &vq->qchase, vq->chase_reply, 0)) {
+ if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
+ vq->orig_msg->rep, 0)) {
log_err("out of memory caching validator results");
}
qstate->return_rcode = LDNS_RCODE_NOERROR;
qstate->return_msg = vq->orig_msg;
- qstate->return_msg->rep = vq->chase_reply;
qstate->ext_state[id] = module_finished;
return 0;
}
if(event == module_event_moddone) {
/* check if validation is needed */
verbose(VERB_ALGO, "validator: nextmodule returned");
- if(qstate->return_rcode != LDNS_RCODE_NOERROR &&
- qstate->return_rcode != LDNS_RCODE_NXDOMAIN &&
- qstate->return_rcode != LDNS_RCODE_YXDOMAIN) {
- verbose(VERB_ALGO, "rcode for error not validated");
- qstate->ext_state[id] = module_finished;
- return;
- }
- if(qstate->query_flags & BIT_CD) {
+ if(!needs_validation(qstate, qstate->return_rcode,
+ qstate->return_msg)) {
/* no need to validate this */
- verbose(VERB_ALGO, "CD bit set: query not validated");
qstate->ext_state[id] = module_finished;
return;
}
goto return_bogus;
}
- subtype = val_classify_response(qinfo, msg->rep);
+ subtype = val_classify_response(qinfo, msg->rep, 0);
if(subtype == VAL_CLASS_POSITIVE) {
struct ub_packed_rrset_key* ds;
enum sec_status sec;