summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2014-02-28 18:10:55 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2014-02-28 18:10:55 +0000
commit00a5b5d477568789a4caa2580200f13488fb5d0a (patch)
tree71e57d1ee50ba3de02d894e50edaeaa9a4300119
parentb8eac1917737c51cb640fb370d3ed3b2177f1cc7 (diff)
downloaddnsmasq-00a5b5d477568789a4caa2580200f13488fb5d0a.tar.gz
Check that unsigned replies come from unsigned zones if --dnssec-check-unsigned set.
-rw-r--r--man/dnsmasq.89
-rw-r--r--src/dnsmasq.h11
-rw-r--r--src/dnssec.c276
-rw-r--r--src/forward.c278
-rw-r--r--src/option.c3
-rw-r--r--src/rfc1035.c2
6 files changed, 469 insertions, 110 deletions
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index ed71ebe..7bf1db7 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -608,6 +608,15 @@ key(s) of the root zone,
but trust anchors for limited domains are also possible. The current
root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml
.TP
+.B --dnssec-check-unsigned
+As a default, dnsmasq does not check that unsigned DNS replies are
+legitimate: they are assumed to be valid and passed on (without the
+"authentic data" bit set, of course). This does not protect against an
+attacker forging unsigned replies for signed DNS zones, but it is
+fast. If this flag is set, dnsmasq will check the zones of unsigned
+replies, to ensure that unsigned replies are allowed in those
+zones. The cost of this is more upstream queries and slower performance.
+.TP
.B --proxy-dnssec
Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an
alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index f984f3b..a00d95c 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -232,7 +232,8 @@ struct event_desc {
#define OPT_DNSSEC_VALID 45
#define OPT_DNSSEC_PERMISS 46
#define OPT_DNSSEC_DEBUG 47
-#define OPT_LAST 48
+#define OPT_DNSSEC_NO_SIGN 48
+#define OPT_LAST 49
/* extra flags for my_syslog, we use a couple of facilities since they are known
not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -535,6 +536,10 @@ struct hostsfile {
#define STAT_NEED_KEY 5
#define STAT_TRUNCATED 6
#define STAT_SECURE_WILDCARD 7
+#define STAT_NO_SIG 8
+#define STAT_NO_DS 9
+#define STAT_NEED_DS_NEG 10
+#define STAT_CHASE_CNAME 11
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -544,6 +549,7 @@ struct hostsfile {
#define FREC_AD_QUESTION 32
#define FREC_DO_QUESTION 64
#define FREC_ADDED_PHEADER 128
+#define FREC_CHECK_NOSIGN 256
#ifdef HAVE_DNSSEC
#define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1085,7 +1091,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
int 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 dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer);
+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);
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
diff --git a/src/dnssec.c b/src/dnssec.c
index 1a1c0e4..3592691 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -496,6 +496,8 @@ static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
*wkspc = p;
*sz = new_sz;
+
+ return 1;
}
/* Bubble sort the RRset into the canonical order.
@@ -588,6 +590,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+ STAT_NO_SIG no RRsigs found.
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
@@ -670,9 +673,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
return STAT_INSECURE;
}
- /* RRset empty, no RRSIGs */
- if (rrsetidx == 0 || sigidx == 0)
+ /* RRset empty */
+ if (rrsetidx == 0)
return STAT_INSECURE;
+
+ /* no RRSIGs */
+ if (sigidx == 0)
+ return STAT_NO_SIG;
/* Sort RRset records into canonical order.
Note that at this point keyname and daemon->workspacename buffs are
@@ -1058,6 +1065,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
return codes:
STAT_INSECURE bad packet, no DS in reply, proven no DS in reply.
STAT_SECURE At least one valid DS found and in cache.
+ STAT_NO_DS It's proved there's no DS here.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
*/
@@ -1065,7 +1073,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p = (unsigned char *)(header+1);
- int qtype, qclass, val, i;
+ int qtype, qclass, val, i, neganswer;
if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4)))
@@ -1077,25 +1085,36 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (qtype != T_DS || qclass != class)
val = STAT_BOGUS;
else
- val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
+ val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);
+
+ if (val == STAT_NO_SIG)
+ val = STAT_INSECURE;
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)))
+ return STAT_INSECURE;
+
if (val == STAT_BOGUS)
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
- /* proved that no DS exists, cache neg answer, can't validate */
- if (val == STAT_SECURE && ntohs(header->ancount) == 0)
+ if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)
{
- int rdlen, rc;
+ int rdlen, flags = F_FORWARD | F_DS | F_NEG ;
unsigned long ttl, minttl = ULONG_MAX;
struct all_addr a;
+
+ if (RCODE(header) == NXDOMAIN)
+ flags |= F_NXDOMAIN;
+
+ if (val == STAT_SECURE)
+ flags |= F_DNSSECOK;
for (i = ntohs(header->nscount); i != 0; i--)
{
- if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ if (!(p = skip_name(p, header, plen, 0)))
return STAT_INSECURE;
GETSHORT(qtype, p);
@@ -1103,10 +1122,10 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETLONG(ttl, p);
GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
-
- if (qclass != class || qtype != T_SOA || rc ==2)
+
+ if (qclass != class || qtype != T_SOA)
{
p += rdlen;
continue;
@@ -1126,16 +1145,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETLONG(ttl, p); /* minTTL */
if (ttl < minttl)
minttl = ttl;
+
+ break;
}
- cache_start_insert();
-
- a.addr.dnssec.class = class;
- cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK | F_NEG | (RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0));
-
- cache_end_insert();
+ if (i != 0)
+ {
+ cache_start_insert();
+
+ a.addr.dnssec.class = class;
+ cache_insert(name, &a, now, ttl, flags);
+
+ cache_end_insert();
+ }
- return STAT_INSECURE;
+ return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE;
}
return val;
@@ -1624,22 +1648,76 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* 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)
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)
{
- unsigned char *ans_start, *p1, *p2, **nsecs;
- int type1, class1, rdlen1, type2, class2, rdlen2;
+ 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 = 10;
- int nsec_type = 0;
+ int nsec_type = 0, have_answer = 0;
+ if (neganswer)
+ *neganswer = 0;
+
if (RCODE(header) == SERVFAIL)
return STAT_BOGUS;
if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
return STAT_INSECURE;
+
+ qname = p1 = (unsigned char *)(header+1);
- if (!(ans_start = skip_questions(header, plen)))
+ if (!extract_name(header, plen, &p1, name, 1, 4))
+ return STAT_INSECURE;
+
+ GETSHORT(qtype, p1);
+ GETSHORT(qclass, p1);
+ ans_start = p1;
+
+ /* Can't validate an RRISG 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_INSECURE; /* 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_INSECURE;
+
+ p1 = ans_start;
+ goto cname_loop;
+ }
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen2))
+ return STAT_INSECURE;
+ }
+ if (neganswer && !have_answer)
+ *neganswer = 1;
+
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
if (!extract_name(header, plen, &p1, name, 1, 10))
@@ -1812,70 +1890,98 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
}
/* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
-
- p1 = (unsigned char *)(header+1);
-
- if (!extract_name(header, plen, &p1, name, 1, 4))
- return STAT_INSECURE;
-
- 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--)
- {
- if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
- return STAT_INSECURE; /* bad packet */
-
- GETSHORT(type2, p1);
- GETSHORT(class2, p1);
- p1 += 4; /* TTL */
- GETSHORT(rdlen2, p1);
-
- if (rc == 1 && class1 == class2)
- {
- /* Do we have an answer for the question? */
- if (type1 == type2)
- return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
- else if (type2 == T_CNAME)
- {
- /* looped CNAMES */
- if (!cname_count-- ||
- !extract_name(header, plen, &p1, name, 1, 0) ||
- !(p1 = skip_questions(header, plen)))
- return STAT_INSECURE;
-
- goto cname_loop;
- }
- }
-
- if (!ADD_RDLEN(header, p1, plen, rdlen2))
- return STAT_INSECURE;
- }
-
+ if (have_answer)
+ return STAT_SECURE;
+
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
-
/* First marshall the NSEC records, if we've not done it previously */
if (!nsec_type)
{
- nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
+ nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass);
if (nsec_type == 0)
return STAT_INSECURE; /* Bad packet */
if (nsec_type == -1)
return STAT_BOGUS; /* No NSECs */
}
+
+ /* Get name of missing answer */
+ if (!extract_name(header, plen, &qname, name, 1, 0))
+ return STAT_INSECURE;
if (nsec_type == T_NSEC)
- return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+ return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
else
- return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+ return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
}
+/* 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, qtype, qclass, rdlen, j, rc;
+ int cname_count = 10;
+
+ /* Get question */
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ return STAT_BOGUS;
+
+ GETSHORT(qtype, p);
+ 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, NULL, 0, 0, 0);
+ 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;
+ }
+}
+
+
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
{
@@ -1951,7 +2057,8 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
if (label_type == 0xc0)
{
/* pointer for compression. */
- unsigned int offset, i;
+ unsigned int offset;
+ int i;
unsigned char *p;
if (!CHECK_LEN(header, ansp, plen, 2))
@@ -1971,7 +2078,7 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
/* does the pointer end up in an elided RR? */
if (i & 1)
- return -1;
+ return 0;
/* No, scale the pointer */
if (fixup)
@@ -2023,27 +2130,26 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
{
int i, type, class, rdlen;
+ unsigned char *pp;
for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
- if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
- {
- if (!check_name(&p, header, plen, fixup, rrs, rr_count))
- return 0;
- }
- else
- {
- if (!(p = skip_name(p, header, plen, 10)))
- return 0;
- }
+ pp = p;
+
+ if (!(p = skip_name(p, header, plen, 10)))
+ return 0;
GETSHORT(type, p);
GETSHORT(class, p);
p += 4; /* TTL */
GETSHORT(rdlen, p);
-
+
if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
{
+ /* fixup name of RR */
+ if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+ return 0;
+
if (class == C_IN)
{
u16 *d;
diff --git a/src/forward.c b/src/forward.c
index c05efe4..7916716 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -24,6 +24,14 @@ static unsigned short get_id(void);
static void free_frec(struct frec *f);
static struct randfd *allocate_rfd(int family);
+#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(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+static int send_check_sign(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,
@@ -250,6 +258,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+ (void)do_bit;
+
/* may be no servers available. */
if (!daemon->servers)
forward = NULL;
@@ -522,6 +532,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
size_t plen;
(void)ad_reqd;
+ (void) do_bit;
#ifdef HAVE_IPSET
/* Similar algorithm to search_servers. */
@@ -793,13 +804,27 @@ void reply_query(int fd, int family, time_t now)
else if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ {
+ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ if (status == STAT_NO_DS)
+ status = STAT_INSECURE;
+ }
+ else if (forward->flags & FREC_CHECK_NOSIGN)
+ status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
- status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
-
+ {
+ status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
+ if (status == STAT_NO_SIG)
+ {
+ if (option_bool(OPT_DNSSEC_NO_SIGN))
+ status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
+ else
+ 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)
+ if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
{
struct frec *new, *orig;
@@ -829,7 +854,7 @@ void reply_query(int fd, int family, time_t now)
#ifdef HAVE_IPV6
new->rfd6 = NULL;
#endif
- new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
@@ -842,7 +867,10 @@ void reply_query(int fd, int family, time_t now)
}
else
{
- new->flags |= FREC_DS_QUERY;
+ if (status == STAT_NEED_DS_NEG)
+ new->flags |= FREC_CHECK_NOSIGN;
+ else
+ new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
daemon->keyname, forward->class, T_DS, &server->addr);
}
@@ -906,11 +934,26 @@ void reply_query(int fd, int family, time_t now)
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);
+ {
+ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ if (status == STAT_NO_DS)
+ status = STAT_INSECURE;
+ }
+ else if (forward->flags & FREC_CHECK_NOSIGN)
+ status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
- status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
-
- if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+ {
+ status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
+ if (status == STAT_NO_SIG)
+ {
+ if (option_bool(OPT_DNSSEC_NO_SIGN))
+ status = send_check_sign(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;
}
}
@@ -1207,6 +1250,164 @@ void receive_query(struct listener *listen, time_t now)
}
#ifdef HAVE_DNSSEC
+
+/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
+ and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or
+ STAT_NEED_DS_NEG and keyname if we need to do the query. */
+static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+ struct crec *crecp;
+ char *name_start = name;
+ int status = dnssec_chase_cname(now, header, plen, name, keyname);
+
+ if (status != STAT_INSECURE)
+ return status;
+
+ while (1)
+ {
+ crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+
+ if (crecp && (crecp->flags & F_DNSSECOK))
+ return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+
+ if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+ {
+ name_start++; /* chop a label off and try again */
+ continue;
+ }
+
+ strcpy(keyname, name_start);
+ return STAT_NEED_DS_NEG;
+ }
+}
+
+/* Got answer to DS query from send_check_sign, check for proven non-existence, or make the next DS query to try. */
+static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+
+{
+ char *name_start;
+ unsigned char *p;
+ int status = dnssec_validate_ds(now, header, plen, name, keyname, class);
+
+ if (status != STAT_INSECURE)
+ {
+ if (status == STAT_NO_DS)
+ status = STAT_INSECURE;
+ return status;
+ }
+
+ p = (unsigned char *)(header+1);
+
+ if (extract_name(header, plen, &p, name, 1, 4) &&
+ (name_start = strchr(name, '.')))
+ {
+ name_start++; /* chop a label off and try again */
+ strcpy(keyname, name_start);
+ return STAT_NEED_DS_NEG;
+ }
+
+ return STAT_BOGUS;
+}
+
+/* Move toward 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;
+ unsigned char *p = (unsigned char *)(header+1);
+ int status;
+ char *name_start = name;
+
+ /* 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;
+
+ while (1)
+ {
+ unsigned char *newhash, hash[HASH_SIZE];
+ unsigned char c1, c2;
+ struct crec *crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+
+ if (--(*keycount) == 0)
+ return STAT_BOGUS;
+
+ if (crecp && (crecp->flags & F_DNSSECOK))
+ {
+ free(packet);
+ return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+ }
+
+ /* If we have cached insecurely that a DS doesn't exist,
+ ise that is a hit for where to start looking for the secure one */
+ if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+ {
+ name_start++; /* chop a label off and try again */
+ continue;
+ }
+
+ m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+
+ /* We rely on the question section coming back unchanged, ensure it is with the hash. */
+ if ((newhash = hash_questions(header, (unsigned int)m, name)))
+ memcpy(hash, newhash, HASH_SIZE);
+
+ *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;
+
+ newhash = hash_questions(header, (unsigned int)m, name);
+ if (newhash && memcmp(hash, newhash, HASH_SIZE) == 0)
+ {
+ /* Note this trashes all three name workspaces */
+ status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
+
+ /* We've found a DS which proves the bit of the DNS where the
+ original query is, is unsigned, so the answer is OK,
+ if unvalidated. */
+ if (status == STAT_NO_DS)
+ {
+ free(packet);
+ return STAT_INSECURE;
+ }
+
+ /* No DS, not got to DNSSEC-land yet, go up. */
+ if (status == STAT_INSECURE)
+ {
+ p = (unsigned char *)(header+1);
+
+ if (extract_name(header, plen, &p, name, 1, 4) &&
+ (name_start = strchr(name, '.')))
+ {
+ name_start++; /* chop a label off and try again */
+ continue;
+ }
+ }
+ }
+ }
+
+ free(packet);
+
+ return STAT_BOGUS;
+ }
+}
+
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)
{
@@ -1219,11 +1420,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
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);
-
+ 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 && new_status == STAT_NO_DS)
+ new_status = STAT_INSECURE;
+ }
+ 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);
+
+ 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;
+ }
+ }
+
/* 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)
@@ -1253,7 +1470,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
{
m = (c1 << 8) | c2;
- if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
+ 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.
@@ -1261,11 +1480,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
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);
-
+ 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 && new_status == STAT_NO_DS)
+ new_status = STAT_INSECURE; /* Validated no DS */
+ }
+ 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);
+
+ 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;
}
@@ -1273,7 +1508,6 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
free(packet);
}
-
return new_status;
}
#endif
diff --git a/src/option.c b/src/option.c
index 2d860d5..b898231 100644
--- a/src/option.c
+++ b/src/option.c
@@ -143,6 +143,7 @@ struct myoption {
#define LOPT_DNSSEC_DEBUG 331
#define LOPT_REV_SERV 332
#define LOPT_SERVERS_FILE 333
+#define LOPT_DNSSEC_CHECK 334
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -283,6 +284,7 @@ static const struct myoption opts[] =
{ "dnssec", 0, 0, LOPT_SEC_VALID },
{ "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
{ "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
+ { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK },
#ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif
@@ -438,6 +440,7 @@ static struct {
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
+ { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
#ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
#endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 77156e4..2fd43e2 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -927,7 +927,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
for (i = ntohs(header->qdcount); i != 0; i--)
{
- int found = 0, cname_count = 5;
+ int found = 0, cname_count = 10;
struct crec *cpp = NULL;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
int secflag = secure ? F_DNSSECOK : 0;