Caching of DNSSEC records.
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
#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 @@
} 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 @@
#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 @@
{
/* 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 @@
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 @@
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 @@
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 @@
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_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 @@
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 @@
/* 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 @@
}
/* 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 @@
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 @@
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 @@
}
}
+#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 @@
}
}
}
-
+
if (qtype == T_MX || qtype == T_ANY)
{
int found = 0;