--- /dev/null
+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
+