]> git.ipfire.org Git - people/jschlag/ipfire-2.x.git/commitdiff
dnsmasq 2.75: latest upstream patches
authorMatthias Fischer <matthias.fischer@ipfire.org>
Wed, 16 Dec 2015 20:42:41 +0000 (21:42 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 18 Dec 2015 15:02:26 +0000 (15:02 +0000)
Since 'Makefile' was affected, I had to rewrite
'dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch', too.

Signed-off-by: Matthias Fischer <matthias.fischer@ipfire.org>
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
lfs/dnsmasq
src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch
src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch [new file with mode: 0644]
src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch [new file with mode: 0644]
src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch [new file with mode: 0644]
src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch [new file with mode: 0644]
src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch [new file with mode: 0644]

index d166392317020bc82f541134e11991cb689f4697..eeb7e037b267e73a3fba8be847c57902b1dff0d7 100644 (file)
@@ -88,6 +88,11 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects))
        cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/013-Fix_crash_when_empty_address_from_DNS_overlays_A_record_from.patch
        cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/014-Handle_unknown_DS_hash_algos_correctly.patch
        cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/015-Fix_crash_at_start_up_with_conf-dir.patch
+       cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch
+       cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch
+       cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch
+       cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch
+       cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch
        cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch
 
        cd $(DIR_APP) && sed -i src/config.h \
index 0482c4b1cf394c2d24cfb6cff3bcaa959c7dcbe2..f55ebe81e1485ba0cce65047705f1cc0dfd2a6f7 100644 (file)
@@ -1,21 +1,5 @@
-diff --git a/Makefile b/Makefile
-index 4c87ea9..4e0ea10 100644
---- a/Makefile
-+++ b/Makefile
-@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
-        dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
-        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
-        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
--       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o
-+       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o \
-+       isc.o
- hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
-        dns-protocol.h radv-protocol.h ip6addr.h
-diff --git a/src/cache.c b/src/cache.c
-index 178d654..6d0b131 100644
---- a/src/cache.c
-+++ b/src/cache.c
+--- a/src/cache.c      Wed Dec 16 19:24:12 2015
++++ b/src/cache.c      Wed Dec 16 19:37:37 2015
 @@ -17,7 +17,7 @@
  #include "dnsmasq.h"
  
@@ -25,7 +9,7 @@ index 178d654..6d0b131 100644
  static struct crec *dhcp_spare = NULL;
  #endif
  static struct crec *new_chain = NULL;
-@@ -222,6 +222,9 @@ static void cache_free(struct crec *crecp)
+@@ -217,6 +217,9 @@
        crecp->flags &= ~F_BIGNAME;
      }
  
@@ -35,7 +19,7 @@ index 178d654..6d0b131 100644
  #ifdef HAVE_DNSSEC
    cache_blockdata_free(crecp);
  #endif
-@@ -1151,7 +1154,7 @@ void cache_reload(void)
+@@ -1131,7 +1134,7 @@
    
  } 
  
@@ -44,16 +28,7 @@ index 178d654..6d0b131 100644
  struct in_addr a_record_from_hosts(char *name, time_t now)
  {
    struct crec *crecp = NULL;
-@@ -1229,7 +1232,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
-       addrlen = sizeof(struct in6_addr);
-     }
- #endif
--  
-+
-   inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
-   
-   while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
-@@ -1294,7 +1297,11 @@ void cache_add_dhcp_entry(char *host_name, int prot,
+@@ -1274,7 +1277,11 @@
        else
        crec->ttd = ttd;
        crec->addr.addr = *host_address;
@@ -65,11 +40,9 @@ index 178d654..6d0b131 100644
        crec->uid = next_uid();
        cache_hash(crec);
  
-diff --git a/src/dnsmasq.c b/src/dnsmasq.c
-index 81254f6..ce2d1a7 100644
---- a/src/dnsmasq.c
-+++ b/src/dnsmasq.c
-@@ -982,6 +982,11 @@ int main (int argc, char **argv)
+--- a/src/dnsmasq.c    Thu Jul 30 20:59:06 2015
++++ b/src/dnsmasq.c    Wed Dec 16 19:38:32 2015
+@@ -982,6 +982,11 @@
  
          poll_resolv(0, daemon->last_resolv != 0, now);          
          daemon->last_resolv = now;
@@ -81,11 +54,9 @@ index 81254f6..ce2d1a7 100644
        }
  #endif
  
-diff --git a/src/dnsmasq.h b/src/dnsmasq.h
-index cf1a782..30437aa 100644
---- a/src/dnsmasq.h
-+++ b/src/dnsmasq.h
-@@ -1519,3 +1519,7 @@ int poll_check(int fd, short event);
+--- a/src/dnsmasq.h    Wed Dec 16 19:24:12 2015
++++ b/src/dnsmasq.h    Wed Dec 16 19:40:11 2015
+@@ -1513,8 +1513,12 @@
  void poll_listen(int fd, short event);
  int do_poll(int timeout);
  
@@ -93,11 +64,14 @@ index cf1a782..30437aa 100644
 +#ifdef HAVE_ISC_READER
 +void load_dhcp(time_t now);
 +#endif
-diff --git a/src/isc.c b/src/isc.c
-new file mode 100644
-index 0000000..5106442
---- /dev/null
-+++ b/src/isc.c
++
+ /* rrfilter.c */
+ size_t rrfilter(struct dns_header *header, size_t plen, int mode);
+ u16 *rrfilter_desc(int type);
+ int expand_workspace(unsigned char ***wkspc, int *szp, int new);
+-
+--- /dev/null  Wed Dec 16 19:48:08 2015
++++ b/src/isc.c        Wed Dec 16 19:41:35 2015
 @@ -0,0 +1,251 @@
 +/* dnsmasq is Copyright (c) 2014 John Volpe, Simon Kelley and
 +     Michael Tremer
@@ -350,11 +324,9 @@ index 0000000..5106442
 +}
 +
 +#endif
-diff --git a/src/option.c b/src/option.c
-index ecc2619..527c5aa 100644
---- a/src/option.c
-+++ b/src/option.c
-@@ -1699,7 +1699,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
+--- a/src/option.c     Wed Dec 16 19:24:12 2015
++++ b/src/option.c     Wed Dec 16 19:42:48 2015
+@@ -1754,7 +1754,7 @@
        ret_err(_("bad MX target"));
        break;
  
@@ -363,3 +335,14 @@ index ecc2619..527c5aa 100644
      case 'l':  /* --dhcp-leasefile */
        daemon->lease_file = opt_string_alloc(arg);
        break;
+--- a/Makefile Wed Dec 16 19:24:12 2015
++++ b/Makefile Wed Dec 16 19:28:45 2015
+@@ -74,7 +74,7 @@
+        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+        domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
+-       poll.o rrfilter.o
++       poll.o rrfilter.o isc.o
+ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
+        dns-protocol.h radv-protocol.h ip6addr.h
diff --git a/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch b/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch
new file mode 100644 (file)
index 0000000..7f25066
--- /dev/null
@@ -0,0 +1,2209 @@
+From 9a31b68b59adcac01016d4026d906b69c4216c01 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Tue, 15 Dec 2015 10:20:39 +0000
+Subject: [PATCH] Major rationalisation of DNSSEC validation.
+
+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 |   19 +-
+ src/dnssec.c  |  926 ++++++++++++++++++++++++++++++---------------------------
+ src/forward.c |  741 ++++++++++-----------------------------------
+ 3 files changed, 653 insertions(+), 1033 deletions(-)
+
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index f42acdb..023a1cf 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -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);
+diff --git a/src/dnssec.c b/src/dnssec.c
+index b4dc14e..de7b335 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -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;
+ }
+diff --git a/src/forward.c b/src/forward.c
+index b76a974..dd22a62 100644
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -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)
+-- 
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch b/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch
new file mode 100644 (file)
index 0000000..5ffaf97
--- /dev/null
@@ -0,0 +1,612 @@
+From 93be5b1e023b0c661e1ec2cd6d811a8ec9055c49 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Tue, 15 Dec 2015 12:04:40 +0000
+Subject: [PATCH] Abandon caching RRSIGs and returning them from cache.
+
+The list of exceptions to being able to locally answer
+cached data for validated records when DNSSEC data is requested
+was getting too long, so don't ever do that. This means
+that the cache no longer has to hold RRSIGS and allows
+us to lose lots of code. Note that cached validated
+answers are still returned as long as do=0
+---
+ src/cache.c   |   38 ++---------
+ src/dnsmasq.h |   10 +--
+ src/dnssec.c  |   94 ++++-----------------------
+ src/rfc1035.c |  197 ++++++---------------------------------------------------
+ 4 files changed, 42 insertions(+), 297 deletions(-)
+
+diff --git a/src/cache.c b/src/cache.c
+index 1b76b67..51ba7cc 100644
+--- a/src/cache.c
++++ b/src/cache.c
+@@ -189,12 +189,7 @@ static void cache_hash(struct crec *crecp)
+ static void cache_blockdata_free(struct crec *crecp)
+ {
+   if (crecp->flags & F_DNSKEY)
+-    {
+-      if (crecp->flags & F_DS)
+-      blockdata_free(crecp->addr.sig.keydata);
+-      else
+-      blockdata_free(crecp->addr.key.keydata);
+-    }
++    blockdata_free(crecp->addr.key.keydata);
+   else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG))
+     blockdata_free(crecp->addr.ds.keydata);
+ }
+@@ -369,13 +364,8 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no
+               }
+             
+ #ifdef HAVE_DNSSEC
+-            /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also 
+-               type-covered sensitive for  RRSIG */
+-            if ((flags & (F_DNSKEY | F_DS)) &&
+-                (flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS)) &&
+-                crecp->uid == addr->addr.dnssec.class &&
+-                (!((flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) || 
+-                 crecp->addr.sig.type_covered == addr->addr.dnssec.type))
++            /* Deletion has to be class-sensitive for DS and DNSKEY */
++            if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == addr->addr.dnssec.class)
+               {
+                 if (crecp->flags & F_CONFIG)
+                   return crecp;
+@@ -532,13 +522,9 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
+           struct all_addr free_addr = new->addr.addr;;
+ #ifdef HAVE_DNSSEC
+-          /* For DNSSEC records, addr holds class and type_covered for RRSIG */
++          /* For DNSSEC records, addr holds class. */
+           if (new->flags & (F_DS | F_DNSKEY))
+-            {
+-              free_addr.addr.dnssec.class = new->uid;
+-              if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY))
+-                free_addr.addr.dnssec.type = new->addr.sig.type_covered;
+-            }
++            free_addr.addr.dnssec.class = new->uid;
+ #endif
+           
+           free_avail = 1; /* Must be free space now. */
+@@ -653,9 +639,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
+         if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
+           {
+             if ((crecp->flags & F_FORWARD) && 
+-#ifdef HAVE_DNSSEC
+-                (((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) &&
+-#endif
+                 (crecp->flags & prot) &&
+                 hostname_isequal(cache_get_name(crecp), name))
+               {
+@@ -713,9 +696,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
+   if (ans && 
+       (ans->flags & F_FORWARD) &&
+-#ifdef HAVE_DNSSEC
+-      (((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) &&
+-#endif
+       (ans->flags & prot) &&     
+       hostname_isequal(cache_get_name(ans), name))
+     return ans;
+@@ -1472,11 +1452,7 @@ void dump_cache(time_t now)
+ #ifdef HAVE_DNSSEC
+           else if (cache->flags & F_DS)
+             {
+-              if (cache->flags & F_DNSKEY)
+-                /* RRSIG */
+-                sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
+-                        cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered));
+-              else if (!(cache->flags & F_NEG))
++              if (!(cache->flags & F_NEG))
+                 sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+                         cache->addr.ds.algo, cache->addr.ds.digest);
+             }
+@@ -1502,8 +1478,6 @@ void dump_cache(time_t now)
+           else if (cache->flags & F_CNAME)
+             t = "C";
+ #ifdef HAVE_DNSSEC
+-          else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY))
+-            t = "G"; /* DNSKEY and DS set -> RRISG */
+           else if (cache->flags & F_DS)
+             t = "S";
+           else if (cache->flags & F_DNSKEY)
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index 023a1cf..4344cae 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -398,14 +398,9 @@ struct crec {
+       unsigned char algo;
+       unsigned char digest; 
+     } ds; 
+-    struct {
+-      struct blockdata *keydata;
+-      unsigned short keylen, type_covered, keytag;
+-      char algo;
+-    } sig;
+   } addr;
+   time_t ttd; /* time to die */
+-  /* used as class if DNSKEY/DS/RRSIG, index to source for F_HOSTS */
++  /* used as class if DNSKEY/DS, index to source for F_HOSTS */
+   unsigned int uid; 
+   unsigned short flags;
+   union {
+@@ -445,8 +440,7 @@ struct crec {
+ #define F_SECSTAT   (1u<<24)
+ #define F_NO_RR     (1u<<25)
+ #define F_IPSET     (1u<<26)
+-#define F_NSIGMATCH (1u<<27)
+-#define F_NOEXTRA   (1u<<28)
++#define F_NOEXTRA   (1u<<27)
+ /* Values of uid in crecs with F_CONFIG bit set. */
+ #define SRC_INTERFACE 0
+diff --git a/src/dnssec.c b/src/dnssec.c
+index de7b335..1ae03a6 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -1004,7 +1004,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
+ {
+   unsigned char *psave, *p = (unsigned char *)(header+1);
+   struct crec *crecp, *recp1;
+-  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered;
++  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
+   struct blockdata *key;
+   struct all_addr a;
+@@ -1115,7 +1115,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
+   if (valid)
+     {
+-      /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */
++      /* DNSKEY RRset determined to be OK, now cache it. */
+       cache_start_insert();
+       
+       p = skip_questions(header, plen);
+@@ -1155,7 +1155,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
+                 if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+                   {
+                     if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
+-                      blockdata_free(key);
++                      {
++                        blockdata_free(key);
++                        return STAT_BOGUS;
++                      }
+                     else
+                       {
+                         a.addr.keytag = keytag;
+@@ -1169,38 +1172,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
+                       }
+                   }
+               }
+-            else if (qtype == T_RRSIG)
+-              {
+-                /* RRSIG, cache if covers DNSKEY RRset */
+-                if (rdlen < 18)
+-                  return STAT_BOGUS; /* bad packet */
+-                
+-                GETSHORT(type_covered, p);
+-                
+-                if (type_covered == T_DNSKEY)
+-                  {
+-                    a.addr.dnssec.class = class;
+-                    a.addr.dnssec.type = type_covered;
+-                    
+-                    algo = *p++;
+-                    p += 13; /* labels, orig_ttl, expiration, inception */
+-                    GETSHORT(keytag, p);      
+-                    if ((key = blockdata_alloc((char*)psave, rdlen)))
+-                      {
+-                        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 = rdlen;
+-                            crecp->addr.sig.keytag = keytag;
+-                            crecp->addr.sig.type_covered = type_covered;
+-                            crecp->addr.sig.algo = algo;
+-                          }
+-                      }
+-                  }
+-              }
+-            
++                    
+             p = psave;
+           }
+@@ -1326,7 +1298,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
+         cache_start_insert();
+         
+         a.addr.dnssec.class = class;
+-        cache_insert(name, &a, now, ttl, flags);
++        if (!cache_insert(name, &a, now, ttl, flags))
++          return STAT_BOGUS;
+         
+         cache_end_insert();  
+         
+@@ -2028,14 +2001,13 @@ 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, sigcnt, rrcnt;
++            int ttl, keytag, algo, digest, sigcnt, rrcnt;
+             unsigned char *psave;
+             struct all_addr a;
+             struct blockdata *key;
+             struct crec *crecp;
+             char *wildname;
+-            int have_wildcard = 0;
+-
++            
+             if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+               return STAT_BOGUS;
+@@ -2096,8 +2068,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+                           
+                 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
+@@ -2119,7 +2089,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+                       return rc;
+                   } 
+                 
+-                /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
++                /* 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();
+                 
+@@ -2168,45 +2138,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+                                   } 
+                               }
+                           }
+-                        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;
+                       }
+                     
+diff --git a/src/rfc1035.c b/src/rfc1035.c
+index 4eb1772..def8fa0 100644
+--- a/src/rfc1035.c
++++ b/src/rfc1035.c
+@@ -1275,11 +1275,9 @@ int check_for_local_domain(char *name, time_t now)
+   struct naptr *naptr;
+   /* Note: the call to cache_find_by_name is intended to find any record which matches
+-     ie A, AAAA, CNAME, DS. Because RRSIG records are marked by setting both F_DS and F_DNSKEY,
+-     cache_find_by name ordinarily only returns records with an exact match on those bits (ie
+-     for the call below, only DS records). The F_NSIGMATCH bit changes this behaviour */
++     ie A, AAAA, CNAME. */
+-  if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) &&
++  if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) &&
+       (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+     return 1;
+   
+@@ -1566,9 +1564,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+       GETSHORT(flags, pheader);
+       
+       if ((sec_reqd = flags & 0x8000))
+-      *do_bit = 1;/* do bit */ 
++      {
++        *do_bit = 1;/* do bit */ 
++        *ad_reqd = 1;
++      }
+-      *ad_reqd = 1;
+       dryrun = 1;
+     }
+@@ -1636,98 +1636,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+           }
+       }
+-#ifdef HAVE_DNSSEC
+-      if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS))
+-      {
+-        int gotone = 0;
+-        struct blockdata *keydata;
+-
+-        /* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */
+-        if (sec_reqd)
+-          {
+-            crecp = NULL;
+-            while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
+-              if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype)
+-                break;
+-          }
+-        
+-        if (!sec_reqd || crecp)
+-          {
+-            if (qtype == T_DS)
+-              {
+-                crecp = NULL;
+-                while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
+-                  if (crecp->uid == qclass)
+-                    {
+-                      gotone = 1; 
+-                      if (!dryrun)
+-                        {
+-                          if (crecp->flags & F_NEG)
+-                            {
+-                              if (crecp->flags & F_NXDOMAIN)
+-                                nxdomain = 1;
+-                              log_query(F_UPSTREAM, name, NULL, "no DS");     
+-                            }
+-                          else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
+-                            {                                               
+-                              struct all_addr a;
+-                              a.addr.keytag =  crecp->addr.ds.keytag;
+-                              log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
+-                              if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+-                                                      crec_ttl(crecp, now), &nameoffset,
+-                                                      T_DS, qclass, "sbbt", 
+-                                                      crecp->addr.ds.keytag, crecp->addr.ds.algo, 
+-                                                      crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
+-                                anscount++;
+-                              
+-                            } 
+-                        }
+-                    }
+-              }
+-            else /* DNSKEY */
+-              {
+-                crecp = NULL;
+-                while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
+-                  if (crecp->uid == qclass)
+-                    {
+-                      gotone = 1;
+-                      if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
+-                        {                                                   
+-                          struct all_addr a;
+-                          a.addr.keytag =  crecp->addr.key.keytag;
+-                          log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u");
+-                          if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+-                                                  crec_ttl(crecp, now), &nameoffset,
+-                                                  T_DNSKEY, qclass, "sbbt", 
+-                                                  crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata))
+-                            anscount++;
+-                        }
+-                    }
+-              }
+-          }
+-        
+-        /* Now do RRSIGs */
+-        if (gotone)
+-          {
+-            ans = 1;
+-            auth = 0;
+-            if (!dryrun && sec_reqd)
+-              {
+-                crecp = NULL;
+-                while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
+-                  if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype &&
+-                      (keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
+-                    {
+-                      add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+-                                          crec_ttl(crecp, now), &nameoffset,
+-                                          T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata);
+-                      anscount++;
+-                    }
+-              }
+-          }
+-      }
+-#endif             
+-      
+       if (qclass == C_IN)
+       {
+         struct txt_record *t;
+@@ -1736,6 +1644,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+           if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name))
+             {
+               ans = 1;
++              sec_data = 0;
+               if (!dryrun)
+                 {
+                   log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>");
+@@ -1792,6 +1701,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+             
+             if (intr)
+               {
++                sec_data = 0;
+                 ans = 1;
+                 if (!dryrun)
+                   {
+@@ -1805,6 +1715,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+             else if (ptr)
+               {
+                 ans = 1;
++                sec_data = 0;
+                 if (!dryrun)
+                   {
+                     log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>");
+@@ -1819,38 +1730,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+               }
+             else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa)))
+               {
+-                if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd)
+-                  {
+-                    if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)))
+-                      crecp = NULL;
+-#ifdef HAVE_DNSSEC
+-                    else if (crecp->flags & F_DNSSECOK)
+-                      {
+-                        int gotsig = 0;
+-                        struct crec *rr_crec = NULL;
+-
+-                        while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
+-                          {
+-                            if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN)
+-                              {
+-                                char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
+-                                gotsig = 1;
+-                                
+-                                if (!dryrun && 
+-                                    add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+-                                                        rr_crec->ttd - now, &nameoffset,
+-                                                        T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata))
+-                                  anscount++;
+-                              }
+-                          } 
+-                        
+-                        if (!gotsig)
+-                          crecp = NULL;
+-                      }
+-#endif
+-                  }
+-
+-                if (crecp)
++                /* Don't use cache when DNSSEC data required. */
++                if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK))
+                   {
+                     do 
+                       { 
+@@ -1860,19 +1741,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+                         
+                         if (!(crecp->flags & F_DNSSECOK))
+                           sec_data = 0;
+-                        
++                         
++                        ans = 1;
++                         
+                         if (crecp->flags & F_NEG)
+                           {
+-                            ans = 1;
+                             auth = 0;
+                             if (crecp->flags & F_NXDOMAIN)
+                               nxdomain = 1;
+                             if (!dryrun)
+                               log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL);
+                           }
+-                        else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID))
++                        else
+                           {
+-                            ans = 1;
+                             if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+                               auth = 0;
+                             if (!dryrun)
+@@ -1892,6 +1773,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+             else if (is_rev_synth(is_arpa, &addr, name))
+               {
+                 ans = 1;
++                sec_data = 0;
+                 if (!dryrun)
+                   {
+                     log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); 
+@@ -1908,6 +1790,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+               {
+                 /* if not in cache, enabled and private IPV4 address, return NXDOMAIN */
+                 ans = 1;
++                sec_data = 0;
+                 nxdomain = 1;
+                 if (!dryrun)
+                   log_query(F_CONFIG | F_REVERSE | F_IPV4 | F_NEG | F_NXDOMAIN, 
+@@ -1955,6 +1838,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+                 if (i == 4)
+                   {
+                     ans = 1;
++                    sec_data = 0;
+                     if (!dryrun)
+                       {
+                         addr.addr.addr4.s_addr = htonl(a);
+@@ -1993,6 +1877,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+                               continue;
+ #endif        
+                             ans = 1;  
++                            sec_data = 0;
+                             if (!dryrun)
+                               {
+                                 gotit = 1;
+@@ -2032,48 +1917,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+                     crecp = save;
+                   }
+-                /* If the client asked for DNSSEC and we can't provide RRSIGs, either
+-                   because we've not doing DNSSEC or the cached answer is signed by negative,
+-                   don't answer from the cache, forward instead. */
+-                if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd)
+-                  {
+-                    if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)))
+-                      crecp = NULL;
+-#ifdef HAVE_DNSSEC
+-                    else if (crecp->flags & F_DNSSECOK)
+-                      {
+-                        /* We're returning validated data, need to return the RRSIG too. */
+-                        struct crec *rr_crec = NULL;
+-                        int sigtype = type;
+-                        /* The signature may have expired even though the data is still in cache, 
+-                           forward instead of answering from cache if so. */
+-                        int gotsig = 0;
+-                        
+-                        if (crecp->flags & F_CNAME)
+-                          sigtype = T_CNAME;
+-                        
+-                        while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
+-                          {
+-                            if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN)
+-                              {
+-                                char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
+-                                gotsig = 1;
+-                                
+-                                if (!dryrun && 
+-                                    add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+-                                                        rr_crec->ttd - now, &nameoffset,
+-                                                        T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata))
+-                                  anscount++;
+-                              }
+-                          }
+-                        
+-                        if (!gotsig)
+-                          crecp = NULL;
+-                      }
+-#endif
+-                  }            
+-
+-                if (crecp)
++                /* If the client asked for DNSSEC  don't use cached data. */
++                if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK))
+                   do
+                     { 
+                       /* don't answer wildcard queries with data not from /etc/hosts
+-- 
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch b/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch
new file mode 100644 (file)
index 0000000..ff055f7
--- /dev/null
@@ -0,0 +1,269 @@
+From d64c81fff7faf4392b688223ef3a617c5c07e7dc Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Tue, 15 Dec 2015 16:11:06 +0000
+Subject: [PATCH] Move code which caches DS records to a more logical place.
+
+---
+ src/dnssec.c |  179 +++++++++++++++++++++++++++++-----------------------------
+ 1 file changed, 90 insertions(+), 89 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index 1ae03a6..359231f 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -1204,7 +1204,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
+ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+ {
+   unsigned char *p = (unsigned char *)(header+1);
+-  int qtype, qclass, val, i, neganswer, nons;
++  int qtype, qclass, rc, i, neganswer, nons;
++  int aclass, atype, rdlen;
++  unsigned long ttl;
++  struct all_addr a;
+   if (ntohs(header->qdcount) != 1 ||
+       !(p = skip_name(p, header, plen, 4)))
+@@ -1214,40 +1217,100 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
+   GETSHORT(qclass, p);
+   if (qtype != T_DS || qclass != class)
+-    val = STAT_BOGUS;
++    rc = STAT_BOGUS;
+   else
+-    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
++    rc = 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)
+-    val = STAT_BOGUS;
+-
++  if (rc == STAT_INSECURE)
++    rc = STAT_BOGUS;
++ 
+   p = (unsigned char *)(header+1);
+   extract_name(header, plen, &p, name, 1, 4);
+   p += 4; /* qtype, qclass */
+   
+-  if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
+-    val = STAT_BOGUS;
+-  
+   /* 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 (rc == STAT_BOGUS || (rc == 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 (neganswer)
++  if (rc != STAT_SECURE)
++    return rc;
++   
++  if (!neganswer)
+     {
+-      int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
+-      unsigned long ttl, minttl = ULONG_MAX;
+-      struct all_addr a;
++      cache_start_insert();
++      
++      for (i = 0; i < ntohs(header->ancount); i++)
++      {
++        if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
++          return STAT_BOGUS; /* bad packet */
++        
++        GETSHORT(atype, p);
++        GETSHORT(aclass, p);
++        GETLONG(ttl, p);
++        GETSHORT(rdlen, p);
++        
++        if (!CHECK_LEN(header, p, plen, rdlen))
++          return STAT_BOGUS; /* bad packet */
++        
++        if (aclass == class && atype == T_DS && rc == 1)
++          { 
++            int algo, digest, keytag;
++            unsigned char *psave = p;
++            struct blockdata *key;
++            struct crec *crecp;
++            if (rdlen < 4)
++              return STAT_BOGUS; /* bad packet */
++            
++            GETSHORT(keytag, p);
++            algo = *p++;
++            digest = *p++;
++            
++            /* Cache needs to known class for DNSSEC stuff */
++            a.addr.dnssec.class = class;
++            
++            if ((key = blockdata_alloc((char*)p, rdlen - 4)))
++              {
++                if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
++                  {
++                    blockdata_free(key);
++                    return STAT_BOGUS;
++                  }
++                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 = rdlen - 4; 
++                  } 
++              }
++            
++            p = psave;
++            
++            if (!ADD_RDLEN(header, p, plen, rdlen))
++              return STAT_BOGUS; /* bad packet */
++          }
++        
++        cache_end_insert();
++      }
++    }
++  else
++    {
++      int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
++      unsigned long minttl = ULONG_MAX;
++      
++      if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
++      return STAT_BOGUS;
++      
+       if (RCODE(header) == NXDOMAIN)
+       flags |= F_NXDOMAIN;
+       
+@@ -1261,20 +1324,20 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
+         if (!(p = skip_name(p, header, plen, 0)))
+           return STAT_BOGUS;
+         
+-        GETSHORT(qtype, p); 
+-        GETSHORT(qclass, p);
++        GETSHORT(atype, p); 
++        GETSHORT(aclass, p);
+         GETLONG(ttl, p);
+         GETSHORT(rdlen, p);
+-
++        
+         if (!CHECK_LEN(header, p, plen, rdlen))
+           return STAT_BOGUS; /* bad packet */
+-          
+-        if (qclass != class || qtype != T_SOA)
++        
++        if (aclass != class || atype != T_SOA)
+           {
+             p += rdlen;
+             continue;
+           }
+-           
++        
+         if (ttl < minttl)
+           minttl = ttl;
+         
+@@ -1306,7 +1369,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
+         log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
+       }
+     }
+-
++      
+   return STAT_OK;
+ }
+@@ -2001,11 +2064,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, sigcnt, rrcnt;
+-            unsigned char *psave;
+-            struct all_addr a;
+-            struct blockdata *key;
+-            struct crec *crecp;
++            int sigcnt, rrcnt;
+             char *wildname;
+             
+             if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+@@ -2032,6 +2091,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+                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 */
+@@ -2088,65 +2148,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+                     if (rc == STAT_BOGUS)
+                       return rc;
+                   } 
+-                
+-                /* 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;
+-                        
+-                        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; 
+-                                  } 
+-                              }
+-                          }
+-
+-                        p2 = psave;
+-                      }
+-                    
+-                    if (!ADD_RDLEN(header, p2, plen, rdlen2))
+-                      return STAT_BOGUS; /* bad packet */
+-                  }
+-                
+-                cache_end_insert();
+               }
+           }
+       }
+-- 
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch b/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch
new file mode 100644 (file)
index 0000000..0a4942a
--- /dev/null
@@ -0,0 +1,755 @@
+From c2bcd1e183bcc5fdd63811c045355fc57e36ecfd Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Tue, 15 Dec 2015 17:25:21 +0000
+Subject: [PATCH] Generalise RR-filtering code, for use with EDNS0.
+
+---
+ Makefile       |    3 +-
+ bld/Android.mk |    2 +-
+ src/dnsmasq.h  |    5 +
+ src/dnssec.c   |  307 +-------------------------------------------------
+ src/forward.c  |    2 +-
+ src/rrfilter.c |  339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 349 insertions(+), 309 deletions(-)
+ create mode 100644 src/rrfilter.c
+
+diff --git a/Makefile b/Makefile
+index 4c87ea9..b664160 100644
+--- a/Makefile
++++ b/Makefile
+@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
+        dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
+        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+-       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o
++       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
++       poll.o rrfilter.o
+ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
+        dns-protocol.h radv-protocol.h ip6addr.h
+diff --git a/bld/Android.mk b/bld/Android.mk
+index 5364ee7..67b9c4b 100644
+--- a/bld/Android.mk
++++ b/bld/Android.mk
+@@ -10,7 +10,7 @@ LOCAL_SRC_FILES :=  bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
+                   dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
+                   radv.c slaac.c auth.c ipset.c domain.c \
+                   dnssec.c dnssec-openssl.c blockdata.c tables.c \
+-                  loop.c inotify.c poll.c
++                  loop.c inotify.c poll.c rrfilter.c
+ LOCAL_MODULE := dnsmasq
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index 4344cae..39a930c 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -1513,3 +1513,8 @@ int poll_check(int fd, short event);
+ void poll_listen(int fd, short event);
+ int do_poll(int timeout);
++/* rrfilter.c */
++size_t rrfilter(struct dns_header *header, size_t plen, int mode);
++u16 *rrfilter_desc(int type);
++int expand_workspace(unsigned char ***wkspc, int *szp, int new);
++
+diff --git a/src/dnssec.c b/src/dnssec.c
+index 359231f..fa3eb81 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -507,50 +507,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end)
+     && serial_compare_32(curtime, date_end) == SERIAL_LT;
+ }
+-static u16 *get_desc(int type)
+-{
+-  /* List of RRtypes which include domains in the data.
+-     0 -> domain
+-     integer -> no of plain bytes
+-     -1 -> end
+-
+-     zero is not a valid RRtype, so the final entry is returned for
+-     anything which needs no mangling.
+-  */
+-  
+-  static u16 rr_desc[] = 
+-    { 
+-      T_NS, 0, -1, 
+-      T_MD, 0, -1,
+-      T_MF, 0, -1,
+-      T_CNAME, 0, -1,
+-      T_SOA, 0, 0, -1,
+-      T_MB, 0, -1,
+-      T_MG, 0, -1,
+-      T_MR, 0, -1,
+-      T_PTR, 0, -1,
+-      T_MINFO, 0, 0, -1,
+-      T_MX, 2, 0, -1,
+-      T_RP, 0, 0, -1,
+-      T_AFSDB, 2, 0, -1,
+-      T_RT, 2, 0, -1,
+-      T_SIG, 18, 0, -1,
+-      T_PX, 2, 0, 0, -1,
+-      T_NXT, 0, -1,
+-      T_KX, 2, 0, -1,
+-      T_SRV, 6, 0, -1,
+-      T_DNAME, 0, -1,
+-      0, -1 /* wildcard/catchall */
+-    }; 
+-  
+-  u16 *p = rr_desc;
+-  
+-  while (*p != type && *p != 0)
+-    while (*p++ != (u16)-1);
+-
+-  return p+1;
+-}
+-
+ /* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
+    data, pointed to by *p, should be used raw. */
+ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
+@@ -594,34 +550,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
+     }
+ }
+-static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
+-{
+-  unsigned char **p;
+-  int old = *szp;
+-
+-  if (old >= new+1)
+-    return 1;
+-
+-  if (new >= 100)
+-    return 0;
+-
+-  new += 5;
+-  
+-  if (!(p = whine_malloc(new * sizeof(unsigned char **))))
+-    return 0;  
+-  
+-  if (old != 0 && *wkspc)
+-    {
+-      memcpy(p, *wkspc, old * sizeof(unsigned char **));
+-      free(*wkspc);
+-    }
+-  
+-  *wkspc = p;
+-  *szp = new;
+-
+-  return 1;
+-}
+-
+ /* Bubble sort the RRset into the canonical order. 
+    Note that the byte-streams from two RRs may get unsynced: consider 
+    RRs which have two domain-names at the start and then other data.
+@@ -849,7 +777,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
+   int rdlen, j, name_labels;
+   struct crec *crecp = NULL;
+   int algo, labels, orig_ttl, key_tag;
+-  u16 *rr_desc = get_desc(type);
++  u16 *rr_desc = rrfilter_desc(type);
+  
+   if (wildcard_out)
+     *wildcard_out = NULL;
+@@ -2266,239 +2194,6 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
+   return ret;
+ }
+-/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+-   we've chopped out of the packet, or check they don't point into an elided part.  */
+-static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+-{
+-  unsigned char *ansp = *namep;
+-
+-  while(1)
+-    {
+-      unsigned int label_type;
+-      
+-      if (!CHECK_LEN(header, ansp, plen, 1))
+-      return 0;
+-      
+-      label_type = (*ansp) & 0xc0;
+-
+-      if (label_type == 0xc0)
+-      {
+-        /* pointer for compression. */
+-        unsigned int offset;
+-        int i;
+-        unsigned char *p;
+-        
+-        if (!CHECK_LEN(header, ansp, plen, 2))
+-          return 0;
+-
+-        offset = ((*ansp++) & 0x3f) << 8;
+-        offset |= *ansp++;
+-
+-        p = offset + (unsigned char *)header;
+-        
+-        for (i = 0; i < rr_count; i++)
+-          if (p < rrs[i])
+-            break;
+-          else
+-            if (i & 1)
+-              offset -= rrs[i] - rrs[i-1];
+-
+-        /* does the pointer end up in an elided RR? */
+-        if (i & 1)
+-          return 0;
+-
+-        /* No, scale the pointer */
+-        if (fixup)
+-          {
+-            ansp -= 2;
+-            *ansp++ = (offset >> 8) | 0xc0;
+-            *ansp++ = offset & 0xff;
+-          }
+-        break;
+-      }
+-      else if (label_type == 0x80)
+-      return 0; /* reserved */
+-      else if (label_type == 0x40)
+-      {
+-        /* Extended label type */
+-        unsigned int count;
+-        
+-        if (!CHECK_LEN(header, ansp, plen, 2))
+-          return 0;
+-        
+-        if (((*ansp++) & 0x3f) != 1)
+-          return 0; /* we only understand bitstrings */
+-        
+-        count = *(ansp++); /* Bits in bitstring */
+-        
+-        if (count == 0) /* count == 0 means 256 bits */
+-          ansp += 32;
+-        else
+-          ansp += ((count-1)>>3)+1;
+-      }
+-      else
+-      { /* label type == 0 Bottom six bits is length */
+-        unsigned int len = (*ansp++) & 0x3f;
+-        
+-        if (!ADD_RDLEN(header, ansp, plen, len))
+-          return 0;
+-
+-        if (len == 0)
+-          break; /* zero length label marks the end. */
+-      }
+-    }
+-
+-  *namep = ansp;
+-
+-  return 1;
+-}
+-
+-/* Go through RRs and check or fixup the domain names contained within */
+-static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+-{
+-  int i, type, class, rdlen;
+-  unsigned char *pp;
+-  
+-  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
+-    {
+-      pp = p;
+-
+-      if (!(p = skip_name(p, header, plen, 10)))
+-      return 0;
+-      
+-      GETSHORT(type, p); 
+-      GETSHORT(class, p);
+-      p += 4; /* TTL */
+-      GETSHORT(rdlen, p);
+-
+-      if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+-      {
+-        /* fixup name of RR */
+-        if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+-          return 0;
+-        
+-        if (class == C_IN)
+-          {
+-            u16 *d;
+- 
+-            for (pp = p, d = get_desc(type); *d != (u16)-1; d++)
+-              {
+-                if (*d != 0)
+-                  pp += *d;
+-                else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+-                  return 0;
+-              }
+-          }
+-      }
+-      
+-      if (!ADD_RDLEN(header, p, plen, rdlen))
+-      return 0;
+-    }
+-  
+-  return 1;
+-}
+-      
+-
+-size_t filter_rrsigs(struct dns_header *header, size_t plen)
+-{
+-  static unsigned char **rrs;
+-  static int rr_sz = 0;
+-  
+-  unsigned char *p = (unsigned char *)(header+1);
+-  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
+-
+-  if (ntohs(header->qdcount) != 1 ||
+-      !(p = skip_name(p, header, plen, 4)))
+-    return plen;
+-  
+-  GETSHORT(qtype, p);
+-  GETSHORT(qclass, p);
+-
+-  /* First pass, find pointers to start and end of all the records we wish to elide:
+-     records added for DNSSEC, unless explicity queried for */
+-  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
+-       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
+-       i++)
+-    {
+-      unsigned char *pstart = p;
+-      int type, class;
+-
+-      if (!(p = skip_name(p, header, plen, 10)))
+-      return plen;
+-      
+-      GETSHORT(type, p); 
+-      GETSHORT(class, p);
+-      p += 4; /* TTL */
+-      GETSHORT(rdlen, p);
+-      
+-      if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && 
+-        (type != qtype || class != qclass))
+-      {
+-        if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+-          return plen; 
+-        
+-        rrs[rr_found++] = pstart;
+-
+-        if (!ADD_RDLEN(header, p, plen, rdlen))
+-          return plen;
+-        
+-        rrs[rr_found++] = p;
+-        
+-        if (i < ntohs(header->ancount))
+-          chop_an++;
+-        else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
+-          chop_ns++;
+-        else
+-          chop_ar++;
+-      }
+-      else if (!ADD_RDLEN(header, p, plen, rdlen))
+-      return plen;
+-    }
+-  
+-  /* Nothing to do. */
+-  if (rr_found == 0)
+-    return plen;
+-
+-  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+-     point to records we're going to elide. This is theoretically possible, but unlikely. If
+-     it happens, we give up and leave the answer unchanged. */
+-  p = (unsigned char *)(header+1);
+-  
+-  /* question first */
+-  if (!check_name(&p, header, plen, 0, rrs, rr_found))
+-    return plen;
+-  p += 4; /* qclass, qtype */
+-  
+-  /* Now answers and NS */
+-  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+-    return plen;
+-  
+-  /* Third pass, elide records */
+-  for (p = rrs[0], i = 1; i < rr_found; i += 2)
+-    {
+-      unsigned char *start = rrs[i];
+-      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
+-      
+-      memmove(p, start, end-start);
+-      p += end-start;
+-    }
+-     
+-  plen = p - (unsigned char *)header;
+-  header->ancount = htons(ntohs(header->ancount) - chop_an);
+-  header->nscount = htons(ntohs(header->nscount) - chop_ns);
+-  header->arcount = htons(ntohs(header->arcount) - chop_ar);
+-
+-  /* Fourth pass, fix up pointers in the remaining records */
+-  p = (unsigned char *)(header+1);
+-  
+-  check_name(&p, header, plen, 1, rrs, rr_found);
+-  p += 4; /* qclass, qtype */
+-  
+-  check_rrs(p, header, plen, 1, rrs, rr_found);
+-  
+-  return plen;
+-}
+-
+ unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
+ {
+   int q;
+diff --git a/src/forward.c b/src/forward.c
+index dd22a62..3e801c8 100644
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -662,7 +662,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
+   /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+   if (!do_bit)
+-    n = filter_rrsigs(header, n);
++    n = rrfilter(header, n, 1);
+ #endif
+   /* do this after extract_addresses. Ensure NODATA reply and remove
+diff --git a/src/rrfilter.c b/src/rrfilter.c
+new file mode 100644
+index 0000000..ae12261
+--- /dev/null
++++ b/src/rrfilter.c
+@@ -0,0 +1,339 @@
++/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; version 2 dated June, 1991, or
++   (at your option) version 3 dated 29 June, 2007.
++ 
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++     
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>.
++*/
++
++/* Code to safely remove RRs from an DNS answer */ 
++
++#include "dnsmasq.h"
++
++/* Go through a domain name, find "pointers" and fix them up based on how many bytes
++   we've chopped out of the packet, or check they don't point into an elided part.  */
++static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
++{
++  unsigned char *ansp = *namep;
++
++  while(1)
++    {
++      unsigned int label_type;
++      
++      if (!CHECK_LEN(header, ansp, plen, 1))
++      return 0;
++      
++      label_type = (*ansp) & 0xc0;
++
++      if (label_type == 0xc0)
++      {
++        /* pointer for compression. */
++        unsigned int offset;
++        int i;
++        unsigned char *p;
++        
++        if (!CHECK_LEN(header, ansp, plen, 2))
++          return 0;
++
++        offset = ((*ansp++) & 0x3f) << 8;
++        offset |= *ansp++;
++
++        p = offset + (unsigned char *)header;
++        
++        for (i = 0; i < rr_count; i++)
++          if (p < rrs[i])
++            break;
++          else
++            if (i & 1)
++              offset -= rrs[i] - rrs[i-1];
++
++        /* does the pointer end up in an elided RR? */
++        if (i & 1)
++          return 0;
++
++        /* No, scale the pointer */
++        if (fixup)
++          {
++            ansp -= 2;
++            *ansp++ = (offset >> 8) | 0xc0;
++            *ansp++ = offset & 0xff;
++          }
++        break;
++      }
++      else if (label_type == 0x80)
++      return 0; /* reserved */
++      else if (label_type == 0x40)
++      {
++        /* Extended label type */
++        unsigned int count;
++        
++        if (!CHECK_LEN(header, ansp, plen, 2))
++          return 0;
++        
++        if (((*ansp++) & 0x3f) != 1)
++          return 0; /* we only understand bitstrings */
++        
++        count = *(ansp++); /* Bits in bitstring */
++        
++        if (count == 0) /* count == 0 means 256 bits */
++          ansp += 32;
++        else
++          ansp += ((count-1)>>3)+1;
++      }
++      else
++      { /* label type == 0 Bottom six bits is length */
++        unsigned int len = (*ansp++) & 0x3f;
++        
++        if (!ADD_RDLEN(header, ansp, plen, len))
++          return 0;
++
++        if (len == 0)
++          break; /* zero length label marks the end. */
++      }
++    }
++
++  *namep = ansp;
++
++  return 1;
++}
++
++/* Go through RRs and check or fixup the domain names contained within */
++static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
++{
++  int i, j, type, class, rdlen;
++  unsigned char *pp;
++  
++  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
++    {
++      pp = p;
++
++      if (!(p = skip_name(p, header, plen, 10)))
++      return 0;
++      
++      GETSHORT(type, p); 
++      GETSHORT(class, p);
++      p += 4; /* TTL */
++      GETSHORT(rdlen, p);
++
++      /* If this RR is to be elided, don't fix up its contents */
++      for (j = 0; j < rr_count; j += 2)
++      if (rrs[j] == pp)
++        break;
++
++      if (j >= rr_count)
++      {
++        /* fixup name of RR */
++        if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
++          return 0;
++        
++        if (class == C_IN)
++          {
++            u16 *d;
++ 
++            for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
++              {
++                if (*d != 0)
++                  pp += *d;
++                else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
++                  return 0;
++              }
++          }
++      }
++      
++      if (!ADD_RDLEN(header, p, plen, rdlen))
++      return 0;
++    }
++  
++  return 1;
++}
++      
++
++/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */
++size_t rrfilter(struct dns_header *header, size_t plen, int mode)
++{
++  static unsigned char **rrs;
++  static int rr_sz = 0;
++
++  unsigned char *p = (unsigned char *)(header+1);
++  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
++
++  if (ntohs(header->qdcount) != 1 ||
++      !(p = skip_name(p, header, plen, 4)))
++    return plen;
++  
++  GETSHORT(qtype, p);
++  GETSHORT(qclass, p);
++
++  /* First pass, find pointers to start and end of all the records we wish to elide:
++     records added for DNSSEC, unless explicity queried for */
++  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
++       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
++       i++)
++    {
++      unsigned char *pstart = p;
++      int type, class;
++
++      if (!(p = skip_name(p, header, plen, 10)))
++      return plen;
++      
++      GETSHORT(type, p); 
++      GETSHORT(class, p);
++      p += 4; /* TTL */
++      GETSHORT(rdlen, p);
++        
++      if (!ADD_RDLEN(header, p, plen, rdlen))
++      return plen;
++
++      /* Don't remove the answer. */
++      if (i < ntohs(header->ancount) && type == qtype && class == qclass)
++      continue;
++      
++      if (mode == 0) /* EDNS */
++      {
++        /* EDNS mode, remove T_OPT from additional section only */
++        if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
++          continue;
++      }
++      else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
++      /* DNSSEC mode, remove SIGs and NSECs from all three sections. */
++      continue;
++      
++      
++      if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
++      return plen; 
++      
++      rrs[rr_found++] = pstart;
++      rrs[rr_found++] = p;
++      
++      if (i < ntohs(header->ancount))
++      chop_an++;
++      else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
++      chop_ns++;
++      else
++      chop_ar++;
++    }
++  
++  /* Nothing to do. */
++  if (rr_found == 0)
++    return plen;
++
++  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
++     point to records we're going to elide. This is theoretically possible, but unlikely. If
++     it happens, we give up and leave the answer unchanged. */
++  p = (unsigned char *)(header+1);
++  
++  /* question first */
++  if (!check_name(&p, header, plen, 0, rrs, rr_found))
++    return plen;
++  p += 4; /* qclass, qtype */
++  
++  /* Now answers and NS */
++  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
++    return plen;
++  
++  /* Third pass, elide records */
++  for (p = rrs[0], i = 1; i < rr_found; i += 2)
++    {
++      unsigned char *start = rrs[i];
++      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
++      
++      memmove(p, start, end-start);
++      p += end-start;
++    }
++     
++  plen = p - (unsigned char *)header;
++  header->ancount = htons(ntohs(header->ancount) - chop_an);
++  header->nscount = htons(ntohs(header->nscount) - chop_ns);
++  header->arcount = htons(ntohs(header->arcount) - chop_ar);
++
++  /* Fourth pass, fix up pointers in the remaining records */
++  p = (unsigned char *)(header+1);
++  
++  check_name(&p, header, plen, 1, rrs, rr_found);
++  p += 4; /* qclass, qtype */
++  
++  check_rrs(p, header, plen, 1, rrs, rr_found);
++  
++  return plen;
++}
++
++/* This is used in the DNSSEC code too, hence it's exported */
++u16 *rrfilter_desc(int type)
++{
++  /* List of RRtypes which include domains in the data.
++     0 -> domain
++     integer -> no of plain bytes
++     -1 -> end
++
++     zero is not a valid RRtype, so the final entry is returned for
++     anything which needs no mangling.
++  */
++  
++  static u16 rr_desc[] = 
++    { 
++      T_NS, 0, -1, 
++      T_MD, 0, -1,
++      T_MF, 0, -1,
++      T_CNAME, 0, -1,
++      T_SOA, 0, 0, -1,
++      T_MB, 0, -1,
++      T_MG, 0, -1,
++      T_MR, 0, -1,
++      T_PTR, 0, -1,
++      T_MINFO, 0, 0, -1,
++      T_MX, 2, 0, -1,
++      T_RP, 0, 0, -1,
++      T_AFSDB, 2, 0, -1,
++      T_RT, 2, 0, -1,
++      T_SIG, 18, 0, -1,
++      T_PX, 2, 0, 0, -1,
++      T_NXT, 0, -1,
++      T_KX, 2, 0, -1,
++      T_SRV, 6, 0, -1,
++      T_DNAME, 0, -1,
++      0, -1 /* wildcard/catchall */
++    }; 
++  
++  u16 *p = rr_desc;
++  
++  while (*p != type && *p != 0)
++    while (*p++ != (u16)-1);
++
++  return p+1;
++}
++
++int expand_workspace(unsigned char ***wkspc, int *szp, int new)
++{
++  unsigned char **p;
++  int old = *szp;
++
++  if (old >= new+1)
++    return 1;
++
++  if (new >= 100)
++    return 0;
++
++  new += 5;
++  
++  if (!(p = whine_malloc(new * sizeof(unsigned char **))))
++    return 0;  
++  
++  if (old != 0 && *wkspc)
++    {
++      memcpy(p, *wkspc, old * sizeof(unsigned char **));
++      free(*wkspc);
++    }
++  
++  *wkspc = p;
++  *szp = new;
++
++  return 1;
++}
+-- 
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch b/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch
new file mode 100644 (file)
index 0000000..ffb412b
--- /dev/null
@@ -0,0 +1,134 @@
+From 2dbba34b2c1289a108f876c78b84889f2a93115d Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Wed, 16 Dec 2015 13:41:58 +0000
+Subject: [PATCH] DNSSEC validation tweak.
+
+A zone which has at least one key with an algorithm we don't
+support should be considered as insecure.
+---
+ src/dnssec.c |   82 ++++++++++++++++++++++++++++++++++++++--------------------
+ 1 file changed, 54 insertions(+), 28 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index fa3eb81..dc563e0 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -763,10 +763,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
+    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,
++   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.
+-   name is unchanged on exit. keyname is used as workspace and trashed.
++   Name is unchanged on exit. keyname is used as workspace and trashed.
+    Call explore_rrset first to find and count RRs and sigs.
+ */
+@@ -919,6 +919,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
+   return STAT_BOGUS;
+ }
+  
++
+ /* 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:
+@@ -1831,15 +1832,15 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ /* 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.
+-   
++   STAT_SECURE      zone is signed.
++   STAT_INSECURE    zone proved unsigned.
++   STAT_NEED_DS     require DS record of name returned in keyname.
++   STAT_NEED_DNSKEY require DNSKEY 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);
++  int secure_ds, name_start = strlen(name);
+   struct crec *crecp;
+   char *p;
+   
+@@ -1850,27 +1851,52 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
+       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)));
+-      
++      {
++        secure_ds = 0;
++        
++        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 */
++                else
++                  secure_ds = 1;
++              }
++          }
++        while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
++      }
++
++      if (secure_ds)
++      {
++        /* We've found only DS records that attest to the DNSKEY RRset in the zone, so we believe
++           that RRset is good. Furthermore the DNSKEY whose hash is proved by the DS record is
++           one we can use. However the DNSKEY RRset may contain more than one key and
++           one of the other keys may use an algorithm we don't support. If that's 
++           the case the zone is insecure for us. */
++        
++        if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
++          return STAT_NEED_KEY;
++
++        do 
++          {
++            if (crecp->uid == (unsigned int)class && !algo_digest_name(crecp->addr.key.algo))
++              return STAT_INSECURE;
++          }
++        while ((crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)));
++      }
++
+       if (name_start == 0)
+       break;
+-- 
+1.7.10.4
+