Fix caching logic for validated answers.
The current logic is naive in the case that there is more than
one RRset in an answer (Typically, when a non-CNAME query is answered
by one or more CNAME RRs, and then then an answer RRset.)
If all the RRsets validate, then they are cached and marked as validated,
but if any RRset doesn't validate, then the AD flag is not set (good) and
ALL the RRsets are cached marked as not validated.
This breaks when, eg, the answer contains a validated CNAME, pointing
to a non-validated answer. A subsequent query for the CNAME without do
will get an answer with the AD flag wrongly reset, and worse, the same
query with do will get a cached answer without RRSIGS, rather than
being forwarded.
The code now records the validation of individual RRsets and that
is used to correctly set the "validated" bits in the cache entries.
diff --git a/src/forward.c b/src/forward.c
index 4f3a366..31786f6 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -560,7 +560,8 @@
char **sets = 0;
int munged = 0, is_sign;
size_t plen;
-
+ char *rr_status = NULL;
+
(void)ad_reqd;
(void)do_bit;
(void)bogusanswer;
@@ -650,6 +651,11 @@
server->flags |= SERV_WARNED_RECURSIVE;
}
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ rr_status = daemon->rr_status;
+#endif
+
if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
{
@@ -675,7 +681,7 @@
cache_secure = 0;
}
- if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored, rr_status))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
@@ -859,7 +865,7 @@
(RCODE(header) != REFUSED && RCODE(header) != SERVFAIL))
{
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
-
+
if (option_bool(OPT_NO_REBIND))
check_rebind = !(forward->flags & FREC_NOREBIND);
@@ -899,7 +905,8 @@
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class,
- option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), NULL, NULL);
+ option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
+ NULL, NULL, daemon->rr_status);
}
/* Can't validate, as we're missing key data. Put this
@@ -1483,7 +1490,8 @@
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) && (server->flags & SERV_DO_DNSSEC), NULL, NULL);
+ option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
+ NULL, NULL, daemon->rr_status);
if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
break;