Commit 3a237152 authored by Simon Kelley's avatar Simon Kelley

Commit to allow master merge.

parent 65c9b489
......@@ -81,10 +81,10 @@ int main (int argc, char **argv)
umask(022); /* known umask, create leases and pid files as 0644 */
read_opts(argc, argv, compile_opts);
if (option_bool(OPT_DNSSEC_VALIDATE))
if (option_bool(OPT_DNSSEC_VALID))
if (daemon->doctors) exit(1); /* TODO */
if (daemon->edns_pktsz < PACKETSZ)
daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALIDATE) ? EDNS_PKTSZ : PACKETSZ;
daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ;
daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ?
daemon->edns_pktsz : DNSMASQ_PACKETSZ;
daemon->packet = safe_malloc(daemon->packet_buff_sz);
......@@ -1302,7 +1302,7 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp)
/* will we be able to get memory? */
if (daemon->port != 0)
get_new_frec(now, &wait);
get_new_frec(now, &wait, 0);
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
{
......
......@@ -228,7 +228,7 @@ struct event_desc {
#define OPT_QUIET_DHCP 42
#define OPT_QUIET_DHCP6 43
#define OPT_QUIET_RA 44
#define OPT_DNSSEC_VALIDATE 45
#define OPT_DNSSEC_VALID 45
#define OPT_LAST 46
/* extra flags for my_syslog, we use a couple of facilities since they are known
......@@ -352,7 +352,7 @@ struct crec {
int uid; /* -1 if union is interface-name */
} cname;
struct {
struct keydata *keydata;
struct blockdata *keydata;
unsigned char algo;
unsigned char digest; /* DS only */
unsigned short keytag;
......@@ -499,9 +499,20 @@ struct hostsfile {
int index; /* matches to cache entries for logging */
};
/* DNSSEC status values. */
#define STAT_SECURE 1
#define STAT_INSECURE 2
#define STAT_BOGUS 3
#define STAT_NEED_DS 4
#define STAT_NEED_KEY 5
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
#define FREC_HAS_SUBNET 4
#define FREC_DNSSEC_QUERY 8
#define FREC_DNSKEY_QUERY 16
#define FREC_DS_QUERY 32
struct frec {
union mysockaddr source;
......@@ -516,6 +527,12 @@ struct frec {
int fd, forwardall, flags;
unsigned int crc;
time_t time;
#ifdef HAVE_DNSSEC
struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *blocking_query; /* Query which is blocking us. */
#endif
struct frec *next;
};
......@@ -954,9 +971,9 @@ char *cache_get_name(struct crec *crecp);
char *cache_get_cname_target(struct crec *crecp);
struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC
struct keydata *keydata_alloc(char *data, size_t len);
size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt);
void keydata_free(struct keydata *blocks);
struct blockdata *blockdata_alloc(char *data, size_t len);
size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt);
void blockdata_free(struct blockdata *blocks);
#endif
/* domain.c */
......@@ -992,6 +1009,9 @@ size_t resize_packet(struct dns_header *header, size_t plen,
unsigned char *pheader, size_t hlen);
size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3);
size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source);
#ifdef HAVE_DNSSEC
size_t add_do_bit(struct dns_header *header, size_t plen, char *limit);
#endif
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
int add_resource_record(struct dns_header *header, char *limit, int *truncp,
int nameoffset, unsigned char **pp, unsigned long ttl,
......@@ -1010,7 +1030,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
/* dnssec.c */
int dnssec_validate(struct dns_header *header, size_t plen);
int dnssec_validate(int flags, struct dns_header *header, size_t plen);
/* util.c */
void rand_init(void);
......@@ -1072,7 +1092,7 @@ void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
struct frec *get_new_frec(time_t now, int *wait);
struct frec *get_new_frec(time_t now, int *wait, int force);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, struct all_addr *source,
unsigned int iface);
......
......@@ -713,7 +713,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig
int rdlen, unsigned char *rdata)
{
int flags, proto, alg;
struct keydata *key; struct crec *crecp;
struct blockdata *key; struct crec *crecp;
unsigned char *ordata = rdata; int ordlen = rdlen;
CHECKED_GETSHORT(flags, rdata, rdlen);
......@@ -726,7 +726,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig
if (!(flags & 0x100))
return 0;
key = keydata_alloc((char*)rdata, rdlen);
key = blockdata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
......@@ -741,7 +741,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig
}
else
{
keydata_free(key);
blockdata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DNSKEY: cache insertion failure\n");
......@@ -754,7 +754,7 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign
int rdlen, unsigned char *rdata)
{
int keytag, algo, dig;
struct keydata *key; struct crec *crec_ds, *crec_key;
struct blockdata *key; struct crec *crec_ds, *crec_key;
CHECKED_GETSHORT(keytag, rdata, rdlen);
CHECKED_GETCHAR(algo, rdata, rdlen);
......@@ -763,13 +763,13 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign
if (!digestalg_supported(dig))
return 0;
key = keydata_alloc((char*)rdata, rdlen);
key = blockdata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crec_ds = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DS);
if (!crec_ds)
{
keydata_free(key);
blockdata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DS: cache insertion failure\n");
......@@ -800,7 +800,7 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign
return 0;
}
int dnssec_validate(struct dns_header *header, size_t pktlen)
int dnssec1_validate(struct dns_header *header, size_t pktlen)
{
unsigned char *p, *reply;
char *owner = daemon->namebuff;
......
......@@ -270,7 +270,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
if (!flags && !(forward = get_new_frec(now, NULL)))
if (!flags && !(forward = get_new_frec(now, NULL, 0)))
/* table full - server failure. */
flags = F_NEG;
......@@ -342,6 +342,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
}
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
#endif
while (1)
{
/* only send to servers dealing with our domain.
......@@ -447,12 +452,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
int checking_disabled, int check_subnet, union mysockaddr *query_source)
int no_cache, int cache_secure, int check_subnet, union mysockaddr *query_source)
{
unsigned char *pheader, *sizep;
char **sets = 0;
int munged = 0, is_sign;
size_t plen;
int squash_ad = 0;
#ifdef HAVE_IPSET
/* Similar algorithm to search_servers. */
......@@ -495,11 +501,21 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
}
/* RFC 4035 sect 4.6 para 3 */
if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
header->hb4 &= ~HB4_AD;
squash_ad = 1;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
squash_ad = no_cache;
if (cache_secure)
header->hb4 |= HB4_AD;
#endif
if (squash_ad)
header->hb4 &= ~HB4_AD;
if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN))
return n;
......@@ -513,11 +529,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
server->flags |= SERV_WARNED_RECURSIVE;
}
#ifdef HAVE_DNSSEC
printf("validate\n");
dnssec_validate(header, n);
#endif
if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
{
......@@ -539,7 +550,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NOERROR);
}
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, checking_disabled))
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
......@@ -597,9 +608,7 @@ void reply_query(int fd, int family, time_t now)
n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) ||
!(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff))))
return;
server = forward->sentto;
if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) &&
!option_bool(OPT_ORDER) &&
forward->forwardall == 0)
......@@ -624,6 +633,8 @@ void reply_query(int fd, int family, time_t now)
}
}
}
server = forward->sentto;
if ((forward->sentto->flags & SERV_TYPE) == 0)
{
......@@ -645,7 +656,7 @@ void reply_query(int fd, int family, time_t now)
if (!option_bool(OPT_ALL_SERVERS))
daemon->last_server = server;
}
/* If the answer is an error, keep the forward record in place in case
we get a good reply from another server. Kill it when we've
had replies from all to avoid filling the forwarding table when
......@@ -653,12 +664,106 @@ void reply_query(int fd, int family, time_t now)
if (forward->forwardall == 0 || --forward->forwardall == 1 ||
(RCODE(header) != REFUSED && RCODE(header) != SERVFAIL))
{
int check_rebind = !(forward->flags & FREC_NOREBIND);
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0;
if (option_bool(OPT_NO_REBIND))
check_rebind = !(forward->flags & FREC_NOREBIND);
/* Don't cache replies where DNSSEC validation was turned off, either
the upstream server told us so, or the original query specified it. */
if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
no_cache_dnssec = 1;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{
int status = dnssec_validate(forward->flags, header, n);
/* 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)
{
struct frec *new;
if ((forward->stash = blockdata_alloc((char *)header, n)))
{
forward->stash_len = n;
/* Now formulate a query for the missing data. */
nn = dnssec_generate_query(header, status);
new = get_new_frec(now, NULL, 1);
if (new)
{
int fd;
if (!option_bool(OPT_NO_REBIND))
check_rebind = 0;
new = forward; /* copy everything, then overwrite */
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
new->flags |= FREC_DNSSEC_QUERY;
if (status == STAT_NEED_KEY)
new->flags |= FREC_DNSKEY_QUERY; /* So we verify differently */
else if (status == STAT_NEED_DS)
new->flags |= FREC_DS_QUERY;
new->crc = questions_crc(header, nn, daemon->namebuff);
new->new_id = get_id(new->crc);
/* Don't resend this. */
daemon->srv_save = NULL;
if (server->sfd)
fd = server->sfd->fd;
else
#ifdef HAVE_IPV6
/* Note that we use the same random port for the DNSSEC stuff */
if (server->addr.sa.sa_family == AF_INET6)
{
fd = new->rfd6->fd;
new->rfd6->refcount++;
}
else
#endif
{
fd = new->rfd4->fd;
new->rfd4->refcount++;
}
/* Send DNSSEC query to same server as original query */
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
}
}
return;
}
/* Ok, we reached far enough up the chain-of-trust that we can validate something.
Now wind back down, pulling back answers which wouldn't previously validate
and validate them with the new data. Failure to find needed data here is an internal error.
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */
while (forward->flags & FREC_DNSSEC_QUERY)
{
if (status == STAT_SECURE)
extract_dnssec_replies();
free_frec(forward);
forward = forward->dependent;
blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
if (status == STAT_SECURE)
{
status = dnssec_validate(forward->flags, header, n);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
}
}
if (status == STAT_SECURE)
cache_secure = 1;
/* TODO return SERVFAIL here */
else if (status == STAT_BOGUS)
no_cache_dnssec = 1;
}
#endif
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED,
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
forward->flags & FREC_HAS_SUBNET, &forward->source)))
{
header->id = htons(forward->orig_id);
......@@ -1129,7 +1234,7 @@ unsigned char *tcp_request(int confd, time_t now,
if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff))
m = process_reply(header, now, last_server, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, checking_disabled,
check_subnet, &peer_addr);
0, check_subnet, &peer_addr); /* TODO - cache secure */
break;
}
......@@ -1163,6 +1268,9 @@ static struct frec *allocate_frec(time_t now)
f->flags = 0;
#ifdef HAVE_IPV6
f->rfd6 = NULL;
#endif
#ifdef HAVE_DNSSEC
f->blocking_query = NULL;
#endif
daemon->frec_list = f;
}
......@@ -1221,13 +1329,26 @@ static void free_frec(struct frec *f)
f->rfd6 = NULL;
#endif
#ifdef HAVE_DNSSEC
if (f->stash)
blockdata_free(f->stash);
/* Anything we're waiting on is pointless now, too */
if (f->blocking_query)
free_frec(f->blocking_query);
f->blocking_query = NULL;
#endif
}
/* if wait==NULL return a free or older than TIMEOUT record.
else return *wait zero if one available, or *wait is delay to
when the oldest in-use record will expire. Impose an absolute
limit of 4*TIMEOUT before we wipe things (for random sockets) */
struct frec *get_new_frec(time_t now, int *wait)
limit of 4*TIMEOUT before we wipe things (for random sockets).
If force is set, always return a result, even if we have
to allocate above the limit. */
struct frec *get_new_frec(time_t now, int *wait, int force)
{
struct frec *f, *oldest, *target;
int count;
......@@ -1276,7 +1397,7 @@ struct frec *get_new_frec(time_t now, int *wait)
}
/* none available, calculate time 'till oldest record expires */
if (count > daemon->ftabsize)
if (!force && count > daemon->ftabsize)
{
static time_t last_log = 0;
......
......@@ -427,7 +427,7 @@ static struct {
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
#ifdef HAVE_DNSSEC
{ LOPT_SEC_VALID, OPT_DNSSEC_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
#endif
#ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
......
......@@ -515,7 +515,7 @@ struct macparm {
};
static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
int optno, unsigned char *opt, size_t optlen)
int optno, unsigned char *opt, size_t optlen, int set_do)
{
unsigned char *lenp, *datap, *p;
int rdlen;
......@@ -531,7 +531,8 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
PUTLONG(0, p); /* extended RCODE */
PUTSHORT(0, p); /* extended RCODE and version */
PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
lenp = p;
PUTSHORT(0, p); /* RDLEN */
rdlen = 0;
......@@ -543,7 +544,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
else
{
int i, is_sign;
unsigned short code, len;
unsigned short code, len, flags;
if (ntohs(header->arcount) != 1 ||
!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) ||
......@@ -551,14 +552,24 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
(!(p = skip_name(p, header, plen, 10))))
return plen;
p += 8; /* skip UDP length and RCODE */
p += 6; /* skip UDP length and RCODE */
GETSHORT(flags, p);
if (set_do)
{
p -=2;
PUTSHORT(flags | 0x8000, p);
}
lenp = p;
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return plen; /* bad packet */
datap = p;
/* no option to add */
if (optno == 0)
return plen;
/* check if option already there */
for (i = 0; i + 4 < rdlen; i += len + 4)
{
......@@ -602,7 +613,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
if (!match)
return 1; /* continue */
parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen);
parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0);
return 0; /* done */
}
......@@ -681,9 +692,16 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio
struct subnet_opt opt;
len = calc_subnet_opt(&opt, source);
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len);
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0);
}
#ifdef HAVE_DNSSEC
size_t add_do_bit(struct dns_header *header, size_t plen, char *limit)
{
return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1);
}
#endif
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
{
/* Section 9.2, Check that subnet option in reply matches. */
......@@ -878,7 +896,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
char **ipsets, int is_sign, int check_rebind, int checking_disabled)
char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec)
{
unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
......@@ -1118,15 +1136,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
/* Don't put stuff from a truncated packet into the cache.
Don't cache replies where DNSSEC validation was turned off, either
the upstream server told us so, or the original query specified it.
Don't cache replies from non-recursive nameservers, since we may get a
reply containing a CNAME but not its target, even though the target
does exist. */
if (!(header->hb3 & HB3_TC) &&
!(header->hb4 & HB4_CD) &&
(header->hb4 & HB4_RA) &&
!checking_disabled)
!no_cache_dnssec)
cache_end_insert();
return 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment