diff --git a/src/cache.c b/src/cache.c index fbdcae7..126d259 100644 --- a/src/cache.c +++ b/src/cache.c @@ -316,27 +316,71 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign if (flags & F_FORWARD) { for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next) - if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) - { - *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) - { - cache_unlink(crecp); - cache_free(crecp); - } - } - else if ((crecp->flags & F_FORWARD) && - ((flags & crecp->flags & F_TYPE) || ((crecp->flags | flags) & F_CNAME)) && - hostname_isequal(cache_get_name(crecp), name)) - { - if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) - return 0; - *up = crecp->hash_next; - cache_unlink(crecp); - cache_free(crecp); - } - else + { + if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + { + cache_unlink(crecp); + cache_free(crecp); + } + continue; + } + + if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name)) + { + + if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) + { + if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) + return 0; + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + continue; + } + +#ifdef HAVE_DNSSEC + /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also + type-covered sensitive for RRSIG */ + if (flags & crecp->flags & (F_DNSKEY | F_DS)) + { + int del = 0; + switch (flags & (F_DS | F_DNSKEY)) + { + case F_DS: + if (crecp->addr.ds.class == addr->addr.dnssec.class) + del = 1; + break; + + case F_DNSKEY: + if (crecp->addr.key.class == addr->addr.dnssec.class) + del = 1; + break; + + /* Both set -> RRSIG */ + case F_DS | F_DNSKEY: + if (crecp->addr.sig.class == addr->addr.dnssec.class && + crecp->addr.sig.type_covered == addr->addr.dnssec.type) + del = 1; + break; + } + + if (del) + { + if (crecp->flags & F_CONFIG) + return 0; + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + continue; + } + } +#endif + } up = &crecp->hash_next; + } } else { @@ -409,8 +453,8 @@ struct crec *cache_insert(char *name, struct all_addr *addr, if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl) ttl = daemon->max_cache_ttl; - /* Don't log keys */ - if (flags & (F_IPV4 | F_IPV6)) + /* Don't log keys here, done elsewhere */ + if (flags & (F_IPV4 | F_IPV6 | F_CNAME)) log_query(flags | F_UPSTREAM, name, addr, NULL); /* if previous insertion failed give up now. */ @@ -554,6 +598,9 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) { if ((crecp->flags & F_FORWARD) && +#ifdef HAVE_DNSSEC + ((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) && +#endif (crecp->flags & prot) && hostname_isequal(cache_get_name(crecp), name)) { @@ -611,7 +658,10 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (ans && (ans->flags & F_FORWARD) && - (ans->flags & prot) && +#ifdef HAVE_DNSSEC + ((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) && +#endif + (ans->flags & prot) && hostname_isequal(cache_get_name(ans), name)) return ans; @@ -971,7 +1021,9 @@ void cache_reload(void) cache->name.namep = key->name; cache->uid = key->keylen; cache->addr.key.algo = key->algo; + cache->addr.key.flags = key->flags; cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen); + cache->addr.key.class = C_IN; /* TODO - in option? */ cache_hash(cache); } #endif @@ -1228,16 +1280,17 @@ void dump_cache(time_t now) if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) a = cache_get_cname_target(cache); #ifdef HAVE_DNSSEC - else if (cache->flags & F_DNSKEY) - { - a = daemon->addrbuff; - sprintf(a, "%3u %u", cache->addr.key.algo, cache->uid); - } else if (cache->flags & F_DS) + { + a = daemon->addrbuff; + sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, + cache->addr.ds.algo, cache->addr.ds.digest); + } + else if (cache->flags & F_DNSKEY) { a = daemon->addrbuff; sprintf(a, "%5u %3u %3u", cache->addr.key.keytag, - cache->addr.key.algo, cache->addr.key.digest); + cache->addr.key.algo, cache->addr.key.flags); } #endif else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD)) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 2149e72..6507642 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -76,7 +76,6 @@ #define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ #define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ - struct dns_header { u16 id; u8 hb3,hb4; diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 143e500..b60e639 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -244,7 +244,12 @@ struct all_addr { #ifdef HAVE_IPV6 struct in6_addr addr6; #endif + /* for log_query */ unsigned int keytag; + /* for cache_insert if RRSIG, DNSKEY, DS */ + struct { + unsigned short class, type; + } dnssec; } addr; }; @@ -363,13 +368,22 @@ struct crec { } cname; struct { struct blockdata *keydata; + unsigned short class, flags, keytag; unsigned char algo; - unsigned char digest; /* DS only */ - unsigned short keytag; - } key; + } key; + struct { + struct blockdata *keydata; + unsigned short class, keytag; + unsigned char algo; + unsigned char digest; + } ds; + struct { + struct blockdata *keydata; + unsigned short class, type_covered, keytag; + } sig; } addr; time_t ttd; /* time to die */ - /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */ + /* used as keylen if F_DNSKEY or F_DS, index to source for F_HOSTS */ int uid; unsigned short flags; union { @@ -409,7 +423,7 @@ struct crec { #define F_SECSTAT (1u<<24) /* composites */ -#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */ +#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* F_DS & F_DNSKEY -> RRSIG yuck. */ diff --git a/src/dnssec.c b/src/dnssec.c index 0a80c7b..a80df99 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -707,7 +707,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in { /* iterate through all possible keys 4035 5.3.1 */ for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) - if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && + if (crecp->addr.key.algo == algo && + crecp->addr.key.keytag == key_tag && + crecp->addr.key.class == class && verify(crecp->addr.key.keydata, crecp->uid, sig, sig_len, digest, algo)) return STAT_SECURE; } @@ -732,6 +734,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch struct crec *crecp, *recp1; int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; struct blockdata *key; + struct all_addr a; if (ntohs(header->qdcount) != 1 || !extract_name(header, plen, &p, name, 1, 4)) @@ -786,23 +789,34 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class; + /* Put the key into the cache. Note that if the validation fails, we won't call cache_end_insert() and this will never be committed. */ if ((key = blockdata_alloc((char*)p, rdlen - 4)) && - (recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY))) + (recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) { + struct all_addr a; + + a.addr.keytag = keytag; + log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + recp1->uid = rdlen - 4; recp1->addr.key.keydata = key; recp1->addr.key.algo = algo; recp1->addr.key.keytag = keytag; + recp1->addr.key.flags = flags; + recp1->addr.key.class = class; } p = psave; if (!ADD_RDLEN(header, p, plen, rdlen)) return STAT_INSECURE; /* bad packet */ - /* Already determined that message is OK. Just loop stuffing cache */ - if (valid || !key) + /* Already determined that message is OK or failed to store or ineligable + (ie no zone key flag) key. Don't attempt to validate, just loop stuffing cache */ + if (valid || !key || !(flags & 0x100)) continue; for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) @@ -811,10 +825,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch unsigned char *digest, *ds_digest; const struct nettle_hash *hash; - if (recp1->addr.key.algo == algo && - recp1->addr.key.keytag == keytag && - (flags & 0x100) && /* zone key flag */ - (hash = hash_find(ds_digest_name(recp1->addr.key.digest))) && + if (recp1->addr.ds.algo == algo && + recp1->addr.ds.keytag == keytag && + recp1->addr.ds.class == class && + (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && hash_init(hash, &ctx, &digest)) { @@ -833,10 +847,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch memcmp(ds_digest, digest, recp1->uid) == 0 && validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag)) { - struct all_addr a; valid = 1; - a.addr.keytag = keytag; - log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); break; } } @@ -866,10 +877,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) { - unsigned char *psave, *p = (unsigned char *)(header+1); - struct crec *crecp; - int qtype, qclass, val, j; - struct blockdata *key; + unsigned char *p = (unsigned char *)(header+1); + int qtype, qclass, val; if (ntohs(header->qdcount) != 1 || !extract_name(header, plen, &p, name, 1, 4)) @@ -884,68 +893,12 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0) return STAT_INSECURE; - val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0); - + val = dnssec_validate_reply(now, header, plen, name, keyname, NULL); + if (val == STAT_BOGUS) log_query(F_UPSTREAM, name, NULL, "BOGUS DS"); - /* failed to validate or missing key. */ - if (val != STAT_SECURE) - return val; - - cache_start_insert(); - - for (j = ntohs(header->ancount); j != 0; j--) - { - int ttl, rdlen, rc, algo, digest, keytag; - - /* Ensure we have type, class TTL and length */ - if (!(rc = extract_name(header, plen, &p, name, 0, 10))) - return STAT_INSECURE; /* bad packet */ - - GETSHORT(qtype, p); - GETSHORT(qclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - - /* check type, class and name, skip if not in DS rrset */ - if (qclass == class && qtype == T_DS && rc == 1) - { - if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) - return STAT_INSECURE; /* bad packet */ - - psave = p; - GETSHORT(keytag, p); - algo = *p++; - digest = *p++; - - /* We've proved that the DS is OK, store it in the cache */ - if ((key = blockdata_alloc((char*)p, rdlen - 4)) && - (crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS))) - { - struct all_addr a; - a.addr.keytag = keytag; - log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); - crecp->addr.key.digest = digest; - crecp->addr.key.keydata = key; - crecp->addr.key.algo = algo; - crecp->addr.key.keytag = keytag; - crecp->uid = rdlen - 4; - } - else - return STAT_INSECURE; /* cache problem */ - - p = psave; - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_INSECURE; /* bad packet */ - - } - - cache_end_insert(); - - return STAT_SECURE; + return val; } /* 4034 6.1 */ @@ -1014,6 +967,7 @@ static int hostname_cmp(const char *a, const char *b) /* 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 */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class) { unsigned char *ans_start, *p1, *p2; @@ -1058,10 +1012,68 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch } /* Not done, validate now */ - if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE) + if (j == i) { - *class = class1; /* Class for DS or DNSKEY */ - return rc; + if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + return rc; + } + + /* If we just validated a DS RRset, cache it */ + if (type1 == T_DS) + { + int ttl, keytag, algo, digest; + unsigned char *psave; + struct all_addr a; + struct blockdata *key; + struct crec *crecp; + + cache_start_insert(); + + for (p2 = ans_start, j = 0; j < ntohs(header->ancount) + ntohs(header->nscount); j++) + { + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(type2, p2); + GETSHORT(class2, p2); + GETLONG(ttl, p2); + GETSHORT(rdlen2, p2); + + if (type2 == T_DS && class2 == class1 && rc == 1) + { + psave = p2; + 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)) && + (crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + { + a.addr.keytag = keytag; + log_query(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.class = class2; + crecp->uid = rdlen2 - 4; + } + + p2 = psave; + } + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_INSECURE; /* bad packet */ + } + + cache_end_insert(); + } } } @@ -1079,6 +1091,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(type1, p1); GETSHORT(class1, p1); + /* Can't validate RRSIG query */ + if (type1 == T_RRSIG) + return STAT_INSECURE; + cname_loop: for (j = ntohs(header->ancount); j != 0; j--) { diff --git a/src/rfc1035.c b/src/rfc1035.c index 6827544..302fadd 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1368,6 +1368,11 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int p += INADDRSZ; break; + case 'b': + usval = va_arg(ap, int); + *p++ = usval; + break; + case 's': usval = va_arg(ap, int); PUTSHORT(usval, p); @@ -1538,6 +1543,58 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } +#ifdef HAVE_DNSSEC + if ((qtype == T_DNSKEY || qtype == T_ANY) && (crecp = cache_find_by_name(NULL, name, now, F_DNSKEY))) + { + do { + char *keydata; + + if (crecp->addr.ds.class == qclass && + (qtype == T_DNSKEY || (crecp->flags & F_CONFIG)) && + (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->uid, NULL))) + { + ans = 1; + if (!dryrun) + { + 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->uid, keydata)) + anscount++; + } + } + } while (crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)); + } + + if ((qtype == T_DS || qtype == T_ANY) && (crecp = cache_find_by_name(NULL, name, now, F_DS))) + { + do { + char *keydata; + + if (crecp->addr.ds.class == qclass && + (qtype == T_DS || (crecp->flags & F_CONFIG)) && + (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->uid, NULL))) + { + ans = 1; + if (!dryrun) + { + 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->uid, keydata)) + anscount++; + } + } + } while (crecp = cache_find_by_name(crecp, name, now, F_DS)); + } +#endif + if (qclass == C_IN) { struct txt_record *t; @@ -1901,7 +1958,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } } - + if (qtype == T_MX || qtype == T_ANY) { int found = 0;