]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Major rationalisation of DNSSEC validation.
authorSimon Kelley <simon@thekelleys.org.uk>
Tue, 15 Dec 2015 10:20:39 +0000 (10:20 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Tue, 15 Dec 2015 10:20:39 +0000 (10:20 +0000)
Much gnarly special-case code removed and replaced with correct
general implementaion. Checking of zone-status moved to DNSSEC code,
where it should be, vastly simplifying query-forwarding code.

src/dnsmasq.h
src/dnssec.c
src/forward.c

index f42acdba7965394922904ffd4afa57d988c8061c..023a1cfb12f88d1d8321b34e79ebb89bd4c00223 100644 (file)
@@ -586,12 +586,8 @@ struct hostsfile {
 #define STAT_NEED_KEY           5
 #define STAT_TRUNCATED          6
 #define STAT_SECURE_WILDCARD    7
-#define STAT_NO_SIG             8
-#define STAT_NO_DS              9
-#define STAT_NO_NS             10
-#define STAT_NEED_DS_NEG       11
-#define STAT_CHASE_CNAME       12
-#define STAT_INSECURE_DS       13
+#define STAT_OK                 8
+#define STAT_ABANDONED          9
 
 #define FREC_NOREBIND           1
 #define FREC_CHECKING_DISABLED  2
@@ -601,8 +597,7 @@ struct hostsfile {
 #define FREC_AD_QUESTION       32
 #define FREC_DO_QUESTION       64
 #define FREC_ADDED_PHEADER    128
-#define FREC_CHECK_NOSIGN     256
-#define FREC_TEST_PKTSZ       512
+#define FREC_TEST_PKTSZ       256
 
 #ifdef HAVE_DNSSEC
 #define HASH_SIZE 20 /* SHA-1 digest size */
@@ -626,9 +621,7 @@ struct frec {
 #ifdef HAVE_DNSSEC 
   int class, work_counter;
   struct blockdata *stash; /* Saved reply, whilst we validate */
-  struct blockdata *orig_domain; /* domain of original query, whilst
-                                   we're seeing is if in unsigned domain */
-  size_t stash_len, name_start, name_len;
+  size_t stash_len;
   struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
   struct frec *blocking_query; /* Query which is blocking us. */
 #endif
@@ -1162,8 +1155,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
 size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
-int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
-int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
+                         int check_unsigned, int *neganswer, int *nons);
 int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
 size_t filter_rrsigs(struct dns_header *header, size_t plen);
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
index b4dc14e08328af48172e9d69d7e17a789db2cf0a..de7b3358a1ce234de246f1144ce86d4451e95d20 100644 (file)
@@ -65,8 +65,10 @@ static char *algo_digest_name(int algo)
     case 8: return "sha256";
     case 10: return "sha512";
     case 12: return "gosthash94";
+#ifndef NO_NETTLE_ECC
     case 13: return "sha256";
     case 14: return "sha384";
+#endif
     default: return NULL;
     }
 }
@@ -592,30 +594,30 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
     }
 }
 
-static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
+static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
 {
   unsigned char **p;
-  int new_sz = *sz;
-  
-  if (new_sz > new)
+  int old = *szp;
+
+  if (old >= new+1)
     return 1;
 
   if (new >= 100)
     return 0;
 
-  new_sz += 5;
+  new += 5;
   
-  if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))
+  if (!(p = whine_malloc(new * sizeof(unsigned char **))))
     return 0;  
   
-  if (*wkspc)
+  if (old != 0 && *wkspc)
     {
-      memcpy(p, *wkspc, *sz * sizeof(unsigned char **));
+      memcpy(p, *wkspc, old * sizeof(unsigned char **));
       free(*wkspc);
     }
   
   *wkspc = p;
-  *sz = new_sz;
+  *szp = new;
 
   return 1;
 }
@@ -706,47 +708,28 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
     } while (swap);
 }
 
-/* Validate a single RRset (class, type, name) in the supplied DNS reply 
-   Return code:
-   STAT_SECURE   if it validates.
-   STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
-   (In this case *wildcard_out points to the "body" of the wildcard within name.) 
-   STAT_NO_SIG no RRsigs found.
-   STAT_INSECURE RRset empty.
-   STAT_BOGUS    signature is wrong, bad packet.
-   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
-
-   if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
-   otherwise find the key in the cache.
+static unsigned char **rrset = NULL, **sigs = NULL;
 
-   name is unchanged on exit. keyname is used as workspace and trashed.
-*/
-static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, 
-                         char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
+/* Get pointers to RRset menbers and signature(s) for same.
+   Check signatures, and return keyname associated in keyname. */
+static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, 
+                        char *name, char *keyname, int *sigcnt, int *rrcnt)
 {
-  static unsigned char **rrset = NULL, **sigs = NULL;
-  static int rrset_sz = 0, sig_sz = 0;
-  
+  static int rrset_sz = 0, sig_sz = 0; 
   unsigned char *p;
-  int rrsetidx, sigidx, res, rdlen, j, name_labels;
-  struct crec *crecp = NULL;
-  int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
-  u16 *rr_desc = get_desc(type);
-  if (wildcard_out)
-    *wildcard_out = NULL;
-  
+  int rrsetidx, sigidx, j, rdlen, res;
+  int name_labels = count_labels(name); /* For 4035 5.3.2 check */
+  int gotkey = 0;
+
   if (!(p = skip_questions(header, plen)))
     return STAT_BOGUS;
-  
-  name_labels = count_labels(name); /* For 4035 5.3.2 check */
 
-  /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
+   /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
   for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
        j != 0; j--) 
     {
       unsigned char *pstart, *pdata;
-      int stype, sclass;
+      int stype, sclass, algo, type_covered, labels, sig_expiration, sig_inception;
 
       pstart = p;
       
@@ -762,14 +745,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
       GETSHORT(rdlen, p);
       
       if (!CHECK_LEN(header, p, plen, rdlen))
-       return STAT_BOGUS
+       return 0
       
       if (res == 1 && sclass == class)
        {
          if (stype == type)
            {
              if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
-               return STAT_BOGUS
+               return 0
              
              rrset[rrsetidx++] = pstart;
            }
@@ -777,14 +760,54 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
          if (stype == T_RRSIG)
            {
              if (rdlen < 18)
-               return STAT_BOGUS; /* bad packet */ 
+               return 0; /* bad packet */ 
              
              GETSHORT(type_covered, p);
+             algo = *p++;
+             labels = *p++;
+             p += 4; /* orig_ttl */
+             GETLONG(sig_expiration, p);
+             GETLONG(sig_inception, p);
+             p += 2; /* key_tag */
              
-             if (type_covered == type)
+             if (gotkey)
+               {
+                 /* If there's more than one SIG, ensure they all have same keyname */
+                 if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
+                   return 0;
+               }
+             else
+               {
+                 gotkey = 1;
+                 
+                 if (!extract_name(header, plen, &p, keyname, 1, 0))
+                   return 0;
+                 
+                 /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
+                    the name of the zone containing the RRset. We can't tell that
+                    for certain, but we can check that  the RRset name is equal to
+                    or encloses the signers name, which should be enough to stop 
+                    an attacker using signatures made with the key of an unrelated 
+                    zone he controls. Note that the root key is always allowed. */
+                 if (*keyname != 0)
+                   {
+                     char *name_start;
+                     for (name_start = name; !hostname_isequal(name_start, keyname); )
+                       if ((name_start = strchr(name_start, '.')))
+                         name_start++; /* chop a label off and try again */
+                       else
+                         return 0;
+                   }
+               }
+                 
+             /* Don't count signatures for algos we don't support */
+             if (check_date_range(sig_inception, sig_expiration) &&
+                 labels <= name_labels &&
+                 type_covered == type && 
+                 algo_digest_name(algo))
                {
                  if (!expand_workspace(&sigs, &sig_sz, sigidx))
-                   return STAT_BOGUS
+                   return 0
                  
                  sigs[sigidx++] = pdata;
                } 
@@ -794,17 +817,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
        }
       
       if (!ADD_RDLEN(header, p, plen, rdlen))
-       return STAT_BOGUS;
+       return 0;
     }
   
-  /* RRset empty */
-  if (rrsetidx == 0)
-    return STAT_INSECURE; 
+  *sigcnt = sigidx;
+  *rrcnt = rrsetidx;
+
+  return 1;
+}
+
+/* Validate a single RRset (class, type, name) in the supplied DNS reply 
+   Return code:
+   STAT_SECURE   if it validates.
+   STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+   (In this case *wildcard_out points to the "body" of the wildcard within name.) 
+   STAT_BOGUS    signature is wrong, bad packet.
+   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
+   STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
+
+   if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
+   otherwise find the key in the cache.
 
-  /* no RRSIGs */
-  if (sigidx == 0)
-    return STAT_NO_SIG; 
+   name is unchanged on exit. keyname is used as workspace and trashed.
+
+   Call explore_rrset first to find and count RRs and sigs.
+*/
+static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 
+                         char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
+{
+  unsigned char *p;
+  int rdlen, j, name_labels;
+  struct crec *crecp = NULL;
+  int algo, labels, orig_ttl, key_tag;
+  u16 *rr_desc = get_desc(type);
+  if (wildcard_out)
+    *wildcard_out = NULL;
   
+  name_labels = count_labels(name); /* For 4035 5.3.2 check */
+
   /* Sort RRset records into canonical order. 
      Note that at this point keyname and daemon->workspacename buffs are
      unused, and used as workspace by the sort. */
@@ -828,44 +879,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
       algo = *p++;
       labels = *p++;
       GETLONG(orig_ttl, p);
-      GETLONG(sig_expiration, p);
-      GETLONG(sig_inception, p);
+      p += 8; /* sig_expiration, sig_inception already checked */
       GETSHORT(key_tag, p);
       
       if (!extract_name(header, plen, &p, keyname, 1, 0))
        return STAT_BOGUS;
 
-      /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
-        the name of the zone containing the RRset. We can't tell that
-        for certain, but we can check that  the RRset name is equal to
-        or encloses the signers name, which should be enough to stop 
-        an attacker using signatures made with the key of an unrelated 
-        zone he controls. Note that the root key is always allowed. */
-      if (*keyname != 0)
-       {
-         int failed = 0;
-         
-         for (name_start = name; !hostname_isequal(name_start, keyname); )
-           if ((name_start = strchr(name_start, '.')))
-             name_start++; /* chop a label off and try again */
-           else
-             {
-               failed = 1;
-               break;
-             }
-
-         /* Bad sig, try another */
-         if (failed)
-           continue;
-       }
-      
-      /* Other 5.3.1 checks */
-      if (!check_date_range(sig_inception, sig_expiration) ||
-         labels > name_labels ||
-         !(hash = hash_find(algo_digest_name(algo))) ||
+      if (!(hash = hash_find(algo_digest_name(algo))) ||
          !hash_init(hash, &ctx, &digest))
        continue;
-       
+      
       /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
       if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
        return STAT_NEED_KEY;
@@ -971,10 +994,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
 /* The DNS packet is expected to contain the answer to a DNSKEY query.
    Put all DNSKEYs in the answer which are valid into the cache.
    return codes:
-         STAT_SECURE   At least one valid DNSKEY found and in cache.
-        STAT_BOGUS    No DNSKEYs found, which  can be validated with DS,
-                      or self-sign for DNSKEY RRset is not valid, bad packet.
-        STAT_NEED_DS  DS records to validate a key not found, name in keyname 
+         STAT_OK           Done, key(s) in cache.
+        STAT_BOGUS        No DNSKEYs found, which  can be validated with DS,
+                          or self-sign for DNSKEY RRset is not valid, bad packet.
+        STAT_NEED_DS      DS records to validate a key not found, name in keyname 
+        STAT_NEED_DNSKEY  DNSKEY records to validate a key not found, name in keyname 
 */
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {
@@ -1001,23 +1025,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
       return STAT_NEED_DS;
     }
   
-  /* If we've cached that DS provably doesn't exist, result must be INSECURE */
-  if (crecp->flags & F_NEG)
-    return STAT_INSECURE_DS;
-  
-  /* 4035 5.2 
-     If the validator does not support any of the algorithms listed in an
-     authenticated DS RRset, then the resolver has no supported
-     authentication path leading from the parent to the child.  The
-     resolver should treat this case as it would the case of an
-     authenticated NSEC RRset proving that no DS RRset exists,  */
-  for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
-    if (hash_find(ds_digest_name(recp1->addr.ds.digest)))
-      break;
-  
-  if (!recp1)
-    return STAT_INSECURE_DS;
-
   /* NOTE, we need to find ONE DNSKEY which matches the DS */
   for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) 
     {
@@ -1070,7 +1077,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
          void *ctx;
          unsigned char *digest, *ds_digest;
          const struct nettle_hash *hash;
-         
+         int sigcnt, rrcnt;
+
          if (recp1->addr.ds.algo == algo && 
              recp1->addr.ds.keytag == keytag &&
              recp1->uid == (unsigned int)class &&
@@ -1088,10 +1096,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
              
              from_wire(name);
              
-             if (recp1->addr.ds.keylen == (int)hash->digest_size &&
+             if (!(recp1->flags & F_NEG) &&
+                 recp1->addr.ds.keylen == (int)hash->digest_size &&
                  (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
                  memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
-                 validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
+                 explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
+                 sigcnt != 0 && rrcnt != 0 &&
+                 validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, 
+                                NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
                {
                  valid = 1;
                  break;
@@ -1112,7 +1124,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
        {
          /* Ensure we have type, class  TTL and length */
          if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
-           return STAT_INSECURE; /* bad packet */
+           return STAT_BOGUS; /* bad packet */
          
          GETSHORT(qtype, p); 
          GETSHORT(qclass, p);
@@ -1198,7 +1210,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
       
       /* commit cache insert. */
       cache_end_insert();
-      return STAT_SECURE;
+      return STAT_OK;
     }
 
   log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
@@ -1207,12 +1219,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
 
 /* The DNS packet is expected to contain the answer to a DS query
    Put all DSs in the answer which are valid into the cache.
+   Also handles replies which prove that there's no DS at this location, 
+   either because the zone is unsigned or this isn't a zone cut. These are
+   cached too.
    return codes:
-   STAT_SECURE      At least one valid DS found and in cache.
-   STAT_NO_DS       It's proved there's no DS here.
-   STAT_NO_NS       It's proved there's no DS _or_ NS here.
+   STAT_OK          At least one valid DS found and in cache.
    STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.
    STAT_NEED_KEY    DNSKEY records to validate a DS not found, name in keyname
+   STAT_NEED_DS     DS record needed.
 */
 
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
@@ -1230,7 +1244,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
   if (qtype != T_DS || qclass != class)
     val = STAT_BOGUS;
   else
-    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons);
+    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
   /* Note dnssec_validate_reply() will have cached positive answers */
   
   if (val == STAT_INSECURE)
@@ -1242,22 +1256,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
   
   if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
     val = STAT_BOGUS;
-   
-  /* If we return STAT_NO_SIG, name contains the name of the DS query */
-  if (val == STAT_NO_SIG)
-    return val;
   
   /* If the key needed to validate the DS is on the same domain as the DS, we'll
      loop getting nowhere. Stop that now. This can happen of the DS answer comes
      from the DS's zone, and not the parent zone. */
-  if (val == STAT_BOGUS ||  (val == STAT_NEED_KEY && hostname_isequal(name, keyname)))
+  if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname)))
     {
       log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
       return STAT_BOGUS;
     }
+  
+  if (val != STAT_SECURE)
+    return val;
 
   /* By here, the answer is proved secure, and a positive answer has been cached. */
-  if (val == STAT_SECURE && neganswer)
+  if (neganswer)
     {
       int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
       unsigned long ttl, minttl = ULONG_MAX;
@@ -1317,15 +1330,14 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
          
          cache_end_insert();  
          
-         log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS");
+         log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
        }
-
-      return nons ? STAT_NO_NS : STAT_NO_DS; 
     }
 
-  return val;
+  return STAT_OK;
 }
 
+
 /* 4034 6.1 */
 static int hostname_cmp(const char *a, const char *b)
 {
@@ -1452,7 +1464,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
   int mask = 0x80 >> (type & 0x07);
 
   if (nons)
-    *nons = 0;
+    *nons = 1;
   
   /* Find NSEC record that proves name doesn't exist */
   for (i = 0; i < nsec_count; i++)
@@ -1480,9 +1492,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
          /* rdlen is now length of type map, and p points to it */
          
          /* If we can prove that there's no NS record, return that information. */
-         if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0)
-           *nons = 1;
+         if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
+           *nons = 0;
          
+         if (rdlen >= 2 && p[0] == 0)
+           {
+             /* A CNAME answer would also be valid, so if there's a CNAME is should 
+                have been returned. */
+             if ((p[2] & (0x80 >> T_CNAME)) != 0)
+               return STAT_BOGUS;
+             
+             /* If the SOA bit is set for a DS record, then we have the
+                DS from the wrong side of the delegation. */
+             if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+               return STAT_BOGUS;
+           }
+
          while (rdlen >= 2)
            {
              if (!CHECK_LEN(header, p, plen, rdlen))
@@ -1586,7 +1611,7 @@ static int base32_decode(char *in, unsigned char *out)
 static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
                                char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)
 {
-  int i, hash_len, salt_len, base32_len, rdlen;
+  int i, hash_len, salt_len, base32_len, rdlen, flags;
   unsigned char *p, *psave;
 
   for (i = 0; i < nsec_count; i++)
@@ -1599,7 +1624,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
        p += 8; /* class, type, TTL */
        GETSHORT(rdlen, p);
        psave = p;
-       p += 4; /* algo, flags, iterations */
+       p++; /* algo */
+       flags = *p++; /* flags */
+       p += 2; /* iterations */
        salt_len = *p++; /* salt_len */
        p += salt_len; /* salt */
        hash_len = *p++; /* p now points to next hashed name */
@@ -1626,16 +1653,29 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
                  return 0;
                
                /* If we can prove that there's no NS record, return that information. */
-               if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0)
-                 *nons = 1;
+               if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
+                 *nons = 0;
                
+               if (rdlen >= 2 && p[0] == 0)
+                 {
+                   /* A CNAME answer would also be valid, so if there's a CNAME is should 
+                      have been returned. */
+                   if ((p[2] & (0x80 >> T_CNAME)) != 0)
+                     return 0;
+                   
+                   /* If the SOA bit is set for a DS record, then we have the
+                      DS from the wrong side of the delegation. */
+                   if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+                     return 0;
+                 }
+
                while (rdlen >= 2)
                  {
                    if (p[0] == type >> 8)
                      {
                        /* Does the NSEC3 say our type exists? */
                        if (offset < p[1] && (p[offset+2] & mask) != 0)
-                         return STAT_BOGUS;
+                         return 0;
                        
                        break; /* finshed checking */
                      }
@@ -1643,7 +1683,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
                    rdlen -= p[1];
                    p +=  p[1];
                  }
-
+               
                return 1;
              }
            else if (rc < 0)
@@ -1651,16 +1691,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
                /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
                   wrap around case, name-hash falls between NSEC3 name-hash and end */
                if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
-                 return 1;
+                 {
+                   if ((flags & 0x01) && nons) /* opt out */
+                     *nons = 0;
+
+                   return 1;
+                 }
              }
            else 
              {
                /* wrap around case, name falls between start and next domain name */
                if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0)
-                 return 1;
+                 {
+                   if ((flags & 0x01) && nons) /* opt out */
+                     *nons = 0;
+
+                   return 1;
+                 }
              }
          }
       }
+
   return 0;
 }
 
@@ -1673,7 +1724,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
   char *closest_encloser, *next_closest, *wildcard;
   
   if (nons)
-    *nons = 0;
+    *nons = 1;
   
   /* Look though the NSEC3 records to find the first one with 
      an algorithm we support (currently only algo == 1).
@@ -1813,16 +1864,81 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
   
   return STAT_SECURE;
 }
-    
-/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
-/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
+
+/* Check signing status of name.
+   returns:
+   STAT_SECURE zone is signed.
+   STAT_INSECURE zone proved unsigned.
+   STAT_NEED_DS require DS record of name returned in keyname.
+   
+   name returned unaltered.
+*/
+static int zone_status(char *name, int class, char *keyname, time_t now)
+{
+  int name_start = strlen(name);
+  struct crec *crecp;
+  char *p;
+  
+  while (1)
+    {
+      strcpy(keyname, &name[name_start]);
+      
+      if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
+       return STAT_NEED_DS;
+      else
+       do 
+         {
+           if (crecp->uid == (unsigned int)class)
+             {
+               /* F_DNSSECOK misused in DS cache records to non-existance of NS record.
+                  F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
+                  but that's because there's no NS record either, ie this isn't the start
+                  of a zone. We only prove that the DNS tree below a node is unsigned when
+                  we prove that we're at a zone cut AND there's no DS record.
+               */        
+               if (crecp->flags & F_NEG)
+                 {
+                   if (crecp->flags & F_DNSSECOK)
+                     return STAT_INSECURE; /* proved no DS here */
+                 }
+               else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo))
+                 return STAT_INSECURE; /* algo we can't use - insecure */
+             }
+         }
+       while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
+      
+      if (name_start == 0)
+       break;
+
+      for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
+      
+      if (p != name)
+        p++;
+      
+      name_start = p - name;
+    } 
+
+  return STAT_SECURE;
+}
+       
+/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) 
+   Return code:
+   STAT_SECURE   if it validates.
+   STAT_INSECURE at least one RRset not validated, because in unsigned zone.
+   STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
+   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
+   STAT_NEED_DS  need DS to complete validation (name is returned in keyname) 
+*/
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
-                         int *class, int *neganswer, int *nons)
+                         int *class, int check_unsigned, int *neganswer, int *nons)
 {
-  unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
-  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
-  int i, j, rc, nsec_count, cname_count = CNAME_CHAIN;
-  int nsec_type = 0, have_answer = 0;
+  static unsigned char **targets = NULL;
+  static int target_sz = 0;
+
+  unsigned char *ans_start, *p1, *p2, **nsecs;
+  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
+  int i, j, rc, nsec_count;
+  int nsec_type;
 
   if (neganswer)
     *neganswer = 0;
@@ -1833,70 +1949,51 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
   if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
     return STAT_INSECURE;
 
-  qname = p1 = (unsigned char *)(header+1);
+  p1 = (unsigned char *)(header+1);
   
+   /* Find all the targets we're looking for answers to.
+     The zeroth array element is for the query, subsequent ones
+     for CNAME targets, unless the query is for a CNAME. */
+
+  if (!expand_workspace(&targets, &target_sz, 0))
+    return STAT_BOGUS;
+  
+  targets[0] = p1;
+  targetidx = 1;
+   
   if (!extract_name(header, plen, &p1, name, 1, 4))
     return STAT_BOGUS;
-
+  
   GETSHORT(qtype, p1);
   GETSHORT(qclass, p1);
   ans_start = p1;
-
-  if (qtype == T_ANY)
-    have_answer = 1;
  
-  /* Can't validate an RRISG query */
+  /* Can't validate an RRSIG query */
   if (qtype == T_RRSIG)
     return STAT_INSECURE;
- cname_loop:
-  for (j = ntohs(header->ancount); j != 0; j--) 
-    {
-      /* leave pointer to missing name in qname */
-           
-      if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
-       return STAT_BOGUS; /* bad packet */
-      
-      GETSHORT(type2, p1); 
-      GETSHORT(class2, p1);
-      p1 += 4; /* TTL */
-      GETSHORT(rdlen2, p1);
-
-      if (rc == 1 && qclass == class2)
-       {
-         /* Do we have an answer for the question? */
-         if (type2 == qtype)
-           {
-             have_answer = 1;
-             break;
-           }
-         else if (type2 == T_CNAME)
-           {
-             qname = p1;
-             
-             /* looped CNAMES */
-             if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0))
-               return STAT_BOGUS;
-              
-             p1 = ans_start;
-             goto cname_loop;
-           }
-       } 
-
-      if (!ADD_RDLEN(header, p1, plen, rdlen2))
-       return STAT_BOGUS;
-    }
-   
-  if (neganswer && !have_answer)
-    *neganswer = 1;
   
-  /* No data, therefore no sigs */
-  if (ntohs(header->ancount) + ntohs(header->nscount) == 0)
-    {
-      *keyname = 0;
-      return STAT_NO_SIG;
-    }
-
+  if (qtype != T_CNAME)
+    for (j = ntohs(header->ancount); j != 0; j--) 
+      {
+       if (!(p1 = skip_name(p1, header, plen, 10)))
+         return STAT_BOGUS; /* bad packet */
+       
+       GETSHORT(type2, p1); 
+       p1 += 6; /* class, TTL */
+       GETSHORT(rdlen2, p1);  
+       
+       if (type2 == T_CNAME)
+         {
+           if (!expand_workspace(&targets, &target_sz, targetidx))
+             return STAT_BOGUS;
+           
+           targets[targetidx++] = p1; /* pointer to target name */
+         }
+       
+       if (!ADD_RDLEN(header, p1, plen, rdlen2))
+         return STAT_BOGUS;
+      }
+  
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
       if (!extract_name(header, plen, &p1, name, 1, 10))
@@ -1931,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
          /* Not done, validate now */
          if (j == i)
            {
-             int ttl, keytag, algo, digest, type_covered;
+             int ttl, keytag, algo, digest, type_covered, sigcnt, rrcnt;
              unsigned char *psave;
              struct all_addr a;
              struct blockdata *key;
@@ -1939,143 +2036,186 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
              char *wildname;
              int have_wildcard = 0;
 
-             rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0);
-             
-             if (rc == STAT_SECURE_WILDCARD)
-               {
-                 have_wildcard = 1;
-
-                 /* An attacker replay a wildcard answer with a different
-                    answer and overlay a genuine RR. To prove this
-                    hasn't happened, the answer must prove that
-                    the gennuine record doesn't exist. Check that here. */
-                 if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
-                   return STAT_BOGUS; /* No NSECs or bad packet */
-                 
-                 if (nsec_type == T_NSEC)
-                   rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
-                 else
-                   rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, 
-                                                  keyname, name, type1, wildname, NULL);
-                 
-                 if (rc != STAT_SECURE)
-                   return rc;
-               } 
-             else if (rc != STAT_SECURE)
-               {
-                 if (class)
-                   *class = class1; /* Class for DS or DNSKEY */
+             if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+               return STAT_BOGUS;
 
-                 if (rc == STAT_NO_SIG)
+             /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
+             if (sigcnt == 0)
+               {
+                 if (check_unsigned)
                    {
-                     /* If we dropped off the end of a CNAME chain, return
-                        STAT_NO_SIG and the last name is keyname. This is used for proving non-existence
-                        if DS records in CNAME chains. */
-                     if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount)) 
-                       /* No CNAME chain, or no sig in answer section, return empty name. */
-                       *keyname = 0;
-                     else if (!extract_name(header, plen, &qname, keyname, 1, 0))
-                       return STAT_BOGUS;
+                     rc = zone_status(name, class1, keyname, now);
+                     if (rc == STAT_SECURE)
+                       rc = STAT_BOGUS;
+                      if (class)
+                        *class = class1; /* Class for NEED_DS or NEED_DNSKEY */
                    }
+                 else 
+                   rc = STAT_INSECURE; 
+                 
                  return rc;
                }
              
-             /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
-             cache_start_insert();
+             /* explore_rrset() gives us key name from sigs in keyname.
+                Can't overwrite name here. */
+             strcpy(daemon->workspacename, keyname);
+             rc = zone_status(daemon->workspacename, class1, keyname, now);
+             if (rc != STAT_SECURE)
+               {
+                 /* Zone is insecure, don't need to validate RRset */
+                 if (class)
+                   *class = class1; /* Class for NEED_DS or NEED_DNSKEY */
+                 return rc;
+               } 
+             
+             rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
              
-             for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
+             if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
                {
-                 if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
-                   return STAT_BOGUS; /* bad packet */
+                 if (class)
+                   *class = class1; /* Class for DS or DNSKEY */
+                 return rc;
+               } 
+             else 
+               {
+                 /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
+                
+                 /* Note if we've validated either the answer to the question
+                    or the target of a CNAME. Any not noted will need NSEC or
+                    to be in unsigned space. */
+
+                 for (j = 0; j <targetidx; j++)
+                   if ((p2 = targets[j]))
+                     {
+                       if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+                         return STAT_BOGUS; /* bad packet */
+                       
+                       if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
+                         targets[j] = NULL;
+                     }
+                           
+                 if (rc == STAT_SECURE_WILDCARD)
+                   {
+                     have_wildcard = 1;
                      
-                 GETSHORT(type2, p2);
-                 GETSHORT(class2, p2);
-                 GETLONG(ttl, p2);
-                 GETSHORT(rdlen2, p2);
-                      
-                 if (!CHECK_LEN(header, p2, plen, rdlen2))
-                   return STAT_BOGUS; /* bad packet */
-                 
-                 if (class2 == class1 && rc == 1)
-                   { 
-                     psave = p2;
+                     /* An attacker replay a wildcard answer with a different
+                        answer and overlay a genuine RR. To prove this
+                        hasn't happened, the answer must prove that
+                        the gennuine record doesn't exist. Check that here. */
+                     if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
+                       return STAT_BOGUS; /* No NSECs or bad packet */
+                     
+                     /* Note that we may not yet have validated the NSEC/NSEC3 RRsets. Since the check
+                        below returns either SECURE or BOGUS, that's not a problem. If the RRsets later fail
+                        we'll return BOGUS then. */
 
-                     if (type1 == T_DS && type2 == T_DS)
-                       {
-                         if (rdlen2 < 4)
-                           return STAT_BOGUS; /* bad packet */
-                         
-                         GETSHORT(keytag, p2);
-                         algo = *p2++;
-                         digest = *p2++;
-                         
-                         /* Cache needs to known class for DNSSEC stuff */
-                         a.addr.dnssec.class = class2;
-                         
-                         if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
-                           {
-                             if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
-                               blockdata_free(key);
-                             else
-                               {
-                                 a.addr.keytag = keytag;
-                                 log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
-                                 crecp->addr.ds.digest = digest;
-                                 crecp->addr.ds.keydata = key;
-                                 crecp->addr.ds.algo = algo;
-                                 crecp->addr.ds.keytag = keytag;
-                                 crecp->addr.ds.keylen = rdlen2 - 4; 
-                               } 
-                           }
-                       }
-                     else if (type2 == T_RRSIG)
-                       {
-                         if (rdlen2 < 18)
-                           return STAT_BOGUS; /* bad packet */
+                     if (nsec_type == T_NSEC)
+                       rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
+                     else
+                       rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, 
+                                                      keyname, name, type1, wildname, NULL);
+                     
+                     if (rc == STAT_BOGUS)
+                       return rc;
+                   } 
+                 
+                 /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
+                 /* Also note if the RRset is the answer to the question, or the target of a CNAME */
+                 cache_start_insert();
+                 
+                 for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
+                   {
+                     if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+                       return STAT_BOGUS; /* bad packet */
+                     
+                     GETSHORT(type2, p2);
+                     GETSHORT(class2, p2);
+                     GETLONG(ttl, p2);
+                     GETSHORT(rdlen2, p2);
+                     
+                     if (!CHECK_LEN(header, p2, plen, rdlen2))
+                       return STAT_BOGUS; /* bad packet */
+                     
+                     if (class2 == class1 && rc == 1)
+                       { 
+                         psave = p2;
                          
-                         GETSHORT(type_covered, p2);
-
-                         if (type_covered == type1 && 
-                             (type_covered == T_A || type_covered == T_AAAA ||
-                              type_covered == T_CNAME || type_covered == T_DS || 
-                              type_covered == T_DNSKEY || type_covered == T_PTR)) 
+                         if (type1 == T_DS && type2 == T_DS)
                            {
-                             a.addr.dnssec.type = type_covered;
-                             a.addr.dnssec.class = class1;
+                             if (rdlen2 < 4)
+                               return STAT_BOGUS; /* bad packet */
                              
-                             algo = *p2++;
-                             p2 += 13; /* labels, orig_ttl, expiration, inception */
                              GETSHORT(keytag, p2);
+                             algo = *p2++;
+                             digest = *p2++;
+                             
+                             /* Cache needs to known class for DNSSEC stuff */
+                             a.addr.dnssec.class = class2;
                              
-                             /* We don't cache sigs for wildcard answers, because to reproduce the
-                                answer from the cache will require one or more NSEC/NSEC3 records 
-                                which we don't cache. The lack of the RRSIG ensures that a query for
-                                this RRset asking for a secure answer will always be forwarded. */
-                             if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
+                             if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
                                {
-                                 if (!(crecp = cache_insert(name, &a, now, ttl,  F_FORWARD | F_DNSKEY | F_DS)))
+                                 if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
                                    blockdata_free(key);
                                  else
                                    {
-                                     crecp->addr.sig.keydata = key;
-                                     crecp->addr.sig.keylen = rdlen2;
-                                     crecp->addr.sig.keytag = keytag;
-                                     crecp->addr.sig.type_covered = type_covered;
-                                     crecp->addr.sig.algo = algo;
+                                     a.addr.keytag = keytag;
+                                     log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
+                                     crecp->addr.ds.digest = digest;
+                                     crecp->addr.ds.keydata = key;
+                                     crecp->addr.ds.algo = algo;
+                                     crecp->addr.ds.keytag = keytag;
+                                     crecp->addr.ds.keylen = rdlen2 - 4; 
+                                   } 
+                               }
+                           }
+                         else if (type2 == T_RRSIG)
+                           {
+                             if (rdlen2 < 18)
+                               return STAT_BOGUS; /* bad packet */
+                             
+                             GETSHORT(type_covered, p2);
+                             
+                             if (type_covered == type1 && 
+                                 (type_covered == T_A || type_covered == T_AAAA ||
+                                  type_covered == T_CNAME || type_covered == T_DS || 
+                                  type_covered == T_DNSKEY || type_covered == T_PTR)) 
+                               {
+                                 a.addr.dnssec.type = type_covered;
+                                 a.addr.dnssec.class = class1;
+                                 
+                                 algo = *p2++;
+                                 p2 += 13; /* labels, orig_ttl, expiration, inception */
+                                 GETSHORT(keytag, p2);
+                                 
+                                 /* We don't cache sigs for wildcard answers, because to reproduce the
+                                    answer from the cache will require one or more NSEC/NSEC3 records 
+                                    which we don't cache. The lack of the RRSIG ensures that a query for
+                                    this RRset asking for a secure answer will always be forwarded. */
+                                 if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
+                                   {
+                                     if (!(crecp = cache_insert(name, &a, now, ttl,  F_FORWARD | F_DNSKEY | F_DS)))
+                                       blockdata_free(key);
+                                     else
+                                       {
+                                         crecp->addr.sig.keydata = key;
+                                         crecp->addr.sig.keylen = rdlen2;
+                                         crecp->addr.sig.keytag = keytag;
+                                         crecp->addr.sig.type_covered = type_covered;
+                                         crecp->addr.sig.algo = algo;
+                                       }
                                    }
                                }
                            }
+                         
+                         p2 = psave;
                        }
                      
-                     p2 = psave;
+                     if (!ADD_RDLEN(header, p2, plen, rdlen2))
+                       return STAT_BOGUS; /* bad packet */
                    }
                  
-                 if (!ADD_RDLEN(header, p2, plen, rdlen2))
-                   return STAT_BOGUS; /* bad packet */
+                 cache_end_insert();
                }
-                 
-             cache_end_insert();
            }
        }
 
@@ -2083,143 +2223,49 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
        return STAT_BOGUS;
     }
 
-  /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
-  if (have_answer)
-    return STAT_SECURE;
-     
-  /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
-  /* First marshall the NSEC records, if we've not done it previously */
-  if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
-    {
-      /* No NSEC records. If we dropped off the end of a CNAME chain, return
-        STAT_NO_SIG and the last name is keyname. This is used for proving non-existence
-        if DS records in CNAME chains. */
-      if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */
-       *keyname = 0;
-      else if (!extract_name(header, plen, &qname, keyname, 1, 0))
-       return STAT_BOGUS;
-      return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into
-                            an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */
-    }
-   
-  /* Get name of missing answer */
-  if (!extract_name(header, plen, &qname, name, 1, 0))
-    return STAT_BOGUS;
-  
-  if (nsec_type == T_NSEC)
-    return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
-  else
-    return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
-}
-
-/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
-   Needed for proving answer in unsigned space.
-   Return STAT_NEED_* 
-          STAT_BOGUS - error
-          STAT_INSECURE - name of first non-secure record in name 
-*/
-int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
-{
-  unsigned char *p = (unsigned char *)(header+1);
-  int type, class, qclass, rdlen, j, rc;
-  int cname_count = CNAME_CHAIN;
-  char *wildname;
-
-  /* Get question */
-  if (!extract_name(header, plen, &p, name, 1, 4))
-    return STAT_BOGUS;
-  
-  p +=2; /* type */
-  GETSHORT(qclass, p);
-
-  while (1)
-    {
-      for (j = ntohs(header->ancount); j != 0; j--) 
-       {
-         if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
-           return STAT_BOGUS; /* bad packet */
-         
-         GETSHORT(type, p); 
-         GETSHORT(class, p);
-         p += 4; /* TTL */
-         GETSHORT(rdlen, p);
-
-         /* Not target, loop */
-         if (rc == 2 || qclass != class)
-           {
-             if (!ADD_RDLEN(header, p, plen, rdlen))
-               return STAT_BOGUS;
-             continue;
-           }
-         
-         /* Got to end of CNAME chain. */
-         if (type != T_CNAME)
-           return STAT_INSECURE;
-         
-         /* validate CNAME chain, return if insecure or need more data */
-         rc = validate_rrset(now, header, plen, class, type, name, keyname, &wildname, NULL, 0, 0, 0);
-          
-         if (rc == STAT_SECURE_WILDCARD)
-           {
-             int nsec_type, nsec_count, i;
-             unsigned char **nsecs;
-
-             /* An attacker can replay a wildcard answer with a different
-                answer and overlay a genuine RR. To prove this
-                hasn't happened, the answer must prove that
-                the genuine record doesn't exist. Check that here. */
-             if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class)))
-               return STAT_BOGUS; /* No NSECs or bad packet */
-             
-             /* Note that we're called here because something didn't validate in validate_reply,
-                so we can't assume that any NSEC records have been validated. We do them by steam here */
-
-             for (i = 0; i < nsec_count; i++)
-               {
-                 unsigned char *p1 = nsecs[i];
-                 
-                 if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0))
-                   return STAT_BOGUS;
-
-                 rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0);
+  /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
+  for (j = 0; j <targetidx; j++)
+    if ((p2 = targets[j]))
+      {
+       if (neganswer)
+         *neganswer = 1;
 
-                 /* NSECs can't be wildcards. */
-                 if (rc == STAT_SECURE_WILDCARD)
-                   rc = STAT_BOGUS;
+       if (!extract_name(header, plen, &p2, name, 1, 10))
+         return STAT_BOGUS; /* bad packet */
+           
+       /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
 
-                 if (rc != STAT_SECURE)
+       /* For anything other than a DS record, this situation is OK if either
+          the answer is in an unsigned zone, or there's a NSEC records. */
+       if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
+         {
+           /* Empty DS without NSECS */
+           if (qtype == T_DS)
+             return STAT_BOGUS;
+           else
+             {
+               rc = zone_status(name, qclass, keyname, now);
+               if (rc != STAT_SECURE)
+                 {
+                   if (class)
+                     *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
                    return rc;
-               }
-
-             if (nsec_type == T_NSEC)
-               rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL);
-             else
-               rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, 
-                                              keyname, name, type, wildname, NULL);
-             
-             if (rc != STAT_SECURE)
-               return rc;
-           }
-         
-         if (rc != STAT_SECURE)
-           {
-             if (rc == STAT_NO_SIG)
-               rc = STAT_INSECURE;
-             return rc;
-           }
+                 } 
+               
+               return STAT_BOGUS; /* signed zone, no NSECs */
+             }
+         }
 
-         /* Loop down CNAME chain/ */
-         if (!cname_count-- || 
-             !extract_name(header, plen, &p, name, 1, 0) ||
-             !(p = skip_questions(header, plen)))
-           return STAT_BOGUS;
-         
-         break;
-       }
+         if (nsec_type == T_NSEC)
+         rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
+       else
+         rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
 
-      /* End of CNAME chain */
-      return STAT_INSECURE;    
-    }
+       if (rc != STAT_SECURE)
+         return rc;
+      }
+  
+  return STAT_SECURE;
 }
 
 
index b76a974fcd7e19b6f228a49ed0d37edf51a999a4..dd22a6233f35c498411876c3c0e44559284c458c 100644 (file)
@@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
 static unsigned short get_id(void);
 static void free_frec(struct frec *f);
 
-#ifdef HAVE_DNSSEC
-static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
-                          int class, char *name, char *keyname, struct server *server, int *keycount);
-static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname);
-static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, 
-                          char *name, char *keyname);
-#endif
-
-
 /* Send a UDP packet with its source address set as "source" 
    unless nowild is true, when we just send it with the kernel default */
 int send_from(int fd, int nowild, char *packet, size_t len, 
@@ -825,236 +816,142 @@ void reply_query(int fd, int family, time_t now)
 #ifdef HAVE_DNSSEC
       if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
        {
-         int status;
+         int status = 0;
 
          /* We've had a reply already, which we're validating. Ignore this duplicate */
          if (forward->blocking_query)
            return;
-
-         if (header->hb3 & HB3_TC)
-           {
-             /* Truncated answer can't be validated.
+         
+          /* Truncated answer can't be validated.
                 If this is an answer to a DNSSEC-generated query, we still
                 need to get the client to retry over TCP, so return
                 an answer with the TC bit set, even if the actual answer fits.
              */
-             status = STAT_TRUNCATED;
-           }
-         else if (forward->flags & FREC_DNSKEY_QUERY)
-           status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-         else if (forward->flags & FREC_DS_QUERY)
-           {
-             status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-             /* Provably no DS, everything below is insecure, even if signatures are offered */
-             if (status == STAT_NO_DS)
-               /* We only cache sigs when we've validated a reply.
-                  Avoid caching a reply with sigs if there's a vaildated break in the 
-                  DS chain, so we don't return replies from cache missing sigs. */
-               status = STAT_INSECURE_DS;
-             else if (status == STAT_NO_SIG)
-                {
-                  if (option_bool(OPT_DNSSEC_NO_SIGN))
-                    {
-                     status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
-                     if (status == STAT_INSECURE)
-                       status = STAT_INSECURE_DS;
-                   }
-                 else
-                   status = STAT_INSECURE_DS;
-               }
-              else if (status == STAT_NO_NS)
-               status = STAT_BOGUS;
-           }
-         else if (forward->flags & FREC_CHECK_NOSIGN)
-           {
-             status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-             if (status != STAT_NEED_KEY)
-               status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
-           }
-         else
+         if (header->hb3 & HB3_TC)
+           status = STAT_TRUNCATED;
+         
+         while (1)
            {
-             status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
-             if (status == STAT_NO_SIG)
+             /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
+                would invite infinite loops, since the answers to DNSKEY and DS queries
+                will not be cached, so they'll be repeated. */
+             if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
                {
-                 if (option_bool(OPT_DNSSEC_NO_SIGN))
-                   status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
+                 if (forward->flags & FREC_DNSKEY_QUERY)
+                   status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+                 else if (forward->flags & FREC_DS_QUERY)
+                   status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
                  else
-                   status = STAT_INSECURE;
+                   status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, 
+                                                  option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
                }
-           }
-         /* Can't validate, as we're missing key data. Put this
-            answer aside, whilst we get that. */     
-         if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
-           {
-             struct frec *new, *orig;
-             
-             /* Free any saved query */
-             if (forward->stash)
-               blockdata_free(forward->stash);
-             
-             /* Now save reply pending receipt of key data */
-             if (!(forward->stash = blockdata_alloc((char *)header, n)))
-               return;
-             forward->stash_len = n;
              
-           anotherkey:       
-             /* Find the original query that started it all.... */
-             for (orig = forward; orig->dependent; orig = orig->dependent);
-
-             if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
-               status = STAT_INSECURE;
-             else
+             /* Can't validate, as we're missing key data. Put this
+                answer aside, whilst we get that. */     
+             if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
                {
-                 int fd;
-                 struct frec *next = new->next;
-                 *new = *forward; /* copy everything, then overwrite */
-                 new->next = next;
-                 new->blocking_query = NULL;
-                 new->sentto = server;
-                 new->rfd4 = NULL;
-                 new->orig_domain = NULL;
-#ifdef HAVE_IPV6
-                 new->rfd6 = NULL;
-#endif
-                 new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
+                 struct frec *new, *orig;
                  
-                 new->dependent = forward; /* to find query awaiting new one. */
-                 forward->blocking_query = new; /* for garbage cleaning */
-                 /* validate routines leave name of required record in daemon->keyname */
-                 if (status == STAT_NEED_KEY)
-                   {
-                     new->flags |= FREC_DNSKEY_QUERY; 
-                     nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
-                                                daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
-                   }
-                 else 
-                   {
-                     if (status == STAT_NEED_DS_NEG)
-                       new->flags |= FREC_CHECK_NOSIGN;
-                     else
-                       new->flags |= FREC_DS_QUERY;
-                     nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
-                                                daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
-                   }
-                 if ((hash = hash_questions(header, nn, daemon->namebuff)))
-                   memcpy(new->hash, hash, HASH_SIZE);
-                 new->new_id = get_id();
-                 header->id = htons(new->new_id);
-                 /* Save query for retransmission */
-                 if (!(new->stash = blockdata_alloc((char *)header, nn)))
+                 /* Free any saved query */
+                 if (forward->stash)
+                   blockdata_free(forward->stash);
+                 
+                 /* Now save reply pending receipt of key data */
+                 if (!(forward->stash = blockdata_alloc((char *)header, n)))
                    return;
-                     
-                 new->stash_len = nn;
+                 forward->stash_len = n;
                  
-                 /* Don't resend this. */
-                 daemon->srv_save = NULL;
+                 /* Find the original query that started it all.... */
+                 for (orig = forward; orig->dependent; orig = orig->dependent);
                  
-                 if (server->sfd)
-                   fd = server->sfd->fd;
+                 if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
+                   status = STAT_ABANDONED;
                  else
                    {
-                     fd = -1;
+                     int fd;
+                     struct frec *next = new->next;
+                     *new = *forward; /* copy everything, then overwrite */
+                     new->next = next;
+                     new->blocking_query = NULL;
+                     new->sentto = server;
+                     new->rfd4 = NULL;
 #ifdef HAVE_IPV6
-                     if (server->addr.sa.sa_family == AF_INET6)
+                     new->rfd6 = NULL;
+#endif
+                     new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+                     
+                     new->dependent = forward; /* to find query awaiting new one. */
+                     forward->blocking_query = new; /* for garbage cleaning */
+                     /* validate routines leave name of required record in daemon->keyname */
+                     if (status == STAT_NEED_KEY)
+                       {
+                         new->flags |= FREC_DNSKEY_QUERY; 
+                         nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
+                                                    daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
+                       }
+                     else 
                        {
-                         if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
-                           fd = new->rfd6->fd;
+                         new->flags |= FREC_DS_QUERY;
+                         nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
+                                                    daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
                        }
+                     if ((hash = hash_questions(header, nn, daemon->namebuff)))
+                       memcpy(new->hash, hash, HASH_SIZE);
+                     new->new_id = get_id();
+                     header->id = htons(new->new_id);
+                     /* Save query for retransmission */
+                     new->stash = blockdata_alloc((char *)header, nn);
+                     new->stash_len = nn;
+                     
+                     /* Don't resend this. */
+                     daemon->srv_save = NULL;
+                     
+                     if (server->sfd)
+                       fd = server->sfd->fd;
                      else
+                       {
+                         fd = -1;
+#ifdef HAVE_IPV6
+                         if (server->addr.sa.sa_family == AF_INET6)
+                           {
+                             if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
+                               fd = new->rfd6->fd;
+                           }
+                         else
 #endif
+                           {
+                             if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
+                               fd = new->rfd4->fd;
+                           }
+                       }
+                     
+                     if (fd != -1)
                        {
-                         if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
-                           fd = new->rfd4->fd;
+                         while (retry_send(sendto(fd, (char *)header, nn, 0, 
+                                                  &server->addr.sa, 
+                                                  sa_len(&server->addr)))); 
+                         server->queries++;
                        }
-                   }
-                 
-                 if (fd != -1)
-                   {
-                     while (retry_send(sendto(fd, (char *)header, nn, 0, 
-                                              &server->addr.sa, 
-                                              sa_len(&server->addr)))); 
-                     server->queries++;
-                   }
-                 
+                   }             
                  return;
                }
-           }
          
-         /* Ok, we reached far enough up the chain-of-trust that we can validate something.
-            Now wind back down, pulling back answers which wouldn't previously validate
-            and validate them with the new data. Note that if an answer needs multiple
-            keys to validate, we may find another key is needed, in which case we set off
-            down another branch of the tree. Once we get to the original answer 
-            (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */
-         while (forward->dependent)
-           {
+             /* Validated original answer, all done. */
+             if (!forward->dependent)
+               break;
+             
+             /* validated subsdiary query, (and cached result)
+                pop that and return to the previous query we were working on. */
              struct frec *prev = forward->dependent;
              free_frec(forward);
              forward = prev;
              forward->blocking_query = NULL; /* already gone */
              blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
              n = forward->stash_len;
-             
-             if (status == STAT_SECURE)
-               {
-                 if (forward->flags & FREC_DNSKEY_QUERY)
-                   status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-                 else if (forward->flags & FREC_DS_QUERY)
-                   {
-                     status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-                      /* Provably no DS, everything below is insecure, even if signatures are offered */
-                     if (status == STAT_NO_DS)
-                       /* We only cache sigs when we've validated a reply.
-                          Avoid caching a reply with sigs if there's a vaildated break in the 
-                          DS chain, so we don't return replies from cache missing sigs. */
-                       status = STAT_INSECURE_DS;
-                      else if (status == STAT_NO_SIG)
-                        {
-                          if (option_bool(OPT_DNSSEC_NO_SIGN))
-                            {
-                              status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); 
-                              if (status == STAT_INSECURE)
-                                status = STAT_INSECURE_DS;
-                            }
-                          else
-                            status = STAT_INSECURE_DS;
-                        }
-                      else if (status == STAT_NO_NS)
-                        status = STAT_BOGUS;
-                   }
-                 else if (forward->flags & FREC_CHECK_NOSIGN)
-                   {
-                     status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
-                     if (status != STAT_NEED_KEY)
-                       status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
-                   }
-                 else
-                   {
-                     status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);   
-                     if (status == STAT_NO_SIG)
-                       {
-                         if (option_bool(OPT_DNSSEC_NO_SIGN))
-                           status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
-                         else
-                           status = STAT_INSECURE;
-                       }
-                   }
-              
-                 if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
-                   goto anotherkey;
-               }
            }
+       
          
          no_cache_dnssec = 0;
-
-         if (status == STAT_INSECURE_DS)
-           {
-             /* We only cache sigs when we've validated a reply.
-                Avoid caching a reply with sigs if there's a vaildated break in the 
-                DS chain, so we don't return replies from cache missing sigs. */
-             status = STAT_INSECURE;
-             no_cache_dnssec = 1;
-           }
          
          if (status == STAT_TRUNCATED)
            header->hb3 |= HB3_TC;
@@ -1062,7 +959,7 @@ void reply_query(int fd, int family, time_t now)
            {
              char *result, *domain = "result";
              
-             if (forward->work_counter == 0)
+             if (status == STAT_ABANDONED)
                {
                  result = "ABANDONED";
                  status = STAT_BOGUS;
@@ -1072,7 +969,7 @@ void reply_query(int fd, int family, time_t now)
              
              if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL))
                domain = daemon->namebuff;
-
+             
              log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result);
            }
          
@@ -1415,315 +1312,49 @@ void receive_query(struct listener *listen, time_t now)
 }
 
 #ifdef HAVE_DNSSEC
-
-/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
-   and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or 
-   STAT_NEED_DS_NEG and keyname if we need to do the query. */
-static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, 
-                          char *name, char *keyname)
-{
-  int status = dnssec_chase_cname(now, header, plen, name, keyname);
-  
-  if (status != STAT_INSECURE)
-    return status;
-
-  /* Store the domain we're trying to check. */
-  forward->name_start = strlen(name);
-  forward->name_len = forward->name_start + 1;
-  if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len)))
-    return STAT_BOGUS;
-  
-  return do_check_sign(forward, 0, now, name, keyname);
-}
-/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */ 
-static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname)
-{
-  /* get domain we're checking back from blockdata store, it's stored on the original query. */
-  while (forward->dependent && !forward->orig_domain)
-    forward = forward->dependent;
-
-  blockdata_retrieve(forward->orig_domain, forward->name_len, name);
-  
-  while (1)
-    {
-      char *p; 
-
-      if (status == 0)
-       {
-         struct crec *crecp;
-
-         /* Haven't received answer, see if in cache */
-         if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS)))
-           {
-             /* put name of DS record we're missing into keyname */
-             strcpy(keyname, &name[forward->name_start]);
-             /* and wait for reply to arrive */
-             return STAT_NEED_DS_NEG;
-           }
-
-         /* F_DNSSECOK misused in DS cache records to non-existance of NS record */ 
-         if (!(crecp->flags & F_NEG))
-           status = STAT_SECURE;
-         else if (crecp->flags & F_DNSSECOK)
-           status = STAT_NO_DS;
-         else
-           status = STAT_NO_NS;
-       }
-      
-      /* Have entered non-signed part of DNS tree. */ 
-      if (status == STAT_NO_DS)
-       return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE;
-
-      if (status == STAT_BOGUS)
-       return STAT_BOGUS;
-
-      if (status == STAT_NO_SIG && *keyname != 0)
-       {
-         /* There is a validated CNAME chain that doesn't end in a DS record. Start 
-            the search again in that domain. */
-         blockdata_free(forward->orig_domain);
-         forward->name_start = strlen(keyname);
-         forward->name_len = forward->name_start + 1;
-         if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len)))
-           return STAT_BOGUS;
-         
-         strcpy(name, keyname);
-         status = 0; /* force to cache when we iterate. */
-         continue;
-       }
-      
-      /* There's a proven DS record, or we're within a zone, where there doesn't need
-        to be a DS record. Add a name and try again. 
-        If we've already tried the whole name, then fail */
-
-      if (forward->name_start == 0)
-       return STAT_BOGUS;
-      
-      for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--);
-      
-      if (p != name)
-       p++;
-      
-      forward->name_start = p - name;
-      status = 0; /* force to cache when we iterate. */
-    }
-}
-
-/* Move down from the root, until we find a signed non-existance of a DS, in which case
-   an unsigned answer is OK, or we find a signed DS, in which case there should be 
-   a signature, and the answer is BOGUS */
-static int  tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, 
-                                       char *keyname, struct server *server, int *keycount)
-{
-  size_t m;
-  unsigned char *packet, *payload;
-  u16 *length;
-  int status, name_len;
-  struct blockdata *block;
-
-  char *name_start;
-
-  /* Get first insecure entry in CNAME chain */
-  status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
-  if (status == STAT_BOGUS)
-    return STAT_BOGUS;
-  
-  if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16))))
-    return STAT_BOGUS;
-  
-  payload = &packet[2];
-  header = (struct dns_header *)payload;
-  length = (u16 *)packet;
-
-  /* Stash the name away, since the buffer will be trashed when we recurse */
-  name_len = strlen(name) + 1;
-  name_start = name + name_len - 1;
-  
-  if (!(block = blockdata_alloc(name, name_len)))
-    {
-      free(packet);
-      return STAT_BOGUS;
-    }
-
-  while (1)
-    {
-      unsigned char c1, c2;
-      struct crec *crecp;
-
-      if (--(*keycount) == 0)
-       {
-         free(packet);
-         blockdata_free(block);
-         return STAT_BOGUS;    
-       }
-      
-      while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS)))
-       {      
-         if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))
-           {
-             /* Found a secure denial of DS - delegation is indeed insecure */
-             free(packet);
-             blockdata_free(block);
-             return STAT_INSECURE;
-           }
-      
-         /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
-            Add another label and continue. */
-         if (name_start == name)
-           {
-             free(packet);
-             blockdata_free(block);
-             return STAT_BOGUS; /* run out of labels */
-           }
-         
-         name_start -= 2;
-         while (*name_start != '.' && name_start != name) 
-           name_start--;
-         if (name_start != name)
-           name_start++;
-       }
-      
-      /* Can't find it in the cache, have to send a query */
-
-      m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
-      
-      *length = htons(m);
-      
-      if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
-         read_write(server->tcpfd, &c1, 1, 1) &&
-         read_write(server->tcpfd, &c2, 1, 1) &&
-         read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
-       {
-         m = (c1 << 8) | c2;
-         
-         /* Note this trashes all three name workspaces */
-         status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
-         
-         if (status == STAT_NO_DS)
-           {
-             /* Found a secure denial of DS - delegation is indeed insecure */
-             free(packet);
-             blockdata_free(block);
-             return STAT_INSECURE;
-           }
-         
-         if (status == STAT_NO_SIG && *keyname != 0)
-           {
-             /* There is a validated CNAME chain that doesn't end in a DS record. Start 
-                the search again in that domain. */
-             blockdata_free(block);
-             name_len = strlen(keyname) + 1;
-             name_start = name + name_len - 1;
-             
-             if (!(block = blockdata_alloc(keyname, name_len)))
-               return STAT_BOGUS;
-             
-             strcpy(name, keyname);
-             continue;
-           }
-         
-         if (status == STAT_BOGUS)
-           {
-             free(packet);
-             blockdata_free(block);
-             return STAT_BOGUS;
-           }
-         
-         /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
-            Add another label and continue. */
-         
-         /* Get name we're checking back. */
-         blockdata_retrieve(block, name_len, name);
-         
-         if (name_start == name)
-           {
-             free(packet);
-             blockdata_free(block);
-             return STAT_BOGUS; /* run out of labels */
-           }
-         
-         name_start -= 2;
-         while (*name_start != '.' && name_start != name) 
-           name_start--;
-         if (name_start != name)
-           name_start++;
-       }
-      else
-       {
-         /* IO failure */
-         free(packet);
-         blockdata_free(block);
-         return STAT_BOGUS; /* run out of labels */
-       }
-    }
-}
-
 static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
                           int class, char *name, char *keyname, struct server *server, int *keycount)
 {
   /* Recurse up the key heirarchy */
   int new_status;
+  unsigned char *packet = NULL;
+  size_t m; 
+  unsigned char *payload = NULL;
+  struct dns_header *new_header = NULL;
+  u16 *length = NULL;
+  unsigned char c1, c2;
 
-  /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
-  if (--(*keycount) == 0)
-    return STAT_INSECURE;
-  
-  if (status == STAT_NEED_KEY)
-    new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-  else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+  while (1)
     {
-      new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-      if (status == STAT_NEED_DS)
+      /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
+      if (--(*keycount) == 0)
+       new_status = STAT_ABANDONED;
+      else if (status == STAT_NEED_KEY)
+       new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
+      else if (status == STAT_NEED_DS)
+       new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+      else 
+       new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
+      
+      if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
+       break;
+
+      /* Can't validate because we need a key/DS whose name now in keyname.
+        Make query for same, and recurse to validate */
+      if (!packet)
        {
-         if (new_status == STAT_NO_DS)
-           new_status = STAT_INSECURE_DS;
-         if (new_status == STAT_NO_SIG)
-          {
-            if (option_bool(OPT_DNSSEC_NO_SIGN))
-              {
-                new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
-                if (new_status == STAT_INSECURE)
-                  new_status = STAT_INSECURE_DS;
-              }
-            else
-              new_status = STAT_INSECURE_DS;
-          }
-         else if (new_status == STAT_NO_NS)
-           new_status = STAT_BOGUS;
+         packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+         payload = &packet[2];
+         new_header = (struct dns_header *)payload;
+         length = (u16 *)packet;
        }
-    }
-  else if (status == STAT_CHASE_CNAME)
-    new_status = dnssec_chase_cname(now, header, n, name, keyname);
-  else 
-    {
-      new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
       
-      if (new_status == STAT_NO_SIG)
+      if (!packet)
        {
-         if (option_bool(OPT_DNSSEC_NO_SIGN))
-           new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
-         else
-           new_status = STAT_INSECURE;
+         new_status = STAT_ABANDONED;
+         break;
        }
-    }
-
-  /* Can't validate because we need a key/DS whose name now in keyname.
-     Make query for same, and recurse to validate */
-  if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
-    {
-      size_t m; 
-      unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
-      unsigned char *payload = &packet[2];
-      struct dns_header *new_header = (struct dns_header *)payload;
-      u16 *length = (u16 *)packet;
-      unsigned char c1, c2;
-       
-      if (!packet)
-       return STAT_INSECURE;
-
-    another_tcp_key:
+        
       m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, 
                                new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
       
@@ -1733,65 +1364,22 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
          !read_write(server->tcpfd, &c1, 1, 1) ||
          !read_write(server->tcpfd, &c2, 1, 1) ||
          !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
-       new_status = STAT_INSECURE;
-      else
        {
-         m = (c1 << 8) | c2;
-         
-         new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
-         
-         if (new_status == STAT_SECURE)
-           {
-             /* Reached a validated record, now try again at this level.
-                Note that we may get ANOTHER NEED_* if an answer needs more than one key.
-                If so, go round again. */
-             
-             if (status == STAT_NEED_KEY)
-               new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-             else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
-               {
-                 new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-                 if (status == STAT_NEED_DS)
-                   {
-                     if (new_status == STAT_NO_DS)
-                       new_status = STAT_INSECURE_DS;
-                     else if (new_status == STAT_NO_SIG)
-                       {
-                         if (option_bool(OPT_DNSSEC_NO_SIGN))
-                           {
-                             new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); 
-                             if (new_status == STAT_INSECURE)
-                               new_status = STAT_INSECURE_DS;
-                           }
-                         else
-                           new_status = STAT_INSECURE_DS;
-                       }
-                     else if (new_status == STAT_NO_NS)
-                       new_status = STAT_BOGUS;
-                   }
-               }
-             else if (status == STAT_CHASE_CNAME)
-               new_status = dnssec_chase_cname(now, header, n, name, keyname);
-             else 
-               {
-                 new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
-                 
-                 if (new_status == STAT_NO_SIG)
-                   {
-                     if (option_bool(OPT_DNSSEC_NO_SIGN))
-                       new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
-                     else
-                       new_status = STAT_INSECURE;
-                   }
-               }
-             
-             if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
-               goto another_tcp_key;
-           }
+         new_status = STAT_ABANDONED;
+         break;
        }
+
+      m = (c1 << 8) | c2;
       
-      free(packet);
+      new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
+      
+      if (new_status != STAT_OK)
+       break;
     }
+
+  if (packet)
+    free(packet);
+    
   return new_status;
 }
 #endif
@@ -2075,19 +1663,10 @@ unsigned char *tcp_request(int confd, time_t now,
                      if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
                        {
                          int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
-                         int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
+                         int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
                          char *result, *domain = "result";
-
-                         if (status == STAT_INSECURE_DS)
-                           {
-                             /* We only cache sigs when we've validated a reply.
-                                Avoid caching a reply with sigs if there's a vaildated break in the 
-                                DS chain, so we don't return replies from cache missing sigs. */
-                             status = STAT_INSECURE;
-                             no_cache_dnssec = 1;
-                           }
                          
-                         if (keycount == 0)
+                         if (status == STAT_ABANDONED)
                            {
                              result = "ABANDONED";
                              status = STAT_BOGUS;
@@ -2179,7 +1758,6 @@ static struct frec *allocate_frec(time_t now)
       f->dependent = NULL;
       f->blocking_query = NULL;
       f->stash = NULL;
-      f->orig_domain = NULL;
 #endif
       daemon->frec_list = f;
     }
@@ -2248,12 +1826,6 @@ static void free_frec(struct frec *f)
       f->stash = NULL;
     }
 
-  if (f->orig_domain)
-    {
-      blockdata_free(f->orig_domain);
-      f->orig_domain = NULL;
-    }
-
   /* Anything we're waiting on is pointless now, too */
   if (f->blocking_query)
     free_frec(f->blocking_query);
@@ -2281,14 +1853,23 @@ struct frec *get_new_frec(time_t now, int *wait, int force)
       target = f;
     else 
       {
-       if (difftime(now, f->time) >= 4*TIMEOUT)
-         {
-           free_frec(f);
-           target = f;
-         }
-       
-       if (!oldest || difftime(f->time, oldest->time) <= 0)
-         oldest = f;
+#ifdef HAVE_DNSSEC
+           /* Don't free DNSSEC sub-queries here, as we may end up with
+              dangling references to them. They'll go when their "real" query 
+              is freed. */
+           if (!f->dependent)
+#endif
+             {
+               if (difftime(now, f->time) >= 4*TIMEOUT)
+                 {
+                   free_frec(f);
+                   target = f;
+                 }
+            
+           
+               if (!oldest || difftime(f->time, oldest->time) <= 0)
+                 oldest = f;
+             }
       }
 
   if (target)