The neverending story continues...
Signed-off-by: Matthias Fischer <matthias.fischer(a)ipfire.org>
---
lfs/dnsmasq | 4 +
...1-Tweaks_to_EDNS0_handling_in_DNS_replies.patch | 133 +++++++
..._code_Check_zone_status_is_NSEC_proof_bad.patch | 409 +++++++++++++++++++++
...023-Fix_brace_botch_in_dnssec_validate_ds.patch | 98 +++++
...ning_which_DNSSEC_sig_algos_are_supported.patch | 145 ++++++++
5 files changed, 789 insertions(+)
create mode 100644 src/patches/dnsmasq/021-Tweaks_to_EDNS0_handling_in_DNS_replies.patch
create mode 100644 src/patches/dnsmasq/022-Tidy_up_DNSSEC_non-existence_code_Check_zone_status_is_NSEC_proof_bad.patch
create mode 100644 src/patches/dnsmasq/023-Fix_brace_botch_in_dnssec_validate_ds.patch
create mode 100644 src/patches/dnsmasq/024-Do_a_better_job_of_determining_which_DNSSEC_sig_algos_are_supported.patch
diff --git a/lfs/dnsmasq b/lfs/dnsmasq
index eeb7e03..c8fd7db 100644
--- a/lfs/dnsmasq
+++ b/lfs/dnsmasq
@@ -93,6 +93,10 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects))
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/021-Tweaks_to_EDNS0_handling_in_DNS_replies.patch
+ cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/022-Tidy_up_DNSSEC_non-existence_code_Check_zone_status_is_NSEC_proof_bad.patch
+ cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/023-Fix_brace_botch_in_dnssec_validate_ds.patch
+ cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/024-Do_a_better_job_of_determining_which_DNSSEC_sig_algos_are_supported.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 \
diff --git a/src/patches/dnsmasq/021-Tweaks_to_EDNS0_handling_in_DNS_replies.patch b/src/patches/dnsmasq/021-Tweaks_to_EDNS0_handling_in_DNS_replies.patch
new file mode 100644
index 0000000..c3c74cc
--- /dev/null
+++ b/src/patches/dnsmasq/021-Tweaks_to_EDNS0_handling_in_DNS_replies.patch
@@ -0,0 +1,133 @@
+From dd4ad9ac7ea6d51dcc34a1f2cd2da14efbb87714 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon(a)thekelleys.org.uk>
+Date: Thu, 17 Dec 2015 10:44:58 +0000
+Subject: [PATCH] Tweaks to EDNS0 handling in DNS replies.
+
+---
+ src/dnssec.c | 20 +++++++++-----------
+ src/rfc1035.c | 57 +++++++++++++++++++++++++++++++++------------------------
+ 2 files changed, 42 insertions(+), 35 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index dc563e0..012b2a6 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -2129,18 +2129,16 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ /* Empty DS without NSECS */
+ if (qtype == T_DS)
+ return STAT_BOGUS;
+- else
++
++ rc = zone_status(name, qclass, keyname, now);
++ if (rc != STAT_SECURE)
+ {
+- rc = zone_status(name, qclass, keyname, now);
+- if (rc != STAT_SECURE)
+- {
+- if (class)
+- *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
+- return rc;
+- }
+-
+- return STAT_BOGUS; /* signed zone, no NSECs */
+- }
++ if (class)
++ *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
++ return rc;
++ }
++
++ return STAT_BOGUS; /* signed zone, no NSECs */
+ }
+
+ if (nsec_type == T_NSEC)
+diff --git a/src/rfc1035.c b/src/rfc1035.c
+index def8fa0..188d05f 100644
+--- a/src/rfc1035.c
++++ b/src/rfc1035.c
+@@ -1539,7 +1539,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+ int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
+ struct mx_srv_record *rec;
+ size_t len;
+-
++
++ if (ntohs(header->ancount) != 0 ||
++ ntohs(header->nscount) != 0 ||
++ ntohs(header->qdcount) == 0 ||
++ OPCODE(header) != QUERY )
++ return 0;
++
+ /* Don't return AD set if checking disabled. */
+ if (header->hb4 & HB4_CD)
+ sec_data = 0;
+@@ -1548,33 +1554,32 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+ *ad_reqd = header->hb4 & HB4_AD;
+ *do_bit = 0;
+
+- /* If there is an RFC2671 pseudoheader then it will be overwritten by
++ /* If there is an additional data section then it will be overwritten by
+ partial replies, so we have to do a dry run to see if we can answer
+- the query. We check to see if the do bit is set, if so we always
+- forward rather than answering from the cache, which doesn't include
+- security information, unless we're in DNSSEC validation mode. */
++ the query. */
+
+- if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
+- {
+- unsigned short flags;
+-
+- have_pseudoheader = 1;
++ if (ntohs(header->arcount) != 0)
++ {
++ dryrun = 1;
+
+- pheader += 4; /* udp size, ext_rcode */
+- GETSHORT(flags, pheader);
+-
+- if ((sec_reqd = flags & 0x8000))
+- {
+- *do_bit = 1;/* do bit */
+- *ad_reqd = 1;
++ /* If there's an additional section, there might be an EDNS(0) pseudoheader */
++ if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
++ {
++ unsigned short flags;
++
++ have_pseudoheader = 1;
++
++ pheader += 4; /* udp size, ext_rcode */
++ GETSHORT(flags, pheader);
++
++ if ((sec_reqd = flags & 0x8000))
++ {
++ *do_bit = 1;/* do bit */
++ *ad_reqd = 1;
++ }
+ }
+-
+- dryrun = 1;
+ }
+
+- if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
+- return 0;
+-
+ for (rec = daemon->mxnames; rec; rec = rec->next)
+ rec->offset = 0;
+
+@@ -1730,8 +1735,12 @@ 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)))
+ {
+- /* Don't use cache when DNSSEC data required. */
+- if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK))
++ /* Don't use cache when DNSSEC data required, unless we know that
++ the zone is unsigned, which implies that we're doing
++ validation. */
++ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
++ !sec_reqd ||
++ (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))
+ {
+ do
+ {
+--
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/022-Tidy_up_DNSSEC_non-existence_code_Check_zone_status_is_NSEC_proof_bad.patch b/src/patches/dnsmasq/022-Tidy_up_DNSSEC_non-existence_code_Check_zone_status_is_NSEC_proof_bad.patch
new file mode 100644
index 0000000..60503e9
--- /dev/null
+++ b/src/patches/dnsmasq/022-Tidy_up_DNSSEC_non-existence_code_Check_zone_status_is_NSEC_proof_bad.patch
@@ -0,0 +1,409 @@
+From b40f26c0199235073abc37e1e1d6ed93bed372f5 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon(a)thekelleys.org.uk>
+Date: Thu, 17 Dec 2015 11:57:26 +0000
+Subject: [PATCH] Tidy up DNSSEC non-existence code. Check zone status is NSEC
+ proof bad.
+
+---
+ src/dnssec.c | 207 +++++++++++++++++++++++++---------------------------------
+ 1 file changed, 90 insertions(+), 117 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index 012b2a6..ddae497 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -1367,59 +1367,6 @@ static int hostname_cmp(const char *a, const char *b)
+ }
+ }
+
+-/* Find all the NSEC or NSEC3 records in a reply.
+- return an array of pointers to them. */
+-static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd)
+-{
+- static unsigned char **nsecset = NULL;
+- static int nsecset_sz = 0;
+-
+- int type_found = 0;
+- unsigned char *p = skip_questions(header, plen);
+- int type, class, rdlen, i, nsecs_found;
+-
+- /* Move to NS section */
+- if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
+- return 0;
+-
+- for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
+- {
+- unsigned char *pstart = p;
+-
+- if (!(p = skip_name(p, header, plen, 10)))
+- return 0;
+-
+- GETSHORT(type, p);
+- GETSHORT(class, p);
+- p += 4; /* TTL */
+- GETSHORT(rdlen, p);
+-
+- if (class == class_reqd && (type == T_NSEC || type == T_NSEC3))
+- {
+- /* No mixed NSECing 'round here, thankyouverymuch */
+- if (type_found == T_NSEC && type == T_NSEC3)
+- return 0;
+- if (type_found == T_NSEC3 && type == T_NSEC)
+- return 0;
+-
+- type_found = type;
+-
+- if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+- return 0;
+-
+- nsecset[nsecs_found++] = pstart;
+- }
+-
+- if (!ADD_RDLEN(header, p, plen, rdlen))
+- return 0;
+- }
+-
+- *nsecsetp = nsecset;
+- *nsecsetl = nsecs_found;
+-
+- return type_found;
+-}
+-
+ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
+ char *workspace1, char *workspace2, char *name, int type, int *nons)
+ {
+@@ -1436,12 +1383,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
+ {
+ p = nsecs[i];
+ if (!extract_name(header, plen, &p, workspace1, 1, 10))
+- return STAT_BOGUS;
++ return 0;
+ p += 8; /* class, type, TTL */
+ GETSHORT(rdlen, p);
+ psave = p;
+ if (!extract_name(header, plen, &p, workspace2, 1, 10))
+- return STAT_BOGUS;
++ return 0;
+
+ rc = hostname_cmp(workspace1, name);
+
+@@ -1449,7 +1396,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
+ {
+ /* 4035 para 5.4. Last sentence */
+ if (type == T_NSEC || type == T_RRSIG)
+- return STAT_SECURE;
++ return 1;
+
+ /* NSEC with the same name as the RR we're testing, check
+ that the type in question doesn't appear in the type map */
+@@ -1465,24 +1412,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
+ /* 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;
++ 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 STAT_BOGUS;
++ return 0;
+ }
+
+ while (rdlen >= 2)
+ {
+ if (!CHECK_LEN(header, p, plen, rdlen))
+- return STAT_BOGUS;
++ return 0;
+
+ if (p[0] == type >> 8)
+ {
+ /* Does the NSEC say our type exists? */
+ if (offset < p[1] && (p[offset+2] & mask) != 0)
+- return STAT_BOGUS;
++ return 0;
+
+ break; /* finshed checking */
+ }
+@@ -1491,24 +1438,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
+ p += p[1];
+ }
+
+- return STAT_SECURE;
++ return 1;
+ }
+ else if (rc == -1)
+ {
+ /* Normal case, name falls between NSEC name and next domain name,
+ wrap around case, name falls between NSEC name (rc == -1) and end */
+ if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
+- return STAT_SECURE;
++ return 1;
+ }
+ else
+ {
+ /* wrap around case, name falls between start and next domain name */
+ if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
+- return STAT_SECURE;
++ return 1;
+ }
+ }
+
+- return STAT_BOGUS;
++ return 0;
+ }
+
+ /* return digest length, or zero on error */
+@@ -1701,7 +1648,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ for (i = 0; i < nsec_count; i++)
+ {
+ if (!(p = skip_name(nsecs[i], header, plen, 15)))
+- return STAT_BOGUS; /* bad packet */
++ return 0; /* bad packet */
+
+ p += 10; /* type, class, TTL, rdlen */
+ algo = *p++;
+@@ -1712,14 +1659,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+
+ /* No usable NSEC3s */
+ if (i == nsec_count)
+- return STAT_BOGUS;
++ return 0;
+
+ p++; /* flags */
+ GETSHORT (iterations, p);
+ salt_len = *p++;
+ salt = p;
+ if (!CHECK_LEN(header, salt, plen, salt_len))
+- return STAT_BOGUS; /* bad packet */
++ return 0; /* bad packet */
+
+ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
+ for (i = 0; i < nsec_count; i++)
+@@ -1730,7 +1677,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ nsecs[i] = NULL; /* Speculative, will be restored if OK. */
+
+ if (!(p = skip_name(nsec3p, header, plen, 15)))
+- return STAT_BOGUS; /* bad packet */
++ return 0; /* bad packet */
+
+ p += 10; /* type, class, TTL, rdlen */
+
+@@ -1747,7 +1694,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ continue;
+
+ if (!CHECK_LEN(header, p, plen, salt_len))
+- return STAT_BOGUS; /* bad packet */
++ return 0; /* bad packet */
+
+ if (memcmp(p, salt, salt_len) != 0)
+ continue;
+@@ -1758,13 +1705,13 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+
+ /* Algo is checked as 1 above */
+ if (!(hash = hash_find("sha1")))
+- return STAT_BOGUS;
++ return 0;
+
+ if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
+- return STAT_BOGUS;
++ return 0;
+
+ if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons))
+- return STAT_SECURE;
++ return 1;
+
+ /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
+ or an answer inferred from a wildcard record. */
+@@ -1780,14 +1727,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ break;
+
+ if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
+- return STAT_BOGUS;
++ return 0;
+
+ for (i = 0; i < nsec_count; i++)
+ if ((p = nsecs[i]))
+ {
+ if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+ !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+- return STAT_BOGUS;
++ return 0;
+
+ if (digest_len == base32_len &&
+ memcmp(digest, workspace2, digest_len) == 0)
+@@ -1802,32 +1749,81 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
+ while ((closest_encloser = strchr(closest_encloser, '.')));
+
+ if (!closest_encloser)
+- return STAT_BOGUS;
++ return 0;
+
+ /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
+ if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
+- return STAT_BOGUS;
++ return 0;
+
+ if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
+- return STAT_BOGUS;
++ return 0;
+
+ /* Finally, check that there's no seat of wildcard synthesis */
+ if (!wildname)
+ {
+ if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
+- return STAT_BOGUS;
++ return 0;
+
+ wildcard--;
+ *wildcard = '*';
+
+ if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
+- return STAT_BOGUS;
++ return 0;
+
+ if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
+- return STAT_BOGUS;
++ return 0;
+ }
+
+- return STAT_SECURE;
++ return 1;
++}
++
++static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
++{
++ static unsigned char **nsecset = NULL;
++ static int nsecset_sz = 0;
++
++ int type_found = 0;
++ unsigned char *p = skip_questions(header, plen);
++ int type, class, rdlen, i, nsecs_found;
++
++ /* Move to NS section */
++ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
++ return 0;
++
++ for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
++ {
++ unsigned char *pstart = p;
++
++ if (!(p = skip_name(p, header, plen, 10)))
++ return 0;
++
++ GETSHORT(type, p);
++ GETSHORT(class, p);
++ p += 4; /* TTL */
++ GETSHORT(rdlen, p);
++
++ if (class == qclass && (type == T_NSEC || type == T_NSEC3))
++ {
++ /* No mixed NSECing 'round here, thankyouverymuch */
++ if (type_found != 0 && type_found != type)
++ return 0;
++
++ type_found = type;
++
++ if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
++ return 0;
++
++ nsecset[nsecs_found++] = pstart;
++ }
++
++ if (!ADD_RDLEN(header, p, plen, rdlen))
++ return 0;
++ }
++
++ if (type_found == T_NSEC)
++ return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
++ else
++ return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+ }
+
+ /* Check signing status of name.
+@@ -1925,10 +1921,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ static unsigned char **targets = NULL;
+ static int target_sz = 0;
+
+- unsigned char *ans_start, *p1, *p2, **nsecs;
++ unsigned char *ans_start, *p1, *p2;
+ int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
+- int i, j, rc, nsec_count;
+- int nsec_type;
++ int i, j, rc;
+
+ if (neganswer)
+ *neganswer = 0;
+@@ -2080,28 +2075,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ targets[j] = NULL;
+ }
+
+- if (rc == STAT_SECURE_WILDCARD)
+- {
+- /* 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 (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;
+- }
++ /* 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.
++ Note that we may not yet have validated the NSEC/NSEC3 RRsets.
++ That's not a problem since if the RRsets later fail
++ we'll return BOGUS then. */
++ if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
++ return STAT_BOGUS;
+ }
+ }
+ }
+@@ -2124,14 +2106,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+
+ /* 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)))
++ if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons))
+ {
+ /* Empty DS without NSECS */
+ if (qtype == T_DS)
+ return STAT_BOGUS;
+
+- rc = zone_status(name, qclass, keyname, now);
+- if (rc != STAT_SECURE)
++ if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
+ {
+ if (class)
+ *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
+@@ -2140,14 +2121,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+
+ return STAT_BOGUS; /* signed zone, no NSECs */
+ }
+-
+- 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);
+-
+- if (rc != STAT_SECURE)
+- return rc;
+ }
+
+ return STAT_SECURE;
+--
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/023-Fix_brace_botch_in_dnssec_validate_ds.patch b/src/patches/dnsmasq/023-Fix_brace_botch_in_dnssec_validate_ds.patch
new file mode 100644
index 0000000..eda6fbd
--- /dev/null
+++ b/src/patches/dnsmasq/023-Fix_brace_botch_in_dnssec_validate_ds.patch
@@ -0,0 +1,98 @@
+From 3b799c826db05fc2da1c6d15cbe372e394209d27 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon(a)thekelleys.org.uk>
+Date: Thu, 17 Dec 2015 16:58:04 +0000
+Subject: [PATCH] Fix brace botch in dnssec_validate_ds()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf8
+Content-Transfer-Encoding: 8bit
+
+Thanks to Michał Kępień for spotting this.
+---
+ src/dnssec.c | 34 +++++++++++++++++-----------------
+ 1 file changed, 17 insertions(+), 17 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index ddae497..1f8c954 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -923,11 +923,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_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
++ 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_KEY 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)
+ {
+@@ -1224,13 +1224,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
+ }
+
+ p = psave;
+-
+- if (!ADD_RDLEN(header, p, plen, rdlen))
+- return STAT_BOGUS; /* bad packet */
+ }
+-
+- cache_end_insert();
++ if (!ADD_RDLEN(header, p, plen, rdlen))
++ return STAT_BOGUS; /* bad packet */
+ }
++
++ cache_end_insert();
++
+ }
+ else
+ {
+@@ -1828,10 +1828,10 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
+
+ /* 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_NEED_DNSKEY require DNSKEY 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_KEY require DNSKEY record of name returned in keyname.
+ name returned unaltered.
+ */
+ static int zone_status(char *name, int class, char *keyname, time_t now)
+@@ -2028,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ if (rc == STAT_SECURE)
+ rc = STAT_BOGUS;
+ if (class)
+- *class = class1; /* Class for NEED_DS or NEED_DNSKEY */
++ *class = class1; /* Class for NEED_DS or NEED_KEY */
+ }
+ else
+ rc = STAT_INSECURE;
+@@ -2045,7 +2045,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ {
+ /* Zone is insecure, don't need to validate RRset */
+ if (class)
+- *class = class1; /* Class for NEED_DS or NEED_DNSKEY */
++ *class = class1; /* Class for NEED_DS or NEED_KEY */
+ return rc;
+ }
+
+@@ -2115,7 +2115,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
+ if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
+ {
+ if (class)
+- *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
++ *class = qclass; /* Class for NEED_DS or NEED_KEY */
+ return rc;
+ }
+
+--
+1.7.10.4
+
diff --git a/src/patches/dnsmasq/024-Do_a_better_job_of_determining_which_DNSSEC_sig_algos_are_supported.patch b/src/patches/dnsmasq/024-Do_a_better_job_of_determining_which_DNSSEC_sig_algos_are_supported.patch
new file mode 100644
index 0000000..abcae5c
--- /dev/null
+++ b/src/patches/dnsmasq/024-Do_a_better_job_of_determining_which_DNSSEC_sig_algos_are_supported.patch
@@ -0,0 +1,145 @@
+From 14a4ae883d51130d33da7133287e8867c64bab65 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon(a)thekelleys.org.uk>
+Date: Thu, 17 Dec 2015 17:23:03 +0000
+Subject: [PATCH] Do a better job of determining which DNSSEC sig algos are
+ supported.
+
+---
+ src/dnssec.c | 52 +++++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 37 insertions(+), 15 deletions(-)
+
+diff --git a/src/dnssec.c b/src/dnssec.c
+index 1f8c954..82394ee 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -65,10 +65,9 @@ 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;
+ }
+ }
+@@ -129,13 +128,15 @@ static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char
+ }
+
+ static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+- unsigned char *digest, int algo)
++ unsigned char *digest, size_t digest_len, int algo)
+ {
+ unsigned char *p;
+ size_t exp_len;
+
+ static struct rsa_public_key *key = NULL;
+ static mpz_t sig_mpz;
++
++ (void)digest_len;
+
+ if (key == NULL)
+ {
+@@ -181,7 +182,7 @@ static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len,
+ }
+
+ static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+- unsigned char *digest, int algo)
++ unsigned char *digest, size_t digest_len, int algo)
+ {
+ unsigned char *p;
+ unsigned int t;
+@@ -189,6 +190,8 @@ static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len,
+ static struct dsa_public_key *key = NULL;
+ static struct dsa_signature *sig_struct;
+
++ (void)digest_len;
++
+ if (key == NULL)
+ {
+ if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||
+@@ -292,26 +295,45 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len
+ }
+ #endif
+
+-static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+- unsigned char *digest, size_t digest_len, int algo)
++static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
++ unsigned char *digest, size_t digest_len, int algo)
+ {
+- (void)digest_len;
+-
++
++ /* Enure at runtime that we have support for this digest */
++ if (!hash_find(algo_digest_name(algo)))
++ return NULL;
++
++ /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */
+ switch (algo)
+ {
+ case 1: case 5: case 7: case 8: case 10:
+- return dnsmasq_rsa_verify(key_data, key_len, sig, sig_len, digest, algo);
++ return dnsmasq_rsa_verify;
+
+ case 3: case 6:
+- return dnsmasq_dsa_verify(key_data, key_len, sig, sig_len, digest, algo);
++ return dnsmasq_dsa_verify;
+
+ #ifndef NO_NETTLE_ECC
+ case 13: case 14:
+- return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo);
++ return dnsmasq_ecdsa_verify;
+ #endif
+ }
+
+- return 0;
++ return NULL;
++}
++
++static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
++ unsigned char *digest, size_t digest_len, int algo)
++{
++
++ int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
++ unsigned char *digest, size_t digest_len, int algo);
++
++ func = verify_func(algo);
++
++ if (!func)
++ return 0;
++
++ return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo);
+ }
+
+ /* Convert from presentation format to wire format, in place.
+@@ -732,7 +754,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
+ if (check_date_range(sig_inception, sig_expiration) &&
+ labels <= name_labels &&
+ type_covered == type &&
+- algo_digest_name(algo))
++ verify_func(algo))
+ {
+ if (!expand_workspace(&sigs, &sig_sz, sigidx))
+ return 0;
+@@ -1865,7 +1887,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
+ 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))
++ else if (!hash_find(ds_digest_name(crecp->addr.ds.digest)) || !verify_func(crecp->addr.ds.algo))
+ return STAT_INSECURE; /* algo we can't use - insecure */
+ else
+ secure_ds = 1;
+@@ -1887,7 +1909,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
+
+ do
+ {
+- if (crecp->uid == (unsigned int)class && !algo_digest_name(crecp->addr.key.algo))
++ if (crecp->uid == (unsigned int)class && !verify_func(crecp->addr.key.algo))
+ return STAT_INSECURE;
+ }
+ while ((crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)));
+--
+1.7.10.4
+
--
2.6.4