]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
CNAME validation.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 23 Aug 2007 15:23:45 +0000 (15:23 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 23 Aug 2007 15:23:45 +0000 (15:23 +0000)
git-svn-id: file:///svn/unbound/trunk@542 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
validator/val_utils.c
validator/val_utils.h
validator/validator.c
validator/validator.h

index 7eda8301a46877b2dc7a793ca8dba3036094a3e3..418f3104d75e7ec557add1aeee46df24a305b4f8 100644 (file)
@@ -1,3 +1,12 @@
+23 August 2007: Wouter
+       - CNAME handling - move needs_validation to before val_new().
+         val_new() setups the chase-reply to be an edited copy of the msg.
+         new classification, and find signer can find for it. 
+         removal of unsigned crap from additional, and query restart for
+         cname.
+       - refuse to follow wildcarded DNAMEs when validating.
+         But you can query for qtype ANY, or qtype DNAME and validate that.
+
 22 August 2007: Wouter
        - bogus TTL.
        - review - use val_error().
index 40e025e36ff879d864f1cbd41f1e59f112911474..c4a35b1942490d2865befda8c840d15a0878284f 100644 (file)
@@ -49,7 +49,8 @@
 #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;
@@ -60,6 +61,10 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep)
                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;
@@ -74,7 +79,7 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep)
        
        /* 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)
@@ -135,17 +140,57 @@ val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname,
                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], 
@@ -168,6 +213,21 @@ val_find_signer(struct query_info* qinf, struct reply_info* rep,
                                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");
@@ -408,3 +468,120 @@ val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc)
        *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--;
+               }
+       }
+}
index 2819c646935b570505a497f589e1ff89061d3869..83c60862fd0cd3efa56e847bd5b9f7682ccd048f 100644 (file)
@@ -66,32 +66,42 @@ enum val_classification {
        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
@@ -169,4 +179,39 @@ int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset);
  */
 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 */
index e24324962dfb64a69305c72c020a9fc69a4e36f8..b41fffa9d8c259f4466d780823c5a4af3dc6cba9 100644 (file)
@@ -143,7 +143,17 @@ val_new(struct module_qstate* qstate, int id)
                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;
 }
 
@@ -170,12 +180,14 @@ val_error(struct module_qstate* qstate, int id)
  * 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;
 
@@ -186,20 +198,23 @@ needs_validation(struct module_qstate* qstate, struct val_qstate* vq)
                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;
 }
 
@@ -422,8 +437,9 @@ validate_nodata_response(struct module_env* env, struct val_env* ve,
        /* 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.*/
@@ -648,6 +664,265 @@ validate_any_response(struct module_env* env, struct val_env* ve,
        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.
@@ -672,12 +947,9 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
 {
        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) {
@@ -689,8 +961,8 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
        }
 
        /* 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;
@@ -699,6 +971,14 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq,
                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);
        
@@ -877,7 +1157,8 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
                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");
@@ -899,13 +1180,31 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
 
                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");
@@ -935,21 +1234,56 @@ static int
 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;
 }
@@ -1019,16 +1353,9 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id,
        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;
                }
@@ -1175,7 +1502,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
                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;
index 1d0989bfd506b8f5bcceb56d0f51626826d01d55..ea75e5ef84eff9d372c367b3eb075ea583e48b02 100644 (file)
@@ -113,10 +113,21 @@ struct val_qstate {
         * The chased reply, extract from original message. Can be:
         *      o CNAME
         *      o DNAME + CNAME
-        *      o answer plus authority, additional (nsecs).
+        *      o answer 
+        *      plus authority, additional (nsecs) that have same signature.
         */
        struct reply_info* chase_reply;
 
+       /**
+        * The cname skip value; the number of rrsets that have been skipped
+        * due to chasing cnames. This is the offset into the 
+        * orig_msg->rep->rrsets array, into the answer section.
+        * starts at 0 - for the full original message.
+        * if it is >0 - qchase followed the cname, chase_reply setup to be
+        * that message and relevant authority rrsets.
+        */
+       size_t cname_skip;
+
        /** the trust anchor rrset */
        struct trust_anchor* trust_anchor;