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 --- lfs/dnsmasq | 5 + ...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 ++ 7 files changed, 4017 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
diff --git a/lfs/dnsmasq b/lfs/dnsmasq index d166392..eeb7e03 100644 --- a/lfs/dnsmasq +++ b/lfs/dnsmasq @@ -88,6 +88,11 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/013-Fix_crash_when_empty_address_from_DNS_overlays_A_record_from.patch cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/014-Handle_unknown_DS_hash_algos_correctly.patch cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/015-Fix_crash_at_start_up_with_conf-dir.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/016-Major_rationalisation_of_DNSSEC_validation.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/017-Abandon_caching_RRSIGs_and_returning_them_from_cache.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/018-Move_code_which_caches_DS_records_to_a_more_logical_place.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/019-Generalise_RR-filtering_code_for_use_with_EDNS0.patch + cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq/020-DNSSEC_validation_tweak.patch cd $(DIR_APP) && patch -Np1 -i $(DIR_SRC)/src/patches/dnsmasq-Add-support-to-read-ISC-DHCP-lease-file.patch
cd $(DIR_APP) && sed -i src/config.h \ 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 +