diff -u dnsmasq-2.75/debian/changelog dnsmasq-2.75/debian/changelog --- dnsmasq-2.75/debian/changelog +++ dnsmasq-2.75/debian/changelog @@ -1,10 +1,13 @@ -dnsmasq (2.75-1ubuntu0.16.04.9) xenial-security; urgency=medium +dnsmasq (2.75-1ubuntu0.16.04.10) xenial-security; urgency=medium * SECURITY UPDATE: Multiple security issues - CVE-2017-15107: wildcard NSEC records interpretation issue + + 4fe6744a220eddd3f1749b40cac3dfc510787de6 + + cd7df612b14ec1bf831a966ccaf076be0dae7404 - CVE-2019-14513: DoS via improper bounds checking + + d3a8b39c7df2f0debf3b5f274a1c37a9e261f94e - -- Marc Deslauriers Tue, 20 Apr 2021 07:17:08 -0400 + -- Marc Deslauriers Thu, 22 Apr 2021 09:12:18 -0400 dnsmasq (2.75-1ubuntu0.16.04.8) xenial-security; urgency=medium diff -u dnsmasq-2.75/src/cache.c dnsmasq-2.75/src/cache.c --- dnsmasq-2.75/src/cache.c +++ dnsmasq-2.75/src/cache.c @@ -189,7 +189,12 @@ static void cache_blockdata_free(struct crec *crecp) { if (crecp->flags & F_DNSKEY) - blockdata_free(crecp->addr.key.keydata); + { + if (crecp->flags & F_DS) + blockdata_free(crecp->addr.sig.keydata); + else + blockdata_free(crecp->addr.key.keydata); + } else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG)) blockdata_free(crecp->addr.ds.keydata); } @@ -364,8 +369,13 @@ } #ifdef HAVE_DNSSEC - /* Deletion has to be class-sensitive for DS and DNSKEY */ - if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == addr->addr.dnssec.class) + /* 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)) { if (crecp->flags & F_CONFIG) return crecp; @@ -522,9 +532,13 @@ struct all_addr free_addr = new->addr.addr;; #ifdef HAVE_DNSSEC - /* For DNSSEC records, addr holds class. */ + /* For DNSSEC records, addr holds class and type_covered for RRSIG */ if (new->flags & (F_DS | F_DNSKEY)) - free_addr.addr.dnssec.class = new->uid; + { + 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; + } #endif free_avail = 1; /* Must be free space now. */ @@ -639,6 +653,9 @@ 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)) { @@ -696,6 +713,9 @@ 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; @@ -1452,7 +1472,11 @@ #ifdef HAVE_DNSSEC else if (cache->flags & F_DS) { - if (!(cache->flags & F_NEG)) + 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)) sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, cache->addr.ds.algo, cache->addr.ds.digest); } @@ -1478,6 +1502,8 @@ 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 -u dnsmasq-2.75/src/dnsmasq.h dnsmasq-2.75/src/dnsmasq.h --- dnsmasq-2.75/src/dnsmasq.h +++ dnsmasq-2.75/src/dnsmasq.h @@ -395,9 +395,14 @@ 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, index to source for F_HOSTS */ + /* used as class if DNSKEY/DS/RRSIG, index to source for F_HOSTS */ unsigned int uid; unsigned short flags; union { @@ -437,7 +442,8 @@ #define F_SECSTAT (1u<<24) #define F_NO_RR (1u<<25) #define F_IPSET (1u<<26) -#define F_NOEXTRA (1u<<27) +#define F_NSIGMATCH (1u<<27) +#define F_NOEXTRA (1u<<28) /* Values of uid in crecs with F_CONFIG bit set. */ #define SRC_INTERFACE 0 @@ -571,8 +577,12 @@ #define STAT_NEED_KEY 5 #define STAT_TRUNCATED 6 #define STAT_SECURE_WILDCARD 7 -#define STAT_OK 8 -#define STAT_ABANDONED 9 +#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 FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 @@ -582,9 +592,10 @@ #define FREC_AD_QUESTION 32 #define FREC_DO_QUESTION 64 #define FREC_ADDED_PHEADER 128 -#define FREC_TEST_PKTSZ 256 -#define FREC_HAS_EXTRADATA 512 +#define FREC_CHECK_NOSIGN 256 +#define FREC_TEST_PKTSZ 512 #define FREC_HAS_PHEADER 1024 +#define FREC_HAS_EXTRADATA 2048 #define HASH_SIZE 32 /* SHA-256 digest size */ @@ -609,7 +620,9 @@ #ifdef HAVE_DNSSEC int class, work_counter; struct blockdata *stash; /* Saved reply, whilst we validate */ - size_t stash_len; + 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; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *blocking_query; /* Query which is blocking us. */ #endif @@ -1144,8 +1157,8 @@ 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 check_unsigned, int *neganswer, int *nons); +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 dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); size_t filter_rrsigs(struct dns_header *header, size_t plen); int setup_timestamp(void); interdiff impossible; taking evasive action reverted: --- dnsmasq-2.75/src/dnssec.c +++ dnsmasq-2.75.orig/src/dnssec.c @@ -65,16 +65,14 @@ 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; } } /* Find pointer to correct hash function in nettle library */ +static const struct nettle_hash *hash_find(char *name) -const struct nettle_hash *hash_find(char *name) { int i; @@ -91,7 +89,7 @@ } /* expand ctx and digest memory allocations if necessary and init hash function */ +static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) -int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) { static void *ctx = NULL; static unsigned char *digest = NULL; @@ -393,17 +391,15 @@ static int count_labels(char *name) { int i; + - char *p; - if (*name == 0) return 0; + for (i = 0; *name; name++) + if (*name == '.') - for (p = name, i = 0; *p; p++) - if (*p == '.') i++; + return i+1; - /* Don't count empty first label. */ - return *name == '.' ? i : i+1; } /* Implement RFC1982 wrapped compare for 32-bit numbers */ @@ -553,205 +549,204 @@ 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, + unsigned char **p, u16 **desc) +{ + int d = **desc; + + /* No more data needs mangling */ + if (d == (u16)-1) + { + /* If there's more data than we have space for, just return what fits, + we'll get called again for more chunks */ + if (end - *p > bufflen) + { + memcpy(buff, *p, bufflen); + *p += bufflen; + return bufflen; + } + + return 0; -/* Return bytes of canonicalised rrdata one by one. - Init state->ip with the RR, and state->end with the end of same. - Init state->op to NULL. - Init state->desc to RR descriptor. - Init state->buff with a MAXDNAME * 2 buffer. - - After each call which returns 1, state->op points to the next byte of data. - On returning 0, the end has been reached. -*/ -struct rdata_state { - u16 *desc; - size_t c; - unsigned char *end, *ip, *op; - char *buff; -}; - -static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) -{ - int d; - - if (state->op && state->c != 1) - { - state->op++; - state->c--; - return 1; } + (*desc)++; + + if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) + /* domain-name, canonicalise */ + return to_wire(buff); + else + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((end - *p) < d) + d = end - *p; - - while (1) - { - d = *(state->desc); + if (d != 0) - if (d == (u16)-1) - { - /* all the bytes to the end. */ - if ((state->c = state->end - state->ip) != 0) - { - state->op = state->ip; - state->ip = state->end;; - } - else - return 0; - } - else { + memcpy(buff, *p, d); + *p += d; - state->desc++; - - if (d == (u16)0) - { - /* domain-name, canonicalise */ - int len; - - if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || - (len = to_wire(state->buff)) == 0) - continue; - - state->c = len; - state->op = (unsigned char *)state->buff; - } - else - { - /* plain data preceding a domain-name, don't run off the end of the data */ - if ((state->end - state->ip) < d) - d = state->end - state->ip; - - if (d == 0) - continue; - - state->op = state->ip; - state->c = d; - state->ip += d; - } } + return d; - return 1; } } +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; } +/* 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. + The domain-names may have different lengths in each RR, but sort equal + + ------------ + |abcde|fghi| + ------------ + |abcd|efghi| + ------------ -/* Bubble sort the RRset into the canonical order. */ + leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +*/ + +static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) -static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, - unsigned char **rrset, char *buff1, char *buff2) { + int swap, quit, i; - int swap, i, j; do { for (swap = 0, i = 0; i < rrsetidx-1; i++) { + int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; + u16 *dp1, *dp2; + unsigned char *end1, *end2; - int rdlen1, rdlen2; - struct rdata_state state1, state2; - /* Note that these have been determined to be OK previously, so we don't need to check for NULL return here. */ + unsigned char *p1 = skip_name(rrset[i], header, plen, 10); + unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); + + p1 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, p1); + end1 = p1 + rdlen1; + + p2 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, p2); + end2 = p2 + rdlen2; + + dp1 = dp2 = rr_desc; + + for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) - state1.ip = skip_name(rrset[i], header, plen, 10); - state2.ip = skip_name(rrset[i+1], header, plen, 10); - state1.op = state2.op = NULL; - state1.buff = buff1; - state2.buff = buff2; - state1.desc = state2.desc = rr_desc; - - state1.ip += 8; /* skip class, type, ttl */ - GETSHORT(rdlen1, state1.ip); - if (!CHECK_LEN(header, state1.ip, plen, rdlen1)) - return rrsetidx; /* short packet */ - state1.end = state1.ip + rdlen1; - - state2.ip += 8; /* skip class, type, ttl */ - GETSHORT(rdlen2, state2.ip); - if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) - return rrsetidx; /* short packet */ - state2.end = state2.ip + rdlen2; - - while (1) { + if (left1 != 0) + memmove(buff1, buff1 + len1 - left1, left1); + + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) + { + quit = 1; + len1 = end1 - p1; + memcpy(buff1 + left1, p1, len1); + } + len1 += left1; - int ok1, ok2; + if (left2 != 0) + memmove(buff2, buff2 + len2 - left2, left2); + + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) - ok1 = get_rdata(header, plen, &state1); - ok2 = get_rdata(header, plen, &state2); - - if (!ok1 && !ok2) { + quit = 1; + len2 = end2 - p2; + memcpy(buff2 + left2, p2, len2); - /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ - for (j = i+1; j < rrsetidx-1; j++) - rrset[j] = rrset[j+1]; - rrsetidx--; - i--; - break; } + len2 += left2; + + if (len1 > len2) + left1 = len1 - len2, left2 = 0, len = len2; + else + left2 = len2 - len1, left1 = 0, len = len1; + + rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); + + if (rc > 0 || (rc == 0 && quit && len1 > len2)) - else if (ok1 && (!ok2 || *state1.op > *state2.op)) { unsigned char *tmp = rrset[i+1]; rrset[i+1] = rrset[i]; rrset[i] = tmp; + swap = quit = 1; - swap = 1; - break; } + else if (rc < 0) + quit = 1; - else if (ok2 && (!ok1 || *state2.op > *state1.op)) - break; - - /* arrive here when bytes are equal, go round the loop again - and compare the next ones. */ } } } 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) - return rrsetidx; -} + 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; @@ -767,14 +762,14 @@ 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; } @@ -782,54 +777,14 @@ 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; } @@ -839,49 +794,21 @@ } if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_BOGUS; - return 0; } + /* RRset empty */ + if (rrsetidx == 0) + return STAT_INSECURE; + + /* no RRSIGs */ + if (sigidx == 0) + return STAT_NO_SIG; - *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. - - 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. */ + sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); - rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); /* Now try all the sigs to try and find one which validates */ for (j = 0; j 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; @@ -924,18 +879,14 @@ wire_len = to_wire(keyname); hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); from_wire(keyname); - -#define RRBUFLEN 300 /* Most RRs are smaller than this. */ for (i = 0; i < rrsetidx; ++i) { + int seg; + unsigned char *end, *cp; + u16 len, *dp; - int j; - struct rdata_state state; - u16 len; - unsigned char rrbuf[RRBUFLEN]; p = rrset[i]; - if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_BOGUS; @@ -944,11 +895,12 @@ /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ if (labels < name_labels) { + int k; + for (k = name_labels - labels; k != 0; k--) - for (j = name_labels - labels; j != 0; j--) { while (*name_start != '.' && *name_start != 0) name_start++; + if (k != 1 && *name_start == '.') - if (j != 1 && *name_start == '.') name_start++; } @@ -969,44 +921,24 @@ if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; + end = p + rdlen; + + /* canonicalise rdata and calculate length of same, use name buffer as workspace. + Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ + cp = p; + dp = rr_desc; + for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); + len += end - cp; + len = htons(len); - /* canonicalise rdata and calculate length of same, use - name buffer as workspace for get_rdata. */ - state.ip = p; - state.op = NULL; - state.desc = rr_desc; - state.buff = name; - state.end = p + rdlen; - - for (j = 0; get_rdata(header, plen, &state); j++) - if (j < RRBUFLEN) - rrbuf[j] = *state.op; - - len = htons((u16)j); hash->update(ctx, 2, (unsigned char *)&len); - - /* If the RR is shorter than RRBUFLEN (most of them, in practice) - then we can just digest it now. If it exceeds RRBUFLEN we have to - go back to the start and do it in chunks. */ - if (j >= RRBUFLEN) - { - state.ip = p; - state.op = NULL; - state.desc = rr_desc; - - for (j = 0; get_rdata(header, plen, &state); j++) - { - rrbuf[j] = *state.op; - - if (j == RRBUFLEN - 1) - { - hash->update(ctx, RRBUFLEN, rrbuf); - j = -1; - } - } - } + /* Now canonicalise again and digest. */ + cp = p; + dp = rr_desc; + while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) + hash->update(ctx, seg, (unsigned char *)name); + if (cp != end) + hash->update(ctx, end - cp, cp); - if (j != 0) - hash->update(ctx, j, rrbuf); } hash->digest(ctx, hash->digest_size, digest); @@ -1039,17 +971,16 @@ /* 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) { 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; @@ -1070,6 +1001,10 @@ 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; + /* NOTE, we need to find ONE DNSKEY which matches the DS */ for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) { @@ -1122,8 +1057,7 @@ 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 && @@ -1141,14 +1075,10 @@ 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; @@ -1160,7 +1090,7 @@ 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); @@ -1169,7 +1099,7 @@ { /* 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); @@ -1200,10 +1130,7 @@ 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; @@ -1217,7 +1144,38 @@ } } } + 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; } @@ -1227,7 +1185,7 @@ /* commit cache insert. */ cache_end_insert(); + return STAT_SECURE; - return STAT_OK; } log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); @@ -1236,23 +1194,18 @@ /* 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) { 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))) @@ -1262,100 +1215,44 @@ 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, &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 we return STAT_NO_SIG, name contains the name of the DS query */ + if (val == STAT_NO_SIG) + { + *keyname = 0; + 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 (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) { log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); return STAT_BOGUS; } + + /* By here, the answer is proved secure, and a positive answer has been cached. */ + if (val == STAT_SECURE && 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; @@ -1369,20 +1266,20 @@ 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; @@ -1406,19 +1303,19 @@ 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(); + 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) { @@ -1483,59 +1380,89 @@ } } +/* Find all the NSEC or NSEC3 records in a reply. + return an array of pointers to them. */ +static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd) -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, - char *workspace1_in, char *workspace2, char *name, int type, int *nons) { + static unsigned char **nsecset = NULL; + static int nsecset_sz = 0; + + int type_found = 0; + unsigned char *p = skip_questions(header, plen); + int type, class, rdlen, i, nsecs_found; - int i, rc, rdlen; - unsigned char *p, *psave; - int offset = (type & 0xff) >> 3; - int mask = 0x80 >> (type & 0x07); + /* Move to NS section */ + if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) + return 0; - if (nons) - *nons = 1; + for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) - /* Find NSEC record that proves name doesn't exist */ - for (i = 0; i < nsec_count; i++) { + unsigned char *pstart = p; + + if (!(p = skip_name(p, header, plen, 10))) - char *workspace1 = workspace1_in; - int sig_labels, name_labels; - - p = nsecs[i]; - if (!extract_name(header, plen, &p, workspace1, 1, 10)) return 0; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ - p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); - psave = p; - if (!extract_name(header, plen, &p, workspace2, 1, 10)) - return 0; + if (class == class_reqd && (type == T_NSEC || type == T_NSEC3)) + { + /* No mixed NSECing 'round here, thankyouverymuch */ + if (type_found == T_NSEC && type == T_NSEC3) + return 0; + if (type_found == T_NSEC3 && type == T_NSEC) + return 0; + + type_found = type; - /* If NSEC comes from wildcard expansion, use original wildcard - as name for computation. */ - sig_labels = *labels[i]; - name_labels = count_labels(workspace1); + if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) + return 0; - if (sig_labels < name_labels) - { - int k; - for (k = name_labels - sig_labels; k != 0; k--) - { - while (*workspace1 != '.' && *workspace1 != 0) - workspace1++; - if (k != 1 && *workspace1 == '.') - workspace1++; - } + nsecset[nsecs_found++] = pstart; - workspace1--; - *workspace1 = '*'; } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return 0; + } + + *nsecsetp = nsecset; + *nsecsetl = nsecs_found; + + return type_found; +} + +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, + char *workspace1, char *workspace2, char *name, int type, int *nons) +{ + int i, rc, rdlen; + unsigned char *p, *psave; + int offset = (type & 0xff) >> 3; + int mask = 0x80 >> (type & 0x07); + + if (nons) + *nons = 0; + + /* Find NSEC record that proves name doesn't exist */ + for (i = 0; i < nsec_count; i++) + { + p = nsecs[i]; + if (!extract_name(header, plen, &p, workspace1, 1, 10)) + return STAT_BOGUS; + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; + if (!extract_name(header, plen, &p, workspace2, 1, 10)) + return STAT_BOGUS; + - rc = hostname_cmp(workspace1, name); if (rc == 0) { /* 4035 para 5.4. Last sentence */ if (type == T_NSEC || type == T_RRSIG) + return STAT_SECURE; - return 1; /* NSEC with the same name as the RR we're testing, check that the type in question doesn't appear in the type map */ @@ -1543,32 +1470,19 @@ /* 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 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 (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; - return 0; if (p[0] == type >> 8) { /* Does the NSEC say our type exists? */ if (offset < p[1] && (p[offset+2] & mask) != 0) + return STAT_BOGUS; - return 0; break; /* finshed checking */ } @@ -1577,24 +1491,24 @@ p += p[1]; } + return STAT_SECURE; - return 1; } else if (rc == -1) { /* Normal case, name falls between NSEC name and next domain name, wrap around case, name falls between NSEC name (rc == -1) and end */ if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) + return STAT_SECURE; - return 1; } else { /* wrap around case, name falls between start and next domain name */ if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) + return STAT_SECURE; - return 1; } } + return STAT_BOGUS; - return 0; } /* return digest length, or zero on error */ @@ -1662,7 +1576,7 @@ 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++) @@ -1675,9 +1589,7 @@ 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 */ @@ -1704,29 +1616,16 @@ 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 */ } @@ -1734,7 +1633,7 @@ rdlen -= p[1]; p += p[1]; } + - return 1; } else if (rc < 0) @@ -1742,27 +1641,16 @@ /* 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; } @@ -1775,7 +1663,7 @@ 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). @@ -1787,7 +1675,7 @@ for (i = 0; i < nsec_count; i++) { if (!(p = skip_name(nsecs[i], header, plen, 15))) + return STAT_BOGUS; /* bad packet */ - return 0; /* bad packet */ p += 10; /* type, class, TTL, rdlen */ algo = *p++; @@ -1798,14 +1686,14 @@ /* No usable NSEC3s */ if (i == nsec_count) + return STAT_BOGUS; - return 0; p++; /* flags */ GETSHORT (iterations, p); salt_len = *p++; salt = p; if (!CHECK_LEN(header, salt, plen, salt_len)) + return STAT_BOGUS; /* bad packet */ - return 0; /* bad packet */ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ for (i = 0; i < nsec_count; i++) @@ -1816,7 +1704,7 @@ nsecs[i] = NULL; /* Speculative, will be restored if OK. */ if (!(p = skip_name(nsec3p, header, plen, 15))) + return STAT_BOGUS; /* bad packet */ - return 0; /* bad packet */ p += 10; /* type, class, TTL, rdlen */ @@ -1833,7 +1721,7 @@ continue; if (!CHECK_LEN(header, p, plen, salt_len)) + return STAT_BOGUS; /* bad packet */ - return 0; /* bad packet */ if (memcmp(p, salt, salt_len) != 0) continue; @@ -1844,13 +1732,13 @@ /* Algo is checked as 1 above */ if (!(hash = hash_find("sha1"))) + return STAT_BOGUS; - return 0; if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; - return 0; if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons)) + return STAT_SECURE; - return 1; /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" or an answer inferred from a wildcard record. */ @@ -1866,14 +1754,14 @@ break; if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; - return 0; for (i = 0; i < nsec_count; i++) if ((p = nsecs[i])) { if (!extract_name(header, plen, &p, workspace1, 1, 0) || !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return STAT_BOGUS; - return 0; if (digest_len == base32_len && memcmp(digest, workspace2, digest_len) == 0) @@ -1888,220 +1776,43 @@ while ((closest_encloser = strchr(closest_encloser, '.'))); if (!closest_encloser) + return STAT_BOGUS; - return 0; /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; - return 0; if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + return STAT_BOGUS; - return 0; /* Finally, check that there's no seat of wildcard synthesis */ if (!wildname) { if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) + return STAT_BOGUS; - return 0; wildcard--; *wildcard = '*'; if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; - return 0; if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + return STAT_BOGUS; - return 0; - } - - return 1; -} - -static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons) -{ - static unsigned char **nsecset = NULL, **rrsig_labels = NULL; - static int nsecset_sz = 0, rrsig_labels_sz = 0; - - int type_found = 0; - unsigned char *auth_start, *p = skip_questions(header, plen); - int type, class, rdlen, i, nsecs_found; - - /* Move to NS section */ - if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) - return 0; - - auth_start = p; - - for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) - { - unsigned char *pstart = p; - - if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) - return 0; - - GETSHORT(type, p); - GETSHORT(class, p); - p += 4; /* TTL */ - GETSHORT(rdlen, p); - - if (class == qclass && (type == T_NSEC || type == T_NSEC3)) - { - /* No mixed NSECing 'round here, thankyouverymuch */ - if (type_found != 0 && type_found != type) - return 0; - - type_found = type; - - if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) - return 0; - - if (type == T_NSEC) - { - /* If we're looking for NSECs, find the corresponding SIGs, to - extract the labels value, which we need in case the NSECs - are the result of wildcard expansion. - Note that the NSEC may not have been validated yet - so if there are multiple SIGs, make sure the label value - is the same in all, to avoid be duped by a rogue one. - If there are no SIGs, that's an error */ - unsigned char *p1 = auth_start; - int res, j, rdlen1, type1, class1; - - if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) - return 0; - - rrsig_labels[nsecs_found] = NULL; - - for (j = ntohs(header->nscount); j != 0; j--) - { - if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) - return 0; - - GETSHORT(type1, p1); - GETSHORT(class1, p1); - p1 += 4; /* TTL */ - GETSHORT(rdlen1, p1); - - if (!CHECK_LEN(header, p1, plen, rdlen1)) - return 0; - - if (res == 1 && class1 == qclass && type1 == T_RRSIG) - { - int type_covered; - unsigned char *psav = p1; - - if (rdlen1 < 18) - return 0; /* bad packet */ - - GETSHORT(type_covered, p1); - - if (type_covered == T_NSEC) - { - p1++; /* algo */ - - /* labels field must be the same in every SIG we find. */ - if (!rrsig_labels[nsecs_found]) - rrsig_labels[nsecs_found] = p1; - else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ - return 0; - } - p1 = psav; - } - - if (!ADD_RDLEN(header, p1, plen, rdlen1)) - return 0; - } - - /* Must have found at least one sig. */ - if (!rrsig_labels[nsecs_found]) - return 0; - } - - nsecset[nsecs_found++] = pstart; - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return 0; } - if (type_found == T_NSEC) - return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); - else - return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); -} - -/* Check signing status of name. - 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) */ +/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */ - -/* 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; - int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx; - int i, j, rc; if (neganswer) *neganswer = 0; @@ -2112,51 +1823,70 @@ 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)) @@ -2191,78 +1921,151 @@ /* Not done, validate now */ if (j == i) { + int ttl, keytag, algo, digest, type_covered; + unsigned char *psave; + struct all_addr a; + struct blockdata *key; + struct crec *crecp; - int sigcnt, rrcnt; 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; - if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) - return STAT_BOGUS; + /* 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) - /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */ - if (sigcnt == 0) { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + + if (rc == STAT_NO_SIG) - 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 */ + + 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; + } + } + } + else if (type2 == T_RRSIG) + { + if (rdlen2 < 18) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type_covered, p2); - 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. */ + 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; + } + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; /* bad packet */ - for (j = 0; j 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); + + /* NSECs can't be wildcards. */ + if (rc == STAT_SECURE_WILDCARD) + rc = STAT_BOGUS; - /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ - for (j = 0; j 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) - /* 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 (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons)) - { - /* Empty DS without NSECS */ - if (qtype == T_DS) - return STAT_BOGUS; - - if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) - { - if (class) - *class = qclass; /* Class for NEED_DS or NEED_DNSKEY */ return rc; + } + + if (rc != STAT_SECURE) + { + if (rc == STAT_NO_SIG) + rc = STAT_INSECURE; + return rc; + } + + /* Loop down CNAME chain/ */ + if (!cname_count-- || + !extract_name(header, plen, &p, name, 1, 0) || + !(p = skip_questions(header, plen))) + return STAT_BOGUS; + + break; + } + + /* End of CNAME chain */ + return STAT_INSECURE; + } - } - - return STAT_BOGUS; /* signed zone, no NSECs */ - } - } - - return STAT_SECURE; } @@ -2356,7 +2264,7 @@ p = (unsigned char *)(header+1); + p = do_rfc1035_name(p, name); - p = do_rfc1035_name(p, name, NULL); *p++ = 0; PUTSHORT(type, p); PUTSHORT(class, p); @@ -2602,4 +2510,35 @@ return plen; } +unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned int len; + unsigned char *p = (unsigned char *)(header+1); + const struct nettle_hash *hash; + void *ctx; + unsigned char *digest; + + if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) + return NULL; + + for (q = ntohs(header->qdcount); q != 0; q--) + { + if (!extract_name(header, plen, &p, name, 1, 4)) + break; /* bad packet */ + + len = to_wire(name); + hash->update(ctx, len, (unsigned char *)name); + /* CRC the class and type as well */ + hash->update(ctx, 4, p); + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + break; /* bad packet */ + } + + hash->digest(ctx, hash->digest_size, digest); + return digest; +} + #endif /* HAVE_DNSSEC */ unchanged: --- dnsmasq-2.75.orig/src/dnssec.c +++ dnsmasq-2.75/src/dnssec.c @@ -72,7 +72,7 @@ } /* Find pointer to correct hash function in nettle library */ -static const struct nettle_hash *hash_find(char *name) +const struct nettle_hash *hash_find(char *name) { int i; @@ -89,7 +89,7 @@ } /* expand ctx and digest memory allocations if necessary and init hash function */ -static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) +int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) { static void *ctx = NULL; static unsigned char *digest = NULL; @@ -391,15 +391,17 @@ static int count_labels(char *name) { int i; - + char *p; + if (*name == 0) return 0; - for (i = 0; *name; name++) - if (*name == '.') + for (p = name, i = 0; *p; p++) + if (*p == '.') i++; - return i+1; + /* Don't count empty first label. */ + return *name == '.' ? i : i+1; } /* Implement RFC1982 wrapped compare for 32-bit numbers */ @@ -549,46 +551,81 @@ 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, - unsigned char **p, u16 **desc) -{ - int d = **desc; - - /* No more data needs mangling */ - if (d == (u16)-1) - { - /* If there's more data than we have space for, just return what fits, - we'll get called again for more chunks */ - if (end - *p > bufflen) - { - memcpy(buff, *p, bufflen); - *p += bufflen; - return bufflen; - } - - return 0; +/* Return bytes of canonicalised rrdata one by one. + Init state->ip with the RR, and state->end with the end of same. + Init state->op to NULL. + Init state->desc to RR descriptor. + Init state->buff with a MAXDNAME * 2 buffer. + + After each call which returns 1, state->op points to the next byte of data. + On returning 0, the end has been reached. +*/ +struct rdata_state { + u16 *desc; + size_t c; + unsigned char *end, *ip, *op; + char *buff; +}; + +static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) +{ + int d; + + if (state->op && state->c != 1) + { + state->op++; + state->c--; + return 1; } - (*desc)++; - - if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) - /* domain-name, canonicalise */ - return to_wire(buff); - else - { - /* plain data preceding a domain-name, don't run off the end of the data */ - if ((end - *p) < d) - d = end - *p; + + while (1) + { + d = *(state->desc); - if (d != 0) + if (d == (u16)-1) + { + /* all the bytes to the end. */ + if ((state->c = state->end - state->ip) != 0) + { + state->op = state->ip; + state->ip = state->end;; + } + else + return 0; + } + else { - memcpy(buff, *p, d); - *p += d; + state->desc++; + + if (d == (u16)0) + { + /* domain-name, canonicalise */ + int len; + + if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || + (len = to_wire(state->buff)) == 0) + continue; + + state->c = len; + state->op = (unsigned char *)state->buff; + } + else + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((state->end - state->ip) < d) + d = state->end - state->ip; + + if (d == 0) + continue; + + state->op = state->ip; + state->c = d; + state->ip += d; + } } - return d; + return 1; } } @@ -620,90 +657,75 @@ 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. - The domain-names may have different lengths in each RR, but sort equal - - ------------ - |abcde|fghi| - ------------ - |abcd|efghi| - ------------ - - leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. -*/ +/* Bubble sort the RRset into the canonical order. */ -static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, - unsigned char **rrset, char *buff1, char *buff2) +static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) { - int swap, quit, i; + int swap, i, j; do { for (swap = 0, i = 0; i < rrsetidx-1; i++) { - int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; - u16 *dp1, *dp2; - unsigned char *end1, *end2; + int rdlen1, rdlen2; + struct rdata_state state1, state2; + /* Note that these have been determined to be OK previously, so we don't need to check for NULL return here. */ - unsigned char *p1 = skip_name(rrset[i], header, plen, 10); - unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); - - p1 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen1, p1); - end1 = p1 + rdlen1; - - p2 += 8; /* skip class, type, ttl */ - GETSHORT(rdlen2, p2); - end2 = p2 + rdlen2; - - dp1 = dp2 = rr_desc; - - for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) + state1.ip = skip_name(rrset[i], header, plen, 10); + state2.ip = skip_name(rrset[i+1], header, plen, 10); + state1.op = state2.op = NULL; + state1.buff = buff1; + state2.buff = buff2; + state1.desc = state2.desc = rr_desc; + + state1.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, state1.ip); + if (!CHECK_LEN(header, state1.ip, plen, rdlen1)) + return rrsetidx; /* short packet */ + state1.end = state1.ip + rdlen1; + + state2.ip += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, state2.ip); + if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) + return rrsetidx; /* short packet */ + state2.end = state2.ip + rdlen2; + + while (1) { - if (left1 != 0) - memmove(buff1, buff1 + len1 - left1, left1); - - if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) - { - quit = 1; - len1 = end1 - p1; - memcpy(buff1 + left1, p1, len1); - } - len1 += left1; + int ok1, ok2; - if (left2 != 0) - memmove(buff2, buff2 + len2 - left2, left2); - - if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) + ok1 = get_rdata(header, plen, &state1); + ok2 = get_rdata(header, plen, &state2); + + if (!ok1 && !ok2) { - quit = 1; - len2 = end2 - p2; - memcpy(buff2 + left2, p2, len2); + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; + break; } - len2 += left2; - - if (len1 > len2) - left1 = len1 - len2, left2 = 0, len = len2; - else - left2 = len2 - len1, left1 = 0, len = len1; - - rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); - - if (rc > 0 || (rc == 0 && quit && len1 > len2)) + else if (ok1 && (!ok2 || *state1.op > *state2.op)) { unsigned char *tmp = rrset[i+1]; rrset[i+1] = rrset[i]; rrset[i] = tmp; - swap = quit = 1; + swap = 1; + break; } - else if (rc < 0) - quit = 1; + else if (ok2 && (!ok1 || *state2.op > *state1.op)) + break; + + /* arrive here when bytes are equal, go round the loop again + and compare the next ones. */ } } } while (swap); + + return rrsetidx; } /* Validate a single RRset (class, type, name) in the supplied DNS reply @@ -808,7 +830,7 @@ /* 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. */ - sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); + rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); /* Now try all the sigs to try and find one which validates */ for (j = 0; j update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); from_wire(keyname); + +#define RRBUFLEN 300 /* Most RRs are smaller than this. */ for (i = 0; i < rrsetidx; ++i) { - int seg; - unsigned char *end, *cp; - u16 len, *dp; + int j; + struct rdata_state state; + u16 len; + unsigned char rrbuf[RRBUFLEN]; p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_BOGUS; @@ -895,12 +921,11 @@ /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ if (labels < name_labels) { - int k; - for (k = name_labels - labels; k != 0; k--) + for (j = name_labels - labels; j != 0; j--) { while (*name_start != '.' && *name_start != 0) name_start++; - if (k != 1 && *name_start == '.') + if (j != 1 && *name_start == '.') name_start++; } @@ -921,24 +946,44 @@ if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; - end = p + rdlen; - - /* canonicalise rdata and calculate length of same, use name buffer as workspace. - Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ - cp = p; - dp = rr_desc; - for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); - len += end - cp; - len = htons(len); + /* canonicalise rdata and calculate length of same, use + name buffer as workspace for get_rdata. */ + state.ip = p; + state.op = NULL; + state.desc = rr_desc; + state.buff = name; + state.end = p + rdlen; + + for (j = 0; get_rdata(header, plen, &state); j++) + if (j < RRBUFLEN) + rrbuf[j] = *state.op; + + len = htons((u16)j); hash->update(ctx, 2, (unsigned char *)&len); + + /* If the RR is shorter than RRBUFLEN (most of them, in practice) + then we can just digest it now. If it exceeds RRBUFLEN we have to + go back to the start and do it in chunks. */ + if (j >= RRBUFLEN) + { + state.ip = p; + state.op = NULL; + state.desc = rr_desc; + + for (j = 0; get_rdata(header, plen, &state); j++) + { + rrbuf[j] = *state.op; + + if (j == RRBUFLEN - 1) + { + hash->update(ctx, RRBUFLEN, rrbuf); + j = -1; + } + } + } - /* Now canonicalise again and digest. */ - cp = p; - dp = rr_desc; - while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) - hash->update(ctx, seg, (unsigned char *)name); - if (cp != end) - hash->update(ctx, end - cp, cp); + if (j != 0) + hash->update(ctx, j, rrbuf); } hash->digest(ctx, hash->digest_size, digest); @@ -1382,24 +1427,26 @@ /* Find all the NSEC or NSEC3 records in a reply. return an array of pointers to them. */ -static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd) +static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd, unsigned char ***labels) { - static unsigned char **nsecset = NULL; - static int nsecset_sz = 0; + static unsigned char **nsecset = NULL, **rrsig_labels = NULL; + static int nsecset_sz = 0, rrsig_labels_sz = 0; int type_found = 0; - unsigned char *p = skip_questions(header, plen); + unsigned char *auth_start, *p = skip_questions(header, plen); int type, class, rdlen, i, nsecs_found; /* Move to NS section */ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) return 0; + + auth_start = p; for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) { unsigned char *pstart = p; - if (!(p = skip_name(p, header, plen, 10))) + if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) return 0; GETSHORT(type, p); @@ -1420,7 +1467,69 @@ if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) return 0; - nsecset[nsecs_found++] = pstart; + if (type == T_NSEC) + { + /* If we're looking for NSECs, find the corresponding SIGs, to + extract the labels value, which we need in case the NSECs + are the result of wildcard expansion. + Note that the NSEC may not have been validated yet + so if there are multiple SIGs, make sure the label value + is the same in all, to avoid be duped by a rogue one. + If there are no SIGs, that's an error */ + unsigned char *p1 = auth_start; + int res, j, rdlen1, type1, class1; + + if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) + return 0; + + rrsig_labels[nsecs_found] = NULL; + + for (j = ntohs(header->nscount); j != 0; j--) + { + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) + return 0; + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + if (!CHECK_LEN(header, p1, plen, rdlen1)) + return 0; + + if (res == 1 && class1 == class_reqd && type1 == T_RRSIG) + { + int type_covered; + unsigned char *psav = p1; + + if (rdlen1 < 18) + return 0; /* bad packet */ + + GETSHORT(type_covered, p1); + + if (type_covered == T_NSEC) + { + p1++; /* algo */ + + /* labels field must be the same in every SIG we find. */ + if (!rrsig_labels[nsecs_found]) + rrsig_labels[nsecs_found] = p1; + else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ + return 0; + } + p1 = psav; + } + + if (!ADD_RDLEN(header, p1, plen, rdlen1)) + return 0; + } + + /* Must have found at least one sig. */ + if (!rrsig_labels[nsecs_found]) + return 0; + } + + nsecset[nsecs_found++] = pstart; } if (!ADD_RDLEN(header, p, plen, rdlen)) @@ -1429,12 +1538,13 @@ *nsecsetp = nsecset; *nsecsetl = nsecs_found; - + *labels = rrsig_labels; + return type_found; } -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type, int *nons) +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, + char *workspace1_in, char *workspace2, char *name, int type, int *nons) { int i, rc, rdlen; unsigned char *p, *psave; @@ -1447,6 +1557,9 @@ /* Find NSEC record that proves name doesn't exist */ for (i = 0; i < nsec_count; i++) { + char *workspace1 = workspace1_in; + int sig_labels, name_labels; + p = nsecs[i]; if (!extract_name(header, plen, &p, workspace1, 1, 10)) return STAT_BOGUS; @@ -1455,7 +1568,27 @@ psave = p; if (!extract_name(header, plen, &p, workspace2, 1, 10)) return STAT_BOGUS; - + + /* If NSEC comes from wildcard expansion, use original wildcard + as name for computation. */ + sig_labels = *labels[i]; + name_labels = count_labels(workspace1); + + if (sig_labels < name_labels) + { + int k; + for (k = name_labels - sig_labels; k != 0; k--) + { + while (*workspace1 != '.' && *workspace1 != 0) + workspace1++; + if (k != 1 && *workspace1 == '.') + workspace1++; + } + + workspace1--; + *workspace1 = '*'; + } + rc = hostname_cmp(workspace1, name); if (rc == 0) @@ -1809,7 +1942,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons) { - unsigned char *ans_start, *qname, *p1, *p2, **nsecs; + unsigned char *ans_start, *qname, *p1, *p2, **nsecs, **rrsig_labels = NULL; 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; @@ -1939,11 +2072,11 @@ 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))) + if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1, &rrsig_labels))) 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); + rc = prove_non_existence_nsec(header, plen, nsecs, rrsig_labels, 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); @@ -2079,7 +2212,7 @@ /* 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))) + if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass, &rrsig_labels))) { /* 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 @@ -2097,7 +2230,7 @@ return STAT_BOGUS; if (nsec_type == T_NSEC) - return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons); + return prove_non_existence_nsec(header, plen, nsecs, rrsig_labels, 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); } @@ -2110,7 +2243,7 @@ */ 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); + unsigned char *p = (unsigned char *)(header+1), **rrsig_labels = NULL; int type, class, qclass, rdlen, j, rc; int cname_count = CNAME_CHAIN; char *wildname; @@ -2158,7 +2291,7 @@ 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))) + if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class, &rrsig_labels))) return STAT_BOGUS; /* No NSECs or bad packet */ /* Note that we're called here because something didn't validate in validate_reply, @@ -2182,7 +2315,7 @@ } if (nsec_type == T_NSEC) - rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL); + rc = prove_non_existence_nsec(header, plen, nsecs, rrsig_labels, 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); @@ -2264,7 +2397,7 @@ p = (unsigned char *)(header+1); - p = do_rfc1035_name(p, name); + p = do_rfc1035_name(p, name, NULL); *p++ = 0; PUTSHORT(type, p); PUTSHORT(class, p); @@ -2510,35 +2643,4 @@ return plen; } -unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) -{ - int q; - unsigned int len; - unsigned char *p = (unsigned char *)(header+1); - const struct nettle_hash *hash; - void *ctx; - unsigned char *digest; - - if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) - return NULL; - - for (q = ntohs(header->qdcount); q != 0; q--) - { - if (!extract_name(header, plen, &p, name, 1, 4)) - break; /* bad packet */ - - len = to_wire(name); - hash->update(ctx, len, (unsigned char *)name); - /* CRC the class and type as well */ - hash->update(ctx, 4, p); - - p += 4; - if (!CHECK_LEN(header, p, plen, 0)) - break; /* bad packet */ - } - - hash->digest(ctx, hash->digest_size, digest); - return digest; -} - #endif /* HAVE_DNSSEC */ diff -u dnsmasq-2.75/src/forward.c dnsmasq-2.75/src/forward.c --- dnsmasq-2.75/src/forward.c +++ dnsmasq-2.75/src/forward.c @@ -22,6 +22,15 @@ 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, @@ -444,6 +453,7 @@ { struct server *firstsentto = start; int forwarded = 0; + size_t edns0_len; /* If a query is retried, use the log_id for the retry when logging the answer. */ @@ -872,141 +882,236 @@ #ifdef HAVE_DNSSEC if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { - int status = 0; + int status; /* We've had a reply already, which we're validating. Ignore this duplicate */ if (forward->blocking_query) return; - - /* Truncated answer can't be validated. + if (header->hb3 & HB3_TC) + { + /* 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. */ - if (header->hb3 & HB3_TC) - status = STAT_TRUNCATED; - - while (1) + 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) { - /* 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) + 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 (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); + if (option_bool(OPT_DNSSEC_NO_SIGN)) + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); else - status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, - option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL); + status = STAT_INSECURE; } - /* 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) + } + /* 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 { - struct frec *new, *orig; - - /* Free any saved query */ - if (forward->stash) - blockdata_free(forward->stash); + 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->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN); - /* Now save reply pending receipt of key data */ - if (!(forward->stash = blockdata_alloc((char *)header, n))) + 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) + server->edns_pktsz, + 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) + server->edns_pktsz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } + memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), 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))) return; - forward->stash_len = n; + new->stash_len = nn; - /* Find the original query that started it all.... */ - for (orig = forward; orig->dependent; orig = orig->dependent); + /* Don't resend this. */ + daemon->srv_save = NULL; - if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) - status = STAT_ABANDONED; + if (server->sfd) + fd = server->sfd->fd; else { - 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; + fd = -1; #ifdef HAVE_IPV6 - 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) + if (server->addr.sa.sa_family == AF_INET6) { - new->flags |= FREC_DNSKEY_QUERY; - nn = dnssec_generate_query(header, ((char *) header) + server->edns_pktsz, - daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); + if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) + fd = new->rfd6->fd; } - else - { - new->flags |= FREC_DS_QUERY; - nn = dnssec_generate_query(header,((char *) header) + server->edns_pktsz, - 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) { - while (retry_send(sendto(fd, (char *)header, nn, 0, - &server->addr.sa, - sa_len(&server->addr)))); - server->queries++; + if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) + fd = new->rfd4->fd; } - } + } + + if (fd != -1) + { + while (retry_send(sendto(fd, (char *)header, nn, 0, + &server->addr.sa, + sa_len(&server->addr)))); + server->queries++; + } + return; } + } - /* 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. */ + /* 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) + { 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; @@ -1014,7 +1119,7 @@ { char *result, *domain = "result"; - if (status == STAT_ABANDONED) + if (forward->work_counter == 0) { result = "ABANDONED"; status = STAT_BOGUS; @@ -1024,7 +1129,7 @@ if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) domain = daemon->namebuff; - + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); } @@ -1384,49 +1489,315 @@ } #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) + +/* 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) { - /* 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; + 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) { - /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ + 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) - 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); + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; + } - if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) - break; + 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 */ - /* Can't validate because we need a key/DS whose name now in keyname. - Make query for same, and recurse to validate */ - if (!packet) + 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; + + /* 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) + { + new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + if (status == STAT_NEED_DS) { - packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); - payload = &packet[2]; - new_header = (struct dns_header *)payload; - length = (u16 *)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; } + } + 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 (!packet) + if (new_status == STAT_NO_SIG) { - new_status = STAT_ABANDONED; - break; + 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; } - + } + + /* 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); @@ -1436,22 +1807,65 @@ !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 { - new_status = STAT_ABANDONED; - break; + 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; + } } - - m = (c1 << 8) | c2; - new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount); - - if (new_status != STAT_OK) - break; + free(packet); } - - if (packet) - free(packet); - return new_status; } #endif @@ -1729,10 +2143,19 @@ 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_OK, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); + int status = tcp_key_recurse(now, STAT_TRUNCATED, 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 (status == STAT_ABANDONED) + if (keycount == 0) { result = "ABANDONED"; status = STAT_BOGUS; @@ -1815,6 +2238,7 @@ f->dependent = NULL; f->blocking_query = NULL; f->stash = NULL; + f->orig_domain = NULL; #endif daemon->frec_list = f; } @@ -1894,6 +2318,12 @@ 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); @@ -1921,23 +2351,14 @@ target = f; else { -#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 (difftime(now, f->time) >= 4*TIMEOUT) + { + free_frec(f); + target = f; + } + + if (!oldest || difftime(f->time, oldest->time) <= 0) + oldest = f; } if (target) diff -u dnsmasq-2.75/src/rfc1035.c dnsmasq-2.75/src/rfc1035.c --- dnsmasq-2.75/src/rfc1035.c +++ dnsmasq-2.75/src/rfc1035.c @@ -1195,9 +1195,11 @@ struct naptr *naptr; /* Note: the call to cache_find_by_name is intended to find any record which matches - ie A, AAAA, CNAME. */ + 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 */ - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) && + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) && (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) return 1; @@ -1494,13 +1496,7 @@ int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; size_t len; - - if (ntohs(header->ancount) != 0 || - ntohs(header->nscount) != 0 || - ntohs(header->qdcount) == 0 || - OPCODE(header) != QUERY ) - return 0; - + /* Don't return AD set if checking disabled. */ if (header->hb4 & HB4_CD) sec_data = 0; @@ -1509,32 +1505,31 @@ *ad_reqd = header->hb4 & HB4_AD; *do_bit = 0; - /* If there is an additional data section then it will be overwritten by + /* If there is an RFC2671 pseudoheader then it will be overwritten by partial replies, so we have to do a dry run to see if we can answer - the query. */ + the query. We check to see if the do bit is set, if so we always + forward rather than answering from the cache, which doesn't include + security information, unless we're in DNSSEC validation mode. */ + + if (find_pseudoheader(header, qlen, NULL, &pheader, NULL)) + { + unsigned short flags; + + have_pseudoheader = 1; - if (ntohs(header->arcount) != 0) - { - dryrun = 1; + pheader += 4; /* udp size, ext_rcode */ + GETSHORT(flags, pheader); + + if ((sec_reqd = flags & 0x8000)) + *do_bit = 1;/* do bit */ - /* If there's an additional section, there might be an EDNS(0) pseudoheader */ - if (find_pseudoheader(header, qlen, NULL, &pheader, NULL)) - { - unsigned short flags; - - have_pseudoheader = 1; - - pheader += 4; /* udp size, ext_rcode */ - GETSHORT(flags, pheader); - - if ((sec_reqd = flags & 0x8000)) - { - *do_bit = 1;/* do bit */ - *ad_reqd = 1; - } - } + *ad_reqd = 1; + dryrun = 1; } + if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) + return 0; + for (rec = daemon->mxnames; rec; rec = rec->next) rec->offset = 0; @@ -1596,6 +1591,98 @@ } } +#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; @@ -1604,7 +1691,6 @@ 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, ""); @@ -1661,7 +1747,6 @@ if (intr) { - sec_data = 0; ans = 1; if (!dryrun) { @@ -1675,7 +1760,6 @@ else if (ptr) { ans = 1; - sec_data = 0; if (!dryrun) { log_query(F_CONFIG | F_RRNAME, name, NULL, ""); @@ -1690,12 +1774,38 @@ } else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) { - /* Don't use cache when DNSSEC data required, unless we know that - the zone is unsigned, which implies that we're doing - validation. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || - !sec_reqd || - (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) + 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) { do { @@ -1705,19 +1815,19 @@ 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 + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) { + ans = 1; if (!(crecp->flags & (F_HOSTS | F_DHCP))) auth = 0; if (!dryrun) @@ -1737,7 +1847,6 @@ 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); @@ -1754,7 +1863,6 @@ { /* 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, @@ -1802,7 +1910,6 @@ if (i == 4) { ans = 1; - sec_data = 0; if (!dryrun) { addr.addr.addr4.s_addr = htonl(a); @@ -1841,7 +1948,6 @@ continue; #endif ans = 1; - sec_data = 0; if (!dryrun) { gotit = 1; @@ -1881,8 +1987,48 @@ crecp = save; } - /* 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)) + /* 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) do { /* don't answer wildcard queries with data not from /etc/hosts