diff options
author | Seonah Moon <seonah1.moon@samsung.com> | 2018-06-21 16:36:11 +0900 |
---|---|---|
committer | Seonah Moon <seonah1.moon@samsung.com> | 2018-06-21 16:54:17 +0900 |
commit | e673fe0d7a174fd6708d6c91873913c0df224a6f (patch) | |
tree | 364b296df4659c400f92b6ad69ac8f9ece2ece18 /src | |
parent | 7407256e98e9edb6a87e5f7dadd38fa90a022d2b (diff) | |
parent | 2b5ed83fec55f5d96338fe3ae5e394bed1a407f4 (diff) | |
download | dnsmasq-e673fe0d7a174fd6708d6c91873913c0df224a6f.tar.gz dnsmasq-e673fe0d7a174fd6708d6c91873913c0df224a6f.tar.bz2 dnsmasq-e673fe0d7a174fd6708d6c91873913c0df224a6f.zip |
Update to 2.79
Change-Id: I36382b896dd583a66872d458f1c3b55461a9e95d
Diffstat (limited to 'src')
-rw-r--r-- | src/arp.c | 247 | ||||
-rw-r--r-- | src/auth.c | 160 | ||||
-rw-r--r-- | src/blockdata.c | 7 | ||||
-rw-r--r-- | src/bpf.c | 10 | ||||
-rw-r--r-- | src/cache.c | 69 | ||||
-rw-r--r-- | src/config.h | 61 | ||||
-rw-r--r-- | src/conntrack.c | 2 | ||||
-rw-r--r-- | src/crypto.c | 460 | ||||
-rw-r--r-- | src/dbus.c | 11 | ||||
-rw-r--r-- | src/dhcp-common.c | 25 | ||||
-rw-r--r-- | src/dhcp-protocol.h | 6 | ||||
-rw-r--r-- | src/dhcp.c | 189 | ||||
-rw-r--r-- | src/dhcp6-protocol.h | 2 | ||||
-rw-r--r-- | src/dhcp6.c | 87 | ||||
-rw-r--r-- | src/dns-protocol.h | 6 | ||||
-rw-r--r-- | src/dnsmasq.c | 307 | ||||
-rw-r--r-- | src/dnsmasq.h | 279 | ||||
-rw-r--r-- | src/dnssec.c | 2018 | ||||
-rw-r--r-- | src/domain.c | 209 | ||||
-rw-r--r-- | src/edns0.c | 449 | ||||
-rw-r--r-- | src/forward.c | 1336 | ||||
-rw-r--r-- | src/helper.c | 125 | ||||
-rw-r--r-- | src/inotify.c | 38 | ||||
-rw-r--r-- | src/ip6addr.h | 2 | ||||
-rw-r--r-- | src/ipset.c | 15 | ||||
-rw-r--r-- | src/lease.c | 189 | ||||
-rw-r--r-- | src/log.c | 10 | ||||
-rw-r--r-- | src/loop.c | 2 | ||||
-rw-r--r-- | src/netlink.c | 15 | ||||
-rw-r--r-- | src/network.c | 206 | ||||
-rw-r--r-- | src/option.c | 685 | ||||
-rw-r--r-- | src/outpacket.c | 12 | ||||
-rw-r--r-- | src/poll.c | 2 | ||||
-rw-r--r-- | src/radv-protocol.h | 2 | ||||
-rw-r--r-- | src/radv.c | 71 | ||||
-rw-r--r-- | src/rfc1035.c | 896 | ||||
-rw-r--r-- | src/rfc2131.c | 178 | ||||
-rw-r--r-- | src/rfc3315.c | 50 | ||||
-rw-r--r-- | src/rrfilter.c | 339 | ||||
-rw-r--r-- | src/slaac.c | 13 | ||||
-rw-r--r-- | src/tables.c | 92 | ||||
-rw-r--r-- | src/tftp.c | 80 | ||||
-rw-r--r-- | src/util.c | 105 |
43 files changed, 5351 insertions, 3716 deletions
diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 0000000..8beaed4 --- /dev/null +++ b/src/arp.c @@ -0,0 +1,247 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +/* Time between forced re-loads from kernel. */ +#define INTERVAL 90 + +#define ARP_MARK 0 +#define ARP_FOUND 1 /* Confirmed */ +#define ARP_NEW 2 /* Newly created */ +#define ARP_EMPTY 3 /* No MAC addr */ + +struct arp_record { + unsigned short hwlen, status; + int family; + unsigned char hwaddr[DHCP_CHADDR_MAX]; + struct all_addr addr; + struct arp_record *next; +}; + +static struct arp_record *arps = NULL, *old = NULL, *freelist = NULL; +static time_t last = 0; + +static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + struct arp_record *arp; + + (void)parmv; + + if (maclen > DHCP_CHADDR_MAX) + return 1; + +#ifndef HAVE_IPV6 + if (family != AF_INET) + return 1; +#endif + + /* Look for existing entry */ + for (arp = arps; arp; arp = arp->next) + { + if (family != arp->family || arp->status == ARP_NEW) + continue; + + if (family == AF_INET) + { + if (arp->addr.addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr) + continue; + } +#ifdef HAVE_IPV6 + else + { + if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, (struct in6_addr *)addrp)) + continue; + } +#endif + + if (arp->status == ARP_EMPTY) + { + /* existing address, was negative. */ + arp->status = ARP_NEW; + arp->hwlen = maclen; + memcpy(arp->hwaddr, mac, maclen); + } + else if (arp->hwlen == maclen && memcmp(arp->hwaddr, mac, maclen) == 0) + /* Existing entry matches - confirm. */ + arp->status = ARP_FOUND; + else + continue; + + break; + } + + if (!arp) + { + /* New entry */ + if (freelist) + { + arp = freelist; + freelist = freelist->next; + } + else if (!(arp = whine_malloc(sizeof(struct arp_record)))) + return 1; + + arp->next = arps; + arps = arp; + arp->status = ARP_NEW; + arp->hwlen = maclen; + arp->family = family; + memcpy(arp->hwaddr, mac, maclen); + if (family == AF_INET) + arp->addr.addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr; +#ifdef HAVE_IPV6 + else + memcpy(&arp->addr.addr.addr6, addrp, IN6ADDRSZ); +#endif + } + + return 1; +} + +/* If in lazy mode, we cache absence of ARP entries. */ +int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now) +{ + struct arp_record *arp, *tmp, **up; + int updated = 0; + + again: + + /* If the database is less then INTERVAL old, look in there */ + if (difftime(now, last) < INTERVAL) + { + /* addr == NULL -> just make cache up-to-date */ + if (!addr) + return 0; + + for (arp = arps; arp; arp = arp->next) + { + if (addr->sa.sa_family != arp->family) + continue; + + if (arp->family == AF_INET && + arp->addr.addr.addr4.s_addr != addr->in.sin_addr.s_addr) + continue; + +#ifdef HAVE_IPV6 + if (arp->family == AF_INET6 && + !IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, &addr->in6.sin6_addr)) + continue; +#endif + + /* Only accept positive entries unless in lazy mode. */ + if (arp->status != ARP_EMPTY || lazy || updated) + { + if (mac && arp->hwlen != 0) + memcpy(mac, arp->hwaddr, arp->hwlen); + return arp->hwlen; + } + } + } + + /* Not found, try the kernel */ + if (!updated) + { + updated = 1; + last = now; + + /* Mark all non-negative entries */ + for (arp = arps; arp; arp = arp->next) + if (arp->status != ARP_EMPTY) + arp->status = ARP_MARK; + + iface_enumerate(AF_UNSPEC, NULL, filter_mac); + + /* Remove all unconfirmed entries to old list. */ + for (arp = arps, up = &arps; arp; arp = tmp) + { + tmp = arp->next; + + if (arp->status == ARP_MARK) + { + *up = arp->next; + arp->next = old; + old = arp; + } + else + up = &arp->next; + } + + goto again; + } + + /* record failure, so we don't consult the kernel each time + we're asked for this address */ + if (freelist) + { + arp = freelist; + freelist = freelist->next; + } + else + arp = whine_malloc(sizeof(struct arp_record)); + + if (arp) + { + arp->next = arps; + arps = arp; + arp->status = ARP_EMPTY; + arp->family = addr->sa.sa_family; + arp->hwlen = 0; + + if (addr->sa.sa_family == AF_INET) + arp->addr.addr.addr4.s_addr = addr->in.sin_addr.s_addr; +#ifdef HAVE_IPV6 + else + memcpy(&arp->addr.addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ); +#endif + } + + return 0; +} + +int do_arp_script_run(void) +{ + struct arp_record *arp; + + /* Notify any which went, then move to free list */ + if (old) + { +#ifdef HAVE_SCRIPT + if (option_bool(OPT_SCRIPT_ARP)) + queue_arp(ACTION_ARP_DEL, old->hwaddr, old->hwlen, old->family, &old->addr); +#endif + arp = old; + old = arp->next; + arp->next = freelist; + freelist = arp; + return 1; + } + + for (arp = arps; arp; arp = arp->next) + if (arp->status == ARP_NEW) + { +#ifdef HAVE_SCRIPT + if (option_bool(OPT_SCRIPT_ARP)) + queue_arp(ACTION_ARP, arp->hwaddr, arp->hwlen, arp->family, &arp->addr); +#endif + arp->status = ARP_FOUND; + return 1; + } + + return 0; +} + + @@ -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 @@ -18,36 +18,53 @@ #ifdef HAVE_AUTH -static struct addrlist *find_subnet(struct auth_zone *zone, int flag, struct all_addr *addr_u) +static struct addrlist *find_addrlist(struct addrlist *list, int flag, struct all_addr *addr_u) { - struct addrlist *subnet; - - for (subnet = zone->subnet; subnet; subnet = subnet->next) - { - if (!(subnet->flags & ADDRLIST_IPV6)) - { - struct in_addr netmask, addr = addr_u->addr.addr4; - - if (!(flag & F_IPV4)) - continue; - - netmask.s_addr = htonl(~(in_addr_t)0 << (32 - subnet->prefixlen)); - - if (is_same_net(addr, subnet->addr.addr.addr4, netmask)) - return subnet; - } + do { + if (!(list->flags & ADDRLIST_IPV6)) + { + struct in_addr netmask, addr = addr_u->addr.addr4; + + if (!(flag & F_IPV4)) + continue; + + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - list->prefixlen)); + + if (is_same_net(addr, list->addr.addr.addr4, netmask)) + return list; + } #ifdef HAVE_IPV6 - else if (is_same_net6(&(addr_u->addr.addr6), &subnet->addr.addr.addr6, subnet->prefixlen)) - return subnet; + else if (is_same_net6(&(addr_u->addr.addr6), &list->addr.addr.addr6, list->prefixlen)) + return list; #endif - - } + + } while ((list = list->next)); + return NULL; } +static struct addrlist *find_subnet(struct auth_zone *zone, int flag, struct all_addr *addr_u) +{ + if (!zone->subnet) + return NULL; + + return find_addrlist(zone->subnet, flag, addr_u); +} + +static struct addrlist *find_exclude(struct auth_zone *zone, int flag, struct all_addr *addr_u) +{ + if (!zone->exclude) + return NULL; + + return find_addrlist(zone->exclude, flag, addr_u); +} + static int filter_zone(struct auth_zone *zone, int flag, struct all_addr *addr_u) { - /* No zones specified, no filter */ + if (find_exclude(zone, flag, addr_u)) + return 0; + + /* No subnets specified, no filter */ if (!zone->subnet) return 1; @@ -81,7 +98,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut) } -size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr, int local_query) +size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr, + int local_query, int do_bit, int have_pseudoheader) { char *name = daemon->namebuff; unsigned char *p, *ansp; @@ -98,7 +116,8 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n struct interface_name *intr; struct naptr *na; struct all_addr addr; - struct cname *a; + struct cname *a, *candidate; + unsigned int wclen; if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) return 0; @@ -114,6 +133,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { unsigned short flag = 0; int found = 0; + int cname_wildcard = 0; /* save pointer to name for copying into answers */ nameoffset = p - (unsigned char *)header; @@ -388,25 +408,6 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } } - for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(name, a->alias) ) - { - log_query(F_CONFIG | F_CNAME, name, NULL, NULL); - strcpy(name, a->target); - if (!strchr(name, '.')) - { - strcat(name, "."); - strcat(name, zone->domain); - } - found = 1; - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->auth_ttl, &nameoffset, - T_CNAME, C_IN, "d", name)) - anscount++; - - goto cname_restart; - } - if (!cut) { nxdomain = 0; @@ -512,8 +513,62 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6))); } - if (!found) - log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + /* Only supply CNAME if no record for any type is known. */ + if (nxdomain) + { + /* Check for possible wildcard match against *.domain + return length of match, to get longest. + Note that if return length of wildcard section, so + we match b.simon to _both_ *.simon and b.simon + but return a longer (better) match to b.simon. + */ + for (wclen = 0, candidate = NULL, a = daemon->cnames; a; a = a->next) + if (a->alias[0] == '*') + { + char *test = name; + + while ((test = strchr(test+1, '.'))) + { + if (hostname_isequal(test, &(a->alias[1]))) + { + if (strlen(test) > wclen && !cname_wildcard) + { + wclen = strlen(test); + candidate = a; + cname_wildcard = 1; + } + break; + } + } + + } + else if (hostname_isequal(a->alias, name) && strlen(a->alias) > wclen) + { + /* Simple case, no wildcard */ + wclen = strlen(a->alias); + candidate = a; + } + + if (candidate) + { + log_query(F_CONFIG | F_CNAME, name, NULL, NULL); + strcpy(name, candidate->target); + if (!strchr(name, '.')) + { + strcat(name, "."); + strcat(name, zone->domain); + } + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, &nameoffset, + T_CNAME, C_IN, "d", name)) + anscount++; + + goto cname_restart; + } + + log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + } } @@ -537,12 +592,12 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n char *p = name; if (subnet->prefixlen >= 24) - p += sprintf(p, "%d.", a & 0xff); + p += sprintf(p, "%u.", a & 0xff); a = a >> 8; if (subnet->prefixlen >= 16 ) - p += sprintf(p, "%d.", a & 0xff); + p += sprintf(p, "%u.", a & 0xff); a = a >> 8; - p += sprintf(p, "%d.in-addr.arpa", a & 0xff); + p += sprintf(p, "%u.in-addr.arpa", a & 0xff); } #ifdef HAVE_IPV6 @@ -805,7 +860,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n header->hb4 &= ~HB4_RA; } - /* authoritive */ + /* authoritative */ if (auth) header->hb3 |= HB3_AA; @@ -820,6 +875,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n header->ancount = htons(anscount); header->nscount = htons(authcount); header->arcount = htons(0); + + /* Advertise our packet size limit in our reply */ + if (have_pseudoheader) + return add_pseudoheader(header, ansp - (unsigned char *)header, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + return ansp - (unsigned char *)header; } diff --git a/src/blockdata.c b/src/blockdata.c index c8f5eae..72f0575 100644 --- a/src/blockdata.c +++ b/src/blockdata.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 @@ -25,7 +25,7 @@ static void blockdata_expand(int n) { struct blockdata *new = whine_malloc(n * sizeof(struct blockdata)); - if (n > 0 && new) + if (new) { int i; @@ -49,7 +49,7 @@ void blockdata_init(void) /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */ if (option_bool(OPT_DNSSEC_VALID)) - blockdata_expand((daemon->cachesize * 100) / sizeof(struct blockdata)); + blockdata_expand(daemon->cachesize); } void blockdata_report(void) @@ -100,6 +100,7 @@ struct blockdata *blockdata_alloc(char *data, size_t len) return ret; } + void blockdata_free(struct blockdata *blocks) { struct blockdata *tmp; @@ -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 @@ -20,7 +20,9 @@ #include <ifaddrs.h> #include <sys/param.h> +#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) #include <sys/sysctl.h> +#endif #include <net/if.h> #include <net/route.h> #include <net/if_dl.h> @@ -103,7 +105,7 @@ int arp_enumerate(void *parm, int (*callback)()) int iface_enumerate(int family, void *parm, int (*callback)()) { struct ifaddrs *head, *addrs; - int errsav, fd = -1, ret = 0; + int errsave, fd = -1, ret = 0; if (family == AF_UNSPEC) #if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) @@ -235,11 +237,11 @@ int iface_enumerate(int family, void *parm, int (*callback)()) ret = 1; err: - errsav = errno; + errsave = errno; freeifaddrs(head); if (fd != -1) close(fd); - errno = errsav; + errno = errsave; return ret; } diff --git a/src/cache.c b/src/cache.c index 1b76b67..8b1b560 100644 --- a/src/cache.c +++ b/src/cache.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 @@ -45,6 +45,7 @@ static const struct { { 24, "SIG" }, { 25, "KEY" }, { 28, "AAAA" }, + { 29, "LOC" }, { 33, "SRV" }, { 35, "NAPTR" }, { 36, "KX" }, @@ -57,6 +58,10 @@ static const struct { { 47, "NSEC" }, { 48, "DNSKEY" }, { 50, "NSEC3" }, + { 51, "NSEC3PARAM" }, + { 52, "TLSA" }, + { 53, "SMIMEA" }, + { 55, "HIP" }, { 249, "TKEY" }, { 250, "TSIG" }, { 251, "IXFR" }, @@ -189,12 +194,7 @@ static void cache_hash(struct crec *crecp) static void cache_blockdata_free(struct crec *crecp) { if (crecp->flags & F_DNSKEY) - { - if (crecp->flags & F_DS) - blockdata_free(crecp->addr.sig.keydata); - else - blockdata_free(crecp->addr.key.keydata); - } + blockdata_free(crecp->addr.key.keydata); else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG)) blockdata_free(crecp->addr.ds.keydata); } @@ -369,13 +369,8 @@ static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t no } #ifdef HAVE_DNSSEC - /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also - type-covered sensitive for RRSIG */ - if ((flags & (F_DNSKEY | F_DS)) && - (flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS)) && - crecp->uid == addr->addr.dnssec.class && - (!((flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) || - crecp->addr.sig.type_covered == addr->addr.dnssec.type)) + /* Deletion has to be class-sensitive for DS and DNSKEY */ + if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == addr->addr.dnssec.class) { if (crecp->flags & F_CONFIG) return crecp; @@ -532,13 +527,9 @@ struct crec *cache_insert(char *name, struct all_addr *addr, struct all_addr free_addr = new->addr.addr;; #ifdef HAVE_DNSSEC - /* For DNSSEC records, addr holds class and type_covered for RRSIG */ + /* For DNSSEC records, addr holds class. */ if (new->flags & (F_DS | F_DNSKEY)) - { - free_addr.addr.dnssec.class = new->uid; - if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) - free_addr.addr.dnssec.type = new->addr.sig.type_covered; - } + free_addr.addr.dnssec.class = new->uid; #endif free_avail = 1; /* Must be free space now. */ @@ -653,9 +644,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) { if ((crecp->flags & F_FORWARD) && -#ifdef HAVE_DNSSEC - (((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && -#endif (crecp->flags & prot) && hostname_isequal(cache_get_name(crecp), name)) { @@ -713,9 +701,6 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (ans && (ans->flags & F_FORWARD) && -#ifdef HAVE_DNSSEC - (((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && -#endif (ans->flags & prot) && hostname_isequal(cache_get_name(ans), name)) return ans; @@ -794,10 +779,12 @@ static void add_hosts_cname(struct crec *target) struct cname *a; for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(cache_get_name(target), a->target) && + if (a->alias[1] != '*' && + hostname_isequal(cache_get_name(target), a->target) && (crec = whine_malloc(sizeof(struct crec)))) { crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME; + crec->ttd = a->ttl; crec->name.namep = a->alias; crec->addr.cname.target.cache = target; crec->addr.cname.uid = target->uid; @@ -939,7 +926,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr if (!f) { my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno)); - return 0; + return cache_size; } eatspace(f); @@ -1001,6 +988,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr strcat(cache->name.sname, "."); strcat(cache->name.sname, domain_suffix); cache->flags = flags; + cache->ttd = daemon->local_ttl; add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; } @@ -1008,6 +996,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { strcpy(cache->name.sname, canon); cache->flags = flags; + cache->ttd = daemon->local_ttl; add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; } @@ -1073,10 +1062,12 @@ void cache_reload(void) /* Add CNAMEs to interface_names to the cache */ for (a = daemon->cnames; a; a = a->next) for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(a->target, intr->name) && + if (a->alias[1] != '*' && + hostname_isequal(a->target, intr->name) && ((cache = whine_malloc(sizeof(struct crec))))) { cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; + cache->ttd = a->ttl; cache->name.namep = a->alias; cache->addr.cname.target.int_name = intr; cache->addr.cname.uid = SRC_INTERFACE; @@ -1091,6 +1082,7 @@ void cache_reload(void) (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) { cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; + cache->ttd = daemon->local_ttl; cache->name.namep = ds->name; cache->addr.ds.keylen = ds->digestlen; cache->addr.ds.algo = ds->algo; @@ -1115,6 +1107,7 @@ void cache_reload(void) (cache = whine_malloc(sizeof(struct crec)))) { cache->name.namep = nl->name; + cache->ttd = hr->ttl; cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG; add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); } @@ -1123,6 +1116,7 @@ void cache_reload(void) (cache = whine_malloc(sizeof(struct crec)))) { cache->name.namep = nl->name; + cache->ttd = hr->ttl; cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG; add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); } @@ -1190,7 +1184,8 @@ static void add_dhcp_cname(struct crec *target, time_t ttd) struct cname *a; for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(cache_get_name(target), a->target)) + if (a->alias[1] != '*' && + hostname_isequal(cache_get_name(target), a->target)) { if ((aliasc = dhcp_spare)) dhcp_spare = dhcp_spare->next; @@ -1303,6 +1298,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, } #endif +#ifndef NO_ID int cache_make_stat(struct txt_record *t) { static char *buff = NULL; @@ -1398,6 +1394,7 @@ int cache_make_stat(struct txt_record *t) *buff = len; return 1; } +#endif /* There can be names in the cache containing control chars, don't mess up logging or open security holes. */ @@ -1472,11 +1469,7 @@ void dump_cache(time_t now) #ifdef HAVE_DNSSEC else if (cache->flags & F_DS) { - if (cache->flags & F_DNSKEY) - /* RRSIG */ - sprintf(a, "%5u %3u %s", cache->addr.sig.keytag, - cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered)); - else if (!(cache->flags & F_NEG)) + if (!(cache->flags & F_NEG)) sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, cache->addr.ds.algo, cache->addr.ds.digest); } @@ -1502,8 +1495,6 @@ void dump_cache(time_t now) else if (cache->flags & F_CNAME) t = "C"; #ifdef HAVE_DNSSEC - else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) - t = "G"; /* DNSKEY and DS set -> RRISG */ else if (cache->flags & F_DS) t = "S"; else if (cache->flags & F_DNSKEY) @@ -1525,7 +1516,7 @@ void dump_cache(time_t now) /* ctime includes trailing \n - eat it */ *(p-1) = 0; #endif - my_syslog(LOG_INFO, daemon->namebuff); + my_syslog(LOG_INFO, "%s", daemon->namebuff); } } } @@ -1606,7 +1597,7 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) if (addr) { if (flags & F_KEYTAG) - sprintf(daemon->addrbuff, arg, addr->addr.keytag); + sprintf(daemon->addrbuff, arg, addr->addr.log.keytag, addr->addr.log.algo, addr->addr.log.digest); else { #ifdef HAVE_IPV6 diff --git a/src/config.h b/src/config.h index 8d964af..67a5ac5 100644 --- a/src/config.h +++ b/src/config.h @@ -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 @@ -14,19 +14,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define VERSION "2.74" +#define VERSION "2.79" #define FTABSIZ 150 /* max number of outstanding requests (default) */ #define MAX_PROCS 20 /* max no children for TCP requests */ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */ +#define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */ #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */ #define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */ -#define KEYBLOCK_LEN 40 /* choose to mininise fragmentation when storing DNSSEC keys */ +#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ #define DNSSEC_WORK 50 /* Max number of queries to validate one question */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TIME 20 /* or 20 seconds */ +#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */ +#define SERVERS_LOGGED 30 /* Only log this many servers when logging state */ +#define LOCALS_LOGGED 8 /* Only log this many local addresses when logging state */ #define RANDOM_SOCKS 64 /* max simultaneous random ports */ #define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */ #define CACHESIZ 150 /* default cache size */ @@ -93,13 +97,13 @@ HAVE_DBUS servers via DBus. HAVE_IDN - define this if you want international domain name support. - NOTE: for backwards compatibility, IDN support is automatically - included when internationalisation support is built, using the - *-i18n makefile targets, even if HAVE_IDN is not explicitly set. + define this if you want international domain name 2003 support. + +HAVE_LIBIDN2 + define this if you want international domain name 2008 support. HAVE_CONNTRACK - define this to include code which propogates conntrack marks from + define this to include code which propagates conntrack marks from incoming DNS queries to the corresponding upstream queries. This adds a build-dependency on libnetfilter_conntrack, but the resulting binary will still run happily on a kernel without conntrack support. @@ -121,6 +125,8 @@ HAVE_LOOP HAVE_INOTIFY use the Linux inotify facility to efficiently re-read configuration files. +NO_ID + Don't report *.bind CHAOS info to clients, forward such requests upstream instead. NO_IPV6 NO_TFTP NO_DHCP @@ -129,13 +135,10 @@ NO_SCRIPT NO_LARGEFILE NO_AUTH NO_INOTIFY - these are avilable to explictly disable compile time options which would + these are available to explicitly disable compile time options which would otherwise be enabled automatically (HAVE_IPV6, >2Gb file sizes) or which are enabled by default in the distributed source tree. Building dnsmasq with something like "make COPTS=-DNO_SCRIPT" will do the trick. - -NO_NETTLE_ECC - Don't include the ECDSA cypher in DNSSEC validation. Needed for older Nettle versions. NO_GMP Don't use and link against libgmp, Useful if nettle is built with --enable-mini-gmp. @@ -174,6 +177,7 @@ RESOLVFILE /* #define HAVE_LUASCRIPT */ #define HAVE_DBUS /* #define HAVE_IDN */ +/* #define HAVE_LIBIDN2 */ /* #define HAVE_CONNTRACK */ /* #define HAVE_DNSSEC */ @@ -230,7 +234,7 @@ HAVE_SOCKADDR_SA_LEN defined if struct sockaddr has sa_len field (*BSD) */ -/* Must preceed __linux__ since uClinux defines __linux__ too. */ +/* Must precede __linux__ since uClinux defines __linux__ too. */ #if defined(__uClinux__) #define HAVE_LINUX_NETWORK #define HAVE_GETOPT_LONG @@ -268,7 +272,7 @@ HAVE_SOCKADDR_SA_LEN defined(__DragonFly__) || \ defined(__FreeBSD_kernel__) #define HAVE_BSD_NETWORK -/* Later verions of FreeBSD have getopt_long() */ +/* Later versions of FreeBSD have getopt_long() */ #if defined(optional_argument) && defined(required_argument) # define HAVE_GETOPT_LONG #endif @@ -339,7 +343,7 @@ HAVE_SOCKADDR_SA_LEN #define HAVE_DHCP #endif -#if defined(NO_SCRIPT) || !defined(HAVE_DHCP) || defined(NO_FORK) +#if defined(NO_SCRIPT) || defined(NO_FORK) #undef HAVE_SCRIPT #undef HAVE_LUASCRIPT #endif @@ -366,7 +370,7 @@ HAVE_SOCKADDR_SA_LEN #endif /* Define a string indicating which options are in use. - DNSMASQP_COMPILE_OPTS is only defined in dnsmasq.c */ + DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */ #ifdef DNSMASQ_COMPILE_OPTS @@ -393,10 +397,14 @@ static char *compile_opts = "no-" #endif "i18n " -#if !defined(LOCALEDIR) && !defined(HAVE_IDN) +#if defined(HAVE_LIBIDN2) +"IDN2 " +#else + #if !defined(HAVE_IDN) "no-" -#endif -"IDN " + #endif +"IDN " +#endif #ifndef HAVE_DHCP "no-" #endif @@ -406,14 +414,14 @@ static char *compile_opts = "no-" # endif "DHCPv6 " -# if !defined(HAVE_SCRIPT) +#endif +#if !defined(HAVE_SCRIPT) "no-scripts " -# else -# if !defined(HAVE_LUASCRIPT) - "no-" -# endif - "Lua " +#else +# if !defined(HAVE_LUASCRIPT) + "no-" # endif + "Lua " #endif #ifndef HAVE_TFTP "no-" @@ -435,6 +443,9 @@ static char *compile_opts = "no-" #endif "DNSSEC " +#ifdef NO_ID +"no-ID " +#endif #ifndef HAVE_LOOP "no-" #endif diff --git a/src/conntrack.c b/src/conntrack.c index 0fa2da9..2929f8c 100644 --- a/src/conntrack.c +++ b/src/conntrack.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 diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..ebb871e --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,460 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DNSSEC + +#include <nettle/rsa.h> +#include <nettle/dsa.h> +#include <nettle/ecdsa.h> +#include <nettle/ecc-curve.h> +#include <nettle/eddsa.h> +#include <nettle/nettle-meta.h> +#include <nettle/bignum.h> + +/* Implement a "hash-function" to the nettle API, which simply returns + the input data, concatenated into a single, statically maintained, buffer. + + Used for the EdDSA sigs, which operate on the whole message, rather + than a digest. */ + +struct null_hash_digest +{ + uint8_t *buff; + size_t len; +}; + +struct null_hash_ctx +{ + size_t len; +}; + +static size_t null_hash_buff_sz = 0; +static uint8_t *null_hash_buff = NULL; +#define BUFF_INCR 128 + +static void null_hash_init(void *ctx) +{ + ((struct null_hash_ctx *)ctx)->len = 0; +} + +static void null_hash_update(void *ctxv, size_t length, const uint8_t *src) +{ + struct null_hash_ctx *ctx = ctxv; + size_t new_len = ctx->len + length; + + if (new_len > null_hash_buff_sz) + { + uint8_t *new; + + if (!(new = whine_malloc(new_len + BUFF_INCR))) + return; + + if (null_hash_buff) + { + if (ctx->len != 0) + memcpy(new, null_hash_buff, ctx->len); + free(null_hash_buff); + } + + null_hash_buff_sz = new_len + BUFF_INCR; + null_hash_buff = new; + } + + memcpy(null_hash_buff + ctx->len, src, length); + ctx->len += length; +} + + +static void null_hash_digest(void *ctx, size_t length, uint8_t *dst) +{ + (void)length; + + ((struct null_hash_digest *)dst)->buff = null_hash_buff; + ((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len; +} + +static struct nettle_hash null_hash = { + "null_hash", + sizeof(struct null_hash_ctx), + sizeof(struct null_hash_digest), + 0, + (nettle_hash_init_func *) null_hash_init, + (nettle_hash_update_func *) null_hash_update, + (nettle_hash_digest_func *) null_hash_digest +}; + +/* Find pointer to correct hash function in nettle library */ +const struct nettle_hash *hash_find(char *name) +{ + if (!name) + return NULL; + + /* We provide a "null" hash which returns the input data as digest. */ + if (strcmp(null_hash.name, name) == 0) + return &null_hash; + + /* libnettle >= 3.4 provides nettle_lookup_hash() which avoids nasty ABI + incompatibilities if sizeof(nettle_hashes) changes between library + versions. It also #defines nettle_hashes, so use that to tell + if we have the new facilities. */ + +#ifdef nettle_hashes + return nettle_lookup_hash(name); +#else + { + int i; + + for (i = 0; nettle_hashes[i]; i++) + if (strcmp(nettle_hashes[i]->name, name) == 0) + return nettle_hashes[i]; + } + + return NULL; +#endif +} + +/* expand ctx and digest memory allocations if necessary and init hash function */ +int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) +{ + static void *ctx = NULL; + static unsigned char *digest = NULL; + static unsigned int ctx_sz = 0; + static unsigned int digest_sz = 0; + + void *new; + + if (ctx_sz < hash->context_size) + { + if (!(new = whine_malloc(hash->context_size))) + return 0; + if (ctx) + free(ctx); + ctx = new; + ctx_sz = hash->context_size; + } + + if (digest_sz < hash->digest_size) + { + if (!(new = whine_malloc(hash->digest_size))) + return 0; + if (digest) + free(digest); + digest = new; + digest_sz = hash->digest_size; + } + + *ctxp = ctx; + *digestp = digest; + + hash->init(ctx); + + return 1; +} + +static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + size_t exp_len; + + static struct rsa_public_key *key = NULL; + static mpz_t sig_mpz; + + (void)digest_len; + + if (key == NULL) + { + if (!(key = whine_malloc(sizeof(struct rsa_public_key)))) + return 0; + + nettle_rsa_public_key_init(key); + mpz_init(sig_mpz); + } + + if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + key_len--; + if ((exp_len = *p++) == 0) + { + GETSHORT(exp_len, p); + key_len -= 2; + } + + if (exp_len >= key_len) + return 0; + + key->size = key_len - exp_len; + mpz_import(key->e, exp_len, 1, 1, 0, 0, p); + mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len); + + mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig); + + switch (algo) + { + case 1: + return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); + case 5: case 7: + return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); + case 8: + return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz); + case 10: + return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz); + } + + return 0; +} + +static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + unsigned int t; + + static mpz_t y; + static struct dsa_params *params = NULL; + static struct dsa_signature *sig_struct; + + (void)digest_len; + + if (params == NULL) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || + !(params = whine_malloc(sizeof(struct dsa_params)))) + return 0; + + mpz_init(y); + nettle_dsa_params_init(params); + nettle_dsa_signature_init(sig_struct); + } + + if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + t = *p++; + + if (key_len < (213 + (t * 24))) + return 0; + + mpz_import(params->q, 20, 1, 1, 0, 0, p); p += 20; + mpz_import(params->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(params->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + + mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); + mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); + + (void)algo; + + return nettle_dsa_verify(params, y, digest_len, digest, sig_struct); +} + +static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, + unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + unsigned int t; + struct ecc_point *key; + + static struct ecc_point *key_256 = NULL, *key_384 = NULL; + static mpz_t x, y; + static struct dsa_signature *sig_struct; + + if (!sig_struct) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature)))) + return 0; + + nettle_dsa_signature_init(sig_struct); + mpz_init(x); + mpz_init(y); + } + + switch (algo) + { + case 13: + if (!key_256) + { + if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) + return 0; + + nettle_ecc_point_init(key_256, &nettle_secp_256r1); + } + + key = key_256; + t = 32; + break; + + case 14: + if (!key_384) + { + if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) + return 0; + + nettle_ecc_point_init(key_384, &nettle_secp_384r1); + } + + key = key_384; + t = 48; + break; + + default: + return 0; + } + + if (sig_len != 2*t || key_len != 2*t || + !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + mpz_import(x, t , 1, 1, 0, 0, p); + mpz_import(y, t , 1, 1, 0, 0, p + t); + + if (!ecc_point_set(key, x, y)) + return 0; + + mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig); + mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t); + + return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); +} + +static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len, + unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + + if (key_len != ED25519_KEY_SIZE || + sig_len != ED25519_SIGNATURE_SIZE || + digest_len != sizeof(struct null_hash_digest) || + !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + /* The "digest" returned by the null_hash function is simply a struct null_hash_digest + which has a pointer to the actual data and a length, because the buffer + may need to be extended during "hashing". */ + + switch (algo) + { + case 15: + return ed25519_sha512_verify(p, + ((struct null_hash_digest *)digest)->len, + ((struct null_hash_digest *)digest)->buff, + sig); + case 16: + /* Ed448 when available */ + return 0; + } + + return 0; +} + +static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + + /* Enure at runtime that we have support for this digest */ + if (!hash_find(algo_digest_name(algo))) + return NULL; + + /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */ + switch (algo) + { + case 1: case 5: case 7: case 8: case 10: + return dnsmasq_rsa_verify; + + case 3: case 6: + return dnsmasq_dsa_verify; + + case 13: case 14: + return dnsmasq_ecdsa_verify; + + case 15: case 16: + return dnsmasq_eddsa_verify; + } + + return NULL; +} + +int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + + int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo); + + func = verify_func(algo); + + if (!func) + return 0; + + return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo); +} + +/* Note the ds_digest_name(), algo_digest_name() and nsec3_digest_name() + define which algo numbers we support. If algo_digest_name() returns + non-NULL for an algorithm number, we assume that algorithm is + supported by verify(). */ + +/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +char *ds_digest_name(int digest) +{ + switch (digest) + { + case 1: return "sha1"; + case 2: return "sha256"; + case 3: return "gosthash94"; + case 4: return "sha384"; + default: return NULL; + } +} + +/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +char *algo_digest_name(int algo) +{ + switch (algo) + { + case 1: return NULL; /* RSA/MD5 - Must Not Implement. RFC 6944 para 2.3. */ + case 2: return NULL; /* Diffie-Hellman */ + case 3: return "sha1"; /* DSA/SHA1 */ + case 5: return "sha1"; /* RSA/SHA1 */ + case 6: return "sha1"; /* DSA-NSEC3-SHA1 */ + case 7: return "sha1"; /* RSASHA1-NSEC3-SHA1 */ + case 8: return "sha256"; /* RSA/SHA-256 */ + case 10: return "sha512"; /* RSA/SHA-512 */ + case 12: return NULL; /* ECC-GOST */ + case 13: return "sha256"; /* ECDSAP256SHA256 */ + case 14: return "sha384"; /* ECDSAP384SHA384 */ + case 15: return "null_hash"; /* ED25519 */ + case 16: return NULL; /* ED448 */ + default: return NULL; + } +} + +/* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ +char *nsec3_digest_name(int digest) +{ + switch (digest) + { + case 1: return "sha1"; + default: return NULL; + } +} + +#endif @@ -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 @@ -549,17 +549,16 @@ static DBusMessage *dbus_add_lease(DBusMessage* message) return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, "Invalid IP address '%s'", ipaddr); - hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, - &hw_type); + hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, &hw_type); if (hw_type == 0 && hw_len != 0) hw_type = ARPHRD_ETHER; - - lease_set_hwaddr(lease, dhcp_chaddr, clid, hw_len, hw_type, + + lease_set_hwaddr(lease, dhcp_chaddr, clid, hw_len, hw_type, clid_len, now, 0); lease_set_expires(lease, expires, now); if (hostname_len != 0) lease_set_hostname(lease, hostname, 0, get_domain(lease->addr), NULL); - + lease_update_file(now); lease_update_dns(0); diff --git a/src/dhcp-common.c b/src/dhcp-common.c index bc48f41..d9719d1 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.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 @@ -20,11 +20,11 @@ void dhcp_common_init(void) { - /* These each hold a DHCP option max size 255 - and get a terminating zero added */ - daemon->dhcp_buff = safe_malloc(256); - daemon->dhcp_buff2 = safe_malloc(256); - daemon->dhcp_buff3 = safe_malloc(256); + /* These each hold a DHCP option max size 255 + and get a terminating zero added */ + daemon->dhcp_buff = safe_malloc(DHCP_BUFF_SZ); + daemon->dhcp_buff2 = safe_malloc(DHCP_BUFF_SZ); + daemon->dhcp_buff3 = safe_malloc(DHCP_BUFF_SZ); /* dhcp_packet is used by v4 and v6, outpacket only by v6 sizeof(struct dhcp_packet) is as good an initial size as any, @@ -485,11 +485,8 @@ char *whichdevice(void) void bindtodevice(char *device, int fd) { - struct ifreq ifr; - - strcpy(ifr.ifr_name, device); /* only allowed by root. */ - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) == -1 && + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, IFNAMSIZ) == -1 && errno != EPERM) die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET); } @@ -599,7 +596,7 @@ static const struct opttab_t opttab6[] = { { "sntp-server", 31, OT_ADDR_LIST }, { "information-refresh-time", 32, OT_TIME }, { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME }, - { "ntp-server", 56, OT_ADDR_LIST }, + { "ntp-server", 56, 0 }, { "bootfile-url", 59, OT_NAME }, { "bootfile-param", 60, OT_CSTRING }, { NULL, 0, 0 } @@ -855,14 +852,14 @@ void log_context(int family, struct dhcp_context *context) if (context->flags & CONTEXT_RA_STATELESS) { if (context->flags & CONTEXT_TEMPLATE) - strncpy(daemon->dhcp_buff, context->template_interface, 256); + strncpy(daemon->dhcp_buff, context->template_interface, DHCP_BUFF_SZ); else strcpy(daemon->dhcp_buff, daemon->addrbuff); } else #endif - inet_ntop(family, start, daemon->dhcp_buff, 256); - inet_ntop(family, end, daemon->dhcp_buff3, 256); + inet_ntop(family, start, daemon->dhcp_buff, DHCP_BUFF_SZ); + inet_ntop(family, end, daemon->dhcp_buff3, DHCP_BUFF_SZ); my_syslog(MS_DHCP | LOG_INFO, (context->flags & CONTEXT_RA_STATELESS) ? _("%s stateless on %s%.0s%.0s%s") : diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h index 701b6cb..a4a3535 100644 --- a/src/dhcp-protocol.h +++ b/src/dhcp-protocol.h @@ -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 @@ -19,6 +19,10 @@ #define DHCP_CLIENT_ALTPORT 1068 #define PXE_PORT 4011 +/* These each hold a DHCP option max size 255 + and get a terminating zero added */ +#define DHCP_BUFF_SZ 256 + #define BOOTREQUEST 1 #define BOOTREPLY 2 #define DHCP_COOKIE 0x63825363 @@ -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 @@ -67,9 +67,9 @@ static int make_fd(int port) setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &oneopt, sizeof(oneopt)) == -1) die(_("failed to set options on DHCP socket: %s"), NULL, EC_BADNET); - /* When bind-interfaces is set, there might be more than one dnmsasq + /* When bind-interfaces is set, there might be more than one dnsmasq instance binding port 67. That's OK if they serve different networks. - Need to set REUSEADDR|REUSEPORT to make this posible. + Need to set REUSEADDR|REUSEPORT to make this possible. Handle the case that REUSEPORT is defined, but the kernel doesn't support it. This handles the introduction of REUSEPORT on Linux. */ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) @@ -145,11 +145,14 @@ void dhcp_packet(time_t now, int pxe_fd) struct cmsghdr *cmptr; struct iovec iov; ssize_t sz; - int iface_index = 0, unicast_dest = 0, is_inform = 0; + int iface_index = 0, unicast_dest = 0, is_inform = 0, loopback = 0; + int rcvd_iface_index; struct in_addr iface_addr; struct iface_param parm; + time_t recvtime = now; #ifdef HAVE_LINUX_NETWORK struct arpreq arp_req; + struct timeval tv; #endif union { @@ -176,6 +179,9 @@ void dhcp_packet(time_t now, int pxe_fd) return; #if defined (HAVE_LINUX_NETWORK) + if (ioctl(fd, SIOCGSTAMP, &tv) == 0) + recvtime = tv.tv_sec; + if (msg.msg_controllen >= sizeof(struct cmsghdr)) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) @@ -217,9 +223,13 @@ void dhcp_packet(time_t now, int pxe_fd) } #endif - if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name)) + if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name) || + ioctl(daemon->dhcpfd, SIOCGIFFLAGS, &ifr) != 0) return; - + + mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; + loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK); + #ifdef HAVE_LINUX_NETWORK /* ARP fiddling uses original interface even if we pretend to use a different one. */ strncpy(arp_req.arp_dev, ifr.ifr_name, 16); @@ -230,6 +240,7 @@ void dhcp_packet(time_t now, int pxe_fd) --bridge-interface option), change ifr.ifr_name so that we look for DHCP contexts associated with the aliased interface instead of with the aliasing one. */ + rcvd_iface_index = iface_index; for (bridge = daemon->bridges; bridge; bridge = bridge->next) { for (alias = bridge->alias; alias; alias = alias->next) @@ -262,8 +273,8 @@ void dhcp_packet(time_t now, int pxe_fd) if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name))) { /* Reply from server, using us as relay. */ - iface_index = relay->iface_index; - if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name)) + rcvd_iface_index = relay->iface_index; + if (!indextoname(daemon->dhcpfd, rcvd_iface_index, ifr.ifr_name)) return; is_relay_reply = 1; iov.iov_len = sz; @@ -278,7 +289,8 @@ void dhcp_packet(time_t now, int pxe_fd) iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; else { - my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); + if (iface_check(AF_INET, NULL, ifr.ifr_name, NULL)) + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); return; } @@ -323,7 +335,7 @@ void dhcp_packet(time_t now, int pxe_fd) /* We're relaying this request */ if (parm.relay_local.s_addr != 0 && - relay_upstream4(parm.relay, (struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, iface_index)) + relay_upstream4(parm.relay, mess, (size_t)sz, iface_index)) return; /* May have configured relay, but not DHCP server */ @@ -332,14 +344,14 @@ void dhcp_packet(time_t now, int pxe_fd) lease_prune(NULL, now); /* lose any expired leases */ iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, - now, unicast_dest, &is_inform, pxe_fd, iface_addr); + now, unicast_dest, loopback, &is_inform, pxe_fd, iface_addr, recvtime); lease_update_file(now); lease_update_dns(0); if (iov.iov_len == 0) return; } - + msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); msg.msg_control = NULL; @@ -387,7 +399,7 @@ void dhcp_packet(time_t now, int pxe_fd) msg.msg_controllen = sizeof(control_u); cmptr = CMSG_FIRSTHDR(&msg); pkt = (struct in_pktinfo *)CMSG_DATA(cmptr); - pkt->ipi_ifindex = iface_index; + pkt->ipi_ifindex = rcvd_iface_index; pkt->ipi_spec_dst.s_addr = 0; msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); cmptr->cmsg_level = IPPROTO_IP; @@ -452,8 +464,13 @@ void dhcp_packet(time_t now, int pxe_fd) #endif while(retry_send(sendmsg(fd, &msg, 0))); + + /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */ + if (errno != 0) + my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"), + inet_ntoa(dest.sin_addr), strerror(errno)); } - + /* check against secondary interface addresses */ static int check_listen_addrs(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) @@ -488,7 +505,7 @@ static int check_listen_addrs(struct in_addr local, int if_index, char *label, 3) Fills in local (this host) and router (this host or relay) addresses. 4) Links contexts which are valid for hosts directly connected to the arrival interface on ->current. - Note that the current chain may be superceded later for configured hosts or those coming via gateways. */ + Note that the current chain may be superseded later for configured hosts or those coming via gateways. */ static int complete_context(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) @@ -588,7 +605,7 @@ struct dhcp_context *narrow_context(struct dhcp_context *context, { /* We start of with a set of possible contexts, all on the current physical interface. These are chained on ->current. - Here we have an address, and return the actual context correponding to that + Here we have an address, and return the actual context corresponding to that address. Note that none may fit, if the address came a dhcp-host and is outside any dhcp-range. In that case we return a static range if possible, or failing that, any context on the correct subnet. (If there's more than one, this is a dodgy @@ -630,9 +647,69 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i return NULL; } +/* Check if and address is in use by sending ICMP ping. + This wrapper handles a cache and load-limiting. + Return is NULL is address in use, or a pointer to a cache entry + recording that it isn't. */ +struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopback) +{ + static struct ping_result dummy; + struct ping_result *r, *victim = NULL; + int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/ + ((float)PING_WAIT))); + + /* check if we failed to ping addr sometime in the last + PING_CACHE_TIME seconds. If so, assume the same situation still exists. + This avoids problems when a stupid client bangs + on us repeatedly. As a final check, if we did more + than 60% of the possible ping checks in the last + PING_CACHE_TIME, we are in high-load mode, so don't do any more. */ + for (count = 0, r = daemon->ping_results; r; r = r->next) + if (difftime(now, r->time) > (float)PING_CACHE_TIME) + victim = r; /* old record */ + else + { + count++; + if (r->addr.s_addr == addr.s_addr) + return r; + } + + /* didn't find cached entry */ + if ((count >= max) || option_bool(OPT_NO_PING) || loopback) + { + /* overloaded, or configured not to check, loopback interface, return "not in use" */ + dummy.hash = 0; + return &dummy; + } + else if (icmp_ping(addr)) + return NULL; /* address in use. */ + else + { + /* at this point victim may hold an expired record */ + if (!victim) + { + if ((victim = whine_malloc(sizeof(struct ping_result)))) + { + victim->next = daemon->ping_results; + daemon->ping_results = victim; + } + } + + /* record that this address is OK for 30s + without more ping checks */ + if (victim) + { + victim->addr = addr; + victim->time = now; + victim->hash = hash; + } + return victim; + } +} + int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, - struct dhcp_netid *netids, time_t now) + struct dhcp_netid *netids, time_t now, int loopback) { /* Find a free address: exclude anything in use and anything allocated to a particular hwaddr/clientid/hostname in our configuration. @@ -646,7 +723,11 @@ int address_allocate(struct dhcp_context *context, /* hash hwaddr: use the SDBM hashing algorithm. Seems to give good dispersal even with similarly-valued "strings". */ for (j = 0, i = 0; i < hw_len; i++) - j += hwaddr[i] + (j << 6) + (j << 16) - j; + j = hwaddr[i] + (j << 6) + (j << 16) - j; + + /* j == 0 is marker */ + if (j == 0) + j = 1; for (pass = 0; pass <= 1; pass++) for (c = context; c; c = c->current) @@ -684,69 +765,27 @@ int address_allocate(struct dhcp_context *context, (!IN_CLASSC(ntohl(addr.s_addr)) || ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0)))) { - struct ping_result *r, *victim = NULL; - int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/ - ((float)PING_WAIT))); + struct ping_result *r; - *addrp = addr; - - /* check if we failed to ping addr sometime in the last - PING_CACHE_TIME seconds. If so, assume the same situation still exists. - This avoids problems when a stupid client bangs - on us repeatedly. As a final check, if we did more - than 60% of the possible ping checks in the last - PING_CACHE_TIME, we are in high-load mode, so don't do any more. */ - for (count = 0, r = daemon->ping_results; r; r = r->next) - if (difftime(now, r->time) > (float)PING_CACHE_TIME) - victim = r; /* old record */ - else - { - count++; - if (r->addr.s_addr == addr.s_addr) - { - /* consec-ip mode: we offered this address for another client - (different hash) recently, don't offer it to this one. */ - if (option_bool(OPT_CONSEC_ADDR) && r->hash != j) - break; - - return 1; - } - } - - if (!r) - { - if ((count < max) && !option_bool(OPT_NO_PING) && icmp_ping(addr)) - { - /* address in use: perturb address selection so that we are - less likely to try this address again. */ - if (!option_bool(OPT_CONSEC_ADDR)) - c->addr_epoch++; - } - else + if ((r = do_icmp_ping(now, addr, j, loopback))) + { + /* consec-ip mode: we offered this address for another client + (different hash) recently, don't offer it to this one. */ + if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j) { - /* at this point victim may hold an expired record */ - if (!victim) - { - if ((victim = whine_malloc(sizeof(struct ping_result)))) - { - victim->next = daemon->ping_results; - daemon->ping_results = victim; - } - } - - /* record that this address is OK for 30s - without more ping checks */ - if (victim) - { - victim->addr = addr; - victim->time = now; - victim->hash = j; - } + *addrp = addr; return 1; } } + else + { + /* address in use: perturb address selection so that we are + less likely to try this address again. */ + if (!option_bool(OPT_CONSEC_ADDR)) + c->addr_epoch++; + } } - + addr.s_addr = htonl(ntohl(addr.s_addr) + 1); if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h index 928a2fa..fee5d28 100644 --- a/src/dhcp6-protocol.h +++ b/src/dhcp6-protocol.h @@ -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 diff --git a/src/dhcp6.c b/src/dhcp6.c index 8286ff4..0853664 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.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 @@ -27,17 +27,10 @@ struct iface_param { int ind, addr_match; }; -struct mac_param { - struct in6_addr *target; - unsigned char *mac; - unsigned int maclen; -}; - static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam); -static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv); static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); void dhcp6_init(void) @@ -58,9 +51,9 @@ void dhcp6_init(void) !set_ipv6pktinfo(fd)) die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET); - /* When bind-interfaces is set, there might be more than one dnmsasq + /* When bind-interfaces is set, there might be more than one dnsmasq instance binding port 547. That's OK if they serve different networks. - Need to set REUSEADDR|REUSEPORT to make this posible. + Need to set REUSEADDR|REUSEPORT to make this possible. Handle the case that REUSEPORT is defined, but the kernel doesn't support it. This handles the introduction of REUSEPORT on Linux. */ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) @@ -227,7 +220,7 @@ void dhcp6_packet(time_t now) inet_pton(AF_INET6, ALL_SERVERS, &all_servers); if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers)) - relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id); + relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id, now); return; } @@ -257,16 +250,15 @@ void dhcp6_packet(time_t now) } } -void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep) +void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now) { - /* Recieving a packet from a host does not populate the neighbour + /* Receiving a packet from a host does not populate the neighbour cache, so we send a neighbour discovery request if we can't find the sender. Repeat a few times in case of packet loss. */ struct neigh_packet neigh; - struct sockaddr_in6 addr; - struct mac_param mac_param; - int i; + union mysockaddr addr; + int i, maclen; neigh.type = ND_NEIGHBOR_SOLICIT; neigh.code = 0; @@ -277,55 +269,31 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN - addr.sin6_len = sizeof(struct sockaddr_in6); + addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(IPPROTO_ICMPV6); - addr.sin6_addr = *client; - addr.sin6_scope_id = iface; - - mac_param.target = client; - mac_param.maclen = 0; - mac_param.mac = mac; + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(IPPROTO_ICMPV6); + addr.in6.sin6_addr = *client; + addr.in6.sin6_scope_id = iface; for (i = 0; i < 5; i++) { struct timespec ts; - iface_enumerate(AF_UNSPEC, &mac_param, find_mac); - - if (mac_param.maclen != 0) + if ((maclen = find_mac(&addr, mac, 0, now)) != 0) break; - - sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, (struct sockaddr *)&addr, sizeof(addr)); + + sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr)); ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 100ms */ nanosleep(&ts, NULL); } - *maclenp = mac_param.maclen; + *maclenp = maclen; *mactypep = ARPHRD_ETHER; } -static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct mac_param *parm = parmv; - - if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(parm->target, (struct in6_addr *)addrp)) - { - if (maclen <= DHCP_CHADDR_MAX) - { - parm->maclen = maclen; - memcpy(parm->mac, mac, maclen); - } - - return 0; /* found, abort */ - } - - return 1; -} - static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam) @@ -376,7 +344,7 @@ static int complete_context6(struct in6_addr *local, int prefix, { struct dhcp_context *tmp, **up; - /* use interface values only for contructed contexts */ + /* use interface values only for constructed contexts */ if (!(context->flags & CONTEXT_CONSTRUCTED)) preferred = valid = 0xffffffff; else if (flags & IFACE_DEPRECATED) @@ -452,7 +420,7 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c j = rand64(); else for (j = iaid, i = 0; i < clid_len; i++) - j += clid[i] + (j << 6) + (j << 16) - j; + j = clid[i] + (j << 6) + (j << 16) - j; for (pass = 0; pass <= plain_range ? 1 : 0; pass++) for (c = context; c; c = c->current) @@ -466,7 +434,16 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c /* seed is largest extant lease addr in this context */ start = lease_find_max_addr6(c) + serial; else - start = addr6part(&c->start6) + ((j + c->addr_epoch) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); + { + u64 range = 1 + addr6part(&c->end6) - addr6part(&c->start6); + u64 offset = j + c->addr_epoch; + + /* don't divide by zero if range is whole 2^64 */ + if (range != 0) + offset = offset % range; + + start = addr6part(&c->start6) + offset; + } /* iterate until we find a free address. */ addr = start; @@ -695,7 +672,7 @@ static int construct_worker(struct in6_addr *local, int prefix, /* address went, now it's back */ log_context(AF_INET6, context); /* fast RAs for a while */ - ra_start_unsolicted(param->now, context); + ra_start_unsolicited(param->now, context); param->newone = 1; /* Add address to name again */ if (context->flags & CONTEXT_RA_NAME) @@ -718,7 +695,7 @@ static int construct_worker(struct in6_addr *local, int prefix, context->next = daemon->dhcp6; daemon->dhcp6 = context; - ra_start_unsolicted(param->now, context); + ra_start_unsolicited(param->now, context); /* we created a new one, need to call lease_update_file to get periodic functions called */ param->newone = 1; @@ -766,7 +743,7 @@ void dhcp_construct_contexts(time_t now) /* maximum time is 2 hours, from RFC */ if (context->saved_valid > 7200) /* 2 hours */ context->saved_valid = 7200; - ra_start_unsolicted(now, context); + ra_start_unsolicited(now, context); param.newone = 1; /* include deletion */ if (context->flags & CONTEXT_RA_NAME) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 6cf5158..4958830 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -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 @@ -16,6 +16,8 @@ #define NAMESERVER_PORT 53 #define TFTP_PORT 69 +#define MIN_PORT 1024 /* first non-reserved port */ +#define MAX_PORT 65535u #define IN6ADDRSZ 16 #define INADDRSZ 4 @@ -77,6 +79,8 @@ #define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ #define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ +#define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */ +#define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */ struct dns_header { u16 id; diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 04d5758..ce44809 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.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 @@ -48,6 +48,7 @@ int main (int argc, char **argv) long i, max_fd = sysconf(_SC_OPEN_MAX); char *baduser = NULL; int log_err; + int chown_warn = 0; #if defined(HAVE_LINUX_NETWORK) cap_user_header_t hdr = NULL; cap_user_data_t data = NULL; @@ -77,7 +78,8 @@ int main (int argc, char **argv) sigaction(SIGTERM, &sigact, NULL); sigaction(SIGALRM, &sigact, NULL); sigaction(SIGCHLD, &sigact, NULL); - + sigaction(SIGINT, &sigact, NULL); + /* ignore SIGPIPE */ sigact.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sigact, NULL); @@ -91,8 +93,11 @@ int main (int argc, char **argv) if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = PACKETSZ; - daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? - daemon->edns_pktsz : DNSMASQ_PACKETSZ; + /* Min buffer size: we check after adding each record, so there must be + memory for the largest packet, and the largest record so the + min for DNS is PACKETSZ+MAXDNAME+RRFIXEDSZ which is < 1000. + This might be increased is EDNS packet size if greater than the minimum. */ + daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ; daemon->packet = safe_malloc(daemon->packet_buff_sz); daemon->addrbuff = safe_malloc(ADDRSTRLEN); @@ -115,6 +120,9 @@ int main (int argc, char **argv) daemon->namebuff = safe_malloc(MAXDNAME * 2); daemon->keyname = safe_malloc(MAXDNAME * 2); daemon->workspacename = safe_malloc(MAXDNAME * 2); + /* one char flag per possible RR in answer section (may get extended). */ + daemon->rr_status_sz = 64; + daemon->rr_status = safe_malloc(daemon->rr_status_sz); } #endif @@ -166,8 +174,16 @@ int main (int argc, char **argv) if (option_bool(OPT_DNSSEC_VALID)) { #ifdef HAVE_DNSSEC - if (!daemon->ds) - die(_("no trust anchors provided for DNSSEC"), NULL, EC_BADCONF); + struct ds_config *ds; + + /* Must have at least a root trust anchor, or the DNSSEC code + can loop forever. */ + for (ds = daemon->ds; ds; ds = ds->next) + if (ds->name[0] == 0) + break; + + if (!ds) + die(_("no root trust anchor provided for DNSSEC"), NULL, EC_BADCONF); if (daemon->cachesize < CACHESIZ) die(_("cannot reduce cache size from default when DNSSEC enabled"), NULL, EC_BADCONF); @@ -191,12 +207,12 @@ int main (int argc, char **argv) #ifdef HAVE_SOLARIS_NETWORK if (daemon->max_logs != 0) - die(_("asychronous logging is not available under Solaris"), NULL, EC_BADCONF); + die(_("asynchronous logging is not available under Solaris"), NULL, EC_BADCONF); #endif #ifdef __ANDROID__ if (daemon->max_logs != 0) - die(_("asychronous logging is not available under Android"), NULL, EC_BADCONF); + die(_("asynchronous logging is not available under Android"), NULL, EC_BADCONF); #endif #ifndef HAVE_AUTH @@ -208,7 +224,10 @@ int main (int argc, char **argv) if (option_bool(OPT_LOOP_DETECT)) die(_("loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF); #endif - + + if (daemon->max_port < daemon->min_port) + die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF); + now = dnsmasq_time(); /* Create a serial at startup if not configured. */ @@ -242,8 +261,11 @@ int main (int argc, char **argv) /* Note that order matters here, we must call lease_init before creating any file descriptors which shouldn't be leaked to the lease-script init process. We need to call common_init - before lease_init to allocate buffers it uses.*/ - if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || daemon->relay6) + before lease_init to allocate buffers it uses. + The script subsystem relies on DHCP buffers, hence the last two + conditions below. */ + if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || + daemon->relay6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) { dhcp_common_init(); if (daemon->dhcp || daemon->doing_dhcp6) @@ -338,7 +360,8 @@ int main (int argc, char **argv) } #ifdef HAVE_INOTIFY - if (daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6) + if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6) + && (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs)) inotify_dnsmasq_init(); else daemon->inotifyfd = -1; @@ -366,10 +389,12 @@ int main (int argc, char **argv) daemon->scriptuser && (daemon->lease_change_command || daemon->luascript)) { - if ((ent_pw = getpwnam(daemon->scriptuser))) + struct passwd *scr_pw; + + if ((scr_pw = getpwnam(daemon->scriptuser))) { - script_uid = ent_pw->pw_uid; - script_gid = ent_pw->pw_gid; + script_uid = scr_pw->pw_uid; + script_gid = scr_pw->pw_gid; } else baduser = daemon->scriptuser; @@ -494,7 +519,7 @@ int main (int argc, char **argv) extent that an attacker running as the unprivileged user could replace the pidfile with a symlink, and have the target of that symlink overwritten as root next time dnsmasq starts. - The folowing code first deletes any existing file, and then opens it with the O_EXCL flag, + The following code first deletes any existing file, and then opens it with the O_EXCL flag, ensuring that the open() fails should there be any existing file (because the unlink() failed, or an attacker exploited the race between unlink() and open()). This ensures that no symlink attack can succeed. @@ -517,9 +542,18 @@ int main (int argc, char **argv) } else { + /* We're still running as root here. Change the ownership of the PID file + to the user we will be running as. Note that this is not to allow + us to delete the file, since that depends on the permissions + of the directory containing the file. That directory will + need to by owned by the dnsmasq user, and the ownership of the + file has to match, to keep systemd >273 happy. */ + if (getuid() == 0 && ent_pw && ent_pw->pw_uid != 0 && fchown(fd, ent_pw->pw_uid, ent_pw->pw_gid) == -1) + chown_warn = errno; + if (!read_write(fd, (unsigned char *)daemon->namebuff, strlen(daemon->namebuff), 0)) err = 1; - else + else { while (retry_send(close(fd))); if (errno != 0) @@ -541,17 +575,21 @@ int main (int argc, char **argv) { /* open stdout etc to /dev/null */ int nullfd = open("/dev/null", O_RDWR); - dup2(nullfd, STDOUT_FILENO); - dup2(nullfd, STDERR_FILENO); - dup2(nullfd, STDIN_FILENO); - close(nullfd); + if (nullfd != -1) + { + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + dup2(nullfd, STDIN_FILENO); + close(nullfd); + } } /* if we are to run scripts, we need to fork a helper before dropping root. */ daemon->helperfd = -1; #ifdef HAVE_SCRIPT - if ((daemon->dhcp || daemon->dhcp6) && (daemon->lease_change_command || daemon->luascript)) - daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd); + if ((daemon->dhcp || daemon->dhcp6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) && + (daemon->lease_change_command || daemon->luascript)) + daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd); #endif if (!option_bool(OPT_DEBUG) && getuid() == 0) @@ -559,7 +597,7 @@ int main (int argc, char **argv) int bad_capabilities = 0; gid_t dummy; - /* remove all supplimentary groups */ + /* remove all supplementary groups */ if (gp && (setgroups(0, &dummy) == -1 || setgid(gp->gr_gid) == -1)) @@ -631,7 +669,7 @@ int main (int argc, char **argv) (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); data->inheritable = 0; - /* lose the setuid and setgid capbilities */ + /* lose the setuid and setgid capabilities */ if (capset(hdr, data) == -1) { send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL); @@ -690,12 +728,21 @@ int main (int argc, char **argv) if (daemon->port == 0) my_syslog(LOG_INFO, _("started, version %s DNS disabled"), VERSION); - else if (daemon->cachesize != 0) - my_syslog(LOG_INFO, _("started, version %s cachesize %d"), VERSION, daemon->cachesize); - else - my_syslog(LOG_INFO, _("started, version %s cache disabled"), VERSION); + else + { + if (daemon->cachesize != 0) + my_syslog(LOG_INFO, _("started, version %s cachesize %d"), VERSION, daemon->cachesize); + else + my_syslog(LOG_INFO, _("started, version %s cache disabled"), VERSION); + + if (option_bool(OPT_LOCAL_SERVICE)) + my_syslog(LOG_INFO, _("DNS service limited to local subnets")); + } my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts); + + if (chown_warn != 0) + my_syslog(LOG_WARNING, "chown of PID file %s failed: %s", daemon->runfile, strerror(chown_warn)); #ifdef HAVE_DBUS if (option_bool(OPT_DBUS)) @@ -707,9 +754,6 @@ int main (int argc, char **argv) } #endif - if (option_bool(OPT_LOCAL_SERVICE)) - my_syslog(LOG_INFO, _("DNS service limited to local subnets")); - #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) { @@ -726,8 +770,9 @@ int main (int argc, char **argv) my_syslog(LOG_INFO, _("DNSSEC validation enabled")); - if (option_bool(OPT_DNSSEC_TIME)) - my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until first cache reload")); + daemon->dnssec_no_time_check = option_bool(OPT_DNSSEC_TIME); + if (option_bool(OPT_DNSSEC_TIME) && !daemon->back_to_the_future) + my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until receipt of SIGINT")); if (rc == 1) my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid")); @@ -743,6 +788,8 @@ int main (int argc, char **argv) if (option_bool(OPT_NOWILD)) warn_bound_listeners(); + else if (!option_bool(OPT_CLEVERBIND)) + warn_wild_labels(); warn_int_names(); @@ -790,7 +837,7 @@ int main (int argc, char **argv) my_syslog(MS_DHCP | LOG_INFO, _("DHCP, sockets bound exclusively to interface %s"), bound_device); # endif - /* after dhcp_contruct_contexts */ + /* after dhcp_construct_contexts */ if (daemon->dhcp || daemon->doing_dhcp6) lease_find_interfaces(now); #endif @@ -911,9 +958,15 @@ int main (int argc, char **argv) poll_listen(piperead, POLLIN); -#ifdef HAVE_DHCP -# ifdef HAVE_SCRIPT - while (helper_buf_empty() && do_script_run(now)); +#ifdef HAVE_SCRIPT +# ifdef HAVE_DHCP + while (helper_buf_empty() && do_script_run(now)); +# endif + + /* Refresh cache */ + if (option_bool(OPT_SCRIPT_ARP)) + find_mac(NULL, NULL, 0, now); + while (helper_buf_empty() && do_arp_script_run()); # ifdef HAVE_TFTP while (helper_buf_empty() && do_tftp_script_run()); @@ -921,16 +974,20 @@ int main (int argc, char **argv) if (!helper_buf_empty()) poll_listen(daemon->helperfd, POLLOUT); -# else +#else /* need this for other side-effects */ +# ifdef HAVE_DHCP while (do_script_run(now)); +# endif + + while (do_arp_script_run()); # ifdef HAVE_TFTP while (do_tftp_script_run()); # endif -# endif #endif + /* must do this just before select(), when we know no more calls to my_syslog() can occur */ @@ -1025,7 +1082,7 @@ int main (int argc, char **argv) #endif # ifdef HAVE_SCRIPT - if (daemon->helperfd != -1 && poll_check(daemon->helperfd, POLLIN)) + if (daemon->helperfd != -1 && poll_check(daemon->helperfd, POLLOUT)) helper_write(); # endif #endif @@ -1039,7 +1096,7 @@ static void sig_handler(int sig) { /* ignore anything other than TERM during startup and in helper proc. (helper ignore TERM too) */ - if (sig == SIGTERM) + if (sig == SIGTERM || sig == SIGINT) exit(EC_MISC); } else if (pid != getpid()) @@ -1065,6 +1122,15 @@ static void sig_handler(int sig) event = EVENT_DUMP; else if (sig == SIGUSR2) event = EVENT_REOPEN; + else if (sig == SIGINT) + { + /* Handle SIGINT normally in debug mode, so + ctrl-c continues to operate. */ + if (option_bool(OPT_DEBUG)) + exit(EC_MISC); + else + event = EVENT_TIME; + } else return; @@ -1147,31 +1213,40 @@ static void fatal_event(struct event_desc *ev, char *msg) case EVENT_FORK_ERR: die(_("cannot fork into background: %s"), NULL, EC_MISC); - + + /* fall through */ case EVENT_PIPE_ERR: die(_("failed to create helper: %s"), NULL, EC_MISC); - + + /* fall through */ case EVENT_CAP_ERR: die(_("setting capabilities failed: %s"), NULL, EC_MISC); + /* fall through */ case EVENT_USER_ERR: die(_("failed to change user-id to %s: %s"), msg, EC_MISC); + /* fall through */ case EVENT_GROUP_ERR: die(_("failed to change group-id to %s: %s"), msg, EC_MISC); - + + /* fall through */ case EVENT_PIDFILE: die(_("failed to open pidfile %s: %s"), msg, EC_FILE); + /* fall through */ case EVENT_LOG_ERR: die(_("cannot open log %s: %s"), msg, EC_FILE); - + + /* fall through */ case EVENT_LUA_ERR: die(_("failed to load Lua script: %s"), msg, EC_MISC); + /* fall through */ case EVENT_TFTP_ERR: die(_("TFTP directory %s inaccessible: %s"), msg, EC_FILE); - + + /* fall through */ case EVENT_TIME_ERR: die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF); } @@ -1191,13 +1266,8 @@ static void async_event(int pipe, time_t now) switch (ev.event) { case EVENT_RELOAD: -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && option_bool(OPT_DNSSEC_TIME)) - { - my_syslog(LOG_INFO, _("now checking DNSSEC signature timestamps")); - reset_option_bool(OPT_DNSSEC_TIME); - } -#endif + daemon->soa_sn++; /* Bump zone serial, as it may have changed. */ + /* fall through */ case EVENT_INIT: @@ -1260,6 +1330,7 @@ static void async_event(int pipe, time_t now) daemon->tcp_pids[i] = 0; break; +#if defined(HAVE_SCRIPT) case EVENT_KILLED: my_syslog(LOG_WARNING, _("script process killed by signal %d"), ev.data); break; @@ -1273,12 +1344,19 @@ static void async_event(int pipe, time_t now) daemon->lease_change_command, strerror(ev.data)); break; + case EVENT_SCRIPT_LOG: + my_syslog(MS_SCRIPT | LOG_DEBUG, "%s", msg ? msg : ""); + free(msg); + msg = NULL; + break; + /* necessary for fatal errors in helper */ case EVENT_USER_ERR: case EVENT_DIE: case EVENT_LUA_ERR: fatal_event(&ev, msg); break; +#endif case EVENT_REOPEN: /* Note: this may leave TCP-handling processes with the old file still open. @@ -1298,13 +1376,24 @@ static void async_event(int pipe, time_t now) poll_resolv(0, 1, now); break; + case EVENT_TIME: +#ifdef HAVE_DNSSEC + if (daemon->dnssec_no_time_check && option_bool(OPT_DNSSEC_VALID) && option_bool(OPT_DNSSEC_TIME)) + { + my_syslog(LOG_INFO, _("now checking DNSSEC signature timestamps")); + daemon->dnssec_no_time_check = 0; + clear_cache_and_reload(now); + } +#endif + break; + case EVENT_TERM: /* Knock all our children on the head. */ for (i = 0; i < MAX_PROCS; i++) if (daemon->tcp_pids[i] != 0) kill(daemon->tcp_pids[i], SIGALRM); -#if defined(HAVE_SCRIPT) +#if defined(HAVE_SCRIPT) && defined(HAVE_DHCP) /* handle pending lease transitions */ if (daemon->helperfd != -1) { @@ -1325,7 +1414,7 @@ static void async_event(int pipe, time_t now) /* update timestamp file on TERM if time is considered valid */ if (daemon->back_to_the_future) { - if (utime(daemon->timestamp_file, NULL) == -1) + if (utimes(daemon->timestamp_file, NULL) == -1) my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); } #endif @@ -1422,9 +1511,6 @@ void clear_cache_and_reload(time_t now) if (option_bool(OPT_ETHERS)) dhcp_read_ethers(); reread_dhcp(); -#ifdef HAVE_INOTIFY - set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0); -#endif dhcp_update_configs(daemon->dhcp_conf); lease_update_from_configs(); lease_update_file(now); @@ -1640,7 +1726,7 @@ static void check_dns_listeners(time_t now) } #ifndef NO_FORK - /* Arrange for SIGALARM after CHILD_LIFETIME seconds to + /* Arrange for SIGALRM after CHILD_LIFETIME seconds to terminate the process. */ if (!option_bool(OPT_DEBUG)) alarm(CHILD_LIFETIME); @@ -1705,29 +1791,15 @@ int icmp_ping(struct in_addr addr) { /* Try and get an ICMP echo from a machine. */ - /* Note that whilst in the three second wait, we check for - (and service) events on the DNS and TFTP sockets, (so doing that - better not use any resources our caller has in use...) - but we remain deaf to signals or further DHCP packets. */ - - /* There can be a problem using dnsmasq_time() to end the loop, since - it's not monotonic, and can go backwards if the system clock is - tweaked, leading to the code getting stuck in this loop and - ignoring DHCP requests. To fix this, we check to see if select returned - as a result of a timeout rather than a socket becoming available. We - only allow this to happen as many times as it takes to get to the wait time - in quarter-second chunks. This provides a fallback way to end loop. */ - - int fd, rc; + int fd; struct sockaddr_in saddr; struct { struct ip ip; struct icmp icmp; } packet; unsigned short id = rand16(); - unsigned int i, j, timeout_count; + unsigned int i, j; int gotreply = 0; - time_t start, now; #if defined(HAVE_LINUX_NETWORK) || defined (HAVE_SOLARIS_NETWORK) if ((fd = make_icmp_sock()) == -1) @@ -1757,14 +1829,46 @@ int icmp_ping(struct in_addr addr) while (retry_send(sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, (struct sockaddr *)&saddr, sizeof(saddr)))); - for (now = start = dnsmasq_time(), timeout_count = 0; - (difftime(now, start) < (float)PING_WAIT) && (timeout_count < PING_WAIT * 4);) + gotreply = delay_dhcp(dnsmasq_time(), PING_WAIT, fd, addr.s_addr, id); + +#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK) + while (retry_send(close(fd))); +#else + opt = 1; + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +#endif + + return gotreply; +} + +int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) +{ + /* Delay processing DHCP packets for "sec" seconds counting from "start". + If "fd" is not -1 it will stop waiting if an ICMP echo reply is received + from "addr" with ICMP ID "id" and return 1 */ + + /* Note that whilst waiting, we check for + (and service) events on the DNS and TFTP sockets, (so doing that + better not use any resources our caller has in use...) + but we remain deaf to signals or further DHCP packets. */ + + /* There can be a problem using dnsmasq_time() to end the loop, since + it's not monotonic, and can go backwards if the system clock is + tweaked, leading to the code getting stuck in this loop and + ignoring DHCP requests. To fix this, we check to see if select returned + as a result of a timeout rather than a socket becoming available. We + only allow this to happen as many times as it takes to get to the wait time + in quarter-second chunks. This provides a fallback way to end loop. */ + + int rc, timeout_count; + time_t now; + + for (now = dnsmasq_time(), timeout_count = 0; + (difftime(now, start) <= (float)sec) && (timeout_count < sec * 4);) { - struct sockaddr_in faddr; - socklen_t len = sizeof(faddr); - poll_reset(); - poll_listen(fd, POLLIN); + if (fd != -1) + poll_listen(fd, POLLIN); set_dns_listeners(now); set_log_writer(); @@ -1781,10 +1885,10 @@ int icmp_ping(struct in_addr addr) timeout_count++; now = dnsmasq_time(); - + check_log_writer(0); check_dns_listeners(now); - + #ifdef HAVE_DHCP6 if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN)) icmp6_packet(now); @@ -1794,27 +1898,26 @@ int icmp_ping(struct in_addr addr) check_tftp_listeners(now); #endif - if (poll_check(fd, POLLIN) && - recvfrom(fd, &packet, sizeof(packet), 0, - (struct sockaddr *)&faddr, &len) == sizeof(packet) && - saddr.sin_addr.s_addr == faddr.sin_addr.s_addr && - packet.icmp.icmp_type == ICMP_ECHOREPLY && - packet.icmp.icmp_seq == 0 && - packet.icmp.icmp_id == id) - { - gotreply = 1; - break; + if (fd != -1) + { + struct { + struct ip ip; + struct icmp icmp; + } packet; + struct sockaddr_in faddr; + socklen_t len = sizeof(faddr); + + if (poll_check(fd, POLLIN) && + recvfrom(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&faddr, &len) == sizeof(packet) && + addr == faddr.sin_addr.s_addr && + packet.icmp.icmp_type == ICMP_ECHOREPLY && + packet.icmp.icmp_seq == 0 && + packet.icmp.icmp_id == id) + return 1; } } - -#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK) - while (retry_send(close(fd))); -#else - opt = 1; - setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); -#endif - return gotreply; + return 0; } #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index ef15e6d..6773b69 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -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 @@ -14,7 +14,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define COPYRIGHT "Copyright (c) 2000-2015 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2018 Simon Kelley" + +/* We do defines that influence behavior of stdio.h, so complain + if included too early. */ +#ifdef _STDIO_H +# error "Header file stdio.h included too early!" +#endif #ifndef NO_LARGEFILE /* Ensure we can use files >2GB (log files may grow this big) */ @@ -117,7 +123,6 @@ typedef unsigned long long u64; #include <sys/uio.h> #include <syslog.h> #include <dirent.h> -#include <utime.h> #ifndef HAVE_LINUX_NETWORK # include <net/if_dl.h> #endif @@ -125,7 +130,7 @@ typedef unsigned long long u64; #if defined(HAVE_LINUX_NETWORK) #include <linux/capability.h> /* There doesn't seem to be a universally-available - userpace header for these. */ + userspace header for these. */ extern int capset(cap_user_header_t header, cap_user_data_t data); extern int capget(cap_user_header_t header, cap_user_data_t data); #define LINUX_CAPABILITY_VERSION_1 0x19980330 @@ -137,6 +142,10 @@ extern int capget(cap_user_header_t header, cap_user_data_t data); #include <priv.h> #endif +#ifdef HAVE_DNSSEC +# include <nettle/nettle-meta.h> +#endif + /* daemon is function in the C library.... */ #define daemon dnsmasq_daemon @@ -145,30 +154,32 @@ struct event_desc { int event, data, msg_sz; }; -#define EVENT_RELOAD 1 -#define EVENT_DUMP 2 -#define EVENT_ALARM 3 -#define EVENT_TERM 4 -#define EVENT_CHILD 5 -#define EVENT_REOPEN 6 -#define EVENT_EXITED 7 -#define EVENT_KILLED 8 -#define EVENT_EXEC_ERR 9 -#define EVENT_PIPE_ERR 10 -#define EVENT_USER_ERR 11 -#define EVENT_CAP_ERR 12 -#define EVENT_PIDFILE 13 -#define EVENT_HUSER_ERR 14 -#define EVENT_GROUP_ERR 15 -#define EVENT_DIE 16 -#define EVENT_LOG_ERR 17 -#define EVENT_FORK_ERR 18 -#define EVENT_LUA_ERR 19 -#define EVENT_TFTP_ERR 20 -#define EVENT_INIT 21 -#define EVENT_NEWADDR 22 -#define EVENT_NEWROUTE 23 -#define EVENT_TIME_ERR 24 +#define EVENT_RELOAD 1 +#define EVENT_DUMP 2 +#define EVENT_ALARM 3 +#define EVENT_TERM 4 +#define EVENT_CHILD 5 +#define EVENT_REOPEN 6 +#define EVENT_EXITED 7 +#define EVENT_KILLED 8 +#define EVENT_EXEC_ERR 9 +#define EVENT_PIPE_ERR 10 +#define EVENT_USER_ERR 11 +#define EVENT_CAP_ERR 12 +#define EVENT_PIDFILE 13 +#define EVENT_HUSER_ERR 14 +#define EVENT_GROUP_ERR 15 +#define EVENT_DIE 16 +#define EVENT_LOG_ERR 17 +#define EVENT_FORK_ERR 18 +#define EVENT_LUA_ERR 19 +#define EVENT_TFTP_ERR 20 +#define EVENT_INIT 21 +#define EVENT_NEWADDR 22 +#define EVENT_NEWROUTE 23 +#define EVENT_TIME_ERR 24 +#define EVENT_SCRIPT_LOG 25 +#define EVENT_TIME 26 /* Exit codes. */ #define EC_GOOD 0 @@ -179,13 +190,6 @@ struct event_desc { #define EC_MISC 5 #define EC_INIT_OFFSET 10 -/* Min buffer size: we check after adding each record, so there must be - memory for the largest packet, and the largest record so the - min for DNS is PACKETSZ+MAXDNAME+RRFIXEDSZ which is < 1000. - This might be increased is EDNS packet size if greater than the minimum. -*/ -#define DNSMASQ_PACKETSZ PACKETSZ+MAXDNAME+RRFIXEDSZ - /* Trust the compiler dead-code eliminator.... */ #define option_bool(x) (((x) < 32) ? daemon->options & (1u << (x)) : daemon->options2 & (1u << ((x) - 32))) @@ -218,7 +222,7 @@ struct event_desc { #define OPT_TFTP_SECURE 26 #define OPT_TFTP_NOBLOCK 27 #define OPT_LOG_OPTS 28 -#define OPT_TFTP_APREF 29 +#define OPT_TFTP_APREF_IP 29 #define OPT_NO_OVERRIDE 30 #define OPT_NO_REBIND 31 #define OPT_ADD_MAC 32 @@ -242,12 +246,17 @@ struct event_desc { #define OPT_LOOP_DETECT 50 #define OPT_EXTRALOG 51 #define OPT_TFTP_NO_FAIL 52 -#define OPT_LAST 53 +#define OPT_SCRIPT_ARP 53 +#define OPT_MAC_B64 54 +#define OPT_MAC_HEX 55 +#define OPT_TFTP_APREF_MAC 56 +#define OPT_LAST 57 /* 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. */ -#define MS_TFTP LOG_USER -#define MS_DHCP LOG_DAEMON +#define MS_TFTP LOG_USER +#define MS_DHCP LOG_DAEMON +#define MS_SCRIPT LOG_MAIL struct all_addr { union { @@ -256,8 +265,10 @@ struct all_addr { struct in6_addr addr6; #endif /* for log_query */ - unsigned int keytag; - /* for cache_insert if RRSIG, DNSKEY, DS */ + struct { + unsigned short keytag, algo, digest; + } log; + /* for cache_insert of DNSKEY, DS */ struct { unsigned short class, type; } dnssec; @@ -288,6 +299,7 @@ struct naptr { struct naptr *next; }; +#ifndef NO_ID #define TXT_STAT_CACHESIZE 1 #define TXT_STAT_INSERTS 2 #define TXT_STAT_EVICTIONS 3 @@ -295,6 +307,7 @@ struct naptr { #define TXT_STAT_HITS 5 #define TXT_STAT_AUTH 6 #define TXT_STAT_SERVERS 7 +#endif struct txt_record { char *name; @@ -310,8 +323,9 @@ struct ptr_record { }; struct cname { + int ttl, flag; char *alias, *target; - struct cname *next; + struct cname *next, *targetp; }; struct ds_config { @@ -341,11 +355,13 @@ struct auth_zone { struct auth_name_list *next; } *interface_names; struct addrlist *subnet; + struct addrlist *exclude; struct auth_zone *next; }; struct host_record { + int ttl; struct name_list { char *name; struct name_list *next; @@ -398,14 +414,9 @@ struct crec { unsigned char algo; unsigned char digest; } ds; - struct { - struct blockdata *keydata; - unsigned short keylen, type_covered, keytag; - char algo; - } sig; } addr; time_t ttd; /* time to die */ - /* used as class if DNSKEY/DS/RRSIG, index to source for F_HOSTS */ + /* used as class if DNSKEY/DS, index to source for F_HOSTS */ unsigned int uid; unsigned short flags; union { @@ -445,8 +456,8 @@ struct crec { #define F_SECSTAT (1u<<24) #define F_NO_RR (1u<<25) #define F_IPSET (1u<<26) -#define F_NSIGMATCH (1u<<27) -#define F_NOEXTRA (1u<<28) +#define F_NOEXTRA (1u<<27) +#define F_SERVFAIL (1u<<28) /* Values of uid in crecs with F_CONFIG bit set. */ #define SRC_INTERFACE 0 @@ -487,11 +498,14 @@ union mysockaddr { #define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */ #define SERV_FROM_FILE 4096 /* read from --servers-file */ #define SERV_LOOP 8192 /* server causes forwarding loop */ +#define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */ +#define SERV_GOT_TCP 32768 /* Got some data from the TCP connection */ struct serverfd { int fd; union mysockaddr source_addr; char interface[IF_NAMESIZE+1]; + unsigned int ifindex, used; struct serverfd *next; }; @@ -506,6 +520,7 @@ struct server { struct serverfd *sfd; char *domain; /* set if this server only handles a domain. */ int flags, tcpfd, edns_pktsz; + time_t pktsz_reduced; unsigned int queries, failed_queries; #ifdef HAVE_LOOP u32 uid; @@ -522,7 +537,7 @@ struct ipsets { struct irec { union mysockaddr addr; struct in_addr netmask; /* only valid for IPv4 */ - int tftp_ok, dhcp_ok, mtu, done, warned, dad, dns_auth, index, multicast_done, found; + int tftp_ok, dhcp_ok, mtu, done, warned, dad, dns_auth, index, multicast_done, found, label; char *name; struct irec *next; }; @@ -541,6 +556,13 @@ struct iname { struct iname *next; }; +/* subnet parameters from command line */ +struct mysubnet { + union mysockaddr addr; + int addr_used; + int mask; +}; + /* resolv-file parms from command-line */ struct resolvc { struct resolvc *next; @@ -579,12 +601,8 @@ 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_NO_NS 10 -#define STAT_NEED_DS_NEG 11 -#define STAT_CHASE_CNAME 12 -#define STAT_INSECURE_DS 13 +#define STAT_OK 8 +#define STAT_ABANDONED 9 #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 @@ -594,8 +612,8 @@ struct hostsfile { #define FREC_AD_QUESTION 32 #define FREC_DO_QUESTION 64 #define FREC_ADDED_PHEADER 128 -#define FREC_CHECK_NOSIGN 256 -#define FREC_TEST_PKTSZ 512 +#define FREC_TEST_PKTSZ 256 +#define FREC_HAS_EXTRADATA 512 #ifdef HAVE_DNSSEC #define HASH_SIZE 20 /* SHA-1 digest size */ @@ -619,9 +637,7 @@ struct frec { #ifdef HAVE_DNSSEC int class, work_counter; struct blockdata *stash; /* Saved reply, whilst we validate */ - struct blockdata *orig_domain; /* domain of original query, whilst - we're seeing is if in unsigned domain */ - size_t stash_len, name_start, name_len; + 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 @@ -643,6 +659,8 @@ struct frec { #define ACTION_OLD 3 #define ACTION_ADD 4 #define ACTION_TFTP 5 +#define ACTION_ARP 6 +#define ACTION_ARP_DEL 7 #define LEASE_NEW 1 /* newly created */ #define LEASE_CHANGED 2 /* modified */ @@ -701,6 +719,12 @@ struct tag_if { struct tag_if *next; }; +struct delay_config { + int delay; + struct dhcp_netid *netid; + struct delay_config *next; +}; + struct hwaddr_config { int hwaddr_len, hwaddr_type; unsigned char hwaddr[DHCP_CHADDR_MAX]; @@ -787,7 +811,7 @@ struct pxe_service { #define MATCH_REMOTE 4 #define MATCH_SUBSCRIBER 5 -/* vendorclass, userclass, remote-id or cicuit-id */ +/* vendorclass, userclass, remote-id or circuit-id */ struct dhcp_vendor { int len, match_type; unsigned int enterprise; @@ -815,7 +839,7 @@ struct cond_domain { #ifdef HAVE_IPV6 struct in6_addr start6, end6; #endif - int is6; + int is6, indexed; struct cond_domain *next; }; @@ -829,7 +853,8 @@ struct prefix_class { struct ra_interface { char *name; - int interval, lifetime, prio; + char *mtu_name; + int interval, lifetime, prio, mtu; struct ra_interface *next; }; @@ -935,9 +960,9 @@ extern struct daemon { struct auth_zone *auth_zones; struct interface_name *int_names; char *mxtarget; - int addr4_netmask; - int addr6_netmask; - char *lease_file; + struct mysubnet *add_subnet4; + struct mysubnet *add_subnet6; + char *lease_file; char *username, *groupname, *scriptuser; char *luascript; char *authserver, *hostmaster; @@ -956,8 +981,9 @@ extern struct daemon { char *log_file; /* optional log file */ int max_logs; /* queue limit */ int cachesize, ftabsize; - int port, query_port, min_port; - unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl; + int port, query_port, min_port, max_port; + unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl; + char *dns_client_id; struct hostsfile *addn_hosts; struct dhcp_context *dhcp, *dhcp6; struct ra_interface *ra_interfaces; @@ -970,13 +996,14 @@ extern struct daemon { struct tag_if *tag_if; struct addr_list *override_relays; struct dhcp_relay *relay4, *relay6; + struct delay_config *delay_conf; int override; int enable_pxe; int doing_ra, doing_dhcp6; struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names; struct dhcp_netid_list *force_broadcast, *bootp_dynamic; struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs; - int dhcp_max, tftp_max; + int dhcp_max, tftp_max, tftp_mtu; int dhcp_server_port, dhcp_client_port; int start_tftp_port, end_tftp_port; unsigned int min_leasetime; @@ -993,6 +1020,7 @@ extern struct daemon { #endif #ifdef HAVE_DNSSEC struct ds_config *ds; + int dnssec_no_time_check; int back_to_the_future; char *timestamp_file; #endif @@ -1004,6 +1032,8 @@ extern struct daemon { #ifdef HAVE_DNSSEC char *keyname; /* MAXDNAME size buffer */ char *workspacename; /* ditto */ + char *rr_status; /* flags for individual RRs */ + int rr_status_sz; #endif unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; @@ -1079,7 +1109,9 @@ void cache_add_dhcp_entry(char *host_name, int prot, struct all_addr *host_addre struct in_addr a_record_from_hosts(char *name, time_t now); void cache_unhash_dhcp(void); void dump_cache(time_t now); +#ifndef NO_ID int cache_make_stat(struct txt_record *t); +#endif char *cache_get_name(struct crec *crecp); char *cache_get_cname_target(struct crec *crecp); struct crec *cache_enumerate(int init); @@ -1113,28 +1145,20 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); size_t setup_reply(struct dns_header *header, size_t qlen, struct all_addr *addrp, unsigned int flags, - unsigned long local_ttl); -int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, - time_t now, char **ipsets, int is_sign, int checkrebind, - int no_cache, int secure, int *doctored); + unsigned long ttl); +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); 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); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, - struct bogus_addr *addr, time_t now); + struct bogus_addr *baddr, time_t now); int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); -unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, - size_t *len, unsigned char **p, int *is_sign); int check_for_local_domain(char *name, time_t now); -unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff); +unsigned int questions_crc(struct dns_header *header, size_t plen, char *name); 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, int *offset, unsigned short type, unsigned short class, char *format, ...); @@ -1147,28 +1171,38 @@ int private_net(struct in_addr addr, int ban_localhost); /* auth.c */ #ifdef HAVE_AUTH size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, - time_t now, union mysockaddr *peer_addr, int local_query); + time_t now, union mysockaddr *peer_addr, int local_query, + int do_bit, int have_pseudoheader); int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); -int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); +size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, 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 *neganswer, int *nons); -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); +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, + int check_unsigned, int *neganswer, int *nons); +int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); int setup_timestamp(void); +/* crypto.c */ +const struct nettle_hash *hash_find(char *name); +int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp); +int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo); +char *ds_digest_name(int digest); +char *algo_digest_name(int algo); +char *nsec3_digest_name(int digest); + /* util.c */ void rand_init(void); unsigned short rand16(void); u32 rand32(void); u64 rand64(void); -int legal_hostname(char *c); -char *canonicalise(char *s, int *nomem); +int legal_hostname(char *name); +char *canonicalise(char *in, int *nomem); unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit); void *safe_malloc(size_t size); void safe_pipe(int *fd, int read_noblock); @@ -1202,7 +1236,9 @@ int wildcard_matchn(const char* wildcard, const char* match, int num); void die(char *message, char *arg1, int exit_code); int log_start(struct passwd *ent_pw, int errfd); int log_reopen(char *log_file); + void my_syslog(int priority, const char *format, ...); + void set_log_writer(void); void check_log_writer(int force); void flush_log(void); @@ -1230,13 +1266,13 @@ 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); -void resend_query(); +void resend_query(void); struct randfd *allocate_rfd(int family); void free_rfd(struct randfd *rfd); /* network.c */ int indextoname(int fd, int index, char *name); -int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp); +int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); int random_sock(int family); void pre_allocate_sfds(void); int reload_servers(char *fname); @@ -1250,11 +1286,12 @@ void add_update_server(int flags, void check_servers(void); int enumerate_interfaces(int reset); void create_wildcard_listeners(void); -void create_bound_listeners(int die); +void create_bound_listeners(int dienow); void warn_bound_listeners(void); +void warn_wild_labels(void); void warn_int_names(void); int is_dad_listeners(void); -int iface_check(int family, struct all_addr *addr, char *name, int *auth_dns); +int iface_check(int family, struct all_addr *addr, char *name, int *auth); int loopback_exception(int fd, int family, struct all_addr *addr, char *name); int label_exception(int index, int family, struct all_addr *addr); int fix_fd(int fd); @@ -1275,14 +1312,16 @@ void newaddress(time_t now); void dhcp_init(void); void dhcp_packet(time_t now, int pxe_fd); struct dhcp_context *address_available(struct dhcp_context *context, - struct in_addr addr, + struct in_addr taddr, struct dhcp_netid *netids); struct dhcp_context *narrow_context(struct dhcp_context *context, struct in_addr taddr, struct dhcp_netid *netids); +struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, + unsigned int hash, int loopback); int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, - struct dhcp_netid *netids, time_t now); + struct dhcp_netid *netids, time_t now, int loopback); void dhcp_read_ethers(void); struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr); char *host_from_dns(struct in_addr addr); @@ -1331,7 +1370,8 @@ void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, /* rfc2131.c */ #ifdef HAVE_DHCP size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, - size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe_fd, struct in_addr fallback); + size_t sz, time_t now, int unicast_dest, int loopback, + int *is_inform, int pxe, struct in_addr fallback, time_t recvtime); unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, int clid_len, unsigned char *clid, int *len_out); #endif @@ -1340,6 +1380,7 @@ unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, #ifdef HAVE_DHCP int make_icmp_sock(void); int icmp_ping(struct in_addr addr); +int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id); #endif void queue_event(int event); void send_alarm(time_t event, time_t now); @@ -1389,6 +1430,8 @@ void queue_script(int action, struct dhcp_lease *lease, #ifdef HAVE_TFTP void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer); #endif +void queue_arp(int action, unsigned char *mac, int maclen, + int family, struct all_addr *addr); int helper_buf_empty(void); #endif @@ -1425,7 +1468,7 @@ struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct void make_duid(time_t now); void dhcp_construct_contexts(time_t now); void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, - unsigned int *maclenp, unsigned int *mactypep); + unsigned int *maclenp, unsigned int *mactypep, time_t now); #endif /* rfc3315.c */ @@ -1433,7 +1476,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr, size_t sz, struct in6_addr *client_addr, time_t now); -void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id); +void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, + u32 scope_id, time_t now); unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); #endif @@ -1442,10 +1486,10 @@ unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arriva #ifdef HAVE_DHCP void dhcp_common_init(void); ssize_t recv_dhcp_packet(int fd, struct msghdr *msg); -struct dhcp_netid *run_tag_if(struct dhcp_netid *input); +struct dhcp_netid *run_tag_if(struct dhcp_netid *tags); struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts); -int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int negonly); +int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded); char *strip_hostname(char *hostname); void log_tags(struct dhcp_netid *netid, u32 xid); int match_bytes(struct dhcp_opt *o, unsigned char *p, int len); @@ -1475,6 +1519,7 @@ void log_relay(int family, struct dhcp_relay *relay); /* outpacket.c */ #ifdef HAVE_DHCP6 void end_opt6(int container); +void reset_counter(void); int save_counter(int newval); void *expand(size_t headroom); int new_opt6(int opt); @@ -1490,7 +1535,7 @@ void put_opt6_string(char *s); void ra_init(time_t now); void icmp6_packet(time_t now); time_t periodic_ra(time_t now); -void ra_start_unsolicted(time_t now, struct dhcp_context *context); +void ra_start_unsolicited(time_t now, struct dhcp_context *context); #endif /* slaac.c */ @@ -1502,13 +1547,13 @@ void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *inte /* loop.c */ #ifdef HAVE_LOOP -void loop_send_probes(); +void loop_send_probes(void); int detect_loop(char *query, int type); #endif /* inotify.c */ #ifdef HAVE_INOTIFY -void inotify_dnsmasq_init(); +void inotify_dnsmasq_init(void); int inotify_check(time_t now); void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz); #endif @@ -1519,3 +1564,21 @@ int poll_check(int fd, short event); void poll_listen(int fd, short event); int do_poll(int timeout); +/* rrfilter.c */ +size_t rrfilter(struct dns_header *header, size_t plen, int mode); +u16 *rrfilter_desc(int type); +int expand_workspace(unsigned char ***wkspc, int *szp, int new); + +/* edns0.c */ +unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, + size_t *len, unsigned char **p, int *is_sign, int *is_last); +size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, + unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace); +size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit); +size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *source, time_t now, int *check_subnet); +int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); + +/* arp.c */ +int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now); +int do_arp_script_run(void); diff --git a/src/dnssec.c b/src/dnssec.c index 830f304..8143185 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1,5 +1,5 @@ /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com> - and Copyright (c) 2012-2015 Simon Kelley + and Copyright (c) 2012-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 @@ -19,299 +19,11 @@ #ifdef HAVE_DNSSEC -#include <nettle/rsa.h> -#include <nettle/dsa.h> -#ifndef NO_NETTLE_ECC -# include <nettle/ecdsa.h> -# include <nettle/ecc-curve.h> -#endif -#include <nettle/nettle-meta.h> -#include <nettle/bignum.h> - -/* Nettle-3.0 moved to a new API for DSA. We use a name that's defined in the new API - to detect Nettle-3, and invoke the backwards compatibility mode. */ -#ifdef dsa_params_init -#include <nettle/dsa-compat.h> -#endif - #define SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 #define SERIAL_GT 1 -/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -static char *ds_digest_name(int digest) -{ - switch (digest) - { - case 1: return "sha1"; - case 2: return "sha256"; - case 3: return "gosthash94"; - case 4: return "sha384"; - default: return NULL; - } -} - -/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -static char *algo_digest_name(int algo) -{ - switch (algo) - { - case 1: return "md5"; - case 3: return "sha1"; - case 5: return "sha1"; - case 6: return "sha1"; - case 7: return "sha1"; - case 8: return "sha256"; - case 10: return "sha512"; - case 12: return "gosthash94"; - case 13: return "sha256"; - case 14: return "sha384"; - default: return NULL; - } -} - -/* Find pointer to correct hash function in nettle library */ -static const struct nettle_hash *hash_find(char *name) -{ - int i; - - if (!name) - return NULL; - - for (i = 0; nettle_hashes[i]; i++) - { - if (strcmp(nettle_hashes[i]->name, name) == 0) - return nettle_hashes[i]; - } - - return NULL; -} - -/* expand ctx and digest memory allocations if necessary and init hash function */ -static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) -{ - static void *ctx = NULL; - static unsigned char *digest = NULL; - static unsigned int ctx_sz = 0; - static unsigned int digest_sz = 0; - - void *new; - - if (ctx_sz < hash->context_size) - { - if (!(new = whine_malloc(hash->context_size))) - return 0; - if (ctx) - free(ctx); - ctx = new; - ctx_sz = hash->context_size; - } - - if (digest_sz < hash->digest_size) - { - if (!(new = whine_malloc(hash->digest_size))) - return 0; - if (digest) - free(digest); - digest = new; - digest_sz = hash->digest_size; - } - - *ctxp = ctx; - *digestp = digest; - - hash->init(ctx); - - return 1; -} - -static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, int algo) -{ - unsigned char *p; - size_t exp_len; - - static struct rsa_public_key *key = NULL; - static mpz_t sig_mpz; - - if (key == NULL) - { - if (!(key = whine_malloc(sizeof(struct rsa_public_key)))) - return 0; - - nettle_rsa_public_key_init(key); - mpz_init(sig_mpz); - } - - if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - key_len--; - if ((exp_len = *p++) == 0) - { - GETSHORT(exp_len, p); - key_len -= 2; - } - - if (exp_len >= key_len) - return 0; - - key->size = key_len - exp_len; - mpz_import(key->e, exp_len, 1, 1, 0, 0, p); - mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len); - - mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig); - - switch (algo) - { - case 1: - return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); - case 5: case 7: - return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); - case 8: - return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz); - case 10: - return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz); - } - - return 0; -} - -static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, int algo) -{ - unsigned char *p; - unsigned int t; - - static struct dsa_public_key *key = NULL; - static struct dsa_signature *sig_struct; - - if (key == NULL) - { - if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || - !(key = whine_malloc(sizeof(struct dsa_public_key)))) - return 0; - - nettle_dsa_public_key_init(key); - nettle_dsa_signature_init(sig_struct); - } - - if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - t = *p++; - - if (key_len < (213 + (t * 24))) - return 0; - - mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20; - mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); - - mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); - mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); - - (void)algo; - - return nettle_dsa_sha1_verify_digest(key, digest, sig_struct); -} - -#ifndef NO_NETTLE_ECC -static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, - unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - unsigned char *p; - unsigned int t; - struct ecc_point *key; - - static struct ecc_point *key_256 = NULL, *key_384 = NULL; - static mpz_t x, y; - static struct dsa_signature *sig_struct; - - if (!sig_struct) - { - if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature)))) - return 0; - - nettle_dsa_signature_init(sig_struct); - mpz_init(x); - mpz_init(y); - } - - switch (algo) - { - case 13: - if (!key_256) - { - if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) - return 0; - - nettle_ecc_point_init(key_256, &nettle_secp_256r1); - } - - key = key_256; - t = 32; - break; - - case 14: - if (!key_384) - { - if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) - return 0; - - nettle_ecc_point_init(key_384, &nettle_secp_384r1); - } - - key = key_384; - t = 48; - break; - - default: - return 0; - } - - if (sig_len != 2*t || key_len != 2*t || - !(p = blockdata_retrieve(key_data, key_len, NULL))) - return 0; - - mpz_import(x, t , 1, 1, 0, 0, p); - mpz_import(y, t , 1, 1, 0, 0, p + t); - - if (!ecc_point_set(key, x, y)) - return 0; - - mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig); - mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t); - - return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); -} -#endif - -static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, - unsigned char *digest, size_t digest_len, int algo) -{ - (void)digest_len; - - switch (algo) - { - case 1: case 5: case 7: case 8: case 10: - return dnsmasq_rsa_verify(key_data, key_len, sig, sig_len, digest, algo); - - case 3: case 6: - return dnsmasq_dsa_verify(key_data, key_len, sig, sig_len, digest, algo); - -#ifndef NO_NETTLE_ECC - case 13: case 14: - return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo); -#endif - } - - return 0; -} - /* Convert from presentation format to wire format, in place. Also map UC -> LC. Note that using extract_name to get presentation format @@ -325,7 +37,7 @@ static int verify(struct blockdata *key_data, unsigned int key_len, unsigned cha character. In theory, if all the characters in a name were /000 or '.' or NAME_ESCAPE then all would have to be escaped, so the presentation format would be twice as long as the spec (1024). - The buffers are all delcared as 2049 (allowing for the trailing zero) + The buffers are all declared as 2049 (allowing for the trailing zero) for this reason. */ static int to_wire(char *name) @@ -391,19 +103,21 @@ static void from_wire(char *name) static int count_labels(char *name) { int i; - + char *p; + if (*name == 0) return 0; - for (i = 0; *name; name++) - if (*name == '.') + for (p = name, i = 0; *p; p++) + if (*p == '.') i++; - return i+1; + /* Don't count empty first label. */ + return *name == '.' ? i : i+1; } /* Implement RFC1982 wrapped compare for 32-bit numbers */ -static int serial_compare_32(unsigned long s1, unsigned long s2) +static int serial_compare_32(u32 s1, u32 s2) { if (s1 == s2) return SERIAL_EQ; @@ -442,7 +156,7 @@ int setup_timestamp(void) if (difftime(timestamp_time, time(0)) <= 0) { /* time already OK, update timestamp, and do key checking from the start. */ - if (utime(daemon->timestamp_file, NULL) == -1) + if (utimes(daemon->timestamp_file, NULL) == -1) my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); daemon->back_to_the_future = 1; return 0; @@ -456,12 +170,14 @@ int setup_timestamp(void) int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666); if (fd != -1) { - struct utimbuf timbuf; + struct timeval tv[2]; close(fd); - timestamp_time = timbuf.actime = timbuf.modtime = 1420070400; /* 1-1-2015 */ - if (utime(daemon->timestamp_file, &timbuf) == 0) + timestamp_time = 1420070400; /* 1-1-2015 */ + tv[0].tv_sec = tv[1].tv_sec = timestamp_time; + tv[0].tv_usec = tv[1].tv_usec = 0; + if (utimes(daemon->timestamp_file, tv) == 0) goto check_and_exit; } } @@ -470,7 +186,7 @@ int setup_timestamp(void) } /* Check whether today/now is between date_start and date_end */ -static int check_date_range(unsigned long date_start, unsigned long date_end) +static int check_date_range(u32 date_start, u32 date_end) { unsigned long curtime = time(0); @@ -486,18 +202,19 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) { if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0) { - if (utime(daemon->timestamp_file, NULL) != 0) + if (utimes(daemon->timestamp_file, NULL) != 0) my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); + my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps.")); daemon->back_to_the_future = 1; - set_option_bool(OPT_DNSSEC_TIME); + daemon->dnssec_no_time_check = 0; queue_event(EVENT_RELOAD); /* purge cache */ } if (daemon->back_to_the_future == 0) return 1; } - else if (option_bool(OPT_DNSSEC_TIME)) + else if (daemon->dnssec_no_time_check) return 1; /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ @@ -505,50 +222,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) && serial_compare_32(curtime, date_end) == SERIAL_LT; } -static u16 *get_desc(int type) -{ - /* List of RRtypes which include domains in the data. - 0 -> domain - integer -> no of plain bytes - -1 -> end - - zero is not a valid RRtype, so the final entry is returned for - anything which needs no mangling. - */ - - static u16 rr_desc[] = - { - T_NS, 0, -1, - T_MD, 0, -1, - T_MF, 0, -1, - T_CNAME, 0, -1, - T_SOA, 0, 0, -1, - T_MB, 0, -1, - T_MG, 0, -1, - T_MR, 0, -1, - T_PTR, 0, -1, - T_MINFO, 0, 0, -1, - T_MX, 2, 0, -1, - T_RP, 0, 0, -1, - T_AFSDB, 2, 0, -1, - T_RT, 2, 0, -1, - T_SIG, 18, 0, -1, - T_PX, 2, 0, 0, -1, - T_NXT, 0, -1, - T_KX, 2, 0, -1, - T_SRV, 6, 0, -1, - T_DNAME, 0, -1, - 0, -1 /* wildcard/catchall */ - }; - - u16 *p = rr_desc; - - while (*p != type && *p != 0) - while (*p++ != (u16)-1); - - return p+1; -} - /* Return bytes of canonicalised rdata, when the return value is zero, the remaining data, pointed to by *p, should be used raw. */ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, @@ -592,34 +265,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, } } -static int expand_workspace(unsigned char ***wkspc, int *sz, int new) -{ - unsigned char **p; - int new_sz = *sz; - - if (new_sz > new) - return 1; - - if (new >= 100) - return 0; - - new_sz += 5; - - if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **)))) - return 0; - - if (*wkspc) - { - memcpy(p, *wkspc, *sz * sizeof(unsigned char **)); - free(*wkspc); - } - - *wkspc = p; - *sz = new_sz; - - return 1; -} - /* Bubble sort the RRset into the canonical order. Note that the byte-streams from two RRs may get unsynced: consider RRs which have two domain-names at the start and then other data. @@ -634,10 +279,10 @@ static int expand_workspace(unsigned char ***wkspc, int *sz, int new) leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. */ -static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, - unsigned char **rrset, char *buff1, char *buff2) +static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) { - int swap, quit, i; + int swap, quit, i, j; do { @@ -699,54 +344,44 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrset[i] = tmp; swap = quit = 1; } + else if (rc == 0 && quit && len1 == len2) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; + } else if (rc < 0) quit = 1; } } } while (swap); -} -/* Validate a single RRset (class, type, name) in the supplied DNS reply - Return code: - STAT_SECURE if it validates. - STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. - (In this case *wildcard_out points to the "body" of the wildcard within name.) - STAT_NO_SIG no RRsigs found. - STAT_INSECURE RRset empty. - STAT_BOGUS signature is wrong, bad packet. - STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + return rrsetidx; +} - if key is non-NULL, use that key, which has the algo and tag given in the params of those names, - otherwise find the key in the cache. +static unsigned char **rrset = NULL, **sigs = NULL; - name is unchanged on exit. keyname is used as workspace and trashed. -*/ -static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, - char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) +/* Get pointers to RRset members and signature(s) for same. + Check signatures, and return keyname associated in keyname. */ +static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, + char *name, char *keyname, int *sigcnt, int *rrcnt) { - static unsigned char **rrset = NULL, **sigs = NULL; - static int rrset_sz = 0, sig_sz = 0; - + static int rrset_sz = 0, sig_sz = 0; unsigned char *p; - int rrsetidx, sigidx, res, rdlen, j, name_labels; - struct crec *crecp = NULL; - int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; - u16 *rr_desc = get_desc(type); - - if (wildcard_out) - *wildcard_out = NULL; - + int rrsetidx, sigidx, j, rdlen, res; + int gotkey = 0; + if (!(p = skip_questions(header, plen))) return STAT_BOGUS; - - name_labels = count_labels(name); /* For 4035 5.3.2 check */ - /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ + /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); j != 0; j--) { unsigned char *pstart, *pdata; - int stype, sclass; + int stype, sclass, type_covered; pstart = p; @@ -762,14 +397,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in GETSHORT(rdlen, p); if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; + return 0; if (res == 1 && sclass == class) { if (stype == type) { if (!expand_workspace(&rrset, &rrset_sz, rrsetidx)) - return STAT_BOGUS; + return 0; rrset[rrsetidx++] = pstart; } @@ -777,14 +412,46 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (stype == T_RRSIG) { if (rdlen < 18) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ GETSHORT(type_covered, p); + p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */ + + if (gotkey) + { + /* If there's more than one SIG, ensure they all have same keyname */ + if (extract_name(header, plen, &p, keyname, 0, 0) != 1) + return 0; + } + else + { + gotkey = 1; + + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return 0; + + /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal + the name of the zone containing the RRset. We can't tell that + for certain, but we can check that the RRset name is equal to + or encloses the signers name, which should be enough to stop + an attacker using signatures made with the key of an unrelated + zone he controls. Note that the root key is always allowed. */ + if (*keyname != 0) + { + char *name_start; + for (name_start = name; !hostname_isequal(name_start, keyname); ) + if ((name_start = strchr(name_start, '.'))) + name_start++; /* chop a label off and try again */ + else + return 0; + } + } + if (type_covered == type) { if (!expand_workspace(&sigs, &sig_sz, sigidx)) - return STAT_BOGUS; + return 0; sigs[sigidx++] = pdata; } @@ -794,21 +461,49 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in } if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_BOGUS; + return 0; } - /* RRset empty */ - if (rrsetidx == 0) - return STAT_INSECURE; + *sigcnt = sigidx; + *rrcnt = rrsetidx; + + return 1; +} - /* no RRSIGs */ - if (sigidx == 0) - return STAT_NO_SIG; +/* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. + (In this case *wildcard_out points to the "body" of the wildcard within name.) + STAT_BOGUS signature is wrong, bad packet. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) + + If key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. + + Name is unchanged on exit. keyname is used as workspace and trashed. + + Call explore_rrset first to find and count RRs and sigs. +*/ +static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) +{ + unsigned char *p; + int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag; + struct crec *crecp = NULL; + u16 *rr_desc = rrfilter_desc(type); + u32 sig_expiration, sig_inception +; + if (wildcard_out) + *wildcard_out = NULL; + name_labels = count_labels(name); /* For 4035 5.3.2 check */ + /* Sort RRset records into canonical order. Note that at this point keyname and daemon->workspacename buffs are unused, and used as workspace by the sort. */ - sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); + rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); /* Now try all the sigs to try and find one which validates */ for (j = 0; j <sigidx; j++) @@ -835,37 +530,12 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_BOGUS; - /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal - the name of the zone containing the RRset. We can't tell that - for certain, but we can check that the RRset name is equal to - or encloses the signers name, which should be enough to stop - an attacker using signatures made with the key of an unrelated - zone he controls. Note that the root key is always allowed. */ - if (*keyname != 0) - { - int failed = 0; - - for (name_start = name; !hostname_isequal(name_start, keyname); ) - if ((name_start = strchr(name_start, '.'))) - name_start++; /* chop a label off and try again */ - else - { - failed = 1; - break; - } - - /* Bad sig, try another */ - if (failed) - continue; - } - - /* Other 5.3.1 checks */ if (!check_date_range(sig_inception, sig_expiration) || labels > name_labels || !(hash = hash_find(algo_digest_name(algo))) || !hash_init(hash, &ctx, &digest)) continue; - + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) return STAT_NEED_KEY; @@ -887,6 +557,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in u16 len, *dp; p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_BOGUS; @@ -968,19 +639,21 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in return STAT_BOGUS; } + /* The DNS packet is expected to contain the answer to a DNSKEY query. Put all DNSKEYs in the answer which are valid into the cache. return codes: - STAT_SECURE At least one valid DNSKEY found and in cache. - STAT_BOGUS No DNSKEYs found, which can be validated with DS, - or self-sign for DNSKEY RRset is not valid, bad packet. - STAT_NEED_DS DS records to validate a key not found, name in keyname + STAT_OK Done, key(s) in cache. + STAT_BOGUS No DNSKEYs found, which can be validated with DS, + or self-sign for DNSKEY RRset is not valid, bad packet. + STAT_NEED_DS DS records to validate a key not found, name in keyname + STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname */ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) { unsigned char *psave, *p = (unsigned char *)(header+1); struct crec *crecp, *recp1; - int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered; + int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; struct blockdata *key; struct all_addr a; @@ -1001,10 +674,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch return STAT_NEED_DS; } - /* If we've cached that DS provably doesn't exist, result must be INSECURE */ - if (crecp->flags & F_NEG) - return STAT_INSECURE_DS; - /* NOTE, we need to find ONE DNSKEY which matches the DS */ for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) { @@ -1057,7 +726,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch void *ctx; unsigned char *digest, *ds_digest; const struct nettle_hash *hash; - + int sigcnt, rrcnt; + if (recp1->addr.ds.algo == algo && recp1->addr.ds.keytag == keytag && recp1->uid == (unsigned int)class && @@ -1075,10 +745,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch from_wire(name); - if (recp1->addr.ds.keylen == (int)hash->digest_size && + if (!(recp1->flags & F_NEG) && + recp1->addr.ds.keylen == (int)hash->digest_size && (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && - validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) + explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && + sigcnt != 0 && rrcnt != 0 && + validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, + NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) { valid = 1; break; @@ -1090,7 +764,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch if (valid) { - /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */ + /* DNSKEY RRset determined to be OK, now cache it. */ cache_start_insert(); p = skip_questions(header, plen); @@ -1099,7 +773,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch { /* Ensure we have type, class TTL and length */ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) - return STAT_INSECURE; /* bad packet */ + return STAT_BOGUS; /* bad packet */ GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -1130,11 +804,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch if ((key = blockdata_alloc((char*)p, rdlen - 4))) { if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) - blockdata_free(key); + { + blockdata_free(key); + return STAT_BOGUS; + } else { - a.addr.keytag = keytag; - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + a.addr.log.keytag = keytag; + a.addr.log.algo = algo; + if (algo_digest_name(algo)) + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu"); + else + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)"); recp1->addr.key.keylen = rdlen - 4; recp1->addr.key.keydata = key; @@ -1144,38 +825,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch } } } - else if (qtype == T_RRSIG) - { - /* RRSIG, cache if covers DNSKEY RRset */ - if (rdlen < 18) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type_covered, p); - - if (type_covered == T_DNSKEY) - { - a.addr.dnssec.class = class; - a.addr.dnssec.type = type_covered; - - algo = *p++; - p += 13; /* labels, orig_ttl, expiration, inception */ - GETSHORT(keytag, p); - if ((key = blockdata_alloc((char*)psave, rdlen))) - { - if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) - blockdata_free(key); - else - { - crecp->addr.sig.keydata = key; - crecp->addr.sig.keylen = rdlen; - crecp->addr.sig.keytag = keytag; - crecp->addr.sig.type_covered = type_covered; - crecp->addr.sig.algo = algo; - } - } - } - } - + p = psave; } @@ -1185,7 +835,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch /* commit cache insert. */ cache_end_insert(); - return STAT_SECURE; + return STAT_OK; } log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); @@ -1194,18 +844,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch /* The DNS packet is expected to contain the answer to a DS query Put all DSs in the answer which are valid into the cache. + Also handles replies which prove that there's no DS at this location, + either because the zone is unsigned or this isn't a zone cut. These are + cached too. return codes: - STAT_SECURE At least one valid DS found and in cache. - STAT_NO_DS It's proved there's no DS here. - STAT_NO_NS It's proved there's no DS _or_ NS here. + STAT_OK At least one valid DS found and in cache. STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname + STAT_NEED_DS DS record needed. */ 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, neganswer, nons; + int qtype, qclass, rc, i, neganswer, nons; + int aclass, atype, rdlen; + unsigned long ttl; + struct all_addr a; if (ntohs(header->qdcount) != 1 || !(p = skip_name(p, header, plen, 4))) @@ -1215,44 +870,105 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char GETSHORT(qclass, p); if (qtype != T_DS || qclass != class) - val = STAT_BOGUS; + rc = STAT_BOGUS; else - val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons); - /* Note dnssec_validate_reply() will have cached positive answers */ + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); - if (val == STAT_INSECURE) - val = STAT_BOGUS; - + if (rc == STAT_INSECURE) + rc = STAT_BOGUS; + 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))) - val = STAT_BOGUS; - - /* If we return STAT_NO_SIG, name contains the name of the DS query */ - if (val == STAT_NO_SIG) - { - *keyname = 0; - return val; - } - /* If the key needed to validate the DS is on the same domain as the DS, we'll loop getting nowhere. Stop that now. This can happen of the DS answer comes from the DS's zone, and not the parent zone. */ - if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) + if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) { log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); return STAT_BOGUS; } - - /* By here, the answer is proved secure, and a positive answer has been cached. */ - if (val == STAT_SECURE && neganswer) + + if (rc != STAT_SECURE) + return rc; + + if (!neganswer) { - int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; - unsigned long ttl, minttl = ULONG_MAX; - struct all_addr a; + cache_start_insert(); + + for (i = 0; i < ntohs(header->ancount); i++) + { + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(atype, p); + GETSHORT(aclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + + if (aclass == class && atype == T_DS && rc == 1) + { + int algo, digest, keytag; + unsigned char *psave = p; + struct blockdata *key; + struct crec *crecp; + + if (rdlen < 4) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(keytag, p); + algo = *p++; + digest = *p++; + + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class; + + if ((key = blockdata_alloc((char*)p, rdlen - 4))) + { + if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + { + blockdata_free(key); + return STAT_BOGUS; + } + else + { + a.addr.log.keytag = keytag; + a.addr.log.algo = algo; + a.addr.log.digest = digest; + if (ds_digest_name(digest) && algo_digest_name(algo)) + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu"); + else + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)"); + + crecp->addr.ds.digest = digest; + crecp->addr.ds.keydata = key; + crecp->addr.ds.algo = algo; + crecp->addr.ds.keytag = keytag; + crecp->addr.ds.keylen = rdlen - 4; + } + } + + p = psave; + } + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + } + cache_end_insert(); + + } + else + { + int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; + unsigned long minttl = ULONG_MAX; + + if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) + return STAT_BOGUS; + if (RCODE(header) == NXDOMAIN) flags |= F_NXDOMAIN; @@ -1266,20 +982,20 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (!(p = skip_name(p, header, plen, 0))) return STAT_BOGUS; - GETSHORT(qtype, p); - GETSHORT(qclass, p); + GETSHORT(atype, p); + GETSHORT(aclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); - + if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ - - if (qclass != class || qtype != T_SOA) + + if (aclass != class || atype != T_SOA) { p += rdlen; continue; } - + if (ttl < minttl) minttl = ttl; @@ -1303,19 +1019,19 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char cache_start_insert(); a.addr.dnssec.class = class; - cache_insert(name, &a, now, ttl, flags); + if (!cache_insert(name, &a, now, ttl, flags)) + return STAT_BOGUS; cache_end_insert(); - log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS"); + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); } - - return nons ? STAT_NO_NS : STAT_NO_DS; } - - return val; + + return STAT_OK; } + /* 4034 6.1 */ static int hostname_cmp(const char *a, const char *b) { @@ -1375,66 +1091,13 @@ static int hostname_cmp(const char *a, const char *b) if (sb == b) return 1; - ea = sa--; - eb = sb--; + ea = --sa; + eb = --sb; } } -/* Find all the NSEC or NSEC3 records in a reply. - return an array of pointers to them. */ -static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd) -{ - static unsigned char **nsecset = NULL; - static int nsecset_sz = 0; - - int type_found = 0; - unsigned char *p = skip_questions(header, plen); - int type, class, rdlen, i, nsecs_found; - - /* Move to NS section */ - if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) - return 0; - - for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) - { - unsigned char *pstart = p; - - if (!(p = skip_name(p, header, plen, 10))) - return 0; - - GETSHORT(type, p); - GETSHORT(class, p); - p += 4; /* TTL */ - GETSHORT(rdlen, p); - - if (class == class_reqd && (type == T_NSEC || type == T_NSEC3)) - { - /* No mixed NSECing 'round here, thankyouverymuch */ - if (type_found == T_NSEC && type == T_NSEC3) - return 0; - if (type_found == T_NSEC3 && type == T_NSEC) - return 0; - - type_found = type; - - if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) - return 0; - - nsecset[nsecs_found++] = pstart; - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return 0; - } - - *nsecsetp = nsecset; - *nsecsetl = nsecs_found; - - return type_found; -} - -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type, int *nons) +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, + char *workspace1_in, char *workspace2, char *name, int type, int *nons) { int i, rc, rdlen; unsigned char *p, *psave; @@ -1442,27 +1105,50 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi int mask = 0x80 >> (type & 0x07); if (nons) - *nons = 0; + *nons = 1; /* Find NSEC record that proves name doesn't exist */ for (i = 0; i < nsec_count; i++) { + char *workspace1 = workspace1_in; + int sig_labels, name_labels; + p = nsecs[i]; if (!extract_name(header, plen, &p, workspace1, 1, 10)) - return STAT_BOGUS; + return 0; p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); psave = p; if (!extract_name(header, plen, &p, workspace2, 1, 10)) - return STAT_BOGUS; - + return 0; + + /* If NSEC comes from wildcard expansion, use original wildcard + as name for computation. */ + sig_labels = *labels[i]; + name_labels = count_labels(workspace1); + + if (sig_labels < name_labels) + { + int k; + for (k = name_labels - sig_labels; k != 0; k--) + { + while (*workspace1 != '.' && *workspace1 != 0) + workspace1++; + if (k != 1 && *workspace1 == '.') + workspace1++; + } + + workspace1--; + *workspace1 = '*'; + } + rc = hostname_cmp(workspace1, name); if (rc == 0) { /* 4035 para 5.4. Last sentence */ if (type == T_NSEC || type == T_RRSIG) - return STAT_SECURE; + return 1; /* NSEC with the same name as the RR we're testing, check that the type in question doesn't appear in the type map */ @@ -1470,45 +1156,59 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi /* rdlen is now length of type map, and p points to it */ /* If we can prove that there's no NS record, return that information. */ - if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) - *nons = 1; + if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) + *nons = 0; + if (rdlen >= 2 && p[0] == 0) + { + /* A CNAME answer would also be valid, so if there's a CNAME is should + have been returned. */ + if ((p[2] & (0x80 >> T_CNAME)) != 0) + return 0; + + /* If the SOA bit is set for a DS record, then we have the + DS from the wrong side of the delegation. For the root DS, + this is expected. */ + if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) + return 0; + } + while (rdlen >= 2) { if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; + return 0; if (p[0] == type >> 8) { /* Does the NSEC say our type exists? */ if (offset < p[1] && (p[offset+2] & mask) != 0) - return STAT_BOGUS; + return 0; - break; /* finshed checking */ + break; /* finished checking */ } rdlen -= p[1]; p += p[1]; } - return STAT_SECURE; + return 1; } else if (rc == -1) { /* Normal case, name falls between NSEC name and next domain name, wrap around case, name falls between NSEC name (rc == -1) and end */ if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) - return STAT_SECURE; + return 1; } else { /* wrap around case, name falls between start and next domain name */ if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) - return STAT_SECURE; + return 1; } } - return STAT_BOGUS; + return 0; } /* return digest length, or zero on error */ @@ -1574,9 +1274,9 @@ static int base32_decode(char *in, unsigned char *out) } static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, - char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons) + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels) { - int i, hash_len, salt_len, base32_len, rdlen; + int i, hash_len, salt_len, base32_len, rdlen, flags; unsigned char *p, *psave; for (i = 0; i < nsec_count; i++) @@ -1589,7 +1289,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); psave = p; - p += 4; /* algo, flags, iterations */ + p++; /* algo */ + flags = *p++; /* flags */ + p += 2; /* iterations */ salt_len = *p++; /* salt_len */ p += salt_len; /* salt */ hash_len = *p++; /* p now points to next hashed name */ @@ -1615,25 +1317,39 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige if (!CHECK_LEN(header, p, plen, rdlen)) return 0; - /* If we can prove that there's no NS record, return that information. */ - if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) - *nons = 1; + if (rdlen >= 2 && p[0] == 0) + { + /* If we can prove that there's no NS record, return that information. */ + if (nons && (p[2] & (0x80 >> T_NS)) != 0) + *nons = 0; + /* A CNAME answer would also be valid, so if there's a CNAME is should + have been returned. */ + if ((p[2] & (0x80 >> T_CNAME)) != 0) + return 0; + + /* If the SOA bit is set for a DS record, then we have the + DS from the wrong side of the delegation. For the root DS, + this is expected. */ + if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) + return 0; + } + while (rdlen >= 2) { if (p[0] == type >> 8) { /* Does the NSEC3 say our type exists? */ if (offset < p[1] && (p[offset+2] & mask) != 0) - return STAT_BOGUS; + return 0; - break; /* finshed checking */ + break; /* finished checking */ } rdlen -= p[1]; p += p[1]; } - + return 1; } else if (rc < 0) @@ -1641,16 +1357,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, wrap around case, name-hash falls between NSEC3 name-hash and end */ if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0) - return 1; + { + if ((flags & 0x01) && nons) /* opt out */ + *nons = 0; + + return 1; + } } else { /* wrap around case, name falls between start and next domain name */ if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0) - return 1; + { + if ((flags & 0x01) && nons) /* opt out */ + *nons = 0; + + return 1; + } } } } + return 0; } @@ -1663,10 +1390,10 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns char *closest_encloser, *next_closest, *wildcard; if (nons) - *nons = 0; + *nons = 1; /* Look though the NSEC3 records to find the first one with - an algorithm we support (currently only algo == 1). + an algorithm we support. Take the algo, iterations, and salt of that record as the ones we're going to use, and prune any @@ -1675,44 +1402,56 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns for (i = 0; i < nsec_count; i++) { if (!(p = skip_name(nsecs[i], header, plen, 15))) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ p += 10; /* type, class, TTL, rdlen */ algo = *p++; - if (algo == 1) + if ((hash = hash_find(nsec3_digest_name(algo)))) break; /* known algo */ } /* No usable NSEC3s */ if (i == nsec_count) - return STAT_BOGUS; + return 0; p++; /* flags */ + GETSHORT (iterations, p); + /* Upper-bound iterations, to avoid DoS. + Strictly, there are lower bounds for small keys, but + since we don't have key size info here, at least limit + to the largest bound, for 4096-bit keys. RFC 5155 10.3 */ + if (iterations > 2500) + return 0; + salt_len = *p++; salt = p; if (!CHECK_LEN(header, salt, plen, salt_len)) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ for (i = 0; i < nsec_count; i++) { unsigned char *nsec3p = nsecs[i]; - int this_iter; + int this_iter, flags; nsecs[i] = NULL; /* Speculative, will be restored if OK. */ if (!(p = skip_name(nsec3p, header, plen, 15))) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ p += 10; /* type, class, TTL, rdlen */ if (*p++ != algo) continue; - p++; /* flags */ + flags = *p++; /* flags */ + /* 5155 8.2 */ + if (flags != 0 && flags != 1) + continue; + GETSHORT(this_iter, p); if (this_iter != iterations) continue; @@ -1721,7 +1460,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns continue; if (!CHECK_LEN(header, p, plen, salt_len)) - return STAT_BOGUS; /* bad packet */ + return 0; /* bad packet */ if (memcmp(p, salt, salt_len) != 0) continue; @@ -1730,15 +1469,11 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns nsecs[i] = nsec3p; } - /* Algo is checked as 1 above */ - if (!(hash = hash_find("sha1"))) - return STAT_BOGUS; - if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) - return STAT_BOGUS; + return 0; - if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons)) - return STAT_SECURE; + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name))) + return 1; /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" or an answer inferred from a wildcard record. */ @@ -1754,14 +1489,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns break; if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) - return STAT_BOGUS; + return 0; for (i = 0; i < nsec_count; i++) if ((p = nsecs[i])) { if (!extract_name(header, plen, &p, workspace1, 1, 0) || !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) - return STAT_BOGUS; + return 0; if (digest_len == base32_len && memcmp(digest, workspace2, digest_len) == 0) @@ -1775,441 +1510,493 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns } while ((closest_encloser = strchr(closest_encloser, '.'))); - if (!closest_encloser) - return STAT_BOGUS; + if (!closest_encloser || !next_closest) + return 0; /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) - return STAT_BOGUS; + return 0; - if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) - return STAT_BOGUS; + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) + return 0; /* Finally, check that there's no seat of wildcard synthesis */ if (!wildname) { if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) - return STAT_BOGUS; + return 0; wildcard--; *wildcard = '*'; if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) - return STAT_BOGUS; + return 0; - if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) - return STAT_BOGUS; + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) + return 0; } - return STAT_SECURE; + return 1; } - -/* 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 *neganswer, int *nons) -{ - 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 = CNAME_CHAIN; - int nsec_type = 0, have_answer = 0; - if (neganswer) - *neganswer = 0; +static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons) +{ + static unsigned char **nsecset = NULL, **rrsig_labels = NULL; + static int nsecset_sz = 0, rrsig_labels_sz = 0; - if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1) - return STAT_BOGUS; + int type_found = 0; + unsigned char *auth_start, *p = skip_questions(header, plen); + int type, class, rdlen, i, nsecs_found; - if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) - return STAT_INSECURE; + /* Move to NS section */ + if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) + return 0; - qname = p1 = (unsigned char *)(header+1); + auth_start = p; - if (!extract_name(header, plen, &p1, name, 1, 4)) - return STAT_BOGUS; - - GETSHORT(qtype, p1); - GETSHORT(qclass, p1); - ans_start = p1; - - if (qtype == T_ANY) - have_answer = 1; - - /* Can't validate an RRISG query */ - if (qtype == T_RRSIG) - return STAT_INSECURE; - - cname_loop: - for (j = ntohs(header->ancount); j != 0; j--) + for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) { - /* leave pointer to missing name in qname */ - - if (!(rc = extract_name(header, plen, &p1, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ + unsigned char *pstart = p; - GETSHORT(type2, p1); - GETSHORT(class2, p1); - p1 += 4; /* TTL */ - GETSHORT(rdlen2, p1); + if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) + return 0; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); - if (rc == 1 && qclass == class2) + if (class == qclass && (type == T_NSEC || type == T_NSEC3)) { - /* Do we have an answer for the question? */ - if (type2 == qtype) - { - have_answer = 1; - break; - } - else if (type2 == T_CNAME) + /* No mixed NSECing 'round here, thankyouverymuch */ + if (type_found != 0 && type_found != type) + return 0; + + type_found = type; + + if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) + return 0; + + if (type == T_NSEC) { - qname = p1; + /* If we're looking for NSECs, find the corresponding SIGs, to + extract the labels value, which we need in case the NSECs + are the result of wildcard expansion. + Note that the NSEC may not have been validated yet + so if there are multiple SIGs, make sure the label value + is the same in all, to avoid be duped by a rogue one. + If there are no SIGs, that's an error */ + unsigned char *p1 = auth_start; + int res, j, rdlen1, type1, class1; - /* looped CNAMES */ - if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0)) - return STAT_BOGUS; - - p1 = ans_start; - goto cname_loop; + if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) + return 0; + + rrsig_labels[nsecs_found] = NULL; + + for (j = ntohs(header->nscount); j != 0; j--) + { + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) + return 0; + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + if (!CHECK_LEN(header, p1, plen, rdlen1)) + return 0; + + if (res == 1 && class1 == qclass && type1 == T_RRSIG) + { + int type_covered; + unsigned char *psav = p1; + + if (rdlen1 < 18) + return 0; /* bad packet */ + + GETSHORT(type_covered, p1); + + if (type_covered == T_NSEC) + { + p1++; /* algo */ + + /* labels field must be the same in every SIG we find. */ + if (!rrsig_labels[nsecs_found]) + rrsig_labels[nsecs_found] = p1; + else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ + return 0; + } + p1 = psav; + } + + if (!ADD_RDLEN(header, p1, plen, rdlen1)) + return 0; + } + + /* Must have found at least one sig. */ + if (!rrsig_labels[nsecs_found]) + return 0; } - } - if (!ADD_RDLEN(header, p1, plen, rdlen2)) - return STAT_BOGUS; + nsecset[nsecs_found++] = pstart; + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return 0; } - - if (neganswer && !have_answer) - *neganswer = 1; - /* No data, therefore no sigs */ - if (ntohs(header->ancount) + ntohs(header->nscount) == 0) + if (type_found == T_NSEC) + return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); + else if (type_found == T_NSEC3) + return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); + else + return 0; +} + +/* Check signing status of name. + returns: + STAT_SECURE zone is signed. + STAT_INSECURE zone proved unsigned. + STAT_NEED_DS require DS record of name returned in keyname. + STAT_NEED_KEY require DNSKEY record of name returned in keyname. + name returned unaltered. +*/ +static int zone_status(char *name, int class, char *keyname, time_t now) +{ + int name_start = strlen(name); /* for when TA is root */ + struct crec *crecp; + char *p; + + /* First, work towards the root, looking for a trust anchor. + This can either be one configured, or one previously cached. + We can assume, if we don't find one first, that there is + a trust anchor at the root. */ + for (p = name; p; p = strchr(p, '.')) { - *keyname = 0; - return STAT_NO_SIG; + if (*p == '.') + p++; + + if (cache_find_by_name(NULL, p, now, F_DS)) + { + name_start = p - name; + break; + } } - for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) + /* Now work away from the trust anchor */ + while (1) { - if (!extract_name(header, plen, &p1, name, 1, 10)) - return STAT_BOGUS; /* bad packet */ + strcpy(keyname, &name[name_start]); - GETSHORT(type1, p1); - GETSHORT(class1, p1); - p1 += 4; /* TTL */ - GETSHORT(rdlen1, p1); + if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS))) + return STAT_NEED_DS; - /* Don't try and validate RRSIGs! */ - if (type1 != T_RRSIG) + /* F_DNSSECOK misused in DS cache records to non-existence of NS record. + F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here, + but that's because there's no NS record either, ie this isn't the start + of a zone. We only prove that the DNS tree below a node is unsigned when + we prove that we're at a zone cut AND there's no DS record. */ + if (crecp->flags & F_NEG) + { + if (crecp->flags & F_DNSSECOK) + return STAT_INSECURE; /* proved no DS here */ + } + else { - /* Check if we've done this RRset already */ - for (p2 = ans_start, j = 0; j < i; j++) + /* If all the DS records have digest and/or sig algos we don't support, + then the zone is insecure. Note that if an algo + appears in the DS, then RRSIGs for that algo MUST + exist for each RRset: 4035 para 2.2 So if we find + a DS here with digest and sig we can do, we're entitled + to assume we can validate the zone and if we can't later, + because an RRSIG is missing we return BOGUS. + */ + do { - if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type2, p2); - GETSHORT(class2, p2); - p2 += 4; /* TTL */ - GETSHORT(rdlen2, p2); - - if (type2 == type1 && class2 == class1 && rc == 1) - break; /* Done it before: name, type, class all match. */ - - if (!ADD_RDLEN(header, p2, plen, rdlen2)) - return STAT_BOGUS; + if (crecp->uid == (unsigned int)class && + ds_digest_name(crecp->addr.ds.digest) && + algo_digest_name(crecp->addr.ds.algo)) + break; } - - /* Not done, validate now */ - if (j == i) - { - int ttl, keytag, algo, digest, type_covered; - unsigned char *psave; - struct all_addr a; - struct blockdata *key; - struct crec *crecp; - char *wildname; - int have_wildcard = 0; + while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS))); - rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); - - if (rc == STAT_SECURE_WILDCARD) - { - have_wildcard = 1; + if (!crecp) + return STAT_INSECURE; + } - /* An attacker replay a wildcard answer with a different - answer and overlay a genuine RR. To prove this - hasn't happened, the answer must prove that - the gennuine record doesn't exist. Check that here. */ - if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1))) - return STAT_BOGUS; /* No NSECs or bad packet */ - - if (nsec_type == T_NSEC) - rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL); - else - rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, - keyname, name, type1, wildname, NULL); - - if (rc != STAT_SECURE) - return rc; - } - else if (rc != STAT_SECURE) - { - if (class) - *class = class1; /* Class for DS or DNSKEY */ + if (name_start == 0) + break; - if (rc == STAT_NO_SIG) - { - /* If we dropped off the end of a CNAME chain, return - STAT_NO_SIG and the last name is keyname. This is used for proving non-existence - if DS records in CNAME chains. */ - if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount)) - /* No CNAME chain, or no sig in answer section, return empty name. */ - *keyname = 0; - else if (!extract_name(header, plen, &qname, keyname, 1, 0)) - return STAT_BOGUS; - } - - return rc; - } - - /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ - cache_start_insert(); - - for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) - { - if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type2, p2); - GETSHORT(class2, p2); - GETLONG(ttl, p2); - GETSHORT(rdlen2, p2); - - if (!CHECK_LEN(header, p2, plen, rdlen2)) - return STAT_BOGUS; /* bad packet */ - - if (class2 == class1 && rc == 1) - { - psave = p2; + for (p = &name[name_start-2]; (*p != '.') && (p != name); p--); + + if (p != name) + p++; + + name_start = p - name; + } - if (type1 == T_DS && type2 == T_DS) - { - if (rdlen2 < 4) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(keytag, p2); - algo = *p2++; - digest = *p2++; - - /* Cache needs to known class for DNSSEC stuff */ - a.addr.dnssec.class = class2; - - if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) - { - if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) - blockdata_free(key); - else - { - a.addr.keytag = keytag; - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); - crecp->addr.ds.digest = digest; - crecp->addr.ds.keydata = key; - crecp->addr.ds.algo = algo; - crecp->addr.ds.keytag = keytag; - crecp->addr.ds.keylen = rdlen2 - 4; - } - } - } - else if (type2 == T_RRSIG) - { - if (rdlen2 < 18) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type_covered, p2); - - if (type_covered == type1 && - (type_covered == T_A || type_covered == T_AAAA || - type_covered == T_CNAME || type_covered == T_DS || - type_covered == T_DNSKEY || type_covered == T_PTR)) - { - a.addr.dnssec.type = type_covered; - a.addr.dnssec.class = class1; - - algo = *p2++; - p2 += 13; /* labels, orig_ttl, expiration, inception */ - GETSHORT(keytag, p2); - - /* We don't cache sigs for wildcard answers, because to reproduce the - answer from the cache will require one or more NSEC/NSEC3 records - which we don't cache. The lack of the RRSIG ensures that a query for - this RRset asking for a secure answer will always be forwarded. */ - if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2))) - { - if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) - blockdata_free(key); - else - { - crecp->addr.sig.keydata = key; - crecp->addr.sig.keylen = rdlen2; - crecp->addr.sig.keytag = keytag; - crecp->addr.sig.type_covered = type_covered; - crecp->addr.sig.algo = algo; - } - } - } - } - - p2 = psave; - } - - if (!ADD_RDLEN(header, p2, plen, rdlen2)) - return STAT_BOGUS; /* bad packet */ - } - - cache_end_insert(); - } - } + return STAT_SECURE; +} + +/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) + Return code: + STAT_SECURE if it validates. + STAT_INSECURE at least one RRset not validated, because in unsigned zone. + STAT_BOGUS signature is wrong, bad packet, no validation where there should be. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) - if (!ADD_RDLEN(header, p1, plen, rdlen1)) - return STAT_BOGUS; - } + daemon->rr_status points to a char array which corressponds to the RRs in the + answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't. +*/ +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, + int *class, int check_unsigned, int *neganswer, int *nons) +{ + static unsigned char **targets = NULL; + static int target_sz = 0; - /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */ - 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, qclass))) + unsigned char *ans_start, *p1, *p2; + int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; + int i, j, rc; + int secure = STAT_SECURE; + + /* extend rr_status if necessary */ + if (daemon->rr_status_sz < ntohs(header->ancount)) { - /* No NSEC records. If we dropped off the end of a CNAME chain, return - STAT_NO_SIG and the last name is keyname. This is used for proving non-existence - if DS records in CNAME chains. */ - if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */ - *keyname = 0; - else if (!extract_name(header, plen, &qname, keyname, 1, 0)) + char *new = whine_malloc(ntohs(header->ancount) + 64); + + if (!new) return STAT_BOGUS; - return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into - an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */ + + free(daemon->rr_status); + daemon->rr_status = new; + daemon->rr_status_sz = ntohs(header->ancount) + 64; } - - /* Get name of missing answer */ - if (!extract_name(header, plen, &qname, name, 1, 0)) + + memset(daemon->rr_status, 0, ntohs(header->ancount)); + + if (neganswer) + *neganswer = 0; + + if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1) return STAT_BOGUS; - if (nsec_type == T_NSEC) - return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons); - else - return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons); -} + if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) + return STAT_INSECURE; -/* 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, qclass, rdlen, j, rc; - int cname_count = CNAME_CHAIN; - char *wildname; + p1 = (unsigned char *)(header+1); + + /* Find all the targets we're looking for answers to. + The zeroth array element is for the query, subsequent ones + for CNAME targets, unless the query is for a CNAME. */ - /* Get question */ - if (!extract_name(header, plen, &p, name, 1, 4)) + if (!expand_workspace(&targets, &target_sz, 0)) return STAT_BOGUS; - p +=2; /* type */ - GETSHORT(qclass, p); - - while (1) + targets[0] = p1; + targetidx = 1; + + if (!extract_name(header, plen, &p1, name, 1, 4)) + return STAT_BOGUS; + + GETSHORT(qtype, p1); + GETSHORT(qclass, p1); + ans_start = p1; + + /* Can't validate an RRSIG query */ + if (qtype == T_RRSIG) + return STAT_INSECURE; + + if (qtype != T_CNAME) + for (j = ntohs(header->ancount); j != 0; j--) + { + if (!(p1 = skip_name(p1, header, plen, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type2, p1); + p1 += 6; /* class, TTL */ + GETSHORT(rdlen2, p1); + + if (type2 == T_CNAME) + { + if (!expand_workspace(&targets, &target_sz, targetidx)) + return STAT_BOGUS; + + targets[targetidx++] = p1; /* pointer to target name */ + } + + if (!ADD_RDLEN(header, p1, plen, rdlen2)) + return STAT_BOGUS; + } + + for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) { - for (j = ntohs(header->ancount); j != 0; j--) + if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_BOGUS; + + if (!extract_name(header, plen, &p1, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + /* Don't try and validate RRSIGs! */ + if (type1 == T_RRSIG) + continue; + + /* Check if we've done this RRset already */ + for (p2 = ans_start, j = 0; j < i; j++) { - if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + if (!(rc = extract_name(header, plen, &p2, 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; - } + GETSHORT(type2, p2); + GETSHORT(class2, p2); + p2 += 4; /* TTL */ + GETSHORT(rdlen2, p2); - /* Got to end of CNAME chain. */ - if (type != T_CNAME) - return STAT_INSECURE; + if (type2 == type1 && class2 == class1 && rc == 1) + break; /* Done it before: name, type, class all match. */ - /* validate CNAME chain, return if insecure or need more data */ - rc = validate_rrset(now, header, plen, class, type, name, keyname, &wildname, NULL, 0, 0, 0); - - if (rc == STAT_SECURE_WILDCARD) + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; + } + + if (j != i) + { + /* Done already: copy the validation status */ + if (i < ntohs(header->ancount)) + daemon->rr_status[i] = daemon->rr_status[j]; + } + else + { + /* Not done, validate now */ + int sigcnt, rrcnt; + char *wildname; + + if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) + return STAT_BOGUS; + + /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */ + if (sigcnt == 0) { - int nsec_type, nsec_count, i; - unsigned char **nsecs; - - /* An attacker can replay a wildcard answer with a different - answer and overlay a genuine RR. To prove this - hasn't happened, the answer must prove that - the genuine record doesn't exist. Check that here. */ - if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class))) - return STAT_BOGUS; /* No NSECs or bad packet */ - - /* Note that we're called here because something didn't validate in validate_reply, - so we can't assume that any NSEC records have been validated. We do them by steam here */ - - for (i = 0; i < nsec_count; i++) + if (check_unsigned) { - unsigned char *p1 = nsecs[i]; - - if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0)) - return STAT_BOGUS; - - rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0); - - /* NSECs can't be wildcards. */ - if (rc == STAT_SECURE_WILDCARD) + rc = zone_status(name, class1, keyname, now); + if (rc == STAT_SECURE) rc = STAT_BOGUS; - - if (rc != STAT_SECURE) - return rc; + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ } - - if (nsec_type == T_NSEC) - rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL); - else - rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, - keyname, name, type, wildname, NULL); + else + rc = STAT_INSECURE; - if (rc != STAT_SECURE) + if (rc != STAT_INSECURE) return rc; } - - if (rc != STAT_SECURE) + else { - if (rc == STAT_NO_SIG) - rc = STAT_INSECURE; - return rc; - } + /* explore_rrset() gives us key name from sigs in keyname. + Can't overwrite name here. */ + strcpy(daemon->workspacename, keyname); + rc = zone_status(daemon->workspacename, class1, keyname, now); + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + { + /* Zone is insecure, don't need to validate RRset */ + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ + return rc; + } + + /* Zone is insecure, don't need to validate RRset */ + if (rc == STAT_SECURE) + { + rc = validate_rrset(now, header, plen, class1, type1, sigcnt, + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + return rc; + } + + /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ + + /* Note that RR is validated */ + if (i < ntohs(header->ancount)) + daemon->rr_status[i] = 1; + + /* Note if we've validated either the answer to the question + or the target of a CNAME. Any not noted will need NSEC or + to be in unsigned space. */ + for (j = 0; j <targetidx; j++) + if ((p2 = targets[j])) + { + int rc1; + if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY )) + targets[j] = NULL; + } + + /* An attacker replay a wildcard answer with a different + answer and overlay a genuine RR. To prove this + hasn't happened, the answer must prove that + the genuine record doesn't exist. Check that here. + Note that we may not yet have validated the NSEC/NSEC3 RRsets. + That's not a problem since if the RRsets later fail + we'll return BOGUS then. */ + if (rc == STAT_SECURE_WILDCARD && + !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL)) + return STAT_BOGUS; - /* Loop down CNAME chain/ */ - if (!cname_count-- || - !extract_name(header, plen, &p, name, 1, 0) || - !(p = skip_questions(header, plen))) - return STAT_BOGUS; - - break; + rc = STAT_SECURE; + } + } } - /* End of CNAME chain */ - return STAT_INSECURE; + if (rc == STAT_INSECURE) + secure = STAT_INSECURE; } + + /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ + for (j = 0; j <targetidx; j++) + if ((p2 = targets[j])) + { + if (neganswer) + *neganswer = 1; + + if (!extract_name(header, plen, &p2, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ + + /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */ + + /* For anything other than a DS record, this situation is OK if either + the answer is in an unsigned zone, or there's a NSEC records. */ + if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons)) + { + /* Empty DS without NSECS */ + if (qtype == T_DS) + return STAT_BOGUS; + + if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) + { + if (class) + *class = qclass; /* Class for NEED_DS or NEED_KEY */ + return rc; + } + + return STAT_BOGUS; /* signed zone, no NSECs */ + } + } + + return secure; } @@ -2235,7 +2022,7 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) } } -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, +size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz) { unsigned char *p; @@ -2271,245 +2058,12 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i ret = add_do_bit(header, p - (unsigned char *)header, end); - if (find_pseudoheader(header, ret, NULL, &p, NULL)) + if (find_pseudoheader(header, ret, NULL, &p, NULL, NULL)) PUTSHORT(edns_pktsz, p); return ret; } -/* Go through a domain name, find "pointers" and fix them up based on how many bytes - we've chopped out of the packet, or check they don't point into an elided part. */ -static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) -{ - unsigned char *ansp = *namep; - - while(1) - { - unsigned int label_type; - - if (!CHECK_LEN(header, ansp, plen, 1)) - return 0; - - label_type = (*ansp) & 0xc0; - - if (label_type == 0xc0) - { - /* pointer for compression. */ - unsigned int offset; - int i; - unsigned char *p; - - if (!CHECK_LEN(header, ansp, plen, 2)) - return 0; - - offset = ((*ansp++) & 0x3f) << 8; - offset |= *ansp++; - - p = offset + (unsigned char *)header; - - for (i = 0; i < rr_count; i++) - if (p < rrs[i]) - break; - else - if (i & 1) - offset -= rrs[i] - rrs[i-1]; - - /* does the pointer end up in an elided RR? */ - if (i & 1) - return 0; - - /* No, scale the pointer */ - if (fixup) - { - ansp -= 2; - *ansp++ = (offset >> 8) | 0xc0; - *ansp++ = offset & 0xff; - } - break; - } - else if (label_type == 0x80) - return 0; /* reserved */ - else if (label_type == 0x40) - { - /* Extended label type */ - unsigned int count; - - if (!CHECK_LEN(header, ansp, plen, 2)) - return 0; - - if (((*ansp++) & 0x3f) != 1) - return 0; /* we only understand bitstrings */ - - count = *(ansp++); /* Bits in bitstring */ - - if (count == 0) /* count == 0 means 256 bits */ - ansp += 32; - else - ansp += ((count-1)>>3)+1; - } - else - { /* label type == 0 Bottom six bits is length */ - unsigned int len = (*ansp++) & 0x3f; - - if (!ADD_RDLEN(header, ansp, plen, len)) - return 0; - - if (len == 0) - break; /* zero length label marks the end. */ - } - } - - *namep = ansp; - - return 1; -} - -/* Go through RRs and check or fixup the domain names contained within */ -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) + ntohs(header->arcount); i++) - { - 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; - - for (pp = p, d = get_desc(type); *d != (u16)-1; d++) - { - if (*d != 0) - pp += *d; - else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) - return 0; - } - } - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return 0; - } - - return 1; -} - - -size_t filter_rrsigs(struct dns_header *header, size_t plen) -{ - static unsigned char **rrs; - static int rr_sz = 0; - - unsigned char *p = (unsigned char *)(header+1); - int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; - - if (ntohs(header->qdcount) != 1 || - !(p = skip_name(p, header, plen, 4))) - return plen; - - GETSHORT(qtype, p); - GETSHORT(qclass, p); - - /* First pass, find pointers to start and end of all the records we wish to elide: - records added for DNSSEC, unless explicity queried for */ - for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; - i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); - i++) - { - unsigned char *pstart = p; - int type, class; - - if (!(p = skip_name(p, header, plen, 10))) - return plen; - - GETSHORT(type, p); - GETSHORT(class, p); - p += 4; /* TTL */ - GETSHORT(rdlen, p); - - if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && - (type != qtype || class != qclass)) - { - if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) - return plen; - - rrs[rr_found++] = pstart; - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return plen; - - rrs[rr_found++] = p; - - if (i < ntohs(header->ancount)) - chop_an++; - else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) - chop_ns++; - else - chop_ar++; - } - else if (!ADD_RDLEN(header, p, plen, rdlen)) - return plen; - } - - /* Nothing to do. */ - if (rr_found == 0) - return plen; - - /* Second pass, look for pointers in names in the records we're keeping and make sure they don't - point to records we're going to elide. This is theoretically possible, but unlikely. If - it happens, we give up and leave the answer unchanged. */ - p = (unsigned char *)(header+1); - - /* question first */ - if (!check_name(&p, header, plen, 0, rrs, rr_found)) - return plen; - p += 4; /* qclass, qtype */ - - /* Now answers and NS */ - if (!check_rrs(p, header, plen, 0, rrs, rr_found)) - return plen; - - /* Third pass, elide records */ - for (p = rrs[0], i = 1; i < rr_found; i += 2) - { - unsigned char *start = rrs[i]; - unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen; - - memmove(p, start, end-start); - p += end-start; - } - - plen = p - (unsigned char *)header; - header->ancount = htons(ntohs(header->ancount) - chop_an); - header->nscount = htons(ntohs(header->nscount) - chop_ns); - header->arcount = htons(ntohs(header->arcount) - chop_ar); - - /* Fourth pass, fix up pointers in the remaining records */ - p = (unsigned char *)(header+1); - - check_name(&p, header, plen, 1, rrs, rr_found); - p += 4; /* qclass, qtype */ - - check_rrs(p, header, plen, 1, rrs, rr_found); - - return plen; -} - unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) { int q; diff --git a/src/domain.c b/src/domain.c index 278698c..04d7cf8 100644 --- a/src/domain.c +++ b/src/domain.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 @@ -55,70 +55,133 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr) if (pref && *pref != 0) continue; /* prefix match fail */ - - /* NB, must not alter name if we return zero */ - for (p = tail; *p; p++) + + if (c->indexed) { - char c = *p; + for (p = tail; *p; p++) + { + char c = *p; + + if (c < '0' || c > '9') + break; + } - if ((c >='0' && c <= '9') || c == '-') + if (*p != '.') continue; -#ifdef HAVE_IPV6 - if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) - continue; -#endif + *p = 0; - break; - } - - if (*p != '.') - continue; - - *p = 0; - - /* swap . or : for - */ - for (p = tail; *p; p++) - if (*p == '-') - { - if (prot == AF_INET) - *p = '.'; -#ifdef HAVE_IPV6 - else - *p = ':'; + if (hostname_isequal(c->domain, p+1)) + { + if (prot == AF_INET) + { + unsigned int index = atoi(tail); + + if (!c->is6 && + index <= ntohl(c->end.s_addr) - ntohl(c->start.s_addr)) + { + addr->addr.addr4.s_addr = htonl(ntohl(c->start.s_addr) + index); + found = 1; + } + } +#ifdef HAVE_IPV6 + else + { + u64 index = atoll(tail); + + if (c->is6 && + index <= addr6part(&c->end6) - addr6part(&c->start6)) + { + u64 start = addr6part(&c->start6); + addr->addr.addr6 = c->start6; + setaddr6part(&addr->addr.addr6, start + index); + found = 1; + } + } #endif - } - - if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr)) + } + } + else { - if (prot == AF_INET) + /* NB, must not alter name if we return zero */ + for (p = tail; *p; p++) { - if (!c->is6 && - ntohl(addr->addr.addr4.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr->addr.addr4.s_addr) <= ntohl(c->end.s_addr)) - found = 1; + char c = *p; + + if ((c >='0' && c <= '9') || c == '-') + continue; + +#ifdef HAVE_IPV6 + if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) + continue; +#endif + + break; } + + if (*p != '.') + continue; + + *p = 0; + #ifdef HAVE_IPV6 + if (prot == AF_INET6 && strstr(tail, "--ffff-") == tail) + { + /* special hack for v4-mapped. */ + memcpy(tail, "::ffff:", 7); + for (p = tail + 7; *p; p++) + if (*p == '-') + *p = '.'; + } else +#endif { - u64 addrpart = addr6part(&addr->addr.addr6); - - if (c->is6 && - is_same_net6(&addr->addr.addr6, &c->start6, 64) && - addrpart >= addr6part(&c->start6) && - addrpart <= addr6part(&c->end6)) - found = 1; + /* swap . or : for - */ + for (p = tail; *p; p++) + if (*p == '-') + { + if (prot == AF_INET) + *p = '.'; +#ifdef HAVE_IPV6 + else + *p = ':'; +#endif + } } + + if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr)) + { + if (prot == AF_INET) + { + if (!c->is6 && + ntohl(addr->addr.addr4.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr->addr.addr4.s_addr) <= ntohl(c->end.s_addr)) + found = 1; + } +#ifdef HAVE_IPV6 + else + { + u64 addrpart = addr6part(&addr->addr.addr6); + + if (c->is6 && + is_same_net6(&addr->addr.addr6, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + found = 1; + } #endif + } + } - + /* restore name */ for (p = tail; *p; p++) if (*p == '.' || *p == ':') *p = '-'; *p = '.'; - + + if (found) return 1; } @@ -136,14 +199,22 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) char *p; *name = 0; - if (c->prefix) - strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); + if (c->indexed) + { + unsigned int index = ntohl(addr->addr.addr4.s_addr) - ntohl(c->start.s_addr); + snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index); + } + else + { + if (c->prefix) + strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); + + inet_ntop(AF_INET, &addr->addr.addr4, name + strlen(name), ADDRSTRLEN); + for (p = name; *p; p++) + if (*p == '.') + *p = '-'; + } - inet_ntop(AF_INET, &addr->addr.addr4, name + strlen(name), ADDRSTRLEN); - for (p = name; *p; p++) - if (*p == '.') - *p = '-'; - strncat(name, ".", MAXDNAME); strncat(name, c->domain, MAXDNAME); @@ -156,22 +227,32 @@ int is_rev_synth(int flag, struct all_addr *addr, char *name) char *p; *name = 0; - if (c->prefix) - strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); - - inet_ntop(AF_INET6, &addr->addr.addr6, name + strlen(name), ADDRSTRLEN); - - /* IPv6 presentation address can start with ":", but valid domain names - cannot start with "-" so prepend a zero in that case. */ - if (!c->prefix && *name == ':') + if (c->indexed) { - *name = '0'; - inet_ntop(AF_INET6, &addr->addr.addr6, name+1, ADDRSTRLEN); + u64 index = addr6part(&addr->addr.addr6) - addr6part(&c->start6); + snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index); } + else + { + if (c->prefix) + strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); + + inet_ntop(AF_INET6, &addr->addr.addr6, name + strlen(name), ADDRSTRLEN); - for (p = name; *p; p++) - if (*p == ':') - *p = '-'; + /* IPv6 presentation address can start with ":", but valid domain names + cannot start with "-" so prepend a zero in that case. */ + if (!c->prefix && *name == ':') + { + *name = '0'; + inet_ntop(AF_INET6, &addr->addr.addr6, name+1, ADDRSTRLEN); + } + + /* V4-mapped have periods.... */ + for (p = name; *p; p++) + if (*p == ':' || *p == '.') + *p = '-'; + + } strncat(name, ".", MAXDNAME); strncat(name, c->domain, MAXDNAME); diff --git a/src/edns0.c b/src/edns0.c new file mode 100644 index 0000000..af33877 --- /dev/null +++ b/src/edns0.c @@ -0,0 +1,449 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t *len, unsigned char **p, int *is_sign, int *is_last) +{ + /* 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 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; + + if (is_last) + *is_last = (i == arcount-1); + + ret = start; + } + else if (is_sign && + i == arcount - 1 && + class == C_ANY && + type == T_TSIG) + *is_sign = 1; + } + + return ret; +} + + +/* replace == 2 ->delete existing option only. */ +size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, + unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace) +{ + unsigned char *lenp, *datap, *p, *udp_len, *buff = NULL; + int rdlen = 0, is_sign, is_last; + unsigned short flags = set_do ? 0x8000 : 0, rcode = 0; + + p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last); + + if (is_sign) + return plen; + + if (p) + { + /* Existing header */ + int i; + unsigned short code, len; + + p = udp_len; + GETSHORT(udp_sz, p); + GETSHORT(rcode, p); + GETSHORT(flags, p); + + if (set_do) + { + p -= 2; + flags |= 0x8000; + PUTSHORT(flags, 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;) + { + GETSHORT(code, p); + GETSHORT(len, p); + + /* malformed option, delete the whole OPT RR and start again. */ + if (i + 4 + len > rdlen) + { + rdlen = 0; + is_last = 0; + break; + } + + if (code == optno) + { + if (replace == 0) + return plen; + + /* delete option if we're to replace it. */ + p -= 4; + rdlen -= len + 4; + memmove(p, p+len+4, rdlen - i); + PUTSHORT(rdlen, lenp); + lenp -= 2; + } + else + { + p += len; + i += len + 4; + } + } + + /* If we're going to extend the RR, it has to be the last RR in the packet */ + if (!is_last) + { + /* First, take a copy of the options. */ + if (rdlen != 0 && (buff = whine_malloc(rdlen))) + memcpy(buff, datap, rdlen); + + /* now, delete OPT RR */ + plen = rrfilter(header, plen, 0); + + /* Now, force addition of a new one */ + p = NULL; + } + } + + if (!p) + { + /* We are (re)adding the pseudoheader */ + if (!(p = skip_questions(header, plen)) || + !(p = skip_section(p, + ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), + header, plen))) + { + free(buff); + return plen; + } + if (p + 11 > limit) + { + free(buff); + return plen; /* Too big */ + } + *p++ = 0; /* empty name */ + PUTSHORT(T_OPT, p); + PUTSHORT(udp_sz, p); /* max packet length, 512 if not given in EDNS0 header */ + PUTSHORT(rcode, p); /* extended RCODE and version */ + PUTSHORT(flags, p); /* DO flag */ + lenp = p; + PUTSHORT(rdlen, p); /* RDLEN */ + datap = p; + /* Copy back any options */ + if (buff) + { + if (p + rdlen > limit) + { + free(buff); + return plen; /* Too big */ + } + memcpy(p, buff, rdlen); + free(buff); + p += rdlen; + } + + /* Only bump arcount if RR is going to fit */ + if (((ssize_t)optlen) <= (limit - (p + 4))) + header->arcount = htons(ntohs(header->arcount) + 1); + } + + if (((ssize_t)optlen) > (limit - (p + 4))) + return plen; /* Too big */ + + /* Add new option */ + if (optno != 0 && replace != 2) + { + if (p + 4 > limit) + return plen; /* Too big */ + PUTSHORT(optno, p); + PUTSHORT(optlen, p); + if (p + optlen > limit) + return plen; /* Too big */ + memcpy(p, opt, optlen); + p += optlen; + PUTSHORT(p - datap, lenp); + } + return p - (unsigned char *)header; +} + +size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit) +{ + return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1, 0); +} + +static unsigned char char64(unsigned char c) +{ + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f]; +} + +static void encoder(unsigned char *in, char *out) +{ + out[0] = char64(in[0]>>2); + out[1] = char64((in[0]<<4) | (in[1]>>4)); + out[2] = char64((in[1]<<2) | (in[2]>>6)); + out[3] = char64(in[2]); +} + +static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +{ + int maclen, replace = 2; /* can't get mac address, just delete any incoming. */ + unsigned char mac[DHCP_CHADDR_MAX]; + char encode[18]; /* handle 6 byte MACs */ + + if ((maclen = find_mac(l3, mac, 1, now)) == 6) + { + replace = 1; + + if (option_bool(OPT_MAC_HEX)) + print_mac(encode, mac, maclen); + else + { + encoder(mac, encode); + encoder(mac+3, encode+4); + encode[8] = 0; + } + } + + return add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace); +} + + +static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +{ + int maclen; + unsigned char mac[DHCP_CHADDR_MAX]; + + if ((maclen = find_mac(l3, mac, 1, now)) != 0) + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); + + return plen; +} + +struct subnet_opt { + u16 family; + u8 source_netmask, scope_netmask; +#ifdef HAVE_IPV6 + u8 addr[IN6ADDRSZ]; +#else + u8 addr[INADDRSZ]; +#endif +}; + +static void *get_addrp(union mysockaddr *addr, const short family) +{ +#ifdef HAVE_IPV6 + if (family == AF_INET6) + return &addr->in6.sin6_addr; +#endif + + return &addr->in.sin_addr; +} + +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 = NULL; + int sa_family = source->sa.sa_family; + + opt->source_netmask = 0; + opt->scope_netmask = 0; + +#ifdef HAVE_IPV6 + if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6) + { + opt->source_netmask = daemon->add_subnet6->mask; + if (daemon->add_subnet6->addr_used) + { + sa_family = daemon->add_subnet6->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet6->addr, sa_family); + } + else + addrp = &source->in6.sin6_addr; + } +#endif + + if (source->sa.sa_family == AF_INET && daemon->add_subnet4) + { + opt->source_netmask = daemon->add_subnet4->mask; + if (daemon->add_subnet4->addr_used) + { + sa_family = daemon->add_subnet4->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet4->addr, sa_family); + } + else + addrp = &source->in.sin_addr; + } + +#ifdef HAVE_IPV6 + opt->family = htons(sa_family == AF_INET6 ? 2 : 1); +#else + opt->family = htons(1); +#endif + + len = 0; + + if (addrp && 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; +} + +static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned 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, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0); +} + +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; +} + +size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *source, time_t now, int *check_subnet) +{ + *check_subnet = 0; + + if (option_bool(OPT_ADD_MAC)) + plen = add_mac(header, plen, limit, source, now); + + if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) + plen = add_dns_client(header, plen, limit, source, now); + + if (daemon->dns_client_id) + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, + (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); + + if (option_bool(OPT_CLIENT_SUBNET)) + { + plen = add_source_addr(header, plen, limit, source); + *check_subnet = 1; + } + + return plen; +} diff --git a/src/forward.c b/src/forward.c index 5e6d9b8..cdd11d3 100644 --- a/src/forward.c +++ b/src/forward.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 @@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id, static unsigned short get_id(void); static void free_frec(struct frec *f); -#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(struct frec *forward, int status, time_t now, char *name, char *keyname); -static int send_check_sign(struct frec *forward, 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, @@ -115,8 +106,8 @@ int send_from(int fd, int nowild, char *packet, size_t len, return 1; } -static unsigned int search_servers(time_t now, struct all_addr **addrpp, - unsigned int qtype, char *qdomain, int *type, char **domain, int *norebind) +static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigned int qtype, + char *qdomain, int *type, char **domain, int *norebind) { /* If the query ends in the domain in one of our servers, set @@ -129,8 +120,10 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigned int flags = 0; for (serv = daemon->servers; serv; serv=serv->next) + if (qtype == F_DNSSECOK && !(serv->flags & SERV_DO_DNSSEC)) + continue; /* domain matches take priority over NODOTS matches */ - if ((serv->flags & SERV_FOR_NODOTS) && *type != SERV_HAS_DOMAIN && !strchr(qdomain, '.') && namelen != 0) + else if ((serv->flags & SERV_FOR_NODOTS) && *type != SERV_HAS_DOMAIN && !strchr(qdomain, '.') && namelen != 0) { unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; *type = SERV_FOR_NODOTS; @@ -160,7 +153,7 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, hostname_isequal(matchstart, serv->domain) && (domainlen == 0 || namelen == domainlen || *(matchstart-1) == '.' )) { - if (serv->flags & SERV_NO_REBIND) + if ((serv->flags & SERV_NO_REBIND) && norebind) *norebind = 1; else { @@ -184,7 +177,7 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, if (domainlen >= matchlen) { - *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND); + *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_DO_DNSSEC); *domain = serv->domain; matchlen = domainlen; if (serv->flags & SERV_NO_ADDR) @@ -211,7 +204,7 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, } } - if (flags == 0 && !(qtype & F_QUERY) && + if (flags == 0 && !(qtype & (F_QUERY | F_DNSSECOK)) && option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0) /* don't forward A or AAAA queries for simple names, except the empty name */ flags = F_NOERR; @@ -242,25 +235,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, struct frec *forward, int ad_reqd, int do_bit) { char *domain = NULL; - int type = 0, norebind = 0; + int type = SERV_DO_DNSSEC, norebind = 0; struct all_addr *addrp = NULL; unsigned int flags = 0; struct server *start = NULL; #ifdef HAVE_DNSSEC void *hash = hash_questions(header, plen, daemon->namebuff); + int do_dnssec = 0; #else unsigned int crc = questions_crc(header, plen, daemon->namebuff); void *hash = &crc; #endif unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); - unsigned char *pheader; (void)do_bit; /* may be no servers available. */ - if (!daemon->servers) - forward = NULL; - else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) + if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) { /* If we didn't get an answer advertising a maximal packet in EDNS, fall back to 1280, which should work everywhere on IPv6. @@ -273,21 +264,21 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, there's no point retrying the query, retry the key query instead...... */ if (forward->blocking_query) { - int fd; + int fd, is_sign; + unsigned char *pheader; forward->flags &= ~FREC_TEST_PKTSZ; while (forward->blocking_query) forward = forward->blocking_query; - forward->flags |= FREC_TEST_PKTSZ; - blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); plen = forward->stash_len; - if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) - PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader); - + forward->flags |= FREC_TEST_PKTSZ; + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(SAFE_PKTSZ, pheader); + if (forward->sentto->addr.sa.sa_family == AF_INET) log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); #ifdef HAVE_IPV6 @@ -324,6 +315,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, daemon->last_server = NULL; } type = forward->sentto->flags & SERV_TYPE; +#ifdef HAVE_DNSSEC + do_dnssec = forward->sentto->flags & SERV_DO_DNSSEC; +#endif + if (!(start = forward->sentto->next)) start = daemon->servers; /* at end of list, recycle */ header->id = htons(forward->new_id); @@ -333,9 +328,14 @@ 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, 0))) - /* table full - server failure. */ - flags = F_NEG; +#ifdef HAVE_DNSSEC + do_dnssec = type & SERV_DO_DNSSEC; +#endif + type &= ~SERV_DO_DNSSEC; + + if (daemon->servers && !flags) + forward = get_new_frec(now, NULL, 0); + /* table full - flags == 0, return REFUSED */ if (forward) { @@ -397,40 +397,46 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (!flags && forward) { struct server *firstsentto = start; - int forwarded = 0; + int subnet, forwarded = 0; + size_t edns0_len; + unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); + unsigned char *pheader; /* If a query is retried, use the log_id for the retry when logging the answer. */ forward->log_id = daemon->log_id; - if (option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); + plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet); + + if (subnet) + forward->flags |= FREC_HAS_SUBNET; - if (option_bool(OPT_CLIENT_SUBNET)) - { - size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); - if (new != plen) - { - plen = new; - forward->flags |= FREC_HAS_SUBNET; - } - } - #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID)) + if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) { - size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz); - + plen = add_do_bit(header, plen, ((unsigned char *) header) + PACKETSZ); + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, this allows it to select auth servers when one is returning bad data. */ if (option_bool(OPT_DNSSEC_DEBUG)) header->hb4 |= HB4_CD; - if (new_plen != plen) + } +#endif + + if (find_pseudoheader(header, plen, &edns0_len, &pheader, NULL, NULL)) + { + /* If there wasn't a PH before, and there is now, we added it. */ + if (!oph) forward->flags |= FREC_ADDED_PHEADER; - plen = new_plen; + /* If we're sending an EDNS0 with any options, we can't recreate the query from a reply. */ + if (edns0_len > 11) + forward->flags |= FREC_HAS_EXTRADATA; + + /* Reduce udp size on retransmits. */ + if (forward->flags & FREC_TEST_PKTSZ) + PUTSHORT(SAFE_PKTSZ, pheader); } -#endif while (1) { @@ -478,10 +484,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } #endif } - - if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) - PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader); +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) + { + /* Difficult one here. If our client didn't send EDNS0, we will have set the UDP + packet size to 512. But that won't provide space for the RRSIGS in many cases. + The RRSIGS will be stripped out before the answer goes back, so the packet should + shrink again. So, if we added a do-bit, bump the udp packet size to the value + known to be OK for this server. We check returned size after stripping and set + the truncated bit if it's still too big. */ + unsigned char *pheader; + int is_sign; + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(start->edns_pktsz, pheader); + } +#endif + if (retry_send(sendto(fd, (char *)header, plen, 0, &start->addr.sa, sa_len(&start->addr)))) @@ -545,7 +564,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server char **sets = 0; int munged = 0, is_sign; size_t plen; - + (void)ad_reqd; (void)do_bit; (void)bogusanswer; @@ -572,30 +591,49 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } #endif - /* If upstream is advertising a larger UDP packet size - than we allow, trim it so that we don't get overlarge - requests for the client. We can't do this for signed packets. */ - - if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign))) + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) { - unsigned short udpsz; - unsigned char *psave = sizep; - - GETSHORT(udpsz, sizep); - - if (!is_sign && udpsz > daemon->edns_pktsz) - PUTSHORT(daemon->edns_pktsz, psave); - if (check_subnet && !check_source(header, plen, pheader, query_source)) { my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch")); return 0; } - if (added_pheader) + if (!is_sign) { - pheader = 0; - header->arcount = htons(0); + if (added_pheader) + { + /* client didn't send EDNS0, we added one, strip it off before returning answer. */ + n = rrfilter(header, n, 0); + pheader = NULL; + } + else + { + unsigned short udpsz; + + /* If upstream is advertising a larger UDP packet size + than we allow, trim it so that we don't get overlarge + requests for the client. We can't do this for signed packets. */ + GETSHORT(udpsz, sizep); + if (udpsz > daemon->edns_pktsz) + { + sizep -= 2; + PUTSHORT(daemon->edns_pktsz, sizep); + } + +#ifdef HAVE_DNSSEC + /* If the client didn't set the do bit, but we did, reset it. */ + if (option_bool(OPT_DNSSEC_VALID) && !do_bit) + { + unsigned short flags; + sizep += 2; /* skip RCODE */ + GETSHORT(flags, sizep); + flags &= ~0x8000; + sizep -= 2; + PUTSHORT(flags, sizep); + } +#endif + } } } @@ -607,7 +645,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server return resize_packet(header, n, pheader, plen); /* Complain loudly if the upstream server is non-recursive. */ - if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && ntohs(header->ancount) == 0 && + if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && server && !(server->flags & SERV_WARNED_RECURSIVE)) { prettyprint_addr(&server->addr, daemon->namebuff); @@ -653,25 +691,24 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } #ifdef HAVE_DNSSEC - if (bogusanswer && !(header->hb4 & HB4_CD)) + if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG)) { - if (!option_bool(OPT_DNSSEC_DEBUG)) - { - /* Bogus reply, turn into SERVFAIL */ - SET_RCODE(header, SERVFAIL); - munged = 1; - } + /* Bogus reply, turn into SERVFAIL */ + SET_RCODE(header, SERVFAIL); + munged = 1; } if (option_bool(OPT_DNSSEC_VALID)) - header->hb4 &= ~HB4_AD; - - if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) - header->hb4 |= HB4_AD; - - /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ - if (!do_bit) - n = filter_rrsigs(header, n); + { + header->hb4 &= ~HB4_AD; + + if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) + header->hb4 |= HB4_AD; + + /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ + if (!do_bit) + n = rrfilter(header, n, 1); + } #endif /* do this after extract_addresses. Ensure NODATA reply and remove @@ -731,7 +768,11 @@ void reply_query(int fd, int family, time_t now) if (!server) return; - + + /* If sufficient time has elapsed, try and expand UDP buffer size again. */ + if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) + server->edns_pktsz = daemon->edns_pktsz; + #ifdef HAVE_DNSSEC hash = hash_questions(header, n, daemon->namebuff); #else @@ -751,18 +792,30 @@ void reply_query(int fd, int family, time_t now) check_for_ignored_address(header, n, daemon->ignore_addr)) return; + /* Note: if we send extra options in the EDNS0 header, we can't recreate + the query from the reply. */ if (RCODE(header) == REFUSED && - !option_bool(OPT_ORDER) && - forward->forwardall == 0) + forward->forwardall == 0 && + !(forward->flags & FREC_HAS_EXTRADATA)) /* for broken servers, attempt to send to another one. */ { unsigned char *pheader; size_t plen; int is_sign; - + + /* In strict order mode, there must be a server later in the chain + left to send to, otherwise without the forwardall mechanism, + code further on will cycle around the list forwever if they + all return REFUSED. Note that server is always non-NULL before + this executes. */ + if (option_bool(OPT_ORDER)) + for (server = forward->sentto->next; server; server = server->next) + if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR | SERV_LOOP))) + break; + /* recreate query from reply */ - pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign); - if (!is_sign) + pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign, NULL); + if (!is_sign && server) { header->ancount = htons(0); header->nscount = htons(0); @@ -770,8 +823,14 @@ void reply_query(int fd, int family, time_t now) if ((nn = resize_packet(header, (size_t)n, pheader, plen))) { header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC); - header->hb4 &= ~(HB4_RA | HB4_RCODE); - forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0); + header->hb4 &= ~(HB4_RA | HB4_RCODE | HB4_CD | HB4_AD); + if (forward->flags & FREC_CHECKING_DISABLED) + header->hb4 |= HB4_CD; + if (forward->flags & FREC_AD_QUESTION) + header->hb4 |= HB4_AD; + if (forward->flags & FREC_DO_QUESTION) + add_do_bit(header, nn, (unsigned char *)pheader + plen); + forward_query(-1, NULL, NULL, 0, header, nn, now, forward, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION); return; } } @@ -800,20 +859,27 @@ void reply_query(int fd, int family, time_t now) } /* We tried resending to this server with a smaller maximum size and got an answer. - Make that permanent. To avoid reduxing the packet size for an single dropped packet, + Make that permanent. To avoid reduxing the packet size for a single dropped packet, only do this when we get a truncated answer, or one larger than the safe size. */ - if (server && (forward->flags & FREC_TEST_PKTSZ) && + if (server && server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) - server->edns_pktsz = SAFE_PKTSZ; - + { + server->edns_pktsz = SAFE_PKTSZ; + server->pktsz_reduced = now; + prettyprint_addr(&server->addr, daemon->addrbuff); + my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ); + } + + /* 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 everything is broken */ - if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != SERVFAIL) + if (forward->forwardall == 0 || --forward->forwardall == 1 || + (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) { int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; - + if (option_bool(OPT_NO_REBIND)) check_rebind = !(forward->flags & FREC_NOREBIND); @@ -823,238 +889,189 @@ void reply_query(int fd, int family, time_t now) no_cache_dnssec = 1; #ifdef HAVE_DNSSEC - if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) + if (server && (server->flags & SERV_DO_DNSSEC) && + option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { - int status; + int status = 0; /* We've had a reply already, which we're validating. Ignore this duplicate */ if (forward->blocking_query) return; - + + /* Truncated answer can't be validated. + If this is an answer to a DNSSEC-generated query, we still + need to get the client to retry over TCP, so return + an answer with the TC bit set, even if the actual answer fits. + */ if (header->hb3 & HB3_TC) + status = STAT_TRUNCATED; + + while (1) { - /* Truncated answer can't be validated. - If this is an answer to a DNSSEC-generated query, we still - need to get the client to retry over TCP, so return - an answer with the TC bit set, even if the actual answer fits. - */ - status = STAT_TRUNCATED; - } - 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); - /* Provably no DS, everything below is insecure, even if signatures are offered */ - if (status == STAT_NO_DS) - /* We only cache sigs when we've validated a reply. - Avoid caching a reply with sigs if there's a vaildated break in the - DS chain, so we don't return replies from cache missing sigs. */ - status = STAT_INSECURE_DS; - else if (status == STAT_NO_SIG) - { - if (option_bool(OPT_DNSSEC_NO_SIGN)) - { - status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); - if (status == STAT_INSECURE) - status = STAT_INSECURE_DS; - } - else - status = STAT_INSECURE_DS; - } - else if (status == STAT_NO_NS) - status = STAT_BOGUS; - } - else if (forward->flags & FREC_CHECK_NOSIGN) - { - status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); - if (status != STAT_NEED_KEY) - status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); - } - else - { - status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); - if (status == STAT_NO_SIG) + /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise + would invite infinite loops, since the answers to DNSKEY and DS queries + will not be cached, so they'll be repeated. */ + if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) { - if (option_bool(OPT_DNSSEC_NO_SIGN)) - status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + 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); else - status = STAT_INSECURE; + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, + option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), + NULL, NULL); } - } - /* 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_DS_NEG || status == STAT_NEED_KEY) - { - struct frec *new, *orig; - - /* Free any saved query */ - if (forward->stash) - blockdata_free(forward->stash); - - /* Now save reply pending receipt of key data */ - if (!(forward->stash = blockdata_alloc((char *)header, n))) - return; - forward->stash_len = n; - anotherkey: - /* Find the original query that started it all.... */ - for (orig = forward; orig->dependent; orig = orig->dependent); - - if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) - status = STAT_INSECURE; - else + /* 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) { - int fd; - struct frec *next = new->next; - *new = *forward; /* copy everything, then overwrite */ - new->next = next; - new->blocking_query = NULL; - new->sentto = server; - new->rfd4 = NULL; - new->orig_domain = NULL; -#ifdef HAVE_IPV6 - new->rfd6 = NULL; -#endif - new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN); + struct frec *new, *orig; - new->dependent = forward; /* to find query awaiting new one. */ - forward->blocking_query = new; /* for garbage cleaning */ - /* validate routines leave name of required record in daemon->keyname */ - if (status == STAT_NEED_KEY) - { - new->flags |= FREC_DNSKEY_QUERY; - nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, - daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); - } - else - { - 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, server->edns_pktsz); - } - if ((hash = hash_questions(header, nn, daemon->namebuff))) - memcpy(new->hash, hash, HASH_SIZE); - new->new_id = get_id(); - header->id = htons(new->new_id); - /* Save query for retransmission */ - if (!(new->stash = blockdata_alloc((char *)header, nn))) + /* Free any saved query */ + if (forward->stash) + blockdata_free(forward->stash); + + /* Now save reply pending receipt of key data */ + if (!(forward->stash = blockdata_alloc((char *)header, n))) return; - - new->stash_len = nn; + forward->stash_len = n; - /* Don't resend this. */ - daemon->srv_save = NULL; + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); - if (server->sfd) - fd = server->sfd->fd; + if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) + status = STAT_ABANDONED; else { - fd = -1; + int fd, type = SERV_DO_DNSSEC; + struct frec *next = new->next; + char *domain; + + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->blocking_query = NULL; + + /* Find server to forward to. This will normally be the + same as for the original query, but may be another if + servers for domains are involved. */ + if (search_servers(now, NULL, F_DNSSECOK, daemon->keyname, &type, &domain, NULL) == 0) + { + struct server *start = server, *new_server = NULL; + + while (1) + { + if (type == (start->flags & (SERV_TYPE | SERV_DO_DNSSEC)) && + (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && + !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) + { + new_server = start; + if (server == start) + { + new_server = NULL; + break; + } + } + + if (!(start = start->next)) + start = daemon->servers; + if (start == server) + break; + } + + if (new_server) + server = new_server; + } + + new->sentto = server; + new->rfd4 = NULL; #ifdef HAVE_IPV6 - if (server->addr.sa.sa_family == AF_INET6) + new->rfd6 = NULL; +#endif + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); + + new->dependent = forward; /* to find query awaiting new one. */ + forward->blocking_query = new; /* for garbage cleaning */ + /* validate routines leave name of required record in daemon->keyname */ + if (status == STAT_NEED_KEY) { - if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) - fd = new->rfd6->fd; + new->flags |= FREC_DNSKEY_QUERY; + nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); } + else + { + new->flags |= FREC_DS_QUERY; + nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } + if ((hash = hash_questions(header, nn, daemon->namebuff))) + memcpy(new->hash, hash, HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ + new->stash = blockdata_alloc((char *)header, nn); + new->stash_len = nn; + + /* Don't resend this. */ + daemon->srv_save = NULL; + + if (server->sfd) + fd = server->sfd->fd; else + { + fd = -1; +#ifdef HAVE_IPV6 + if (server->addr.sa.sa_family == AF_INET6) + { + if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) + fd = new->rfd6->fd; + } + else #endif + { + if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) + fd = new->rfd4->fd; + } + } + + if (fd != -1) { - if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) - fd = new->rfd4->fd; +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif + while (retry_send(sendto(fd, (char *)header, nn, 0, + &server->addr.sa, + sa_len(&server->addr)))); + server->queries++; } - } - - if (fd != -1) - { - while (retry_send(sendto(fd, (char *)header, nn, 0, - &server->addr.sa, - sa_len(&server->addr)))); - server->queries++; - } - + } 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. Note that if an answer needs multiple - keys to validate, we may find another key is needed, in which case we set off - down another branch of the tree. Once we get to the original answer - (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ - while (forward->dependent) - { + /* Validated original answer, all done. */ + if (!forward->dependent) + break; + + /* validated subsidiary query, (and cached result) + pop that and return to the previous query we were working on. */ struct frec *prev = forward->dependent; free_frec(forward); forward = prev; forward->blocking_query = NULL; /* already gone */ blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); n = forward->stash_len; - - if (status == STAT_SECURE) - { - 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); - /* Provably no DS, everything below is insecure, even if signatures are offered */ - if (status == STAT_NO_DS) - /* We only cache sigs when we've validated a reply. - Avoid caching a reply with sigs if there's a vaildated break in the - DS chain, so we don't return replies from cache missing sigs. */ - status = STAT_INSECURE_DS; - else if (status == STAT_NO_SIG) - { - if (option_bool(OPT_DNSSEC_NO_SIGN)) - { - status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); - if (status == STAT_INSECURE) - status = STAT_INSECURE_DS; - } - else - status = STAT_INSECURE_DS; - } - else if (status == STAT_NO_NS) - status = STAT_BOGUS; - } - else if (forward->flags & FREC_CHECK_NOSIGN) - { - status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); - if (status != STAT_NEED_KEY) - status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); - } - else - { - status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); - if (status == STAT_NO_SIG) - { - if (option_bool(OPT_DNSSEC_NO_SIGN)) - status = send_check_sign(forward, 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; - } } + no_cache_dnssec = 0; - - if (status == STAT_INSECURE_DS) - { - /* We only cache sigs when we've validated a reply. - Avoid caching a reply with sigs if there's a vaildated break in the - DS chain, so we don't return replies from cache missing sigs. */ - status = STAT_INSECURE; - no_cache_dnssec = 1; - } if (status == STAT_TRUNCATED) header->hb3 |= HB3_TC; @@ -1062,7 +1079,7 @@ void reply_query(int fd, int family, time_t now) { char *result, *domain = "result"; - if (forward->work_counter == 0) + if (status == STAT_ABANDONED) { result = "ABANDONED"; status = STAT_BOGUS; @@ -1072,7 +1089,7 @@ void reply_query(int fd, int family, time_t now) if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) domain = daemon->namebuff; - + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); } @@ -1092,12 +1109,25 @@ void reply_query(int fd, int family, time_t now) else header->hb4 &= ~HB4_CD; - if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) { header->id = htons(forward->orig_id); header->hb4 |= HB4_RA; /* recursion if available */ +#ifdef HAVE_DNSSEC + /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size + greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0 + header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */ + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ)) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + header->hb3 |= HB3_TC; + nn = resize_packet(header, nn, NULL, 0); + } +#endif send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } @@ -1110,12 +1140,13 @@ void receive_query(struct listener *listen, time_t now) { struct dns_header *header = (struct dns_header *)daemon->packet; union mysockaddr source_addr; - unsigned short type; + unsigned char *pheader; + unsigned short type, udp_size = PACKETSZ; /* default if no EDNS0 */ struct all_addr dst_addr; struct in_addr netmask, dst_addr_4; size_t m; ssize_t n; - int if_index = 0, auth_dns = 0; + int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0; #ifdef HAVE_AUTH int local_auth = 0; #endif @@ -1321,8 +1352,8 @@ void receive_query(struct listener *listen, time_t now) { struct irec *iface; - /* get the netmask of the interface whch has the address we were sent to. - This is no neccessarily the interface we arrived on. */ + /* get the netmask of the interface which has the address we were sent to. + This is no necessarily the interface we arrived on. */ for (iface = daemon->interfaces; iface; iface = iface->next) if (iface->addr.sa.sa_family == AF_INET && @@ -1369,7 +1400,7 @@ void receive_query(struct listener *listen, time_t now) #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ - if (!auth_dns) + if (!auth_dns && !option_bool(OPT_LOCALISE)) for (zone = daemon->auth_zones; zone; zone = zone->next) if (in_zone(zone, daemon->namebuff, NULL)) { @@ -1386,10 +1417,32 @@ void receive_query(struct listener *listen, time_t now) #endif } + if (find_pseudoheader(header, (size_t)n, NULL, &pheader, NULL, NULL)) + { + unsigned short flags; + + have_pseudoheader = 1; + GETSHORT(udp_size, pheader); + pheader += 2; /* ext_rcode */ + GETSHORT(flags, pheader); + + if (flags & 0x8000) + do_bit = 1;/* do bit */ + + /* If the client provides an EDNS0 UDP size, use that to limit our reply. + (bounded by the maximum configured). If no EDNS0, then it + defaults to 512 */ + if (udp_size > daemon->edns_pktsz) + udp_size = daemon->edns_pktsz; + else if (udp_size < PACKETSZ) + udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ + } + #ifdef HAVE_AUTH if (auth_dns) { - m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth); + m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, + local_auth, do_bit, have_pseudoheader); if (m >= 1) { send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), @@ -1400,9 +1453,13 @@ void receive_query(struct listener *listen, time_t now) else #endif { - int ad_reqd, do_bit; - m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, - dst_addr_4, netmask, now, &ad_reqd, &do_bit); + int ad_reqd = do_bit; + /* RFC 6840 5.7 */ + if (header->hb4 & HB4_AD) + ad_reqd = 1; + + m = answer_request(header, ((char *) header) + udp_size, (size_t)n, + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader); if (m >= 1) { @@ -1419,390 +1476,152 @@ 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(struct frec *forward, time_t now, struct dns_header *header, size_t plen, - char *name, char *keyname) +/* Recurse up the key hierarchy */ +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 have_mark, unsigned int mark, int *keycount) { - int status = dnssec_chase_cname(now, header, plen, name, keyname); - - if (status != STAT_INSECURE) - return status; - - /* Store the domain we're trying to check. */ - forward->name_start = strlen(name); - forward->name_len = forward->name_start + 1; - if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len))) - return STAT_BOGUS; - - return do_check_sign(forward, 0, now, name, keyname); -} + int new_status; + unsigned char *packet = NULL; + unsigned char *payload = NULL; + struct dns_header *new_header = NULL; + u16 *length = NULL; -/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */ -static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname) -{ - /* get domain we're checking back from blockdata store, it's stored on the original query. */ - while (forward->dependent && !forward->orig_domain) - forward = forward->dependent; - - blockdata_retrieve(forward->orig_domain, forward->name_len, name); - while (1) { - char *p; + int type = SERV_DO_DNSSEC; + char *domain; + size_t m; + unsigned char c1, c2; + struct server *firstsendto = NULL; + + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ + if (--(*keycount) == 0) + new_status = STAT_ABANDONED; + else 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, + option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), + NULL, NULL); + + if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) + break; - if (status == 0) + /* Can't validate because we need a key/DS whose name now in keyname. + Make query for same, and recurse to validate */ + if (!packet) { - struct crec *crecp; - - /* Haven't received answer, see if in cache */ - if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS))) - { - /* put name of DS record we're missing into keyname */ - strcpy(keyname, &name[forward->name_start]); - /* and wait for reply to arrive */ - return STAT_NEED_DS_NEG; - } - - /* F_DNSSECOK misused in DS cache records to non-existance of NS record */ - if (!(crecp->flags & F_NEG)) - status = STAT_SECURE; - else if (crecp->flags & F_DNSSECOK) - status = STAT_NO_DS; - else - status = STAT_NO_NS; + packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + payload = &packet[2]; + new_header = (struct dns_header *)payload; + length = (u16 *)packet; } - /* Have entered non-signed part of DNS tree. */ - if (status == STAT_NO_DS) - return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE; - - if (status == STAT_BOGUS) - return STAT_BOGUS; - - if (status == STAT_NO_SIG && *keyname != 0) + if (!packet) { - /* There is a validated CNAME chain that doesn't end in a DS record. Start - the search again in that domain. */ - blockdata_free(forward->orig_domain); - forward->name_start = strlen(keyname); - forward->name_len = forward->name_start + 1; - if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len))) - return STAT_BOGUS; - - strcpy(name, keyname); - status = 0; /* force to cache when we iterate. */ - continue; + new_status = STAT_ABANDONED; + break; } + + m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); - /* There's a proven DS record, or we're within a zone, where there doesn't need - to be a DS record. Add a name and try again. - If we've already tried the whole name, then fail */ - - if (forward->name_start == 0) - return STAT_BOGUS; - - for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--); - - if (p != name) - p++; - - forward->name_start = p - name; - status = 0; /* force to cache when we iterate. */ - } -} - -/* Move down from 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; - int status, name_len; - struct blockdata *block; - - char *name_start; - - /* 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; - - /* Stash the name away, since the buffer will be trashed when we recurse */ - name_len = strlen(name) + 1; - name_start = name + name_len - 1; - - if (!(block = blockdata_alloc(name, name_len))) - { - free(packet); - return STAT_BOGUS; - } - - while (1) - { - unsigned char c1, c2; - struct crec *crecp; + *length = htons(m); - if (--(*keycount) == 0) + /* Find server to forward to. This will normally be the + same as for the original query, but may be another if + servers for domains are involved. */ + if (search_servers(now, NULL, F_DNSSECOK, keyname, &type, &domain, NULL) != 0) { - free(packet); - blockdata_free(block); - return STAT_BOGUS; - } - - while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS))) - { - if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)) - { - /* Found a secure denial of DS - delegation is indeed insecure */ - free(packet); - blockdata_free(block); - return STAT_INSECURE; - } - - /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. - Add another label and continue. */ - - if (name_start == name) - { - free(packet); - blockdata_free(block); - return STAT_BOGUS; /* run out of labels */ - } - - name_start -= 2; - while (*name_start != '.' && name_start != name) - name_start--; - if (name_start != name) - name_start++; + new_status = STAT_ABANDONED; + break; } - - /* Can't find it in the cache, have to send a query */ - - m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz); - - *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)) + + while (1) { - m = (c1 << 8) | c2; - - /* Note this trashes all three name workspaces */ - status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount); - - if (status == STAT_NO_DS) + if (!firstsendto) + firstsendto = server; + else { - /* Found a secure denial of DS - delegation is indeed insecure */ - free(packet); - blockdata_free(block); - return STAT_INSECURE; + if (!(server = server->next)) + server = daemon->servers; + if (server == firstsendto) + { + /* can't find server to accept our query. */ + new_status = STAT_ABANDONED; + break; + } } - if (status == STAT_NO_SIG && *keyname != 0) - { - /* There is a validated CNAME chain that doesn't end in a DS record. Start - the search again in that domain. */ - blockdata_free(block); - name_len = strlen(keyname) + 1; - name_start = name + name_len - 1; - - if (!(block = blockdata_alloc(keyname, name_len))) - return STAT_BOGUS; - - strcpy(name, keyname); - continue; - } + if (type != (server->flags & (SERV_TYPE | SERV_DO_DNSSEC)) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, server->domain)) || + (server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) + continue; + + retry: + /* may need to make new connection. */ + if (server->tcpfd == -1) + { + if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; /* No good, next server */ + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (have_mark) + setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +#endif + + if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 0, 1) || + connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1) + { + close(server->tcpfd); + server->tcpfd = -1; + continue; /* No good, next server */ + } + + server->flags &= ~SERV_GOT_TCP; + } - if (status == STAT_BOGUS) + 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)) { - free(packet); - blockdata_free(block); - return STAT_BOGUS; + close(server->tcpfd); + server->tcpfd = -1; + /* We get data then EOF, reopen connection to same server, + else try next. This avoids DoS from a server which accepts + connections and then closes them. */ + if (server->flags & SERV_GOT_TCP) + goto retry; + else + continue; } - /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. - Add another label and continue. */ + server->flags |= SERV_GOT_TCP; - /* Get name we're checking back. */ - blockdata_retrieve(block, name_len, name); - - if (name_start == name) - { - free(packet); - blockdata_free(block); - return STAT_BOGUS; /* run out of labels */ - } - - name_start -= 2; - while (*name_start != '.' && name_start != name) - name_start--; - if (name_start != name) - name_start++; - } - else - { - /* IO failure */ - free(packet); - blockdata_free(block); - return STAT_BOGUS; /* run out of labels */ - } - } -} - -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) -{ - /* Recurse up the key heirarchy */ - int new_status; - - /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ - if (--(*keycount) == 0) - return STAT_INSECURE; - - if (status == STAT_NEED_KEY) - new_status = dnssec_validate_by_ds(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) - { - if (new_status == STAT_NO_DS) - new_status = STAT_INSECURE_DS; - 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); - if (new_status == STAT_INSECURE) - new_status = STAT_INSECURE_DS; - } - else - new_status = STAT_INSECURE_DS; - } - else if (new_status == STAT_NO_NS) - new_status = STAT_BOGUS; - } - } - 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, 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) - { - size_t m; - unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); - unsigned char *payload = &packet[2]; - struct dns_header *new_header = (struct dns_header *)payload; - u16 *length = (u16 *)packet; - unsigned char c1, c2; - - if (!packet) - return STAT_INSECURE; - - another_tcp_key: - m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, - new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); - - *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)) - new_status = STAT_INSECURE; - else - { m = (c1 << 8) | c2; - - 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. - If so, go round again. */ - - if (status == STAT_NEED_KEY) - new_status = dnssec_validate_by_ds(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) - { - if (new_status == STAT_NO_DS) - new_status = STAT_INSECURE_DS; - else 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); - if (new_status == STAT_INSECURE) - new_status = STAT_INSECURE_DS; - } - else - new_status = STAT_INSECURE_DS; - } - else if (new_status == STAT_NO_NS) - new_status = STAT_BOGUS; - } - } - 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, 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; - } + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); + break; } - free(packet); + if (new_status != STAT_OK) + break; } + + if (packet) + free(packet); + return new_status; } #endif /* The daemon forks before calling this: it should deal with one connection, - blocking as neccessary, and then return. Note, need to be a bit careful + blocking as necessary, and then return. Note, need to be a bit careful about resources for debug mode, when the fork is suppressed: that's done by the caller. */ unsigned char *tcp_request(int confd, time_t now, @@ -1813,7 +1632,7 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_AUTH int local_auth = 0; #endif - int checking_disabled, ad_question, do_bit, added_pheader = 0; + int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; unsigned short qtype; @@ -1830,10 +1649,32 @@ unsigned char *tcp_request(int confd, time_t now, union mysockaddr peer_addr; socklen_t peer_len = sizeof(union mysockaddr); int query_count = 0; + unsigned char *pheader; + unsigned int mark = 0; + int have_mark = 0; + + (void)mark; + (void)have_mark; if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) return packet; - + +#ifdef HAVE_CONNTRACK + /* Get connection mark of incoming query to set on outgoing connections. */ + if (option_bool(OPT_CONNTRACK)) + { + struct all_addr local; +#ifdef HAVE_IPV6 + if (local_addr->sa.sa_family == AF_INET6) + local.addr.addr6 = local_addr->in6.sin6_addr; + else +#endif + local.addr.addr4 = local_addr->in.sin_addr; + + have_mark = get_incoming_mark(&peer_addr, &local, 1, &mark); + } +#endif + /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ if (option_bool(OPT_LOCAL_SERVICE)) { @@ -1888,8 +1729,6 @@ unsigned char *tcp_request(int confd, time_t now, daemon->log_display_id = ++daemon->log_id; daemon->log_source_addr = &peer_addr; - check_subnet = 0; - /* save state of "cd" flag in query */ if ((checking_disabled = header->hb4 & HB4_CD)) no_cache_dnssec = 1; @@ -1912,7 +1751,7 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ - if (!auth_dns) + if (!auth_dns && !option_bool(OPT_LOCALISE)) for (zone = daemon->auth_zones; zone; zone = zone->next) if (in_zone(zone, daemon->namebuff, NULL)) { @@ -1928,15 +1767,35 @@ unsigned char *tcp_request(int confd, time_t now, else dst_addr_4.s_addr = 0; + do_bit = 0; + + if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL)) + { + unsigned short flags; + + have_pseudoheader = 1; + pheader += 4; /* udp_size, ext_rcode */ + GETSHORT(flags, pheader); + + if (flags & 0x8000) + do_bit = 1; /* do bit */ + } + #ifdef HAVE_AUTH if (auth_dns) - m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth); + m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, + local_auth, do_bit, have_pseudoheader); else #endif { - /* m > 0 if answered from cache */ - m = answer_request(header, ((char *) header) + 65536, (size_t)size, - dst_addr_4, netmask, now, &ad_question, &do_bit); + int ad_reqd = do_bit; + /* RFC 6840 5.7 */ + if (header->hb4 & HB4_AD) + ad_reqd = 1; + + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (size_t)size, + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader); /* Do this by steam now we're not in the select() loop */ check_log_writer(1); @@ -1945,24 +1804,33 @@ unsigned char *tcp_request(int confd, time_t now, { unsigned int flags = 0; struct all_addr *addrp = NULL; - int type = 0; + int type = SERV_DO_DNSSEC; char *domain = NULL; - - if (option_bool(OPT_ADD_MAC)) - size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); - - if (option_bool(OPT_CLIENT_SUBNET)) - { - size_t new = add_source_addr(header, size, ((char *) header) + 65536, &peer_addr); - if (size != new) - { - size = new; - check_subnet = 1; - } - } + unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); + + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (type & SERV_DO_DNSSEC)) + { + size = add_do_bit(header, size, ((unsigned char *) header) + 65536); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) + header->hb4 |= HB4_CD; + } +#endif + + /* Check if we added a pheader on forwarding - may need to + strip it from the reply. */ + if (!oph && find_pseudoheader(header, size, NULL, NULL, NULL, NULL)) + added_pheader = 1; + + type &= ~SERV_DO_DNSSEC; if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) last_server = daemon->servers; @@ -1982,7 +1850,7 @@ unsigned char *tcp_request(int confd, time_t now, unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); #endif /* Loop round available servers until we succeed in connecting to one. - Note that this code subtley ensures that consecutive queries on this connection + Note that this code subtly ensures that consecutive queries on this connection which can go to the same server, do so. */ while (1) { @@ -2002,7 +1870,8 @@ unsigned char *tcp_request(int confd, time_t now, (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) || (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) continue; - + + retry: if (last_server->tcpfd == -1) { if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) @@ -2010,23 +1879,11 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_CONNTRACK /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - { - unsigned int mark; - struct all_addr local; -#ifdef HAVE_IPV6 - if (local_addr->sa.sa_family == AF_INET6) - local.addr.addr6 = local_addr->in6.sin6_addr; - else -#endif - local.addr.addr4 = local_addr->in.sin_addr; - - if (get_incoming_mark(&peer_addr, &local, 1, &mark)) - setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); - } + if (have_mark) + setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); #endif - if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || + if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 0, 1) || connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) { close(last_server->tcpfd); @@ -2034,22 +1891,7 @@ unsigned char *tcp_request(int confd, time_t now, continue; } -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID)) - { - size_t new_size = add_do_bit(header, size, ((char *) header) + 65536); - - /* For debugging, set Checking Disabled, otherwise, have the upstream check too, - this allows it to select auth servers when one is returning bad data. */ - if (option_bool(OPT_DNSSEC_DEBUG)) - header->hb4 |= HB4_CD; - - if (size != new_size) - added_pheader = 1; - - size = new_size; - } -#endif + last_server->flags &= ~SERV_GOT_TCP; } *length = htons(size); @@ -2065,9 +1907,17 @@ unsigned char *tcp_request(int confd, time_t now, { close(last_server->tcpfd); last_server->tcpfd = -1; - continue; - } + /* We get data then EOF, reopen connection to same server, + else try next. This avoids DoS from a server which accepts + connections and then closes them. */ + if (last_server->flags & SERV_GOT_TCP) + goto retry; + else + continue; + } + last_server->flags |= SERV_GOT_TCP; + m = (c1 << 8) | c2; if (last_server->addr.sa.sa_family == AF_INET) @@ -2080,22 +1930,14 @@ unsigned char *tcp_request(int confd, time_t now, #endif #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC)) { int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ - int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); + int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, + last_server, have_mark, mark, &keycount); char *result, *domain = "result"; - - if (status == STAT_INSECURE_DS) - { - /* We only cache sigs when we've validated a reply. - Avoid caching a reply with sigs if there's a vaildated break in the - DS chain, so we don't return replies from cache missing sigs. */ - status = STAT_INSECURE; - no_cache_dnssec = 1; - } - if (keycount == 0) + if (status == STAT_ABANDONED) { result = "ABANDONED"; status = STAT_BOGUS; @@ -2148,7 +1990,7 @@ unsigned char *tcp_request(int confd, time_t now, m = process_reply(header, now, last_server, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, - ad_question, do_bit, added_pheader, check_subnet, &peer_addr); + ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); break; } @@ -2187,7 +2029,6 @@ static struct frec *allocate_frec(time_t now) f->dependent = NULL; f->blocking_query = NULL; f->stash = NULL; - f->orig_domain = NULL; #endif daemon->frec_list = f; } @@ -2256,12 +2097,6 @@ static void free_frec(struct frec *f) f->stash = NULL; } - if (f->orig_domain) - { - blockdata_free(f->orig_domain); - f->orig_domain = NULL; - } - /* Anything we're waiting on is pointless now, too */ if (f->blocking_query) free_frec(f->blocking_query); @@ -2270,6 +2105,8 @@ static void free_frec(struct frec *f) #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 @@ -2289,14 +2126,23 @@ struct frec *get_new_frec(time_t now, int *wait, int force) target = f; else { - if (difftime(now, f->time) >= 4*TIMEOUT) - { - free_frec(f); - target = f; - } - - if (!oldest || difftime(f->time, oldest->time) <= 0) - oldest = f; +#ifdef HAVE_DNSSEC + /* Don't free DNSSEC sub-queries here, as we may end up with + dangling references to them. They'll go when their "real" query + is freed. */ + if (!f->dependent) +#endif + { + if (difftime(now, f->time) >= 4*TIMEOUT) + { + free_frec(f); + target = f; + } + + + if (!oldest || difftime(f->time, oldest->time) <= 0) + oldest = f; + } } if (target) @@ -2307,7 +2153,7 @@ struct frec *get_new_frec(time_t now, int *wait, int force) /* can't find empty one, use oldest if there is one and it's older than timeout */ - if (oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT) + if (!force && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT) { /* keep stuff for twice timeout if we can by allocating a new record instead */ @@ -2347,7 +2193,7 @@ struct frec *get_new_frec(time_t now, int *wait, int force) return f; /* OK if malloc fails and this is NULL */ } - + /* crc is all-ones if not known. */ static struct frec *lookup_frec(unsigned short id, void *hash) { diff --git a/src/helper.c b/src/helper.c index 1fee72d..c134071 100644 --- a/src/helper.c +++ b/src/helper.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 @@ -18,7 +18,7 @@ #ifdef HAVE_SCRIPT -/* This file has code to fork a helper process which recieves data via a pipe +/* This file has code to fork a helper process which receives data via a pipe shared with the main process and which is responsible for calling a script when DHCP leases change. @@ -97,13 +97,14 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) return pipefd[1]; } - /* ignore SIGTERM, so that we can clean up when the main process gets hit + /* ignore SIGTERM and SIGINT, so that we can clean up when the main process gets hit and SIGALRM so that we can use sleep() */ sigact.sa_handler = SIG_IGN; sigact.sa_flags = 0; sigemptyset(&sigact.sa_mask); sigaction(SIGTERM, &sigact, NULL); sigaction(SIGALRM, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); if (!option_bool(OPT_DEBUG) && uid != 0) { @@ -135,7 +136,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) max_fd != STDIN_FILENO && max_fd != pipefd[0] && max_fd != event_fd && max_fd != err_fd) close(max_fd); - + #ifdef HAVE_LUASCRIPT if (daemon->luascript) { @@ -189,6 +190,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) unsigned char *buf = (unsigned char *)daemon->namebuff; unsigned char *end, *extradata, *alloc_buff = NULL; int is6, err = 0; + int pipeout[2]; free(alloc_buff); @@ -219,7 +221,18 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) action_str = "tftp"; is6 = (data.flags != AF_INET); } - else + else if (data.action == ACTION_ARP) + { + action_str = "arp-add"; + is6 = (data.flags != AF_INET); + } + else if (data.action == ACTION_ARP_DEL) + { + action_str = "arp-del"; + is6 = (data.flags != AF_INET); + data.action = ACTION_ARP; + } + else continue; @@ -289,7 +302,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) if (!is6) inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN); -#ifdef HAVE_DHCP6 +#ifdef HAVE_IPV6 else inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN); #endif @@ -321,6 +334,22 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ } } + else if (data.action == ACTION_ARP) + { + lua_getglobal(lua, "arp"); + if (lua_type(lua, -1) != LUA_TFUNCTION) + lua_pop(lua, 1); /* arp function optional */ + else + { + lua_pushstring(lua, action_str); /* arg1 - action */ + lua_newtable(lua); /* arg2 - data table */ + lua_pushstring(lua, daemon->addrbuff); + lua_setfield(lua, -2, "client_address"); + lua_pushstring(lua, daemon->dhcp_buff); + lua_setfield(lua, -2, "mac_address"); + lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ + } + } else { lua_getglobal(lua, "lease"); /* function to call */ @@ -445,16 +474,54 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) if (!daemon->lease_change_command) continue; + /* Pipe to capture stdout and stderr from script */ + if (!option_bool(OPT_DEBUG) && pipe(pipeout) == -1) + continue; + /* possible fork errors are all temporary resource problems */ while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM)) sleep(2); if (pid == -1) - continue; + { + if (!option_bool(OPT_DEBUG)) + { + close(pipeout[0]); + close(pipeout[1]); + } + continue; + } /* wait for child to complete */ if (pid != 0) { + if (!option_bool(OPT_DEBUG)) + { + FILE *fp; + + close(pipeout[1]); + + /* Read lines sent to stdout/err by the script and pass them back to be logged */ + if (!(fp = fdopen(pipeout[0], "r"))) + close(pipeout[0]); + else + { + while (fgets(daemon->packet, daemon->packet_buff_sz, fp)) + { + /* do not include new lines, log will append them */ + size_t len = strlen(daemon->packet); + if (len > 0) + { + --len; + if (daemon->packet[len] == '\n') + daemon->packet[len] = 0; + } + send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet); + } + fclose(fp); + } + } + /* reap our children's children, if necessary */ while (1) { @@ -477,8 +544,17 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) continue; } + + if (!option_bool(OPT_DEBUG)) + { + /* map stdout/stderr of script to pipeout */ + close(pipeout[0]); + dup2(pipeout[1], STDOUT_FILENO); + dup2(pipeout[1], STDERR_FILENO); + close(pipeout[1]); + } - if (data.action != ACTION_TFTP) + if (data.action != ACTION_TFTP && data.action != ACTION_ARP) { #ifdef HAVE_DHCP6 my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err); @@ -529,6 +605,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) buf = grab_extradata(buf, end, "DNSMASQ_CIRCUIT_ID", &err); buf = grab_extradata(buf, end, "DNSMASQ_SUBSCRIBER_ID", &err); buf = grab_extradata(buf, end, "DNSMASQ_REMOTE_ID", &err); + buf = grab_extradata(buf, end, "DNSMASQ_REQUESTED_OPTIONS", &err); } buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err); @@ -550,9 +627,9 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) my_setenv("DNSMASQ_OLD_HOSTNAME", data.action == ACTION_OLD_HOSTNAME ? hostname : NULL, &err); if (data.action == ACTION_OLD_HOSTNAME) hostname = NULL; + + my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err); } - - my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err); /* we need to have the event_fd around if exec fails */ if ((i = fcntl(event_fd, F_GETFD)) != -1) @@ -563,8 +640,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) if (err == 0) { execl(daemon->lease_change_command, - p ? p+1 : daemon->lease_change_command, - action_str, is6 ? daemon->packet : daemon->dhcp_buff, + p ? p+1 : daemon->lease_change_command, action_str, + (is6 && data.action != ACTION_ARP) ? daemon->packet : daemon->dhcp_buff, daemon->addrbuff, hostname, (char*)NULL); err = errno; } @@ -760,6 +837,30 @@ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer) } #endif +void queue_arp(int action, unsigned char *mac, int maclen, int family, struct all_addr *addr) +{ + /* no script */ + if (daemon->helperfd == -1) + return; + + buff_alloc(sizeof(struct script_data)); + memset(buf, 0, sizeof(struct script_data)); + + buf->action = action; + buf->hwaddr_len = maclen; + buf->hwaddr_type = ARPHRD_ETHER; + if ((buf->flags = family) == AF_INET) + buf->addr = addr->addr.addr4; +#ifdef HAVE_IPV6 + else + buf->addr6 = addr->addr.addr6; +#endif + + memcpy(buf->hwaddr, mac, maclen); + + bytes_in_buf = sizeof(struct script_data); +} + int helper_buf_empty(void) { return bytes_in_buf == 0; diff --git a/src/inotify.c b/src/inotify.c index 52d412f..7107833 100644 --- a/src/inotify.c +++ b/src/inotify.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 @@ -20,7 +20,7 @@ #include <sys/inotify.h> #include <sys/param.h> /* For MAXSYMLINKS */ -/* the strategy is to set a inotify on the directories containing +/* the strategy is to set an inotify on the directories containing resolv files, for any files in the directory which are close-write or moved into the directory. @@ -54,7 +54,10 @@ static char *my_readlink(char *path) { /* Not link or doesn't exist. */ if (errno == EINVAL || errno == ENOENT) - return NULL; + { + free(buf); + return NULL; + } else die(_("cannot access path %s: %s"), path, EC_MISC); } @@ -90,6 +93,9 @@ void inotify_dnsmasq_init() if (daemon->inotifyfd == -1) die(_("failed to create inotify: %s"), NULL, EC_MISC); + + if (option_bool(OPT_NO_RESOLV)) + return; for (res = daemon->resolv_files; res; res = res->next) { @@ -98,7 +104,7 @@ void inotify_dnsmasq_init() strcpy(path, res->name); - /* Follow symlinks until we reach a non-symlink, or a non-existant file. */ + /* Follow symlinks until we reach a non-symlink, or a non-existent file. */ while ((new_path = my_readlink(path))) { if (links-- == 0) @@ -197,6 +203,8 @@ void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revh free(path); } } + + closedir(dir_stream); } } @@ -219,19 +227,21 @@ int inotify_check(time_t now) for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) { + size_t namelen; + in = (struct inotify_event*)p; - for (res = daemon->resolv_files; res; res = res->next) - if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0) - hit = 1; - /* ignore emacs backups and dotfiles */ - if (in->len == 0 || - in->name[in->len - 1] == '~' || - (in->name[0] == '#' && in->name[in->len - 1] == '#') || + if (in->len == 0 || (namelen = strlen(in->name)) == 0 || + in->name[namelen - 1] == '~' || + (in->name[0] == '#' && in->name[namelen - 1] == '#') || in->name[0] == '.') continue; - + + for (res = daemon->resolv_files; res; res = res->next) + if (res->wd == in->wd && strcmp(res->file, in->name) == 0) + hit = 1; + for (ah = daemon->dynamic_dirs; ah; ah = ah->next) if (ah->wd == in->wd) { @@ -252,7 +262,7 @@ int inotify_check(time_t now) #ifdef HAVE_DHCP if (daemon->dhcp || daemon->doing_dhcp6) { - /* Propogate the consequences of loading a new dhcp-host */ + /* Propagate the consequences of loading a new dhcp-host */ dhcp_update_configs(daemon->dhcp_conf); lease_update_from_configs(); lease_update_file(now); @@ -265,7 +275,7 @@ int inotify_check(time_t now) { if (option_read_dynfile(path, AH_DHCP_HST)) { - /* Propogate the consequences of loading a new dhcp-host */ + /* Propagate the consequences of loading a new dhcp-host */ dhcp_update_configs(daemon->dhcp_conf); lease_update_from_configs(); lease_update_file(now); diff --git a/src/ip6addr.h b/src/ip6addr.h index f0b7e82..6f6dff7 100644 --- a/src/ip6addr.h +++ b/src/ip6addr.h @@ -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 diff --git a/src/ipset.c b/src/ipset.c index a315e86..3a5ecc5 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -211,7 +211,7 @@ static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove) { - int af = AF_INET; + int ret = 0, af = AF_INET; #ifdef HAVE_IPV6 if (flags & F_IPV6) @@ -219,11 +219,20 @@ int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, af = AF_INET6; /* old method only supports IPv4 */ if (old_kernel) - return -1; + { + errno = EAFNOSUPPORT ; + ret = -1; + } } #endif - return old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); + if (ret != -1) + ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); + + if (ret == -1) + my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno)); + + return ret; } #endif diff --git a/src/lease.c b/src/lease.c index 8adb605..5c33df7 100644 --- a/src/lease.c +++ b/src/lease.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 @@ -21,94 +21,62 @@ static struct dhcp_lease *leases = NULL, *old_leases = NULL; static int dns_dirty, file_dirty, leases_left; -void lease_init(time_t now) +static int read_leases(time_t now, FILE *leasestream) { unsigned long ei; struct all_addr addr; struct dhcp_lease *lease; int clid_len, hw_len, hw_type; - FILE *leasestream; - - leases_left = daemon->dhcp_max; - - if (option_bool(OPT_LEASE_RO)) - { - /* run "<lease_change_script> init" once to get the - initial state of the database. If leasefile-ro is - set without a script, we just do without any - lease database. */ -#ifdef HAVE_SCRIPT - if (daemon->lease_change_command) - { - strcpy(daemon->dhcp_buff, daemon->lease_change_command); - strcat(daemon->dhcp_buff, " init"); - leasestream = popen(daemon->dhcp_buff, "r"); - } - else + int items; + char *domain = NULL; + + *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0'; + + /* client-id max length is 255 which is 255*2 digits + 254 colons + borrow DNS packet buffer which is always larger than 1000 bytes + + Check various buffers are big enough for the code below */ + +#if (DHCP_BUFF_SZ < 255) || (MAXDNAME < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764) +# error Buffer size breakage in leasefile parsing. #endif - { - file_dirty = dns_dirty = 0; - return; - } - } - else - { - /* NOTE: need a+ mode to create file if it doesn't exist */ - leasestream = daemon->lease_stream = fopen(daemon->lease_file, "a+"); - - if (!leasestream) - die(_("cannot open or create lease file %s: %s"), daemon->lease_file, EC_FILE); - - /* a+ mode leaves pointer at end. */ - rewind(leasestream); - } - - /* client-id max length is 255 which is 255*2 digits + 254 colons - borrow DNS packet buffer which is always larger than 1000 bytes */ - if (leasestream) - while (fscanf(leasestream, "%255s %255s", daemon->dhcp_buff3, daemon->dhcp_buff2) == 2) + while ((items=fscanf(leasestream, "%255s %255s", daemon->dhcp_buff3, daemon->dhcp_buff2)) == 2) { + *daemon->namebuff = *daemon->dhcp_buff = *daemon->packet = '\0'; + hw_len = hw_type = clid_len = 0; + #ifdef HAVE_DHCP6 if (strcmp(daemon->dhcp_buff3, "duid") == 0) { daemon->duid_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, 130, NULL, NULL); + if (daemon->duid_len < 0) + return 0; daemon->duid = safe_malloc(daemon->duid_len); memcpy(daemon->duid, daemon->dhcp_buff2, daemon->duid_len); continue; } #endif - - ei = atol(daemon->dhcp_buff3); if (fscanf(leasestream, " %64s %255s %764s", daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3) - break; + return 0; - clid_len = 0; - if (strcmp(daemon->packet, "*") != 0) - clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL); - - if (inet_pton(AF_INET, daemon->namebuff, &addr.addr.addr4) && - (lease = lease4_allocate(addr.addr.addr4))) + if (inet_pton(AF_INET, daemon->namebuff, &addr.addr.addr4)) { + if ((lease = lease4_allocate(addr.addr.addr4))) + domain = get_domain(lease->addr); + hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type); - /* For backwards compatibility, no explict MAC address type means ether. */ + /* For backwards compatibility, no explicit MAC address type means ether. */ if (hw_type == 0 && hw_len != 0) hw_type = ARPHRD_ETHER; - - lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, - hw_len, hw_type, clid_len, now, 0); - - if (strcmp(daemon->dhcp_buff, "*") != 0) - lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain(lease->addr), NULL); } #ifdef HAVE_DHCP6 else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr.addr6)) { char *s = daemon->dhcp_buff2; int lease_type = LEASE_NA; - int iaid; if (s[0] == 'T') { @@ -116,23 +84,30 @@ void lease_init(time_t now) s++; } - iaid = strtoul(s, NULL, 10); - if ((lease = lease6_allocate(&addr.addr.addr6, lease_type))) { - lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, 0, clid_len, now, 0); - lease_set_iaid(lease, iaid); - if (strcmp(daemon->dhcp_buff, "*") != 0) - lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain6((struct in6_addr *)lease->hwaddr), NULL); + lease_set_iaid(lease, strtoul(s, NULL, 10)); + domain = get_domain6((struct in6_addr *)lease->hwaddr); } } #endif else - break; + return 0; if (!lease) die (_("too many stored leases"), NULL, EC_MISC); - + + if (strcmp(daemon->packet, "*") != 0) + clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL); + + lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, + hw_len, hw_type, clid_len, now, 0); + + if (strcmp(daemon->dhcp_buff, "*") != 0) + lease_set_hostname(lease, daemon->dhcp_buff, 0, domain, NULL); + + ei = atol(daemon->dhcp_buff3); + #ifdef HAVE_BROKEN_RTC if (ei != 0) lease->expires = (time_t)ei + now; @@ -148,7 +123,62 @@ void lease_init(time_t now) /* set these correctly: the "old" events are generated later from the startup synthesised SIGHUP. */ lease->flags &= ~(LEASE_NEW | LEASE_CHANGED); + + *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0'; } + + return (items == 0 || items == EOF); +} + +void lease_init(time_t now) +{ + FILE *leasestream; + + leases_left = daemon->dhcp_max; + + if (option_bool(OPT_LEASE_RO)) + { + /* run "<lease_change_script> init" once to get the + initial state of the database. If leasefile-ro is + set without a script, we just do without any + lease database. */ +#ifdef HAVE_SCRIPT + if (daemon->lease_change_command) + { + strcpy(daemon->dhcp_buff, daemon->lease_change_command); + strcat(daemon->dhcp_buff, " init"); + leasestream = popen(daemon->dhcp_buff, "r"); + } + else +#endif + { + file_dirty = dns_dirty = 0; + return; + } + + } + else + { + /* NOTE: need a+ mode to create file if it doesn't exist */ + leasestream = daemon->lease_stream = fopen(daemon->lease_file, "a+"); + + if (!leasestream) + die(_("cannot open or create lease file %s: %s"), daemon->lease_file, EC_FILE); + + /* a+ mode leaves pointer at end. */ + rewind(leasestream); + } + + if (leasestream) + { + if (!read_leases(now, leasestream)) + my_syslog(MS_DHCP | LOG_ERR, _("failed to parse lease database, invalid line: %s %s %s %s ..."), + daemon->dhcp_buff3, daemon->dhcp_buff2, + daemon->namebuff, daemon->dhcp_buff); + + if (ferror(leasestream)) + die(_("failed to read lease file %s: %s"), daemon->lease_file, EC_FILE); + } #ifdef HAVE_SCRIPT if (!daemon->lease_stream) @@ -162,6 +192,7 @@ void lease_init(time_t now) errno = ENOENT; else if (WEXITSTATUS(rc) == 126) errno = EACCES; + die(_("cannot run lease-init script %s: %s"), daemon->lease_change_command, EC_FILE); } @@ -198,7 +229,7 @@ void lease_update_from_configs(void) else if ((name = host_from_dns(lease->addr))) lease_set_hostname(lease, name, 1, get_domain(lease->addr), NULL); /* updates auth flag only */ } - + static void ourprintf(int *errp, char *format, ...) { va_list ap; @@ -406,7 +437,7 @@ void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *inte void lease_update_slaac(time_t now) { - /* Called when we contruct a new RA-names context, to add putative + /* Called when we construct a new RA-names context, to add putative new SLAAC addresses to existing leases. */ struct dhcp_lease *lease; @@ -776,7 +807,7 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) { exp = now + (time_t)len; /* Check for 2038 overflow. Make the lease - inifinite in that case, as the least disruptive + infinite in that case, as the least disruptive thing we can do. */ if (difftime(exp, now) <= 0.0) exp = 0; @@ -1110,18 +1141,22 @@ int do_script_run(time_t now) } #ifdef HAVE_SCRIPT +/* delim == -1 -> delim = 0, but embedded 0s, creating extra records, are OK. */ void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim) { unsigned int i; - /* check for embeded NULLs */ - for (i = 0; i < len; i++) - if (data[i] == 0) - { - len = i; - break; - } - + if (delim == -1) + delim = 0; + else + /* check for embedded NULLs */ + for (i = 0; i < len; i++) + if (data[i] == 0) + { + len = i; + break; + } + if ((lease->extradata_size - lease->extradata_len) < (len + 1)) { size_t newsz = lease->extradata_len + len + 100; @@ -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 @@ -154,7 +154,7 @@ static void log_write(void) while (entries) { - /* The data in the payoad is written with a terminating zero character + /* The data in the payload is written with a terminating zero character and the length reflects this. For a stream connection we need to send the zero as a record terminator, but this isn't done for a datagram connection, so treat the length as one less than reality @@ -288,7 +288,9 @@ void my_syslog(int priority, const char *format, ...) func = "-tftp"; else if ((LOG_FACMASK & priority) == MS_DHCP) func = "-dhcp"; - + else if ((LOG_FACMASK & priority) == MS_SCRIPT) + func = "-script"; + #ifdef LOG_PRI priority = LOG_PRI(priority); #else @@ -436,7 +438,7 @@ void check_log_writer(int force) void flush_log(void) { /* write until queue empty, but don't loop forever if there's - no connection to the syslog in existance */ + no connection to the syslog in existence */ while (log_fd != -1) { struct timespec waiter; @@ -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 diff --git a/src/netlink.c b/src/netlink.c index 753784d..05153f5 100644 --- a/src/netlink.c +++ b/src/netlink.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 @@ -73,7 +73,7 @@ void netlink_init(void) } if (daemon->netlinkfd == -1 || - getsockname(daemon->netlinkfd, (struct sockaddr *)&addr, &slen) == 1) + getsockname(daemon->netlinkfd, (struct sockaddr *)&addr, &slen) == -1) die(_("cannot create netlink socket: %s"), NULL, EC_MISC); /* save pid assigned by bind() and retrieved by getsockname() */ @@ -188,11 +188,17 @@ int iface_enumerate(int family, void *parm, int (*callback)()) } for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) - if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) + if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) { /* May be multicast arriving async */ nl_async(h); } + else if (h->nlmsg_seq != seq) + { + /* May be part of incomplete response to previous request after + ENOBUFS. Drop it. */ + continue; + } else if (h->nlmsg_type == NLMSG_DONE) return callback_ok; else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL) @@ -288,7 +294,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) rta = RTA_NEXT(rta, len1); } - if (inaddr && mac && callback_ok) + if (!(neigh->ndm_state & (NUD_NOARP | NUD_INCOMPLETE | NUD_FAILED)) && + inaddr && mac && callback_ok) if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm))) callback_ok = 0; } diff --git a/src/network.c b/src/network.c index a1d90c8..0381513 100644 --- a/src/network.c +++ b/src/network.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 @@ -178,7 +178,7 @@ int iface_check(int family, struct all_addr *addr, char *name, int *auth) } -/* Fix for problem that the kernel sometimes reports the loopback inerface as the +/* Fix for problem that the kernel sometimes reports the loopback interface as the arrival interface when a packet originates locally, even when sent to address of an interface other than the loopback. Accept packet if it arrived via a loopback interface, even when we're not accepting packets that way, as long as the destination @@ -244,6 +244,7 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, int tftp_ok = !!option_bool(OPT_TFTP); int dhcp_ok = 1; int auth_dns = 0; + int is_label = 0; #if defined(HAVE_DHCP) || defined(HAVE_TFTP) struct iname *tmp; #endif @@ -264,6 +265,8 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, if (!label) label = ifr.ifr_name; + else + is_label = strcmp(label, ifr.ifr_name); /* maintain a list of all addresses on all interfaces for --local-service option */ if (option_bool(OPT_LOCAL_SERVICE)) @@ -482,6 +485,7 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, iface->found = 1; iface->done = iface->multicast_done = iface->warned = 0; iface->index = if_index; + iface->label = is_label; if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1))) { strcpy(iface->name, ifr.ifr_name); @@ -532,13 +536,14 @@ static int iface_allowed_v4(struct in_addr local, int if_index, char *label, { union mysockaddr addr; int prefix, bit; + + (void)broadcast; /* warning */ memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(addr.in); #endif addr.in.sin_family = AF_INET; - addr.in.sin_addr = broadcast; /* warning */ addr.in.sin_addr = local; addr.in.sin_port = htons(daemon->port); @@ -643,7 +648,7 @@ int enumerate_interfaces(int reset) /* Garbage-collect listeners listening on addresses that no longer exist. Does nothing when not binding interfaces or for listeners on localhost, since the ->iface field is NULL. Note that this needs the protections - against re-entrancy, hence it's here. It also means there's a possibility, + against reentrancy, hence it's here. It also means there's a possibility, in OPT_CLEVERBIND mode, that at listener will just disappear after a call to enumerate_interfaces, this is checked OK on all calls. */ struct listener *l, *tmp, **up; @@ -698,7 +703,7 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) if ((fd = socket(family, type, 0)) == -1) { - int port, errsav; + int port, errsave; char *s; /* No error if the kernel just doesn't support this IP flavour */ @@ -708,7 +713,7 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) return -1; err: - errsav = errno; + errsave = errno; port = prettyprint_addr(addr, daemon->addrbuff); if (!option_bool(OPT_NOWILD) && !option_bool(OPT_CLEVERBIND)) sprintf(daemon->addrbuff, "port %d", port); @@ -717,7 +722,7 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) if (fd != -1) close (fd); - errno = errsav; + errno = errsave; if (dienow) { @@ -745,7 +750,7 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) if (type == SOCK_STREAM) { - if (listen(fd, 5) == -1) + if (listen(fd, TCP_BACKLOG) == -1) goto err; } else if (family == AF_INET) @@ -809,10 +814,11 @@ int tcp_interface(int fd, int af) int opt = 1; struct cmsghdr *cmptr; struct msghdr msg; + socklen_t len; - /* use mshdr do that the CMSDG_* macros are available */ + /* use mshdr so that the CMSDG_* macros are available */ msg.msg_control = daemon->packet; - msg.msg_controllen = daemon->packet_buff_sz; + msg.msg_controllen = len = daemon->packet_buff_sz; /* we overwrote the buffer... */ daemon->srv_save = NULL; @@ -820,18 +826,21 @@ int tcp_interface(int fd, int af) if (af == AF_INET) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && - getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, (socklen_t *)&msg.msg_controllen) != -1) - for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) - { - union { - unsigned char *c; - struct in_pktinfo *p; - } p; - - p.c = CMSG_DATA(cmptr); - if_index = p.p->ipi_ifindex; - } + getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, &len) != -1) + { + msg.msg_controllen = len; + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) + { + union { + unsigned char *c; + struct in_pktinfo *p; + } p; + + p.c = CMSG_DATA(cmptr); + if_index = p.p->ipi_ifindex; + } + } } #ifdef HAVE_IPV6 else @@ -849,9 +858,10 @@ int tcp_interface(int fd, int af) #endif if (set_ipv6pktinfo(fd) && - getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, (socklen_t *)&msg.msg_controllen) != -1) + getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, &len) != -1) { - for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + msg.msg_controllen = len; + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { union { @@ -1028,6 +1038,15 @@ void warn_bound_listeners(void) my_syslog(LOG_WARNING, _("LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification attacks via these interface(s)")); } +void warn_wild_labels(void) +{ + struct irec *iface; + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->found && iface->name && iface->label) + my_syslog(LOG_WARNING, _("warning: using interface %s instead"), iface->name); +} + void warn_int_names(void) { struct interface_name *intname; @@ -1076,23 +1095,30 @@ void join_multicast(int dienow) if ((daemon->doing_dhcp6 || daemon->relay6) && setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) - err = 1; + err = errno; inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr); if (daemon->doing_dhcp6 && setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) - err = 1; + err = errno; inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr); if (daemon->doing_ra && setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) - err = 1; + err = errno; if (err) { char *s = _("interface %s failed to join DHCPv6 multicast group: %s"); + errno = err; + +#ifdef HAVE_LINUX_NETWORK + if (errno == ENOMEM) + my_syslog(LOG_ERR, _("try increasing /proc/sys/net/core/optmem_max")); +#endif + if (dienow) die(s, iface->name, EC_BADNET); else @@ -1112,7 +1138,7 @@ int random_sock(int family) if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) { union mysockaddr addr; - unsigned int ports_avail = 65536u - (unsigned short)daemon->min_port; + unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; int tries = ports_avail < 30 ? 3 * ports_avail : 100; memset(&addr, 0, sizeof(addr)); @@ -1123,10 +1149,7 @@ int random_sock(int family) if (fix_fd(fd)) while(tries--) { - unsigned short port = rand16(); - - if (daemon->min_port != 0) - port = htons(daemon->min_port + (port % ((unsigned short)ports_avail))); + unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); if (family == AF_INET) { @@ -1161,7 +1184,7 @@ int random_sock(int family) } -int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp) +int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) { union mysockaddr addr_copy = *addr; @@ -1178,7 +1201,25 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp) if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1) return 0; - + + if (!is_tcp && ifindex > 0) + { +#if defined(IP_UNICAST_IF) + if (addr_copy.sa.sa_family == AF_INET) + { + uint32_t ifindex_opt = htonl(ifindex); + return setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0; + } +#endif +#if defined(HAVE_IPV6) && defined (IPV6_UNICAST_IF) + if (addr_copy.sa.sa_family == AF_INET6) + { + uint32_t ifindex_opt = htonl(ifindex); + return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0; + } +#endif + } + #if defined(SO_BINDTODEVICE) if (intname[0] != 0 && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE) == -1) @@ -1191,6 +1232,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp) static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) { struct serverfd *sfd; + unsigned int ifindex = 0; int errsave; /* when using random ports, servers which would otherwise use @@ -1211,11 +1253,15 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) return NULL; #endif } + + if (intname && strlen(intname) != 0) + ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ /* may have a suitable one already */ for (sfd = daemon->sfds; sfd; sfd = sfd->next ) if (sockaddr_isequal(&sfd->source_addr, addr) && - strcmp(intname, sfd->interface) == 0) + strcmp(intname, sfd->interface) == 0 && + ifindex == sfd->ifindex) return sfd; /* need to make a new one. */ @@ -1229,7 +1275,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) return NULL; } - if (!local_bind(sfd->fd, addr, intname, 0) || !fix_fd(sfd->fd)) + if (!local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd)) { errsave = errno; /* save error from bind. */ close(sfd->fd); @@ -1237,11 +1283,13 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) errno = errsave; return NULL; } - + strcpy(sfd->interface, intname); sfd->source_addr = *addr; sfd->next = daemon->sfds; + sfd->ifindex = ifindex; daemon->sfds = sfd; + return sfd; } @@ -1396,7 +1444,6 @@ void add_update_server(int flags, serv->domain = domain_str; serv->next = next; serv->queries = serv->failed_queries = 0; - serv->edns_pktsz = daemon->edns_pktsz; #ifdef HAVE_LOOP serv->uid = rand32(); #endif @@ -1417,18 +1464,54 @@ void check_servers(void) { struct irec *iface; struct server *serv; - int port = 0; + struct serverfd *sfd, *tmp, **up; + int port = 0, count; + int locals = 0; /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) enumerate_interfaces(0); - for (serv = daemon->servers; serv; serv = serv->next) + for (sfd = daemon->sfds; sfd; sfd = sfd->next) + sfd->used = 0; + + for (count = 0, serv = daemon->servers; serv; serv = serv->next) { - if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) + if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) { - port = prettyprint_addr(&serv->addr, daemon->namebuff); + /* Init edns_pktsz for newly created server records. */ + if (serv->edns_pktsz == 0) + serv->edns_pktsz = daemon->edns_pktsz; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + if (!(serv->flags & SERV_FOR_NODOTS)) + serv->flags |= SERV_DO_DNSSEC; + + /* Disable DNSSEC validation when using server=/domain/.... servers + unless there's a configured trust anchor. */ + if (serv->flags & SERV_HAS_DOMAIN) + { + struct ds_config *ds; + char *domain = serv->domain; + + /* .example.com is valid */ + while (*domain == '.') + domain++; + + for (ds = daemon->ds; ds; ds = ds->next) + if (ds->name[0] != 0 && hostname_isequal(domain, ds->name)) + break; + + if (!ds) + serv->flags &= ~SERV_DO_DNSSEC; + } + } +#endif + port = prettyprint_addr(&serv->addr, daemon->namebuff); + /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ if (serv->addr.sa.sa_family == AF_INET && serv->addr.in.sin_addr.s_addr == 0) @@ -1458,13 +1541,23 @@ void check_servers(void) serv->flags |= SERV_MARK; continue; } + + if (serv->sfd) + serv->sfd->used = 1; } if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS)) { + if (++count > SERVERS_LOGGED) + continue; + if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) { - char *s1, *s2; + char *s1, *s2, *s3 = ""; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC)) + s3 = _("(no DNSSEC)"); +#endif if (!(serv->flags & SERV_HAS_DOMAIN)) s1 = _("unqualified"), s2 = _("names"); else if (strlen(serv->domain) == 0) @@ -1473,11 +1566,15 @@ void check_servers(void) s1 = _("domain"), s2 = serv->domain; if (serv->flags & SERV_NO_ADDR) - my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2); + { + count--; + if (++locals <= LOCALS_LOGGED) + my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2); + } else if (serv->flags & SERV_USE_RESOLV) my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2); else - my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2); + my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3); } #ifdef HAVE_LOOP else if (serv->flags & SERV_LOOP) @@ -1489,7 +1586,26 @@ void check_servers(void) my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); } } + + if (locals > LOCALS_LOGGED) + my_syslog(LOG_INFO, _("using %d more local addresses"), locals - LOCALS_LOGGED); + if (count - 1 > SERVERS_LOGGED) + my_syslog(LOG_INFO, _("using %d more nameservers"), count - SERVERS_LOGGED - 1); + /* Remove unused sfds */ + for (sfd = daemon->sfds, up = &daemon->sfds; sfd; sfd = tmp) + { + tmp = sfd->next; + if (!sfd->used) + { + *up = sfd->next; + close(sfd->fd); + free(sfd); + } + else + up = &sfd->next; + } + cleanup_servers(); } diff --git a/src/option.c b/src/option.c index ed204fb..d358d99 100644 --- a/src/option.c +++ b/src/option.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 @@ -154,7 +154,13 @@ struct myoption { #define LOPT_HOST_INOTIFY 342 #define LOPT_DNSSEC_STAMP 343 #define LOPT_TFTP_NO_FAIL 344 - +#define LOPT_MAXPORT 345 +#define LOPT_CPE_ID 346 +#define LOPT_SCRIPT_ARP 347 +#define LOPT_DHCPTTL 348 +#define LOPT_TFTP_MTU 349 +#define LOPT_REPLY_DELAY 350 + #ifdef HAVE_GETOPT_LONG static const struct option opts[] = #else @@ -237,9 +243,10 @@ static const struct myoption opts[] = { "enable-tftp", 2, 0, LOPT_TFTP }, { "tftp-secure", 0, 0, LOPT_SECURE }, { "tftp-no-fail", 0, 0, LOPT_TFTP_NO_FAIL }, - { "tftp-unique-root", 0, 0, LOPT_APREF }, + { "tftp-unique-root", 2, 0, LOPT_APREF }, { "tftp-root", 1, 0, LOPT_PREFIX }, { "tftp-max", 1, 0, LOPT_TFTP_MAX }, + { "tftp-mtu", 1, 0, LOPT_TFTP_MTU }, { "tftp-lowercase", 0, 0, LOPT_TFTP_LC }, { "ptr-record", 1, 0, LOPT_PTR }, { "naptr-record", 1, 0, LOPT_NAPTR }, @@ -270,6 +277,7 @@ static const struct myoption opts[] = { "dhcp-alternate-port", 2, 0, LOPT_ALTPORT }, { "dhcp-scriptuser", 1, 0, LOPT_SCRIPTUSR }, { "min-port", 1, 0, LOPT_MINPORT }, + { "max-port", 1, 0, LOPT_MAXPORT }, { "dhcp-fqdn", 0, 0, LOPT_DHCP_FQDN }, { "cname", 1, 0, LOPT_CNAME }, { "pxe-prompt", 1, 0, LOPT_PXE_PROMT }, @@ -279,8 +287,9 @@ static const struct myoption opts[] = { "dhcp-proxy", 2, 0, LOPT_PROXY }, { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES }, { "rebind-localhost-ok", 0, 0, LOPT_LOC_REBND }, - { "add-mac", 0, 0, LOPT_ADD_MAC }, + { "add-mac", 2, 0, LOPT_ADD_MAC }, { "add-subnet", 2, 0, LOPT_ADD_SBNET }, + { "add-cpe-id", 1, 0 , LOPT_CPE_ID }, { "proxy-dnssec", 0, 0, LOPT_DNSSEC }, { "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR }, { "conntrack", 0, 0, LOPT_CONNTRACK }, @@ -313,6 +322,9 @@ static const struct myoption opts[] = { "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 }, { "quiet-ra", 0, 0, LOPT_QUIET_RA }, { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT }, + { "script-arp", 0, 0, LOPT_SCRIPT_ARP }, + { "dhcp-ttl", 1, 0 , LOPT_DHCPTTL }, + { "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY }, { NULL, 0, 0, 0 } }; @@ -392,7 +404,7 @@ static struct { { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL }, { 'V', ARG_DUP, "<ipaddr>,<ipaddr>,<netmask>", gettext_noop("Translate IPv4 addresses from upstream servers."), NULL }, { 'W', ARG_DUP, "<name>,<target>,...", gettext_noop("Specify a SRV record."), NULL }, - { 'w', 0, NULL, gettext_noop("Display this message. Use --help dhcp for known DHCP options."), NULL }, + { 'w', 0, NULL, gettext_noop("Display this message. Use --help dhcp or --help dhcp6 for known DHCP options."), NULL }, { 'x', ARG_ONE, "<path>", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE }, { 'X', ARG_ONE, "<integer>", gettext_noop("Specify maximum number of DHCP leases (defaults to %s)."), "&" }, { 'y', OPT_LOCALISE, NULL, gettext_noop("Answer DNS queries based on the interface a query was sent to."), NULL }, @@ -410,8 +422,9 @@ static struct { { '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL }, { LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL }, { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change scripts as this user."), NULL }, + { LOPT_SCRIPT_ARP, OPT_SCRIPT_ARP, NULL, gettext_noop("Call dhcp-script with changes to local ARP table."), NULL }, { '7', ARG_DUP, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL }, - { '8', ARG_ONE, "<facilty>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL }, + { '8', ARG_ONE, "<facility>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL }, { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL }, { '0', ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, { LOPT_RELOAD, OPT_RELOAD, NULL, gettext_noop("Clear DNS cache when reloading %s."), RESOLVFILE }, @@ -419,10 +432,11 @@ static struct { { LOPT_OVERRIDE, OPT_NO_OVERRIDE, NULL, gettext_noop("Do NOT reuse filename and server fields for extra DHCP options."), NULL }, { LOPT_TFTP, ARG_DUP, "[=<intr>[,<intr>]]", gettext_noop("Enable integrated read-only TFTP server."), NULL }, { LOPT_PREFIX, ARG_DUP, "<dir>[,<iface>]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL }, - { LOPT_APREF, OPT_TFTP_APREF, NULL, gettext_noop("Add client IP address to tftp-root."), NULL }, + { LOPT_APREF, ARG_DUP, "[=ip|mac]", gettext_noop("Add client IP or hardware address to tftp-root."), NULL }, { LOPT_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL }, { LOPT_TFTP_NO_FAIL, OPT_TFTP_NO_FAIL, NULL, gettext_noop("Do not terminate the service if TFTP directories are inaccessible."), NULL }, - { LOPT_TFTP_MAX, ARG_ONE, "<integer>", gettext_noop("Maximum number of conncurrent TFTP transfers (defaults to %s)."), "#" }, + { LOPT_TFTP_MAX, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent TFTP transfers (defaults to %s)."), "#" }, + { LOPT_TFTP_MTU, ARG_ONE, "<integer>", gettext_noop("Maximum MTU to use for TFTP transfers."), NULL }, { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL }, { LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL }, { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL }, @@ -436,32 +450,34 @@ static struct { { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL }, { LOPT_NAPTR, ARG_DUP, "<name>,<naptr>", gettext_noop("Specify NAPTR DNS record."), NULL }, { LOPT_MINPORT, ARG_ONE, "<port>", gettext_noop("Specify lowest port available for DNS query transmission."), NULL }, + { LOPT_MAXPORT, ARG_ONE, "<port>", gettext_noop("Specify highest port available for DNS query transmission."), NULL }, { LOPT_DHCP_FQDN, OPT_DHCP_FQDN, NULL, gettext_noop("Use only fully qualified domain names for DHCP clients."), NULL }, { LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL}, { LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL }, - { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<interface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL}, - { LOPT_CNAME, ARG_DUP, "<alias>,<target>", gettext_noop("Specify alias name for LOCAL DNS name."), NULL }, + { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<iface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL}, + { LOPT_CNAME, ARG_DUP, "<alias>,<target>[,<ttl>]", gettext_noop("Specify alias name for LOCAL DNS name."), NULL }, { LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL }, { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL }, { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL }, - { LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL }, - { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL }, + { LOPT_ADD_MAC, ARG_DUP, "[=base64|text]", gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL }, + { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add specified IP subnet to forwarded DNS queries."), NULL }, + { LOPT_CPE_ID, ARG_ONE, "<text>", gettext_noop("Add client identification to forwarded DNS queries."), NULL }, { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL }, { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL }, { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL }, { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL }, { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, - { LOPT_HOST_REC, ARG_DUP, "<name>,<address>", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, { LOPT_AUTHSERV, ARG_ONE, "<NS>,<interface>", gettext_noop("Export local names to global DNS"), NULL }, { LOPT_AUTHZONE, ARG_DUP, "<domain>,[<subnet>...]", gettext_noop("Domain to export to global DNS"), NULL }, { LOPT_AUTHTTL, ARG_ONE, "<integer>", gettext_noop("Set TTL for authoritative replies"), NULL }, - { LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritive zone information"), NULL }, + { LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritative zone information"), NULL }, { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, - { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<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 }, { 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 }, @@ -472,17 +488,19 @@ static struct { #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, #endif - { LOPT_RA_PARAM, ARG_DUP, "<interface>,[high,|low,]<interval>[,<lifetime>]", gettext_noop("Set priority, resend-interval and router-lifetime"), NULL }, + { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL }, { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL }, - { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks"), NULL }, - { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops"), NULL }, + { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL }, + { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL }, { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, + { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, + { LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL }, { 0, 0, NULL, NULL, NULL } }; -/* We hide metacharaters in quoted strings by mapping them into the ASCII control +/* We hide metacharacters in quoted strings by mapping them into the ASCII control character space. Note that the \0, \t \b \r \033 and \n characters are carefully placed in the following sequence so that they map to themselves: it is therefore possible to call unhide_metas repeatedly on string without breaking things. @@ -642,7 +660,8 @@ static int atoi_check8(char *a, int *res) return 1; } #endif - + +#ifndef NO_ID static void add_txt(char *name, char *txt, int stat) { struct txt_record *r = opt_malloc(sizeof(struct txt_record)); @@ -655,13 +674,14 @@ static void add_txt(char *name, char *txt, int stat) *(r->txt) = len; memcpy((r->txt)+1, txt, len); } - + r->stat = stat; r->name = opt_string_alloc(name); r->next = daemon->txt; daemon->txt = r; r->class = C_CHAOS; } +#endif static void do_usage(void) { @@ -706,7 +726,7 @@ static void do_usage(void) sprintf(buff, " "); sprintf(buff+4, "--%s%s%s", opts[j].name, eq, desc); - printf("%-40.40s", buff); + printf("%-55.55s", buff); if (usage[i].arg) { @@ -722,10 +742,25 @@ static void do_usage(void) #define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0) +static char *parse_mysockaddr(char *arg, union mysockaddr *addr) +{ + if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) + addr->sa.sa_family = AF_INET; +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) + addr->sa.sa_family = AF_INET6; +#endif + else + return _("bad address"); + + return NULL; +} + char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags) { int source_port = 0, serv_port = NAMESERVER_PORT; char *portno, *source; + char *interface_opt = NULL; #ifdef HAVE_IPV6 int scope_index = 0; char *scope_id; @@ -751,6 +786,19 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a scope_id = split_chr(arg, '%'); #endif + if (source) { + interface_opt = split_chr(source, '@'); + + if (interface_opt) + { +#if defined(SO_BINDTODEVICE) + strncpy(interface, interface_opt, IF_NAMESIZE - 1); +#else + return _("interface binding not supported"); +#endif + } + } + if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) { addr->in.sin_port = htons(serv_port); @@ -769,6 +817,9 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0)) { #if defined(SO_BINDTODEVICE) + if (interface_opt) + return _("interface can only be specified once"); + source_addr->in.sin_addr.s_addr = INADDR_ANY; strncpy(interface, source, IF_NAMESIZE - 1); #else @@ -801,7 +852,10 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0) { #if defined(SO_BINDTODEVICE) - source_addr->in6.sin6_addr = in6addr_any; + if (interface_opt) + return _("interface can only be specified once"); + + source_addr->in6.sin6_addr = in6addr_any; strncpy(interface, source, IF_NAMESIZE - 1); #else return _("interface binding not supported"); @@ -819,19 +873,31 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a static struct server *add_rev4(struct in_addr addr, int msize) { struct server *serv = opt_malloc(sizeof(struct server)); - in_addr_t a = ntohl(addr.s_addr) >> 8; + in_addr_t a = ntohl(addr.s_addr); char *p; memset(serv, 0, sizeof(struct server)); - p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */ - - if (msize == 24) - p += sprintf(p, "%d.", a & 0xff); - a = a >> 8; - if (msize != 8) - p += sprintf(p, "%d.", a & 0xff); - a = a >> 8; - p += sprintf(p, "%d.in-addr.arpa", a & 0xff); + p = serv->domain = opt_malloc(29); /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ + + switch (msize) + { + case 32: + p += sprintf(p, "%u.", a & 0xff); + /* fall through */ + case 24: + p += sprintf(p, "%d.", (a >> 8) & 0xff); + /* fall through */ + case 16: + p += sprintf(p, "%d.", (a >> 16) & 0xff); + /* fall through */ + case 8: + p += sprintf(p, "%d.", (a >> 24) & 0xff); + break; + default: + return NULL; + } + + p += sprintf(p, "in-addr.arpa"); serv->flags = SERV_HAS_DOMAIN; serv->next = daemon->servers; @@ -1097,7 +1163,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) case 'd': case 'D': fac *= 24; - /* fall though */ + /* fall through */ case 'h': case 'H': fac *= 60; @@ -1170,7 +1236,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) cp = comma; comma = split(cp); slash = split_chr(cp, '/'); - inet_pton(AF_INET, cp, &in); + if (!inet_pton(AF_INET, cp, &in)) + ret_err(_("bad IPv4 address")); if (!slash) { memcpy(op, &in, INADDRSZ); @@ -1501,10 +1568,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma li = opt_malloc(sizeof(struct list)); if (*arg == '*') { - li->next = match_suffix; - match_suffix = li; - /* Have to copy: buffer is overwritten */ - li->suffix = opt_string_alloc(arg+1); + /* "*" with no suffix is a no-op */ + if (arg[1] == 0) + free(li); + else + { + li->next = match_suffix; + match_suffix = li; + /* Have to copy: buffer is overwritten */ + li->suffix = opt_string_alloc(arg+1); + } } else { @@ -1585,7 +1658,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma li = match_suffix->next; free(match_suffix->suffix); free(match_suffix); - } + } break; } @@ -1593,10 +1666,46 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma set_option_bool(OPT_CLIENT_SUBNET); if (arg) { + char *err, *end; comma = split(arg); - if (!atoi_check(arg, &daemon->addr4_netmask) || - (comma && !atoi_check(comma, &daemon->addr6_netmask))) - ret_err(gen_err); + + struct mysubnet* new = opt_malloc(sizeof(struct mysubnet)); + if ((end = split_chr(arg, '/'))) + { + /* has subnet+len */ + err = parse_mysockaddr(arg, &new->addr); + if (err) + ret_err(err); + if (!atoi_check(end, &new->mask)) + ret_err(gen_err); + new->addr_used = 1; + } + else if (!atoi_check(arg, &new->mask)) + ret_err(gen_err); + + daemon->add_subnet4 = new; + + if (comma) + { + new = opt_malloc(sizeof(struct mysubnet)); + if ((end = split_chr(comma, '/'))) + { + /* has subnet+len */ + err = parse_mysockaddr(comma, &new->addr); + if (err) + ret_err(err); + if (!atoi_check(end, &new->mask)) + ret_err(gen_err); + new->addr_used = 1; + } + else + { + if (!atoi_check(comma, &new->mask)) + ret_err(gen_err); + } + + daemon->add_subnet6 = new; + } } break; @@ -1834,6 +1943,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new = opt_malloc(sizeof(struct auth_zone)); new->domain = opt_string_alloc(arg); new->subnet = NULL; + new->exclude = NULL; new->interface_names = NULL; new->next = daemon->auth_zones; daemon->auth_zones = new; @@ -1841,6 +1951,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma while ((arg = comma)) { int prefixlen = 0; + int is_exclude = 0; char *prefix; struct addrlist *subnet = NULL; struct all_addr addr; @@ -1851,6 +1962,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (prefix && !atoi_check(prefix, &prefixlen)) ret_err(gen_err); + if (strstr(arg, "exclude:") == arg) + { + is_exclude = 1; + arg = arg+8; + } + if (inet_pton(AF_INET, arg, &addr.addr.addr4)) { subnet = opt_malloc(sizeof(struct addrlist)); @@ -1888,8 +2005,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (subnet) { subnet->addr = addr; - subnet->next = new->subnet; - new->subnet = subnet; + + if (is_exclude) + { + subnet->next = new->exclude; + new->exclude = subnet; + } + else + { + subnet->next = new->subnet; + new->subnet = subnet; + } } } break; @@ -1919,11 +2045,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma comma = split(arg); daemon->soa_retry = (u32)atoi(arg); if (comma) - { - arg = comma; - comma = split(arg); - daemon->soa_expiry = (u32)atoi(arg); - } + daemon->soa_expiry = (u32)atoi(comma); } } } @@ -1949,7 +2071,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma char *netpart; new->prefix = NULL; - + new->indexed = 0; + unhide_metas(comma); if ((netpart = split_chr(comma, '/'))) { @@ -1980,6 +2103,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* generate the equivalent of local=/xxx.yyy.zzz.in-addr.arpa/ */ struct server *serv = add_rev4(new->start, msize); + if (!serv) + ret_err(_("bad prefix")); + serv->flags |= SERV_NO_ADDR; /* local=/<domain>/ */ @@ -2083,8 +2209,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } else { + char *star; new->next = daemon->synth_domains; daemon->synth_domains = new; + if ((star = strrchr(new->prefix, '*')) && *(star+1) == 0) + { + *star = 0; + new->indexed = 1; + } } } else if (option == 's') @@ -2095,6 +2227,26 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } break; + case LOPT_CPE_ID: /* --add-dns-client */ + if (arg) + daemon->dns_client_id = opt_string_alloc(arg); + break; + + case LOPT_ADD_MAC: /* --add-mac */ + if (!arg) + set_option_bool(OPT_ADD_MAC); + else + { + unhide_metas(arg); + if (strcmp(arg, "base64") == 0) + set_option_bool(OPT_MAC_B64); + else if (strcmp(arg, "text") == 0) + set_option_bool(OPT_MAC_HEX); + else + ret_err(gen_err); + } + break; + case 'u': /* --user */ daemon->username = opt_string_alloc(arg); break; @@ -2331,7 +2483,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(gen_err); if (inet_pton(AF_INET, arg, &addr4)) - serv = add_rev4(addr4, size); + { + serv = add_rev4(addr4, size); + if (!serv) + ret_err(_("bad prefix")); + } #ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, arg, &addr6)) serv = add_rev6(&addr6, size); @@ -2448,6 +2604,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(gen_err); break; + case LOPT_MAXPORT: /* --max-port */ + if (!atoi_check16(arg, &daemon->max_port)) + ret_err(gen_err); + break; + case '0': /* --dns-forward-max */ if (!atoi_check(arg, &daemon->ftabsize)) ret_err(gen_err); @@ -2491,6 +2652,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_MINCTTL: /* --min-cache-ttl */ case LOPT_MAXCTTL: /* --max-cache-ttl */ case LOPT_AUTHTTL: /* --auth-ttl */ + case LOPT_DHCPTTL: /* --dhcp-ttl */ { int ttl; if (!atoi_check(arg, &ttl)) @@ -2509,6 +2671,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->max_cache_ttl = (unsigned long)ttl; else if (option == LOPT_AUTHTTL) daemon->auth_ttl = (unsigned long)ttl; + else if (option == LOPT_DHCPTTL) + { + daemon->dhcp_ttl = (unsigned long)ttl; + daemon->use_dhcp_ttl = 1; + } else daemon->local_ttl = (unsigned long)ttl; break; @@ -2527,6 +2694,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(gen_err); break; + case LOPT_TFTP_MTU: /* --tftp-mtu */ + if (!atoi_check(arg, &daemon->tftp_mtu)) + ret_err(gen_err); + break; + case LOPT_PREFIX: /* --tftp-prefix */ comma = split(arg); if (comma) @@ -2555,19 +2727,37 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } break; + + case LOPT_APREF: /* --tftp-unique-root */ + if (!arg || strcasecmp(arg, "ip") == 0) + set_option_bool(OPT_TFTP_APREF_IP); + else if (strcasecmp(arg, "mac") == 0) + set_option_bool(OPT_TFTP_APREF_MAC); + else + ret_err(gen_err); + break; #endif case LOPT_BRIDGE: /* --bridge-interface */ { - struct dhcp_bridge *new = opt_malloc(sizeof(struct dhcp_bridge)); + struct dhcp_bridge *new; + if (!(comma = split(arg)) || strlen(arg) > IF_NAMESIZE - 1 ) ret_err(_("bad bridge-interface")); - - strcpy(new->iface, arg); - new->alias = NULL; - new->next = daemon->bridges; - daemon->bridges = new; + for (new = daemon->bridges; new; new = new->next) + if (strcmp(new->iface, arg) == 0) + break; + + if (!new) + { + new = opt_malloc(sizeof(struct dhcp_bridge)); + strcpy(new->iface, arg); + new->alias = NULL; + new->next = daemon->bridges; + daemon->bridges = new; + } + do { arg = comma; comma = split(arg); @@ -2669,13 +2859,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma leasepos = 3; if (!is_same_net(new->start, new->end, new->netmask)) ret_err(_("inconsistent DHCP range")); - } + - if (k >= 4 && strchr(a[3], '.') && - (inet_pton(AF_INET, a[3], &new->broadcast) > 0)) - { - new->flags |= CONTEXT_BRDCAST; - leasepos = 4; + if (k >= 4 && strchr(a[3], '.') && + (inet_pton(AF_INET, a[3], &new->broadcast) > 0)) + { + new->flags |= CONTEXT_BRDCAST; + leasepos = 4; + } } } #ifdef HAVE_DHCP6 @@ -2765,6 +2956,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (leasepos < k) { + if (leasepos != k-1) + ret_err(_("bad dhcp-range")); + if (strcmp(a[leasepos], "infinite") == 0) new->lease_time = 0xffffffff; else if (strcmp(a[leasepos], "deprecated") == 0) @@ -2783,7 +2977,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case 'd': case 'D': fac *= 24; - /* fall though */ + /* fall through */ case 'h': case 'H': fac *= 60; @@ -2859,7 +3053,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } if (len == -1) - ret_err(_("bad hex constant")); else if ((new->clid = opt_malloc(len))) { @@ -3143,11 +3336,43 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; } + case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */ + { + struct dhcp_netid *id = NULL; + while (is_tag_prefix(arg)) + { + struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid)); + newid->next = id; + id = newid; + comma = split(arg); + newid->net = opt_string_alloc(arg+4); + arg = comma; + }; + + if (!arg) + ret_err(gen_err); + else + { + struct delay_config *new; + int delay; + if (!atoi_check(arg, &delay)) + ret_err(gen_err); + + new = opt_malloc(sizeof(struct delay_config)); + new->delay = delay; + new->netid = id; + new->next = daemon->delay_conf; + daemon->delay_conf = new; + } + + break; + } + case LOPT_PXE_PROMT: /* --pxe-prompt */ { struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt)); int timeout; - + new->netid = NULL; new->opt = 10; /* PXE_MENU_PROMPT */ @@ -3191,7 +3416,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { struct pxe_service *new = opt_malloc(sizeof(struct pxe_service)); char *CSA[] = { "x86PC", "PC98", "IA64_EFI", "Alpha", "Arc_x86", "Intel_Lean_Client", - "IA32_EFI", "BC_EFI", "Xscale_EFI", "x86-64_EFI", NULL }; + "IA32_EFI", "x86-64_EFI", "Xscale_EFI", "BC_EFI", + "ARM32_EFI", "ARM64_EFI", NULL }; static int boottype = 32768; new->netid = NULL; @@ -3496,7 +3722,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma struct ra_interface *new = opt_malloc(sizeof(struct ra_interface)); new->lifetime = -1; new->prio = 0; + new->mtu = 0; + new->mtu_name = NULL; new->name = opt_string_alloc(arg); + if (strcasestr(comma, "mtu:") == comma) + { + arg = comma + 4; + if (!(comma = split(comma))) + goto err; + if (!strcasecmp(arg, "off")) + new->mtu = -1; + else if (!atoi_check(arg, &new->mtu)) + new->mtu_name = opt_string_alloc(arg); + else if (new->mtu < 1280) + goto err; + } if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma) { if (*comma == 'l' || *comma == 'L') @@ -3508,6 +3748,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma arg = split(comma); if (!atoi_check(comma, &new->interval) || (arg && !atoi_check(arg, &new->lifetime))) +err: ret_err(_("bad RA-params")); new->next = daemon->ra_interfaces; @@ -3552,8 +3793,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma (!(inet_pton(AF_INET, a[1], &new->out) > 0))) option = '?'; - if (k == 3) - inet_pton(AF_INET, a[2], &new->mask); + if (k == 3 && !inet_pton(AF_INET, a[2], &new->mask)) + option = '?'; if (dash && (!(inet_pton(AF_INET, dash, &new->end) > 0) || @@ -3603,27 +3844,42 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_CNAME: /* --cname */ { struct cname *new; - char *alias; - char *target; + char *alias, *target, *last, *pen; + int ttl = -1; - if (!(comma = split(arg))) - ret_err(gen_err); - - alias = canonicalise_opt(arg); - target = canonicalise_opt(comma); - - if (!alias || !target) + for (last = pen = NULL, comma = arg; comma; comma = split(comma)) + { + pen = last; + last = comma; + } + + if (!pen) ret_err(_("bad CNAME")); - else + + if (pen != arg && atoi_check(last, &ttl)) + last = pen; + + target = canonicalise_opt(last); + + while (arg != last) { + int arglen = strlen(arg); + alias = canonicalise_opt(arg); + + if (!alias || !target) + ret_err(_("bad CNAME")); + for (new = daemon->cnames; new; new = new->next) - if (hostname_isequal(new->alias, arg)) + if (hostname_isequal(new->alias, alias)) ret_err(_("duplicate CNAME")); new = opt_malloc(sizeof(struct cname)); new->next = daemon->cnames; daemon->cnames = new; new->alias = alias; new->target = target; + new->ttl = ttl; + + for (arg += arglen+1; *arg && isspace(*arg); arg++); } break; @@ -3689,7 +3945,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_RR: /* dns-rr */ { struct txt_record *new; - size_t len = len; + size_t len = 0; char *data; int val; @@ -3797,13 +4053,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (!atoi_check16(arg, &priority)) ret_err(_("invalid priority")); - if (comma) - { - arg = comma; - comma = split(arg); - if (!atoi_check16(arg, &weight)) - ret_err(_("invalid weight")); - } + if (comma && !atoi_check16(comma, &weight)) + ret_err(_("invalid weight")); } } } @@ -3824,14 +4075,22 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { struct host_record *new = opt_malloc(sizeof(struct host_record)); memset(new, 0, sizeof(struct host_record)); - + new->ttl = -1; + if (!arg || !(comma = split(arg))) ret_err(_("Bad host-record")); while (arg) { struct all_addr addr; - if (inet_pton(AF_INET, arg, &addr)) + char *dig; + + for (dig = arg; *dig != 0; dig++) + if (*dig < '0' || *dig > '9') + break; + if (*dig == 0) + new->ttl = atoi(arg); + else if (inet_pton(AF_INET, arg, &addr)) new->addr = addr.addr.addr4; #ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, arg, &addr)) @@ -3948,7 +4207,7 @@ static void read_file(char *file, FILE *f, int hard_opt) { int white, i; volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt; - char *errmess, *p, *arg = NULL, *start; + char *errmess, *p, *arg, *start; size_t len; /* Memory allocation failure longjmps here if mem_recover == 1 */ @@ -3959,6 +4218,7 @@ static void read_file(char *file, FILE *f, int hard_opt) mem_recover = 1; } + arg = NULL; lineno++; errmess = NULL; @@ -4075,7 +4335,7 @@ static void read_file(char *file, FILE *f, int hard_opt) fclose(f); } -#ifdef HAVE_DHCP +#if defined(HAVE_DHCP) && defined(HAVE_INOTIFY) int option_read_dynfile(char *file, int flags) { my_syslog(MS_DHCP | LOG_INFO, _("read %s"), file); @@ -4276,86 +4536,99 @@ void read_servers_file(void) #ifdef HAVE_DHCP -void reread_dhcp(void) +static void clear_dynamic_conf(void) { - struct hostsfile *hf; - - if (daemon->dhcp_hosts_file) - { - struct dhcp_config *configs, *cp, **up; + struct dhcp_config *configs, *cp, **up; - /* remove existing... */ - for (up = &daemon->dhcp_conf, configs = daemon->dhcp_conf; configs; configs = cp) + /* remove existing... */ + for (up = &daemon->dhcp_conf, configs = daemon->dhcp_conf; configs; configs = cp) + { + cp = configs->next; + + if (configs->flags & CONFIG_BANK) { - cp = configs->next; + struct hwaddr_config *mac, *tmp; + struct dhcp_netid_list *list, *tmplist; - if (configs->flags & CONFIG_BANK) + for (mac = configs->hwaddr; mac; mac = tmp) { - struct hwaddr_config *mac, *tmp; - struct dhcp_netid_list *list, *tmplist; - - for (mac = configs->hwaddr; mac; mac = tmp) - { - tmp = mac->next; - free(mac); - } + tmp = mac->next; + free(mac); + } + + if (configs->flags & CONFIG_CLID) + free(configs->clid); + + for (list = configs->netid; list; list = tmplist) + { + free(list->list); + tmplist = list->next; + free(list); + } + + if (configs->flags & CONFIG_NAME) + free(configs->hostname); + + *up = configs->next; + free(configs); + } + else + up = &configs->next; + } +} - if (configs->flags & CONFIG_CLID) - free(configs->clid); +static void clear_dynamic_opt(void) +{ + struct dhcp_opt *opts, *cp, **up; + struct dhcp_netid *id, *next; - for (list = configs->netid; list; list = tmplist) - { - free(list->list); - tmplist = list->next; - free(list); - } - - if (configs->flags & CONFIG_NAME) - free(configs->hostname); - - *up = configs->next; - free(configs); + for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp) + { + cp = opts->next; + + if (opts->flags & DHOPT_BANK) + { + if ((opts->flags & DHOPT_VENDOR)) + free(opts->u.vendor_class); + free(opts->val); + for (id = opts->netid; id; id = next) + { + next = id->next; + free(id->net); + free(id); } - else - up = &configs->next; + *up = opts->next; + free(opts); } - + else + up = &opts->next; + } +} + +void reread_dhcp(void) +{ + struct hostsfile *hf; + + /* Do these even if there is no daemon->dhcp_hosts_file or + daemon->dhcp_opts_file since entries may have been created by the + inotify dynamic file reading system. */ + + clear_dynamic_conf(); + clear_dynamic_opt(); + + if (daemon->dhcp_hosts_file) + { daemon->dhcp_hosts_file = expand_filelist(daemon->dhcp_hosts_file); for (hf = daemon->dhcp_hosts_file; hf; hf = hf->next) - if (!(hf->flags & AH_INACTIVE)) - { - if (one_file(hf->fname, LOPT_BANK)) - my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); - } + if (!(hf->flags & AH_INACTIVE)) + { + if (one_file(hf->fname, LOPT_BANK)) + my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); + } } if (daemon->dhcp_opts_file) { - struct dhcp_opt *opts, *cp, **up; - struct dhcp_netid *id, *next; - - for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp) - { - cp = opts->next; - - if (opts->flags & DHOPT_BANK) - { - if ((opts->flags & DHOPT_VENDOR)) - free(opts->u.vendor_class); - free(opts->val); - for (id = opts->netid; id; id = next) - { - next = id->next; - free(id->net); - free(id); - } - *up = opts->next; - free(opts); - } - else - up = &opts->next; - } - daemon->dhcp_opts_file = expand_filelist(daemon->dhcp_opts_file); for (hf = daemon->dhcp_opts_file; hf; hf = hf->next) if (!(hf->flags & AH_INACTIVE)) @@ -4364,11 +4637,18 @@ void reread_dhcp(void) my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); } } + +# ifdef HAVE_INOTIFY + /* Setup notify and read pre-existing files. */ + set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0); +# endif } #endif - + void read_opts(int argc, char **argv, char *compile_opts) { + size_t argbuf_size = MAXDNAME; + char *argbuf = opt_malloc(argbuf_size); char *buff = opt_malloc(MAXDNAME); int option, conffile_opt = '7', testmode = 0; char *arg, *conffile = CONFFILE; @@ -4398,7 +4678,10 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->soa_refresh = SOA_REFRESH; daemon->soa_retry = SOA_RETRY; daemon->soa_expiry = SOA_EXPIRY; + daemon->max_port = MAX_PORT; + daemon->min_port = MIN_PORT; +#ifndef NO_ID add_txt("version.bind", "dnsmasq-" VERSION, 0 ); add_txt("authors.bind", "Simon Kelley", 0); add_txt("copyright.bind", COPYRIGHT, 0); @@ -4411,6 +4694,7 @@ void read_opts(int argc, char **argv, char *compile_opts) add_txt("auth.bind", NULL, TXT_STAT_AUTH); #endif add_txt("servers.bind", NULL, TXT_STAT_SERVERS); +#endif while (1) { @@ -4435,9 +4719,15 @@ void read_opts(int argc, char **argv, char *compile_opts) /* Copy optarg so that argv doesn't get changed */ if (optarg) { - strncpy(buff, optarg, MAXDNAME); - buff[MAXDNAME-1] = 0; - arg = buff; + if (strlen(optarg) >= argbuf_size) + { + free(argbuf); + argbuf_size = strlen(optarg) + 1; + argbuf = opt_malloc(argbuf_size); + } + strncpy(argbuf, optarg, argbuf_size); + argbuf[argbuf_size-1] = 0; + arg = argbuf; } else arg = NULL; @@ -4485,6 +4775,8 @@ void read_opts(int argc, char **argv, char *compile_opts) } } + free(argbuf); + if (conffile) { one_file(conffile, conffile_opt); @@ -4497,21 +4789,68 @@ void read_opts(int argc, char **argv, char *compile_opts) { struct server *tmp; for (tmp = daemon->servers; tmp; tmp = tmp->next) - { - tmp->edns_pktsz = daemon->edns_pktsz; - - if (!(tmp->flags & SERV_HAS_SOURCE)) - { - if (tmp->source_addr.sa.sa_family == AF_INET) - tmp->source_addr.in.sin_port = htons(daemon->query_port); + if (!(tmp->flags & SERV_HAS_SOURCE)) + { + if (tmp->source_addr.sa.sa_family == AF_INET) + tmp->source_addr.in.sin_port = htons(daemon->query_port); #ifdef HAVE_IPV6 - else if (tmp->source_addr.sa.sa_family == AF_INET6) - tmp->source_addr.in6.sin6_port = htons(daemon->query_port); + else if (tmp->source_addr.sa.sa_family == AF_INET6) + tmp->source_addr.in6.sin6_port = htons(daemon->query_port); #endif + } + } + + if (daemon->host_records) + { + struct host_record *hr; + + for (hr = daemon->host_records; hr; hr = hr->next) + if (hr->ttl == -1) + hr->ttl = daemon->local_ttl; + } + + if (daemon->cnames) + { + struct cname *cn, *cn2, *cn3; + +#define NOLOOP 1 +#define TESTLOOP 2 + + /* Fill in TTL for CNAMES noe we have local_ttl. + Also prepare to do loop detection. */ + for (cn = daemon->cnames; cn; cn = cn->next) + { + if (cn->ttl == -1) + cn->ttl = daemon->local_ttl; + cn->flag = 0; + cn->targetp = NULL; + for (cn2 = daemon->cnames; cn2; cn2 = cn2->next) + if (hostname_isequal(cn->target, cn2->alias)) + { + cn->targetp = cn2; + break; + } + } + + /* Find any CNAME loops.*/ + for (cn = daemon->cnames; cn; cn = cn->next) + { + for (cn2 = cn->targetp; cn2; cn2 = cn2->targetp) + { + if (cn2->flag == NOLOOP) + break; + + if (cn2->flag == TESTLOOP) + die(_("CNAME loop involving %s"), cn->alias, EC_BADCONF); + + cn2->flag = TESTLOOP; } - } + + for (cn3 = cn->targetp; cn3 != cn2; cn3 = cn3->targetp) + cn3->flag = NOLOOP; + } } - + if (daemon->if_addrs) { struct iname *tmp; diff --git a/src/outpacket.c b/src/outpacket.c index 5b1ff93..d20bd33 100644 --- a/src/outpacket.c +++ b/src/outpacket.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 @@ -29,9 +29,19 @@ void end_opt6(int container) PUTSHORT(len, p); } +void reset_counter(void) +{ + /* Clear out buffer when starting from beginning */ + if (daemon->outpacket.iov_base) + memset(daemon->outpacket.iov_base, 0, daemon->outpacket.iov_len); + + save_counter(0); +} + int save_counter(int newval) { int ret = outpacket_counter; + if (newval != -1) outpacket_counter = newval; @@ -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 diff --git a/src/radv-protocol.h b/src/radv-protocol.h index 4cc1ea4..2ca9ec7 100644 --- a/src/radv-protocol.h +++ b/src/radv-protocol.h @@ -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 @@ -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 @@ -28,11 +28,12 @@ struct ra_param { time_t now; - int ind, managed, other, found_context, first, adv_router; + int ind, managed, other, first, adv_router; char *if_name; struct dhcp_netid *tags; struct in6_addr link_local, link_global, ula; unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval, prio; + struct dhcp_context *found_context; }; struct search_param { @@ -81,7 +82,7 @@ void ra_init(time_t now) /* ensure this is around even if we're not doing DHCPv6 */ expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet)); - /* See if we're guessing SLAAC addresses, if so we need to recieve ping replies */ + /* See if we're guessing SLAAC addresses, if so we need to receive ping replies */ for (context = daemon->dhcp6; context; context = context->next) if ((context->flags & CONTEXT_RA_NAME)) break; @@ -111,10 +112,10 @@ void ra_init(time_t now) daemon->icmp6fd = fd; if (daemon->doing_ra) - ra_start_unsolicted(now, NULL); + ra_start_unsolicited(now, NULL); } -void ra_start_unsolicted(time_t now, struct dhcp_context *context) +void ra_start_unsolicited(time_t now, struct dhcp_context *context) { /* init timers so that we do ra's for some/all soon. some ra_times will end up zeroed if it's not appropriate to advertise those contexts. @@ -245,7 +246,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad struct dhcp_netid iface_id; struct dhcp_opt *opt_cfg; struct ra_interface *ra_param = find_iface_param(iface_name); - int done_dns = 0, old_prefix = 0; + int done_dns = 0, old_prefix = 0, mtu = 0; unsigned int min_pref_time; #ifdef HAVE_LINUX_NETWORK FILE *f; @@ -254,7 +255,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad parm.ind = iface; parm.managed = 0; parm.other = 0; - parm.found_context = 0; + parm.found_context = NULL; parm.adv_router = 0; parm.if_name = iface_name; parm.first = 1; @@ -263,8 +264,10 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad parm.adv_interval = calc_interval(ra_param); parm.prio = calc_prio(ra_param); - save_counter(0); - ra = expand(sizeof(struct ra_packet)); + reset_counter(); + + if (!(ra = expand(sizeof(struct ra_packet)))) + return; ra->type = ND_ROUTER_ADVERT; ra->code = 0; @@ -311,8 +314,14 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad unsigned int old = difftime(now, context->address_lost_time); if (old > context->saved_valid) - { + { /* We've advertised this enough, time to go */ + + /* If this context held the timeout, and there's another context in use + transfer the timeout there. */ + if (context->ra_time != 0 && parm.found_context && parm.found_context->ra_time == 0) + new_timeout(parm.found_context, iface_name, now); + *up = context->next; free(context); } @@ -393,22 +402,32 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad put_opt6_long(1000 * calc_interval(find_iface_param(iface_name))); } + /* Set the MTU from ra_param if any, an MTU of 0 mean automatic for linux, */ + /* an MTU of -1 prevents the option from being sent. */ + if (ra_param) + mtu = ra_param->mtu; #ifdef HAVE_LINUX_NETWORK - /* Note that IPv6 MTU is not necessarilly the same as the IPv4 MTU + /* Note that IPv6 MTU is not necessarily the same as the IPv4 MTU available from SIOCGIFMTU */ - sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", iface_name); - if ((f = fopen(daemon->namebuff, "r"))) + if (mtu == 0) { - if (fgets(daemon->namebuff, MAXDNAME, f)) - { - put_opt6_char(ICMP6_OPT_MTU); - put_opt6_char(1); - put_opt6_short(0); - put_opt6_long(atoi(daemon->namebuff)); - } - fclose(f); + char *mtu_name = ra_param ? ra_param->mtu_name : NULL; + sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? : iface_name); + if ((f = fopen(daemon->namebuff, "r"))) + { + if (fgets(daemon->namebuff, MAXDNAME, f)) + mtu = atoi(daemon->namebuff); + fclose(f); + } } #endif + if (mtu > 0) + { + put_opt6_char(ICMP6_OPT_MTU); + put_opt6_char(1); + put_opt6_short(0); + put_opt6_long(mtu); + } iface_enumerate(AF_LOCAL, &send_iface, add_lla); @@ -522,7 +541,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad } while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base, - save_counter(0), 0, (struct sockaddr *)&addr, + save_counter(-1), 0, (struct sockaddr *)&addr, sizeof(addr)))); } @@ -639,8 +658,10 @@ static int add_prefixes(struct in6_addr *local, int prefix, off_link = (context->flags & CONTEXT_RA_OFF_LINK); } - param->first = 0; - param->found_context = 1; + param->first = 0; + /* found_context is the _last_ one we found, so if there's + more than one, it's not the first. */ + param->found_context = context; } /* configured time is ceiling */ @@ -772,7 +793,7 @@ time_t periodic_ra(time_t now) associated with it, because it's for a subnet we dont have an interface on. Probably we're doing DHCP on a remote subnet via a relay. Zero the timer, since we won't - ever be able to send ra's and satistfy it. */ + ever be able to send ra's and satisfy it. */ context->ra_time = 0; if (param.iface != 0 && diff --git a/src/rfc1035.c b/src/rfc1035.c index de009d0..91c1bb6 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,336 +408,36 @@ 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) - { - if (p + 4 > limit) - return plen; /* Too big */ - PUTSHORT(optno, p); - PUTSHORT(optlen, p); - if (p + optlen > limit) - return plen; /* Too big */ - 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) { @@ -798,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++) { @@ -882,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; @@ -893,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(); @@ -901,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. */ @@ -915,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; @@ -943,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) || @@ -970,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; } @@ -996,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 @@ -1024,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 */ @@ -1042,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--) @@ -1133,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; @@ -1209,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); @@ -1248,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; @@ -1366,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, ...) { @@ -1375,18 +1108,15 @@ 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) \ - { \ - va_end(ap); \ - goto truncated; \ - } - - if (truncp && *truncp) - return 0; + if (limit && p + (size) > (unsigned char*)limit) goto truncated; va_start(ap, format); /* make ap point to 1st unamed argument */ - + + if (truncp && *truncp) + goto truncated; + if (nameoffset > 0) { CHECK_LIMIT(2); @@ -1395,27 +1125,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int else { char *name = va_arg(ap, char *); - if (name && !(p = do_rfc1035_name(p, name, limit))) - { - va_end(ap); - goto truncated; - } - + if (name && !(p = do_rfc1035_name(p, name, limit))) + goto truncated; + if (nameoffset < 0) { - CHECK_LIMIT(2); + CHECK_LIMIT(2); PUTSHORT(-nameoffset | 0xc000, p); } else - { - CHECK_LIMIT(1); + { + 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 */ @@ -1463,20 +1190,16 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int 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 *), limit); - if (!p) - { - va_end(ap); - goto truncated; - } - CHECK_LIMIT(1); + *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); + CHECK_LIMIT(usval); sval = va_arg(ap, char *); if (usval != 0) memcpy(p, sval, usval); @@ -1495,35 +1218,45 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int break; } -#undef CHECK_LIMIT va_end(ap); /* clean up variable argument pointer */ + /* Now, store real RDLength. sav already checked against limit. */ j = p - sav - 2; - /* this has already been checked against limit before */ - PUTSHORT(j, sav); /* Now, store real RDLength */ - - /* check for overflow of buffer */ - if (limit && ((unsigned char *)limit - p) < 0) - { -truncated: - 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 @@ -1534,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; @@ -1605,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) @@ -1625,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) { @@ -1632,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)) @@ -1643,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; @@ -1743,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>"); @@ -1799,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) { @@ -1812,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>"); @@ -1826,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 { @@ -1867,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) @@ -1899,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); @@ -1909,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; @@ -1937,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) @@ -1983,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)) @@ -1995,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; @@ -2023,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) { @@ -2039,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 @@ -2114,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 { @@ -2167,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; @@ -2344,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; @@ -2362,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; } - diff --git a/src/rfc2131.c b/src/rfc2131.c index bcfa5d6..c08a8ab 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.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 @@ -32,7 +32,7 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, char *string, int null_term); static struct in_addr option_addr(unsigned char *opt); -static unsigned int option_uint(unsigned char *opt, int i, int size); +static unsigned int option_uint(unsigned char *opt, int offset, int size); static void log_packet(char *type, void *addr, unsigned char *ext_mac, int mac_len, char *interface, char *string, char *err, u32 xid); static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize); @@ -42,14 +42,14 @@ static void clear_packet(struct dhcp_packet *mess, unsigned char *end); static int in_list(unsigned char *list, int opt); static void do_options(struct dhcp_context *context, struct dhcp_packet *mess, - unsigned char *real_end, + unsigned char *end, unsigned char *req_options, char *hostname, - char *config_domain, + char *domain, struct dhcp_netid *netid, struct in_addr subnet_addr, unsigned char fqdn_flags, - int null_term, int pxearch, + int null_term, int pxe_arch, unsigned char *uuid, int vendor_class_len, time_t now, @@ -58,15 +58,17 @@ static void do_options(struct dhcp_context *context, static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); -static int do_encap_opts(struct dhcp_opt *opts, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term); +static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term); static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid); static int prune_vendor_opts(struct dhcp_netid *netid); static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now); struct dhcp_boot *find_boot(struct dhcp_netid *netid); +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe); +static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid); - size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, - size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe, struct in_addr fallback) + size_t sz, time_t now, int unicast_dest, int loopback, + int *is_inform, int pxe, struct in_addr fallback, time_t recvtime) { unsigned char *opt, *clid = NULL; struct dhcp_lease *ltmp, *lease = NULL; @@ -155,7 +157,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, for (offset = 0; offset < (len - 5); offset += elen + 5) { elen = option_uint(opt, offset + 4 , 1); - if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA) + if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA && offset + elen + 5 <= len) { unsigned char *x = option_ptr(opt, offset + 5); unsigned char *y = option_ptr(opt, offset + elen + 5); @@ -186,7 +188,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, be enough free space at the end of the packet to copy the option. */ unsigned char *sopt; unsigned int total = option_len(opt) + 2; - unsigned char *last_opt = option_find(mess, sz, OPTION_END, 0); + unsigned char *last_opt = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + sz, + OPTION_END, 0); if (last_opt && last_opt < end - total) { end -= total; @@ -364,7 +367,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match. Otherwise assume the option is an array, and look for a matching element. - If no data given, existance of the option is enough. This code handles + If no data given, existence of the option is enough. This code handles rfc3925 V-I classes too. */ for (o = daemon->dhcp_match; o; o = o->next) { @@ -380,7 +383,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, { len = option_uint(opt, offset + 4 , 1); /* Need to take care that bad data can't run us off the end of the packet */ - if ((offset + len + 5 <= (option_len(opt))) && + if ((offset + len + 5 <= (unsigned)(option_len(opt))) && (option_uint(opt, offset, 4) == (unsigned int)o->u.encap)) for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) { @@ -485,6 +488,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, known_id.next = netid; netid = &known_id; } + else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, + mess->chaddr, mess->hlen, mess->htype, NULL)) + { + known_id.net = "known-othernet"; + known_id.next = netid; + netid = &known_id; + } if (mess_type == 0 && !pxe) { @@ -566,7 +576,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, lease_prune(lease, now); lease = NULL; } - if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now)) + if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now, loopback)) message = _("no address available"); } else @@ -824,7 +834,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else mess->siaddr = context->local; - snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer); + if (strchr(service->basename, '.')) + snprintf((char *)mess->file, sizeof(mess->file), + "%s", service->basename); + else + snprintf((char *)mess->file, sizeof(mess->file), + "%s.%d", service->basename, layer); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); pxe_misc(mess, end, uuid); @@ -851,6 +867,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST))) { struct dhcp_context *tmp; + int workaround = 0; for (tmp = context; tmp; tmp = tmp->current) if ((tmp->flags & CONTEXT_PROXY) && @@ -860,7 +877,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (tmp) { struct dhcp_boot *boot; - + int redirect4011 = 0; + if (tmp->netid.net) { tmp->netid.next = netid; @@ -878,10 +896,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, clear_packet(mess, end); - /* Provide the bootfile here, for gPXE, and in case we have no menu items - and set discovery_control = 8 */ - if (boot) + /* Redirect EFI clients to port 4011 */ + if (pxearch >= 6) + { + redirect4011 = 1; + mess->siaddr = tmp->local; + } + + /* Returns true if only one matching service is available. On port 4011, + it also inserts the boot file and server name. */ + workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe); + + if (!workaround && boot) { + /* Provide the bootfile here, for iPXE, and in case we have no menu items + and set discovery_control = 8 */ if (boot->next_server.s_addr) mess->siaddr = boot->next_server; else if (boot->tftp_sname) @@ -896,10 +925,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr)); pxe_misc(mess, end, uuid); prune_vendor_opts(tagif_netid); - do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); - + if ((pxe && !workaround) || !redirect4011) + do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid); log_tags(tagif_netid, ntohl(mess->xid)); + if (!ignore) + apply_delay(mess->xid, recvtime, tagif_netid); return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); } } @@ -1020,11 +1052,11 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, !config_find_by_address(daemon->dhcp_conf, lease->addr)) mess->yiaddr = lease->addr; else if (opt && address_available(context, addr, tagif_netid) && !lease_find_by_addr(addr) && - !config_find_by_address(daemon->dhcp_conf, addr)) + !config_find_by_address(daemon->dhcp_conf, addr) && do_icmp_ping(now, addr, 0, loopback)) mess->yiaddr = addr; else if (emac_len == 0) message = _("no unique-id"); - else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now)) + else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now, loopback)) message = _("no address available"); } @@ -1040,7 +1072,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } log_tags(tagif_netid, ntohl(mess->xid)); - + apply_delay(mess->xid, recvtime, tagif_netid); log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); @@ -1286,6 +1318,24 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, add_extradata_opt(lease, NULL); } + /* DNSMASQ_REQUESTED_OPTIONS */ + if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 1))) + { + int len = option_len(opt); + unsigned char *rop = option_ptr(opt, 0); + char *q = daemon->namebuff; + int i; + for (i = 0; i < len; i++) + { + q += snprintf(q, MAXDNAME - (q - daemon->namebuff), "%d%s", rop[i], i + 1 == len ? "" : ","); + } + lease_add_extradata(lease, (unsigned char *)daemon->namebuff, (q - daemon->namebuff), 0); + } + else + { + add_extradata_opt(lease, NULL); + } + /* space-concat tag set */ if (!tagif_netid) add_extradata_opt(lease, NULL); @@ -1308,7 +1358,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, /* If the user-class option started as counted strings, the first byte will be zero. */ if (len != 0 && ucp[0] == 0) ucp++, len--; - lease_add_extradata(lease, ucp, len, 0); + lease_add_extradata(lease, ucp, len, -1); } } #endif @@ -1589,7 +1639,7 @@ static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt { while (1) { - if (p > end) + if (p >= end) return NULL; else if (*p == OPTION_END) return opt == OPTION_END ? p : NULL; @@ -1975,6 +2025,56 @@ static int prune_vendor_opts(struct dhcp_netid *netid) return force; } + +/* Many UEFI PXE implementations have badly broken menu code. + If there's exactly one relevant menu item, we abandon the menu system, + and jamb the data direct into the DHCP file, siaddr and sname fields. + Note that in this case, we have to assume that layer zero would be requested + by the client PXE stack. */ +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe) +{ + struct pxe_service *service, *found; + + /* Only workaround UEFI archs. */ + if (pxe_arch < 6) + return 0; + + for (found = NULL, service = daemon->pxe_services; service; service = service->next) + if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1)) + { + if (found) + return 0; /* More than one relevant menu item */ + + found = service; + } + + if (!found) + return 0; /* No relevant menu items. */ + + if (!pxe) + return 1; + + if (found->sname) + { + mess->siaddr = a_record_from_hosts(found->sname, now); + snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname); + } + else + { + if (found->server.s_addr != 0) + mess->siaddr = found->server; + else + mess->siaddr = local; + + inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN); + } + + snprintf((char *)mess->file, sizeof(mess->file), + strchr(found->basename, '.') ? "%s" : "%s.0", found->basename); + + return 1; +} + static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now) { #define NUM_OPTS 4 @@ -2184,7 +2284,7 @@ static void do_options(struct dhcp_context *context, /* See if we can send the boot stuff as options. To do this we need a requested option list, BOOTP and very old DHCP clients won't have this, we also - provide an manual option to disable it. + provide a manual option to disable it. Some PXE ROMs have bugs (surprise!) and need zero-terminated names, so we always send those. */ if ((boot = find_boot(tagif))) @@ -2509,7 +2609,8 @@ static void do_options(struct dhcp_context *context, if (context && pxe_arch != -1) { pxe_misc(mess, end, uuid); - config_opts = pxe_opts(pxe_arch, tagif, context->local, now); + if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0)) + config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) && @@ -2528,6 +2629,29 @@ static void do_options(struct dhcp_context *context, } } +static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid) +{ + struct delay_config *delay_conf; + + /* Decide which delay_config option we're using */ + for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next) + if (match_netid(delay_conf->netid, netid, 0)) + break; + + if (!delay_conf) + /* No match, look for one without a netid */ + for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next) + if (match_netid(delay_conf->netid, netid, 1)) + break; + + if (delay_conf) + { + if (!option_bool(OPT_QUIET_DHCP)) + my_syslog(MS_DHCP | LOG_INFO, _("%u reply delay: %d"), ntohl(xid), delay_conf->delay); + delay_dhcp(recvtime, delay_conf->delay, -1, 0, 0); + } +} + #endif diff --git a/src/rfc3315.c b/src/rfc3315.c index 17612b0..3a2ed75 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.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 @@ -89,7 +89,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) vendor->netid.next = &vendor->netid; - save_counter(0); + reset_counter(); state.context = context; state.interface = interface; state.iface_name = iface_name; @@ -118,7 +118,7 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, void *opt; struct dhcp_vendor *vendor; - /* if not an encaplsulated relayed message, just do the stuff */ + /* if not an encapsulated relayed message, just do the stuff */ if (msg_type != DHCP6RELAYFORW) { /* if link_address != NULL if points to the link address field of the @@ -130,7 +130,7 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, MAC address from the local ND cache. */ if (!state->link_address) - get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type); + get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now); else { struct dhcp_context *c; @@ -216,9 +216,9 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, for (opt = opts; opt; opt = opt6_next(opt, end)) { - if (opt6_ptr(opt, 0) + opt6_len(opt) >= end) { + if (opt6_ptr(opt, 0) + opt6_len(opt) > end) return 0; - } + int o = new_opt6(opt6_type(opt)); if (opt6_type(opt) == OPTION6_RELAY_MSG) { @@ -268,7 +268,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ state->hostname_auth = 0; state->hostname = NULL; state->client_hostname = NULL; - state->fqdn_flags = 0x01; /* default to send if we recieve no FQDN option */ + state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */ #ifdef OPTION6_PREFIX_CLASS state->send_prefix_class = NULL; #endif @@ -387,7 +387,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match. Otherwise assume the option is an array, and look for a matching element. - If no data given, existance of the option is enough. This code handles + If no data given, existence of the option is enough. This code handles V-I opts too. */ for (opt_cfg = daemon->dhcp_match6; opt_cfg; opt_cfg = opt_cfg->next) { @@ -532,6 +532,13 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ if (have_config(config, CONFIG_DISABLE)) ignore = 1; } + else if (state->clid && + find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL)) + { + known_id.net = "known-othernet"; + known_id.next = state->tags; + state->tags = &known_id; + } #ifdef OPTION6_PREFIX_CLASS /* OPTION_PREFIX_CLASS in ORO, send addresses in all prefix classes */ @@ -875,7 +882,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ if (!ia_option) { - /* If we get a request with a IA_*A without addresses, treat it exactly like + /* If we get a request with an IA_*A without addresses, treat it exactly like a SOLICT with rapid commit set. */ save_counter(start); goto request_no_address; @@ -1279,7 +1286,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ } - /* We must anwser with 'success' in global section anyway */ + /* We must answer with 'success' in global section anyway */ o1 = new_opt6(OPTION6_STATUS_CODE); put_opt6_short(DHCP6SUCCESS); put_opt6_string(_("success")); @@ -1327,14 +1334,14 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) if (opt_cfg->opt == OPTION6_REFRESH_TIME) done_refresh = 1; + if (opt_cfg->opt == OPTION6_DNS_SERVER) + done_dns = 1; + if (opt_cfg->flags & DHOPT_ADDR6) { int len, j; struct in6_addr *a; - if (opt_cfg->opt == OPTION6_DNS_SERVER) - done_dns = 1; - for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0; j < opt_cfg->len; j += IN6ADDRSZ, a++) if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) || @@ -1393,7 +1400,7 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) unsigned int lease_time = 0xffffffff; /* Find the smallest lease tie of all contexts, - subjext to the RFC-4242 stipulation that this must not + subject to the RFC-4242 stipulation that this must not be less than 600. */ for (c = state->context; c; c = c->next) if (c->lease_time < lease_time) @@ -1618,7 +1625,7 @@ static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz) { if (t1cntr != 0) { - /* go back an fill in fields in IA_NA option */ + /* go back and fill in fields in IA_NA option */ int sav = save_counter(t1cntr); unsigned int t1, t2, fuzz = 0; @@ -1981,7 +1988,7 @@ static void log6_packet(struct state *state, char *type, struct in6_addr *addr, if (addr) { - inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, 255); + inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, DHCP_BUFF_SZ - 1); strcat(daemon->dhcp_buff2, " "); } else @@ -2060,7 +2067,8 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size) return ret; } -void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id) +void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, + struct in6_addr *peer_address, u32 scope_id, time_t now) { /* ->local is same value for all relays on ->current chain */ @@ -2074,7 +2082,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer unsigned char mac[DHCP_CHADDR_MAX]; inet_pton(AF_INET6, ALL_SERVERS, &multicast); - get_client_mac(peer_address, scope_id, mac, &maclen, &mactype); + get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now); /* source address == relay address */ from.addr.addr6 = relay->local.addr.addr6; @@ -2089,7 +2097,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer if (hopcount > 32) return; - save_counter(0); + reset_counter(); if ((header = put_opt6(NULL, 34))) { @@ -2132,7 +2140,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast to DHCPv6 server without correct interface")); } - send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(0), &to, &from, 0); + send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(-1), &to, &from, 0); if (option_bool(OPT_LOG_OPTS)) { @@ -2166,7 +2174,7 @@ unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival (!relay->interface || wildcard_match(relay->interface, arrival_interface))) break; - save_counter(0); + reset_counter(); if (relay) { diff --git a/src/rrfilter.c b/src/rrfilter.c new file mode 100644 index 0000000..f5b9c61 --- /dev/null +++ b/src/rrfilter.c @@ -0,0 +1,339 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Code to safely remove RRs from a DNS answer */ + +#include "dnsmasq.h" + +/* Go through a domain name, find "pointers" and fix them up based on how many bytes + we've chopped out of the packet, or check they don't point into an elided part. */ +static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +{ + unsigned char *ansp = *namep; + + while(1) + { + unsigned int label_type; + + if (!CHECK_LEN(header, ansp, plen, 1)) + return 0; + + label_type = (*ansp) & 0xc0; + + if (label_type == 0xc0) + { + /* pointer for compression. */ + unsigned int offset; + int i; + unsigned char *p; + + if (!CHECK_LEN(header, ansp, plen, 2)) + return 0; + + offset = ((*ansp++) & 0x3f) << 8; + offset |= *ansp++; + + p = offset + (unsigned char *)header; + + for (i = 0; i < rr_count; i++) + if (p < rrs[i]) + break; + else + if (i & 1) + offset -= rrs[i] - rrs[i-1]; + + /* does the pointer end up in an elided RR? */ + if (i & 1) + return 0; + + /* No, scale the pointer */ + if (fixup) + { + ansp -= 2; + *ansp++ = (offset >> 8) | 0xc0; + *ansp++ = offset & 0xff; + } + break; + } + else if (label_type == 0x80) + return 0; /* reserved */ + else if (label_type == 0x40) + { + /* Extended label type */ + unsigned int count; + + if (!CHECK_LEN(header, ansp, plen, 2)) + return 0; + + if (((*ansp++) & 0x3f) != 1) + return 0; /* we only understand bitstrings */ + + count = *(ansp++); /* Bits in bitstring */ + + if (count == 0) /* count == 0 means 256 bits */ + ansp += 32; + else + ansp += ((count-1)>>3)+1; + } + else + { /* label type == 0 Bottom six bits is length */ + unsigned int len = (*ansp++) & 0x3f; + + if (!ADD_RDLEN(header, ansp, plen, len)) + return 0; + + if (len == 0) + break; /* zero length label marks the end. */ + } + } + + *namep = ansp; + + return 1; +} + +/* Go through RRs and check or fixup the domain names contained within */ +static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +{ + int i, j, type, class, rdlen; + unsigned char *pp; + + for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) + { + 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 this RR is to be elided, don't fix up its contents */ + for (j = 0; j < rr_count; j += 2) + if (rrs[j] == pp) + break; + + if (j >= rr_count) + { + /* fixup name of RR */ + if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) + return 0; + + if (class == C_IN) + { + u16 *d; + + for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++) + { + if (*d != 0) + pp += *d; + else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) + return 0; + } + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return 0; + } + + return 1; +} + + +/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */ +size_t rrfilter(struct dns_header *header, size_t plen, int mode) +{ + static unsigned char **rrs; + static int rr_sz = 0; + + unsigned char *p = (unsigned char *)(header+1); + int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; + + if (ntohs(header->qdcount) != 1 || + !(p = skip_name(p, header, plen, 4))) + return plen; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + /* First pass, find pointers to start and end of all the records we wish to elide: + records added for DNSSEC, unless explicitly queried for */ + for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; + i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); + i++) + { + unsigned char *pstart = p; + int type, class; + + if (!(p = skip_name(p, header, plen, 10))) + return plen; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return plen; + + /* Don't remove the answer. */ + if (i < ntohs(header->ancount) && type == qtype && class == qclass) + continue; + + if (mode == 0) /* EDNS */ + { + /* EDNS mode, remove T_OPT from additional section only */ + if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT) + continue; + } + else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG) + /* DNSSEC mode, remove SIGs and NSECs from all three sections. */ + continue; + + + if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) + return plen; + + rrs[rr_found++] = pstart; + rrs[rr_found++] = p; + + if (i < ntohs(header->ancount)) + chop_an++; + else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) + chop_ns++; + else + chop_ar++; + } + + /* Nothing to do. */ + if (rr_found == 0) + return plen; + + /* Second pass, look for pointers in names in the records we're keeping and make sure they don't + point to records we're going to elide. This is theoretically possible, but unlikely. If + it happens, we give up and leave the answer unchanged. */ + p = (unsigned char *)(header+1); + + /* question first */ + if (!check_name(&p, header, plen, 0, rrs, rr_found)) + return plen; + p += 4; /* qclass, qtype */ + + /* Now answers and NS */ + if (!check_rrs(p, header, plen, 0, rrs, rr_found)) + return plen; + + /* Third pass, actually fix up pointers in the records */ + p = (unsigned char *)(header+1); + + check_name(&p, header, plen, 1, rrs, rr_found); + p += 4; /* qclass, qtype */ + + check_rrs(p, header, plen, 1, rrs, rr_found); + + /* Fourth pass, elide records */ + for (p = rrs[0], i = 1; i < rr_found; i += 2) + { + unsigned char *start = rrs[i]; + unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen; + + memmove(p, start, end-start); + p += end-start; + } + + plen = p - (unsigned char *)header; + header->ancount = htons(ntohs(header->ancount) - chop_an); + header->nscount = htons(ntohs(header->nscount) - chop_ns); + header->arcount = htons(ntohs(header->arcount) - chop_ar); + + return plen; +} + +/* This is used in the DNSSEC code too, hence it's exported */ +u16 *rrfilter_desc(int type) +{ + /* List of RRtypes which include domains in the data. + 0 -> domain + integer -> no. of plain bytes + -1 -> end + + zero is not a valid RRtype, so the final entry is returned for + anything which needs no mangling. + */ + + static u16 rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, + T_MF, 0, -1, + T_CNAME, 0, -1, + T_SOA, 0, 0, -1, + T_MB, 0, -1, + T_MG, 0, -1, + T_MR, 0, -1, + T_PTR, 0, -1, + T_MINFO, 0, 0, -1, + T_MX, 2, 0, -1, + T_RP, 0, 0, -1, + T_AFSDB, 2, 0, -1, + T_RT, 2, 0, -1, + T_SIG, 18, 0, -1, + T_PX, 2, 0, 0, -1, + T_NXT, 0, -1, + T_KX, 2, 0, -1, + T_SRV, 6, 0, -1, + T_DNAME, 0, -1, + 0, -1 /* wildcard/catchall */ + }; + + u16 *p = rr_desc; + + while (*p != type && *p != 0) + while (*p++ != (u16)-1); + + return p+1; +} + +int expand_workspace(unsigned char ***wkspc, int *szp, int new) +{ + unsigned char **p; + int old = *szp; + + if (old >= new+1) + return 1; + + if (new >= 100) + return 0; + + new += 5; + + if (!(p = whine_malloc(new * sizeof(unsigned char *)))) + return 0; + + if (old != 0 && *wkspc) + { + memcpy(p, *wkspc, old * sizeof(unsigned char *)); + free(*wkspc); + } + + *wkspc = p; + *szp = new; + + return 1; +} diff --git a/src/slaac.c b/src/slaac.c index abaad53..13317d8 100644 --- a/src/slaac.c +++ b/src/slaac.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 @@ -94,7 +94,7 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force) slaac->backoff = 1; slaac->addr = addr; /* Do RA's to prod it */ - ra_start_unsolicted(now, context); + ra_start_unsolicited(now, context); } if (slaac) @@ -146,8 +146,11 @@ time_t periodic_slaac(time_t now, struct dhcp_lease *leases) struct ping_packet *ping; struct sockaddr_in6 addr; - save_counter(0); - ping = expand(sizeof(struct ping_packet)); + reset_counter(); + + if (!(ping = expand(sizeof(struct ping_packet)))) + continue; + ping->type = ICMP6_ECHO_REQUEST; ping->code = 0; ping->identifier = ping_id; @@ -161,7 +164,7 @@ time_t periodic_slaac(time_t now, struct dhcp_lease *leases) addr.sin6_port = htons(IPPROTO_ICMPV6); addr.sin6_addr = slaac->addr; - if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(0), 0, + if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(-1), 0, (struct sockaddr *)&addr, sizeof(addr)) == -1 && errno == EHOSTUNREACH) slaac->ping_time = 0; /* Give up */ diff --git a/src/tables.c b/src/tables.c index aae1252..a3382ce 100644 --- a/src/tables.c +++ b/src/tables.c @@ -20,9 +20,7 @@ #if defined(HAVE_IPSET) && defined(HAVE_BSD_NETWORK) -#ifndef __FreeBSD__ #include <string.h> -#endif #include <sys/types.h> #include <sys/ioctl.h> @@ -53,52 +51,6 @@ static char *pfr_strerror(int errnum) } } -static int pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) -{ - struct pfioc_table io; - - if (size < 0 || (size && tbl == NULL)) - { - errno = EINVAL; - return (-1); - } - bzero(&io, sizeof io); - io.pfrio_flags = flags; - io.pfrio_buffer = tbl; - io.pfrio_esize = sizeof(*tbl); - io.pfrio_size = size; - if (ioctl(dev, DIOCRADDTABLES, &io)) - return (-1); - if (nadd != NULL) - *nadd = io.pfrio_nadd; - return (0); -} - -static int fill_addr(const struct all_addr *ipaddr, int flags, struct pfr_addr* addr) { - if ( !addr || !ipaddr) - { - my_syslog(LOG_ERR, _("error: fill_addr missused")); - return -1; - } - bzero(addr, sizeof(*addr)); -#ifdef HAVE_IPV6 - if (flags & F_IPV6) - { - addr->pfra_af = AF_INET6; - addr->pfra_net = 0x80; - memcpy(&(addr->pfra_ip6addr), &(ipaddr->addr), sizeof(struct in6_addr)); - } - else -#endif - { - addr->pfra_af = AF_INET; - addr->pfra_net = 0x20; - addr->pfra_ip4addr.s_addr = ipaddr->addr.addr4.s_addr; - } - return 1; -} - -/*****************************************************************************/ void ipset_init(void) { @@ -111,14 +63,13 @@ void ipset_init(void) } int add_to_ipset(const char *setname, const struct all_addr *ipaddr, - int flags, int remove) + int flags, int remove) { struct pfr_addr addr; struct pfioc_table io; struct pfr_table table; - int n = 0, rc = 0; - if ( dev == -1 ) + if (dev == -1) { my_syslog(LOG_ERR, _("warning: no opened pf devices %s"), pf_device); return -1; @@ -126,31 +77,52 @@ int add_to_ipset(const char *setname, const struct all_addr *ipaddr, bzero(&table, sizeof(struct pfr_table)); table.pfrt_flags |= PFR_TFLAG_PERSIST; - if ( strlen(setname) >= PF_TABLE_NAME_SIZE ) + if (strlen(setname) >= PF_TABLE_NAME_SIZE) { my_syslog(LOG_ERR, _("error: cannot use table name %s"), setname); errno = ENAMETOOLONG; return -1; } - if ( strlcpy(table.pfrt_name, setname, - sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + if (strlcpy(table.pfrt_name, setname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) { my_syslog(LOG_ERR, _("error: cannot strlcpy table name %s"), setname); return -1; } - if ((rc = pfr_add_tables(&table, 1, &n, 0))) + bzero(&io, sizeof io); + io.pfrio_flags = 0; + io.pfrio_buffer = &table; + io.pfrio_esize = sizeof(table); + io.pfrio_size = 1; + if (ioctl(dev, DIOCRADDTABLES, &io)) { - my_syslog(LOG_WARNING, _("warning: pfr_add_tables: %s(%d)"), - pfr_strerror(errno),rc); + my_syslog(LOG_WARNING, _("IPset: error:%s"), pfr_strerror(errno)); + return -1; } + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; - if (n) + if (io.pfrio_nadd) my_syslog(LOG_INFO, _("info: table created")); - - fill_addr(ipaddr,flags,&addr); + + bzero(&addr, sizeof(addr)); +#ifdef HAVE_IPV6 + if (flags & F_IPV6) + { + addr.pfra_af = AF_INET6; + addr.pfra_net = 0x80; + memcpy(&(addr.pfra_ip6addr), &(ipaddr->addr), sizeof(struct in6_addr)); + } + else +#endif + { + addr.pfra_af = AF_INET; + addr.pfra_net = 0x20; + addr.pfra_ip4addr.s_addr = ipaddr->addr.addr4.s_addr; + } + bzero(&io, sizeof(io)); io.pfrio_flags = 0; io.pfrio_table = table; @@ -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 @@ -20,7 +20,7 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix); static void free_transfer(struct tftp_transfer *transfer); -static ssize_t tftp_err(int err, char *packet, char *mess, char *file); +static ssize_t tftp_err(int err, char *packet, char *message, char *file); static ssize_t tftp_err_oops(char *packet, char *file); static ssize_t get_block(char *packet, struct tftp_transfer *transfer); static char *next(char **p, char *end); @@ -103,8 +103,10 @@ void tftp_request(struct listener *listen, time_t now) if (listen->iface) { addr = listen->iface->addr; - mtu = listen->iface->mtu; name = listen->iface->name; + mtu = listen->iface->mtu; + if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) + mtu = daemon->tftp_mtu; } else { @@ -234,9 +236,17 @@ void tftp_request(struct listener *listen, time_t now) strncpy(ifr.ifr_name, name, IF_NAMESIZE); if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) - mtu = ifr.ifr_mtu; + { + mtu = ifr.ifr_mtu; + if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) + mtu = daemon->tftp_mtu; + } } + /* Failed to get interface mtu - can use configured value. */ + if (mtu == 0) + mtu = daemon->tftp_mtu; + if (name) { /* check for per-interface prefix */ @@ -336,14 +346,15 @@ void tftp_request(struct listener *listen, time_t now) { if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) { + /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */ + int overhead = (listen->family == AF_INET) ? 32 : 52; transfer->blocksize = atoi(opt); if (transfer->blocksize < 1) transfer->blocksize = 1; if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4) transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4; - /* 32 bytes for IP, UDP and TFTP headers */ - if (mtu != 0 && transfer->blocksize > (unsigned)mtu - 32) - transfer->blocksize = (unsigned)mtu - 32; + if (mtu != 0 && transfer->blocksize > (unsigned)mtu - overhead) + transfer->blocksize = (unsigned)mtu - overhead; transfer->opt_blocksize = 1; transfer->block = 0; } @@ -371,7 +382,7 @@ void tftp_request(struct listener *listen, time_t now) if (prefix[strlen(prefix)-1] != '/') strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); - if (option_bool(OPT_TFTP_APREF)) + if (option_bool(OPT_TFTP_APREF_IP)) { size_t oldlen = strlen(daemon->namebuff); struct stat statbuf; @@ -383,7 +394,40 @@ void tftp_request(struct listener *listen, time_t now) if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) daemon->namebuff[oldlen] = 0; } - + + if (option_bool(OPT_TFTP_APREF_MAC)) + { + unsigned char *macaddr = NULL; + unsigned char macbuf[DHCP_CHADDR_MAX]; + +#ifdef HAVE_DHCP + if (daemon->dhcp && peer.sa.sa_family == AF_INET) + { + /* Check if the client IP is in our lease database */ + struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr); + if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN) + macaddr = lease->hwaddr; + } +#endif + + /* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */ + if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0) + macaddr = macbuf; + + if (macaddr) + { + size_t oldlen = strlen(daemon->namebuff); + struct stat statbuf; + + snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/", + macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); + + /* remove unique-directory if it doesn't exist */ + if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) + daemon->namebuff[oldlen] = 0; + } + } + /* Absolute pathnames OK if they match prefix */ if (filename[0] == '/') { @@ -396,7 +440,7 @@ void tftp_request(struct listener *listen, time_t now) else if (filename[0] == '/') daemon->namebuff[0] = 0; strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff)); - + /* check permissions and open file */ if ((transfer->file = check_tftp_fileperm(&len, prefix))) { @@ -459,7 +503,7 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix) else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) goto perm; - /* If we're doing many tranfers from the same file, only + /* If we're doing many transfers from the same file, only open it once this saves lots of file descriptors when mass-booting a big cluster, for instance. Be conservative and only share when inode and name match @@ -641,20 +685,24 @@ static void sanitise(char *buf) } +#define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */ static ssize_t tftp_err(int err, char *packet, char *message, char *file) { struct errmess { unsigned short op, err; char message[]; } *mess = (struct errmess *)packet; - ssize_t ret = 4; + ssize_t len, ret = 4; char *errstr = strerror(errno); + memset(packet, 0, daemon->packet_buff_sz); sanitise(file); - + mess->op = htons(OP_ERR); mess->err = htons(err); - ret += (snprintf(mess->message, 500, message, file, errstr) + 1); + len = snprintf(mess->message, MAXMESSAGE, message, file, errstr); + ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */ + my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); return ret; @@ -670,6 +718,8 @@ static ssize_t tftp_err_oops(char *packet, char *file) /* return -1 for error, zero for done. */ static ssize_t get_block(char *packet, struct tftp_transfer *transfer) { + memset(packet, 0, daemon->packet_buff_sz); + if (transfer->block == 0) { /* send OACK */ @@ -684,7 +734,7 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer) if (transfer->opt_blocksize) { p += (sprintf(p, "blksize") + 1); - p += (sprintf(p, "%d", transfer->blocksize) + 1); + p += (sprintf(p, "%u", transfer->blocksize) + 1); } if (transfer->opt_transize) { @@ -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 @@ -24,7 +24,9 @@ #include <sys/times.h> #endif -#if defined(LOCALEDIR) || defined(HAVE_IDN) +#if defined(HAVE_LIBIDN2) +#include <idn2.h> +#elif defined(HAVE_IDN) #include <idna.h> #endif @@ -109,6 +111,7 @@ u64 rand64(void) return (u64)out[outleft+1] + (((u64)out[outleft]) << 32); } +/* returns 2 if names is OK but contains one or more underscores */ static int check_name(char *in) { /* remove trailing . @@ -116,6 +119,7 @@ static int check_name(char *in) size_t dotgap = 0, l = strlen(in); char c; int nowhite = 0; + int hasuscore = 0; if (l == 0 || l > MAXDNAME) return 0; @@ -134,18 +138,22 @@ static int check_name(char *in) else if (isascii((unsigned char)c) && iscntrl((unsigned char)c)) /* iscntrl only gives expected results for ascii */ return 0; -#if !defined(LOCALEDIR) && !defined(HAVE_IDN) +#if !defined(HAVE_IDN) && !defined(HAVE_LIBIDN2) else if (!isascii((unsigned char)c)) return 0; #endif else if (c != ' ') - nowhite = 1; + { + nowhite = 1; + if (c == '_') + hasuscore = 1; + } } if (!nowhite) return 0; - return 1; + return hasuscore ? 2 : 1; } /* Hostnames have a more limited valid charset than domain names @@ -184,36 +192,50 @@ int legal_hostname(char *name) char *canonicalise(char *in, int *nomem) { char *ret = NULL; -#if defined(LOCALEDIR) || defined(HAVE_IDN) int rc; -#endif - + if (nomem) *nomem = 0; - if (!check_name(in)) + if (!(rc = check_name(in))) return NULL; -#if defined(LOCALEDIR) || defined(HAVE_IDN) - if ((rc = idna_to_ascii_lz(in, &ret, 0)) != IDNA_SUCCESS) +#if defined(HAVE_LIBIDN2) && (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003) + /* older libidn2 strips underscores, so don't do IDN processing + if the name has an underscore (check_name() returned 2) */ + if (rc != 2) +#endif +#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) { - if (ret) - free(ret); - - if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR)) +# ifdef HAVE_LIBIDN2 + rc = idn2_to_ascii_lz(in, &ret, IDN2_NONTRANSITIONAL); + if (rc == IDN2_DISALLOWED) + rc = idn2_to_ascii_lz(in, &ret, IDN2_TRANSITIONAL); +# else + rc = idna_to_ascii_lz(in, &ret, 0); +# endif + if (rc != IDNA_SUCCESS) { - my_syslog(LOG_ERR, _("failed to allocate memory")); - *nomem = 1; + if (ret) + free(ret); + + if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR)) + { + my_syslog(LOG_ERR, _("failed to allocate memory")); + *nomem = 1; + } + + return NULL; } - - return NULL; + + return ret; } -#else +#endif + if ((ret = whine_malloc(strlen(in)+1))) strcpy(ret, in); else if (nomem) *nomem = 1; -#endif return ret; } @@ -224,14 +246,16 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit) while (sval && *sval) { - if (limit && p + 1 > (unsigned char*)limit) - return p; - unsigned char *cp = p++; + + if (limit && p > (unsigned char*)limit) + return NULL; + for (j = 0; *sval && (*sval != '.'); sval++, j++) { - if (limit && p + 1 > (unsigned char*)limit) - return p; + if (limit && p + 1 > (unsigned char*)limit) + return NULL; + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE) *p++ = (*(++sval))-1; @@ -239,21 +263,23 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit) #endif *p++ = *sval; } + *cp = j; if (*sval) sval++; } + return p; } /* for use during startup */ void *safe_malloc(size_t size) { - void *ret = malloc(size); + void *ret = calloc(1, size); if (!ret) die(_("could not get memory"), NULL, EC_NOMEM); - + return ret; } @@ -267,11 +293,11 @@ void safe_pipe(int *fd, int read_noblock) void *whine_malloc(size_t size) { - void *ret = malloc(size); + void *ret = calloc(1, size); if (!ret) my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size); - + return ret; } @@ -328,7 +354,7 @@ int hostname_isequal(const char *a, const char *b) return 1; } - + time_t dnsmasq_time(void) { #ifdef HAVE_BROKEN_RTC @@ -378,7 +404,7 @@ int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen) return 0; } -/* return least signigicant 64 bits if IPv6 address */ +/* return least significant 64 bits if IPv6 address */ u64 addr6part(struct in6_addr *addr) { int i; @@ -444,13 +470,13 @@ void prettyprint_time(char *buf, unsigned int t) { unsigned int x, p = 0; if ((x = t/86400)) - p += sprintf(&buf[p], "%dd", x); + p += sprintf(&buf[p], "%ud", x); if ((x = (t/3600)%24)) - p += sprintf(&buf[p], "%dh", x); + p += sprintf(&buf[p], "%uh", x); if ((x = (t/60)%60)) - p += sprintf(&buf[p], "%dm", x); + p += sprintf(&buf[p], "%um", x); if ((x = t%60)) - p += sprintf(&buf[p], "%ds", x); + p += sprintf(&buf[p], "%us", x); } } @@ -502,9 +528,14 @@ int parse_hex(char *in, unsigned char *out, int maxlen, sav = in[(j+1)*2]; in[(j+1)*2] = 0; } + /* checks above allow mix of hexdigit and *, which + is illegal. */ + if (strchr(&in[j*2], '*')) + return -1; out[i] = strtol(&in[j*2], NULL, 16); mask = mask << 1; - i++; + if (++i == maxlen) + break; if (j < bytes - 1) in[(j+1)*2] = sav; } |