summaryrefslogtreecommitdiff
path: root/src/rfc1035.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rfc1035.c')
-rw-r--r--src/rfc1035.c886
1 files changed, 247 insertions, 639 deletions
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 56647b0..b078b59 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -36,8 +36,8 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
if ((l = *p++) == 0)
/* end marker */
{
- /* check that there are the correct no of bytes after the name */
- if (!CHECK_LEN(header, p, plen, extrabytes))
+ /* check that there are the correct no. of bytes after the name */
+ if (!CHECK_LEN(header, p1 ? p1 : p, plen, extrabytes))
return 0;
if (isExtract)
@@ -156,7 +156,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp)
memset(addrp, 0, sizeof(struct all_addr));
/* turn name into a series of asciiz strings */
- /* j counts no of labels */
+ /* j counts no. of labels */
for(j = 1,cp1 = name; *namein; cp1++, namein++)
if (*namein == '.')
{
@@ -176,7 +176,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp)
if (hostname_isequal(lastchunk, "arpa") && hostname_isequal(penchunk, "in-addr"))
{
/* IP v4 */
- /* address arives as a name of the form
+ /* address arrives as a name of the form
www.xxx.yyy.zzz.in-addr.arpa
some of the low order address octets might be missing
and should be set to zero. */
@@ -206,7 +206,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp)
Address arrives as 0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.ip6.[int|arpa]
or \[xfedcba9876543210fedcba9876543210/128].ip6.[int|arpa]
- Note that most of these the various reprentations are obsolete and
+ Note that most of these the various representations are obsolete and
left-over from the many DNS-for-IPv6 wars. We support all the formats
that we can since there is no reason not to.
*/
@@ -336,7 +336,7 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h
}
/* CRC the question section. This is used to safely detect query
- retransmision and to detect answers to questions we didn't ask, which
+ retransmission and to detect answers to questions we didn't ask, which
might be poisoning attacks. Note that we decode the name rather
than CRC the raw bytes, since replies might be compressed differently.
We ignore case in the names for the same reason. Return all-ones
@@ -408,333 +408,37 @@ size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *phea
return ansp - (unsigned char *)header;
}
-unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t *len, unsigned char **p, int *is_sign)
-{
- /* See if packet has an RFC2671 pseudoheader, and if so return a pointer to it.
- also return length of pseudoheader in *len and pointer to the UDP size in *p
- Finally, check to see if a packet is signed. If it is we cannot change a single bit before
- forwarding. We look for SIG and TSIG in the addition section, and TKEY queries (for GSS-TSIG) */
-
- int i, arcount = ntohs(header->arcount);
- unsigned char *ansp = (unsigned char *)(header+1);
- unsigned short rdlen, type, class;
- unsigned char *ret = NULL;
-
- if (is_sign)
- {
- *is_sign = 0;
-
- if (OPCODE(header) == QUERY)
- {
- for (i = ntohs(header->qdcount); i != 0; i--)
- {
- if (!(ansp = skip_name(ansp, header, plen, 4)))
- return NULL;
-
- GETSHORT(type, ansp);
- GETSHORT(class, ansp);
-
- if (class == C_IN && type == T_TKEY)
- *is_sign = 1;
- }
- }
- }
- else
- {
- if (!(ansp = skip_questions(header, plen)))
- return NULL;
- }
-
- if (arcount == 0)
- return NULL;
-
- if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount), header, plen)))
- return NULL;
-
- for (i = 0; i < arcount; i++)
- {
- unsigned char *save, *start = ansp;
- if (!(ansp = skip_name(ansp, header, plen, 10)))
- return NULL;
-
- GETSHORT(type, ansp);
- save = ansp;
- GETSHORT(class, ansp);
- ansp += 4; /* TTL */
- GETSHORT(rdlen, ansp);
- if (!ADD_RDLEN(header, ansp, plen, rdlen))
- return NULL;
- if (type == T_OPT)
- {
- if (len)
- *len = ansp - start;
- if (p)
- *p = save;
- ret = start;
- }
- else if (is_sign &&
- i == arcount - 1 &&
- class == C_ANY &&
- type == T_TSIG)
- *is_sign = 1;
- }
-
- return ret;
-}
-
-struct macparm {
- unsigned char *limit;
- struct dns_header *header;
- size_t plen;
- union mysockaddr *l3;
-};
-
-static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
- int optno, unsigned char *opt, size_t optlen, int set_do)
-{
- unsigned char *lenp, *datap, *p;
- int rdlen, is_sign;
-
- if (!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)))
- {
- if (is_sign)
- return plen;
-
- /* We are adding the pseudoheader */
- if (!(p = skip_questions(header, plen)) ||
- !(p = skip_section(p,
- ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount),
- header, plen)))
- return plen;
- *p++ = 0; /* empty name */
- PUTSHORT(T_OPT, p);
- PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
- PUTSHORT(0, p); /* extended RCODE and version */
- PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
- lenp = p;
- PUTSHORT(0, p); /* RDLEN */
- rdlen = 0;
- if (((ssize_t)optlen) > (limit - (p + 4)))
- return plen; /* Too big */
- header->arcount = htons(ntohs(header->arcount) + 1);
- datap = p;
- }
- else
- {
- int i;
- unsigned short code, len, flags;
-
- /* Must be at the end, if exists */
- if (ntohs(header->arcount) != 1 ||
- is_sign ||
- (!(p = skip_name(p, header, plen, 10))))
- return plen;
-
- 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)
- {
- GETSHORT(code, p);
- GETSHORT(len, p);
- if (code == optno)
- return plen;
- p += len;
- }
-
- if (((ssize_t)optlen) > (limit - (p + 4)))
- return plen; /* Too big */
- }
-
- if (optno != 0)
- {
- PUTSHORT(optno, p);
- PUTSHORT(optlen, p);
- memcpy(p, opt, optlen);
- p += optlen;
- }
-
- PUTSHORT(p - datap, lenp);
- return p - (unsigned char *)header;
-
-}
-
-static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
-{
- struct macparm *parm = parmv;
- int match = 0;
-
- if (family == parm->l3->sa.sa_family)
- {
- if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
- match = 1;
-#ifdef HAVE_IPV6
- else
- if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
- match = 1;
-#endif
- }
-
- if (!match)
- return 1; /* continue */
-
- parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0);
-
- return 0; /* done */
-}
-
-size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3)
-{
- struct macparm parm;
-
-/* Must have an existing pseudoheader as the only ar-record,
- or have no ar-records. Must also not be signed */
-
- if (ntohs(header->arcount) > 1)
- return plen;
-
- parm.header = header;
- parm.limit = (unsigned char *)limit;
- parm.plen = plen;
- parm.l3 = l3;
-
- iface_enumerate(AF_UNSPEC, &parm, filter_mac);
-
- return parm.plen;
-}
-
-struct subnet_opt {
- u16 family;
- u8 source_netmask, scope_netmask;
-#ifdef HAVE_IPV6
- u8 addr[IN6ADDRSZ];
-#else
- u8 addr[INADDRSZ];
-#endif
-};
-
-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
-{
- /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
-
- int len;
- void *addrp;
-
-#ifdef HAVE_IPV6
- if (source->sa.sa_family == AF_INET6)
- {
- opt->family = htons(2);
- opt->source_netmask = daemon->addr6_netmask;
- addrp = &source->in6.sin6_addr;
- }
- else
-#endif
- {
- opt->family = htons(1);
- opt->source_netmask = daemon->addr4_netmask;
- addrp = &source->in.sin_addr;
- }
-
- opt->scope_netmask = 0;
- len = 0;
-
- if (opt->source_netmask != 0)
- {
- len = ((opt->source_netmask - 1) >> 3) + 1;
- memcpy(opt->addr, addrp, len);
- if (opt->source_netmask & 7)
- opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
- }
-
- return len + 4;
-}
-
-size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source)
-{
- /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
-
- int len;
- 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, 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. */
-
-
- int len, calc_len;
- struct subnet_opt opt;
- unsigned char *p;
- int code, i, rdlen;
-
- calc_len = calc_subnet_opt(&opt, peer);
-
- if (!(p = skip_name(pseudoheader, header, plen, 10)))
- return 1;
-
- p += 8; /* skip UDP length and RCODE */
-
- GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, plen, rdlen))
- return 1; /* bad packet */
-
- /* check if option there */
- for (i = 0; i + 4 < rdlen; i += len + 4)
- {
- GETSHORT(code, p);
- GETSHORT(len, p);
- if (code == EDNS0_OPTION_CLIENT_SUBNET)
- {
- /* make sure this doesn't mismatch. */
- opt.scope_netmask = p[3];
- if (len != calc_len || memcmp(p, &opt, len) != 0)
- return 0;
- }
- p += len;
- }
-
- return 1;
-}
-
/* is addr in the non-globally-routed IP space? */
int private_net(struct in_addr addr, int ban_localhost)
{
in_addr_t ip_addr = ntohl(addr.s_addr);
return
- (((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost) /* 127.0.0.0/8 (loopback) */ ||
- ((ip_addr & 0xFFFF0000) == 0xC0A80000) /* 192.168.0.0/16 (private) */ ||
+ (((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost) /* 127.0.0.0/8 (loopback) */ ||
+ ((ip_addr & 0xFF000000) == 0x00000000) /* RFC 5735 section 3. "here" network */ ||
((ip_addr & 0xFF000000) == 0x0A000000) /* 10.0.0.0/8 (private) */ ||
((ip_addr & 0xFFF00000) == 0xAC100000) /* 172.16.0.0/12 (private) */ ||
- ((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ;
+ ((ip_addr & 0xFFFF0000) == 0xC0A80000) /* 192.168.0.0/16 (private) */ ||
+ ((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xC0000200) /* 192.0.2.0/24 (test-net) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xC6336400) /* 198.51.100.0/24(test-net) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xCB007100) /* 203.0.113.0/24 (test-net) */ ||
+ ((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF) /* 255.255.255.255/32 (broadcast)*/ ;
}
+#ifdef HAVE_IPV6
+static int private_net6(struct in6_addr *a)
+{
+ return
+ IN6_IS_ADDR_UNSPECIFIED(a) || /* RFC 6303 4.3 */
+ IN6_IS_ADDR_LOOPBACK(a) || /* RFC 6303 4.3 */
+ IN6_IS_ADDR_LINKLOCAL(a) || /* RFC 6303 4.5 */
+ ((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */
+ ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
+}
+#endif
+
+
static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name, int *doctored)
{
int i, qtype, qclass, rdlen;
@@ -794,6 +498,8 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *
{
unsigned int i, len = *p1;
unsigned char *p2 = p1;
+ if ((p1 + len - p) >= rdlen)
+ return 0; /* bad packet */
/* make counted string zero-term and sanitise */
for (i = 0; i < len; i++)
{
@@ -878,7 +584,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc
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 no_cache_dnssec, int secure, int *doctored)
+ char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
+ int secure, int *doctored)
{
unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
@@ -889,6 +596,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
#else
(void)ipsets; /* unused */
#endif
+
cache_start_insert();
@@ -897,10 +605,18 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
searched_soa = 1;
ttl = find_soa(header, qlen, name, doctored);
+
+ if (*doctored)
+ {
+ if (secure)
+ return 0;
#ifdef HAVE_DNSSEC
- if (*doctored && secure)
- return 0;
+ if (option_bool(OPT_DNSSEC_VALID))
+ for (i = 0; i < ntohs(header->ancount); i++)
+ if (daemon->rr_status[i])
+ return 0;
#endif
+ }
}
/* go through the questions. */
@@ -911,7 +627,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
int found = 0, cname_count = CNAME_CHAIN;
struct crec *cpp = NULL;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
- int secflag = secure ? F_DNSSECOK : 0;
+#ifdef HAVE_DNSSEC
+ int cname_short = 0;
+#endif
unsigned long cttl = ULONG_MAX, attl;
namep = p;
@@ -939,8 +657,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (!(p1 = skip_questions(header, qlen)))
return 0;
- for (j = ntohs(header->ancount); j != 0; j--)
+ for (j = 0; j < ntohs(header->ancount); j++)
{
+ int secflag = 0;
unsigned char *tmp = namep;
/* the loop body overwrites the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0) ||
@@ -966,11 +685,24 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0;
-
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j])
+ {
+ /* validated RR anywhere in CNAME chain, don't cache. */
+ if (cname_short || aqtype == T_CNAME)
+ return 0;
+
+ secflag = F_DNSSECOK;
+ }
+#endif
+
if (aqtype == T_CNAME)
{
- if (!cname_count-- || secure)
- return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */
+ if (!cname_count--)
+ return 0; /* looped CNAMES, we can't cache. */
+#ifdef HAVE_DNSSEC
+ cname_short = 1;
+#endif
goto cname_loop;
}
@@ -992,7 +724,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
ttl = find_soa(header, qlen, NULL, doctored);
}
if (ttl)
- cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag);
+ cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0));
}
}
else
@@ -1020,8 +752,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (!(p1 = skip_questions(header, qlen)))
return 0;
- for (j = ntohs(header->ancount); j != 0; j--)
+ for (j = 0; j < ntohs(header->ancount); j++)
{
+ int secflag = 0;
+
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */
@@ -1038,6 +772,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype))
{
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j])
+ secflag = F_DNSSECOK;
+#endif
if (aqtype == T_CNAME)
{
if (!cname_count--)
@@ -1129,7 +867,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
pointing at this, inherit its TTL */
if (ttl || cpp)
{
- newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag);
+ newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp)
{
cpp->addr.cname.target.cache = newc;
@@ -1205,12 +943,12 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
header->nscount = htons(0);
header->arcount = htons(0);
header->ancount = htons(0); /* no answers unless changed below */
- if (flags == F_NEG)
- SET_RCODE(header, SERVFAIL); /* couldn't get memory */
- else if (flags == F_NOERR)
+ if (flags == F_NOERR)
SET_RCODE(header, NOERROR); /* empty domain */
else if (flags == F_NXDOMAIN)
SET_RCODE(header, NXDOMAIN);
+ else if (flags == F_SERVFAIL)
+ SET_RCODE(header, SERVFAIL);
else if (flags == F_IPV4)
{ /* we know the address */
SET_RCODE(header, NOERROR);
@@ -1244,11 +982,9 @@ int check_for_local_domain(char *name, time_t now)
struct naptr *naptr;
/* Note: the call to cache_find_by_name is intended to find any record which matches
- ie A, AAAA, CNAME, DS. Because RRSIG records are marked by setting both F_DS and F_DNSKEY,
- cache_find_by name ordinarily only returns records with an exact match on those bits (ie
- for the call below, only DS records). The F_NSIGMATCH bit changes this behaviour */
+ ie A, AAAA, CNAME. */
- if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) &&
+ if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) &&
(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
return 1;
@@ -1362,6 +1098,7 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bog
return 0;
}
+
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
{
@@ -1371,29 +1108,41 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
unsigned short usval;
long lval;
char *sval;
+
+#define CHECK_LIMIT(size) \
+ if (limit && p + (size) > (unsigned char*)limit) goto truncated;
- if (truncp && *truncp)
- return 0;
-
va_start(ap, format); /* make ap point to 1st unamed argument */
+ if (truncp && *truncp)
+ goto truncated;
+
if (nameoffset > 0)
{
+ CHECK_LIMIT(2);
PUTSHORT(nameoffset | 0xc000, p);
}
else
{
char *name = va_arg(ap, char *);
- if (name)
- p = do_rfc1035_name(p, name);
+ if (name && !(p = do_rfc1035_name(p, name, limit)))
+ goto truncated;
+
if (nameoffset < 0)
{
+ CHECK_LIMIT(2);
PUTSHORT(-nameoffset | 0xc000, p);
}
else
- *p++ = 0;
+ {
+ CHECK_LIMIT(1);
+ *p++ = 0;
+ }
}
+ /* type (2) + class (2) + ttl (4) + rdlen (2) */
+ CHECK_LIMIT(10);
+
PUTSHORT(type, p);
PUTSHORT(class, p);
PUTLONG(ttl, p); /* TTL */
@@ -1406,6 +1155,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
{
#ifdef HAVE_IPV6
case '6':
+ CHECK_LIMIT(IN6ADDRSZ);
sval = va_arg(ap, char *);
memcpy(p, sval, IN6ADDRSZ);
p += IN6ADDRSZ;
@@ -1413,36 +1163,43 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
#endif
case '4':
+ CHECK_LIMIT(INADDRSZ);
sval = va_arg(ap, char *);
memcpy(p, sval, INADDRSZ);
p += INADDRSZ;
break;
case 'b':
+ CHECK_LIMIT(1);
usval = va_arg(ap, int);
*p++ = usval;
break;
case 's':
+ CHECK_LIMIT(2);
usval = va_arg(ap, int);
PUTSHORT(usval, p);
break;
case 'l':
+ CHECK_LIMIT(4);
lval = va_arg(ap, long);
PUTLONG(lval, p);
break;
case 'd':
- /* get domain-name answer arg and store it in RDATA field */
- if (offset)
- *offset = p - (unsigned char *)header;
- p = do_rfc1035_name(p, va_arg(ap, char *));
- *p++ = 0;
+ /* get domain-name answer arg and store it in RDATA field */
+ if (offset)
+ *offset = p - (unsigned char *)header;
+ if (!(p = do_rfc1035_name(p, va_arg(ap, char *), limit)))
+ goto truncated;
+ CHECK_LIMIT(1);
+ *p++ = 0;
break;
case 't':
usval = va_arg(ap, int);
+ CHECK_LIMIT(usval);
sval = va_arg(ap, char *);
if (usval != 0)
memcpy(p, sval, usval);
@@ -1454,6 +1211,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
usval = sval ? strlen(sval) : 0;
if (usval > 255)
usval = 255;
+ CHECK_LIMIT(usval + 1);
*p++ = (unsigned char)usval;
memcpy(p, sval, usval);
p += usval;
@@ -1462,30 +1220,43 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
va_end(ap); /* clean up variable argument pointer */
+ /* Now, store real RDLength. sav already checked against limit. */
j = p - sav - 2;
- PUTSHORT(j, sav); /* Now, store real RDLength */
-
- /* check for overflow of buffer */
- if (limit && ((unsigned char *)limit - p) < 0)
- {
- if (truncp)
- *truncp = 1;
- return 0;
- }
+ PUTSHORT(j, sav);
*pp = p;
return 1;
+
+ truncated:
+ va_end(ap);
+ if (truncp)
+ *truncp = 1;
+ return 0;
+
+#undef CHECK_LIMIT
}
static unsigned long crec_ttl(struct crec *crecp, time_t now)
{
/* Return 0 ttl for DHCP entries, which might change
- before the lease expires. */
+ before the lease expires, unless configured otherwise. */
- if (crecp->flags & (F_IMMORTAL | F_DHCP))
- return daemon->local_ttl;
+ if (crecp->flags & F_DHCP)
+ {
+ int conf_ttl = daemon->use_dhcp_ttl ? daemon->dhcp_ttl : daemon->local_ttl;
+
+ /* Apply ceiling of actual lease length to configured TTL. */
+ if (!(crecp->flags & F_IMMORTAL) && (crecp->ttd - now) < conf_ttl)
+ return crecp->ttd - now;
+
+ return conf_ttl;
+ }
- /* Return the Max TTL value if it is lower then the actual TTL */
+ /* Immortal entries other than DHCP are local, and hold TTL in TTD field. */
+ if (crecp->flags & F_IMMORTAL)
+ return crecp->ttd;
+
+ /* Return the Max TTL value if it is lower than the actual TTL */
if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl))
return crecp->ttd - now;
else
@@ -1496,54 +1267,41 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
/* return zero if we can't answer from cache, or packet size if we can */
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask,
- time_t now, int *ad_reqd, int *do_bit)
+ time_t now, int ad_reqd, int do_bit, int have_pseudoheader)
{
char *name = daemon->namebuff;
- unsigned char *p, *ansp, *pheader;
+ unsigned char *p, *ansp;
unsigned int qtype, qclass;
struct all_addr addr;
int nameoffset;
unsigned short flag;
int q, ans, anscount = 0, addncount = 0;
- int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
+ int dryrun = 0;
struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
size_t len;
-
+
+ if (ntohs(header->ancount) != 0 ||
+ ntohs(header->nscount) != 0 ||
+ ntohs(header->qdcount) == 0 ||
+ OPCODE(header) != QUERY )
+ return 0;
+
+ /* always servfail queries with RD unset, to avoid cache snooping. */
+ if (!(header->hb3 & HB3_RD))
+ return setup_reply(header, qlen, NULL, F_SERVFAIL, 0);
+
/* Don't return AD set if checking disabled. */
if (header->hb4 & HB4_CD)
sec_data = 0;
- /* RFC 6840 5.7 */
- *ad_reqd = header->hb4 & HB4_AD;
- *do_bit = 0;
-
- /* If there is an RFC2671 pseudoheader then it will be overwritten by
+ /* If there is an additional data section then it will be overwritten by
partial replies, so we have to do a dry run to see if we can answer
- the query. We check to see if the do bit is set, if so we always
- forward rather than answering from the cache, which doesn't include
- security information, unless we're in DNSSEC validation mode. */
-
- if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
- {
- unsigned short flags;
-
- have_pseudoheader = 1;
-
- pheader += 4; /* udp size, ext_rcode */
- GETSHORT(flags, pheader);
-
- if ((sec_reqd = flags & 0x8000))
- *do_bit = 1;/* do bit */
-
- *ad_reqd = 1;
- dryrun = 1;
- }
+ the query. */
+ if (ntohs(header->arcount) != 0)
+ dryrun = 1;
- if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
- return 0;
-
for (rec = daemon->mxnames; rec; rec = rec->next)
rec->offset = 0;
@@ -1567,11 +1325,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
GETSHORT(qtype, p);
GETSHORT(qclass, p);
- /* Don't filter RRSIGS from answers to ANY queries, even if do-bit
- not set. */
- if (qtype == T_ANY)
- *do_bit = 1;
-
ans = 0; /* have we answered this question */
if (qtype == T_TXT || qtype == T_ANY)
@@ -1587,6 +1340,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
unsigned long ttl = daemon->local_ttl;
int ok = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
+#ifndef NO_ID
/* Dynamically generate stat record */
if (t->stat != 0)
{
@@ -1594,7 +1348,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!cache_make_stat(t))
ok = 0;
}
-
+#endif
if (ok && add_resource_record(header, limit, &trunc, nameoffset, &ansp,
ttl, NULL,
T_TXT, t->class, "t", t->len, t->txt))
@@ -1605,98 +1359,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
}
-#ifdef HAVE_DNSSEC
- if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS))
- {
- int gotone = 0;
- struct blockdata *keydata;
-
- /* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */
- if (sec_reqd)
- {
- crecp = NULL;
- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
- if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype)
- break;
- }
-
- if (!sec_reqd || crecp)
- {
- if (qtype == T_DS)
- {
- crecp = NULL;
- while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
- if (crecp->uid == qclass)
- {
- gotone = 1;
- if (!dryrun)
- {
- if (crecp->flags & F_NEG)
- {
- if (crecp->flags & F_NXDOMAIN)
- nxdomain = 1;
- log_query(F_UPSTREAM, name, NULL, "no DS");
- }
- else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
- {
- 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->addr.ds.keylen, keydata))
- anscount++;
-
- }
- }
- }
- }
- else /* DNSKEY */
- {
- crecp = NULL;
- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
- if (crecp->uid == qclass)
- {
- gotone = 1;
- if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
- {
- 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->addr.key.keylen, keydata))
- anscount++;
- }
- }
- }
- }
-
- /* Now do RRSIGs */
- if (gotone)
- {
- ans = 1;
- auth = 0;
- if (!dryrun && sec_reqd)
- {
- crecp = NULL;
- while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
- if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype &&
- (keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
- {
- add_resource_record(header, limit, &trunc, nameoffset, &ansp,
- crec_ttl(crecp, now), &nameoffset,
- T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata);
- anscount++;
- }
- }
- }
- }
-#endif
-
if (qclass == C_IN)
{
struct txt_record *t;
@@ -1705,6 +1367,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name))
{
ans = 1;
+ sec_data = 0;
if (!dryrun)
{
log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>");
@@ -1761,6 +1424,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (intr)
{
+ sec_data = 0;
ans = 1;
if (!dryrun)
{
@@ -1774,6 +1438,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
else if (ptr)
{
ans = 1;
+ sec_data = 0;
if (!dryrun)
{
log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>");
@@ -1788,38 +1453,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa)))
{
- if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd)
- {
- if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)))
- crecp = NULL;
-#ifdef HAVE_DNSSEC
- else if (crecp->flags & F_DNSSECOK)
- {
- int gotsig = 0;
- struct crec *rr_crec = NULL;
-
- while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
- {
- if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN)
- {
- char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
- gotsig = 1;
-
- if (!dryrun &&
- add_resource_record(header, limit, &trunc, nameoffset, &ansp,
- rr_crec->ttd - now, &nameoffset,
- T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata))
- anscount++;
- }
- }
-
- if (!gotsig)
- crecp = NULL;
- }
-#endif
- }
-
- if (crecp)
+ /* Don't use cache when DNSSEC data required, unless we know that
+ the zone is unsigned, which implies that we're doing
+ validation. */
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+ !do_bit ||
+ (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))
{
do
{
@@ -1829,19 +1468,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
-
+
+ ans = 1;
+
if (crecp->flags & F_NEG)
{
- ans = 1;
auth = 0;
if (crecp->flags & F_NXDOMAIN)
nxdomain = 1;
if (!dryrun)
log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL);
}
- else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID))
+ else
{
- ans = 1;
if (!(crecp->flags & (F_HOSTS | F_DHCP)))
auth = 0;
if (!dryrun)
@@ -1861,6 +1500,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
else if (is_rev_synth(is_arpa, &addr, name))
{
ans = 1;
+ sec_data = 0;
if (!dryrun)
{
log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL);
@@ -1871,19 +1511,48 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
anscount++;
}
}
- else if (is_arpa == F_IPV4 &&
- option_bool(OPT_BOGUSPRIV) &&
- private_net(addr.addr.addr4, 1))
+ else if (option_bool(OPT_BOGUSPRIV) && (
+#ifdef HAVE_IPV6
+ (is_arpa == F_IPV6 && private_net6(&addr.addr.addr6)) ||
+#endif
+ (is_arpa == F_IPV4 && private_net(addr.addr.addr4, 1))))
{
- /* if not in cache, enabled and private IPV4 address, return NXDOMAIN */
- ans = 1;
- nxdomain = 1;
- if (!dryrun)
- log_query(F_CONFIG | F_REVERSE | F_IPV4 | F_NEG | F_NXDOMAIN,
- name, &addr, NULL);
+ struct server *serv;
+ unsigned int namelen = strlen(name);
+ char *nameend = name + namelen;
+
+ /* see if have rev-server set */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ {
+ unsigned int domainlen;
+ char *matchstart;
+
+ if ((serv->flags & (SERV_HAS_DOMAIN | SERV_NO_ADDR)) != SERV_HAS_DOMAIN)
+ continue;
+
+ domainlen = strlen(serv->domain);
+ if (domainlen == 0 || domainlen > namelen)
+ continue;
+
+ matchstart = nameend - domainlen;
+ if (hostname_isequal(matchstart, serv->domain) &&
+ (namelen == domainlen || *(matchstart-1) == '.' ))
+ break;
+ }
+
+ /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */
+ if (!serv)
+ {
+ ans = 1;
+ sec_data = 0;
+ nxdomain = 1;
+ if (!dryrun)
+ log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
+ name, &addr, NULL);
+ }
}
}
-
+
for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0)
{
unsigned short type = T_A;
@@ -1899,43 +1568,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype != type && qtype != T_ANY)
continue;
- /* Check for "A for A" queries; be rather conservative
- about what looks like dotted-quad. */
- if (qtype == T_A)
- {
- char *cp;
- unsigned int i, a;
- int x;
-
- for (cp = name, i = 0, a = 0; *cp; i++)
- {
- if (!isdigit((unsigned char)*cp) || (x = strtol(cp, &cp, 10)) > 255)
- {
- i = 5;
- break;
- }
-
- a = (a << 8) + x;
-
- if (*cp == '.')
- cp++;
- }
-
- if (i == 4)
- {
- ans = 1;
- if (!dryrun)
- {
- addr.addr.addr4.s_addr = htonl(a);
- log_query(F_FORWARD | F_CONFIG | F_IPV4, name, &addr, NULL);
- if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
- daemon->local_ttl, NULL, type, C_IN, "4", &addr))
- anscount++;
- }
- continue;
- }
- }
-
/* interface name stuff */
intname_restart:
for (intr = daemon->int_names; intr; intr = intr->next)
@@ -1945,9 +1577,24 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (intr)
{
struct addrlist *addrlist;
- int gotit = 0;
+ int gotit = 0, localise = 0;
enumerate_interfaces(0);
+
+ /* See if a putative address is on the network from which we received
+ the query, is so we'll filter other answers. */
+ if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && type == T_A)
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (hostname_isequal(name, intr->name))
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+#ifdef HAVE_IPV6
+ if (!(addrlist->flags & ADDRLIST_IPV6))
+#endif
+ if (is_same_net(*((struct in_addr *)&addrlist->addr), local_addr, local_netmask))
+ {
+ localise = 1;
+ break;
+ }
for (intr = daemon->int_names; intr; intr = intr->next)
if (hostname_isequal(name, intr->name))
@@ -1957,11 +1604,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type)
#endif
{
+ if (localise &&
+ !is_same_net(*((struct in_addr *)&addrlist->addr), local_addr, local_netmask))
+ continue;
+
#ifdef HAVE_IPV6
if (addrlist->flags & ADDRLIST_REVONLY)
continue;
#endif
ans = 1;
+ sec_data = 0;
if (!dryrun)
{
gotit = 1;
@@ -1985,7 +1637,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
{
int localise = 0;
- /* See if a putative address is on the network from which we recieved
+ /* See if a putative address is on the network from which we received
the query, is so we'll filter other answers. */
if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
{
@@ -2001,48 +1653,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
crecp = save;
}
- /* If the client asked for DNSSEC and we can't provide RRSIGs, either
- because we've not doing DNSSEC or the cached answer is signed by negative,
- don't answer from the cache, forward instead. */
- if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd)
- {
- if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)))
- crecp = NULL;
-#ifdef HAVE_DNSSEC
- else if (crecp->flags & F_DNSSECOK)
- {
- /* We're returning validated data, need to return the RRSIG too. */
- struct crec *rr_crec = NULL;
- int sigtype = type;
- /* The signature may have expired even though the data is still in cache,
- forward instead of answering from cache if so. */
- int gotsig = 0;
-
- if (crecp->flags & F_CNAME)
- sigtype = T_CNAME;
-
- while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
- {
- if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN)
- {
- char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
- gotsig = 1;
-
- if (!dryrun &&
- add_resource_record(header, limit, &trunc, nameoffset, &ansp,
- rr_crec->ttd - now, &nameoffset,
- T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata))
- anscount++;
- }
- }
-
- if (!gotsig)
- crecp = NULL;
- }
-#endif
- }
-
- if (crecp)
+ /* If the client asked for DNSSEC don't use cached data. */
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !do_bit || !(crecp->flags & F_DNSSECOK))
do
{
/* don't answer wildcard queries with data not from /etc/hosts
@@ -2076,17 +1688,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (crecp->flags & F_NEG)
{
- /* We don't cache NSEC records, so if a DNSSEC-validated negative answer
- is cached and the client wants DNSSEC, forward rather than answering from the cache */
- if (!sec_reqd || !(crecp->flags & F_DNSSECOK))
- {
- ans = 1;
- auth = 0;
- if (crecp->flags & F_NXDOMAIN)
- nxdomain = 1;
- if (!dryrun)
- log_query(crecp->flags, name, NULL, NULL);
- }
+ ans = 1;
+ auth = 0;
+ if (crecp->flags & F_NXDOMAIN)
+ nxdomain = 1;
+ if (!dryrun)
+ log_query(crecp->flags, name, NULL, NULL);
}
else
{
@@ -2129,8 +1736,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_CNAME || qtype == T_ANY)
{
- if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
- (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0)))))
+ if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) &&
+ (qtype == T_CNAME || (crecp->flags & F_CONFIG)) &&
+ ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK)))
{
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
@@ -2306,7 +1914,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* set RA flag */
header->hb4 |= HB4_RA;
- /* authoritive - only hosts and DHCP derived names. */
+ /* authoritative - only hosts and DHCP derived names. */
if (auth)
header->hb3 |= HB3_AA;
@@ -2324,14 +1932,14 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
len = ansp - (unsigned char *)header;
+ /* Advertise our packet size limit in our reply */
if (have_pseudoheader)
- len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
+ len = add_pseudoheader(header, len, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
- if (*ad_reqd && sec_data)
+ if (ad_reqd && sec_data)
header->hb4 |= HB4_AD;
else
header->hb4 &= ~HB4_AD;
return len;
}
-