This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "IPFire 2.x development tree".
The branch, next has been updated via 1e1b03d5819269184a85dc5bcc042c978666bc08 (commit) via fbcc3cb7841f10c1390550074d676ddf2afa2c1a (commit) from 78af2f67bba5900eb97989ed271b45a74448b457 (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 1e1b03d5819269184a85dc5bcc042c978666bc08 Author: Matthias Fischer matthias.fischer@ipfire.org Date: Fri Dec 18 15:11:25 2015 +0100
dnsmasq 2.75: latest upstream patches ;-)
The neverending story continues...
Signed-off-by: Matthias Fischer matthias.fischer@ipfire.org Signed-off-by: Michael Tremer michael.tremer@ipfire.org
commit fbcc3cb7841f10c1390550074d676ddf2afa2c1a Author: Matthias Fischer matthias.fischer@ipfire.org Date: Wed Dec 16 21:42:41 2015 +0100
dnsmasq 2.75: latest upstream patches
Since 'Makefile' was affected, I had to rewrite 'dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch', too.
Signed-off-by: Matthias Fischer matthias.fischer@ipfire.org Signed-off-by: Michael Tremer michael.tremer@ipfire.org
-----------------------------------------------------------------------
Summary of changes: lfs/dnsmasq | 9 + ...q-Add-support-to-read-ISC-DHCP-lease-file.patch | 83 +- ...ajor_rationalisation_of_DNSSEC_validation.patch | 2209 ++++++++++++++++++++ ...hing_RRSIGs_and_returning_them_from_cache.patch | 612 ++++++ ...caches_DS_records_to_a_more_logical_place.patch | 269 +++ ...lise_RR-filtering_code_for_use_with_EDNS0.patch | 755 +++++++ .../dnsmasq/020-DNSSEC_validation_tweak.patch | 134 ++ ...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 ++ 11 files changed, 4806 insertions(+), 50 deletions(-) create mode 100644 src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch create mode 100644 src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch create mode 100644 src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch create mode 100644 src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch create mode 100644 src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch 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
Difference in files: diff --git a/lfs/dnsmasq b/lfs/dnsmasq index d166392..c8fd7db 100644 --- a/lfs/dnsmasq +++ b/lfs/dnsmasq @@ -88,6 +88,15 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/013-Fix_crash_when_empty_address_from_DNS_overlays_A_record_from.patch cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/014-Handle_unknown_DS_hash_algos_correctly.patch cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/015-Fix_crash_at_start_up_with_conf-dir.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/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-Add-support-to-read-ISC-DHCP-lease-file.patch b/src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch index 0482c4b..f55ebe8 100644 --- a/src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch +++ b/src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch @@ -1,21 +1,5 @@ -diff --git a/Makefile b/Makefile -index 4c87ea9..4e0ea10 100644 ---- a/Makefile -+++ b/Makefile -@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ - dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ - helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ - dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ -- domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o -+ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o \ -+ isc.o - - hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ - dns-protocol.h radv-protocol.h ip6addr.h -diff --git a/src/cache.c b/src/cache.c -index 178d654..6d0b131 100644 ---- a/src/cache.c -+++ b/src/cache.c +--- a/src/cache.c Wed Dec 16 19:24:12 2015 ++++ b/src/cache.c Wed Dec 16 19:37:37 2015 @@ -17,7 +17,7 @@ #include "dnsmasq.h"
@@ -25,7 +9,7 @@ index 178d654..6d0b131 100644 static struct crec *dhcp_spare = NULL; #endif static struct crec *new_chain = NULL; -@@ -222,6 +222,9 @@ static void cache_free(struct crec *crecp) +@@ -217,6 +217,9 @@ crecp->flags &= ~F_BIGNAME; }
@@ -35,7 +19,7 @@ index 178d654..6d0b131 100644 #ifdef HAVE_DNSSEC cache_blockdata_free(crecp); #endif -@@ -1151,7 +1154,7 @@ void cache_reload(void) +@@ -1131,7 +1134,7 @@
}
@@ -44,16 +28,7 @@ index 178d654..6d0b131 100644 struct in_addr a_record_from_hosts(char *name, time_t now) { struct crec *crecp = NULL; -@@ -1229,7 +1232,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, - addrlen = sizeof(struct in6_addr); - } - #endif -- -+ - inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN); - - while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME))) -@@ -1294,7 +1297,11 @@ void cache_add_dhcp_entry(char *host_name, int prot, +@@ -1274,7 +1277,11 @@ else crec->ttd = ttd; crec->addr.addr = *host_address; @@ -65,11 +40,9 @@ index 178d654..6d0b131 100644 crec->uid = next_uid(); cache_hash(crec);
-diff --git a/src/dnsmasq.c b/src/dnsmasq.c -index 81254f6..ce2d1a7 100644 ---- a/src/dnsmasq.c -+++ b/src/dnsmasq.c -@@ -982,6 +982,11 @@ int main (int argc, char **argv) +--- a/src/dnsmasq.c Thu Jul 30 20:59:06 2015 ++++ b/src/dnsmasq.c Wed Dec 16 19:38:32 2015 +@@ -982,6 +982,11 @@
poll_resolv(0, daemon->last_resolv != 0, now); daemon->last_resolv = now; @@ -81,11 +54,9 @@ index 81254f6..ce2d1a7 100644 } #endif
-diff --git a/src/dnsmasq.h b/src/dnsmasq.h -index cf1a782..30437aa 100644 ---- a/src/dnsmasq.h -+++ b/src/dnsmasq.h -@@ -1519,3 +1519,7 @@ int poll_check(int fd, short event); +--- a/src/dnsmasq.h Wed Dec 16 19:24:12 2015 ++++ b/src/dnsmasq.h Wed Dec 16 19:40:11 2015 +@@ -1513,8 +1513,12 @@ void poll_listen(int fd, short event); int do_poll(int timeout);
@@ -93,11 +64,14 @@ index cf1a782..30437aa 100644 +#ifdef HAVE_ISC_READER +void load_dhcp(time_t now); +#endif -diff --git a/src/isc.c b/src/isc.c -new file mode 100644 -index 0000000..5106442 ---- /dev/null -+++ b/src/isc.c ++ + /* rrfilter.c */ + size_t rrfilter(struct dns_header *header, size_t plen, int mode); + u16 *rrfilter_desc(int type); + int expand_workspace(unsigned char ***wkspc, int *szp, int new); +- +--- /dev/null Wed Dec 16 19:48:08 2015 ++++ b/src/isc.c Wed Dec 16 19:41:35 2015 @@ -0,0 +1,251 @@ +/* dnsmasq is Copyright (c) 2014 John Volpe, Simon Kelley and + Michael Tremer @@ -350,11 +324,9 @@ index 0000000..5106442 +} + +#endif -diff --git a/src/option.c b/src/option.c -index ecc2619..527c5aa 100644 ---- a/src/option.c -+++ b/src/option.c -@@ -1699,7 +1699,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma +--- a/src/option.c Wed Dec 16 19:24:12 2015 ++++ b/src/option.c Wed Dec 16 19:42:48 2015 +@@ -1754,7 +1754,7 @@ ret_err(_("bad MX target")); break;
@@ -363,3 +335,14 @@ index ecc2619..527c5aa 100644 case 'l': /* --dhcp-leasefile */ daemon->lease_file = opt_string_alloc(arg); break; +--- a/Makefile Wed Dec 16 19:24:12 2015 ++++ b/Makefile Wed Dec 16 19:28:45 2015 +@@ -74,7 +74,7 @@ + helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ + dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ + domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ +- poll.o rrfilter.o ++ poll.o rrfilter.o isc.o + + hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ + dns-protocol.h radv-protocol.h ip6addr.h diff --git a/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch b/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch new file mode 100644 index 0000000..7f25066 --- /dev/null +++ b/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch @@ -0,0 +1,2209 @@ +From 9a31b68b59adcac01016d4026d906b69c4216c01 Mon Sep 17 00:00:00 2001 +From: Simon Kelley simon@thekelleys.org.uk +Date: Tue, 15 Dec 2015 10:20:39 +0000 +Subject: [PATCH] Major rationalisation of DNSSEC validation. + +Much gnarly special-case code removed and replaced with correct +general implementaion. Checking of zone-status moved to DNSSEC code, +where it should be, vastly simplifying query-forwarding code. +--- + src/dnsmasq.h | 19 +- + src/dnssec.c | 926 ++++++++++++++++++++++++++++++--------------------------- + src/forward.c | 741 ++++++++++----------------------------------- + 3 files changed, 653 insertions(+), 1033 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index f42acdb..023a1cf 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -586,12 +586,8 @@ struct hostsfile { + #define STAT_NEED_KEY 5 + #define STAT_TRUNCATED 6 + #define STAT_SECURE_WILDCARD 7 +-#define STAT_NO_SIG 8 +-#define STAT_NO_DS 9 +-#define STAT_NO_NS 10 +-#define STAT_NEED_DS_NEG 11 +-#define STAT_CHASE_CNAME 12 +-#define STAT_INSECURE_DS 13 ++#define STAT_OK 8 ++#define STAT_ABANDONED 9 + + #define FREC_NOREBIND 1 + #define FREC_CHECKING_DISABLED 2 +@@ -601,8 +597,7 @@ struct hostsfile { + #define FREC_AD_QUESTION 32 + #define FREC_DO_QUESTION 64 + #define FREC_ADDED_PHEADER 128 +-#define FREC_CHECK_NOSIGN 256 +-#define FREC_TEST_PKTSZ 512 ++#define FREC_TEST_PKTSZ 256 + + #ifdef HAVE_DNSSEC + #define HASH_SIZE 20 /* SHA-1 digest size */ +@@ -626,9 +621,7 @@ struct frec { + #ifdef HAVE_DNSSEC + int class, work_counter; + struct blockdata *stash; /* Saved reply, whilst we validate */ +- struct blockdata *orig_domain; /* domain of original query, whilst +- we're seeing is if in unsigned domain */ +- size_t stash_len, name_start, name_len; ++ size_t stash_len; + struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ + struct frec *blocking_query; /* Query which is blocking us. */ + #endif +@@ -1162,8 +1155,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); + size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); + int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); + int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +-int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons); +-int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname); ++int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, ++ int check_unsigned, int *neganswer, int *nons); + int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); + size_t filter_rrsigs(struct dns_header *header, size_t plen); + unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); +diff --git a/src/dnssec.c b/src/dnssec.c +index b4dc14e..de7b335 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -65,8 +65,10 @@ static char *algo_digest_name(int algo) + case 8: return "sha256"; + case 10: return "sha512"; + case 12: return "gosthash94"; ++#ifndef NO_NETTLE_ECC + case 13: return "sha256"; + case 14: return "sha384"; ++#endif + default: return NULL; + } + } +@@ -592,30 +594,30 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, + } + } + +-static int expand_workspace(unsigned char ***wkspc, int *sz, int new) ++static int expand_workspace(unsigned char ***wkspc, int *szp, int new) + { + unsigned char **p; +- int new_sz = *sz; +- +- if (new_sz > new) ++ int old = *szp; ++ ++ if (old >= new+1) + return 1; + + if (new >= 100) + return 0; + +- new_sz += 5; ++ new += 5; + +- if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **)))) ++ if (!(p = whine_malloc(new * sizeof(unsigned char **)))) + return 0; + +- if (*wkspc) ++ if (old != 0 && *wkspc) + { +- memcpy(p, *wkspc, *sz * sizeof(unsigned char **)); ++ memcpy(p, *wkspc, old * sizeof(unsigned char **)); + free(*wkspc); + } + + *wkspc = p; +- *sz = new_sz; ++ *szp = new; + + return 1; + } +@@ -706,47 +708,28 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int + } while (swap); + } + +-/* Validate a single RRset (class, type, name) in the supplied DNS reply +- Return code: +- STAT_SECURE if it validates. +- STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. +- (In this case *wildcard_out points to the "body" of the wildcard within name.) +- STAT_NO_SIG no RRsigs found. +- STAT_INSECURE RRset empty. +- STAT_BOGUS signature is wrong, bad packet. +- STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) +- +- if key is non-NULL, use that key, which has the algo and tag given in the params of those names, +- otherwise find the key in the cache. ++static unsigned char **rrset = NULL, **sigs = NULL; + +- name is unchanged on exit. keyname is used as workspace and trashed. +-*/ +-static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, +- char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) ++/* Get pointers to RRset menbers and signature(s) for same. ++ Check signatures, and return keyname associated in keyname. */ ++static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, ++ char *name, char *keyname, int *sigcnt, int *rrcnt) + { +- static unsigned char **rrset = NULL, **sigs = NULL; +- static int rrset_sz = 0, sig_sz = 0; +- ++ static int rrset_sz = 0, sig_sz = 0; + unsigned char *p; +- int rrsetidx, sigidx, res, rdlen, j, name_labels; +- struct crec *crecp = NULL; +- int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; +- u16 *rr_desc = get_desc(type); +- +- if (wildcard_out) +- *wildcard_out = NULL; +- ++ int rrsetidx, sigidx, j, rdlen, res; ++ int name_labels = count_labels(name); /* For 4035 5.3.2 check */ ++ int gotkey = 0; ++ + if (!(p = skip_questions(header, plen))) + return STAT_BOGUS; +- +- name_labels = count_labels(name); /* For 4035 5.3.2 check */ + +- /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ ++ /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart, *pdata; +- int stype, sclass; ++ int stype, sclass, algo, type_covered, labels, sig_expiration, sig_inception; + + pstart = p; + +@@ -762,14 +745,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) +- return STAT_BOGUS; ++ return 0; + + if (res == 1 && sclass == class) + { + if (stype == type) + { + if (!expand_workspace(&rrset, &rrset_sz, rrsetidx)) +- return STAT_BOGUS; ++ return 0; + + rrset[rrsetidx++] = pstart; + } +@@ -777,14 +760,54 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + if (stype == T_RRSIG) + { + if (rdlen < 18) +- return STAT_BOGUS; /* bad packet */ ++ return 0; /* bad packet */ + + GETSHORT(type_covered, p); ++ algo = *p++; ++ labels = *p++; ++ p += 4; /* orig_ttl */ ++ GETLONG(sig_expiration, p); ++ GETLONG(sig_inception, p); ++ p += 2; /* key_tag */ + +- if (type_covered == type) ++ if (gotkey) ++ { ++ /* If there's more than one SIG, ensure they all have same keyname */ ++ if (extract_name(header, plen, &p, keyname, 0, 0) != 1) ++ return 0; ++ } ++ else ++ { ++ gotkey = 1; ++ ++ if (!extract_name(header, plen, &p, keyname, 1, 0)) ++ return 0; ++ ++ /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal ++ the name of the zone containing the RRset. We can't tell that ++ for certain, but we can check that the RRset name is equal to ++ or encloses the signers name, which should be enough to stop ++ an attacker using signatures made with the key of an unrelated ++ zone he controls. Note that the root key is always allowed. */ ++ if (*keyname != 0) ++ { ++ char *name_start; ++ for (name_start = name; !hostname_isequal(name_start, keyname); ) ++ if ((name_start = strchr(name_start, '.'))) ++ name_start++; /* chop a label off and try again */ ++ else ++ return 0; ++ } ++ } ++ ++ /* Don't count signatures for algos we don't support */ ++ if (check_date_range(sig_inception, sig_expiration) && ++ labels <= name_labels && ++ type_covered == type && ++ algo_digest_name(algo)) + { + if (!expand_workspace(&sigs, &sig_sz, sigidx)) +- return STAT_BOGUS; ++ return 0; + + sigs[sigidx++] = pdata; + } +@@ -794,17 +817,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) +- return STAT_BOGUS; ++ return 0; + } + +- /* RRset empty */ +- if (rrsetidx == 0) +- return STAT_INSECURE; ++ *sigcnt = sigidx; ++ *rrcnt = rrsetidx; ++ ++ return 1; ++} ++ ++/* Validate a single RRset (class, type, name) in the supplied DNS reply ++ Return code: ++ STAT_SECURE if it validates. ++ STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. ++ (In this case *wildcard_out points to the "body" of the wildcard within name.) ++ STAT_BOGUS signature is wrong, bad packet. ++ STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) ++ STAT_NEED_DS need DS to complete validation (name is returned in keyname) ++ ++ if key is non-NULL, use that key, which has the algo and tag given in the params of those names, ++ otherwise find the key in the cache. + +- /* no RRSIGs */ +- if (sigidx == 0) +- return STAT_NO_SIG; ++ name is unchanged on exit. keyname is used as workspace and trashed. ++ ++ Call explore_rrset first to find and count RRs and sigs. ++*/ ++static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, ++ char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) ++{ ++ unsigned char *p; ++ int rdlen, j, name_labels; ++ struct crec *crecp = NULL; ++ int algo, labels, orig_ttl, key_tag; ++ u16 *rr_desc = get_desc(type); ++ ++ if (wildcard_out) ++ *wildcard_out = NULL; + ++ name_labels = count_labels(name); /* For 4035 5.3.2 check */ ++ + /* Sort RRset records into canonical order. + Note that at this point keyname and daemon->workspacename buffs are + unused, and used as workspace by the sort. */ +@@ -828,44 +879,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + algo = *p++; + labels = *p++; + GETLONG(orig_ttl, p); +- GETLONG(sig_expiration, p); +- GETLONG(sig_inception, p); ++ p += 8; /* sig_expiration, sig_inception already checked */ + GETSHORT(key_tag, p); + + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return STAT_BOGUS; + +- /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal +- the name of the zone containing the RRset. We can't tell that +- for certain, but we can check that the RRset name is equal to +- or encloses the signers name, which should be enough to stop +- an attacker using signatures made with the key of an unrelated +- zone he controls. Note that the root key is always allowed. */ +- if (*keyname != 0) +- { +- int failed = 0; +- +- for (name_start = name; !hostname_isequal(name_start, keyname); ) +- if ((name_start = strchr(name_start, '.'))) +- name_start++; /* chop a label off and try again */ +- else +- { +- failed = 1; +- break; +- } +- +- /* Bad sig, try another */ +- if (failed) +- continue; +- } +- +- /* Other 5.3.1 checks */ +- if (!check_date_range(sig_inception, sig_expiration) || +- labels > name_labels || +- !(hash = hash_find(algo_digest_name(algo))) || ++ if (!(hash = hash_find(algo_digest_name(algo))) || + !hash_init(hash, &ctx, &digest)) + continue; +- ++ + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ + if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) + return STAT_NEED_KEY; +@@ -971,10 +994,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + /* The DNS packet is expected to contain the answer to a DNSKEY query. + Put all DNSKEYs in the answer which are valid into the cache. + return codes: +- STAT_SECURE At least one valid DNSKEY found and in cache. +- STAT_BOGUS No DNSKEYs found, which can be validated with DS, +- or self-sign for DNSKEY RRset is not valid, bad packet. +- STAT_NEED_DS DS records to validate a key not found, name in keyname ++ STAT_OK Done, key(s) in cache. ++ STAT_BOGUS No DNSKEYs found, which can be validated with DS, ++ or self-sign for DNSKEY RRset is not valid, bad packet. ++ STAT_NEED_DS DS records to validate a key not found, name in keyname ++ STAT_NEED_DNSKEY DNSKEY records to validate a key not found, name in keyname + */ + int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) + { +@@ -1001,23 +1025,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + return STAT_NEED_DS; + } + +- /* If we've cached that DS provably doesn't exist, result must be INSECURE */ +- if (crecp->flags & F_NEG) +- return STAT_INSECURE_DS; +- +- /* 4035 5.2 +- If the validator does not support any of the algorithms listed in an +- authenticated DS RRset, then the resolver has no supported +- authentication path leading from the parent to the child. The +- resolver should treat this case as it would the case of an +- authenticated NSEC RRset proving that no DS RRset exists, */ +- for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) +- if (hash_find(ds_digest_name(recp1->addr.ds.digest))) +- break; +- +- if (!recp1) +- return STAT_INSECURE_DS; +- + /* NOTE, we need to find ONE DNSKEY which matches the DS */ + for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) + { +@@ -1070,7 +1077,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + void *ctx; + unsigned char *digest, *ds_digest; + const struct nettle_hash *hash; +- ++ int sigcnt, rrcnt; ++ + if (recp1->addr.ds.algo == algo && + recp1->addr.ds.keytag == keytag && + recp1->uid == (unsigned int)class && +@@ -1088,10 +1096,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + + from_wire(name); + +- if (recp1->addr.ds.keylen == (int)hash->digest_size && ++ if (!(recp1->flags & F_NEG) && ++ recp1->addr.ds.keylen == (int)hash->digest_size && + (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && + memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && +- validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) ++ explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && ++ sigcnt != 0 && rrcnt != 0 && ++ validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, ++ NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) + { + valid = 1; + break; +@@ -1112,7 +1124,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + { + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) +- return STAT_INSECURE; /* bad packet */ ++ return STAT_BOGUS; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); +@@ -1198,7 +1210,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + + /* commit cache insert. */ + cache_end_insert(); +- return STAT_SECURE; ++ return STAT_OK; + } + + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); +@@ -1207,12 +1219,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + + /* The DNS packet is expected to contain the answer to a DS query + Put all DSs in the answer which are valid into the cache. ++ Also handles replies which prove that there's no DS at this location, ++ either because the zone is unsigned or this isn't a zone cut. These are ++ cached too. + return codes: +- STAT_SECURE At least one valid DS found and in cache. +- STAT_NO_DS It's proved there's no DS here. +- STAT_NO_NS It's proved there's no DS _or_ NS here. ++ STAT_OK At least one valid DS found and in cache. + STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. + STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname ++ STAT_NEED_DS DS record needed. + */ + + int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +@@ -1230,7 +1244,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + if (qtype != T_DS || qclass != class) + val = STAT_BOGUS; + else +- val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons); ++ val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); + /* Note dnssec_validate_reply() will have cached positive answers */ + + if (val == STAT_INSECURE) +@@ -1242,22 +1256,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + + if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) + val = STAT_BOGUS; +- +- /* If we return STAT_NO_SIG, name contains the name of the DS query */ +- if (val == STAT_NO_SIG) +- return val; + + /* If the key needed to validate the DS is on the same domain as the DS, we'll + loop getting nowhere. Stop that now. This can happen of the DS answer comes + from the DS's zone, and not the parent zone. */ +- if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) ++ if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) + { + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); + return STAT_BOGUS; + } ++ ++ if (val != STAT_SECURE) ++ return val; + + /* By here, the answer is proved secure, and a positive answer has been cached. */ +- if (val == STAT_SECURE && neganswer) ++ if (neganswer) + { + int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; + unsigned long ttl, minttl = ULONG_MAX; +@@ -1317,15 +1330,14 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + + cache_end_insert(); + +- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS"); ++ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); + } +- +- return nons ? STAT_NO_NS : STAT_NO_DS; + } + +- return val; ++ return STAT_OK; + } + ++ + /* 4034 6.1 */ + static int hostname_cmp(const char *a, const char *b) + { +@@ -1452,7 +1464,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + int mask = 0x80 >> (type & 0x07); + + if (nons) +- *nons = 0; ++ *nons = 1; + + /* Find NSEC record that proves name doesn't exist */ + for (i = 0; i < nsec_count; i++) +@@ -1480,9 +1492,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi + /* rdlen is now length of type map, and p points to it */ + + /* If we can prove that there's no NS record, return that information. */ +- if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) +- *nons = 1; ++ if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) ++ *nons = 0; + ++ if (rdlen >= 2 && p[0] == 0) ++ { ++ /* A CNAME answer would also be valid, so if there's a CNAME is should ++ have been returned. */ ++ if ((p[2] & (0x80 >> T_CNAME)) != 0) ++ return STAT_BOGUS; ++ ++ /* If the SOA bit is set for a DS record, then we have the ++ DS from the wrong side of the delegation. */ ++ if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) ++ return STAT_BOGUS; ++ } ++ + while (rdlen >= 2) + { + if (!CHECK_LEN(header, p, plen, rdlen)) +@@ -1586,7 +1611,7 @@ static int base32_decode(char *in, unsigned char *out) + static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons) + { +- int i, hash_len, salt_len, base32_len, rdlen; ++ int i, hash_len, salt_len, base32_len, rdlen, flags; + unsigned char *p, *psave; + + for (i = 0; i < nsec_count; i++) +@@ -1599,7 +1624,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; +- p += 4; /* algo, flags, iterations */ ++ p++; /* algo */ ++ flags = *p++; /* flags */ ++ p += 2; /* iterations */ + salt_len = *p++; /* salt_len */ + p += salt_len; /* salt */ + hash_len = *p++; /* p now points to next hashed name */ +@@ -1626,16 +1653,29 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + return 0; + + /* If we can prove that there's no NS record, return that information. */ +- if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) +- *nons = 1; ++ if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) ++ *nons = 0; + ++ if (rdlen >= 2 && p[0] == 0) ++ { ++ /* A CNAME answer would also be valid, so if there's a CNAME is should ++ have been returned. */ ++ if ((p[2] & (0x80 >> T_CNAME)) != 0) ++ return 0; ++ ++ /* If the SOA bit is set for a DS record, then we have the ++ DS from the wrong side of the delegation. */ ++ if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) ++ return 0; ++ } ++ + while (rdlen >= 2) + { + if (p[0] == type >> 8) + { + /* Does the NSEC3 say our type exists? */ + if (offset < p[1] && (p[offset+2] & mask) != 0) +- return STAT_BOGUS; ++ return 0; + + break; /* finshed checking */ + } +@@ -1643,7 +1683,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + rdlen -= p[1]; + p += p[1]; + } +- ++ + return 1; + } + else if (rc < 0) +@@ -1651,16 +1691,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige + /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, + wrap around case, name-hash falls between NSEC3 name-hash and end */ + if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0) +- return 1; ++ { ++ if ((flags & 0x01) && nons) /* opt out */ ++ *nons = 0; ++ ++ return 1; ++ } + } + else + { + /* wrap around case, name falls between start and next domain name */ + if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0) +- return 1; ++ { ++ if ((flags & 0x01) && nons) /* opt out */ ++ *nons = 0; ++ ++ return 1; ++ } + } + } + } ++ + return 0; + } + +@@ -1673,7 +1724,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + char *closest_encloser, *next_closest, *wildcard; + + if (nons) +- *nons = 0; ++ *nons = 1; + + /* Look though the NSEC3 records to find the first one with + an algorithm we support (currently only algo == 1). +@@ -1813,16 +1864,81 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + + return STAT_SECURE; + } +- +-/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ +-/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */ ++ ++/* Check signing status of name. ++ returns: ++ STAT_SECURE zone is signed. ++ STAT_INSECURE zone proved unsigned. ++ STAT_NEED_DS require DS record of name returned in keyname. ++ ++ name returned unaltered. ++*/ ++static int zone_status(char *name, int class, char *keyname, time_t now) ++{ ++ int name_start = strlen(name); ++ struct crec *crecp; ++ char *p; ++ ++ while (1) ++ { ++ strcpy(keyname, &name[name_start]); ++ ++ if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS))) ++ return STAT_NEED_DS; ++ else ++ do ++ { ++ if (crecp->uid == (unsigned int)class) ++ { ++ /* F_DNSSECOK misused in DS cache records to non-existance of NS record. ++ F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here, ++ but that's because there's no NS record either, ie this isn't the start ++ of a zone. We only prove that the DNS tree below a node is unsigned when ++ we prove that we're at a zone cut AND there's no DS record. ++ */ ++ if (crecp->flags & F_NEG) ++ { ++ if (crecp->flags & F_DNSSECOK) ++ return STAT_INSECURE; /* proved no DS here */ ++ } ++ else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo)) ++ return STAT_INSECURE; /* algo we can't use - insecure */ ++ } ++ } ++ while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS))); ++ ++ if (name_start == 0) ++ break; ++ ++ for (p = &name[name_start-2]; (*p != '.') && (p != name); p--); ++ ++ if (p != name) ++ p++; ++ ++ name_start = p - name; ++ } ++ ++ return STAT_SECURE; ++} ++ ++/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) ++ Return code: ++ STAT_SECURE if it validates. ++ STAT_INSECURE at least one RRset not validated, because in unsigned zone. ++ STAT_BOGUS signature is wrong, bad packet, no validation where there should be. ++ STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) ++ STAT_NEED_DS need DS to complete validation (name is returned in keyname) ++*/ + int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, +- int *class, int *neganswer, int *nons) ++ int *class, int check_unsigned, int *neganswer, int *nons) + { +- unsigned char *ans_start, *qname, *p1, *p2, **nsecs; +- int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype; +- int i, j, rc, nsec_count, cname_count = CNAME_CHAIN; +- int nsec_type = 0, have_answer = 0; ++ static unsigned char **targets = NULL; ++ static int target_sz = 0; ++ ++ unsigned char *ans_start, *p1, *p2, **nsecs; ++ int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx; ++ int i, j, rc, nsec_count; ++ int nsec_type; + + if (neganswer) + *neganswer = 0; +@@ -1833,70 +1949,51 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) + return STAT_INSECURE; + +- qname = p1 = (unsigned char *)(header+1); ++ p1 = (unsigned char *)(header+1); + ++ /* Find all the targets we're looking for answers to. ++ The zeroth array element is for the query, subsequent ones ++ for CNAME targets, unless the query is for a CNAME. */ ++ ++ if (!expand_workspace(&targets, &target_sz, 0)) ++ return STAT_BOGUS; ++ ++ targets[0] = p1; ++ targetidx = 1; ++ + if (!extract_name(header, plen, &p1, name, 1, 4)) + return STAT_BOGUS; +- ++ + GETSHORT(qtype, p1); + GETSHORT(qclass, p1); + ans_start = p1; +- +- if (qtype == T_ANY) +- have_answer = 1; + +- /* Can't validate an RRISG query */ ++ /* Can't validate an RRSIG query */ + if (qtype == T_RRSIG) + return STAT_INSECURE; +- +- cname_loop: +- for (j = ntohs(header->ancount); j != 0; j--) +- { +- /* leave pointer to missing name in qname */ +- +- if (!(rc = extract_name(header, plen, &p1, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(type2, p1); +- GETSHORT(class2, p1); +- p1 += 4; /* TTL */ +- GETSHORT(rdlen2, p1); +- +- if (rc == 1 && qclass == class2) +- { +- /* Do we have an answer for the question? */ +- if (type2 == qtype) +- { +- have_answer = 1; +- break; +- } +- else if (type2 == T_CNAME) +- { +- qname = p1; +- +- /* looped CNAMES */ +- if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0)) +- return STAT_BOGUS; +- +- p1 = ans_start; +- goto cname_loop; +- } +- } +- +- if (!ADD_RDLEN(header, p1, plen, rdlen2)) +- return STAT_BOGUS; +- } +- +- if (neganswer && !have_answer) +- *neganswer = 1; + +- /* No data, therefore no sigs */ +- if (ntohs(header->ancount) + ntohs(header->nscount) == 0) +- { +- *keyname = 0; +- return STAT_NO_SIG; +- } +- ++ if (qtype != T_CNAME) ++ for (j = ntohs(header->ancount); j != 0; j--) ++ { ++ if (!(p1 = skip_name(p1, header, plen, 10))) ++ return STAT_BOGUS; /* bad packet */ ++ ++ GETSHORT(type2, p1); ++ p1 += 6; /* class, TTL */ ++ GETSHORT(rdlen2, p1); ++ ++ if (type2 == T_CNAME) ++ { ++ if (!expand_workspace(&targets, &target_sz, targetidx)) ++ return STAT_BOGUS; ++ ++ targets[targetidx++] = p1; /* pointer to target name */ ++ } ++ ++ if (!ADD_RDLEN(header, p1, plen, rdlen2)) ++ return STAT_BOGUS; ++ } ++ + for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) + { + if (!extract_name(header, plen, &p1, name, 1, 10)) +@@ -1931,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + /* Not done, validate now */ + if (j == i) + { +- int ttl, keytag, algo, digest, type_covered; ++ int ttl, keytag, algo, digest, type_covered, sigcnt, rrcnt; + unsigned char *psave; + struct all_addr a; + struct blockdata *key; +@@ -1939,143 +2036,186 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + char *wildname; + int have_wildcard = 0; + +- rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); +- +- if (rc == STAT_SECURE_WILDCARD) +- { +- have_wildcard = 1; +- +- /* An attacker replay a wildcard answer with a different +- answer and overlay a genuine RR. To prove this +- hasn't happened, the answer must prove that +- the gennuine record doesn't exist. Check that here. */ +- if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1))) +- return STAT_BOGUS; /* No NSECs or bad packet */ +- +- if (nsec_type == T_NSEC) +- rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL); +- else +- rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, +- keyname, name, type1, wildname, NULL); +- +- if (rc != STAT_SECURE) +- return rc; +- } +- else if (rc != STAT_SECURE) +- { +- if (class) +- *class = class1; /* Class for DS or DNSKEY */ ++ if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) ++ return STAT_BOGUS; + +- if (rc == STAT_NO_SIG) ++ /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */ ++ if (sigcnt == 0) ++ { ++ if (check_unsigned) + { +- /* If we dropped off the end of a CNAME chain, return +- STAT_NO_SIG and the last name is keyname. This is used for proving non-existence +- if DS records in CNAME chains. */ +- if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount)) +- /* No CNAME chain, or no sig in answer section, return empty name. */ +- *keyname = 0; +- else if (!extract_name(header, plen, &qname, keyname, 1, 0)) +- return STAT_BOGUS; ++ rc = zone_status(name, class1, keyname, now); ++ if (rc == STAT_SECURE) ++ rc = STAT_BOGUS; ++ if (class) ++ *class = class1; /* Class for NEED_DS or NEED_DNSKEY */ + } +- ++ else ++ rc = STAT_INSECURE; ++ + return rc; + } + +- /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ +- cache_start_insert(); ++ /* explore_rrset() gives us key name from sigs in keyname. ++ Can't overwrite name here. */ ++ strcpy(daemon->workspacename, keyname); ++ rc = zone_status(daemon->workspacename, class1, keyname, now); ++ if (rc != STAT_SECURE) ++ { ++ /* Zone is insecure, don't need to validate RRset */ ++ if (class) ++ *class = class1; /* Class for NEED_DS or NEED_DNSKEY */ ++ return rc; ++ } ++ ++ rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); + +- for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) ++ if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + { +- if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ ++ if (class) ++ *class = class1; /* Class for DS or DNSKEY */ ++ return rc; ++ } ++ else ++ { ++ /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ ++ ++ /* Note if we've validated either the answer to the question ++ or the target of a CNAME. Any not noted will need NSEC or ++ to be in unsigned space. */ ++ ++ for (j = 0; j <targetidx; j++) ++ if ((p2 = targets[j])) ++ { ++ if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) ++ return STAT_BOGUS; /* bad packet */ ++ ++ if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY )) ++ targets[j] = NULL; ++ } ++ ++ if (rc == STAT_SECURE_WILDCARD) ++ { ++ have_wildcard = 1; + +- GETSHORT(type2, p2); +- GETSHORT(class2, p2); +- GETLONG(ttl, p2); +- GETSHORT(rdlen2, p2); +- +- if (!CHECK_LEN(header, p2, plen, rdlen2)) +- return STAT_BOGUS; /* bad packet */ +- +- if (class2 == class1 && rc == 1) +- { +- psave = p2; ++ /* An attacker replay a wildcard answer with a different ++ answer and overlay a genuine RR. To prove this ++ hasn't happened, the answer must prove that ++ the gennuine record doesn't exist. Check that here. */ ++ if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1))) ++ return STAT_BOGUS; /* No NSECs or bad packet */ ++ ++ /* Note that we may not yet have validated the NSEC/NSEC3 RRsets. Since the check ++ below returns either SECURE or BOGUS, that's not a problem. If the RRsets later fail ++ we'll return BOGUS then. */ + +- if (type1 == T_DS && type2 == T_DS) +- { +- if (rdlen2 < 4) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(keytag, p2); +- algo = *p2++; +- digest = *p2++; +- +- /* Cache needs to known class for DNSSEC stuff */ +- a.addr.dnssec.class = class2; +- +- if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) +- { +- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) +- blockdata_free(key); +- else +- { +- a.addr.keytag = keytag; +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); +- crecp->addr.ds.digest = digest; +- crecp->addr.ds.keydata = key; +- crecp->addr.ds.algo = algo; +- crecp->addr.ds.keytag = keytag; +- crecp->addr.ds.keylen = rdlen2 - 4; +- } +- } +- } +- else if (type2 == T_RRSIG) +- { +- if (rdlen2 < 18) +- return STAT_BOGUS; /* bad packet */ ++ if (nsec_type == T_NSEC) ++ rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL); ++ else ++ rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, ++ keyname, name, type1, wildname, NULL); ++ ++ if (rc == STAT_BOGUS) ++ return rc; ++ } ++ ++ /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ ++ /* Also note if the RRset is the answer to the question, or the target of a CNAME */ ++ cache_start_insert(); ++ ++ for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) ++ { ++ if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) ++ return STAT_BOGUS; /* bad packet */ ++ ++ GETSHORT(type2, p2); ++ GETSHORT(class2, p2); ++ GETLONG(ttl, p2); ++ GETSHORT(rdlen2, p2); ++ ++ if (!CHECK_LEN(header, p2, plen, rdlen2)) ++ return STAT_BOGUS; /* bad packet */ ++ ++ if (class2 == class1 && rc == 1) ++ { ++ psave = p2; + +- GETSHORT(type_covered, p2); +- +- if (type_covered == type1 && +- (type_covered == T_A || type_covered == T_AAAA || +- type_covered == T_CNAME || type_covered == T_DS || +- type_covered == T_DNSKEY || type_covered == T_PTR)) ++ if (type1 == T_DS && type2 == T_DS) + { +- a.addr.dnssec.type = type_covered; +- a.addr.dnssec.class = class1; ++ if (rdlen2 < 4) ++ return STAT_BOGUS; /* bad packet */ + +- algo = *p2++; +- p2 += 13; /* labels, orig_ttl, expiration, inception */ + GETSHORT(keytag, p2); ++ algo = *p2++; ++ digest = *p2++; ++ ++ /* Cache needs to known class for DNSSEC stuff */ ++ a.addr.dnssec.class = class2; + +- /* We don't cache sigs for wildcard answers, because to reproduce the +- answer from the cache will require one or more NSEC/NSEC3 records +- which we don't cache. The lack of the RRSIG ensures that a query for +- this RRset asking for a secure answer will always be forwarded. */ +- if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2))) ++ if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) + { +- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) ++ if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + blockdata_free(key); + else + { +- crecp->addr.sig.keydata = key; +- crecp->addr.sig.keylen = rdlen2; +- crecp->addr.sig.keytag = keytag; +- crecp->addr.sig.type_covered = type_covered; +- crecp->addr.sig.algo = algo; ++ a.addr.keytag = keytag; ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); ++ crecp->addr.ds.digest = digest; ++ crecp->addr.ds.keydata = key; ++ crecp->addr.ds.algo = algo; ++ crecp->addr.ds.keytag = keytag; ++ crecp->addr.ds.keylen = rdlen2 - 4; ++ } ++ } ++ } ++ else if (type2 == T_RRSIG) ++ { ++ if (rdlen2 < 18) ++ return STAT_BOGUS; /* bad packet */ ++ ++ GETSHORT(type_covered, p2); ++ ++ if (type_covered == type1 && ++ (type_covered == T_A || type_covered == T_AAAA || ++ type_covered == T_CNAME || type_covered == T_DS || ++ type_covered == T_DNSKEY || type_covered == T_PTR)) ++ { ++ a.addr.dnssec.type = type_covered; ++ a.addr.dnssec.class = class1; ++ ++ algo = *p2++; ++ p2 += 13; /* labels, orig_ttl, expiration, inception */ ++ GETSHORT(keytag, p2); ++ ++ /* We don't cache sigs for wildcard answers, because to reproduce the ++ answer from the cache will require one or more NSEC/NSEC3 records ++ which we don't cache. The lack of the RRSIG ensures that a query for ++ this RRset asking for a secure answer will always be forwarded. */ ++ if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2))) ++ { ++ if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) ++ blockdata_free(key); ++ else ++ { ++ crecp->addr.sig.keydata = key; ++ crecp->addr.sig.keylen = rdlen2; ++ crecp->addr.sig.keytag = keytag; ++ crecp->addr.sig.type_covered = type_covered; ++ crecp->addr.sig.algo = algo; ++ } + } + } + } ++ ++ p2 = psave; + } + +- p2 = psave; ++ if (!ADD_RDLEN(header, p2, plen, rdlen2)) ++ return STAT_BOGUS; /* bad packet */ + } + +- if (!ADD_RDLEN(header, p2, plen, rdlen2)) +- return STAT_BOGUS; /* bad packet */ ++ cache_end_insert(); + } +- +- cache_end_insert(); + } + } + +@@ -2083,143 +2223,49 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + return STAT_BOGUS; + } + +- /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */ +- if (have_answer) +- return STAT_SECURE; +- +- /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */ +- /* First marshall the NSEC records, if we've not done it previously */ +- if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass))) +- { +- /* No NSEC records. If we dropped off the end of a CNAME chain, return +- STAT_NO_SIG and the last name is keyname. This is used for proving non-existence +- if DS records in CNAME chains. */ +- if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */ +- *keyname = 0; +- else if (!extract_name(header, plen, &qname, keyname, 1, 0)) +- return STAT_BOGUS; +- return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into +- an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */ +- } +- +- /* Get name of missing answer */ +- if (!extract_name(header, plen, &qname, name, 1, 0)) +- return STAT_BOGUS; +- +- if (nsec_type == T_NSEC) +- return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons); +- else +- return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons); +-} +- +-/* Chase the CNAME chain in the packet until the first record which _doesn't validate. +- Needed for proving answer in unsigned space. +- Return STAT_NEED_* +- STAT_BOGUS - error +- STAT_INSECURE - name of first non-secure record in name +-*/ +-int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname) +-{ +- unsigned char *p = (unsigned char *)(header+1); +- int type, class, qclass, rdlen, j, rc; +- int cname_count = CNAME_CHAIN; +- char *wildname; +- +- /* Get question */ +- if (!extract_name(header, plen, &p, name, 1, 4)) +- return STAT_BOGUS; +- +- p +=2; /* type */ +- GETSHORT(qclass, p); +- +- while (1) +- { +- for (j = ntohs(header->ancount); j != 0; j--) +- { +- if (!(rc = extract_name(header, plen, &p, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(type, p); +- GETSHORT(class, p); +- p += 4; /* TTL */ +- GETSHORT(rdlen, p); +- +- /* Not target, loop */ +- if (rc == 2 || qclass != class) +- { +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return STAT_BOGUS; +- continue; +- } +- +- /* Got to end of CNAME chain. */ +- if (type != T_CNAME) +- return STAT_INSECURE; +- +- /* validate CNAME chain, return if insecure or need more data */ +- rc = validate_rrset(now, header, plen, class, type, name, keyname, &wildname, NULL, 0, 0, 0); +- +- if (rc == STAT_SECURE_WILDCARD) +- { +- int nsec_type, nsec_count, i; +- unsigned char **nsecs; +- +- /* An attacker can replay a wildcard answer with a different +- answer and overlay a genuine RR. To prove this +- hasn't happened, the answer must prove that +- the genuine record doesn't exist. Check that here. */ +- if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class))) +- return STAT_BOGUS; /* No NSECs or bad packet */ +- +- /* Note that we're called here because something didn't validate in validate_reply, +- so we can't assume that any NSEC records have been validated. We do them by steam here */ +- +- for (i = 0; i < nsec_count; i++) +- { +- unsigned char *p1 = nsecs[i]; +- +- if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0)) +- return STAT_BOGUS; +- +- rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0); ++ /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ ++ for (j = 0; j <targetidx; j++) ++ if ((p2 = targets[j])) ++ { ++ if (neganswer) ++ *neganswer = 1; + +- /* NSECs can't be wildcards. */ +- if (rc == STAT_SECURE_WILDCARD) +- rc = STAT_BOGUS; ++ if (!extract_name(header, plen, &p2, name, 1, 10)) ++ return STAT_BOGUS; /* bad packet */ ++ ++ /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */ + +- if (rc != STAT_SECURE) ++ /* For anything other than a DS record, this situation is OK if either ++ the answer is in an unsigned zone, or there's a NSEC records. */ ++ if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass))) ++ { ++ /* Empty DS without NSECS */ ++ if (qtype == T_DS) ++ return STAT_BOGUS; ++ else ++ { ++ rc = zone_status(name, qclass, keyname, now); ++ if (rc != STAT_SECURE) ++ { ++ if (class) ++ *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */ + return rc; +- } +- +- if (nsec_type == T_NSEC) +- rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL); +- else +- rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, +- keyname, name, type, wildname, NULL); +- +- if (rc != STAT_SECURE) +- return rc; +- } +- +- if (rc != STAT_SECURE) +- { +- if (rc == STAT_NO_SIG) +- rc = STAT_INSECURE; +- return rc; +- } ++ } ++ ++ return STAT_BOGUS; /* signed zone, no NSECs */ ++ } ++ } + +- /* Loop down CNAME chain/ */ +- if (!cname_count-- || +- !extract_name(header, plen, &p, name, 1, 0) || +- !(p = skip_questions(header, plen))) +- return STAT_BOGUS; +- +- break; +- } ++ if (nsec_type == T_NSEC) ++ rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons); ++ else ++ rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons); + +- /* End of CNAME chain */ +- return STAT_INSECURE; +- } ++ if (rc != STAT_SECURE) ++ return rc; ++ } ++ ++ return STAT_SECURE; + } + + +diff --git a/src/forward.c b/src/forward.c +index b76a974..dd22a62 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id, + static unsigned short get_id(void); + static void free_frec(struct frec *f); + +-#ifdef HAVE_DNSSEC +-static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, +- int class, char *name, char *keyname, struct server *server, int *keycount); +-static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname); +-static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, +- char *name, char *keyname); +-#endif +- +- + /* Send a UDP packet with its source address set as "source" + unless nowild is true, when we just send it with the kernel default */ + int send_from(int fd, int nowild, char *packet, size_t len, +@@ -825,236 +816,142 @@ void reply_query(int fd, int family, time_t now) + #ifdef HAVE_DNSSEC + if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) + { +- int status; ++ int status = 0; + + /* We've had a reply already, which we're validating. Ignore this duplicate */ + if (forward->blocking_query) + return; +- +- if (header->hb3 & HB3_TC) +- { +- /* Truncated answer can't be validated. ++ ++ /* Truncated answer can't be validated. + If this is an answer to a DNSSEC-generated query, we still + need to get the client to retry over TCP, so return + an answer with the TC bit set, even if the actual answer fits. + */ +- status = STAT_TRUNCATED; +- } +- else if (forward->flags & FREC_DNSKEY_QUERY) +- status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- else if (forward->flags & FREC_DS_QUERY) +- { +- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- /* Provably no DS, everything below is insecure, even if signatures are offered */ +- if (status == STAT_NO_DS) +- /* We only cache sigs when we've validated a reply. +- Avoid caching a reply with sigs if there's a vaildated break in the +- DS chain, so we don't return replies from cache missing sigs. */ +- status = STAT_INSECURE_DS; +- else if (status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- { +- status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); +- if (status == STAT_INSECURE) +- status = STAT_INSECURE_DS; +- } +- else +- status = STAT_INSECURE_DS; +- } +- else if (status == STAT_NO_NS) +- status = STAT_BOGUS; +- } +- else if (forward->flags & FREC_CHECK_NOSIGN) +- { +- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- if (status != STAT_NEED_KEY) +- status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); +- } +- else ++ if (header->hb3 & HB3_TC) ++ status = STAT_TRUNCATED; ++ ++ while (1) + { +- status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); +- if (status == STAT_NO_SIG) ++ /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise ++ would invite infinite loops, since the answers to DNSKEY and DS queries ++ will not be cached, so they'll be repeated. */ ++ if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) + { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); ++ if (forward->flags & FREC_DNSKEY_QUERY) ++ status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); ++ else if (forward->flags & FREC_DS_QUERY) ++ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else +- status = STAT_INSECURE; ++ status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, ++ option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL); + } +- } +- /* Can't validate, as we're missing key data. Put this +- answer aside, whilst we get that. */ +- if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) +- { +- struct frec *new, *orig; +- +- /* Free any saved query */ +- if (forward->stash) +- blockdata_free(forward->stash); +- +- /* Now save reply pending receipt of key data */ +- if (!(forward->stash = blockdata_alloc((char *)header, n))) +- return; +- forward->stash_len = n; + +- anotherkey: +- /* Find the original query that started it all.... */ +- for (orig = forward; orig->dependent; orig = orig->dependent); +- +- if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) +- status = STAT_INSECURE; +- else ++ /* Can't validate, as we're missing key data. Put this ++ answer aside, whilst we get that. */ ++ if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { +- int fd; +- struct frec *next = new->next; +- *new = *forward; /* copy everything, then overwrite */ +- new->next = next; +- new->blocking_query = NULL; +- new->sentto = server; +- new->rfd4 = NULL; +- new->orig_domain = NULL; +-#ifdef HAVE_IPV6 +- new->rfd6 = NULL; +-#endif +- new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN); ++ struct frec *new, *orig; + +- new->dependent = forward; /* to find query awaiting new one. */ +- forward->blocking_query = new; /* for garbage cleaning */ +- /* validate routines leave name of required record in daemon->keyname */ +- if (status == STAT_NEED_KEY) +- { +- new->flags |= FREC_DNSKEY_QUERY; +- nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, +- daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); +- } +- else +- { +- if (status == STAT_NEED_DS_NEG) +- new->flags |= FREC_CHECK_NOSIGN; +- else +- new->flags |= FREC_DS_QUERY; +- nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, +- daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); +- } +- if ((hash = hash_questions(header, nn, daemon->namebuff))) +- memcpy(new->hash, hash, HASH_SIZE); +- new->new_id = get_id(); +- header->id = htons(new->new_id); +- /* Save query for retransmission */ +- if (!(new->stash = blockdata_alloc((char *)header, nn))) ++ /* Free any saved query */ ++ if (forward->stash) ++ blockdata_free(forward->stash); ++ ++ /* Now save reply pending receipt of key data */ ++ if (!(forward->stash = blockdata_alloc((char *)header, n))) + return; +- +- new->stash_len = nn; ++ forward->stash_len = n; + +- /* Don't resend this. */ +- daemon->srv_save = NULL; ++ /* Find the original query that started it all.... */ ++ for (orig = forward; orig->dependent; orig = orig->dependent); + +- if (server->sfd) +- fd = server->sfd->fd; ++ if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) ++ status = STAT_ABANDONED; + else + { +- fd = -1; ++ int fd; ++ struct frec *next = new->next; ++ *new = *forward; /* copy everything, then overwrite */ ++ new->next = next; ++ new->blocking_query = NULL; ++ new->sentto = server; ++ new->rfd4 = NULL; + #ifdef HAVE_IPV6 +- if (server->addr.sa.sa_family == AF_INET6) ++ new->rfd6 = NULL; ++#endif ++ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); ++ ++ new->dependent = forward; /* to find query awaiting new one. */ ++ forward->blocking_query = new; /* for garbage cleaning */ ++ /* validate routines leave name of required record in daemon->keyname */ ++ if (status == STAT_NEED_KEY) ++ { ++ new->flags |= FREC_DNSKEY_QUERY; ++ nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, ++ daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); ++ } ++ else + { +- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) +- fd = new->rfd6->fd; ++ new->flags |= FREC_DS_QUERY; ++ nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, ++ daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } ++ if ((hash = hash_questions(header, nn, daemon->namebuff))) ++ memcpy(new->hash, hash, HASH_SIZE); ++ new->new_id = get_id(); ++ header->id = htons(new->new_id); ++ /* Save query for retransmission */ ++ new->stash = blockdata_alloc((char *)header, nn); ++ new->stash_len = nn; ++ ++ /* Don't resend this. */ ++ daemon->srv_save = NULL; ++ ++ if (server->sfd) ++ fd = server->sfd->fd; + else ++ { ++ fd = -1; ++#ifdef HAVE_IPV6 ++ if (server->addr.sa.sa_family == AF_INET6) ++ { ++ if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) ++ fd = new->rfd6->fd; ++ } ++ else + #endif ++ { ++ if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) ++ fd = new->rfd4->fd; ++ } ++ } ++ ++ if (fd != -1) + { +- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) +- fd = new->rfd4->fd; ++ while (retry_send(sendto(fd, (char *)header, nn, 0, ++ &server->addr.sa, ++ sa_len(&server->addr)))); ++ server->queries++; + } +- } +- +- if (fd != -1) +- { +- while (retry_send(sendto(fd, (char *)header, nn, 0, +- &server->addr.sa, +- sa_len(&server->addr)))); +- server->queries++; +- } +- ++ } + return; + } +- } + +- /* Ok, we reached far enough up the chain-of-trust that we can validate something. +- Now wind back down, pulling back answers which wouldn't previously validate +- and validate them with the new data. Note that if an answer needs multiple +- keys to validate, we may find another key is needed, in which case we set off +- down another branch of the tree. Once we get to the original answer +- (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ +- while (forward->dependent) +- { ++ /* Validated original answer, all done. */ ++ if (!forward->dependent) ++ break; ++ ++ /* validated subsdiary query, (and cached result) ++ pop that and return to the previous query we were working on. */ + struct frec *prev = forward->dependent; + free_frec(forward); + forward = prev; + forward->blocking_query = NULL; /* already gone */ + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + n = forward->stash_len; +- +- if (status == STAT_SECURE) +- { +- if (forward->flags & FREC_DNSKEY_QUERY) +- status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- else if (forward->flags & FREC_DS_QUERY) +- { +- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- /* Provably no DS, everything below is insecure, even if signatures are offered */ +- if (status == STAT_NO_DS) +- /* We only cache sigs when we've validated a reply. +- Avoid caching a reply with sigs if there's a vaildated break in the +- DS chain, so we don't return replies from cache missing sigs. */ +- status = STAT_INSECURE_DS; +- else if (status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- { +- status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); +- if (status == STAT_INSECURE) +- status = STAT_INSECURE_DS; +- } +- else +- status = STAT_INSECURE_DS; +- } +- else if (status == STAT_NO_NS) +- status = STAT_BOGUS; +- } +- else if (forward->flags & FREC_CHECK_NOSIGN) +- { +- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); +- if (status != STAT_NEED_KEY) +- status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); +- } +- else +- { +- status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); +- if (status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); +- else +- status = STAT_INSECURE; +- } +- } +- +- if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) +- goto anotherkey; +- } + } ++ + + no_cache_dnssec = 0; +- +- if (status == STAT_INSECURE_DS) +- { +- /* We only cache sigs when we've validated a reply. +- Avoid caching a reply with sigs if there's a vaildated break in the +- DS chain, so we don't return replies from cache missing sigs. */ +- status = STAT_INSECURE; +- no_cache_dnssec = 1; +- } + + if (status == STAT_TRUNCATED) + header->hb3 |= HB3_TC; +@@ -1062,7 +959,7 @@ void reply_query(int fd, int family, time_t now) + { + char *result, *domain = "result"; + +- if (forward->work_counter == 0) ++ if (status == STAT_ABANDONED) + { + result = "ABANDONED"; + status = STAT_BOGUS; +@@ -1072,7 +969,7 @@ void reply_query(int fd, int family, time_t now) + + if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; +- ++ + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + } + +@@ -1415,315 +1312,49 @@ void receive_query(struct listener *listen, time_t now) + } + + #ifdef HAVE_DNSSEC +- +-/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS +- and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or +- STAT_NEED_DS_NEG and keyname if we need to do the query. */ +-static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, +- char *name, char *keyname) +-{ +- int status = dnssec_chase_cname(now, header, plen, name, keyname); +- +- if (status != STAT_INSECURE) +- return status; +- +- /* Store the domain we're trying to check. */ +- forward->name_start = strlen(name); +- forward->name_len = forward->name_start + 1; +- if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len))) +- return STAT_BOGUS; +- +- return do_check_sign(forward, 0, now, name, keyname); +-} +- +-/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */ +-static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname) +-{ +- /* get domain we're checking back from blockdata store, it's stored on the original query. */ +- while (forward->dependent && !forward->orig_domain) +- forward = forward->dependent; +- +- blockdata_retrieve(forward->orig_domain, forward->name_len, name); +- +- while (1) +- { +- char *p; +- +- if (status == 0) +- { +- struct crec *crecp; +- +- /* Haven't received answer, see if in cache */ +- if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS))) +- { +- /* put name of DS record we're missing into keyname */ +- strcpy(keyname, &name[forward->name_start]); +- /* and wait for reply to arrive */ +- return STAT_NEED_DS_NEG; +- } +- +- /* F_DNSSECOK misused in DS cache records to non-existance of NS record */ +- if (!(crecp->flags & F_NEG)) +- status = STAT_SECURE; +- else if (crecp->flags & F_DNSSECOK) +- status = STAT_NO_DS; +- else +- status = STAT_NO_NS; +- } +- +- /* Have entered non-signed part of DNS tree. */ +- if (status == STAT_NO_DS) +- return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE; +- +- if (status == STAT_BOGUS) +- return STAT_BOGUS; +- +- if (status == STAT_NO_SIG && *keyname != 0) +- { +- /* There is a validated CNAME chain that doesn't end in a DS record. Start +- the search again in that domain. */ +- blockdata_free(forward->orig_domain); +- forward->name_start = strlen(keyname); +- forward->name_len = forward->name_start + 1; +- if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len))) +- return STAT_BOGUS; +- +- strcpy(name, keyname); +- status = 0; /* force to cache when we iterate. */ +- continue; +- } +- +- /* There's a proven DS record, or we're within a zone, where there doesn't need +- to be a DS record. Add a name and try again. +- If we've already tried the whole name, then fail */ +- +- if (forward->name_start == 0) +- return STAT_BOGUS; +- +- for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--); +- +- if (p != name) +- p++; +- +- forward->name_start = p - name; +- status = 0; /* force to cache when we iterate. */ +- } +-} +- +-/* Move down from the root, until we find a signed non-existance of a DS, in which case +- an unsigned answer is OK, or we find a signed DS, in which case there should be +- a signature, and the answer is BOGUS */ +-static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, +- char *keyname, struct server *server, int *keycount) +-{ +- size_t m; +- unsigned char *packet, *payload; +- u16 *length; +- int status, name_len; +- struct blockdata *block; +- +- char *name_start; +- +- /* Get first insecure entry in CNAME chain */ +- status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount); +- if (status == STAT_BOGUS) +- return STAT_BOGUS; +- +- if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)))) +- return STAT_BOGUS; +- +- payload = &packet[2]; +- header = (struct dns_header *)payload; +- length = (u16 *)packet; +- +- /* Stash the name away, since the buffer will be trashed when we recurse */ +- name_len = strlen(name) + 1; +- name_start = name + name_len - 1; +- +- if (!(block = blockdata_alloc(name, name_len))) +- { +- free(packet); +- return STAT_BOGUS; +- } +- +- while (1) +- { +- unsigned char c1, c2; +- struct crec *crecp; +- +- if (--(*keycount) == 0) +- { +- free(packet); +- blockdata_free(block); +- return STAT_BOGUS; +- } +- +- while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS))) +- { +- if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)) +- { +- /* Found a secure denial of DS - delegation is indeed insecure */ +- free(packet); +- blockdata_free(block); +- return STAT_INSECURE; +- } +- +- /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. +- Add another label and continue. */ +- +- if (name_start == name) +- { +- free(packet); +- blockdata_free(block); +- return STAT_BOGUS; /* run out of labels */ +- } +- +- name_start -= 2; +- while (*name_start != '.' && name_start != name) +- name_start--; +- if (name_start != name) +- name_start++; +- } +- +- /* Can't find it in the cache, have to send a query */ +- +- m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz); +- +- *length = htons(m); +- +- if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) && +- read_write(server->tcpfd, &c1, 1, 1) && +- read_write(server->tcpfd, &c2, 1, 1) && +- read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) +- { +- m = (c1 << 8) | c2; +- +- /* Note this trashes all three name workspaces */ +- status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount); +- +- if (status == STAT_NO_DS) +- { +- /* Found a secure denial of DS - delegation is indeed insecure */ +- free(packet); +- blockdata_free(block); +- return STAT_INSECURE; +- } +- +- if (status == STAT_NO_SIG && *keyname != 0) +- { +- /* There is a validated CNAME chain that doesn't end in a DS record. Start +- the search again in that domain. */ +- blockdata_free(block); +- name_len = strlen(keyname) + 1; +- name_start = name + name_len - 1; +- +- if (!(block = blockdata_alloc(keyname, name_len))) +- return STAT_BOGUS; +- +- strcpy(name, keyname); +- continue; +- } +- +- if (status == STAT_BOGUS) +- { +- free(packet); +- blockdata_free(block); +- return STAT_BOGUS; +- } +- +- /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. +- Add another label and continue. */ +- +- /* Get name we're checking back. */ +- blockdata_retrieve(block, name_len, name); +- +- if (name_start == name) +- { +- free(packet); +- blockdata_free(block); +- return STAT_BOGUS; /* run out of labels */ +- } +- +- name_start -= 2; +- while (*name_start != '.' && name_start != name) +- name_start--; +- if (name_start != name) +- name_start++; +- } +- else +- { +- /* IO failure */ +- free(packet); +- blockdata_free(block); +- return STAT_BOGUS; /* run out of labels */ +- } +- } +-} +- + static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, int *keycount) + { + /* Recurse up the key heirarchy */ + int new_status; ++ unsigned char *packet = NULL; ++ size_t m; ++ unsigned char *payload = NULL; ++ struct dns_header *new_header = NULL; ++ u16 *length = NULL; ++ unsigned char c1, c2; + +- /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ +- if (--(*keycount) == 0) +- return STAT_INSECURE; +- +- if (status == STAT_NEED_KEY) +- new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); +- else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) ++ while (1) + { +- new_status = dnssec_validate_ds(now, header, n, name, keyname, class); +- if (status == STAT_NEED_DS) ++ /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ ++ if (--(*keycount) == 0) ++ new_status = STAT_ABANDONED; ++ else if (status == STAT_NEED_KEY) ++ new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); ++ else if (status == STAT_NEED_DS) ++ new_status = dnssec_validate_ds(now, header, n, name, keyname, class); ++ else ++ new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL); ++ ++ if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) ++ break; ++ ++ /* Can't validate because we need a key/DS whose name now in keyname. ++ Make query for same, and recurse to validate */ ++ if (!packet) + { +- if (new_status == STAT_NO_DS) +- new_status = STAT_INSECURE_DS; +- if (new_status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- { +- new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); +- if (new_status == STAT_INSECURE) +- new_status = STAT_INSECURE_DS; +- } +- else +- new_status = STAT_INSECURE_DS; +- } +- else if (new_status == STAT_NO_NS) +- new_status = STAT_BOGUS; ++ packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); ++ payload = &packet[2]; ++ new_header = (struct dns_header *)payload; ++ length = (u16 *)packet; + } +- } +- else if (status == STAT_CHASE_CNAME) +- new_status = dnssec_chase_cname(now, header, n, name, keyname); +- else +- { +- new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); + +- if (new_status == STAT_NO_SIG) ++ if (!packet) + { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); +- else +- new_status = STAT_INSECURE; ++ new_status = STAT_ABANDONED; ++ break; + } +- } +- +- /* Can't validate because we need a key/DS whose name now in keyname. +- Make query for same, and recurse to validate */ +- if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) +- { +- size_t m; +- unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); +- unsigned char *payload = &packet[2]; +- struct dns_header *new_header = (struct dns_header *)payload; +- u16 *length = (u16 *)packet; +- unsigned char c1, c2; +- +- if (!packet) +- return STAT_INSECURE; +- +- another_tcp_key: ++ + m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); + +@@ -1733,65 +1364,22 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si + !read_write(server->tcpfd, &c1, 1, 1) || + !read_write(server->tcpfd, &c2, 1, 1) || + !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) +- new_status = STAT_INSECURE; +- else + { +- m = (c1 << 8) | c2; +- +- new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount); +- +- if (new_status == STAT_SECURE) +- { +- /* Reached a validated record, now try again at this level. +- Note that we may get ANOTHER NEED_* if an answer needs more than one key. +- If so, go round again. */ +- +- if (status == STAT_NEED_KEY) +- new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); +- else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) +- { +- new_status = dnssec_validate_ds(now, header, n, name, keyname, class); +- if (status == STAT_NEED_DS) +- { +- if (new_status == STAT_NO_DS) +- new_status = STAT_INSECURE_DS; +- else if (new_status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- { +- new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); +- if (new_status == STAT_INSECURE) +- new_status = STAT_INSECURE_DS; +- } +- else +- new_status = STAT_INSECURE_DS; +- } +- else if (new_status == STAT_NO_NS) +- new_status = STAT_BOGUS; +- } +- } +- else if (status == STAT_CHASE_CNAME) +- new_status = dnssec_chase_cname(now, header, n, name, keyname); +- else +- { +- new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); +- +- if (new_status == STAT_NO_SIG) +- { +- if (option_bool(OPT_DNSSEC_NO_SIGN)) +- new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); +- else +- new_status = STAT_INSECURE; +- } +- } +- +- if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) +- goto another_tcp_key; +- } ++ new_status = STAT_ABANDONED; ++ break; + } ++ ++ m = (c1 << 8) | c2; + +- free(packet); ++ new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount); ++ ++ if (new_status != STAT_OK) ++ break; + } ++ ++ if (packet) ++ free(packet); ++ + return new_status; + } + #endif +@@ -2075,19 +1663,10 @@ unsigned char *tcp_request(int confd, time_t now, + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) + { + int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ +- int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); ++ int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); + char *result, *domain = "result"; +- +- if (status == STAT_INSECURE_DS) +- { +- /* We only cache sigs when we've validated a reply. +- Avoid caching a reply with sigs if there's a vaildated break in the +- DS chain, so we don't return replies from cache missing sigs. */ +- status = STAT_INSECURE; +- no_cache_dnssec = 1; +- } + +- if (keycount == 0) ++ if (status == STAT_ABANDONED) + { + result = "ABANDONED"; + status = STAT_BOGUS; +@@ -2179,7 +1758,6 @@ static struct frec *allocate_frec(time_t now) + f->dependent = NULL; + f->blocking_query = NULL; + f->stash = NULL; +- f->orig_domain = NULL; + #endif + daemon->frec_list = f; + } +@@ -2248,12 +1826,6 @@ static void free_frec(struct frec *f) + f->stash = NULL; + } + +- if (f->orig_domain) +- { +- blockdata_free(f->orig_domain); +- f->orig_domain = NULL; +- } +- + /* Anything we're waiting on is pointless now, too */ + if (f->blocking_query) + free_frec(f->blocking_query); +@@ -2281,14 +1853,23 @@ struct frec *get_new_frec(time_t now, int *wait, int force) + target = f; + else + { +- if (difftime(now, f->time) >= 4*TIMEOUT) +- { +- free_frec(f); +- target = f; +- } +- +- if (!oldest || difftime(f->time, oldest->time) <= 0) +- oldest = f; ++#ifdef HAVE_DNSSEC ++ /* Don't free DNSSEC sub-queries here, as we may end up with ++ dangling references to them. They'll go when their "real" query ++ is freed. */ ++ if (!f->dependent) ++#endif ++ { ++ if (difftime(now, f->time) >= 4*TIMEOUT) ++ { ++ free_frec(f); ++ target = f; ++ } ++ ++ ++ if (!oldest || difftime(f->time, oldest->time) <= 0) ++ oldest = f; ++ } + } + + if (target) +-- +1.7.10.4 + diff --git a/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch b/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch new file mode 100644 index 0000000..5ffaf97 --- /dev/null +++ b/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch @@ -0,0 +1,612 @@ +From 93be5b1e023b0c661e1ec2cd6d811a8ec9055c49 Mon Sep 17 00:00:00 2001 +From: Simon Kelley simon@thekelleys.org.uk +Date: Tue, 15 Dec 2015 12:04:40 +0000 +Subject: [PATCH] Abandon caching RRSIGs and returning them from cache. + +The list of exceptions to being able to locally answer +cached data for validated records when DNSSEC data is requested +was getting too long, so don't ever do that. This means +that the cache no longer has to hold RRSIGS and allows +us to lose lots of code. Note that cached validated +answers are still returned as long as do=0 +--- + src/cache.c | 38 ++--------- + src/dnsmasq.h | 10 +-- + src/dnssec.c | 94 ++++----------------------- + src/rfc1035.c | 197 ++++++--------------------------------------------------- + 4 files changed, 42 insertions(+), 297 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 1b76b67..51ba7cc 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -189,12 +189,7 @@ static void cache_hash(struct crec *crecp) + static void cache_blockdata_free(struct crec *crecp) + { + if (crecp->flags & F_DNSKEY) +- { +- if (crecp->flags & F_DS) +- blockdata_free(crecp->addr.sig.keydata); +- else +- blockdata_free(crecp->addr.key.keydata); +- } ++ blockdata_free(crecp->addr.key.keydata); + else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG)) + blockdata_free(crecp->addr.ds.keydata); + } +@@ -369,13 +364,8 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no + } + + #ifdef HAVE_DNSSEC +- /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also +- type-covered sensitive for RRSIG */ +- if ((flags & (F_DNSKEY | F_DS)) && +- (flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS)) && +- crecp->uid == addr->addr.dnssec.class && +- (!((flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) || +- crecp->addr.sig.type_covered == addr->addr.dnssec.type)) ++ /* Deletion has to be class-sensitive for DS and DNSKEY */ ++ if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == addr->addr.dnssec.class) + { + if (crecp->flags & F_CONFIG) + return crecp; +@@ -532,13 +522,9 @@ struct crec *cache_insert(char *name, struct all_addr *addr, + struct all_addr free_addr = new->addr.addr;; + + #ifdef HAVE_DNSSEC +- /* For DNSSEC records, addr holds class and type_covered for RRSIG */ ++ /* For DNSSEC records, addr holds class. */ + if (new->flags & (F_DS | F_DNSKEY)) +- { +- free_addr.addr.dnssec.class = new->uid; +- if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) +- free_addr.addr.dnssec.type = new->addr.sig.type_covered; +- } ++ free_addr.addr.dnssec.class = new->uid; + #endif + + free_avail = 1; /* Must be free space now. */ +@@ -653,9 +639,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi + if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) + { + if ((crecp->flags & F_FORWARD) && +-#ifdef HAVE_DNSSEC +- (((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && +-#endif + (crecp->flags & prot) && + hostname_isequal(cache_get_name(crecp), name)) + { +@@ -713,9 +696,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi + + if (ans && + (ans->flags & F_FORWARD) && +-#ifdef HAVE_DNSSEC +- (((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && +-#endif + (ans->flags & prot) && + hostname_isequal(cache_get_name(ans), name)) + return ans; +@@ -1472,11 +1452,7 @@ void dump_cache(time_t now) + #ifdef HAVE_DNSSEC + else if (cache->flags & F_DS) + { +- if (cache->flags & F_DNSKEY) +- /* RRSIG */ +- sprintf(a, "%5u %3u %s", cache->addr.sig.keytag, +- cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered)); +- else if (!(cache->flags & F_NEG)) ++ if (!(cache->flags & F_NEG)) + sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, + cache->addr.ds.algo, cache->addr.ds.digest); + } +@@ -1502,8 +1478,6 @@ void dump_cache(time_t now) + else if (cache->flags & F_CNAME) + t = "C"; + #ifdef HAVE_DNSSEC +- else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) +- t = "G"; /* DNSKEY and DS set -> RRISG */ + else if (cache->flags & F_DS) + t = "S"; + else if (cache->flags & F_DNSKEY) +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 023a1cf..4344cae 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -398,14 +398,9 @@ struct crec { + unsigned char algo; + unsigned char digest; + } ds; +- struct { +- struct blockdata *keydata; +- unsigned short keylen, type_covered, keytag; +- char algo; +- } sig; + } addr; + time_t ttd; /* time to die */ +- /* used as class if DNSKEY/DS/RRSIG, index to source for F_HOSTS */ ++ /* used as class if DNSKEY/DS, index to source for F_HOSTS */ + unsigned int uid; + unsigned short flags; + union { +@@ -445,8 +440,7 @@ struct crec { + #define F_SECSTAT (1u<<24) + #define F_NO_RR (1u<<25) + #define F_IPSET (1u<<26) +-#define F_NSIGMATCH (1u<<27) +-#define F_NOEXTRA (1u<<28) ++#define F_NOEXTRA (1u<<27) + + /* Values of uid in crecs with F_CONFIG bit set. */ + #define SRC_INTERFACE 0 +diff --git a/src/dnssec.c b/src/dnssec.c +index de7b335..1ae03a6 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -1004,7 +1004,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + { + unsigned char *psave, *p = (unsigned char *)(header+1); + struct crec *crecp, *recp1; +- int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered; ++ int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; + struct blockdata *key; + struct all_addr a; + +@@ -1115,7 +1115,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + + if (valid) + { +- /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */ ++ /* DNSKEY RRset determined to be OK, now cache it. */ + cache_start_insert(); + + p = skip_questions(header, plen); +@@ -1155,7 +1155,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + if ((key = blockdata_alloc((char*)p, rdlen - 4))) + { + if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) +- blockdata_free(key); ++ { ++ blockdata_free(key); ++ return STAT_BOGUS; ++ } + else + { + a.addr.keytag = keytag; +@@ -1169,38 +1172,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + } + } + } +- else if (qtype == T_RRSIG) +- { +- /* RRSIG, cache if covers DNSKEY RRset */ +- if (rdlen < 18) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(type_covered, p); +- +- if (type_covered == T_DNSKEY) +- { +- a.addr.dnssec.class = class; +- a.addr.dnssec.type = type_covered; +- +- algo = *p++; +- p += 13; /* labels, orig_ttl, expiration, inception */ +- GETSHORT(keytag, p); +- if ((key = blockdata_alloc((char*)psave, rdlen))) +- { +- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) +- blockdata_free(key); +- else +- { +- crecp->addr.sig.keydata = key; +- crecp->addr.sig.keylen = rdlen; +- crecp->addr.sig.keytag = keytag; +- crecp->addr.sig.type_covered = type_covered; +- crecp->addr.sig.algo = algo; +- } +- } +- } +- } +- ++ + p = psave; + } + +@@ -1326,7 +1298,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + cache_start_insert(); + + a.addr.dnssec.class = class; +- cache_insert(name, &a, now, ttl, flags); ++ if (!cache_insert(name, &a, now, ttl, flags)) ++ return STAT_BOGUS; + + cache_end_insert(); + +@@ -2028,14 +2001,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + /* Not done, validate now */ + if (j == i) + { +- int ttl, keytag, algo, digest, type_covered, sigcnt, rrcnt; ++ int ttl, keytag, algo, digest, sigcnt, rrcnt; + unsigned char *psave; + struct all_addr a; + struct blockdata *key; + struct crec *crecp; + char *wildname; +- int have_wildcard = 0; +- ++ + if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) + return STAT_BOGUS; + +@@ -2096,8 +2068,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + + if (rc == STAT_SECURE_WILDCARD) + { +- have_wildcard = 1; +- + /* An attacker replay a wildcard answer with a different + answer and overlay a genuine RR. To prove this + hasn't happened, the answer must prove that +@@ -2119,7 +2089,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + return rc; + } + +- /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ ++ /* If we just validated a DS RRset, cache it */ + /* Also note if the RRset is the answer to the question, or the target of a CNAME */ + cache_start_insert(); + +@@ -2168,45 +2138,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + } + } + } +- else if (type2 == T_RRSIG) +- { +- if (rdlen2 < 18) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(type_covered, p2); +- +- if (type_covered == type1 && +- (type_covered == T_A || type_covered == T_AAAA || +- type_covered == T_CNAME || type_covered == T_DS || +- type_covered == T_DNSKEY || type_covered == T_PTR)) +- { +- a.addr.dnssec.type = type_covered; +- a.addr.dnssec.class = class1; +- +- algo = *p2++; +- p2 += 13; /* labels, orig_ttl, expiration, inception */ +- GETSHORT(keytag, p2); +- +- /* We don't cache sigs for wildcard answers, because to reproduce the +- answer from the cache will require one or more NSEC/NSEC3 records +- which we don't cache. The lack of the RRSIG ensures that a query for +- this RRset asking for a secure answer will always be forwarded. */ +- if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2))) +- { +- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) +- blockdata_free(key); +- else +- { +- crecp->addr.sig.keydata = key; +- crecp->addr.sig.keylen = rdlen2; +- crecp->addr.sig.keytag = keytag; +- crecp->addr.sig.type_covered = type_covered; +- crecp->addr.sig.algo = algo; +- } +- } +- } +- } +- ++ + p2 = psave; + } + +diff --git a/src/rfc1035.c b/src/rfc1035.c +index 4eb1772..def8fa0 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1275,11 +1275,9 @@ int check_for_local_domain(char *name, time_t now) + struct naptr *naptr; + + /* Note: the call to cache_find_by_name is intended to find any record which matches +- ie A, AAAA, CNAME, DS. Because RRSIG records are marked by setting both F_DS and F_DNSKEY, +- cache_find_by name ordinarily only returns records with an exact match on those bits (ie +- for the call below, only DS records). The F_NSIGMATCH bit changes this behaviour */ ++ ie A, AAAA, CNAME. */ + +- if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) && ++ if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) && + (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + return 1; + +@@ -1566,9 +1564,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + GETSHORT(flags, pheader); + + if ((sec_reqd = flags & 0x8000)) +- *do_bit = 1;/* do bit */ ++ { ++ *do_bit = 1;/* do bit */ ++ *ad_reqd = 1; ++ } + +- *ad_reqd = 1; + dryrun = 1; + } + +@@ -1636,98 +1636,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + } + } + +-#ifdef HAVE_DNSSEC +- if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS)) +- { +- int gotone = 0; +- struct blockdata *keydata; +- +- /* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */ +- if (sec_reqd) +- { +- crecp = NULL; +- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) +- if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype) +- break; +- } +- +- if (!sec_reqd || crecp) +- { +- if (qtype == T_DS) +- { +- crecp = NULL; +- while ((crecp = cache_find_by_name(crecp, name, now, F_DS))) +- if (crecp->uid == qclass) +- { +- gotone = 1; +- if (!dryrun) +- { +- if (crecp->flags & F_NEG) +- { +- if (crecp->flags & F_NXDOMAIN) +- nxdomain = 1; +- log_query(F_UPSTREAM, name, NULL, "no DS"); +- } +- else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL))) +- { +- struct all_addr a; +- a.addr.keytag = crecp->addr.ds.keytag; +- log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u"); +- if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, +- crec_ttl(crecp, now), &nameoffset, +- T_DS, qclass, "sbbt", +- crecp->addr.ds.keytag, crecp->addr.ds.algo, +- crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata)) +- anscount++; +- +- } +- } +- } +- } +- else /* DNSKEY */ +- { +- crecp = NULL; +- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY))) +- if (crecp->uid == qclass) +- { +- gotone = 1; +- if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL))) +- { +- struct all_addr a; +- a.addr.keytag = crecp->addr.key.keytag; +- log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u"); +- if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, +- crec_ttl(crecp, now), &nameoffset, +- T_DNSKEY, qclass, "sbbt", +- crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata)) +- anscount++; +- } +- } +- } +- } +- +- /* Now do RRSIGs */ +- if (gotone) +- { +- ans = 1; +- auth = 0; +- if (!dryrun && sec_reqd) +- { +- crecp = NULL; +- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) +- if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype && +- (keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL))) +- { +- add_resource_record(header, limit, &trunc, nameoffset, &ansp, +- crec_ttl(crecp, now), &nameoffset, +- T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata); +- anscount++; +- } +- } +- } +- } +-#endif +- + if (qclass == C_IN) + { + struct txt_record *t; +@@ -1736,6 +1644,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name)) + { + ans = 1; ++ sec_data = 0; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); +@@ -1792,6 +1701,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + + if (intr) + { ++ sec_data = 0; + ans = 1; + if (!dryrun) + { +@@ -1805,6 +1715,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + else if (ptr) + { + ans = 1; ++ sec_data = 0; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>"); +@@ -1819,38 +1730,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + } + else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) + { +- if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) +- { +- if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) +- crecp = NULL; +-#ifdef HAVE_DNSSEC +- else if (crecp->flags & F_DNSSECOK) +- { +- int gotsig = 0; +- struct crec *rr_crec = NULL; +- +- while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) +- { +- if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN) +- { +- char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); +- gotsig = 1; +- +- if (!dryrun && +- add_resource_record(header, limit, &trunc, nameoffset, &ansp, +- rr_crec->ttd - now, &nameoffset, +- T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata)) +- anscount++; +- } +- } +- +- if (!gotsig) +- crecp = NULL; +- } +-#endif +- } +- +- if (crecp) ++ /* Don't use cache when DNSSEC data required. */ ++ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK)) + { + do + { +@@ -1860,19 +1741,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; +- ++ ++ ans = 1; ++ + if (crecp->flags & F_NEG) + { +- ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); + } +- else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) ++ else + { +- ans = 1; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + if (!dryrun) +@@ -1892,6 +1773,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + else if (is_rev_synth(is_arpa, &addr, name)) + { + ans = 1; ++ sec_data = 0; + if (!dryrun) + { + log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); +@@ -1908,6 +1790,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + { + /* if not in cache, enabled and private IPV4 address, return NXDOMAIN */ + ans = 1; ++ sec_data = 0; + nxdomain = 1; + if (!dryrun) + log_query(F_CONFIG | F_REVERSE | F_IPV4 | F_NEG | F_NXDOMAIN, +@@ -1955,6 +1838,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + if (i == 4) + { + ans = 1; ++ sec_data = 0; + if (!dryrun) + { + addr.addr.addr4.s_addr = htonl(a); +@@ -1993,6 +1877,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + continue; + #endif + ans = 1; ++ sec_data = 0; + if (!dryrun) + { + gotit = 1; +@@ -2032,48 +1917,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + crecp = save; + } + +- /* If the client asked for DNSSEC and we can't provide RRSIGs, either +- because we've not doing DNSSEC or the cached answer is signed by negative, +- don't answer from the cache, forward instead. */ +- if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) +- { +- if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) +- crecp = NULL; +-#ifdef HAVE_DNSSEC +- else if (crecp->flags & F_DNSSECOK) +- { +- /* We're returning validated data, need to return the RRSIG too. */ +- struct crec *rr_crec = NULL; +- int sigtype = type; +- /* The signature may have expired even though the data is still in cache, +- forward instead of answering from cache if so. */ +- int gotsig = 0; +- +- if (crecp->flags & F_CNAME) +- sigtype = T_CNAME; +- +- while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) +- { +- if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN) +- { +- char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); +- gotsig = 1; +- +- if (!dryrun && +- add_resource_record(header, limit, &trunc, nameoffset, &ansp, +- rr_crec->ttd - now, &nameoffset, +- T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata)) +- anscount++; +- } +- } +- +- if (!gotsig) +- crecp = NULL; +- } +-#endif +- } +- +- if (crecp) ++ /* If the client asked for DNSSEC don't use cached data. */ ++ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK)) + do + { + /* don't answer wildcard queries with data not from /etc/hosts +-- +1.7.10.4 + diff --git a/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch b/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch new file mode 100644 index 0000000..ff055f7 --- /dev/null +++ b/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch @@ -0,0 +1,269 @@ +From d64c81fff7faf4392b688223ef3a617c5c07e7dc Mon Sep 17 00:00:00 2001 +From: Simon Kelley simon@thekelleys.org.uk +Date: Tue, 15 Dec 2015 16:11:06 +0000 +Subject: [PATCH] Move code which caches DS records to a more logical place. + +--- + src/dnssec.c | 179 +++++++++++++++++++++++++++++----------------------------- + 1 file changed, 90 insertions(+), 89 deletions(-) + +diff --git a/src/dnssec.c b/src/dnssec.c +index 1ae03a6..359231f 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -1204,7 +1204,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch + int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) + { + unsigned char *p = (unsigned char *)(header+1); +- int qtype, qclass, val, i, neganswer, nons; ++ int qtype, qclass, rc, i, neganswer, nons; ++ int aclass, atype, rdlen; ++ unsigned long ttl; ++ struct all_addr a; + + if (ntohs(header->qdcount) != 1 || + !(p = skip_name(p, header, plen, 4))) +@@ -1214,40 +1217,100 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + GETSHORT(qclass, p); + + if (qtype != T_DS || qclass != class) +- val = STAT_BOGUS; ++ rc = STAT_BOGUS; + else +- val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); ++ rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); + /* Note dnssec_validate_reply() will have cached positive answers */ + +- if (val == STAT_INSECURE) +- val = STAT_BOGUS; +- ++ if (rc == STAT_INSECURE) ++ rc = STAT_BOGUS; ++ + p = (unsigned char *)(header+1); + extract_name(header, plen, &p, name, 1, 4); + p += 4; /* qtype, qclass */ + +- if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) +- val = STAT_BOGUS; +- + /* If the key needed to validate the DS is on the same domain as the DS, we'll + loop getting nowhere. Stop that now. This can happen of the DS answer comes + from the DS's zone, and not the parent zone. */ +- if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) ++ if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) + { + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); + return STAT_BOGUS; + } + +- if (val != STAT_SECURE) +- return val; +- +- /* By here, the answer is proved secure, and a positive answer has been cached. */ +- if (neganswer) ++ if (rc != STAT_SECURE) ++ return rc; ++ ++ if (!neganswer) + { +- int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; +- unsigned long ttl, minttl = ULONG_MAX; +- struct all_addr a; ++ cache_start_insert(); ++ ++ for (i = 0; i < ntohs(header->ancount); i++) ++ { ++ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) ++ return STAT_BOGUS; /* bad packet */ ++ ++ GETSHORT(atype, p); ++ GETSHORT(aclass, p); ++ GETLONG(ttl, p); ++ GETSHORT(rdlen, p); ++ ++ if (!CHECK_LEN(header, p, plen, rdlen)) ++ return STAT_BOGUS; /* bad packet */ ++ ++ if (aclass == class && atype == T_DS && rc == 1) ++ { ++ int algo, digest, keytag; ++ unsigned char *psave = p; ++ struct blockdata *key; ++ struct crec *crecp; + ++ if (rdlen < 4) ++ return STAT_BOGUS; /* bad packet */ ++ ++ GETSHORT(keytag, p); ++ algo = *p++; ++ digest = *p++; ++ ++ /* Cache needs to known class for DNSSEC stuff */ ++ a.addr.dnssec.class = class; ++ ++ if ((key = blockdata_alloc((char*)p, rdlen - 4))) ++ { ++ if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) ++ { ++ blockdata_free(key); ++ return STAT_BOGUS; ++ } ++ else ++ { ++ a.addr.keytag = keytag; ++ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); ++ crecp->addr.ds.digest = digest; ++ crecp->addr.ds.keydata = key; ++ crecp->addr.ds.algo = algo; ++ crecp->addr.ds.keytag = keytag; ++ crecp->addr.ds.keylen = rdlen - 4; ++ } ++ } ++ ++ p = psave; ++ ++ if (!ADD_RDLEN(header, p, plen, rdlen)) ++ return STAT_BOGUS; /* bad packet */ ++ } ++ ++ cache_end_insert(); ++ } ++ } ++ else ++ { ++ int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; ++ unsigned long minttl = ULONG_MAX; ++ ++ if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) ++ return STAT_BOGUS; ++ + if (RCODE(header) == NXDOMAIN) + flags |= F_NXDOMAIN; + +@@ -1261,20 +1324,20 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + if (!(p = skip_name(p, header, plen, 0))) + return STAT_BOGUS; + +- GETSHORT(qtype, p); +- GETSHORT(qclass, p); ++ GETSHORT(atype, p); ++ GETSHORT(aclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); +- ++ + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ +- +- if (qclass != class || qtype != T_SOA) ++ ++ if (aclass != class || atype != T_SOA) + { + p += rdlen; + continue; + } +- ++ + if (ttl < minttl) + minttl = ttl; + +@@ -1306,7 +1369,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); + } + } +- ++ + return STAT_OK; + } + +@@ -2001,11 +2064,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + /* Not done, validate now */ + if (j == i) + { +- int ttl, keytag, algo, digest, sigcnt, rrcnt; +- unsigned char *psave; +- struct all_addr a; +- struct blockdata *key; +- struct crec *crecp; ++ int sigcnt, rrcnt; + char *wildname; + + if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) +@@ -2032,6 +2091,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + Can't overwrite name here. */ + strcpy(daemon->workspacename, keyname); + rc = zone_status(daemon->workspacename, class1, keyname, now); ++ + if (rc != STAT_SECURE) + { + /* Zone is insecure, don't need to validate RRset */ +@@ -2088,65 +2148,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + if (rc == STAT_BOGUS) + return rc; + } +- +- /* If we just validated a DS RRset, cache it */ +- /* Also note if the RRset is the answer to the question, or the target of a CNAME */ +- cache_start_insert(); +- +- for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) +- { +- if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(type2, p2); +- GETSHORT(class2, p2); +- GETLONG(ttl, p2); +- GETSHORT(rdlen2, p2); +- +- if (!CHECK_LEN(header, p2, plen, rdlen2)) +- return STAT_BOGUS; /* bad packet */ +- +- if (class2 == class1 && rc == 1) +- { +- psave = p2; +- +- if (type1 == T_DS && type2 == T_DS) +- { +- if (rdlen2 < 4) +- return STAT_BOGUS; /* bad packet */ +- +- GETSHORT(keytag, p2); +- algo = *p2++; +- digest = *p2++; +- +- /* Cache needs to known class for DNSSEC stuff */ +- a.addr.dnssec.class = class2; +- +- if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) +- { +- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) +- blockdata_free(key); +- else +- { +- a.addr.keytag = keytag; +- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); +- crecp->addr.ds.digest = digest; +- crecp->addr.ds.keydata = key; +- crecp->addr.ds.algo = algo; +- crecp->addr.ds.keytag = keytag; +- crecp->addr.ds.keylen = rdlen2 - 4; +- } +- } +- } +- +- p2 = psave; +- } +- +- if (!ADD_RDLEN(header, p2, plen, rdlen2)) +- return STAT_BOGUS; /* bad packet */ +- } +- +- cache_end_insert(); + } + } + } +-- +1.7.10.4 + diff --git a/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch b/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch new file mode 100644 index 0000000..0a4942a --- /dev/null +++ b/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch @@ -0,0 +1,755 @@ +From c2bcd1e183bcc5fdd63811c045355fc57e36ecfd Mon Sep 17 00:00:00 2001 +From: Simon Kelley simon@thekelleys.org.uk +Date: Tue, 15 Dec 2015 17:25:21 +0000 +Subject: [PATCH] Generalise RR-filtering code, for use with EDNS0. + +--- + Makefile | 3 +- + bld/Android.mk | 2 +- + src/dnsmasq.h | 5 + + src/dnssec.c | 307 +------------------------------------------------- + src/forward.c | 2 +- + src/rrfilter.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 349 insertions(+), 309 deletions(-) + create mode 100644 src/rrfilter.c + +diff --git a/Makefile b/Makefile +index 4c87ea9..b664160 100644 +--- a/Makefile ++++ b/Makefile +@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ + dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ + helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ + dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ +- domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o ++ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ ++ poll.o rrfilter.o + + hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ + dns-protocol.h radv-protocol.h ip6addr.h +diff --git a/bld/Android.mk b/bld/Android.mk +index 5364ee7..67b9c4b 100644 +--- a/bld/Android.mk ++++ b/bld/Android.mk +@@ -10,7 +10,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ + dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ + radv.c slaac.c auth.c ipset.c domain.c \ + dnssec.c dnssec-openssl.c blockdata.c tables.c \ +- loop.c inotify.c poll.c ++ loop.c inotify.c poll.c rrfilter.c + + LOCAL_MODULE := dnsmasq + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 4344cae..39a930c 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -1513,3 +1513,8 @@ int poll_check(int fd, short event); + void poll_listen(int fd, short event); + int do_poll(int timeout); + ++/* rrfilter.c */ ++size_t rrfilter(struct dns_header *header, size_t plen, int mode); ++u16 *rrfilter_desc(int type); ++int expand_workspace(unsigned char ***wkspc, int *szp, int new); ++ +diff --git a/src/dnssec.c b/src/dnssec.c +index 359231f..fa3eb81 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -507,50 +507,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) + && serial_compare_32(curtime, date_end) == SERIAL_LT; + } + +-static u16 *get_desc(int type) +-{ +- /* List of RRtypes which include domains in the data. +- 0 -> domain +- integer -> no of plain bytes +- -1 -> end +- +- zero is not a valid RRtype, so the final entry is returned for +- anything which needs no mangling. +- */ +- +- static u16 rr_desc[] = +- { +- T_NS, 0, -1, +- T_MD, 0, -1, +- T_MF, 0, -1, +- T_CNAME, 0, -1, +- T_SOA, 0, 0, -1, +- T_MB, 0, -1, +- T_MG, 0, -1, +- T_MR, 0, -1, +- T_PTR, 0, -1, +- T_MINFO, 0, 0, -1, +- T_MX, 2, 0, -1, +- T_RP, 0, 0, -1, +- T_AFSDB, 2, 0, -1, +- T_RT, 2, 0, -1, +- T_SIG, 18, 0, -1, +- T_PX, 2, 0, 0, -1, +- T_NXT, 0, -1, +- T_KX, 2, 0, -1, +- T_SRV, 6, 0, -1, +- T_DNAME, 0, -1, +- 0, -1 /* wildcard/catchall */ +- }; +- +- u16 *p = rr_desc; +- +- while (*p != type && *p != 0) +- while (*p++ != (u16)-1); +- +- return p+1; +-} +- + /* Return bytes of canonicalised rdata, when the return value is zero, the remaining + data, pointed to by *p, should be used raw. */ + static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, +@@ -594,34 +550,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, + } + } + +-static int expand_workspace(unsigned char ***wkspc, int *szp, int new) +-{ +- unsigned char **p; +- int old = *szp; +- +- if (old >= new+1) +- return 1; +- +- if (new >= 100) +- return 0; +- +- new += 5; +- +- if (!(p = whine_malloc(new * sizeof(unsigned char **)))) +- return 0; +- +- if (old != 0 && *wkspc) +- { +- memcpy(p, *wkspc, old * sizeof(unsigned char **)); +- free(*wkspc); +- } +- +- *wkspc = p; +- *szp = new; +- +- return 1; +-} +- + /* Bubble sort the RRset into the canonical order. + Note that the byte-streams from two RRs may get unsynced: consider + RRs which have two domain-names at the start and then other data. +@@ -849,7 +777,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + int rdlen, j, name_labels; + struct crec *crecp = NULL; + int algo, labels, orig_ttl, key_tag; +- u16 *rr_desc = get_desc(type); ++ u16 *rr_desc = rrfilter_desc(type); + + if (wildcard_out) + *wildcard_out = NULL; +@@ -2266,239 +2194,6 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i + return ret; + } + +-/* Go through a domain name, find "pointers" and fix them up based on how many bytes +- we've chopped out of the packet, or check they don't point into an elided part. */ +-static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +-{ +- unsigned char *ansp = *namep; +- +- while(1) +- { +- unsigned int label_type; +- +- if (!CHECK_LEN(header, ansp, plen, 1)) +- return 0; +- +- label_type = (*ansp) & 0xc0; +- +- if (label_type == 0xc0) +- { +- /* pointer for compression. */ +- unsigned int offset; +- int i; +- unsigned char *p; +- +- if (!CHECK_LEN(header, ansp, plen, 2)) +- return 0; +- +- offset = ((*ansp++) & 0x3f) << 8; +- offset |= *ansp++; +- +- p = offset + (unsigned char *)header; +- +- for (i = 0; i < rr_count; i++) +- if (p < rrs[i]) +- break; +- else +- if (i & 1) +- offset -= rrs[i] - rrs[i-1]; +- +- /* does the pointer end up in an elided RR? */ +- if (i & 1) +- return 0; +- +- /* No, scale the pointer */ +- if (fixup) +- { +- ansp -= 2; +- *ansp++ = (offset >> 8) | 0xc0; +- *ansp++ = offset & 0xff; +- } +- break; +- } +- else if (label_type == 0x80) +- return 0; /* reserved */ +- else if (label_type == 0x40) +- { +- /* Extended label type */ +- unsigned int count; +- +- if (!CHECK_LEN(header, ansp, plen, 2)) +- return 0; +- +- if (((*ansp++) & 0x3f) != 1) +- return 0; /* we only understand bitstrings */ +- +- count = *(ansp++); /* Bits in bitstring */ +- +- if (count == 0) /* count == 0 means 256 bits */ +- ansp += 32; +- else +- ansp += ((count-1)>>3)+1; +- } +- else +- { /* label type == 0 Bottom six bits is length */ +- unsigned int len = (*ansp++) & 0x3f; +- +- if (!ADD_RDLEN(header, ansp, plen, len)) +- return 0; +- +- if (len == 0) +- break; /* zero length label marks the end. */ +- } +- } +- +- *namep = ansp; +- +- return 1; +-} +- +-/* Go through RRs and check or fixup the domain names contained within */ +-static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +-{ +- int i, type, class, rdlen; +- unsigned char *pp; +- +- for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) +- { +- pp = p; +- +- if (!(p = skip_name(p, header, plen, 10))) +- return 0; +- +- GETSHORT(type, p); +- GETSHORT(class, p); +- p += 4; /* TTL */ +- GETSHORT(rdlen, p); +- +- if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG) +- { +- /* fixup name of RR */ +- if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) +- return 0; +- +- if (class == C_IN) +- { +- u16 *d; +- +- for (pp = p, d = get_desc(type); *d != (u16)-1; d++) +- { +- if (*d != 0) +- pp += *d; +- else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) +- return 0; +- } +- } +- } +- +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return 0; +- } +- +- return 1; +-} +- +- +-size_t filter_rrsigs(struct dns_header *header, size_t plen) +-{ +- static unsigned char **rrs; +- static int rr_sz = 0; +- +- unsigned char *p = (unsigned char *)(header+1); +- int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; +- +- if (ntohs(header->qdcount) != 1 || +- !(p = skip_name(p, header, plen, 4))) +- return plen; +- +- GETSHORT(qtype, p); +- GETSHORT(qclass, p); +- +- /* First pass, find pointers to start and end of all the records we wish to elide: +- records added for DNSSEC, unless explicity queried for */ +- for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; +- i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); +- i++) +- { +- unsigned char *pstart = p; +- int type, class; +- +- if (!(p = skip_name(p, header, plen, 10))) +- return plen; +- +- GETSHORT(type, p); +- GETSHORT(class, p); +- p += 4; /* TTL */ +- GETSHORT(rdlen, p); +- +- if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && +- (type != qtype || class != qclass)) +- { +- if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) +- return plen; +- +- rrs[rr_found++] = pstart; +- +- if (!ADD_RDLEN(header, p, plen, rdlen)) +- return plen; +- +- rrs[rr_found++] = p; +- +- if (i < ntohs(header->ancount)) +- chop_an++; +- else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) +- chop_ns++; +- else +- chop_ar++; +- } +- else if (!ADD_RDLEN(header, p, plen, rdlen)) +- return plen; +- } +- +- /* Nothing to do. */ +- if (rr_found == 0) +- return plen; +- +- /* Second pass, look for pointers in names in the records we're keeping and make sure they don't +- point to records we're going to elide. This is theoretically possible, but unlikely. If +- it happens, we give up and leave the answer unchanged. */ +- p = (unsigned char *)(header+1); +- +- /* question first */ +- if (!check_name(&p, header, plen, 0, rrs, rr_found)) +- return plen; +- p += 4; /* qclass, qtype */ +- +- /* Now answers and NS */ +- if (!check_rrs(p, header, plen, 0, rrs, rr_found)) +- return plen; +- +- /* Third pass, elide records */ +- for (p = rrs[0], i = 1; i < rr_found; i += 2) +- { +- unsigned char *start = rrs[i]; +- unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen; +- +- memmove(p, start, end-start); +- p += end-start; +- } +- +- plen = p - (unsigned char *)header; +- header->ancount = htons(ntohs(header->ancount) - chop_an); +- header->nscount = htons(ntohs(header->nscount) - chop_ns); +- header->arcount = htons(ntohs(header->arcount) - chop_ar); +- +- /* Fourth pass, fix up pointers in the remaining records */ +- p = (unsigned char *)(header+1); +- +- check_name(&p, header, plen, 1, rrs, rr_found); +- p += 4; /* qclass, qtype */ +- +- check_rrs(p, header, plen, 1, rrs, rr_found); +- +- return plen; +-} +- + unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) + { + int q; +diff --git a/src/forward.c b/src/forward.c +index dd22a62..3e801c8 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -662,7 +662,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server + + /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ + if (!do_bit) +- n = filter_rrsigs(header, n); ++ n = rrfilter(header, n, 1); + #endif + + /* do this after extract_addresses. Ensure NODATA reply and remove +diff --git a/src/rrfilter.c b/src/rrfilter.c +new file mode 100644 +index 0000000..ae12261 +--- /dev/null ++++ b/src/rrfilter.c +@@ -0,0 +1,339 @@ ++/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; version 2 dated June, 1991, or ++ (at your option) version 3 dated 29 June, 2007. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see http://www.gnu.org/licenses/. ++*/ ++ ++/* Code to safely remove RRs from an DNS answer */ ++ ++#include "dnsmasq.h" ++ ++/* Go through a domain name, find "pointers" and fix them up based on how many bytes ++ we've chopped out of the packet, or check they don't point into an elided part. */ ++static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) ++{ ++ unsigned char *ansp = *namep; ++ ++ while(1) ++ { ++ unsigned int label_type; ++ ++ if (!CHECK_LEN(header, ansp, plen, 1)) ++ return 0; ++ ++ label_type = (*ansp) & 0xc0; ++ ++ if (label_type == 0xc0) ++ { ++ /* pointer for compression. */ ++ unsigned int offset; ++ int i; ++ unsigned char *p; ++ ++ if (!CHECK_LEN(header, ansp, plen, 2)) ++ return 0; ++ ++ offset = ((*ansp++) & 0x3f) << 8; ++ offset |= *ansp++; ++ ++ p = offset + (unsigned char *)header; ++ ++ for (i = 0; i < rr_count; i++) ++ if (p < rrs[i]) ++ break; ++ else ++ if (i & 1) ++ offset -= rrs[i] - rrs[i-1]; ++ ++ /* does the pointer end up in an elided RR? */ ++ if (i & 1) ++ return 0; ++ ++ /* No, scale the pointer */ ++ if (fixup) ++ { ++ ansp -= 2; ++ *ansp++ = (offset >> 8) | 0xc0; ++ *ansp++ = offset & 0xff; ++ } ++ break; ++ } ++ else if (label_type == 0x80) ++ return 0; /* reserved */ ++ else if (label_type == 0x40) ++ { ++ /* Extended label type */ ++ unsigned int count; ++ ++ if (!CHECK_LEN(header, ansp, plen, 2)) ++ return 0; ++ ++ if (((*ansp++) & 0x3f) != 1) ++ return 0; /* we only understand bitstrings */ ++ ++ count = *(ansp++); /* Bits in bitstring */ ++ ++ if (count == 0) /* count == 0 means 256 bits */ ++ ansp += 32; ++ else ++ ansp += ((count-1)>>3)+1; ++ } ++ else ++ { /* label type == 0 Bottom six bits is length */ ++ unsigned int len = (*ansp++) & 0x3f; ++ ++ if (!ADD_RDLEN(header, ansp, plen, len)) ++ return 0; ++ ++ if (len == 0) ++ break; /* zero length label marks the end. */ ++ } ++ } ++ ++ *namep = ansp; ++ ++ return 1; ++} ++ ++/* Go through RRs and check or fixup the domain names contained within */ ++static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) ++{ ++ int i, j, type, class, rdlen; ++ unsigned char *pp; ++ ++ for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) ++ { ++ pp = p; ++ ++ if (!(p = skip_name(p, header, plen, 10))) ++ return 0; ++ ++ GETSHORT(type, p); ++ GETSHORT(class, p); ++ p += 4; /* TTL */ ++ GETSHORT(rdlen, p); ++ ++ /* If this RR is to be elided, don't fix up its contents */ ++ for (j = 0; j < rr_count; j += 2) ++ if (rrs[j] == pp) ++ break; ++ ++ if (j >= rr_count) ++ { ++ /* fixup name of RR */ ++ if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) ++ return 0; ++ ++ if (class == C_IN) ++ { ++ u16 *d; ++ ++ for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++) ++ { ++ if (*d != 0) ++ pp += *d; ++ else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) ++ return 0; ++ } ++ } ++ } ++ ++ if (!ADD_RDLEN(header, p, plen, rdlen)) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++ ++/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */ ++size_t rrfilter(struct dns_header *header, size_t plen, int mode) ++{ ++ static unsigned char **rrs; ++ static int rr_sz = 0; ++ ++ unsigned char *p = (unsigned char *)(header+1); ++ int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; ++ ++ if (ntohs(header->qdcount) != 1 || ++ !(p = skip_name(p, header, plen, 4))) ++ return plen; ++ ++ GETSHORT(qtype, p); ++ GETSHORT(qclass, p); ++ ++ /* First pass, find pointers to start and end of all the records we wish to elide: ++ records added for DNSSEC, unless explicity queried for */ ++ for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; ++ i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); ++ i++) ++ { ++ unsigned char *pstart = p; ++ int type, class; ++ ++ if (!(p = skip_name(p, header, plen, 10))) ++ return plen; ++ ++ GETSHORT(type, p); ++ GETSHORT(class, p); ++ p += 4; /* TTL */ ++ GETSHORT(rdlen, p); ++ ++ if (!ADD_RDLEN(header, p, plen, rdlen)) ++ return plen; ++ ++ /* Don't remove the answer. */ ++ if (i < ntohs(header->ancount) && type == qtype && class == qclass) ++ continue; ++ ++ if (mode == 0) /* EDNS */ ++ { ++ /* EDNS mode, remove T_OPT from additional section only */ ++ if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT) ++ continue; ++ } ++ else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG) ++ /* DNSSEC mode, remove SIGs and NSECs from all three sections. */ ++ continue; ++ ++ ++ if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) ++ return plen; ++ ++ rrs[rr_found++] = pstart; ++ rrs[rr_found++] = p; ++ ++ if (i < ntohs(header->ancount)) ++ chop_an++; ++ else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) ++ chop_ns++; ++ else ++ chop_ar++; ++ } ++ ++ /* Nothing to do. */ ++ if (rr_found == 0) ++ return plen; ++ ++ /* Second pass, look for pointers in names in the records we're keeping and make sure they don't ++ point to records we're going to elide. This is theoretically possible, but unlikely. If ++ it happens, we give up and leave the answer unchanged. */ ++ p = (unsigned char *)(header+1); ++ ++ /* question first */ ++ if (!check_name(&p, header, plen, 0, rrs, rr_found)) ++ return plen; ++ p += 4; /* qclass, qtype */ ++ ++ /* Now answers and NS */ ++ if (!check_rrs(p, header, plen, 0, rrs, rr_found)) ++ return plen; ++ ++ /* Third pass, elide records */ ++ for (p = rrs[0], i = 1; i < rr_found; i += 2) ++ { ++ unsigned char *start = rrs[i]; ++ unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen; ++ ++ memmove(p, start, end-start); ++ p += end-start; ++ } ++ ++ plen = p - (unsigned char *)header; ++ header->ancount = htons(ntohs(header->ancount) - chop_an); ++ header->nscount = htons(ntohs(header->nscount) - chop_ns); ++ header->arcount = htons(ntohs(header->arcount) - chop_ar); ++ ++ /* Fourth pass, fix up pointers in the remaining records */ ++ p = (unsigned char *)(header+1); ++ ++ check_name(&p, header, plen, 1, rrs, rr_found); ++ p += 4; /* qclass, qtype */ ++ ++ check_rrs(p, header, plen, 1, rrs, rr_found); ++ ++ return plen; ++} ++ ++/* This is used in the DNSSEC code too, hence it's exported */ ++u16 *rrfilter_desc(int type) ++{ ++ /* List of RRtypes which include domains in the data. ++ 0 -> domain ++ integer -> no of plain bytes ++ -1 -> end ++ ++ zero is not a valid RRtype, so the final entry is returned for ++ anything which needs no mangling. ++ */ ++ ++ static u16 rr_desc[] = ++ { ++ T_NS, 0, -1, ++ T_MD, 0, -1, ++ T_MF, 0, -1, ++ T_CNAME, 0, -1, ++ T_SOA, 0, 0, -1, ++ T_MB, 0, -1, ++ T_MG, 0, -1, ++ T_MR, 0, -1, ++ T_PTR, 0, -1, ++ T_MINFO, 0, 0, -1, ++ T_MX, 2, 0, -1, ++ T_RP, 0, 0, -1, ++ T_AFSDB, 2, 0, -1, ++ T_RT, 2, 0, -1, ++ T_SIG, 18, 0, -1, ++ T_PX, 2, 0, 0, -1, ++ T_NXT, 0, -1, ++ T_KX, 2, 0, -1, ++ T_SRV, 6, 0, -1, ++ T_DNAME, 0, -1, ++ 0, -1 /* wildcard/catchall */ ++ }; ++ ++ u16 *p = rr_desc; ++ ++ while (*p != type && *p != 0) ++ while (*p++ != (u16)-1); ++ ++ return p+1; ++} ++ ++int expand_workspace(unsigned char ***wkspc, int *szp, int new) ++{ ++ unsigned char **p; ++ int old = *szp; ++ ++ if (old >= new+1) ++ return 1; ++ ++ if (new >= 100) ++ return 0; ++ ++ new += 5; ++ ++ if (!(p = whine_malloc(new * sizeof(unsigned char **)))) ++ return 0; ++ ++ if (old != 0 && *wkspc) ++ { ++ memcpy(p, *wkspc, old * sizeof(unsigned char **)); ++ free(*wkspc); ++ } ++ ++ *wkspc = p; ++ *szp = new; ++ ++ return 1; ++} +-- +1.7.10.4 + diff --git a/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch b/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch new file mode 100644 index 0000000..ffb412b --- /dev/null +++ b/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch @@ -0,0 +1,134 @@ +From 2dbba34b2c1289a108f876c78b84889f2a93115d Mon Sep 17 00:00:00 2001 +From: Simon Kelley simon@thekelleys.org.uk +Date: Wed, 16 Dec 2015 13:41:58 +0000 +Subject: [PATCH] DNSSEC validation tweak. + +A zone which has at least one key with an algorithm we don't +support should be considered as insecure. +--- + src/dnssec.c | 82 ++++++++++++++++++++++++++++++++++++++-------------------- + 1 file changed, 54 insertions(+), 28 deletions(-) + +diff --git a/src/dnssec.c b/src/dnssec.c +index fa3eb81..dc563e0 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -763,10 +763,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) + +- if key is non-NULL, use that key, which has the algo and tag given in the params of those names, ++ If key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. + +- name is unchanged on exit. keyname is used as workspace and trashed. ++ Name is unchanged on exit. keyname is used as workspace and trashed. + + Call explore_rrset first to find and count RRs and sigs. + */ +@@ -919,6 +919,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + return STAT_BOGUS; + } + ++ + /* The DNS packet is expected to contain the answer to a DNSKEY query. + Put all DNSKEYs in the answer which are valid into the cache. + return codes: +@@ -1831,15 +1832,15 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns + + /* Check signing status of name. + returns: +- STAT_SECURE zone is signed. +- STAT_INSECURE zone proved unsigned. +- STAT_NEED_DS require DS record of name returned in keyname. +- ++ STAT_SECURE zone is signed. ++ STAT_INSECURE zone proved unsigned. ++ STAT_NEED_DS require DS record of name returned in keyname. ++ STAT_NEED_DNSKEY require DNSKEY record of name returned in keyname. + name returned unaltered. + */ + static int zone_status(char *name, int class, char *keyname, time_t now) + { +- int name_start = strlen(name); ++ int secure_ds, name_start = strlen(name); + struct crec *crecp; + char *p; + +@@ -1850,27 +1851,52 @@ static int zone_status(char *name, int class, char *keyname, time_t now) + if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS))) + return STAT_NEED_DS; + else +- do +- { +- if (crecp->uid == (unsigned int)class) +- { +- /* F_DNSSECOK misused in DS cache records to non-existance of NS record. +- F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here, +- but that's because there's no NS record either, ie this isn't the start +- of a zone. We only prove that the DNS tree below a node is unsigned when +- we prove that we're at a zone cut AND there's no DS record. +- */ +- if (crecp->flags & F_NEG) +- { +- if (crecp->flags & F_DNSSECOK) +- return STAT_INSECURE; /* proved no DS here */ +- } +- else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo)) +- return STAT_INSECURE; /* algo we can't use - insecure */ +- } +- } +- while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS))); +- ++ { ++ secure_ds = 0; ++ ++ do ++ { ++ if (crecp->uid == (unsigned int)class) ++ { ++ /* F_DNSSECOK misused in DS cache records to non-existance of NS record. ++ F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here, ++ but that's because there's no NS record either, ie this isn't the start ++ of a zone. We only prove that the DNS tree below a node is unsigned when ++ we prove that we're at a zone cut AND there's no DS record. ++ */ ++ if (crecp->flags & F_NEG) ++ { ++ if (crecp->flags & F_DNSSECOK) ++ return STAT_INSECURE; /* proved no DS here */ ++ } ++ else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo)) ++ return STAT_INSECURE; /* algo we can't use - insecure */ ++ else ++ secure_ds = 1; ++ } ++ } ++ while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS))); ++ } ++ ++ if (secure_ds) ++ { ++ /* We've found only DS records that attest to the DNSKEY RRset in the zone, so we believe ++ that RRset is good. Furthermore the DNSKEY whose hash is proved by the DS record is ++ one we can use. However the DNSKEY RRset may contain more than one key and ++ one of the other keys may use an algorithm we don't support. If that's ++ the case the zone is insecure for us. */ ++ ++ if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) ++ return STAT_NEED_KEY; ++ ++ do ++ { ++ if (crecp->uid == (unsigned int)class && !algo_digest_name(crecp->addr.key.algo)) ++ return STAT_INSECURE; ++ } ++ while ((crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))); ++ } ++ + if (name_start == 0) + break; + +-- +1.7.10.4 + 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@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@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@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@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 +
hooks/post-receive -- IPFire 2.x development tree