mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
Check that unsigned replies come from unsigned zones if --dnssec-check-unsigned set.
This commit is contained in:
278
src/forward.c
278
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
|
||||
|
||||
Reference in New Issue
Block a user