diff options
author | HyungKyu Song <hk76.song@samsung.com> | 2013-02-16 00:12:20 +0900 |
---|---|---|
committer | HyungKyu Song <hk76.song@samsung.com> | 2013-02-16 00:12:20 +0900 |
commit | f804b0c3a4c86e555cc04c0a2302af6716dcc32e (patch) | |
tree | 3b846665d64c5f6f36010b2bdbf48ebeb9904d4e /src | |
parent | e5a7e6adba9219ecf8bb5668382fab628f63bf6e (diff) | |
download | dnsmasq-tizen_2.0.tar.gz dnsmasq-tizen_2.0.tar.bz2 dnsmasq-tizen_2.0.zip |
Diffstat (limited to 'src')
-rw-r--r-- | src/bpf.c | 321 | ||||
-rw-r--r-- | src/cache.c | 1226 | ||||
-rw-r--r-- | src/config.h | 280 | ||||
-rw-r--r-- | src/dbus.c | 440 | ||||
-rw-r--r-- | src/dhcp.c | 1094 | ||||
-rw-r--r-- | src/dhcp_protocol.h | 91 | ||||
-rw-r--r-- | src/dns_protocol.h | 111 | ||||
-rw-r--r-- | src/dnsmasq.c | 1325 | ||||
-rw-r--r-- | src/dnsmasq.h | 941 | ||||
-rw-r--r-- | src/forward.c | 1182 | ||||
-rw-r--r-- | src/helper.c | 410 | ||||
-rw-r--r-- | src/lease.c | 615 | ||||
-rw-r--r-- | src/log.c | 466 | ||||
-rw-r--r-- | src/netlink.c | 326 | ||||
-rw-r--r-- | src/network.c | 973 | ||||
-rw-r--r-- | src/option.c | 3416 | ||||
-rw-r--r-- | src/rfc1035.c | 1815 | ||||
-rw-r--r-- | src/rfc2131.c | 2512 | ||||
-rw-r--r-- | src/tftp.c | 711 | ||||
-rw-r--r-- | src/util.c | 516 |
20 files changed, 18771 insertions, 0 deletions
diff --git a/src/bpf.c b/src/bpf.c new file mode 100644 index 0000000..9a77426 --- /dev/null +++ b/src/bpf.c @@ -0,0 +1,321 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +#if defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK) + +static struct iovec ifconf = { + .iov_base = NULL, + .iov_len = 0 +}; + +static struct iovec ifreq = { + .iov_base = NULL, + .iov_len = 0 +}; + +#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) + +#include <sys/sysctl.h> +#include <net/route.h> +#include <net/if_dl.h> +#include <netinet/if_ether.h> + +int arp_enumerate(void *parm, int (*callback)()) +{ + int mib[6]; + size_t needed; + char *next; + struct rt_msghdr *rtm; + struct sockaddr_inarp *sin2; + struct sockaddr_dl *sdl; + int rc; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_FLAGS; +#ifdef RTF_LLINFO + mib[5] = RTF_LLINFO; +#else + mib[5] = 0; +#endif + if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1 || needed == 0) + return 0; + + while (1) + { + if (!expand_buf(&ifconf, needed)) + return 0; + if ((rc = sysctl(mib, 6, ifconf.iov_base, &needed, NULL, 0)) == 0 || + errno != ENOMEM) + break; + needed += needed / 8; + } + if (rc == -1) + return 0; + + for (next = ifconf.iov_base ; next < (char *)ifconf.iov_base + needed; next += rtm->rtm_msglen) + { + rtm = (struct rt_msghdr *)next; + sin2 = (struct sockaddr_inarp *)(rtm + 1); + sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2)); + if (!(*callback)(AF_INET, &sin2->sin_addr, LLADDR(sdl), sdl->sdl_alen, parm)) + return 0; + } + + return 1; +} + +#endif + + +int iface_enumerate(int family, void *parm, int (*callback)()) +{ + char *ptr; + struct ifreq *ifr; + struct ifconf ifc; + int fd, errsav, ret = 0; + int lastlen = 0; + size_t len = 0; + + if (family == AF_UNSPEC) +#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) + return arp_enumerate(parm, callback); +#else + return 0; /* need code for Solaris and MacOS*/ +#endif + + if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + + while(1) + { + len += 10*sizeof(struct ifreq); + + if (!expand_buf(&ifconf, len)) + goto err; + + ifc.ifc_len = len; + ifc.ifc_buf = ifconf.iov_base; + + if (ioctl(fd, SIOCGIFCONF, &ifc) == -1) + { + if (errno != EINVAL || lastlen != 0) + goto err; + } + else + { + if (ifc.ifc_len == lastlen) + break; /* got a big enough buffer now */ + lastlen = ifc.ifc_len; + } + } + + for (ptr = ifc.ifc_buf; ptr < (char *)(ifc.ifc_buf + ifc.ifc_len); ptr += len) + { + /* subsequent entries may not be aligned, so copy into + an aligned buffer to avoid nasty complaints about + unaligned accesses. */ + + len = sizeof(struct ifreq); + +#ifdef HAVE_SOCKADDR_SA_LEN + ifr = (struct ifreq *)ptr; + if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru)) + len = ifr->ifr_addr.sa_len + offsetof(struct ifreq, ifr_ifru); +#endif + + if (!expand_buf(&ifreq, len)) + goto err; + + ifr = (struct ifreq *)ifreq.iov_base; + memcpy(ifr, ptr, len); + + if (ifr->ifr_addr.sa_family == family) + { + if (family == AF_INET) + { + struct in_addr addr, netmask, broadcast; + broadcast.s_addr = 0; + addr = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; + if (ioctl(fd, SIOCGIFNETMASK, ifr) == -1) + continue; + netmask = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; + if (ioctl(fd, SIOCGIFBRDADDR, ifr) != -1) + broadcast = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; + if (!((*callback)(addr, + (int)if_nametoindex(ifr->ifr_name), + netmask, broadcast, + parm))) + goto err; + } +#ifdef HAVE_IPV6 + else if (family == AF_INET6) + { + struct in6_addr *addr = &((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_addr; + /* voodoo to clear interface field in address */ + if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr)) + { + addr->s6_addr[2] = 0; + addr->s6_addr[3] = 0; + } + if (!((*callback)(addr, + (int)((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_scope_id, + (int)if_nametoindex(ifr->ifr_name), + parm))) + goto err; + } +#endif + } + } + + ret = 1; + + err: + errsav = errno; + close(fd); + errno = errsav; + + return ret; +} +#endif + + +#if defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP) +#include <net/bpf.h> + +void init_bpf(void) +{ + int i = 0; + + while (1) + { + /* useful size which happens to be sufficient */ + if (expand_buf(&ifreq, sizeof(struct ifreq))) + { + sprintf(ifreq.iov_base, "/dev/bpf%d", i++); + if ((daemon->dhcp_raw_fd = open(ifreq.iov_base, O_RDWR, 0)) != -1) + return; + } + if (errno != EBUSY) + die(_("cannot create DHCP BPF socket: %s"), NULL, EC_BADNET); + } +} + +void send_via_bpf(struct dhcp_packet *mess, size_t len, + struct in_addr iface_addr, struct ifreq *ifr) +{ + /* Hairy stuff, packet either has to go to the + net broadcast or the destination can't reply to ARP yet, + but we do know the physical address. + Build the packet by steam, and send directly, bypassing + the kernel IP stack */ + + struct ether_header ether; + struct ip ip; + struct udphdr { + u16 uh_sport; /* source port */ + u16 uh_dport; /* destination port */ + u16 uh_ulen; /* udp length */ + u16 uh_sum; /* udp checksum */ + } udp; + + u32 i, sum; + struct iovec iov[4]; + + /* Only know how to do ethernet on *BSD */ + if (mess->htype != ARPHRD_ETHER || mess->hlen != ETHER_ADDR_LEN) + { + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP request for unsupported hardware type (%d) received on %s"), + mess->htype, ifr->ifr_name); + return; + } + + ifr->ifr_addr.sa_family = AF_LINK; + if (ioctl(daemon->dhcpfd, SIOCGIFADDR, ifr) < 0) + return; + + memcpy(ether.ether_shost, LLADDR((struct sockaddr_dl *)&ifr->ifr_addr), ETHER_ADDR_LEN); + ether.ether_type = htons(ETHERTYPE_IP); + + if (ntohs(mess->flags) & 0x8000) + { + memset(ether.ether_dhost, 255, ETHER_ADDR_LEN); + ip.ip_dst.s_addr = INADDR_BROADCAST; + } + else + { + memcpy(ether.ether_dhost, mess->chaddr, ETHER_ADDR_LEN); + ip.ip_dst.s_addr = mess->yiaddr.s_addr; + } + + ip.ip_p = IPPROTO_UDP; + ip.ip_src.s_addr = iface_addr.s_addr; + ip.ip_len = htons(sizeof(struct ip) + + sizeof(struct udphdr) + + len) ; + ip.ip_hl = sizeof(struct ip) / 4; + ip.ip_v = IPVERSION; + ip.ip_tos = 0; + ip.ip_id = htons(0); + ip.ip_off = htons(0x4000); /* don't fragment */ + ip.ip_ttl = IPDEFTTL; + ip.ip_sum = 0; + for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++) + sum += ((u16 *)&ip)[i]; + while (sum>>16) + sum = (sum & 0xffff) + (sum >> 16); + ip.ip_sum = (sum == 0xffff) ? sum : ~sum; + + udp.uh_sport = htons(daemon->dhcp_server_port); + udp.uh_dport = htons(daemon->dhcp_client_port); + if (len & 1) + ((char *)mess)[len] = 0; /* for checksum, in case length is odd. */ + udp.uh_sum = 0; + udp.uh_ulen = sum = htons(sizeof(struct udphdr) + len); + sum += htons(IPPROTO_UDP); + sum += ip.ip_src.s_addr & 0xffff; + sum += (ip.ip_src.s_addr >> 16) & 0xffff; + sum += ip.ip_dst.s_addr & 0xffff; + sum += (ip.ip_dst.s_addr >> 16) & 0xffff; + for (i = 0; i < sizeof(struct udphdr)/2; i++) + sum += ((u16 *)&udp)[i]; + for (i = 0; i < (len + 1) / 2; i++) + sum += ((u16 *)mess)[i]; + while (sum>>16) + sum = (sum & 0xffff) + (sum >> 16); + udp.uh_sum = (sum == 0xffff) ? sum : ~sum; + + ioctl(daemon->dhcp_raw_fd, BIOCSETIF, ifr); + + iov[0].iov_base = ðer; + iov[0].iov_len = sizeof(ether); + iov[1].iov_base = &ip; + iov[1].iov_len = sizeof(ip); + iov[2].iov_base = &udp; + iov[2].iov_len = sizeof(udp); + iov[3].iov_base = mess; + iov[3].iov_len = len; + + while (writev(daemon->dhcp_raw_fd, iov, 4) == -1 && retry_send()); +} + +#endif + + diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..77c1972 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,1226 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL; +#ifdef HAVE_DHCP +static struct crec *dhcp_spare = NULL; +#endif +static struct crec *new_chain = NULL; +static int cache_inserted = 0, cache_live_freed = 0, insert_error; +static union bigname *big_free = NULL; +static int bignames_left, hash_size; +static int uid = 0; +static char *addrbuff = NULL; + +/* type->string mapping: this is also used by the name-hash function as a mixing table. */ +static const struct { + unsigned int type; + const char * const name; +} typestr[] = { + { 1, "A" }, + { 2, "NS" }, + { 5, "CNAME" }, + { 6, "SOA" }, + { 10, "NULL" }, + { 11, "WKS" }, + { 12, "PTR" }, + { 13, "HINFO" }, + { 15, "MX" }, + { 16, "TXT" }, + { 22, "NSAP" }, + { 23, "NSAP_PTR" }, + { 24, "SIG" }, + { 25, "KEY" }, + { 28, "AAAA" }, + { 33, "SRV" }, + { 35, "NAPTR" }, + { 36, "KX" }, + { 37, "CERT" }, + { 38, "A6" }, + { 39, "DNAME" }, + { 41, "OPT" }, + { 48, "DNSKEY" }, + { 249, "TKEY" }, + { 250, "TSIG" }, + { 251, "IXFR" }, + { 252, "AXFR" }, + { 253, "MAILB" }, + { 254, "MAILA" }, + { 255, "ANY" } +}; + +static void cache_free(struct crec *crecp); +static void cache_unlink(struct crec *crecp); +static void cache_link(struct crec *crecp); +static void rehash(int size); +static void cache_hash(struct crec *crecp); + +void cache_init(void) +{ + struct crec *crecp; + int i; + + if (option_bool(OPT_LOG)) + addrbuff = safe_malloc(ADDRSTRLEN); + + bignames_left = daemon->cachesize/10; + + if (daemon->cachesize > 0) + { + crecp = safe_malloc(daemon->cachesize*sizeof(struct crec)); + + for (i=0; i < daemon->cachesize; i++, crecp++) + { + cache_link(crecp); + crecp->flags = 0; + crecp->uid = uid++; + } + } + + /* create initial hash table*/ + rehash(daemon->cachesize); +} + +/* In most cases, we create the hash table once here by calling this with (hash_table == NULL) + but if the hosts file(s) are big (some people have 50000 ad-block entries), the table + will be much too small, so the hosts reading code calls rehash every 1000 addresses, to + expand the table. */ +static void rehash(int size) +{ + struct crec **new, **old, *p, *tmp; + int i, new_size, old_size; + + /* hash_size is a power of two. */ + for (new_size = 64; new_size < size/10; new_size = new_size << 1); + + /* must succeed in getting first instance, failure later is non-fatal */ + if (!hash_table) + new = safe_malloc(new_size * sizeof(struct crec *)); + else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec *)))) + return; + + for(i = 0; i < new_size; i++) + new[i] = NULL; + + old = hash_table; + old_size = hash_size; + hash_table = new; + hash_size = new_size; + + if (old) + { + for (i = 0; i < old_size; i++) + for (p = old[i]; p ; p = tmp) + { + tmp = p->hash_next; + cache_hash(p); + } + free(old); + } +} + +static struct crec **hash_bucket(char *name) +{ + unsigned int c, val = 017465; /* Barker code - minimum self-correlation in cyclic shift */ + const unsigned char *mix_tab = (const unsigned char*)typestr; + + while((c = (unsigned char) *name++)) + { + /* don't use tolower and friends here - they may be messed up by LOCALE */ + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + val = ((val << 7) | (val >> (32 - 7))) + (mix_tab[(val + c) & 0x3F] ^ c); + } + + /* hash_size is a power of two */ + return hash_table + ((val ^ (val >> 16)) & (hash_size - 1)); +} + +static void cache_hash(struct crec *crecp) +{ + /* maintain an invariant that all entries with F_REVERSE set + are at the start of the hash-chain and all non-reverse + immortal entries are at the end of the hash-chain. + This allows reverse searches and garbage collection to be optimised */ + + struct crec **up = hash_bucket(cache_get_name(crecp)); + + if (!(crecp->flags & F_REVERSE)) + { + while (*up && ((*up)->flags & F_REVERSE)) + up = &((*up)->hash_next); + + if (crecp->flags & F_IMMORTAL) + while (*up && !((*up)->flags & F_IMMORTAL)) + up = &((*up)->hash_next); + } + crecp->hash_next = *up; + *up = crecp; +} + +static void cache_free(struct crec *crecp) +{ + crecp->flags &= ~F_FORWARD; + crecp->flags &= ~F_REVERSE; + crecp->uid = uid++; /* invalidate CNAMES pointing to this. */ + + if (cache_tail) + cache_tail->next = crecp; + else + cache_head = crecp; + crecp->prev = cache_tail; + crecp->next = NULL; + cache_tail = crecp; + + /* retrieve big name for further use. */ + if (crecp->flags & F_BIGNAME) + { + crecp->name.bname->next = big_free; + big_free = crecp->name.bname; + crecp->flags &= ~F_BIGNAME; + } +} + +/* insert a new cache entry at the head of the list (youngest entry) */ +static void cache_link(struct crec *crecp) +{ + if (cache_head) /* check needed for init code */ + cache_head->prev = crecp; + crecp->next = cache_head; + crecp->prev = NULL; + cache_head = crecp; + if (!cache_tail) + cache_tail = crecp; +} + +/* remove an arbitrary cache entry for promotion */ +static void cache_unlink (struct crec *crecp) +{ + if (crecp->prev) + crecp->prev->next = crecp->next; + else + cache_head = crecp->next; + + if (crecp->next) + crecp->next->prev = crecp->prev; + else + cache_tail = crecp->prev; +} + +char *cache_get_name(struct crec *crecp) +{ + if (crecp->flags & F_BIGNAME) + return crecp->name.bname->name; + else if (crecp->flags & F_NAMEP) + return crecp->name.namep; + + return crecp->name.sname; +} + +static int is_outdated_cname_pointer(struct crec *crecp) +{ + if (!(crecp->flags & F_CNAME)) + return 0; + + if (crecp->addr.cname.cache && crecp->addr.cname.uid == crecp->addr.cname.cache->uid) + return 0; + + return 1; +} + +static int is_expired(time_t now, struct crec *crecp) +{ + if (crecp->flags & F_IMMORTAL) + return 0; + + if (difftime(now, crecp->ttd) < 0) + return 0; + + return 1; +} + +static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags) +{ + /* Scan and remove old entries. + If (flags & F_FORWARD) then remove any forward entries for name and any expired + entries but only in the same hash bucket as name. + If (flags & F_REVERSE) then remove any reverse entries for addr and any expired + entries in the whole cache. + If (flags == 0) remove any expired entries in the whole cache. + + In the flags & F_FORWARD case, the return code is valid, and returns zero if the + name exists in the cache as a HOSTS or DHCP entry (these are never deleted) + + We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal> + so that when we hit an entry which isn't reverse and is immortal, we're done. */ + + struct crec *crecp, **up; + + if (flags & F_FORWARD) + { + for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next) + if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + { + cache_unlink(crecp); + cache_free(crecp); + } + } + else if ((crecp->flags & F_FORWARD) && + ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) && + hostname_isequal(cache_get_name(crecp), name)) + { + if (crecp->flags & (F_HOSTS | F_DHCP)) + return 0; + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + } + else + up = &crecp->hash_next; + } + else + { + int i; +#ifdef HAVE_IPV6 + int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ; +#else + int addrlen = INADDRSZ; +#endif + for (i = 0; i < hash_size; i++) + for (crecp = hash_table[i], up = &hash_table[i]; + crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL)); + crecp = crecp->hash_next) + if (is_expired(now, crecp)) + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + { + cache_unlink(crecp); + cache_free(crecp); + } + } + else if (!(crecp->flags & (F_HOSTS | F_DHCP)) && + (flags & crecp->flags & F_REVERSE) && + (flags & crecp->flags & (F_IPV4 | F_IPV6)) && + memcmp(&crecp->addr.addr, addr, addrlen) == 0) + { + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + } + else + up = &crecp->hash_next; + } + + return 1; +} + +/* Note: The normal calling sequence is + cache_start_insert + cache_insert * n + cache_end_insert + + but an abort can cause the cache_end_insert to be missed + in which can the next cache_start_insert cleans things up. */ + +void cache_start_insert(void) +{ + /* Free any entries which didn't get committed during the last + insert due to error. + */ + while (new_chain) + { + struct crec *tmp = new_chain->next; + cache_free(new_chain); + new_chain = tmp; + } + new_chain = NULL; + insert_error = 0; +} + +struct crec *cache_insert(char *name, struct all_addr *addr, + time_t now, unsigned long ttl, unsigned short flags) +{ + struct crec *new; + union bigname *big_name = NULL; + int freed_all = flags & F_REVERSE; + int free_avail = 0; + + log_query(flags | F_UPSTREAM, name, addr, NULL); + + /* if previous insertion failed give up now. */ + if (insert_error) + return NULL; + + /* First remove any expired entries and entries for the name/address we + are currently inserting. Fail is we attempt to delete a name from + /etc/hosts or DHCP. */ + if (!cache_scan_free(name, addr, now, flags)) + { + insert_error = 1; + return NULL; + } + + /* Now get a cache entry from the end of the LRU list */ + while (1) { + if (!(new = cache_tail)) /* no entries left - cache is too small, bail */ + { + insert_error = 1; + return NULL; + } + + /* End of LRU list is still in use: if we didn't scan all the hash + chains for expired entries do that now. If we already tried that + then it's time to start spilling things. */ + + if (new->flags & (F_FORWARD | F_REVERSE)) + { + /* If free_avail set, we believe that an entry has been freed. + Bugs have been known to make this not true, resulting in + a tight loop here. If that happens, abandon the + insert. Once in this state, all inserts will probably fail. */ + if (free_avail) + { + insert_error = 1; + return NULL; + } + + if (freed_all) + { + free_avail = 1; /* Must be free space now. */ + cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags); + cache_live_freed++; + } + else + { + cache_scan_free(NULL, NULL, now, 0); + freed_all = 1; + } + continue; + } + + /* Check if we need to and can allocate extra memory for a long name. + If that fails, give up now. */ + if (name && (strlen(name) > SMALLDNAME-1)) + { + if (big_free) + { + big_name = big_free; + big_free = big_free->next; + } + else if (!bignames_left || + !(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) + { + insert_error = 1; + return NULL; + } + else + bignames_left--; + + } + + /* Got the rest: finally grab entry. */ + cache_unlink(new); + break; + } + + new->flags = flags; + if (big_name) + { + new->name.bname = big_name; + new->flags |= F_BIGNAME; + } + + if (name) + strcpy(cache_get_name(new), name); + else + *cache_get_name(new) = 0; + + if (addr) + new->addr.addr = *addr; + else + new->addr.cname.cache = NULL; + + new->ttd = now + (time_t)ttl; + new->next = new_chain; + new_chain = new; + + return new; +} + +/* after end of insertion, commit the new entries */ +void cache_end_insert(void) +{ + if (insert_error) + return; + + while (new_chain) + { + struct crec *tmp = new_chain->next; + /* drop CNAMEs which didn't find a target. */ + if (is_outdated_cname_pointer(new_chain)) + cache_free(new_chain); + else + { + cache_hash(new_chain); + cache_link(new_chain); + cache_inserted++; + } + new_chain = tmp; + } + new_chain = NULL; +} + +struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned short prot) +{ + struct crec *ans; + + if (crecp) /* iterating */ + ans = crecp->next; + else + { + /* first search, look for relevant entries and push to top of list + also free anything which has expired */ + struct crec *next, **up, **insert = NULL, **chainp = &ans; + unsigned short ins_flags = 0; + + for (up = hash_bucket(name), crecp = *up; crecp; crecp = next) + { + next = crecp->hash_next; + + if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) + { + if ((crecp->flags & F_FORWARD) && + (crecp->flags & prot) && + hostname_isequal(cache_get_name(crecp), name)) + { + if (crecp->flags & (F_HOSTS | F_DHCP)) + { + *chainp = crecp; + chainp = &crecp->next; + } + else + { + cache_unlink(crecp); + cache_link(crecp); + } + + /* Move all but the first entry up the hash chain + this implements round-robin. + Make sure that re-ordering doesn't break the hash-chain + order invariants. + */ + if (insert && (crecp->flags & (F_REVERSE | F_IMMORTAL)) == ins_flags) + { + *up = crecp->hash_next; + crecp->hash_next = *insert; + *insert = crecp; + insert = &crecp->hash_next; + } + else + { + if (!insert) + { + insert = up; + ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL); + } + up = &crecp->hash_next; + } + } + else + /* case : not expired, incorrect entry. */ + up = &crecp->hash_next; + } + else + { + /* expired entry, free it */ + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + { + cache_unlink(crecp); + cache_free(crecp); + } + } + } + + *chainp = cache_head; + } + + if (ans && + (ans->flags & F_FORWARD) && + (ans->flags & prot) && + hostname_isequal(cache_get_name(ans), name)) + return ans; + + return NULL; +} + +struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, + time_t now, unsigned short prot) +{ + struct crec *ans; +#ifdef HAVE_IPV6 + int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ; +#else + int addrlen = INADDRSZ; +#endif + + if (crecp) /* iterating */ + ans = crecp->next; + else + { + /* first search, look for relevant entries and push to top of list + also free anything which has expired. All the reverse entries are at the + start of the hash chain, so we can give up when we find the first + non-REVERSE one. */ + int i; + struct crec **up, **chainp = &ans; + + for (i=0; i<hash_size; i++) + for (crecp = hash_table[i], up = &hash_table[i]; + crecp && (crecp->flags & F_REVERSE); + crecp = crecp->hash_next) + if (!is_expired(now, crecp)) + { + if ((crecp->flags & prot) && + memcmp(&crecp->addr.addr, addr, addrlen) == 0) + { + if (crecp->flags & (F_HOSTS | F_DHCP)) + { + *chainp = crecp; + chainp = &crecp->next; + } + else + { + cache_unlink(crecp); + cache_link(crecp); + } + } + up = &crecp->hash_next; + } + else + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + { + cache_unlink(crecp); + cache_free(crecp); + } + } + + *chainp = cache_head; + } + + if (ans && + (ans->flags & F_REVERSE) && + (ans->flags & prot) && + memcmp(&ans->addr.addr, addr, addrlen) == 0) + return ans; + + return NULL; +} + +static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrlen, + unsigned short flags, int index, int addr_dup) +{ + struct crec *lookup = cache_find_by_name(NULL, cache->name.sname, 0, flags & (F_IPV4 | F_IPV6)); + int i, nameexists = 0; + struct cname *a; + + /* Remove duplicates in hosts files. */ + if (lookup && (lookup->flags & F_HOSTS)) + { + nameexists = 1; + if (memcmp(&lookup->addr.addr, addr, addrlen) == 0) + { + free(cache); + return; + } + } + + /* Ensure there is only one address -> name mapping (first one trumps) + We do this by steam here, first we see if the address is the same as + the last one we saw, which eliminates most in the case of an ad-block + file with thousands of entries for the same address. + Then we search and bail at the first matching address that came from + a HOSTS file. Since the first host entry gets reverse, we know + then that it must exist without searching exhaustively for it. */ + + if (addr_dup) + flags &= ~F_REVERSE; + else + for (i=0; i<hash_size; i++) + { + for (lookup = hash_table[i]; lookup; lookup = lookup->hash_next) + if ((lookup->flags & F_HOSTS) && + (lookup->flags & flags & (F_IPV4 | F_IPV6)) && + memcmp(&lookup->addr.addr, addr, addrlen) == 0) + { + flags &= ~F_REVERSE; + break; + } + if (lookup) + break; + } + + cache->flags = flags; + cache->uid = index; + memcpy(&cache->addr.addr, addr, addrlen); + cache_hash(cache); + + /* don't need to do alias stuff for second and subsequent addresses. */ + if (!nameexists) + for (a = daemon->cnames; a; a = a->next) + if (hostname_isequal(cache->name.sname, a->target) && + (lookup = whine_malloc(sizeof(struct crec)))) + { + lookup->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_HOSTS | F_CNAME; + lookup->name.namep = a->alias; + lookup->addr.cname.cache = cache; + lookup->addr.cname.uid = index; + cache_hash(lookup); + } +} + +static int eatspace(FILE *f) +{ + int c, nl = 0; + + while (1) + { + if ((c = getc(f)) == '#') + while (c != '\n' && c != EOF) + c = getc(f); + + if (c == EOF) + return 1; + + if (!isspace(c)) + { + ungetc(c, f); + return nl; + } + + if (c == '\n') + nl = 1; + } +} + +static int gettok(FILE *f, char *token) +{ + int c, count = 0; + + while (1) + { + if ((c = getc(f)) == EOF) + return (count == 0) ? EOF : 1; + + if (isspace(c) || c == '#') + { + ungetc(c, f); + return eatspace(f); + } + + if (count < (MAXDNAME - 1)) + { + token[count++] = c; + token[count] = 0; + } + } +} + +static int read_hostsfile(char *filename, int index, int cache_size) +{ + FILE *f = fopen(filename, "r"); + char *token = daemon->namebuff, *domain_suffix = NULL; + int addr_count = 0, name_count = cache_size, lineno = 0; + unsigned short flags = 0, saved_flags = 0; + struct all_addr addr, saved_addr; + int atnl, addrlen = 0, addr_dup; + + if (!f) + { + my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno)); + return 0; + } + + eatspace(f); + + while ((atnl = gettok(f, token)) != EOF) + { + addr_dup = 0; + lineno++; + +#ifdef HAVE_IPV6 + if (inet_pton(AF_INET, token, &addr) > 0) + { + flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; + addrlen = INADDRSZ; + domain_suffix = get_domain(addr.addr.addr4); + } + else if (inet_pton(AF_INET6, token, &addr) > 0) + { + flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; + addrlen = IN6ADDRSZ; + domain_suffix = daemon->domain_suffix; + } +#else + if ((addr.addr.addr4.s_addr = inet_addr(token)) != (in_addr_t) -1) + { + flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; + addrlen = INADDRSZ; + domain_suffix = get_domain(addr.addr.addr4); + } +#endif + else + { + my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno); + while (atnl == 0) + atnl = gettok(f, token); + continue; + } + + if (saved_flags == flags && memcmp(&addr, &saved_addr, addrlen) == 0) + addr_dup = 1; + else + { + saved_flags = flags; + saved_addr = addr; + } + + addr_count++; + + /* rehash every 1000 names. */ + if ((name_count - cache_size) > 1000) + { + rehash(name_count); + cache_size = name_count; + } + + while (atnl == 0) + { + struct crec *cache; + int fqdn, nomem; + char *canon; + + if ((atnl = gettok(f, token)) == EOF) + break; + + fqdn = !!strchr(token, '.'); + + if ((canon = canonicalise(token, &nomem))) + { + /* If set, add a version of the name with a default domain appended */ + if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && + (cache = whine_malloc(sizeof(struct crec) + + strlen(canon)+2+strlen(domain_suffix)-SMALLDNAME))) + { + strcpy(cache->name.sname, canon); + strcat(cache->name.sname, "."); + strcat(cache->name.sname, domain_suffix); + add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup); + addr_dup = 1; + name_count++; + } + if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon)+1-SMALLDNAME))) + { + strcpy(cache->name.sname, canon); + add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup); + name_count++; + } + free(canon); + + } + else if (!nomem) + my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno); + } + } + + fclose(f); + rehash(name_count); + + my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count); + + return name_count; +} + +void cache_reload(void) +{ + struct crec *cache, **up, *tmp; + int i, total_size = daemon->cachesize; + struct hostsfile *ah; + + cache_inserted = cache_live_freed = 0; + + for (i=0; i<hash_size; i++) + for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) + { + tmp = cache->hash_next; + if (cache->flags & F_HOSTS) + { + *up = cache->hash_next; + free(cache); + } + else if (!(cache->flags & F_DHCP)) + { + *up = cache->hash_next; + if (cache->flags & F_BIGNAME) + { + cache->name.bname->next = big_free; + big_free = cache->name.bname; + } + cache->flags = 0; + } + else + up = &cache->hash_next; + } + + if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts) + { + if (daemon->cachesize > 0) + my_syslog(LOG_INFO, _("cleared cache")); + return; + } + + if (!option_bool(OPT_NO_HOSTS)) + total_size = read_hostsfile(HOSTSFILE, 0, total_size); + + daemon->addn_hosts = expand_filelist(daemon->addn_hosts); + for (ah = daemon->addn_hosts; ah; ah = ah->next) + if (!(ah->flags & AH_INACTIVE)) + total_size = read_hostsfile(ah->fname, ah->index, total_size); +} + +char *get_domain(struct in_addr addr) +{ + struct cond_domain *c; + + for (c = daemon->cond_domain; c; c = c->next) + if (ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) + return c->domain; + + return daemon->domain_suffix; +} + +#ifdef HAVE_DHCP +void cache_unhash_dhcp(void) +{ + struct crec *cache, **up; + int i; + + for (i=0; i<hash_size; i++) + for (cache = hash_table[i], up = &hash_table[i]; cache; cache = cache->hash_next) + if (cache->flags & F_DHCP) + { + *up = cache->hash_next; + cache->next = dhcp_spare; + dhcp_spare = cache; + } + else + up = &cache->hash_next; +} + +void cache_add_dhcp_entry(char *host_name, + struct in_addr *host_address, time_t ttd) +{ + struct crec *crec = NULL, *aliasc; + unsigned short flags = F_NAMEP | F_DHCP | F_FORWARD | F_IPV4 | F_REVERSE; + int in_hosts = 0; + struct cname *a; + + while ((crec = cache_find_by_name(crec, host_name, 0, F_IPV4 | F_CNAME))) + { + /* check all addresses associated with name */ + if (crec->flags & F_HOSTS) + { + /* if in hosts, don't need DHCP record */ + in_hosts = 1; + + if (crec->flags & F_CNAME) + my_syslog(MS_DHCP | LOG_WARNING, + _("%s is a CNAME, not giving it to the DHCP lease of %s"), + host_name, inet_ntoa(*host_address)); + else if (crec->addr.addr.addr.addr4.s_addr != host_address->s_addr) + { + strcpy(daemon->namebuff, inet_ntoa(crec->addr.addr.addr.addr4)); + my_syslog(MS_DHCP | LOG_WARNING, + _("not giving name %s to the DHCP lease of %s because " + "the name exists in %s with address %s"), + host_name, inet_ntoa(*host_address), + record_source(crec->uid), daemon->namebuff); + } + } + else if (!(crec->flags & F_DHCP)) + { + cache_scan_free(host_name, NULL, 0, crec->flags & (F_IPV4 | F_CNAME | F_FORWARD)); + /* scan_free deletes all addresses associated with name */ + break; + } + } + + if (in_hosts) + return; + + if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, F_IPV4))) + { + if (crec->flags & F_NEG) + cache_scan_free(NULL, (struct all_addr *)host_address, 0, F_IPV4 | F_REVERSE); + else + /* avoid multiple reverse mappings */ + flags &= ~F_REVERSE; + } + + if ((crec = dhcp_spare)) + dhcp_spare = dhcp_spare->next; + else /* need new one */ + crec = whine_malloc(sizeof(struct crec)); + + if (crec) /* malloc may fail */ + { + crec->flags = flags; + if (ttd == 0) + crec->flags |= F_IMMORTAL; + else + crec->ttd = ttd; + crec->addr.addr.addr.addr4 = *host_address; + crec->name.namep = host_name; + crec->uid = uid++; + cache_hash(crec); + + for (a = daemon->cnames; a; a = a->next) + if (hostname_isequal(host_name, a->target)) + { + if ((aliasc = dhcp_spare)) + dhcp_spare = dhcp_spare->next; + else /* need new one */ + aliasc = whine_malloc(sizeof(struct crec)); + + if (aliasc) + { + aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME; + if (ttd == 0) + aliasc->flags |= F_IMMORTAL; + else + aliasc->ttd = ttd; + aliasc->name.namep = a->alias; + aliasc->addr.cname.cache = crec; + aliasc->addr.cname.uid = crec->uid; + cache_hash(aliasc); + } + } + } +} +#endif + + +void dump_cache(time_t now) +{ + struct server *serv, *serv1; + + my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now); + my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), + daemon->cachesize, cache_live_freed, cache_inserted); + my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), + daemon->queries_forwarded, daemon->local_answer); + + if (!addrbuff && !(addrbuff = whine_malloc(ADDRSTRLEN))) + return; + + /* sum counts from different records for same server */ + for (serv = daemon->servers; serv; serv = serv->next) + serv->flags &= ~SERV_COUNTED; + + for (serv = daemon->servers; serv; serv = serv->next) + if (!(serv->flags & + (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND))) + { + int port; + unsigned int queries = 0, failed_queries = 0; + for (serv1 = serv; serv1; serv1 = serv1->next) + if (!(serv1->flags & + (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) && + sockaddr_isequal(&serv->addr, &serv1->addr)) + { + serv1->flags |= SERV_COUNTED; + queries += serv1->queries; + failed_queries += serv1->failed_queries; + } + port = prettyprint_addr(&serv->addr, addrbuff); + my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), addrbuff, port, queries, failed_queries); + } + + if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG)) + { + struct crec *cache ; + int i; + my_syslog(LOG_INFO, "Host Address Flags Expires"); + + for (i=0; i<hash_size; i++) + for (cache = hash_table[i]; cache; cache = cache->hash_next) + { + char *a, *p = daemon->namebuff; + p += sprintf(p, "%-40.40s ", cache_get_name(cache)); + if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD)) + a = ""; + else if (cache->flags & F_CNAME) + { + a = ""; + if (!is_outdated_cname_pointer(cache)) + a = cache_get_name(cache->addr.cname.cache); + } +#ifdef HAVE_IPV6 + else + { + a = addrbuff; + if (cache->flags & F_IPV4) + inet_ntop(AF_INET, &cache->addr.addr, addrbuff, ADDRSTRLEN); + else if (cache->flags & F_IPV6) + inet_ntop(AF_INET6, &cache->addr.addr, addrbuff, ADDRSTRLEN); + } +#else + else + a = inet_ntoa(cache->addr.addr.addr.addr4); +#endif + p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s%s ", a, + cache->flags & F_IPV4 ? "4" : "", + cache->flags & F_IPV6 ? "6" : "", + cache->flags & F_CNAME ? "C" : "", + cache->flags & F_FORWARD ? "F" : " ", + cache->flags & F_REVERSE ? "R" : " ", + cache->flags & F_IMMORTAL ? "I" : " ", + cache->flags & F_DHCP ? "D" : " ", + cache->flags & F_NEG ? "N" : " ", + cache->flags & F_NXDOMAIN ? "X" : " ", + cache->flags & F_HOSTS ? "H" : " "); +#ifdef HAVE_BROKEN_RTC + p += sprintf(p, "%lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now)); +#else + p += sprintf(p, "%s", cache->flags & F_IMMORTAL ? "\n" : ctime(&(cache->ttd))); + /* ctime includes trailing \n - eat it */ + *(p-1) = 0; +#endif + my_syslog(LOG_INFO, daemon->namebuff); + } + } +} + +char *record_source(int index) +{ + struct hostsfile *ah; + + if (index == 0) + return HOSTSFILE; + + for (ah = daemon->addn_hosts; ah; ah = ah->next) + if (ah->index == index) + return ah->fname; + + return "<unknown>"; +} + +void querystr(char *str, unsigned short type) +{ + unsigned int i; + + sprintf(str, "query[type=%d]", type); + for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++) + if (typestr[i].type == type) + sprintf(str,"query[%s]", typestr[i].name); +} + +void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) +{ + char *source, *dest = addrbuff; + char *verb = "is"; + + if (!option_bool(OPT_LOG)) + return; + + if (addr) + { +#ifdef HAVE_IPV6 + inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, + addr, addrbuff, ADDRSTRLEN); +#else + strncpy(addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); +#endif + } + + if (flags & F_REVERSE) + { + dest = name; + name = addrbuff; + } + + if (flags & F_NEG) + { + if (flags & F_NXDOMAIN) + { + if (flags & F_IPV4) + dest = "NXDOMAIN-IPv4"; + else if (flags & F_IPV6) + dest = "NXDOMAIN-IPv6"; + else + dest = "NXDOMAIN"; + } + else + { + if (flags & F_IPV4) + dest = "NODATA-IPv4"; + else if (flags & F_IPV6) + dest = "NODATA-IPv6"; + else + dest = "NODATA"; + } + } + else if (flags & F_CNAME) + dest = "<CNAME>"; + else if (flags & F_RRNAME) + dest = arg; + + if (flags & F_CONFIG) + source = "config"; + else if (flags & F_DHCP) + source = "DHCP"; + else if (flags & F_HOSTS) + source = arg; + else if (flags & F_UPSTREAM) + source = "reply"; + else if (flags & F_SERVER) + { + source = "forwarded"; + verb = "to"; + } + else if (flags & F_QUERY) + { + source = arg; + verb = "from"; + } + else + source = "cached"; + + if (strlen(name) == 0) + name = "."; + + my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest); +} + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..0039c3e --- /dev/null +++ b/src/config.h @@ -0,0 +1,280 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +#define VERSION "2.57" + +#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 EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */ +#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 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 */ +#define MAXLEASES 1000 /* maximum number of DHCP leases */ +#define PING_WAIT 3 /* wait for ping address-in-use test */ +#define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */ +#define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */ +#define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */ +#define SMALLDNAME 40 /* most domain names are smaller than this */ +#define HOSTSFILE "/etc/hosts" +#define ETHERSFILE "/etc/ethers" +#ifdef __uClinux__ +# define RESOLVFILE "/etc/config/resolv.conf" +#else +# define RESOLVFILE "/etc/resolv.conf" +#endif +#define RUNFILE "/var/run/dnsmasq.pid" + +#ifndef LEASEFILE +# if defined(__FreeBSD__) || defined (__OpenBSD__) || defined(__DragonFly__) || defined(__NetBSD__) +# define LEASEFILE "/var/db/dnsmasq.leases" +# elif defined(__sun__) || defined (__sun) +# define LEASEFILE "/var/cache/dnsmasq.leases" +# elif defined(__ANDROID__) +# define LEASEFILE "/data/misc/dhcp/dnsmasq.leases" +# else +# define LEASEFILE "/var/lib/misc/dnsmasq.leases" +# endif +#endif + +#ifndef CONFFILE +# if defined(__FreeBSD__) +# define CONFFILE "/usr/local/etc/dnsmasq.conf" +# else +# define CONFFILE "/etc/dnsmasq.conf" +# endif +#endif + +#define DEFLEASE 3600 /* default lease time, 1 hour */ +#define CHUSER "nobody" +#define CHGRP "dip" +#define NAMESERVER_PORT 53 +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 +#define DHCP_SERVER_ALTPORT 1067 +#define DHCP_CLIENT_ALTPORT 1068 +#define PXE_PORT 4011 +#define TFTP_PORT 69 +#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */ +#define LOG_MAX 5 /* log-queue length */ +#define RANDFILE "/dev/urandom" +#define DAD_WAIT 20 /* retry binding IPv6 sockets for this long */ +#define EDNS0_OPTION_MAC 5 /* dyndns.org temporary assignment */ + +/* DBUS interface specifics */ +#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" +#define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq" + +/* Follows system specific switches. If you run on a + new system, you may want to edit these. + May replace this with Autoconf one day. + +HAVE_LINUX_NETWORK +HAVE_BSD_NETWORK +HAVE_SOLARIS_NETWORK + define exactly one of these to alter interaction with kernel networking. + +HAVE_BROKEN_RTC + define this on embedded systems which don't have an RTC + which keeps time over reboots. Causes dnsmasq to use uptime + for timing, and keep lease lengths rather than expiry times + in its leases file. This also make dnsmasq "flash disk friendly". + Normally, dnsmasq tries very hard to keep the on-disk leases file + up-to-date: rewriting it after every renewal. When HAVE_BROKEN_RTC + is in effect, the lease file is only written when a new lease is + created, or an old one destroyed. (Because those are the only times + it changes.) This vastly reduces the number of file writes, and makes + it viable to keep the lease file on a flash filesystem. + NOTE: when enabling or disabling this, be sure to delete any old + leases file, otherwise dnsmasq may get very confused. + +HAVE_TFTP + define this to get dnsmasq's built-in TFTP server. + +HAVE_DHCP + define this to get dnsmasq's DHCP server. + +HAVE_SCRIPT + define this to get the ability to call scripts on lease-change + +HAVE_GETOPT_LONG + define this if you have GNU libc or GNU getopt. + +HAVE_ARC4RANDOM + define this if you have arc4random() to get better security from DNS spoofs + by using really random ids (OpenBSD) + +HAVE_SOCKADDR_SA_LEN + define this if struct sockaddr has sa_len field (*BSD) + +HAVE_DBUS + define this if you want to link against libdbus, and have dnsmasq + support some methods to allow (re)configuration of the upstream DNS + 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. + +NOTES: + For Linux you should define + HAVE_LINUX_NETWORK + HAVE_GETOPT_LONG + you should NOT define + HAVE_ARC4RANDOM + HAVE_SOCKADDR_SA_LEN + + For *BSD systems you should define + HAVE_BSD_NETWORK + HAVE_SOCKADDR_SA_LEN + and you MAY define + HAVE_ARC4RANDOM - OpenBSD and FreeBSD and NetBSD version 2.0 or later + HAVE_GETOPT_LONG - NetBSD, later FreeBSD + (FreeBSD and OpenBSD only if you link GNU getopt) + +*/ + +/* platform independent options- uncomment to enable */ +#define HAVE_DHCP +#define HAVE_TFTP +#define HAVE_SCRIPT +/* #define HAVE_BROKEN_RTC */ +#define HAVE_DBUS +/* #define HAVE_IDN */ + +/* Allow TFTP to be disabled with COPTS=-DNO_TFTP */ +#ifdef NO_TFTP +#undef HAVE_TFTP +#endif + +/* Allow DHCP to be disabled with COPTS=-DNO_DHCP */ +#ifdef NO_DHCP +#undef HAVE_DHCP +#endif + +/* Allow scripts to be disabled with COPTS=-DNO_SCRIPT */ +#ifdef NO_SCRIPT +#undef HAVE_SCRIPT +#endif + + + +/* platform dependent options. */ + +/* Must preceed __linux__ since uClinux defines __linux__ too. */ +#if defined(__uClinux__) +#define HAVE_LINUX_NETWORK +#define HAVE_GETOPT_LONG +#undef HAVE_ARC4RANDOM +#undef HAVE_SOCKADDR_SA_LEN +/* Never use fork() on uClinux. Note that this is subtly different from the + --keep-in-foreground option, since it also suppresses forking new + processes for TCP connections and disables the call-a-script on leasechange + system. It's intended for use on MMU-less kernels. */ +#define NO_FORK + +#elif defined(__UCLIBC__) +#define HAVE_LINUX_NETWORK +#if defined(__UCLIBC_HAS_GNU_GETOPT__) || \ + ((__UCLIBC_MAJOR__==0) && (__UCLIBC_MINOR__==9) && (__UCLIBC_SUBLEVEL__<21)) +# define HAVE_GETOPT_LONG +#endif +#undef HAVE_ARC4RANDOM +#undef HAVE_SOCKADDR_SA_LEN +#if !defined(__ARCH_HAS_MMU__) && !defined(__UCLIBC_HAS_MMU__) +# define NO_FORK +#endif +#if defined(__UCLIBC_HAS_IPV6__) +# ifndef IPV6_V6ONLY +# define IPV6_V6ONLY 26 +# endif +#endif + +/* This is for glibc 2.x */ +#elif defined(__linux__) +#define HAVE_LINUX_NETWORK +#define HAVE_GETOPT_LONG +#undef HAVE_ARC4RANDOM +#undef HAVE_SOCKADDR_SA_LEN + +#elif defined(__FreeBSD__) || \ + defined(__OpenBSD__) || \ + defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) +#define HAVE_BSD_NETWORK +/* Later verions of FreeBSD have getopt_long() */ +#if defined(optional_argument) && defined(required_argument) +# define HAVE_GETOPT_LONG +#endif +#if !defined(__FreeBSD_kernel__) +# define HAVE_ARC4RANDOM +#endif +#define HAVE_SOCKADDR_SA_LEN + +#elif defined(__APPLE__) +#define HAVE_BSD_NETWORK +#define HAVE_GETOPT_LONG +#define HAVE_ARC4RANDOM +#define HAVE_SOCKADDR_SA_LEN +/* Define before sys/socket.h is included so we get socklen_t */ +#define _BSD_SOCKLEN_T_ + +#elif defined(__NetBSD__) +#define HAVE_BSD_NETWORK +#define HAVE_GETOPT_LONG +#undef HAVE_ARC4RANDOM +#define HAVE_SOCKADDR_SA_LEN + +#elif defined(__sun) || defined(__sun__) +#define HAVE_SOLARIS_NETWORK +#define HAVE_GETOPT_LONG +#undef HAVE_ARC4RANDOM +#undef HAVE_SOCKADDR_SA_LEN +#define ETHER_ADDR_LEN 6 + +#endif + +/* Decide if we're going to support IPv6 */ +/* IPv6 can be forced off with "make COPTS=-DNO_IPV6" */ +/* We assume that systems which don't have IPv6 + headers don't have ntop and pton either */ + +#if defined(INET6_ADDRSTRLEN) && defined(IPV6_V6ONLY) && !defined(NO_IPV6) +# define HAVE_IPV6 +# define ADDRSTRLEN INET6_ADDRSTRLEN +# if defined(SOL_IPV6) +# define IPV6_LEVEL SOL_IPV6 +# else +# define IPV6_LEVEL IPPROTO_IPV6 +# endif +#elif defined(INET_ADDRSTRLEN) +# undef HAVE_IPV6 +# define ADDRSTRLEN INET_ADDRSTRLEN +#else +# undef HAVE_IPV6 +# define ADDRSTRLEN 16 /* 4*3 + 3 dots + NULL */ +#endif + +/* Can't do scripts without fork */ +#ifdef NOFORK +# undef HAVE_SCRIPT +#endif + diff --git a/src/dbus.c b/src/dbus.c new file mode 100644 index 0000000..5cb43fb --- /dev/null +++ b/src/dbus.c @@ -0,0 +1,440 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_DBUS + +#include <dbus/dbus.h> + +const char* introspection_xml = +"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" +"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" +"<node name=\"" DNSMASQ_PATH "\">\n" +" <interface name=\"org.freedesktop.DBus.Introspectable\">\n" +" <method name=\"Introspect\">\n" +" <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" +" </method>\n" +" </interface>\n" +" <interface name=\"" DNSMASQ_SERVICE "\">\n" +" <method name=\"ClearCache\">\n" +" </method>\n" +" <method name=\"GetVersion\">\n" +" <arg name=\"version\" direction=\"out\" type=\"s\"/>\n" +" </method>\n" +" <method name=\"SetServers\">\n" +" <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n" +" </method>\n" +" <signal name=\"DhcpLeaseAdded\">\n" +" <arg name=\"ipaddr\" type=\"s\"/>\n" +" <arg name=\"hwaddr\" type=\"s\"/>\n" +" <arg name=\"hostname\" type=\"s\"/>\n" +" </signal>\n" +" <signal name=\"DhcpLeaseDeleted\">\n" +" <arg name=\"ipaddr\" type=\"s\"/>\n" +" <arg name=\"hwaddr\" type=\"s\"/>\n" +" <arg name=\"hostname\" type=\"s\"/>\n" +" </signal>\n" +" <signal name=\"DhcpLeaseUpdated\">\n" +" <arg name=\"ipaddr\" type=\"s\"/>\n" +" <arg name=\"hwaddr\" type=\"s\"/>\n" +" <arg name=\"hostname\" type=\"s\"/>\n" +" </signal>\n" +" </interface>\n" +"</node>\n"; + +struct watch { + DBusWatch *watch; + struct watch *next; +}; + + +static dbus_bool_t add_watch(DBusWatch *watch, void *data) +{ + struct watch *w; + + for (w = daemon->watches; w; w = w->next) + if (w->watch == watch) + return TRUE; + + if (!(w = whine_malloc(sizeof(struct watch)))) + return FALSE; + + w->watch = watch; + w->next = daemon->watches; + daemon->watches = w; + + w = data; /* no warning */ + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *data) +{ + struct watch **up, *w; + + for (up = &(daemon->watches), w = daemon->watches; w; w = w->next) + if (w->watch == watch) + { + *up = w->next; + free(w); + } + else + up = &(w->next); + + w = data; /* no warning */ +} + +static void dbus_read_servers(DBusMessage *message) +{ + struct server *serv, *tmp, **up; + DBusMessageIter iter; + union mysockaddr addr, source_addr; + char *domain; + + dbus_message_iter_init(message, &iter); + + /* mark everything from DBUS */ + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->flags & SERV_FROM_DBUS) + serv->flags |= SERV_MARK; + + while (1) + { + int skip = 0; + + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32) + { + u32 a; + + dbus_message_iter_get_basic(&iter, &a); + dbus_message_iter_next (&iter); + +#ifdef HAVE_SOCKADDR_SA_LEN + source_addr.in.sin_len = addr.in.sin_len = sizeof(struct sockaddr_in); +#endif + addr.in.sin_addr.s_addr = ntohl(a); + source_addr.in.sin_family = addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(NAMESERVER_PORT); + source_addr.in.sin_addr.s_addr = INADDR_ANY; + source_addr.in.sin_port = htons(daemon->query_port); + } + else if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_BYTE) + { + unsigned char p[sizeof(struct in6_addr)]; + unsigned int i; + + skip = 1; + + for(i = 0; i < sizeof(struct in6_addr); i++) + { + dbus_message_iter_get_basic(&iter, &p[i]); + dbus_message_iter_next (&iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) + break; + } + +#ifndef HAVE_IPV6 + my_syslog(LOG_WARNING, _("attempt to set an IPv6 server address via DBus - no IPv6 support")); +#else + if (i == sizeof(struct in6_addr)-1) + { + memcpy(&addr.in6.sin6_addr, p, sizeof(struct in6_addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(NAMESERVER_PORT); + source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0; + source_addr.in6.sin6_scope_id = addr.in6.sin6_scope_id = 0; + source_addr.in6.sin6_addr = in6addr_any; + source_addr.in6.sin6_port = htons(daemon->query_port); + skip = 0; + } +#endif + } + else + /* At the end */ + break; + + do { + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) + { + dbus_message_iter_get_basic(&iter, &domain); + dbus_message_iter_next (&iter); + } + else + domain = NULL; + + if (!skip) + { + /* See if this is already there, and unmark */ + for (serv = daemon->servers; serv; serv = serv->next) + if ((serv->flags & SERV_FROM_DBUS) && + (serv->flags & SERV_MARK)) + { + if (!(serv->flags & SERV_HAS_DOMAIN) && !domain) + { + serv->flags &= ~SERV_MARK; + break; + } + if ((serv->flags & SERV_HAS_DOMAIN) && + domain && + hostname_isequal(domain, serv->domain)) + { + serv->flags &= ~SERV_MARK; + break; + } + } + + if (!serv && (serv = whine_malloc(sizeof (struct server)))) + { + /* Not found, create a new one. */ + memset(serv, 0, sizeof(struct server)); + + if (domain) + serv->domain = whine_malloc(strlen(domain)+1); + + if (domain && !serv->domain) + { + free(serv); + serv = NULL; + } + else + { + serv->next = daemon->servers; + daemon->servers = serv; + serv->flags = SERV_FROM_DBUS; + if (domain) + { + strcpy(serv->domain, domain); + serv->flags |= SERV_HAS_DOMAIN; + } + } + } + + if (serv) + { + if (source_addr.in.sin_family == AF_INET && + addr.in.sin_addr.s_addr == 0 && + serv->domain) + serv->flags |= SERV_NO_ADDR; + else + { + serv->flags &= ~SERV_NO_ADDR; + serv->addr = addr; + serv->source_addr = source_addr; + } + } + } + } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); + } + + /* unlink and free anything still marked. */ + for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + { + tmp = serv->next; + if (serv->flags & SERV_MARK) + { + server_gone(serv); + *up = serv->next; + free(serv); + } + else + up = &serv->next; + } + +} + +DBusHandlerResult message_handler(DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + char *method = (char *)dbus_message_get_member(message); + + if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) + { + DBusMessage *reply = dbus_message_new_method_return(message); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); + } + else if (strcmp(method, "GetVersion") == 0) + { + char *v = VERSION; + DBusMessage *reply = dbus_message_new_method_return(message); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); + } + else if (strcmp(method, "SetServers") == 0) + { + my_syslog(LOG_INFO, _("setting upstream servers from DBus")); + dbus_read_servers(message); + check_servers(); + } + else if (strcmp(method, "ClearCache") == 0) + clear_cache_and_reload(dnsmasq_time()); + else + return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED); + + method = user_data; /* no warning */ + + return (DBUS_HANDLER_RESULT_HANDLED); + +} + + +/* returns NULL or error message, may fail silently if dbus daemon not yet up. */ +char *dbus_init(void) +{ + DBusConnection *connection = NULL; + DBusObjectPathVTable dnsmasq_vtable = {NULL, &message_handler, NULL, NULL, NULL, NULL }; + DBusError dbus_error; + DBusMessage *message; + + dbus_error_init (&dbus_error); + if (!(connection = dbus_bus_get (DBUS_BUS_SYSTEM, &dbus_error))) + return NULL; + + dbus_connection_set_exit_on_disconnect(connection, FALSE); + dbus_connection_set_watch_functions(connection, add_watch, remove_watch, + NULL, NULL, NULL); + dbus_error_init (&dbus_error); + dbus_bus_request_name (connection, DNSMASQ_SERVICE, 0, &dbus_error); + if (dbus_error_is_set (&dbus_error)) + return (char *)dbus_error.message; + + if (!dbus_connection_register_object_path(connection, DNSMASQ_PATH, + &dnsmasq_vtable, NULL)) + return _("could not register a DBus message handler"); + + daemon->dbus = connection; + + if ((message = dbus_message_new_signal(DNSMASQ_PATH, DNSMASQ_SERVICE, "Up"))) + { + dbus_connection_send(connection, message, NULL); + dbus_message_unref(message); + } + + return NULL; +} + + +void set_dbus_listeners(int *maxfdp, + fd_set *rset, fd_set *wset, fd_set *eset) +{ + struct watch *w; + + for (w = daemon->watches; w; w = w->next) + if (dbus_watch_get_enabled(w->watch)) + { + unsigned int flags = dbus_watch_get_flags(w->watch); + int fd = dbus_watch_get_unix_fd(w->watch); + + bump_maxfd(fd, maxfdp); + + if (flags & DBUS_WATCH_READABLE) + FD_SET(fd, rset); + + if (flags & DBUS_WATCH_WRITABLE) + FD_SET(fd, wset); + + FD_SET(fd, eset); + } +} + +void check_dbus_listeners(fd_set *rset, fd_set *wset, fd_set *eset) +{ + DBusConnection *connection = (DBusConnection *)daemon->dbus; + struct watch *w; + + for (w = daemon->watches; w; w = w->next) + if (dbus_watch_get_enabled(w->watch)) + { + unsigned int flags = 0; + int fd = dbus_watch_get_unix_fd(w->watch); + + if (FD_ISSET(fd, rset)) + flags |= DBUS_WATCH_READABLE; + + if (FD_ISSET(fd, wset)) + flags |= DBUS_WATCH_WRITABLE; + + if (FD_ISSET(fd, eset)) + flags |= DBUS_WATCH_ERROR; + + if (flags != 0) + dbus_watch_handle(w->watch, flags); + } + + if (connection) + { + dbus_connection_ref (connection); + while (dbus_connection_dispatch (connection) == DBUS_DISPATCH_DATA_REMAINS); + dbus_connection_unref (connection); + } +} + +#ifdef HAVE_DHCP +void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname) +{ + DBusConnection *connection = (DBusConnection *)daemon->dbus; + DBusMessage* message = NULL; + DBusMessageIter args; + char *action_str, *addr, *mac = daemon->namebuff; + unsigned char *p; + int i; + + if (!connection) + return; + + if (!hostname) + hostname = ""; + + p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len, + lease->hwaddr, lease->clid_len, lease->clid, &i); + print_mac(mac, p, i); + + if (action == ACTION_DEL) + action_str = "DhcpLeaseDeleted"; + else if (action == ACTION_ADD) + action_str = "DhcpLeaseAdded"; + else if (action == ACTION_OLD) + action_str = "DhcpLeaseUpdated"; + else if (action == ACTION_CONNECT) + action_str = "DhcpConnected"; + else + return; + + addr = inet_ntoa(lease->addr); + + if (!(message = dbus_message_new_signal(DNSMASQ_PATH, DNSMASQ_SERVICE, action_str))) + return; + + dbus_message_iter_init_append(message, &args); + + if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &addr) && + dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &mac) && + dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &hostname)) + dbus_connection_send(connection, message, NULL); + + dbus_message_unref(message); +} +#endif + +#endif diff --git a/src/dhcp.c b/src/dhcp.c new file mode 100644 index 0000000..29ddf24 --- /dev/null +++ b/src/dhcp.c @@ -0,0 +1,1094 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_DHCP + +struct iface_param { + struct in_addr relay, primary; + struct dhcp_context *current; + int ind; +}; + +static int complete_context(struct in_addr local, int if_index, + struct in_addr netmask, struct in_addr broadcast, void *vparam); + +static int make_fd(int port) +{ + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + struct sockaddr_in saddr; + int oneopt = 1; +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + int mtu = IP_PMTUDISC_DONT; +#endif + + if (fd == -1) + die (_("cannot create DHCP socket: %s"), NULL, EC_BADNET); + + if (!fix_fd(fd) || +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 || +#endif +#if defined(HAVE_LINUX_NETWORK) + setsockopt(fd, SOL_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 || +#else + setsockopt(fd, IPPROTO_IP, IP_RECVIF, &oneopt, sizeof(oneopt)) == -1 || +#endif + 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 + instance binding port 67. That's OK if they serve different networks. + Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */ + if (option_bool(OPT_NOWILD)) + { +#ifdef SO_REUSEPORT + int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt)); +#else + int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); +#endif + if (rc == -1) + die(_("failed to set SO_REUSE{ADDR|PORT} on DHCP socket: %s"), NULL, EC_BADNET); + } + + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); + saddr.sin_addr.s_addr = INADDR_ANY; +#ifdef HAVE_SOCKADDR_SA_LEN + saddr.sin_len = sizeof(struct sockaddr_in); +#endif + + if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in))) + die(_("failed to bind DHCP server socket: %s"), NULL, EC_BADNET); + + return fd; +} + +void dhcp_init(void) +{ +#if defined(HAVE_BSD_NETWORK) + int oneopt = 1; +#endif + + daemon->dhcpfd = make_fd(daemon->dhcp_server_port); + if (daemon->enable_pxe) + daemon->pxefd = make_fd(PXE_PORT); + else + daemon->pxefd = -1; + +#if defined(HAVE_BSD_NETWORK) + /* When we're not using capabilities, we need to do this here before + we drop root. Also, set buffer size small, to avoid wasting + kernel buffers */ + + if (option_bool(OPT_NO_PING)) + daemon->dhcp_icmp_fd = -1; + else if ((daemon->dhcp_icmp_fd = make_icmp_sock()) == -1 || + setsockopt(daemon->dhcp_icmp_fd, SOL_SOCKET, SO_RCVBUF, &oneopt, sizeof(oneopt)) == -1 ) + die(_("cannot create ICMP raw socket: %s."), NULL, EC_BADNET); + + /* Make BPF raw send socket */ + init_bpf(); +#endif + + check_dhcp_hosts(1); + + daemon->dhcp_packet.iov_len = sizeof(struct dhcp_packet); + daemon->dhcp_packet.iov_base = safe_malloc(daemon->dhcp_packet.iov_len); +} + +void dhcp_packet(time_t now, int pxe_fd) +{ + int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd; + struct dhcp_packet *mess; + struct dhcp_context *context; + struct iname *tmp; + struct ifreq ifr; + struct msghdr msg; + struct sockaddr_in dest; + struct cmsghdr *cmptr; + struct iovec iov; + ssize_t sz; + int iface_index = 0, unicast_dest = 0, is_inform = 0; + struct in_addr iface_addr, *addrp = NULL; + struct iface_param parm; +#ifdef HAVE_LINUX_NETWORK + struct arpreq arp_req; +#endif + + union { + struct cmsghdr align; /* this ensures alignment */ +#if defined(HAVE_LINUX_NETWORK) + char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#elif defined(HAVE_SOLARIS_NETWORK) + char control[CMSG_SPACE(sizeof(unsigned int))]; +#elif defined(HAVE_BSD_NETWORK) + char control[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#endif + } control_u; + + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &daemon->dhcp_packet; + msg.msg_iovlen = 1; + + while (1) + { + msg.msg_flags = 0; + while ((sz = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); + + if (sz == -1) + return; + + if (!(msg.msg_flags & MSG_TRUNC)) + break; + + /* Very new Linux kernels return the actual size needed, + older ones always return truncated size */ + if ((size_t)sz == daemon->dhcp_packet.iov_len) + { + if (!expand_buf(&daemon->dhcp_packet, sz + 100)) + return; + } + else + { + expand_buf(&daemon->dhcp_packet, sz); + break; + } + } + + /* expand_buf may have moved buffer */ + mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; + msg.msg_controllen = sizeof(control_u); + msg.msg_control = control_u.control; + msg.msg_flags = 0; + msg.msg_name = &dest; + msg.msg_namelen = sizeof(dest); + + while ((sz = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR); + + if ((msg.msg_flags & MSG_TRUNC) || sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options))) + return; + +#if defined (HAVE_LINUX_NETWORK) + if (msg.msg_controllen >= sizeof(struct cmsghdr)) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + { + union { + unsigned char *c; + struct in_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + iface_index = p.p->ipi_ifindex; + if (p.p->ipi_addr.s_addr != INADDR_BROADCAST) + unicast_dest = 1; + } + +#elif defined(HAVE_BSD_NETWORK) + 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_RECVIF) + { + union { + unsigned char *c; + struct sockaddr_dl *s; + } p; + p.c = CMSG_DATA(cmptr); + iface_index = p.s->sdl_index; + } + +#elif defined(HAVE_SOLARIS_NETWORK) + 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_RECVIF) + { + union { + unsigned char *c; + unsigned int *i; + } p; + p.c = CMSG_DATA(cmptr); + iface_index = *(p.i); + } +#endif + + if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name)) + return; + +#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); +#endif + +#ifdef MSG_BCAST + /* OpenBSD tells us when a packet was broadcast */ + if (!(msg.msg_flags & MSG_BCAST)) + unicast_dest = 1; +#endif + + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) + { + addrp = &iface_addr; + iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + } + + if (!iface_check(AF_INET, (struct all_addr *)addrp, ifr.ifr_name, &iface_index)) + return; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) + return; + + /* weird libvirt-inspired access control */ + for (context = daemon->dhcp; context; context = context->next) + if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0) + break; + + if (!context) + return; + + /* unlinked contexts are marked by context->current == context */ + for (context = daemon->dhcp; context; context = context->next) + context->current = context; + + parm.relay = mess->giaddr; + parm.primary = iface_addr; + parm.current = NULL; + parm.ind = iface_index; + + /* interface may have been changed by alias in iface_check, make sure it gets priority in case + there is more than one address on the interface in the same subnet */ + if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1) + { + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); + return; + } + else + { + iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + if (ioctl(daemon->dhcpfd, SIOCGIFNETMASK, &ifr) != -1) + { + struct in_addr netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + if (ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) != -1) + { + struct in_addr broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + complete_context(iface_addr, iface_index, netmask, broadcast, &parm); + } + } + } + + if (!iface_enumerate(AF_INET, &parm, complete_context)) + return; + 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); + lease_update_file(now); + lease_update_dns(); + + if (iov.iov_len == 0) + return; + + msg.msg_name = &dest; + msg.msg_namelen = sizeof(dest); + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_iov = &iov; + iov.iov_base = daemon->dhcp_packet.iov_base; + + /* packet buffer may have moved */ + mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; + +#ifdef HAVE_SOCKADDR_SA_LEN + dest.sin_len = sizeof(struct sockaddr_in); +#endif + + if (pxe_fd) + { + if (mess->ciaddr.s_addr != 0) + dest.sin_addr = mess->ciaddr; + } + else if (mess->giaddr.s_addr) + { + /* Send to BOOTP relay */ + dest.sin_port = htons(daemon->dhcp_server_port); + dest.sin_addr = mess->giaddr; + } + else if (mess->ciaddr.s_addr) + { + /* If the client's idea of its own address tallys with + the source address in the request packet, we believe the + source port too, and send back to that. If we're replying + to a DHCPINFORM, trust the source address always. */ + if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) || + dest.sin_port == 0 || dest.sin_addr.s_addr == 0) + { + dest.sin_port = htons(daemon->dhcp_client_port); + dest.sin_addr = mess->ciaddr; + } + } +#if defined(HAVE_LINUX_NETWORK) + else if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 || + mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0) + { + /* broadcast to 255.255.255.255 (or mac address invalid) */ + struct in_pktinfo *pkt; + msg.msg_control = control_u.control; + msg.msg_controllen = sizeof(control_u); + cmptr = CMSG_FIRSTHDR(&msg); + pkt = (struct in_pktinfo *)CMSG_DATA(cmptr); + pkt->ipi_ifindex = iface_index; + pkt->ipi_spec_dst.s_addr = 0; + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmptr->cmsg_level = SOL_IP; + cmptr->cmsg_type = IP_PKTINFO; + dest.sin_addr.s_addr = INADDR_BROADCAST; + dest.sin_port = htons(daemon->dhcp_client_port); + } + else + { + /* unicast to unconfigured client. Inject mac address direct into ARP cache. + struct sockaddr limits size to 14 bytes. */ + dest.sin_addr = mess->yiaddr; + dest.sin_port = htons(daemon->dhcp_client_port); + memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in)); + arp_req.arp_ha.sa_family = mess->htype; + memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen); + /* interface name already copied in */ + arp_req.arp_flags = ATF_COM; + ioctl(daemon->dhcpfd, SIOCSARP, &arp_req); + } +#elif defined(HAVE_SOLARIS_NETWORK) + else if ((ntohs(mess->flags) & 0x8000) || mess->hlen != ETHER_ADDR_LEN || mess->htype != ARPHRD_ETHER) + { + /* broadcast to 255.255.255.255 (or mac address invalid) */ + dest.sin_addr.s_addr = INADDR_BROADCAST; + dest.sin_port = htons(daemon->dhcp_client_port); + /* note that we don't specify the interface here: that's done by the + IP_BOUND_IF sockopt lower down. */ + } + else + { + /* unicast to unconfigured client. Inject mac address direct into ARP cache. + Note that this only works for ethernet on solaris, because we use SIOCSARP + and not SIOCSXARP, which would be perfect, except that it returns ENXIO + mysteriously. Bah. Fall back to broadcast for other net types. */ + struct arpreq req; + dest.sin_addr = mess->yiaddr; + dest.sin_port = htons(daemon->dhcp_client_port); + *((struct sockaddr_in *)&req.arp_pa) = dest; + req.arp_ha.sa_family = AF_UNSPEC; + memcpy(req.arp_ha.sa_data, mess->chaddr, mess->hlen); + req.arp_flags = ATF_COM; + ioctl(daemon->dhcpfd, SIOCSARP, &req); + } +#elif defined(HAVE_BSD_NETWORK) + else + { + send_via_bpf(mess, iov.iov_len, iface_addr, &ifr); + return; + } +#endif + +#ifdef HAVE_SOLARIS_NETWORK + setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index)); +#endif + + while(sendmsg(fd, &msg, 0) == -1 && retry_send()); +} + +/* This is a complex routine: it gets called with each (address,netmask,broadcast) triple + of each interface (and any relay address) and does the following things: + + 1) Discards stuff for interfaces other than the one on which a DHCP packet just arrived. + 2) Fills in any netmask and broadcast addresses which have not been explicitly configured. + 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. */ + +static int complete_context(struct in_addr local, int if_index, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + struct dhcp_context *context; + struct iface_param *param = vparam; + + for (context = daemon->dhcp; context; context = context->next) + { + if (!(context->flags & CONTEXT_NETMASK) && + (is_same_net(local, context->start, netmask) || + is_same_net(local, context->end, netmask))) + { + if (context->netmask.s_addr != netmask.s_addr && + !(is_same_net(local, context->start, netmask) && + is_same_net(local, context->end, netmask))) + { + strcpy(daemon->dhcp_buff, inet_ntoa(context->start)); + strcpy(daemon->dhcp_buff2, inet_ntoa(context->end)); + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"), + daemon->dhcp_buff, daemon->dhcp_buff2, inet_ntoa(netmask)); + } + context->netmask = netmask; + } + + if (context->netmask.s_addr) + { + if (is_same_net(local, context->start, context->netmask) && + is_same_net(local, context->end, context->netmask)) + { + /* link it onto the current chain if we've not seen it before */ + if (if_index == param->ind && context->current == context) + { + context->router = local; + context->local = local; + context->current = param->current; + param->current = context; + } + + if (!(context->flags & CONTEXT_BRDCAST)) + { + if (is_same_net(broadcast, context->start, context->netmask)) + context->broadcast = broadcast; + else + context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; + } + } + else if (param->relay.s_addr && is_same_net(param->relay, context->start, context->netmask)) + { + context->router = param->relay; + context->local = param->primary; + /* fill in missing broadcast addresses for relayed ranges */ + if (!(context->flags & CONTEXT_BRDCAST)) + context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; + } + + } + } + + return 1; +} + +struct dhcp_context *address_available(struct dhcp_context *context, + struct in_addr taddr, + struct dhcp_netid *netids) +{ + /* Check is an address is OK for this network, check all + possible ranges. Make sure that the address isn't in use + by the server itself. */ + + unsigned int start, end, addr = ntohl(taddr.s_addr); + struct dhcp_context *tmp; + + for (tmp = context; tmp; tmp = tmp->current) + if (taddr.s_addr == context->router.s_addr) + return NULL; + + for (tmp = context; tmp; tmp = tmp->current) + { + start = ntohl(tmp->start.s_addr); + end = ntohl(tmp->end.s_addr); + + if (!(tmp->flags & CONTEXT_STATIC) && + addr >= start && + addr <= end && + match_netid(tmp->filter, netids, 1)) + return tmp; + } + + return NULL; +} + +struct dhcp_context *narrow_context(struct dhcp_context *context, + struct in_addr taddr, + struct dhcp_netid *netids) +{ + /* 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 + 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 + configuration: maybe there should be a warning.) */ + + struct dhcp_context *tmp; + + if (!(tmp = address_available(context, taddr, netids))) + { + for (tmp = context; tmp; tmp = tmp->current) + if (match_netid(tmp->filter, netids, 1) && + is_same_net(taddr, tmp->start, tmp->netmask) && + (tmp->flags & CONTEXT_STATIC)) + break; + + if (!tmp) + for (tmp = context; tmp; tmp = tmp->current) + if (match_netid(tmp->filter, netids, 1) && + is_same_net(taddr, tmp->start, tmp->netmask)) + break; + } + + /* Only one context allowed now */ + if (tmp) + tmp->current = NULL; + + return tmp; +} + +struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr) +{ + struct dhcp_config *config; + + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr) + return config; + + return NULL; +} + +/* Is every member of check matched by a member of pool? + If tagnotneeded, untagged is OK */ +int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded) +{ + struct dhcp_netid *tmp1; + + if (!check && !tagnotneeded) + return 0; + + for (; check; check = check->next) + { + /* '#' for not is for backwards compat. */ + if (check->net[0] != '!' && check->net[0] != '#') + { + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (strcmp(check->net, tmp1->net) == 0) + break; + if (!tmp1) + return 0; + } + else + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (strcmp((check->net)+1, tmp1->net) == 0) + return 0; + } + return 1; +} + +struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) +{ + struct tag_if *exprs; + struct dhcp_netid_list *list; + + for (exprs = daemon->tag_if; exprs; exprs = exprs->next) + if (match_netid(exprs->tag, tags, 1)) + for (list = exprs->set; list; list = list->next) + { + list->list->next = tags; + tags = list->list; + } + + return tags; +} + +int address_allocate(struct dhcp_context *context, + struct in_addr *addrp, unsigned char *hwaddr, int hw_len, + struct dhcp_netid *netids, time_t now) +{ + /* Find a free address: exclude anything in use and anything allocated to + a particular hwaddr/clientid/hostname in our configuration. + Try to return from contexts which match netids first. */ + + struct in_addr start, addr; + struct dhcp_context *c, *d; + int i, pass; + unsigned int j; + + /* 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; + + for (pass = 0; pass <= 1; pass++) + for (c = context; c; c = c->current) + if (c->flags & CONTEXT_STATIC) + continue; + else if (!match_netid(c->filter, netids, pass)) + continue; + else + { + /* pick a seed based on hwaddr then iterate until we find a free address. */ + start.s_addr = addr.s_addr = + htonl(ntohl(c->start.s_addr) + + ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr)))); + + do { + /* eliminate addresses in use by the server. */ + for (d = context; d; d = d->current) + if (addr.s_addr == d->router.s_addr) + break; + + /* Addresses which end in .255 and .0 are broken in Windows even when using + supernetting. ie dhcp-range=192.168.0.1,192.168.1.254,255,255,254.0 + then 192.168.0.255 is a valid IP address, but not for Windows as it's + in the class C range. See KB281579. We therefore don't allocate these + addresses to avoid hard-to-diagnose problems. Thanks Bill. */ + if (!d && + !lease_find_by_addr(addr) && + !config_find_by_address(daemon->dhcp_conf, addr) && + (!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))); + + *addrp = addr; + + if (option_bool(OPT_NO_PING)) + return 1; + + /* 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 if (++count == max || r->addr.s_addr == addr.s_addr) + return 1; + + if (icmp_ping(addr)) + /* address in use: perturb address selection so that we are + less likely to try this address again. */ + c->addr_epoch++; + 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; + } + return 1; + } + } + + addr.s_addr = htonl(ntohl(addr.s_addr) + 1); + + if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) + addr = c->start; + + } while (addr.s_addr != start.s_addr); + } + return 0; +} + +static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config *config) +{ + if (!context) /* called via find_config() from lease_update_from_configs() */ + return 1; + if (!(config->flags & CONFIG_ADDR)) + return 1; + for (; context; context = context->current) + if (is_same_net(config->addr, context->start, context->netmask)) + return 1; + + return 0; +} + +int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type) +{ + struct hwaddr_config *conf_addr; + + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask == 0 && + conf_addr->hwaddr_len == len && + (conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) && + memcmp(conf_addr->hwaddr, hwaddr, len) == 0) + return 1; + + return 0; +} + +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname) +{ + int count, new; + struct dhcp_config *config, *candidate; + struct hwaddr_config *conf_addr; + + if (clid) + for (config = configs; config; config = config->next) + if (config->flags & CONFIG_CLID) + { + if (config->clid_len == clid_len && + memcmp(config->clid, clid, clid_len) == 0 && + is_addr_in_context(context, config)) + return config; + + /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and + cope with that here */ + if (*clid == 0 && config->clid_len == clid_len-1 && + memcmp(config->clid, clid+1, clid_len-1) == 0 && + is_addr_in_context(context, config)) + return config; + } + + + for (config = configs; config; config = config->next) + if (config_has_mac(config, hwaddr, hw_len, hw_type) && + is_addr_in_context(context, config)) + return config; + + if (hostname && context) + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_NAME) && + hostname_isequal(config->hostname, hostname) && + is_addr_in_context(context, config)) + return config; + + /* use match with fewest wildcast octets */ + for (candidate = NULL, count = 0, config = configs; config; config = config->next) + if (is_addr_in_context(context, config)) + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask != 0 && + conf_addr->hwaddr_len == hw_len && + (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && + (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) + { + count = new; + candidate = config; + } + + return candidate; +} + +void dhcp_read_ethers(void) +{ + FILE *f = fopen(ETHERSFILE, "r"); + unsigned int flags; + char *buff = daemon->namebuff; + char *ip, *cp; + struct in_addr addr; + unsigned char hwaddr[ETHER_ADDR_LEN]; + struct dhcp_config **up, *tmp; + struct dhcp_config *config; + int count = 0, lineno = 0; + + addr.s_addr = 0; /* eliminate warning */ + + if (!f) + { + my_syslog(MS_DHCP | LOG_ERR, _("failed to read %s: %s"), ETHERSFILE, strerror(errno)); + return; + } + + /* This can be called again on SIGHUP, so remove entries created last time round. */ + for (up = &daemon->dhcp_conf, config = daemon->dhcp_conf; config; config = tmp) + { + tmp = config->next; + if (config->flags & CONFIG_FROM_ETHERS) + { + *up = tmp; + /* cannot have a clid */ + if (config->flags & CONFIG_NAME) + free(config->hostname); + free(config->hwaddr); + free(config); + } + else + up = &config->next; + } + + while (fgets(buff, MAXDNAME, f)) + { + char *host = NULL; + + lineno++; + + while (strlen(buff) > 0 && isspace((int)buff[strlen(buff)-1])) + buff[strlen(buff)-1] = 0; + + if ((*buff == '#') || (*buff == '+') || (*buff == 0)) + continue; + + for (ip = buff; *ip && !isspace((int)*ip); ip++); + for(; *ip && isspace((int)*ip); ip++) + *ip = 0; + if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN) + { + my_syslog(MS_DHCP | LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno); + continue; + } + + /* check for name or dotted-quad */ + for (cp = ip; *cp; cp++) + if (!(*cp == '.' || (*cp >='0' && *cp <= '9'))) + break; + + if (!*cp) + { + if ((addr.s_addr = inet_addr(ip)) == (in_addr_t)-1) + { + my_syslog(MS_DHCP | LOG_ERR, _("bad address at %s line %d"), ETHERSFILE, lineno); + continue; + } + + flags = CONFIG_ADDR; + + for (config = daemon->dhcp_conf; config; config = config->next) + if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr) + break; + } + else + { + int nomem; + if (!(host = canonicalise(ip, &nomem)) || !legal_hostname(host)) + { + if (!nomem) + my_syslog(MS_DHCP | LOG_ERR, _("bad name at %s line %d"), ETHERSFILE, lineno); + free(host); + continue; + } + + flags = CONFIG_NAME; + + for (config = daemon->dhcp_conf; config; config = config->next) + if ((config->flags & CONFIG_NAME) && hostname_isequal(config->hostname, host)) + break; + } + + if (config && (config->flags & CONFIG_FROM_ETHERS)) + { + my_syslog(MS_DHCP | LOG_ERR, _("ignoring %s line %d, duplicate name or IP address"), ETHERSFILE, lineno); + continue; + } + + if (!config) + { + for (config = daemon->dhcp_conf; config; config = config->next) + { + struct hwaddr_config *conf_addr = config->hwaddr; + if (conf_addr && + conf_addr->next == NULL && + conf_addr->wildcard_mask == 0 && + conf_addr->hwaddr_len == ETHER_ADDR_LEN && + (conf_addr->hwaddr_type == ARPHRD_ETHER || conf_addr->hwaddr_type == 0) && + memcmp(conf_addr->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0) + break; + } + + if (!config) + { + if (!(config = whine_malloc(sizeof(struct dhcp_config)))) + continue; + config->flags = CONFIG_FROM_ETHERS; + config->hwaddr = NULL; + config->domain = NULL; + config->netid = NULL; + config->next = daemon->dhcp_conf; + daemon->dhcp_conf = config; + } + + config->flags |= flags; + + if (flags & CONFIG_NAME) + { + config->hostname = host; + host = NULL; + } + + if (flags & CONFIG_ADDR) + config->addr = addr; + } + + config->flags |= CONFIG_NOCLID; + if (!config->hwaddr) + config->hwaddr = whine_malloc(sizeof(struct hwaddr_config)); + if (config->hwaddr) + { + memcpy(config->hwaddr->hwaddr, hwaddr, ETHER_ADDR_LEN); + config->hwaddr->hwaddr_len = ETHER_ADDR_LEN; + config->hwaddr->hwaddr_type = ARPHRD_ETHER; + config->hwaddr->wildcard_mask = 0; + config->hwaddr->next = NULL; + } + count++; + + free(host); + + } + + fclose(f); + + my_syslog(MS_DHCP | LOG_INFO, _("read %s - %d addresses"), ETHERSFILE, count); +} + +void check_dhcp_hosts(int fatal) +{ + /* If the same IP appears in more than one host config, then DISCOVER + for one of the hosts will get the address, but REQUEST will be NAKed, + since the address is reserved by the other one -> protocol loop. + Also check that FQDNs match the domain we are using. */ + + struct dhcp_config *configs, *cp; + + for (configs = daemon->dhcp_conf; configs; configs = configs->next) + { + char *domain; + + if ((configs->flags & DHOPT_BANK) || fatal) + { + for (cp = configs->next; cp; cp = cp->next) + if ((configs->flags & cp->flags & CONFIG_ADDR) && configs->addr.s_addr == cp->addr.s_addr) + { + if (fatal) + die(_("duplicate IP address %s in dhcp-config directive."), + inet_ntoa(cp->addr), EC_BADCONF); + else + my_syslog(MS_DHCP | LOG_ERR, _("duplicate IP address %s in %s."), + inet_ntoa(cp->addr), daemon->dhcp_hosts_file); + configs->flags &= ~CONFIG_ADDR; + } + + /* split off domain part */ + if ((configs->flags & CONFIG_NAME) && (domain = strip_hostname(configs->hostname))) + configs->domain = domain; + } + } +} + +void dhcp_update_configs(struct dhcp_config *configs) +{ + /* Some people like to keep all static IP addresses in /etc/hosts. + This goes through /etc/hosts and sets static addresses for any DHCP config + records which don't have an address and whose name matches. + We take care to maintain the invariant that any IP address can appear + in at most one dhcp-host. Since /etc/hosts can be re-read by SIGHUP, + restore the status-quo ante first. */ + + struct dhcp_config *config; + struct crec *crec; + + for (config = configs; config; config = config->next) + if (config->flags & CONFIG_ADDR_HOSTS) + config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS); + + + if (daemon->port != 0) + for (config = configs; config; config = config->next) + if (!(config->flags & CONFIG_ADDR) && + (config->flags & CONFIG_NAME) && + (crec = cache_find_by_name(NULL, config->hostname, 0, F_IPV4)) && + (crec->flags & F_HOSTS)) + { + if (cache_find_by_name(crec, config->hostname, 0, F_IPV4)) + { + /* use primary (first) address */ + while (crec && !(crec->flags & F_REVERSE)) + crec = cache_find_by_name(crec, config->hostname, 0, F_IPV4); + if (!crec) + continue; /* should be never */ + my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), + config->hostname, inet_ntoa(crec->addr.addr.addr.addr4)); + } + + if (config_find_by_address(configs, crec->addr.addr.addr.addr4)) + my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), + inet_ntoa(crec->addr.addr.addr.addr4), config->hostname); + else + { + config->addr = crec->addr.addr.addr.addr4; + config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; + } + } +} + +/* If we've not found a hostname any other way, try and see if there's one in /etc/hosts + for this address. If it has a domain part, that must match the set domain and + it gets stripped. The set of legal domain names is bigger than the set of legal hostnames + so check here that the domain name is legal as a hostname. + NOTE: we're only allowed to overwrite daemon->dhcp_buff if we succeed. */ +char *host_from_dns(struct in_addr addr) +{ + struct crec *lookup; + + if (daemon->port == 0) + return NULL; /* DNS disabled. */ + + lookup = cache_find_by_addr(NULL, (struct all_addr *)&addr, 0, F_IPV4); + + if (lookup && (lookup->flags & F_HOSTS)) + { + char *dot, *hostname = cache_get_name(lookup); + dot = strchr(hostname, '.'); + + if (dot && strlen(dot+1) != 0) + { + char *d2 = get_domain(addr); + if (!d2 || !hostname_isequal(dot+1, d2)) + return NULL; /* wrong domain */ + } + + if (!legal_hostname(hostname)) + return NULL; + + strncpy(daemon->dhcp_buff, hostname, 256); + daemon->dhcp_buff[255] = 0; + strip_hostname(daemon->dhcp_buff); + + return daemon->dhcp_buff; + } + + return NULL; +} + +/* return domain or NULL if none. */ +char *strip_hostname(char *hostname) +{ + char *dot = strchr(hostname, '.'); + + if (!dot) + return NULL; + + *dot = 0; /* truncate */ + if (strlen(dot+1) != 0) + return dot+1; + + return NULL; +} + +#endif + diff --git a/src/dhcp_protocol.h b/src/dhcp_protocol.h new file mode 100644 index 0000000..e09cad8 --- /dev/null +++ b/src/dhcp_protocol.h @@ -0,0 +1,91 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 +#define DHCP_COOKIE 0x63825363 + +/* The Linux in-kernel DHCP client silently ignores any packet + smaller than this. Sigh........... */ +#define MIN_PACKETSZ 300 + +#define OPTION_PAD 0 +#define OPTION_NETMASK 1 +#define OPTION_ROUTER 3 +#define OPTION_DNSSERVER 6 +#define OPTION_HOSTNAME 12 +#define OPTION_DOMAINNAME 15 +#define OPTION_BROADCAST 28 +#define OPTION_VENDOR_CLASS_OPT 43 +#define OPTION_REQUESTED_IP 50 +#define OPTION_LEASE_TIME 51 +#define OPTION_OVERLOAD 52 +#define OPTION_MESSAGE_TYPE 53 +#define OPTION_SERVER_IDENTIFIER 54 +#define OPTION_REQUESTED_OPTIONS 55 +#define OPTION_MESSAGE 56 +#define OPTION_MAXMESSAGE 57 +#define OPTION_T1 58 +#define OPTION_T2 59 +#define OPTION_VENDOR_ID 60 +#define OPTION_CLIENT_ID 61 +#define OPTION_SNAME 66 +#define OPTION_FILENAME 67 +#define OPTION_USER_CLASS 77 +#define OPTION_CLIENT_FQDN 81 +#define OPTION_AGENT_ID 82 +#define OPTION_ARCH 93 +#define OPTION_PXE_UUID 97 +#define OPTION_SUBNET_SELECT 118 +#define OPTION_DOMAIN_SEARCH 119 +#define OPTION_SIP_SERVER 120 +#define OPTION_VENDOR_IDENT 124 +#define OPTION_VENDOR_IDENT_OPT 125 +#define OPTION_END 255 + +#define SUBOPT_CIRCUIT_ID 1 +#define SUBOPT_REMOTE_ID 2 +#define SUBOPT_SUBNET_SELECT 5 /* RFC 3527 */ +#define SUBOPT_SUBSCR_ID 6 /* RFC 3393 */ +#define SUBOPT_SERVER_OR 11 /* RFC 5107 */ + +#define SUBOPT_PXE_BOOT_ITEM 71 /* PXE standard */ +#define SUBOPT_PXE_DISCOVERY 6 +#define SUBOPT_PXE_SERVERS 8 +#define SUBOPT_PXE_MENU 9 +#define SUBOPT_PXE_MENU_PROMPT 10 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +#define BRDBAND_FORUM_IANA 3561 /* Broadband forum IANA enterprise */ + +#define DHCP_CHADDR_MAX 16 + +struct dhcp_packet { + u8 op, htype, hlen, hops; + u32 xid; + u16 secs, flags; + struct in_addr ciaddr, yiaddr, siaddr, giaddr; + u8 chaddr[DHCP_CHADDR_MAX], sname[64], file[128]; + u8 options[312]; +}; diff --git a/src/dns_protocol.h b/src/dns_protocol.h new file mode 100644 index 0000000..bc18e79 --- /dev/null +++ b/src/dns_protocol.h @@ -0,0 +1,111 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 + +#define PACKETSZ 512 /* maximum packet size */ +#define MAXDNAME 1025 /* maximum presentation domain name */ +#define RRFIXEDSZ 10 /* #/bytes of fixed data in r record */ +#define MAXLABEL 63 /* maximum length of domain label */ + +#define NOERROR 0 /* no error */ +#define FORMERR 1 /* format error */ +#define SERVFAIL 2 /* server failure */ +#define NXDOMAIN 3 /* non existent domain */ +#define NOTIMP 4 /* not implemented */ +#define REFUSED 5 /* query refused */ + +#define QUERY 0 /* opcode */ + +#define C_IN 1 /* the arpa internet */ +#define C_CHAOS 3 /* for chaos net (MIT) */ +#define C_ANY 255 /* wildcard match */ + +#define T_A 1 +#define T_NS 2 +#define T_CNAME 5 +#define T_SOA 6 +#define T_PTR 12 +#define T_MX 15 +#define T_TXT 16 +#define T_SIG 24 +#define T_AAAA 28 +#define T_SRV 33 +#define T_NAPTR 35 +#define T_OPT 41 +#define T_TKEY 249 +#define T_TSIG 250 +#define T_MAILB 253 +#define T_ANY 255 + +struct dns_header { + u16 id; + u8 hb3,hb4; + u16 qdcount,ancount,nscount,arcount; +} ; + +#define HB3_QR 0x80 +#define HB3_OPCODE 0x78 +#define HB3_AA 0x04 +#define HB3_TC 0x02 +#define HB3_RD 0x01 + +#define HB4_RA 0x80 +#define HB4_AD 0x20 +#define HB4_CD 0x10 +#define HB4_RCODE 0x0f + +#define OPCODE(x) (((x)->hb3 & HB3_OPCODE) >> 3) +#define RCODE(x) ((x)->hb4 & HB4_RCODE) +#define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code + +#define GETSHORT(s, cp) { \ + unsigned char *t_cp = (unsigned char *)(cp); \ + (s) = ((u16)t_cp[0] << 8) \ + | ((u16)t_cp[1]) \ + ; \ + (cp) += 2; \ +} + +#define GETLONG(l, cp) { \ + unsigned char *t_cp = (unsigned char *)(cp); \ + (l) = ((u32)t_cp[0] << 24) \ + | ((u32)t_cp[1] << 16) \ + | ((u32)t_cp[2] << 8) \ + | ((u32)t_cp[3]) \ + ; \ + (cp) += 4; \ +} + +#define PUTSHORT(s, cp) { \ + u16 t_s = (u16)(s); \ + unsigned char *t_cp = (unsigned char *)(cp); \ + *t_cp++ = t_s >> 8; \ + *t_cp = t_s; \ + (cp) += 2; \ +} + +#define PUTLONG(l, cp) { \ + u32 t_l = (u32)(l); \ + unsigned char *t_cp = (unsigned char *)(cp); \ + *t_cp++ = t_l >> 24; \ + *t_cp++ = t_l >> 16; \ + *t_cp++ = t_l >> 8; \ + *t_cp = t_l; \ + (cp) += 4; \ +} + diff --git a/src/dnsmasq.c b/src/dnsmasq.c new file mode 100644 index 0000000..827b0dc --- /dev/null +++ b/src/dnsmasq.c @@ -0,0 +1,1325 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +struct daemon *daemon; + +static char *compile_opts = +#ifndef HAVE_IPV6 +"no-" +#endif +"IPv6 " +#ifndef HAVE_GETOPT_LONG +"no-" +#endif +"GNU-getopt " +#ifdef HAVE_BROKEN_RTC +"no-RTC " +#endif +#ifdef NO_FORK +"no-MMU " +#endif +#ifndef HAVE_DBUS +"no-" +#endif +"DBus " +#ifndef LOCALEDIR +"no-" +#endif +"I18N " +#ifndef HAVE_DHCP +"no-" +#endif +"DHCP " +#if defined(HAVE_DHCP) && !defined(HAVE_SCRIPT) +"no-scripts " +#endif +#ifndef HAVE_TFTP +"no-" +#endif +"TFTP " +#if !defined(LOCALEDIR) && !defined(HAVE_IDN) +"no-" +#endif +"IDN"; + + +static volatile pid_t pid = 0; +static volatile int pipewrite; + +static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp); +static void check_dns_listeners(fd_set *set, time_t now); +static void sig_handler(int sig); +static void async_event(int pipe, time_t now); +static void fatal_event(struct event_desc *ev); + +int main (int argc, char **argv) +{ + int bind_fallback = 0; + time_t now; + struct sigaction sigact; + struct iname *if_tmp; + int piperead, pipefd[2], err_pipe[2]; + struct passwd *ent_pw = NULL; +#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) + uid_t script_uid = 0; + gid_t script_gid = 0; +#endif + struct group *gp = NULL; + long i, max_fd = sysconf(_SC_OPEN_MAX); + char *baduser = NULL; + int log_err; +#if defined(HAVE_LINUX_NETWORK) + cap_user_header_t hdr = NULL; + cap_user_data_t data = NULL; +#endif + +#ifdef LOCALEDIR + setlocale(LC_ALL, ""); + bindtextdomain("dnsmasq", LOCALEDIR); + textdomain("dnsmasq"); +#endif + + sigact.sa_handler = sig_handler; + sigact.sa_flags = 0; + sigemptyset(&sigact.sa_mask); + sigaction(SIGUSR1, &sigact, NULL); + sigaction(SIGUSR2, &sigact, NULL); + sigaction(SIGHUP, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + sigaction(SIGALRM, &sigact, NULL); + sigaction(SIGCHLD, &sigact, NULL); + + /* ignore SIGPIPE */ + sigact.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigact, NULL); + + umask(022); /* known umask, create leases and pid files as 0644 */ + + read_opts(argc, argv, compile_opts); + + if (daemon->edns_pktsz < PACKETSZ) + daemon->edns_pktsz = PACKETSZ; + daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? + daemon->edns_pktsz : DNSMASQ_PACKETSZ; + daemon->packet = safe_malloc(daemon->packet_buff_sz); + +#ifdef HAVE_DHCP + if (!daemon->lease_file) + { + if (daemon->dhcp) + daemon->lease_file = LEASEFILE; + } +#endif + + /* Close any file descriptors we inherited apart from std{in|out|err} */ + for (i = 0; i < max_fd; i++) + if (i != STDOUT_FILENO && i != STDERR_FILENO && i != STDIN_FILENO) + close(i); + +#ifdef HAVE_LINUX_NETWORK + netlink_init(); +#elif !(defined(IP_RECVDSTADDR) && \ + defined(IP_RECVIF) && \ + defined(IP_SENDSRCADDR)) + if (!option_bool(OPT_NOWILD)) + { + bind_fallback = 1; + set_option_bool(OPT_NOWILD); + } +#endif + +#ifndef HAVE_TFTP + if (daemon->tftp_unlimited || daemon->tftp_interfaces) + die(_("TFTP server not available: set HAVE_TFTP in src/config.h"), NULL, EC_BADCONF); +#endif + +#ifdef HAVE_SOLARIS_NETWORK + if (daemon->max_logs != 0) + die(_("asychronous 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); +#endif + + rand_init(); + + now = dnsmasq_time(); + +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + /* 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. */ + lease_init(now); + dhcp_init(); + } +#endif + + if (!enumerate_interfaces()) + die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); + + if (option_bool(OPT_NOWILD)) + { + daemon->listeners = create_bound_listeners(); + + for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) + if (if_tmp->name && !if_tmp->used) + die(_("unknown interface %s"), if_tmp->name, EC_BADNET); + + for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next) + if (!if_tmp->used) + { + prettyprint_addr(&if_tmp->addr, daemon->namebuff); + die(_("no interface with address %s"), daemon->namebuff, EC_BADNET); + } + } + else + daemon->listeners = create_wildcard_listeners(); + + if (daemon->port != 0) + cache_init(); + + if (option_bool(OPT_DBUS)) +#ifdef HAVE_DBUS + { + char *err; + daemon->dbus = NULL; + daemon->watches = NULL; + if ((err = dbus_init())) + die(_("DBus error: %s"), err, EC_MISC); + } +#else + die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL, EC_BADCONF); +#endif + + if (daemon->port != 0) + pre_allocate_sfds(); + +#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) + /* Note getpwnam returns static storage */ + if (daemon->dhcp && daemon->lease_change_command && daemon->scriptuser) + { + if ((ent_pw = getpwnam(daemon->scriptuser))) + { + script_uid = ent_pw->pw_uid; + script_gid = ent_pw->pw_gid; + } + else + baduser = daemon->scriptuser; + } +#endif + + if (daemon->username && !(ent_pw = getpwnam(daemon->username))) + baduser = daemon->username; + else if (daemon->groupname && !(gp = getgrnam(daemon->groupname))) + baduser = daemon->groupname; + + if (baduser) + die(_("unknown user or group: %s"), baduser, EC_BADCONF); + + /* implement group defaults, "dip" if available, or group associated with uid */ + if (!daemon->group_set && !gp) + { + if (!(gp = getgrnam(CHGRP)) && ent_pw) + gp = getgrgid(ent_pw->pw_gid); + + /* for error message */ + if (gp) + daemon->groupname = gp->gr_name; + } + +#if defined(HAVE_LINUX_NETWORK) + /* determine capability API version here, while we can still + call safe_malloc */ + if (ent_pw && ent_pw->pw_uid != 0) + { + int capsize = 1; /* for header version 1 */ + hdr = safe_malloc(sizeof(*hdr)); + + /* find version supported by kernel */ + memset(hdr, 0, sizeof(*hdr)); + capget(hdr, NULL); + + if (hdr->version != LINUX_CAPABILITY_VERSION_1) + { + /* if unknown version, use largest supported version (3) */ + if (hdr->version != LINUX_CAPABILITY_VERSION_2) + hdr->version = LINUX_CAPABILITY_VERSION_3; + capsize = 2; + } + + data = safe_malloc(sizeof(*data) * capsize); + memset(data, 0, sizeof(*data) * capsize); + } +#endif + + /* Use a pipe to carry signals and other events back to the event loop + in a race-free manner and another to carry errors to daemon-invoking process */ + safe_pipe(pipefd, 1); + + piperead = pipefd[0]; + pipewrite = pipefd[1]; + /* prime the pipe to load stuff first time. */ + send_event(pipewrite, EVENT_RELOAD, 0); + + err_pipe[1] = -1; + + if (!option_bool(OPT_DEBUG)) + { + /* The following code "daemonizes" the process. + See Stevens section 12.4 */ + + if (chdir("/") != 0) + die(_("cannot chdir to filesystem root: %s"), NULL, EC_MISC); + +#ifndef NO_FORK + if (!option_bool(OPT_NO_FORK)) + { + pid_t pid; + + /* pipe to carry errors back to original process. + When startup is complete we close this and the process terminates. */ + safe_pipe(err_pipe, 0); + + if ((pid = fork()) == -1) + /* fd == -1 since we've not forked, never returns. */ + send_event(-1, EVENT_FORK_ERR, errno); + + if (pid != 0) + { + struct event_desc ev; + + /* close our copy of write-end */ + close(err_pipe[1]); + + /* check for errors after the fork */ + if (read_write(err_pipe[0], (unsigned char *)&ev, sizeof(ev), 1)) + fatal_event(&ev); + + _exit(EC_GOOD); + } + + close(err_pipe[0]); + + /* NO calls to die() from here on. */ + + setsid(); + + if ((pid = fork()) == -1) + send_event(err_pipe[1], EVENT_FORK_ERR, errno); + + if (pid != 0) + _exit(0); + } +#endif + + /* write pidfile _after_ forking ! */ + if (daemon->runfile) + { + FILE *pidfile; + + /* only complain if started as root */ + if ((pidfile = fopen(daemon->runfile, "w"))) + { + fprintf(pidfile, "%d\n", (int) getpid()); + fclose(pidfile); + } + else if (getuid() == 0) + { + send_event(err_pipe[1], EVENT_PIDFILE, errno); + _exit(0); + } + } + } + + log_err = log_start(ent_pw, err_pipe[1]); + + if (!option_bool(OPT_DEBUG)) + { + /* 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 we are to run scripts, we need to fork a helper before dropping root. */ + daemon->helperfd = -1; +#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) + if (daemon->dhcp && daemon->lease_change_command) + daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd); +#endif + + if (!option_bool(OPT_DEBUG) && getuid() == 0) + { + int bad_capabilities = 0; + gid_t dummy; + + /* remove all supplimentary groups */ + if (gp && + (setgroups(0, &dummy) == -1 || + setgid(gp->gr_gid) == -1)) + { + send_event(err_pipe[1], EVENT_GROUP_ERR, errno); + _exit(0); + } + + if (ent_pw && ent_pw->pw_uid != 0) + { +#if defined(HAVE_LINUX_NETWORK) + /* On linux, we keep CAP_NETADMIN (for ARP-injection) and + CAP_NET_RAW (for icmp) if we're doing dhcp */ + data->effective = data->permitted = data->inheritable = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID); + + /* Tell kernel to not clear capabilities when dropping root */ + if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) + bad_capabilities = errno; + +#elif defined(HAVE_SOLARIS_NETWORK) + /* http://developers.sun.com/solaris/articles/program_privileges.html */ + priv_set_t *priv_set; + + if (!(priv_set = priv_str_to_set("basic", ",", NULL)) || + priv_addset(priv_set, PRIV_NET_ICMPACCESS) == -1 || + priv_addset(priv_set, PRIV_SYS_NET_CONFIG) == -1) + bad_capabilities = errno; + + if (priv_set && bad_capabilities == 0) + { + priv_inverse(priv_set); + + if (setppriv(PRIV_OFF, PRIV_LIMIT, priv_set) == -1) + bad_capabilities = errno; + } + + if (priv_set) + priv_freeset(priv_set); + +#endif + + if (bad_capabilities != 0) + { + send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities); + _exit(0); + } + + /* finally drop root */ + if (setuid(ent_pw->pw_uid) == -1) + { + send_event(err_pipe[1], EVENT_USER_ERR, errno); + _exit(0); + } + +#ifdef HAVE_LINUX_NETWORK + data->effective = data->permitted = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); + data->inheritable = 0; + + /* lose the setuid and setgid capbilities */ + if (capset(hdr, data) == -1) + { + send_event(err_pipe[1], EVENT_CAP_ERR, errno); + _exit(0); + } +#endif + + } + } + +#ifdef HAVE_LINUX_NETWORK + if (option_bool(OPT_DEBUG)) + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); +#endif + + 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); + + my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts); + +#ifdef HAVE_DBUS + if (option_bool(OPT_DBUS)) + { + if (daemon->dbus) + my_syslog(LOG_INFO, _("DBus support enabled: connected to system bus")); + else + my_syslog(LOG_INFO, _("DBus support enabled: bus connection pending")); + } +#endif + + if (log_err != 0) + my_syslog(LOG_WARNING, _("warning: failed to change owner of %s: %s"), + daemon->log_file, strerror(log_err)); + + if (bind_fallback) + my_syslog(LOG_WARNING, _("setting --bind-interfaces option because of OS limitations")); + + if (!option_bool(OPT_NOWILD)) + for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) + if (if_tmp->name && !if_tmp->used) + my_syslog(LOG_WARNING, _("warning: interface %s does not currently exist"), if_tmp->name); + + if (daemon->port != 0 && option_bool(OPT_NO_RESOLV)) + { + if (daemon->resolv_files && !daemon->resolv_files->is_default) + my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set")); + daemon->resolv_files = NULL; + if (!daemon->servers) + my_syslog(LOG_WARNING, _("warning: no upstream servers configured")); + } + + if (daemon->max_logs != 0) + my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs); + +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + struct dhcp_context *dhcp_tmp; + + for (dhcp_tmp = daemon->dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next) + { + prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time); + strcpy(daemon->dhcp_buff, inet_ntoa(dhcp_tmp->start)); + my_syslog(MS_DHCP | LOG_INFO, + (dhcp_tmp->flags & CONTEXT_STATIC) ? + _("DHCP, static leases only on %.0s%s, lease time %s") : + (dhcp_tmp->flags & CONTEXT_PROXY) ? + _("DHCP, proxy on subnet %.0s%s%.0s") : + _("DHCP, IP range %s -- %s, lease time %s"), + daemon->dhcp_buff, inet_ntoa(dhcp_tmp->end), daemon->dhcp_buff2); + } + } +#endif + +#ifdef HAVE_TFTP + if (daemon->tftp_unlimited || daemon->tftp_interfaces) + { +#ifdef FD_SETSIZE + if (FD_SETSIZE < (unsigned)max_fd) + max_fd = FD_SETSIZE; +#endif + + my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s", + daemon->tftp_prefix ? _("root is ") : _("enabled"), + daemon->tftp_prefix ? daemon->tftp_prefix: "", + option_bool(OPT_TFTP_SECURE) ? _("secure mode") : ""); + + /* This is a guess, it assumes that for small limits, + disjoint files might be served, but for large limits, + a single file will be sent to may clients (the file only needs + one fd). */ + + max_fd -= 30; /* use other than TFTP */ + + if (max_fd < 0) + max_fd = 5; + else if (max_fd < 100) + max_fd = max_fd/2; + else + max_fd = max_fd - 20; + + /* if we have to use a limited range of ports, + that will limit the number of transfers */ + if (daemon->start_tftp_port != 0 && + daemon->end_tftp_port - daemon->start_tftp_port + 1 < max_fd) + max_fd = daemon->end_tftp_port - daemon->start_tftp_port + 1; + + if (daemon->tftp_max > max_fd) + { + daemon->tftp_max = max_fd; + my_syslog(MS_TFTP | LOG_WARNING, + _("restricting maximum simultaneous TFTP transfers to %d"), + daemon->tftp_max); + } + } +#endif + + /* finished start-up - release original process */ + if (err_pipe[1] != -1) + close(err_pipe[1]); + + if (daemon->port != 0) + check_servers(); + + pid = getpid(); + + while (1) + { + int maxfd = -1; + struct timeval t, *tp = NULL; + fd_set rset, wset, eset; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + + /* if we are out of resources, find how long we have to wait + for some to come free, we'll loop around then and restart + listening for queries */ + if ((t.tv_sec = set_dns_listeners(now, &rset, &maxfd)) != 0) + { + t.tv_usec = 0; + tp = &t; + } + + /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */ + if (daemon->tftp_trans || + (option_bool(OPT_DBUS) && !daemon->dbus)) + { + t.tv_sec = 0; + t.tv_usec = 250000; + tp = &t; + } + +#ifdef HAVE_DBUS + set_dbus_listeners(&maxfd, &rset, &wset, &eset); +#endif + +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + FD_SET(daemon->dhcpfd, &rset); + bump_maxfd(daemon->dhcpfd, &maxfd); + if (daemon->pxefd != -1) + { + FD_SET(daemon->pxefd, &rset); + bump_maxfd(daemon->pxefd, &maxfd); + } + } +#endif + +#ifdef HAVE_LINUX_NETWORK + FD_SET(daemon->netlinkfd, &rset); + bump_maxfd(daemon->netlinkfd, &maxfd); +#endif + + FD_SET(piperead, &rset); + bump_maxfd(piperead, &maxfd); + +#ifdef HAVE_DHCP +# ifdef HAVE_SCRIPT + while (helper_buf_empty() && do_script_run(now)); + + if (!helper_buf_empty()) + { + FD_SET(daemon->helperfd, &wset); + bump_maxfd(daemon->helperfd, &maxfd); + } +# else + /* need this for other side-effects */ + while (do_script_run(now)); +# endif +#endif + + /* must do this just before select(), when we know no + more calls to my_syslog() can occur */ + set_log_writer(&wset, &maxfd); + + if (select(maxfd+1, &rset, &wset, &eset, tp) < 0) + { + /* otherwise undefined after error */ + FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); + } + + now = dnsmasq_time(); + + check_log_writer(&wset); + +#ifdef HAVE_LINUX_NETWORK + if (FD_ISSET(daemon->netlinkfd, &rset)) + netlink_multicast(); +#endif + + /* Check for changes to resolv files once per second max. */ + /* Don't go silent for long periods if the clock goes backwards. */ + if (daemon->last_resolv == 0 || + difftime(now, daemon->last_resolv) > 1.0 || + difftime(now, daemon->last_resolv) < -1.0) + { + /* poll_resolv doesn't need to reload first time through, since + that's queued anyway. */ + + poll_resolv(0, daemon->last_resolv != 0, now); + daemon->last_resolv = now; + } + + if (FD_ISSET(piperead, &rset)) + async_event(piperead, now); + +#ifdef HAVE_DBUS + /* if we didn't create a DBus connection, retry now. */ + if (option_bool(OPT_DBUS) && !daemon->dbus) + { + char *err; + if ((err = dbus_init())) + my_syslog(LOG_WARNING, _("DBus error: %s"), err); + if (daemon->dbus) + my_syslog(LOG_INFO, _("connected to system DBus")); + } + check_dbus_listeners(&rset, &wset, &eset); +#endif + + check_dns_listeners(&rset, now); + +#ifdef HAVE_TFTP + check_tftp_listeners(&rset, now); +#endif + +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + if (FD_ISSET(daemon->dhcpfd, &rset)) + dhcp_packet(now, 0); + if (daemon->pxefd != -1 && FD_ISSET(daemon->pxefd, &rset)) + dhcp_packet(now, 1); + } + +# ifdef HAVE_SCRIPT + if (daemon->helperfd != -1 && FD_ISSET(daemon->helperfd, &wset)) + helper_write(); +# endif +#endif + + } +} + +static void sig_handler(int sig) +{ + if (pid == 0) + { + /* ignore anything other than TERM during startup + and in helper proc. (helper ignore TERM too) */ + if (sig == SIGTERM) + exit(EC_MISC); + } + else if (pid != getpid()) + { + /* alarm is used to kill TCP children after a fixed time. */ + if (sig == SIGALRM) + _exit(0); + } + else + { + /* master process */ + int event, errsave = errno; + + if (sig == SIGHUP) + event = EVENT_RELOAD; + else if (sig == SIGCHLD) + event = EVENT_CHILD; + else if (sig == SIGALRM) + event = EVENT_ALARM; + else if (sig == SIGTERM) + event = EVENT_TERM; + else if (sig == SIGUSR1) + event = EVENT_DUMP; + else if (sig == SIGUSR2) + event = EVENT_REOPEN; + else + return; + + send_event(pipewrite, event, 0); + errno = errsave; + } +} + +void send_event(int fd, int event, int data) +{ + struct event_desc ev; + + ev.event = event; + ev.data = data; + + /* error pipe, debug mode. */ + if (fd == -1) + fatal_event(&ev); + else + /* pipe is non-blocking and struct event_desc is smaller than + PIPE_BUF, so this either fails or writes everything */ + while (write(fd, &ev, sizeof(ev)) == -1 && errno == EINTR); +} + +static void fatal_event(struct event_desc *ev) +{ + errno = ev->data; + + switch (ev->event) + { + case EVENT_DIE: + exit(0); + + case EVENT_FORK_ERR: + die(_("cannot fork into background: %s"), NULL, EC_MISC); + + case EVENT_PIPE_ERR: + die(_("failed to create helper: %s"), NULL, EC_MISC); + + case EVENT_CAP_ERR: + die(_("setting capabilities failed: %s"), NULL, EC_MISC); + + case EVENT_USER_ERR: + case EVENT_HUSER_ERR: + die(_("failed to change user-id to %s: %s"), + ev->event == EVENT_USER_ERR ? daemon->username : daemon->scriptuser, + EC_MISC); + + case EVENT_GROUP_ERR: + die(_("failed to change group-id to %s: %s"), daemon->groupname, EC_MISC); + + case EVENT_PIDFILE: + die(_("failed to open pidfile %s: %s"), daemon->runfile, EC_FILE); + + case EVENT_LOG_ERR: + die(_("cannot open %s: %s"), daemon->log_file ? daemon->log_file : "log", EC_FILE); + } +} + +static void async_event(int pipe, time_t now) +{ + pid_t p; + struct event_desc ev; + int i; + + if (read_write(pipe, (unsigned char *)&ev, sizeof(ev), 1)) + switch (ev.event) + { + case EVENT_RELOAD: + clear_cache_and_reload(now); + if (daemon->port != 0 && daemon->resolv_files && option_bool(OPT_NO_POLL)) + { + reload_servers(daemon->resolv_files->name); + check_servers(); + } +#ifdef HAVE_DHCP + rerun_scripts(); +#endif + break; + + case EVENT_DUMP: + if (daemon->port != 0) + dump_cache(now); + break; + + case EVENT_ALARM: +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + lease_prune(NULL, now); + lease_update_file(now); + } +#endif + break; + + case EVENT_CHILD: + /* See Stevens 5.10 */ + while ((p = waitpid(-1, NULL, WNOHANG)) != 0) + if (p == -1) + { + if (errno != EINTR) + break; + } + else + for (i = 0 ; i < MAX_PROCS; i++) + if (daemon->tcp_pids[i] == p) + daemon->tcp_pids[i] = 0; + break; + + case EVENT_KILLED: + my_syslog(LOG_WARNING, _("child process killed by signal %d"), ev.data); + break; + + case EVENT_EXITED: + my_syslog(LOG_WARNING, _("child process exited with status %d"), ev.data); + break; + + case EVENT_EXEC_ERR: + my_syslog(LOG_ERR, _("failed to execute %s: %s"), + daemon->lease_change_command, strerror(ev.data)); + break; + + /* necessary for fatal errors in helper */ + case EVENT_HUSER_ERR: + case EVENT_DIE: + fatal_event(&ev); + break; + + case EVENT_REOPEN: + /* Note: this may leave TCP-handling processes with the old file still open. + Since any such process will die in CHILD_LIFETIME or probably much sooner, + we leave them logging to the old file. */ + if (daemon->log_file != NULL) + log_reopen(daemon->log_file); + 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_DHCP) && defined(HAVE_SCRIPT) + /* handle pending lease transitions */ + if (daemon->helperfd != -1) + { + /* block in writes until all done */ + if ((i = fcntl(daemon->helperfd, F_GETFL)) != -1) + fcntl(daemon->helperfd, F_SETFL, i & ~O_NONBLOCK); + do { + helper_write(); + } while (!helper_buf_empty() || do_script_run(now)); + close(daemon->helperfd); + } +#endif + + if (daemon->lease_stream) + fclose(daemon->lease_stream); + + if (daemon->runfile) + unlink(daemon->runfile); + + my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM")); + flush_log(); + exit(EC_GOOD); + } +} + +void poll_resolv(int force, int do_reload, time_t now) +{ + struct resolvc *res, *latest; + struct stat statbuf; + time_t last_change = 0; + /* There may be more than one possible file. + Go through and find the one which changed _last_. + Warn of any which can't be read. */ + + if (daemon->port == 0 || option_bool(OPT_NO_POLL)) + return; + + for (latest = NULL, res = daemon->resolv_files; res; res = res->next) + if (stat(res->name, &statbuf) == -1) + { + if (force) + { + res->mtime = 0; + continue; + } + + if (!res->logged) + my_syslog(LOG_WARNING, _("failed to access %s: %s"), res->name, strerror(errno)); + res->logged = 1; + + if (res->mtime != 0) + { + /* existing file evaporated, force selection of the latest + file even if its mtime hasn't changed since we last looked */ + poll_resolv(1, do_reload, now); + return; + } + } + else + { + res->logged = 0; + if (force || (statbuf.st_mtime != res->mtime)) + { + res->mtime = statbuf.st_mtime; + if (difftime(statbuf.st_mtime, last_change) > 0.0) + { + last_change = statbuf.st_mtime; + latest = res; + } + } + } + + if (latest) + { + static int warned = 0; + if (reload_servers(latest->name)) + { + my_syslog(LOG_INFO, _("reading %s"), latest->name); + warned = 0; + check_servers(); + if (option_bool(OPT_RELOAD) && do_reload) + clear_cache_and_reload(now); + } + else + { + latest->mtime = 0; + if (!warned) + { + my_syslog(LOG_WARNING, _("no servers found in %s, will retry"), latest->name); + warned = 1; + } + } + } +} + +void clear_cache_and_reload(time_t now) +{ + if (daemon->port != 0) + cache_reload(); + +#ifdef HAVE_DHCP + if (daemon->dhcp) + { + if (option_bool(OPT_ETHERS)) + dhcp_read_ethers(); + reread_dhcp(); + dhcp_update_configs(daemon->dhcp_conf); + check_dhcp_hosts(0); + lease_update_from_configs(); + lease_update_file(now); + lease_update_dns(); + } +#endif +} + +static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp) +{ + struct serverfd *serverfdp; + struct listener *listener; + int wait = 0, i; + +#ifdef HAVE_TFTP + int tftp = 0; + struct tftp_transfer *transfer; + for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) + { + tftp++; + FD_SET(transfer->sockfd, set); + bump_maxfd(transfer->sockfd, maxfdp); + } +#endif + + /* will we be able to get memory? */ + if (daemon->port != 0) + get_new_frec(now, &wait); + + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + { + FD_SET(serverfdp->fd, set); + bump_maxfd(serverfdp->fd, maxfdp); + } + + if (daemon->port != 0 && !daemon->osport) + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount != 0) + { + FD_SET(daemon->randomsocks[i].fd, set); + bump_maxfd(daemon->randomsocks[i].fd, maxfdp); + } + + for (listener = daemon->listeners; listener; listener = listener->next) + { + /* only listen for queries if we have resources */ + if (listener->fd != -1 && wait == 0) + { + FD_SET(listener->fd, set); + bump_maxfd(listener->fd, maxfdp); + } + + /* death of a child goes through the select loop, so + we don't need to explicitly arrange to wake up here */ + if (listener->tcpfd != -1) + for (i = 0; i < MAX_PROCS; i++) + if (daemon->tcp_pids[i] == 0) + { + FD_SET(listener->tcpfd, set); + bump_maxfd(listener->tcpfd, maxfdp); + break; + } + +#ifdef HAVE_TFTP + if (tftp <= daemon->tftp_max && listener->tftpfd != -1) + { + FD_SET(listener->tftpfd, set); + bump_maxfd(listener->tftpfd, maxfdp); + } +#endif + + } + + return wait; +} + +static void check_dns_listeners(fd_set *set, time_t now) +{ + struct serverfd *serverfdp; + struct listener *listener; + int i; + + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + if (FD_ISSET(serverfdp->fd, set)) + reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); + + if (daemon->port != 0 && !daemon->osport) + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount != 0 && + FD_ISSET(daemon->randomsocks[i].fd, set)) + reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); + + for (listener = daemon->listeners; listener; listener = listener->next) + { + if (listener->fd != -1 && FD_ISSET(listener->fd, set)) + receive_query(listener, now); + +#ifdef HAVE_TFTP + if (listener->tftpfd != -1 && FD_ISSET(listener->tftpfd, set)) + tftp_request(listener, now); +#endif + + if (listener->tcpfd != -1 && FD_ISSET(listener->tcpfd, set)) + { + int confd; + struct irec *iface = NULL; + pid_t p; + + while((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR); + + if (confd == -1) + continue; + + if (option_bool(OPT_NOWILD)) + iface = listener->iface; + else + { + union mysockaddr tcp_addr; + socklen_t tcp_len = sizeof(union mysockaddr); + /* Check for allowed interfaces when binding the wildcard address: + we do this by looking for an interface with the same address as + the local address of the TCP connection, then looking to see if that's + an allowed interface. As a side effect, we get the netmask of the + interface too, for localisation. */ + + /* interface may be new since startup */ + if (enumerate_interfaces() && + getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) != -1) + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&iface->addr, &tcp_addr)) + break; + } + + if (!iface) + { + shutdown(confd, SHUT_RDWR); + close(confd); + } +#ifndef NO_FORK + else if (!option_bool(OPT_DEBUG) && (p = fork()) != 0) + { + if (p != -1) + { + int i; + for (i = 0; i < MAX_PROCS; i++) + if (daemon->tcp_pids[i] == 0) + { + daemon->tcp_pids[i] = p; + break; + } + } + close(confd); + } +#endif + else + { + unsigned char *buff; + struct server *s; + int flags; + struct in_addr dst_addr_4; + + dst_addr_4.s_addr = 0; + +#ifndef NO_FORK + /* Arrange for SIGALARM after CHILD_LIFETIME seconds to + terminate the process. */ + if (!option_bool(OPT_DEBUG)) + alarm(CHILD_LIFETIME); +#endif + + /* start with no upstream connections. */ + for (s = daemon->servers; s; s = s->next) + s->tcpfd = -1; + + /* The connected socket inherits non-blocking + attribute from the listening socket. + Reset that here. */ + if ((flags = fcntl(confd, F_GETFL, 0)) != -1) + fcntl(confd, F_SETFL, flags & ~O_NONBLOCK); + + if (listener->family == AF_INET) + dst_addr_4 = iface->addr.in.sin_addr; + + buff = tcp_request(confd, now, dst_addr_4, iface->netmask); + + shutdown(confd, SHUT_RDWR); + close(confd); + + if (buff) + free(buff); + + for (s = daemon->servers; s; s = s->next) + if (s->tcpfd != -1) + { + shutdown(s->tcpfd, SHUT_RDWR); + close(s->tcpfd); + } +#ifndef NO_FORK + if (!option_bool(OPT_DEBUG)) + { + flush_log(); + _exit(0); + } +#endif + } + } + } +} + +#ifdef HAVE_DHCP +int make_icmp_sock(void) +{ + int fd; + int zeroopt = 0; + + if ((fd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP)) != -1) + { + if (!fix_fd(fd) || + setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &zeroopt, sizeof(zeroopt)) == -1) + { + close(fd); + fd = -1; + } + } + + return fd; +} + +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. */ + + int fd; + struct sockaddr_in saddr; + struct { + struct ip ip; + struct icmp icmp; + } packet; + unsigned short id = rand16(); + 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) + return 0; +#else + int opt = 2000; + fd = daemon->dhcp_icmp_fd; + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +#endif + + saddr.sin_family = AF_INET; + saddr.sin_port = 0; + saddr.sin_addr = addr; +#ifdef HAVE_SOCKADDR_SA_LEN + saddr.sin_len = sizeof(struct sockaddr_in); +#endif + + memset(&packet.icmp, 0, sizeof(packet.icmp)); + packet.icmp.icmp_type = ICMP_ECHO; + packet.icmp.icmp_id = id; + for (j = 0, i = 0; i < sizeof(struct icmp) / 2; i++) + j += ((u16 *)&packet.icmp)[i]; + while (j>>16) + j = (j & 0xffff) + (j >> 16); + packet.icmp.icmp_cksum = (j == 0xffff) ? j : ~j; + + while (sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, + (struct sockaddr *)&saddr, sizeof(saddr)) == -1 && + retry_send()); + + for (now = start = dnsmasq_time(); + difftime(now, start) < (float)PING_WAIT;) + { + struct timeval tv; + fd_set rset, wset; + struct sockaddr_in faddr; + int maxfd = fd; + socklen_t len = sizeof(faddr); + + tv.tv_usec = 250000; + tv.tv_sec = 0; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_SET(fd, &rset); + set_dns_listeners(now, &rset, &maxfd); + set_log_writer(&wset, &maxfd); + + if (select(maxfd+1, &rset, &wset, NULL, &tv) < 0) + { + FD_ZERO(&rset); + FD_ZERO(&wset); + } + + now = dnsmasq_time(); + + check_log_writer(&wset); + check_dns_listeners(&rset, now); + +#ifdef HAVE_TFTP + check_tftp_listeners(&rset, now); +#endif + + if (FD_ISSET(fd, &rset) && + 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 defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK) + close(fd); +#else + opt = 1; + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +#endif + + return gotreply; +} +#endif + + diff --git a/src/dnsmasq.h b/src/dnsmasq.h new file mode 100644 index 0000000..66c602e --- /dev/null +++ b/src/dnsmasq.h @@ -0,0 +1,941 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +#define COPYRIGHT "Copyright (c) 2000-2011 Simon Kelley" + +#ifndef NO_LARGEFILE +/* Ensure we can use files >2GB (log files may grow this big) */ +# define _LARGEFILE_SOURCE 1 +# define _FILE_OFFSET_BITS 64 +#endif + +/* Get linux C library versions and define _GNU_SOURCE for kFreeBSD. */ +#if defined(__linux__) || defined(__GLIBC__) +# ifndef __ANDROID__ +# define _GNU_SOURCE +# endif +# include <features.h> +#endif + +/* Need these defined early */ +#if defined(__sun) || defined(__sun__) +# define _XPG4_2 +# define __EXTENSIONS__ +#endif + +/* get these before config.h for IPv6 stuff... */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +/* and this. */ +#include <getopt.h> + +#include "config.h" + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +#include "dns_protocol.h" +#include "dhcp_protocol.h" + +#define gettext_noop(S) (S) +#ifndef LOCALEDIR +# define _(S) (S) +#else +# include <libintl.h> +# include <locale.h> +# define _(S) gettext(S) +#endif + +#include <arpa/inet.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#if defined(HAVE_SOLARIS_NETWORK) +# include <sys/sockio.h> +#endif +#include <sys/select.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/un.h> +#include <limits.h> +#include <net/if.h> +#if defined(HAVE_SOLARIS_NETWORK) && !defined(ifr_mtu) +/* Some solaris net/if./h omit this. */ +# define ifr_mtu ifr_ifru.ifru_metric +#endif +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <ctype.h> +#include <signal.h> +#include <stddef.h> +#include <time.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__sun__) || defined (__sun) || defined (__ANDROID__) +# include <netinet/if_ether.h> +#else +# include <net/ethernet.h> +#endif +#include <net/if_arp.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <sys/uio.h> +#include <syslog.h> +#include <dirent.h> +#ifndef HAVE_LINUX_NETWORK +# include <net/if_dl.h> +#endif + +#if defined(HAVE_LINUX_NETWORK) +#include <linux/capability.h> +/* There doesn't seem to be a universally-available + userpace 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 +#define LINUX_CAPABILITY_VERSION_2 0x20071026 +#define LINUX_CAPABILITY_VERSION_3 0x20080522 + +#include <sys/prctl.h> +#elif defined(HAVE_SOLARIS_NETWORK) +#include <priv.h> +#endif + +/* daemon is function in the C library.... */ +#define daemon dnsmasq_daemon + +/* Async event queue */ +struct event_desc { + int event, data; +}; + +#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 + +/* Exit codes. */ +#define EC_GOOD 0 +#define EC_BADCONF 1 +#define EC_BADNET 2 +#define EC_FILE 3 +#define EC_NOMEM 4 +#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))) + +#define OPT_BOGUSPRIV 0 +#define OPT_FILTER 1 +#define OPT_LOG 2 +#define OPT_SELFMX 3 +#define OPT_NO_HOSTS 4 +#define OPT_NO_POLL 5 +#define OPT_DEBUG 6 +#define OPT_ORDER 7 +#define OPT_NO_RESOLV 8 +#define OPT_EXPAND 9 +#define OPT_LOCALMX 10 +#define OPT_NO_NEG 11 +#define OPT_NODOTS_LOCAL 12 +#define OPT_NOWILD 13 +#define OPT_ETHERS 14 +#define OPT_RESOLV_DOMAIN 15 +#define OPT_NO_FORK 16 +#define OPT_AUTHORITATIVE 17 +#define OPT_LOCALISE 18 +#define OPT_DBUS 19 +#define OPT_DHCP_FQDN 20 +#define OPT_NO_PING 21 +#define OPT_LEASE_RO 22 +#define OPT_ALL_SERVERS 23 +#define OPT_RELOAD 24 +#define OPT_LOCAL_REBIND 25 +#define OPT_TFTP_SECURE 26 +#define OPT_TFTP_NOBLOCK 27 +#define OPT_LOG_OPTS 28 +#define OPT_TFTP_APREF 29 +#define OPT_NO_OVERRIDE 30 +#define OPT_NO_REBIND 31 +#define OPT_ADD_MAC 32 +#define OPT_DNSSEC 33 +#define OPT_LAST 34 + +/* 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 + +struct all_addr { + union { + struct in_addr addr4; +#ifdef HAVE_IPV6 + struct in6_addr addr6; +#endif + } addr; +}; + +struct bogus_addr { + struct in_addr addr; + struct bogus_addr *next; +}; + +/* dns doctor param */ +struct doctor { + struct in_addr in, end, out, mask; + struct doctor *next; +}; + +struct mx_srv_record { + char *name, *target; + int issrv, srvport, priority, weight; + unsigned int offset; + struct mx_srv_record *next; +}; + +struct naptr { + char *name, *replace, *regexp, *services, *flags; + unsigned int order, pref; + struct naptr *next; +}; + +struct txt_record { + char *name; + unsigned char *txt; + unsigned short class, len; + struct txt_record *next; +}; + +struct ptr_record { + char *name, *ptr; + struct ptr_record *next; +}; + +struct cname { + char *alias, *target; + struct cname *next; +}; + +struct interface_name { + char *name; /* domain name */ + char *intr; /* interface name */ + struct interface_name *next; +}; + +union bigname { + char name[MAXDNAME]; + union bigname *next; /* freelist */ +}; + +struct crec { + struct crec *next, *prev, *hash_next; + time_t ttd; /* time to die */ + int uid; + union { + struct all_addr addr; + struct { + struct crec *cache; + int uid; + } cname; + } addr; + unsigned short flags; + union { + char sname[SMALLDNAME]; + union bigname *bname; + char *namep; + } name; +}; + +#define F_IMMORTAL (1u<<0) +#define F_NAMEP (1u<<1) +#define F_REVERSE (1u<<2) +#define F_FORWARD (1u<<3) +#define F_DHCP (1u<<4) +#define F_NEG (1u<<5) +#define F_HOSTS (1u<<6) +#define F_IPV4 (1u<<7) +#define F_IPV6 (1u<<8) +#define F_BIGNAME (1u<<9) +#define F_NXDOMAIN (1u<<10) +#define F_CNAME (1u<<11) +#define F_NOERR (1u<<12) +#define F_CONFIG (1u<<13) +/* below here are only valid as args to log_query: cache + entries are limited to 16 bits */ +#define F_UPSTREAM (1u<<16) +#define F_RRNAME (1u<<17) +#define F_SERVER (1u<<18) +#define F_QUERY (1u<<19) +#define F_NSRR (1u<<20) + + +/* struct sockaddr is not large enough to hold any address, + and specifically not big enough to hold an IPv6 address. + Blech. Roll our own. */ +union mysockaddr { + struct sockaddr sa; + struct sockaddr_in in; +#if defined(HAVE_IPV6) + struct sockaddr_in6 in6; +#endif +}; + +#define SERV_FROM_RESOLV 1 /* 1 for servers from resolv, 0 for command line. */ +#define SERV_NO_ADDR 2 /* no server, this domain is local only */ +#define SERV_LITERAL_ADDRESS 4 /* addr is the answer, not the server */ +#define SERV_HAS_DOMAIN 8 /* server for one domain only */ +#define SERV_HAS_SOURCE 16 /* source address defined */ +#define SERV_FOR_NODOTS 32 /* server for names with no domain part only */ +#define SERV_WARNED_RECURSIVE 64 /* avoid warning spam */ +#define SERV_FROM_DBUS 128 /* 1 if source is DBus */ +#define SERV_MARK 256 /* for mark-and-delete */ +#define SERV_TYPE (SERV_HAS_DOMAIN | SERV_FOR_NODOTS) +#define SERV_COUNTED 512 /* workspace for log code */ +#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */ +#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */ + +struct serverfd { + int fd; + union mysockaddr source_addr; + char interface[IF_NAMESIZE+1]; + struct serverfd *next; +}; + +struct randfd { + int fd; + unsigned short refcount, family; +}; + +struct server { + union mysockaddr addr, source_addr; + char interface[IF_NAMESIZE+1]; + struct serverfd *sfd; + char *domain; /* set if this server only handles a domain. */ + int flags, tcpfd; + unsigned int queries, failed_queries; + struct server *next; +}; + +struct irec { + union mysockaddr addr; + struct in_addr netmask; /* only valid for IPv4 */ + int tftp_ok, mtu; + char *name; + struct irec *next; +}; + +struct listener { + int fd, tcpfd, tftpfd, family; + struct irec *iface; /* only valid for non-wildcard */ + struct listener *next; +}; + +/* interface and address parms from command line. */ +struct iname { + char *name; + union mysockaddr addr; + int isloop, used; + struct iname *next; +}; + +/* resolv-file parms from command-line */ +struct resolvc { + struct resolvc *next; + int is_default, logged; + time_t mtime; + char *name; +}; + +/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile */ +#define AH_DIR 1 +#define AH_INACTIVE 2 +struct hostsfile { + struct hostsfile *next; + int flags; + char *fname; + int index; /* matches to cache entries for logging */ +}; + +#define FREC_NOREBIND 1 +#define FREC_CHECKING_DISABLED 2 + +struct frec { + union mysockaddr source; + struct all_addr dest; + struct server *sentto; /* NULL means free */ + struct randfd *rfd4; +#ifdef HAVE_IPV6 + struct randfd *rfd6; +#endif + unsigned int iface; + unsigned short orig_id, new_id; + int fd, forwardall, flags; + unsigned int crc; + time_t time; + struct frec *next; +}; + +/* actions in the daemon->helper RPC */ +#define ACTION_DEL 1 +#define ACTION_OLD_HOSTNAME 2 +#define ACTION_OLD 3 +#define ACTION_ADD 4 +#define ACTION_CONNECT 5 + +struct dhcp_lease { + int clid_len; /* length of client identifier */ + unsigned char *clid; /* clientid */ + char *hostname, *fqdn; /* name from client-hostname option or config */ + char *old_hostname; /* hostname before it moved to another lease */ + char auth_name; /* hostname came from config, not from client */ + char new; /* newly created */ + char changed; /* modified */ + char aux_changed; /* CLID or expiry changed */ + time_t expires; /* lease expiry */ +#ifdef HAVE_BROKEN_RTC + unsigned int length; +#endif + int hwaddr_len, hwaddr_type; + unsigned char hwaddr[DHCP_CHADDR_MAX]; + struct in_addr addr, override, giaddr; + unsigned char *extradata; + unsigned int extradata_len, extradata_size; + int last_interface; + struct dhcp_lease *next; +}; + +struct dhcp_netid { + char *net; + struct dhcp_netid *next; +}; + +struct dhcp_netid_list { + struct dhcp_netid *list; + struct dhcp_netid_list *next; +}; + +struct tag_if { + struct dhcp_netid_list *set; + struct dhcp_netid *tag; + struct tag_if *next; +}; + +struct hwaddr_config { + int hwaddr_len, hwaddr_type; + unsigned char hwaddr[DHCP_CHADDR_MAX]; + unsigned int wildcard_mask; + struct hwaddr_config *next; +}; + +struct dhcp_config { + unsigned int flags; + int clid_len; /* length of client identifier */ + unsigned char *clid; /* clientid */ + char *hostname, *domain; + struct dhcp_netid_list *netid; + struct in_addr addr; + time_t decline_time; + unsigned int lease_time; + struct hwaddr_config *hwaddr; + struct dhcp_config *next; +}; + +#define CONFIG_DISABLE 1 +#define CONFIG_CLID 2 +#define CONFIG_TIME 8 +#define CONFIG_NAME 16 +#define CONFIG_ADDR 32 +#define CONFIG_NOCLID 128 +#define CONFIG_FROM_ETHERS 256 /* entry created by /etc/ethers */ +#define CONFIG_ADDR_HOSTS 512 /* address added by from /etc/hosts */ +#define CONFIG_DECLINED 1024 /* address declined by client */ +#define CONFIG_BANK 2048 /* from dhcp hosts file */ + +struct dhcp_opt { + int opt, len, flags; + union { + int encap; + unsigned int wildcard_mask; + unsigned char *vendor_class; + } u; + unsigned char *val; + struct dhcp_netid *netid; + struct dhcp_opt *next; +}; + +#define DHOPT_ADDR 1 +#define DHOPT_STRING 2 +#define DHOPT_ENCAPSULATE 4 +#define DHOPT_ENCAP_MATCH 8 +#define DHOPT_FORCE 16 +#define DHOPT_BANK 32 +#define DHOPT_ENCAP_DONE 64 +#define DHOPT_MATCH 128 +#define DHOPT_VENDOR 256 +#define DHOPT_HEX 512 +#define DHOPT_VENDOR_MATCH 1024 +#define DHOPT_RFC3925 2048 + +struct dhcp_boot { + char *file, *sname; + struct in_addr next_server; + struct dhcp_netid *netid; + struct dhcp_boot *next; +}; + +struct pxe_service { + unsigned short CSA, type; + char *menu, *basename; + struct in_addr server; + struct dhcp_netid *netid; + struct pxe_service *next; +}; + +#define MATCH_VENDOR 1 +#define MATCH_USER 2 +#define MATCH_CIRCUIT 3 +#define MATCH_REMOTE 4 +#define MATCH_SUBSCRIBER 5 + +/* vendorclass, userclass, remote-id or cicuit-id */ +struct dhcp_vendor { + int len, match_type, option; + char *data; + struct dhcp_netid netid; + struct dhcp_vendor *next; +}; + +struct dhcp_mac { + unsigned int mask; + int hwaddr_len, hwaddr_type; + unsigned char hwaddr[DHCP_CHADDR_MAX]; + struct dhcp_netid netid; + struct dhcp_mac *next; +}; + +struct dhcp_bridge { + char iface[IF_NAMESIZE]; + struct dhcp_bridge *alias, *next; +}; + +struct cond_domain { + char *domain; + struct in_addr start, end; + struct cond_domain *next; +}; + +struct dhcp_context { + unsigned int lease_time, addr_epoch; + struct in_addr netmask, broadcast; + struct in_addr local, router; + struct in_addr start, end; /* range of available addresses */ + int flags; + char *interface; + struct dhcp_netid netid, *filter; + struct dhcp_context *next, *current; +}; + +#define CONTEXT_STATIC 1 +#define CONTEXT_NETMASK 2 +#define CONTEXT_BRDCAST 4 +#define CONTEXT_PROXY 8 + +struct ping_result { + struct in_addr addr; + time_t time; + struct ping_result *next; +}; + +struct tftp_file { + int refcount, fd; + off_t size; + dev_t dev; + ino_t inode; + char filename[]; +}; + +struct tftp_transfer { + int sockfd; + time_t timeout; + int backoff; + unsigned int block, blocksize, expansion; + off_t offset; + union mysockaddr peer; + char opt_blocksize, opt_transize, netascii, carrylf; + struct tftp_file *file; + struct tftp_transfer *next; +}; + +struct addr_list { + struct in_addr addr; + struct addr_list *next; +}; + +struct interface_list { + char *interface; + struct interface_list *next; +}; + +struct tftp_prefix { + char *interface; + char *prefix; + struct tftp_prefix *next; +}; + + +extern struct daemon { + /* datastuctures representing the command-line and + config file arguments. All set (including defaults) + in option.c */ + + unsigned int options, options2; + struct resolvc default_resolv, *resolv_files; + time_t last_resolv; + struct mx_srv_record *mxnames; + struct naptr *naptr; + struct txt_record *txt; + struct ptr_record *ptr; + struct cname *cnames; + struct interface_name *int_names; + char *mxtarget; + char *lease_file; + char *username, *groupname, *scriptuser; + int group_set, osport; + char *domain_suffix; + struct cond_domain *cond_domain; + char *runfile; + char *lease_change_command; + struct iname *if_names, *if_addrs, *if_except, *dhcp_except; + struct bogus_addr *bogus_addr; + struct server *servers; + int log_fac; /* log facility */ + 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; + struct hostsfile *addn_hosts; + struct dhcp_context *dhcp; + struct dhcp_config *dhcp_conf; + struct dhcp_opt *dhcp_opts, *dhcp_match; + struct dhcp_vendor *dhcp_vendors; + struct dhcp_mac *dhcp_macs; + struct dhcp_boot *boot_config; + struct pxe_service *pxe_services; + struct tag_if *tag_if; + struct addr_list *override_relays; + int override; + int enable_pxe; + 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; + int dhcp_max, tftp_max; + int dhcp_server_port, dhcp_client_port; + int start_tftp_port, end_tftp_port; + unsigned int min_leasetime; + struct doctor *doctors; + unsigned short edns_pktsz; + char *tftp_prefix; + struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */ + struct interface_list *tftp_interfaces; /* interfaces for limited TFTP service */ + int tftp_unlimited; + + /* globally used stuff for DNS */ + char *packet; /* packet buffer */ + int packet_buff_sz; /* size of above */ + char *namebuff; /* MAXDNAME size buffer */ + unsigned int local_answer, queries_forwarded; + struct frec *frec_list; + struct serverfd *sfds; + struct irec *interfaces; + struct listener *listeners; + struct server *last_server; + time_t forwardtime; + int forwardcount; + struct server *srv_save; /* Used for resend on DoD */ + size_t packet_len; /* " " */ + struct randfd *rfd_save; /* " " */ + pid_t tcp_pids[MAX_PROCS]; + struct randfd randomsocks[RANDOM_SOCKS]; + int v6pktinfo; + + /* DHCP state */ + int dhcpfd, helperfd, pxefd; +#if defined(HAVE_LINUX_NETWORK) + int netlinkfd; +#elif defined(HAVE_BSD_NETWORK) + int dhcp_raw_fd, dhcp_icmp_fd; +#endif + struct iovec dhcp_packet; + char *dhcp_buff, *dhcp_buff2, *dhcp_buff3; + struct ping_result *ping_results; + FILE *lease_stream; + struct dhcp_bridge *bridges; + + /* DBus stuff */ + /* void * here to avoid depending on dbus headers outside dbus.c */ + void *dbus; +#ifdef HAVE_DBUS + struct watch *watches; +#endif + + /* TFTP stuff */ + struct tftp_transfer *tftp_trans; + +} *daemon; + +/* cache.c */ +void cache_init(void); +void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg); +char *record_source(int index); +void querystr(char *str, unsigned short type); +struct crec *cache_find_by_addr(struct crec *crecp, + struct all_addr *addr, time_t now, + unsigned short prot); +struct crec *cache_find_by_name(struct crec *crecp, + char *name, time_t now, unsigned short prot); +void cache_end_insert(void); +void cache_start_insert(void); +struct crec *cache_insert(char *name, struct all_addr *addr, + time_t now, unsigned long ttl, unsigned short flags); +void cache_reload(void); +void cache_add_dhcp_entry(char *host_name, struct in_addr *host_address, time_t ttd); +void cache_unhash_dhcp(void); +void dump_cache(time_t now); +char *cache_get_name(struct crec *crecp); +char *get_domain(struct in_addr addr); + +/* rfc1035.c */ +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, int is_sign, int checkrebind, int checking_disabled); +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 check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, + struct bogus_addr *addr, time_t now); +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); +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); + +/* util.c */ +void rand_init(void); +unsigned short rand16(void); +int legal_hostname(char *c); +char *canonicalise(char *s, int *nomem); +unsigned char *do_rfc1035_name(unsigned char *p, char *sval); +void *safe_malloc(size_t size); +void safe_pipe(int *fd, int read_noblock); +void *whine_malloc(size_t size); +int sa_len(union mysockaddr *addr); +int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); +int hostname_isequal(char *a, char *b); +time_t dnsmasq_time(void); +int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask); +int retry_send(void); +void prettyprint_time(char *buf, unsigned int t); +int prettyprint_addr(union mysockaddr *addr, char *buf); +int parse_hex(char *in, unsigned char *out, int maxlen, + unsigned int *wildcard_mask, int *mac_type); +int memcmp_masked(unsigned char *a, unsigned char *b, int len, + unsigned int mask); +int expand_buf(struct iovec *iov, size_t size); +char *print_mac(char *buff, unsigned char *mac, int len); +void bump_maxfd(int fd, int *max); +int read_write(int fd, unsigned char *packet, int size, int rw); + +/* log.c */ +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(fd_set *set, int *maxfdp); +void check_log_writer(fd_set *set); +void flush_log(void); + +/* option.c */ +void read_opts (int argc, char **argv, char *compile_opts); +char *option_string(unsigned char opt, int *is_ip, int *is_name); +void reread_dhcp(void); +void set_option_bool(unsigned int opt); +struct hostsfile *expand_filelist(struct hostsfile *list); + +/* forward.c */ +void reply_query(int fd, int family, time_t now); +void receive_query(struct listener *listen, time_t now); +unsigned char *tcp_request(int confd, time_t now, + struct in_addr local_addr, struct in_addr netmask); +void server_gone(struct server *server); +struct frec *get_new_frec(time_t now, int *wait); + +/* network.c */ +int indextoname(int fd, int index, char *name); +int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp); +int random_sock(int family); +void pre_allocate_sfds(void); +int reload_servers(char *fname); +void check_servers(void); +int enumerate_interfaces(); +struct listener *create_wildcard_listeners(void); +struct listener *create_bound_listeners(void); +int iface_check(int family, struct all_addr *addr, char *name, int *indexp); +int fix_fd(int fd); +struct in_addr get_ifaddr(char *intr); + +/* dhcp.c */ +#ifdef HAVE_DHCP +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 dhcp_netid *netids); +struct dhcp_context *narrow_context(struct dhcp_context *context, + struct in_addr taddr, + struct dhcp_netid *netids); +int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int negonly); +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 *run_tag_if(struct dhcp_netid *input); +int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type); +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname); +void dhcp_update_configs(struct dhcp_config *configs); +void dhcp_read_ethers(void); +void check_dhcp_hosts(int fatal); +struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr); +char *strip_hostname(char *hostname); +char *host_from_dns(struct in_addr addr); +char *get_domain(struct in_addr addr); +#endif + +/* lease.c */ +#ifdef HAVE_DHCP +void lease_update_file(time_t now); +void lease_update_dns(); +void lease_init(time_t now); +struct dhcp_lease *lease_allocate(struct in_addr addr); +void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, + unsigned char *clid, int hw_len, int hw_type, int clid_len); +void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth); +void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now); +void lease_set_interface(struct dhcp_lease *lease, int interface); +struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type, + unsigned char *clid, int clid_len); +struct dhcp_lease *lease_find_by_addr(struct in_addr addr); +void lease_prune(struct dhcp_lease *target, time_t now); +void lease_update_from_configs(void); +int do_script_run(time_t now); +void rerun_scripts(void); +#endif + +/* 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); +unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, + int clid_len, unsigned char *clid, int *len_out); +#endif + +/* dnsmasq.c */ +#ifdef HAVE_DHCP +int make_icmp_sock(void); +int icmp_ping(struct in_addr addr); +#endif +void send_event(int fd, int event, int data); +void clear_cache_and_reload(time_t now); +void poll_resolv(int force, int do_reload, time_t now); + +/* netlink.c */ +#ifdef HAVE_LINUX_NETWORK +void netlink_init(void); +void netlink_multicast(void); +#endif + +/* bpf.c */ +#ifdef HAVE_BSD_NETWORK +void init_bpf(void); +void send_via_bpf(struct dhcp_packet *mess, size_t len, + struct in_addr iface_addr, struct ifreq *ifr); +#endif + +/* bpf.c or netlink.c */ +int iface_enumerate(int family, void *parm, int (callback)()); + +/* dbus.c */ +#ifdef HAVE_DBUS +char *dbus_init(void); +void check_dbus_listeners(fd_set *rset, fd_set *wset, fd_set *eset); +void set_dbus_listeners(int *maxfdp, fd_set *rset, fd_set *wset, fd_set *eset); +# ifdef HAVE_DHCP +void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname); +# endif +#endif + +/* helper.c */ +#if defined(HAVE_DHCP) && !defined(NO_FORK) +int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd); +void helper_write(void); +void queue_script(int action, struct dhcp_lease *lease, + char *hostname, time_t now); +int helper_buf_empty(void); +#endif + +/* tftp.c */ +#ifdef HAVE_TFTP +void tftp_request(struct listener *listen, time_t now); +void check_tftp_listeners(fd_set *rset, time_t now); +#endif diff --git a/src/forward.c b/src/forward.c new file mode 100644 index 0000000..92bc6b0 --- /dev/null +++ b/src/forward.c @@ -0,0 +1,1182 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +static struct frec *lookup_frec(unsigned short id, unsigned int crc); +static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + unsigned int crc); +static unsigned short get_id(unsigned int crc); +static void free_frec(struct frec *f); +static struct randfd *allocate_rfd(int family); + +/* Send a UDP packet with its source address set as "source" + unless nowild is true, when we just send it with the kernel default */ +static void send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface) +{ + struct msghdr msg; + struct iovec iov[1]; + union { + struct cmsghdr align; /* this ensures alignment */ +#if defined(HAVE_LINUX_NETWORK) + char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#elif defined(IP_SENDSRCADDR) + char control[CMSG_SPACE(sizeof(struct in_addr))]; +#endif +#ifdef HAVE_IPV6 + char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#endif + } control_u; + + iov[0].iov_base = packet; + iov[0].iov_len = len; + + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + msg.msg_name = to; + msg.msg_namelen = sa_len(to); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if (!nowild) + { + struct cmsghdr *cmptr; + msg.msg_control = &control_u; + msg.msg_controllen = sizeof(control_u); + cmptr = CMSG_FIRSTHDR(&msg); + + if (to->sa.sa_family == AF_INET) + { +#if defined(HAVE_LINUX_NETWORK) + struct in_pktinfo p; + p.ipi_ifindex = 0; + p.ipi_spec_dst = source->addr.addr4; + memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmptr->cmsg_level = SOL_IP; + cmptr->cmsg_type = IP_PKTINFO; +#elif defined(IP_SENDSRCADDR) + memcpy(CMSG_DATA(cmptr), &(source->addr.addr4), sizeof(source->addr.addr4)); + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + cmptr->cmsg_level = IPPROTO_IP; + cmptr->cmsg_type = IP_SENDSRCADDR; +#endif + } + else +#ifdef HAVE_IPV6 + { + struct in6_pktinfo p; + p.ipi6_ifindex = iface; /* Need iface for IPv6 to handle link-local addrs */ + p.ipi6_addr = source->addr.addr6; + memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmptr->cmsg_type = daemon->v6pktinfo; + cmptr->cmsg_level = IPV6_LEVEL; + } +#else + iface = 0; /* eliminate warning */ +#endif + } + + retry: + if (sendmsg(fd, &msg, 0) == -1) + { + /* certain Linux kernels seem to object to setting the source address in the IPv6 stack + by returning EINVAL from sendmsg. In that case, try again without setting the + source address, since it will nearly alway be correct anyway. IPv6 stinks. */ + if (errno == EINVAL && msg.msg_controllen) + { + msg.msg_controllen = 0; + goto retry; + } + if (retry_send()) + goto retry; + } +} + +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 + domain to point to that name. We find the largest match to allow both + domain.org and sub.domain.org to exist. */ + + unsigned int namelen = strlen(qdomain); + unsigned int matchlen = 0; + struct server *serv; + unsigned int flags = 0; + + for (serv = daemon->servers; serv; serv=serv->next) + /* domain matches take priority over NODOTS matches */ + 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; + if (serv->flags & SERV_NO_ADDR) + flags = F_NXDOMAIN; + else if (serv->flags & SERV_LITERAL_ADDRESS) + { + if (sflag & qtype) + { + flags = sflag; + if (serv->addr.sa.sa_family == AF_INET) + *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; +#ifdef HAVE_IPV6 + else + *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; +#endif + } + else if (!flags || (flags & F_NXDOMAIN)) + flags = F_NOERR; + } + } + else if (serv->flags & SERV_HAS_DOMAIN) + { + unsigned int domainlen = strlen(serv->domain); + char *matchstart = qdomain + namelen - domainlen; + if (namelen >= domainlen && + hostname_isequal(matchstart, serv->domain) && + (domainlen == 0 || namelen == domainlen || *(matchstart-1) == '.' )) + { + if (serv->flags & SERV_NO_REBIND) + *norebind = 1; + else + { + unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; + /* implement priority rules for --address and --server for same domain. + --address wins if the address is for the correct AF + --server wins otherwise. */ + if (domainlen != 0 && domainlen == matchlen) + { + if ((serv->flags & SERV_LITERAL_ADDRESS)) + { + if (!(sflag & qtype) && flags == 0) + continue; + } + else + { + if (flags & (F_IPV4 | F_IPV6)) + continue; + } + } + + if (domainlen >= matchlen) + { + *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND); + *domain = serv->domain; + matchlen = domainlen; + if (serv->flags & SERV_NO_ADDR) + flags = F_NXDOMAIN; + else if (serv->flags & SERV_LITERAL_ADDRESS) + { + if (sflag & qtype) + { + flags = sflag; + if (serv->addr.sa.sa_family == AF_INET) + *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; +#ifdef HAVE_IPV6 + else + *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; +#endif + } + else if (!flags || (flags & F_NXDOMAIN)) + flags = F_NOERR; + } + else + flags = 0; + } + } + } + } + + if (flags == 0 && !(qtype & F_NSRR) && + option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0) + /* don't forward simple names, make exception for NS queries and empty name. */ + flags = F_NXDOMAIN; + + if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now)) + flags = F_NOERR; + + if (flags) + { + int logflags = 0; + + if (flags == F_NXDOMAIN || flags == F_NOERR) + logflags = F_NEG | qtype; + + log_query(logflags | flags | F_CONFIG | F_FORWARD, qdomain, *addrpp, NULL); + } + else if ((*type) & SERV_USE_RESOLV) + { + *type = 0; /* use normal servers for this domain */ + *domain = NULL; + } + return flags; +} + +static int forward_query(int udpfd, union mysockaddr *udpaddr, + struct all_addr *dst_addr, unsigned int dst_iface, + struct dns_header *header, size_t plen, time_t now, struct frec *forward) +{ + char *domain = NULL; + int type = 0, norebind = 0; + struct all_addr *addrp = NULL; + unsigned int crc = questions_crc(header, plen, daemon->namebuff); + unsigned int flags = 0; + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + struct server *start = NULL; + + /* RFC 4035: sect 4.6 para 2 */ + header->hb4 &= ~HB4_AD; + + /* may be no servers available. */ + if (!daemon->servers) + forward = NULL; + else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) + { + /* retry on existing query, send to all available servers */ + domain = forward->sentto->domain; + forward->sentto->failed_queries++; + if (!option_bool(OPT_ORDER)) + { + forward->forwardall = 1; + daemon->last_server = NULL; + } + type = forward->sentto->flags & SERV_TYPE; + if (!(start = forward->sentto->next)) + start = daemon->servers; /* at end of list, recycle */ + header->id = htons(forward->new_id); + } + else + { + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + + if (!flags && !(forward = get_new_frec(now, NULL))) + /* table full - server failure. */ + flags = F_NEG; + + if (forward) + { + forward->source = *udpaddr; + forward->dest = *dst_addr; + forward->iface = dst_iface; + forward->orig_id = ntohs(header->id); + forward->new_id = get_id(crc); + forward->fd = udpfd; + forward->crc = crc; + forward->forwardall = 0; + if (norebind) + forward->flags |= FREC_NOREBIND; + if (header->hb4 & HB4_CD) + forward->flags |= FREC_CHECKING_DISABLED; + + header->id = htons(forward->new_id); + + /* In strict_order mode, always try servers in the order + specified in resolv.conf, if a domain is given + always try all the available servers, + otherwise, use the one last known to work. */ + + if (type == 0) + { + if (option_bool(OPT_ORDER)) + start = daemon->servers; + else if (!(start = daemon->last_server) || + daemon->forwardcount++ > FORWARD_TEST || + difftime(now, daemon->forwardtime) > FORWARD_TIME) + { + start = daemon->servers; + forward->forwardall = 1; + daemon->forwardcount = 0; + daemon->forwardtime = now; + } + } + else + { + start = daemon->servers; + if (!option_bool(OPT_ORDER)) + forward->forwardall = 1; + } + } + } + + /* check for send errors here (no route to host) + if we fail to send to all nameservers, send back an error + packet straight away (helps modem users when offline) */ + + if (!flags && forward) + { + struct server *firstsentto = start; + int forwarded = 0; + + if (udpaddr && option_bool(OPT_ADD_MAC)) + plen = add_mac(header, plen, ((char *) header) + PACKETSZ, udpaddr); + + while (1) + { + /* only send to servers dealing with our domain. + domain may be NULL, in which case server->domain + must be NULL also. */ + + if (type == (start->flags & SERV_TYPE) && + (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && + !(start->flags & SERV_LITERAL_ADDRESS)) + { + int fd; + + /* find server socket to use, may need to get random one. */ + if (start->sfd) + fd = start->sfd->fd; + else + { +#ifdef HAVE_IPV6 + if (start->addr.sa.sa_family == AF_INET6) + { + if (!forward->rfd6 && + !(forward->rfd6 = allocate_rfd(AF_INET6))) + break; + daemon->rfd_save = forward->rfd6; + fd = forward->rfd6->fd; + } + else +#endif + { + if (!forward->rfd4 && + !(forward->rfd4 = allocate_rfd(AF_INET))) + break; + daemon->rfd_save = forward->rfd4; + fd = forward->rfd4->fd; + } + } + + if (sendto(fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)) == -1) + { + if (retry_send()) + continue; + } + else + { + /* Keep info in case we want to re-send this packet */ + daemon->srv_save = start; + daemon->packet_len = plen; + + if (!gotname) + strcpy(daemon->namebuff, "query"); + if (start->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&start->addr.in.sin_addr, NULL); +#ifdef HAVE_IPV6 + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&start->addr.in6.sin6_addr, NULL); +#endif + start->queries++; + forwarded = 1; + forward->sentto = start; + if (!forward->forwardall) + break; + forward->forwardall++; + } + } + + if (!(start = start->next)) + start = daemon->servers; + + if (start == firstsentto) + break; + } + + if (forwarded) + return 1; + + /* could not send on, prepare to return */ + header->id = htons(forward->orig_id); + free_frec(forward); /* cancel */ + } + + /* could not send on, return empty answer or address if known for whole domain */ + if (udpfd != -1) + { + plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); + send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface); + } + + return 0; +} + +static size_t process_reply(struct dns_header *header, time_t now, + struct server *server, size_t n, int check_rebind, int checking_disabled) +{ + unsigned char *pheader, *sizep; + int munged = 0, is_sign; + size_t plen; + + /* 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)) && !is_sign) + { + unsigned short udpsz; + unsigned char *psave = sizep; + + GETSHORT(udpsz, sizep); + if (udpsz > daemon->edns_pktsz) + PUTSHORT(daemon->edns_pktsz, psave); + } + + /* RFC 4035 sect 4.6 para 3 */ + if (!is_sign && !option_bool(OPT_DNSSEC)) + header->hb4 &= ~HB4_AD; + + if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) + return n; + + /* Complain loudly if the upstream server is non-recursive. */ + if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && ntohs(header->ancount) == 0 && + server && !(server->flags & SERV_WARNED_RECURSIVE)) + { + prettyprint_addr(&server->addr, daemon->namebuff); + my_syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff); + if (!option_bool(OPT_LOG)) + server->flags |= SERV_WARNED_RECURSIVE; + } + + if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && + check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) + { + munged = 1; + SET_RCODE(header, NXDOMAIN); + header->hb3 &= ~HB3_AA; + } + else + { + if (RCODE(header) == NXDOMAIN && + extract_request(header, n, daemon->namebuff, NULL) && + check_for_local_domain(daemon->namebuff, now)) + { + /* if we forwarded a query for a locally known name (because it was for + an unknown type) and the answer is NXDOMAIN, convert that to NODATA, + since we know that the domain exists, even if upstream doesn't */ + munged = 1; + header->hb3 |= HB3_AA; + SET_RCODE(header, NOERROR); + } + + if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled)) + { + my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); + munged = 1; + } + } + + /* do this after extract_addresses. Ensure NODATA reply and remove + nameserver info. */ + + if (munged) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + } + + /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide + sections of the packet. Find the new length here and put back pseudoheader + if it was removed. */ + return resize_packet(header, n, pheader, plen); +} + +/* sets new last_server */ +void reply_query(int fd, int family, time_t now) +{ + /* packet from peer server, extract data for cache, and send to + original requester */ + struct dns_header *header; + union mysockaddr serveraddr; + struct frec *forward; + socklen_t addrlen = sizeof(serveraddr); + ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); + size_t nn; + struct server *server; + + /* packet buffer overwritten */ + daemon->srv_save = NULL; + + /* Determine the address of the server replying so that we can mark that as good */ + serveraddr.sa.sa_family = family; +#ifdef HAVE_IPV6 + if (serveraddr.sa.sa_family == AF_INET6) + serveraddr.in6.sin6_flowinfo = 0; +#endif + + /* spoof check: answer must come from known server, */ + for (server = daemon->servers; server; server = server->next) + if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && + sockaddr_isequal(&server->addr, &serveraddr)) + break; + + header = (struct dns_header *)daemon->packet; + + if (!server || + n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || + !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) + return; + + server = forward->sentto; + + if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && + !option_bool(OPT_ORDER) && + forward->forwardall == 0) + /* for broken servers, attempt to send to another one. */ + { + unsigned char *pheader; + size_t plen; + int is_sign; + + /* recreate query from reply */ + pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign); + if (!is_sign) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + if ((nn = resize_packet(header, (size_t)n, pheader, plen))) + { + header->hb3 &= ~(HB3_QR | HB3_TC); + forward_query(-1, NULL, NULL, 0, header, nn, now, forward); + return; + } + } + } + + if ((forward->sentto->flags & SERV_TYPE) == 0) + { + if (RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) + server = NULL; + else + { + struct server *last_server; + + /* find good server by address if possible, otherwise assume the last one we sent to */ + for (last_server = daemon->servers; last_server; last_server = last_server->next) + if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) && + sockaddr_isequal(&last_server->addr, &serveraddr)) + { + server = last_server; + break; + } + } + if (!option_bool(OPT_ALL_SERVERS)) + daemon->last_server = server; + } + + /* If the answer is an error, keep the forward record in place in case + we get a good reply from another server. Kill it when we've + had replies from all to avoid filling the forwarding table when + everything is broken */ + if (forward->forwardall == 0 || --forward->forwardall == 1 || + (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) + { + int check_rebind = !(forward->flags & FREC_NOREBIND); + + if (!option_bool(OPT_NO_REBIND)) + check_rebind = 0; + + if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED))) + { + header->id = htons(forward->orig_id); + header->hb4 |= HB4_RA; /* recursion if available */ + send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, + &forward->source, &forward->dest, forward->iface); + } + free_frec(forward); /* cancel */ + } +} + + +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; + struct all_addr dst_addr; + struct in_addr netmask, dst_addr_4; + size_t m; + ssize_t n; + int if_index = 0; + struct iovec iov[1]; + struct msghdr msg; + struct cmsghdr *cmptr; + union { + struct cmsghdr align; /* this ensures alignment */ +#ifdef HAVE_IPV6 + char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#endif +#if defined(HAVE_LINUX_NETWORK) + char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#elif defined(IP_RECVDSTADDR) && defined(HAVE_SOLARIS_NETWORK) + char control[CMSG_SPACE(sizeof(struct in_addr)) + + CMSG_SPACE(sizeof(unsigned int))]; +#elif defined(IP_RECVDSTADDR) + char control[CMSG_SPACE(sizeof(struct in_addr)) + + CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#endif + } control_u; + + /* packet buffer overwritten */ + daemon->srv_save = NULL; + + if (listen->family == AF_INET && option_bool(OPT_NOWILD)) + { + dst_addr_4 = listen->iface->addr.in.sin_addr; + netmask = listen->iface->netmask; + } + else + { + dst_addr_4.s_addr = 0; + netmask.s_addr = 0; + } + + iov[0].iov_base = daemon->packet; + iov[0].iov_len = daemon->edns_pktsz; + + msg.msg_control = control_u.control; + msg.msg_controllen = sizeof(control_u); + msg.msg_flags = 0; + msg.msg_name = &source_addr; + msg.msg_namelen = sizeof(source_addr); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if ((n = recvmsg(listen->fd, &msg, 0)) == -1) + return; + + if (n < (int)sizeof(struct dns_header) || + (msg.msg_flags & MSG_TRUNC) || + (header->hb3 & HB3_QR)) + return; + + source_addr.sa.sa_family = listen->family; +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + source_addr.in6.sin6_flowinfo = 0; +#endif + + if (!option_bool(OPT_NOWILD)) + { + struct ifreq ifr; + + if (msg.msg_controllen < sizeof(struct cmsghdr)) + return; + +#if defined(HAVE_LINUX_NETWORK) + if (listen->family == AF_INET) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + { + union { + unsigned char *c; + struct in_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + dst_addr_4 = dst_addr.addr.addr4 = p.p->ipi_spec_dst; + if_index = p.p->ipi_ifindex; + } +#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) + if (listen->family == AF_INET) + { + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + { + union { + unsigned char *c; + unsigned int *i; + struct in_addr *a; +#ifndef HAVE_SOLARIS_NETWORK + struct sockaddr_dl *s; +#endif + } p; + p.c = CMSG_DATA(cmptr); + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) + dst_addr_4 = dst_addr.addr.addr4 = *(p.a); + else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) +#ifdef HAVE_SOLARIS_NETWORK + if_index = *(p.i); +#else + if_index = p.s->sdl_index; +#endif + } + } +#endif + +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + { + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo) + { + union { + unsigned char *c; + struct in6_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + + dst_addr.addr.addr6 = p.p->ipi6_addr; + if_index = p.p->ipi6_ifindex; + } + } +#endif + + /* enforce available interface configuration */ + + if (!indextoname(listen->fd, if_index, ifr.ifr_name) || + !iface_check(listen->family, &dst_addr, ifr.ifr_name, &if_index)) + return; + + if (listen->family == AF_INET && + option_bool(OPT_LOCALISE) && + ioctl(listen->fd, SIOCGIFNETMASK, &ifr) == -1) + return; + + netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + } + + if (extract_request(header, (size_t)n, daemon->namebuff, &type)) + { + char types[20]; + + querystr(types, type); + + if (listen->family == AF_INET) + log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&source_addr.in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&source_addr.in6.sin6_addr, types); +#endif + } + + m = answer_request (header, ((char *) header) + PACKETSZ, (size_t)n, + dst_addr_4, netmask, now); + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD), (char *)header, + m, &source_addr, &dst_addr, if_index); + daemon->local_answer++; + } + else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, + header, (size_t)n, now, NULL)) + daemon->queries_forwarded++; + else + daemon->local_answer++; +} + +/* 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 + 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, + struct in_addr local_addr, struct in_addr netmask) +{ + size_t size = 0; + int norebind = 0; + int checking_disabled; + size_t m; + unsigned short qtype, gotname; + unsigned char c1, c2; + /* Max TCP packet + slop */ + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ); + struct dns_header *header; + struct server *last_server; + + while (1) + { + if (!packet || + !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || + !(size = c1 << 8 | c2) || + !read_write(confd, packet, size, 1)) + return packet; + + if (size < (int)sizeof(struct dns_header)) + continue; + + header = (struct dns_header *)packet; + + /* save state of "cd" flag in query */ + checking_disabled = header->hb4 & HB4_CD; + + /* RFC 4035: sect 4.6 para 2 */ + header->hb4 &= ~HB4_AD; + + if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) + { + union mysockaddr peer_addr; + socklen_t peer_len = sizeof(union mysockaddr); + + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) + { + char types[20]; + + querystr(types, qtype); + + if (peer_addr.sa.sa_family == AF_INET) + log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in6.sin6_addr, types); +#endif + } + } + + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (unsigned int)size, + local_addr, netmask, now); + + /* Do this by steam now we're not in the select() loop */ + check_log_writer(NULL); + + if (m == 0) + { + unsigned int flags = 0; + struct all_addr *addrp = NULL; + int type = 0; + char *domain = NULL; + + if (option_bool(OPT_ADD_MAC)) + { + union mysockaddr peer_addr; + socklen_t peer_len = sizeof(union mysockaddr); + + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) + size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); + } + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + + if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) + last_server = daemon->servers; + else + last_server = daemon->last_server; + + if (!flags && last_server) + { + struct server *firstsendto = NULL; + unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); + + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtley ensures that consecutive queries on this connection + which can go to the same server, do so. */ + while (1) + { + if (!firstsendto) + firstsendto = last_server; + else + { + if (!(last_server = last_server->next)) + last_server = daemon->servers; + + if (last_server == firstsendto) + break; + } + + /* server for wrong domain */ + if (type != (last_server->flags & SERV_TYPE) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) + continue; + + if ((last_server->tcpfd == -1) && + (last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) != -1 && + (!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || + connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + } + + if (last_server->tcpfd == -1) + continue; + + c1 = size >> 8; + c2 = size; + + if (!read_write(last_server->tcpfd, &c1, 1, 0) || + !read_write(last_server->tcpfd, &c2, 1, 0) || + !read_write(last_server->tcpfd, packet, size, 0) || + !read_write(last_server->tcpfd, &c1, 1, 1) || + !read_write(last_server->tcpfd, &c2, 1, 1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + + m = (c1 << 8) | c2; + if (!read_write(last_server->tcpfd, packet, m, 1)) + return packet; + + if (!gotname) + strcpy(daemon->namebuff, "query"); + if (last_server->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in.sin_addr, NULL); +#ifdef HAVE_IPV6 + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); +#endif + + /* There's no point in updating the cache, since this process will exit and + lose the information after a few queries. We make this call for the alias and + bogus-nxdomain side-effects. */ + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ + if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, checking_disabled); + + break; + } + } + + /* In case of local answer or no connections made. */ + if (m == 0) + m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); + } + + check_log_writer(NULL); + + c1 = m>>8; + c2 = m; + if (!read_write(confd, &c1, 1, 0) || + !read_write(confd, &c2, 1, 0) || + !read_write(confd, packet, m, 0)) + return packet; + } +} + +static struct frec *allocate_frec(time_t now) +{ + struct frec *f; + + if ((f = (struct frec *)whine_malloc(sizeof(struct frec)))) + { + f->next = daemon->frec_list; + f->time = now; + f->sentto = NULL; + f->rfd4 = NULL; + f->flags = 0; +#ifdef HAVE_IPV6 + f->rfd6 = NULL; +#endif + daemon->frec_list = f; + } + + return f; +} + +static struct randfd *allocate_rfd(int family) +{ + static int finger = 0; + int i; + + /* limit the number of sockets we have open to avoid starvation of + (eg) TFTP. Once we have a reasonable number, randomness should be OK */ + + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount == 0) + { + if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) + break; + + daemon->randomsocks[i].refcount = 1; + daemon->randomsocks[i].family = family; + return &daemon->randomsocks[i]; + } + + /* No free ones or cannot get new socket, grab an existing one */ + for (i = 0; i < RANDOM_SOCKS; i++) + { + int j = (i+finger) % RANDOM_SOCKS; + if (daemon->randomsocks[j].refcount != 0 && + daemon->randomsocks[j].family == family && + daemon->randomsocks[j].refcount != 0xffff) + { + finger = j; + daemon->randomsocks[j].refcount++; + return &daemon->randomsocks[j]; + } + } + + return NULL; /* doom */ +} + +static void free_frec(struct frec *f) +{ + if (f->rfd4 && --(f->rfd4->refcount) == 0) + close(f->rfd4->fd); + + f->rfd4 = NULL; + f->sentto = NULL; + f->flags = 0; + +#ifdef HAVE_IPV6 + if (f->rfd6 && --(f->rfd6->refcount) == 0) + close(f->rfd6->fd); + + f->rfd6 = NULL; +#endif +} + +/* if wait==NULL return a free or older than TIMEOUT record. + else return *wait zero if one available, or *wait is delay to + when the oldest in-use record will expire. Impose an absolute + limit of 4*TIMEOUT before we wipe things (for random sockets) */ +struct frec *get_new_frec(time_t now, int *wait) +{ + struct frec *f, *oldest, *target; + int count; + + if (wait) + *wait = 0; + + for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next, count++) + if (!f->sentto) + 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; + } + + if (target) + { + target->time = now; + return target; + } + + /* 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) + { + /* keep stuff for twice timeout if we can by allocating a new + record instead */ + if (difftime(now, oldest->time) < 2*TIMEOUT && + count <= daemon->ftabsize && + (f = allocate_frec(now))) + return f; + + if (!wait) + { + free_frec(oldest); + oldest->time = now; + } + return oldest; + } + + /* none available, calculate time 'till oldest record expires */ + if (count > daemon->ftabsize) + { + if (oldest && wait) + *wait = oldest->time + (time_t)TIMEOUT - now; + return NULL; + } + + if (!(f = allocate_frec(now)) && wait) + /* wait one second on malloc failure */ + *wait = 1; + + 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, unsigned int crc) +{ + struct frec *f; + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (f->crc == crc || crc == 0xffffffff)) + return f; + + return NULL; +} + +static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + unsigned int crc) +{ + struct frec *f; + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && + f->orig_id == id && + f->crc == crc && + sockaddr_isequal(&f->source, addr)) + return f; + + return NULL; +} + +/* A server record is going away, remove references to it */ +void server_gone(struct server *server) +{ + struct frec *f; + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->sentto == server) + free_frec(f); + + if (daemon->last_server == server) + daemon->last_server = NULL; + + if (daemon->srv_save == server) + daemon->srv_save = NULL; +} + +/* return unique random ids. */ +static unsigned short get_id(unsigned int crc) +{ + unsigned short ret = 0; + + do + ret = rand16(); + while (lookup_frec(ret, crc)); + + return ret; +} + + + + + diff --git a/src/helper.c b/src/helper.c new file mode 100644 index 0000000..93f99f0 --- /dev/null +++ b/src/helper.c @@ -0,0 +1,410 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +/* This file has code to fork a helper process which recieves data via a pipe + shared with the main process and which is responsible for calling a script when + DHCP leases change. + + The helper process is forked before the main process drops root, so it retains root + privs to pass on to the script. For this reason it tries to be paranoid about + data received from the main process, in case that has been compromised. We don't + want the helper to give an attacker root. In particular, the script to be run is + not settable via the pipe, once the fork has taken place it is not alterable by the + main process. +*/ + +#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) + +static void my_setenv(const char *name, const char *value, int *error); +static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err); + +struct script_data +{ + unsigned char action, hwaddr_len, hwaddr_type; + unsigned char clid_len, hostname_len, ed_len; + struct in_addr addr, giaddr; + unsigned int remaining_time; +#ifdef HAVE_BROKEN_RTC + unsigned int length; +#else + time_t expires; +#endif + unsigned char hwaddr[DHCP_CHADDR_MAX]; + char interface[IF_NAMESIZE]; +}; + +static struct script_data *buf = NULL; +static size_t bytes_in_buf = 0, buf_size = 0; + +int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) +{ + pid_t pid; + int i, pipefd[2]; + struct sigaction sigact; + + /* create the pipe through which the main program sends us commands, + then fork our process. */ + if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) + { + send_event(err_fd, EVENT_PIPE_ERR, errno); + _exit(0); + } + + if (pid != 0) + { + close(pipefd[0]); /* close reader side */ + return pipefd[1]; + } + + /* ignore SIGTERM, 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); + + if (!option_bool(OPT_DEBUG) && uid != 0) + { + gid_t dummy; + if (setgroups(0, &dummy) == -1 || + setgid(gid) == -1 || + setuid(uid) == -1) + { + if (option_bool(OPT_NO_FORK)) + /* send error to daemon process if no-fork */ + send_event(event_fd, EVENT_HUSER_ERR, errno); + else + { + /* kill daemon */ + send_event(event_fd, EVENT_DIE, 0); + /* return error */ + send_event(err_fd, EVENT_HUSER_ERR, errno); + } + _exit(0); + } + } + + /* close all the sockets etc, we don't need them here. This closes err_fd, so that + main process can return. */ + for (max_fd--; max_fd >= 0; max_fd--) + if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && + max_fd != STDIN_FILENO && max_fd != pipefd[0] && max_fd != event_fd) + close(max_fd); + + /* loop here */ + while(1) + { + struct script_data data; + char *p, *action_str, *hostname = NULL; + unsigned char *buf = (unsigned char *)daemon->namebuff; + unsigned char *end, *alloc_buff = NULL; + int err = 0; + + /* we read zero bytes when pipe closed: this is our signal to exit */ + if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1)) + _exit(0); + + if (data.action == ACTION_DEL) + action_str = "del"; + else if (data.action == ACTION_ADD) + action_str = "add"; + else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME) + action_str = "old"; + else + continue; + + /* stringify MAC into dhcp_buff */ + p = daemon->dhcp_buff; + if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0) + p += sprintf(p, "%.2x-", data.hwaddr_type); + for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++) + { + p += sprintf(p, "%.2x", data.hwaddr[i]); + if (i != data.hwaddr_len - 1) + p += sprintf(p, ":"); + } + + /* and CLID into packet, avoid overwrite from bad data */ + if ((data.clid_len > daemon->packet_buff_sz) || !read_write(pipefd[0], buf, data.clid_len, 1)) + continue; + for (p = daemon->packet, i = 0; i < data.clid_len; i++) + { + p += sprintf(p, "%.2x", buf[i]); + if (i != data.clid_len - 1) + p += sprintf(p, ":"); + } + + /* and expiry or length into dhcp_buff2 */ +#ifdef HAVE_BROKEN_RTC + sprintf(daemon->dhcp_buff2, "%u", data.length); +#else + sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires); +#endif + + /* supplied data may just exceed normal buffer (unlikely) */ + if ((data.hostname_len + data.ed_len) > daemon->packet_buff_sz && + !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len))) + continue; + + if (!read_write(pipefd[0], buf, + data.hostname_len + data.ed_len, 1)) + continue; + + /* possible fork errors are all temporary resource problems */ + while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM)) + sleep(2); + + free(alloc_buff); + + if (pid == -1) + continue; + + /* wait for child to complete */ + if (pid != 0) + { + /* reap our children's children, if necessary */ + while (1) + { + int status; + pid_t rc = wait(&status); + + if (rc == pid) + { + /* On error send event back to main process for logging */ + if (WIFSIGNALED(status)) + send_event(event_fd, EVENT_KILLED, WTERMSIG(status)); + else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status)); + break; + } + + if (rc == -1 && errno != EINTR) + break; + } + + continue; + } + + if (data.clid_len != 0) + my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err); + + if (strlen(data.interface) != 0) + my_setenv("DNSMASQ_INTERFACE", data.interface, &err); + +#ifdef HAVE_BROKEN_RTC + my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err); +#else + my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err); +#endif + + if (data.hostname_len != 0) + { + char *dot; + hostname = (char *)buf; + hostname[data.hostname_len - 1] = 0; + if (!legal_hostname(hostname)) + hostname = NULL; + else if ((dot = strchr(hostname, '.'))) + { + my_setenv("DNSMASQ_DOMAIN", dot+1, &err); + *dot = 0; + } + buf += data.hostname_len; + } + + end = buf + data.ed_len; + buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err); + buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err); + buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err); + + for (i = 0; buf; i++) + { + sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i); + buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err); + } + + if (data.giaddr.s_addr != 0) + my_setenv("DNSMASQ_RELAY_ADDRESS", inet_ntoa(data.giaddr), &err); + + if (data.action != ACTION_DEL) + { + sprintf(daemon->dhcp_buff2, "%u", data.remaining_time); + my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err); + } + + if (data.action == ACTION_OLD_HOSTNAME && hostname) + { + my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err); + hostname = NULL; + } + + /* we need to have the event_fd around if exec fails */ + if ((i = fcntl(event_fd, F_GETFD)) != -1) + fcntl(event_fd, F_SETFD, i | FD_CLOEXEC); + close(pipefd[0]); + + p = strrchr(daemon->lease_change_command, '/'); + if (err == 0) + { + execl(daemon->lease_change_command, + p ? p+1 : daemon->lease_change_command, + action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*)NULL); + err = errno; + } + /* failed, send event so the main process logs the problem */ + send_event(event_fd, EVENT_EXEC_ERR, err); + _exit(0); + } +} + +static void my_setenv(const char *name, const char *value, int *error) +{ + if (*error == 0 && setenv(name, value, 1) != 0) + *error = errno; +} + +static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err) +{ + unsigned char *next; + + if (!buf || (buf == end)) + return NULL; + + for (next = buf; *next != 0; next++) + if (next == end) + return NULL; + + if (next != buf) + { + char *p; + /* No "=" in value */ + if ((p = strchr((char *)buf, '='))) + *p = 0; + my_setenv(env, (char *)buf, err); + } + + return next + 1; +} + +/* pack up lease data into a buffer */ +void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now) +{ + unsigned char *p; + size_t size; + unsigned int hostname_len = 0, clid_len = 0, ed_len = 0; + + /* no script */ + if (daemon->helperfd == -1) + return; + + if (lease->extradata) + ed_len = lease->extradata_len; + if (lease->clid) + clid_len = lease->clid_len; + if (hostname) + hostname_len = strlen(hostname) + 1; + + size = sizeof(struct script_data) + clid_len + ed_len + hostname_len; + + if (size > buf_size) + { + struct script_data *new; + + /* start with reasonable size, will almost never need extending. */ + if (size < sizeof(struct script_data) + 200) + size = sizeof(struct script_data) + 200; + + if (!(new = whine_malloc(size))) + return; + if (buf) + free(buf); + buf = new; + buf_size = size; + } + + buf->action = action; + buf->hwaddr_len = lease->hwaddr_len; + buf->hwaddr_type = lease->hwaddr_type; + buf->clid_len = clid_len; + buf->ed_len = ed_len; + buf->hostname_len = hostname_len; + buf->addr = lease->addr; + buf->giaddr = lease->giaddr; + memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len); + if (!indextoname(daemon->dhcpfd, lease->last_interface, buf->interface)) + buf->interface[0] = 0; + +#ifdef HAVE_BROKEN_RTC + buf->length = lease->length; +#else + buf->expires = lease->expires; +#endif + buf->remaining_time = (unsigned int)difftime(lease->expires, now); + + p = (unsigned char *)(buf+1); + if (clid_len != 0) + { + memcpy(p, lease->clid, clid_len); + p += clid_len; + } + if (hostname_len != 0) + { + memcpy(p, hostname, hostname_len); + p += hostname_len; + } + if (ed_len != 0) + { + memcpy(p, lease->extradata, ed_len); + p += ed_len; + } + bytes_in_buf = p - (unsigned char *)buf; +} + +int helper_buf_empty(void) +{ + return bytes_in_buf == 0; +} + +void helper_write(void) +{ + ssize_t rc; + + if (bytes_in_buf == 0) + return; + + if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1) + { + if (bytes_in_buf != (size_t)rc) + memmove(buf, buf + rc, bytes_in_buf - rc); + bytes_in_buf -= rc; + } + else + { + if (errno == EAGAIN || errno == EINTR) + return; + bytes_in_buf = 0; + } +} + +#endif + + diff --git a/src/lease.c b/src/lease.c new file mode 100644 index 0000000..cfa7543 --- /dev/null +++ b/src/lease.c @@ -0,0 +1,615 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_DHCP + +static struct dhcp_lease *leases = NULL, *old_leases = NULL; +static int dns_dirty, file_dirty, leases_left; + +void lease_init(time_t now) +{ + unsigned long ei; + struct in_addr addr; + struct dhcp_lease *lease; + int clid_len, hw_len, hw_type; + FILE *leasestream; + + /* 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); + + 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); + } + + /* 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, "%lu %255s %16s %255s %764s", + &ei, daemon->dhcp_buff2, daemon->namebuff, + daemon->dhcp_buff, daemon->packet) == 5) + { + 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. */ + if (hw_type == 0 && hw_len != 0) + hw_type = ARPHRD_ETHER; + + addr.s_addr = inet_addr(daemon->namebuff); + + /* decode hex in place */ + clid_len = 0; + if (strcmp(daemon->packet, "*") != 0) + clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL); + + if (!(lease = lease_allocate(addr))) + die (_("too many stored leases"), NULL, EC_MISC); + +#ifdef HAVE_BROKEN_RTC + if (ei != 0) + lease->expires = (time_t)ei + now; + else + lease->expires = (time_t)0; + lease->length = ei; +#else + /* strictly time_t is opaque, but this hack should work on all sane systems, + even when sizeof(time_t) == 8 */ + lease->expires = (time_t)ei; +#endif + + lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, hw_len, hw_type, clid_len); + + if (strcmp(daemon->dhcp_buff, "*") != 0) + lease_set_hostname(lease, daemon->dhcp_buff, 0); + + /* set these correctly: the "old" events are generated later from + the startup synthesised SIGHUP. */ + lease->new = lease->changed = 0; + } + +#ifdef HAVE_SCRIPT + if (!daemon->lease_stream) + { + int rc = 0; + + /* shell returns 127 for "command not found", 126 for bad permissions. */ + if (!leasestream || (rc = pclose(leasestream)) == -1 || WEXITSTATUS(rc) == 127 || WEXITSTATUS(rc) == 126) + { + if (WEXITSTATUS(rc) == 127) + errno = ENOENT; + else if (WEXITSTATUS(rc) == 126) + errno = EACCES; + die(_("cannot run lease-init script %s: %s"), daemon->lease_change_command, EC_FILE); + } + + if (WEXITSTATUS(rc) != 0) + { + sprintf(daemon->dhcp_buff, "%d", WEXITSTATUS(rc)); + die(_("lease-init script returned exit code %s"), daemon->dhcp_buff, WEXITSTATUS(rc) + EC_INIT_OFFSET); + } + } +#endif + + /* Some leases may have expired */ + file_dirty = 0; + lease_prune(NULL, now); + dns_dirty = 1; +} + +void lease_update_from_configs(void) +{ + /* changes to the config may change current leases. */ + + struct dhcp_lease *lease; + struct dhcp_config *config; + char *name; + + for (lease = leases; lease; lease = lease->next) + if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, + lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && + (config->flags & CONFIG_NAME) && + (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr)) + lease_set_hostname(lease, config->hostname, 1); + else if ((name = host_from_dns(lease->addr))) + lease_set_hostname(lease, name, 1); /* updates auth flag only */ +} + +static void ourprintf(int *errp, char *format, ...) +{ + va_list ap; + + va_start(ap, format); + if (!(*errp) && vfprintf(daemon->lease_stream, format, ap) < 0) + *errp = errno; + va_end(ap); +} + +void lease_update_file(time_t now) +{ + struct dhcp_lease *lease; + time_t next_event; + int i, err = 0; + + if (file_dirty != 0 && daemon->lease_stream) + { + errno = 0; + rewind(daemon->lease_stream); + if (errno != 0 || ftruncate(fileno(daemon->lease_stream), 0) != 0) + err = errno; + + for (lease = leases; lease; lease = lease->next) + { +#ifdef HAVE_BROKEN_RTC + ourprintf(&err, "%u ", lease->length); +#else + ourprintf(&err, "%lu ", (unsigned long)lease->expires); +#endif + if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0) + ourprintf(&err, "%.2x-", lease->hwaddr_type); + for (i = 0; i < lease->hwaddr_len; i++) + { + ourprintf(&err, "%.2x", lease->hwaddr[i]); + if (i != lease->hwaddr_len - 1) + ourprintf(&err, ":"); + } + + ourprintf(&err, " %s ", inet_ntoa(lease->addr)); + ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*"); + + if (lease->clid && lease->clid_len != 0) + { + for (i = 0; i < lease->clid_len - 1; i++) + ourprintf(&err, "%.2x:", lease->clid[i]); + ourprintf(&err, "%.2x\n", lease->clid[i]); + } + else + ourprintf(&err, "*\n"); + } + + if (fflush(daemon->lease_stream) != 0 || + fsync(fileno(daemon->lease_stream)) < 0) + err = errno; + + if (!err) + file_dirty = 0; + } + + /* Set alarm for when the first lease expires + slop. */ + for (next_event = 0, lease = leases; lease; lease = lease->next) + if (lease->expires != 0 && + (next_event == 0 || difftime(next_event, lease->expires + 10) > 0.0)) + next_event = lease->expires + 10; + + if (err) + { + if (next_event == 0 || difftime(next_event, LEASE_RETRY + now) > 0.0) + next_event = LEASE_RETRY + now; + + my_syslog(MS_DHCP | LOG_ERR, _("failed to write %s: %s (retry in %us)"), + daemon->lease_file, strerror(err), + (unsigned int)difftime(next_event, now)); + } + + if (next_event != 0) + alarm((unsigned)difftime(next_event, now)); +} + +void lease_update_dns(void) +{ + struct dhcp_lease *lease; + + if (daemon->port != 0 && dns_dirty) + { + cache_unhash_dhcp(); + + for (lease = leases; lease; lease = lease->next) + { + if (lease->fqdn) + cache_add_dhcp_entry(lease->fqdn, &lease->addr, lease->expires); + + if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) + cache_add_dhcp_entry(lease->hostname, &lease->addr, lease->expires); + } + + dns_dirty = 0; + } +} + +void lease_prune(struct dhcp_lease *target, time_t now) +{ + struct dhcp_lease *lease, *tmp, **up; + + for (lease = leases, up = &leases; lease; lease = tmp) + { + tmp = lease->next; + if ((lease->expires != 0 && difftime(now, lease->expires) > 0) || lease == target) + { + file_dirty = 1; + if (lease->hostname) + dns_dirty = 1; + + *up = lease->next; /* unlink */ + + /* Put on old_leases list 'till we + can run the script */ + lease->next = old_leases; + old_leases = lease; + + leases_left++; + } + else + up = &lease->next; + } +} + + +struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type, + unsigned char *clid, int clid_len) +{ + struct dhcp_lease *lease; + + if (clid) + for (lease = leases; lease; lease = lease->next) + if (lease->clid && clid_len == lease->clid_len && + memcmp(clid, lease->clid, clid_len) == 0) + return lease; + + for (lease = leases; lease; lease = lease->next) + if ((!lease->clid || !clid) && + hw_len != 0 && + lease->hwaddr_len == hw_len && + lease->hwaddr_type == hw_type && + memcmp(hwaddr, lease->hwaddr, hw_len) == 0) + return lease; + + return NULL; +} + +struct dhcp_lease *lease_find_by_addr(struct in_addr addr) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + if (lease->addr.s_addr == addr.s_addr) + return lease; + + return NULL; +} + + +struct dhcp_lease *lease_allocate(struct in_addr addr) +{ + struct dhcp_lease *lease; + if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease)))) + return NULL; + + memset(lease, 0, sizeof(struct dhcp_lease)); + lease->new = 1; + lease->addr = addr; + lease->hwaddr_len = 256; /* illegal value */ + lease->expires = 1; +#ifdef HAVE_BROKEN_RTC + lease->length = 0xffffffff; /* illegal value */ +#endif + lease->next = leases; + leases = lease; + + file_dirty = 1; + leases_left--; + + return lease; +} + +void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) +{ + time_t exp = now + (time_t)len; + + if (len == 0xffffffff) + { + exp = 0; + len = 0; + } + + if (exp != lease->expires) + { + dns_dirty = 1; + lease->expires = exp; +#ifndef HAVE_BROKEN_RTC + lease->aux_changed = file_dirty = 1; +#endif + } + +#ifdef HAVE_BROKEN_RTC + if (len != lease->length) + { + lease->length = len; + lease->aux_changed = file_dirty = 1; + } +#endif +} + +void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, + unsigned char *clid, int hw_len, int hw_type, int clid_len) +{ + if (hw_len != lease->hwaddr_len || + hw_type != lease->hwaddr_type || + (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0)) + { + memcpy(lease->hwaddr, hwaddr, hw_len); + lease->hwaddr_len = hw_len; + lease->hwaddr_type = hw_type; + lease->changed = file_dirty = 1; /* run script on change */ + } + + /* only update clid when one is available, stops packets + without a clid removing the record. Lease init uses + clid_len == 0 for no clid. */ + if (clid_len != 0 && clid) + { + if (!lease->clid) + lease->clid_len = 0; + + if (lease->clid_len != clid_len) + { + lease->aux_changed = file_dirty = 1; + free(lease->clid); + if (!(lease->clid = whine_malloc(clid_len))) + return; + } + else if (memcmp(lease->clid, clid, clid_len) != 0) + lease->aux_changed = file_dirty = 1; + + lease->clid_len = clid_len; + memcpy(lease->clid, clid, clid_len); + } + +} + +static void kill_name(struct dhcp_lease *lease) +{ + /* run script to say we lost our old name */ + + /* this shouldn't happen unless updates are very quick and the + script very slow, we just avoid a memory leak if it does. */ + free(lease->old_hostname); + + /* If we know the fqdn, pass that. The helper will derive the + unqualified name from it, free the unqulaified name here. */ + + if (lease->fqdn) + { + lease->old_hostname = lease->fqdn; + free(lease->hostname); + } + else + lease->old_hostname = lease->hostname; + + lease->hostname = lease->fqdn = NULL; +} + +void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) +{ + struct dhcp_lease *lease_tmp; + char *new_name = NULL, *new_fqdn = NULL; + + if (lease->hostname && name && hostname_isequal(lease->hostname, name)) + { + lease->auth_name = auth; + return; + } + + if (!name && !lease->hostname) + return; + + /* If a machine turns up on a new net without dropping the old lease, + or two machines claim the same name, then we end up with two interfaces with + the same name. Check for that here and remove the name from the old lease. + Don't allow a name from the client to override a name from dnsmasq config. */ + + if (name) + { + if ((new_name = whine_malloc(strlen(name) + 1))) + { + char *suffix = get_domain(lease->addr); + strcpy(new_name, name); + if (suffix && (new_fqdn = whine_malloc(strlen(new_name) + strlen(suffix) + 2))) + { + strcpy(new_fqdn, name); + strcat(new_fqdn, "."); + strcat(new_fqdn, suffix); + } + } + + /* Depending on mode, we check either unqualified name or FQDN. */ + for (lease_tmp = leases; lease_tmp; lease_tmp = lease_tmp->next) + { + if (option_bool(OPT_DHCP_FQDN)) + { + if (!new_fqdn || !lease_tmp->fqdn || !hostname_isequal(lease_tmp->fqdn, new_fqdn) ) + continue; + } + else + { + if (!new_name || !lease_tmp->hostname || !hostname_isequal(lease_tmp->hostname, new_name) ) + continue; + } + + if (lease_tmp->auth_name && !auth) + { + free(new_name); + free(new_fqdn); + return; + } + + kill_name(lease_tmp); + break; + } + } + + if (lease->hostname) + kill_name(lease); + + lease->hostname = new_name; + lease->fqdn = new_fqdn; + lease->auth_name = auth; + + file_dirty = 1; + dns_dirty = 1; + lease->changed = 1; /* run script on change */ +} + +void lease_set_interface(struct dhcp_lease *lease, int interface) +{ + if (lease->last_interface == interface) + return; + + lease->last_interface = interface; + lease->changed = 1; +} + +void rerun_scripts(void) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + lease->changed = 1; +} + +/* deleted leases get transferred to the old_leases list. + remove them here, after calling the lease change + script. Also run the lease change script on new/modified leases. + + Return zero if nothing to do. */ +int do_script_run(time_t now) +{ + struct dhcp_lease *lease; + +#ifdef HAVE_DBUS + /* If we're going to be sending DBus signals, but the connection is not yet up, + delay everything until it is. */ + if (option_bool(OPT_DBUS) && !daemon->dbus) + return 0; +#endif + + if (old_leases) + { + lease = old_leases; + + /* If the lease still has an old_hostname, do the "old" action on that first */ + if (lease->old_hostname) + { +#ifdef HAVE_SCRIPT + queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now); +#endif + free(lease->old_hostname); + lease->old_hostname = NULL; + return 1; + } + else + { + kill_name(lease); +#ifdef HAVE_SCRIPT + queue_script(ACTION_DEL, lease, lease->old_hostname, now); +#endif +#ifdef HAVE_DBUS + emit_dbus_signal(ACTION_DEL, lease, lease->old_hostname); +#endif + old_leases = lease->next; + + free(lease->old_hostname); + free(lease->clid); + free(lease->extradata); + free(lease); + + return 1; + } + } + + /* make sure we announce the loss of a hostname before its new location. */ + for (lease = leases; lease; lease = lease->next) + if (lease->old_hostname) + { +#ifdef HAVE_SCRIPT + queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now); +#endif + free(lease->old_hostname); + lease->old_hostname = NULL; + return 1; + } + + for (lease = leases; lease; lease = lease->next) + if (lease->new || lease->changed || + (lease->aux_changed && option_bool(OPT_LEASE_RO))) + { +#ifdef HAVE_SCRIPT + queue_script(lease->new ? ACTION_ADD : ACTION_OLD, lease, + lease->fqdn ? lease->fqdn : lease->hostname, now); +#endif +#ifdef HAVE_DBUS + emit_dbus_signal(lease->new ? ACTION_ADD : ACTION_OLD, lease, + lease->fqdn ? lease->fqdn : lease->hostname); +#endif + lease->new = lease->changed = lease->aux_changed = 0; + + /* this is used for the "add" call, then junked, since they're not in the database */ + free(lease->extradata); + lease->extradata = NULL; + + return 1; + } + + return 0; /* nothing to do */ +} + +#endif + + + + diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..baaae7c --- /dev/null +++ b/src/log.c @@ -0,0 +1,466 @@ +/* dnsmasq is Copyright (c) 2000-2011 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 __ANDROID__ +# include <android/log.h> +#endif + +/* Implement logging to /dev/log asynchronously. If syslogd is + making DNS lookups through dnsmasq, and dnsmasq blocks awaiting + syslogd, then the two daemons can deadlock. We get around this + by not blocking when talking to syslog, instead we queue up to + MAX_LOGS messages. If more are queued, they will be dropped, + and the drop event itself logged. */ + +/* The "wire" protocol for logging is defined in RFC 3164 */ + +/* From RFC 3164 */ +#define MAX_MESSAGE 1024 + +/* defaults in case we die() before we log_start() */ +static int log_fac = LOG_DAEMON; +static int log_stderr = 0; +static int echo_stderr = 0; +static int log_fd = -1; +static int log_to_file = 0; +static int entries_alloced = 0; +static int entries_lost = 0; +static int connection_good = 1; +static int max_logs = 0; +static int connection_type = SOCK_DGRAM; + +struct log_entry { + int offset, length; + pid_t pid; /* to avoid duplicates over a fork */ + struct log_entry *next; + char payload[MAX_MESSAGE]; +}; + +static struct log_entry *entries = NULL; +static struct log_entry *free_entries = NULL; + + +int log_start(struct passwd *ent_pw, int errfd) +{ + int ret = 0; + + echo_stderr = option_bool(OPT_DEBUG); + + if (daemon->log_fac != -1) + log_fac = daemon->log_fac; +#ifdef LOG_LOCAL0 + else if (option_bool(OPT_DEBUG)) + log_fac = LOG_LOCAL0; +#endif + + if (daemon->log_file) + { + log_to_file = 1; + daemon->max_logs = 0; + if (strcmp(daemon->log_file, "-") == 0) + { + log_stderr = 1; + echo_stderr = 0; + log_fd = dup(STDERR_FILENO); + } + } + + max_logs = daemon->max_logs; + + if (!log_reopen(daemon->log_file)) + { + send_event(errfd, EVENT_LOG_ERR, errno); + _exit(0); + } + + /* if queuing is inhibited, make sure we allocate + the one required buffer now. */ + if (max_logs == 0) + { + free_entries = safe_malloc(sizeof(struct log_entry)); + free_entries->next = NULL; + entries_alloced = 1; + } + + /* If we're running as root and going to change uid later, + change the ownership here so that the file is always owned by + the dnsmasq user. Then logrotate can just copy the owner. + Failure of the chown call is OK, (for instance when started as non-root) */ + if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0 && + fchown(log_fd, ent_pw->pw_uid, -1) != 0) + ret = errno; + + return ret; +} + +int log_reopen(char *log_file) +{ + if (!log_stderr) + { + if (log_fd != -1) + close(log_fd); + + /* NOTE: umask is set to 022 by the time this gets called */ + + if (log_file) + log_fd = open(log_file, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP); + else + { +#if defined(HAVE_SOLARIS_NETWORK) || defined(__ANDROID__) + /* Solaris logging is "different", /dev/log is not unix-domain socket. + Just leave log_fd == -1 and use the vsyslog call for everything.... */ +# define _PATH_LOG "" /* dummy */ + return 1; +#else + int flags; + log_fd = socket(AF_UNIX, connection_type, 0); + + /* if max_logs is zero, leave the socket blocking */ + if (log_fd != -1 && max_logs != 0 && (flags = fcntl(log_fd, F_GETFL)) != -1) + fcntl(log_fd, F_SETFL, flags | O_NONBLOCK); +#endif + } + } + + return log_fd != -1; +} + +static void free_entry(void) +{ + struct log_entry *tmp = entries; + entries = tmp->next; + tmp->next = free_entries; + free_entries = tmp; +} + +static void log_write(void) +{ + ssize_t rc; + + while (entries) + { + /* Avoid duplicates over a fork() */ + if (entries->pid != getpid()) + { + free_entry(); + continue; + } + + connection_good = 1; + + if ((rc = write(log_fd, entries->payload + entries->offset, entries->length)) != -1) + { + entries->length -= rc; + entries->offset += rc; + if (entries->length == 0) + { + free_entry(); + if (entries_lost != 0) + { + int e = entries_lost; + entries_lost = 0; /* avoid wild recursion */ + my_syslog(LOG_WARNING, _("overflow: %d log entries lost"), e); + } + } + continue; + } + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return; /* syslogd busy, go again when select() or poll() says so */ + + if (errno == ENOBUFS) + { + connection_good = 0; + return; + } + + /* errors handling after this assumes sockets */ + if (!log_to_file) + { + /* Once a stream socket hits EPIPE, we have to close and re-open + (we ignore SIGPIPE) */ + if (errno == EPIPE) + { + if (log_reopen(NULL)) + continue; + } + else if (errno == ECONNREFUSED || + errno == ENOTCONN || + errno == EDESTADDRREQ || + errno == ECONNRESET) + { + /* socket went (syslogd down?), try and reconnect. If we fail, + stop trying until the next call to my_syslog() + ECONNREFUSED -> connection went down + ENOTCONN -> nobody listening + (ECONNRESET, EDESTADDRREQ are *BSD equivalents) */ + + struct sockaddr_un logaddr; + +#ifdef HAVE_SOCKADDR_SA_LEN + logaddr.sun_len = sizeof(logaddr) - sizeof(logaddr.sun_path) + strlen(_PATH_LOG) + 1; +#endif + logaddr.sun_family = AF_UNIX; + strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); + + /* Got connection back? try again. */ + if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1) + continue; + + /* errors from connect which mean we should keep trying */ + if (errno == ENOENT || + errno == EALREADY || + errno == ECONNREFUSED || + errno == EISCONN || + errno == EINTR || + errno == EAGAIN) + { + /* try again on next syslog() call */ + connection_good = 0; + return; + } + + /* try the other sort of socket... */ + if (errno == EPROTOTYPE) + { + connection_type = connection_type == SOCK_DGRAM ? SOCK_STREAM : SOCK_DGRAM; + if (log_reopen(NULL)) + continue; + } + } + } + + /* give up - fall back to syslog() - this handles out-of-space + when logging to a file, for instance. */ + log_fd = -1; + my_syslog(LOG_CRIT, _("log failed: %s"), strerror(errno)); + return; + } +} + +/* priority is one of LOG_DEBUG, LOG_INFO, LOG_NOTICE, etc. See sys/syslog.h. + OR'd to priority can be MS_TFTP, MS_DHCP, ... to be able to do log separation between + DNS, DHCP and TFTP services. +*/ +void my_syslog(int priority, const char *format, ...) +{ + va_list ap; + struct log_entry *entry; + time_t time_now; + char *p; + size_t len; + pid_t pid = getpid(); + char *func = ""; + + if ((LOG_FACMASK & priority) == MS_TFTP) + func = "-tftp"; + else if ((LOG_FACMASK & priority) == MS_DHCP) + func = "-dhcp"; + +#ifdef LOG_PRI + priority = LOG_PRI(priority); +#else + /* Solaris doesn't have LOG_PRI */ + priority &= LOG_PRIMASK; +#endif + + if (echo_stderr) + { + fprintf(stderr, "dnsmasq%s: ", func); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fputc('\n', stderr); + } + + if (log_fd == -1) + { +#ifdef __ANDROID__ + /* do android-specific logging. + log_fd is always -1 on Android except when logging to a file. */ + int alog_lvl; + + if (priority <= LOG_ERR) + alog_lvl = ANDROID_LOG_ERROR; + else if (priority == LOG_WARNING) + alog_lvl = ANDROID_LOG_WARN; + else if (priority <= LOG_INFO) + alog_lvl = ANDROID_LOG_INFO; + else + alog_lvl = ANDROID_LOG_DEBUG; + + va_start(ap, format); + __android_log_vprint(alog_lvl, "dnsmasq", format, ap); + va_end(ap); +#else + /* fall-back to syslog if we die during startup or + fail during running (always on Solaris). */ + static int isopen = 0; + + if (!isopen) + { + openlog("dnsmasq", LOG_PID, log_fac); + isopen = 1; + } + va_start(ap, format); + vsyslog(priority, format, ap); + va_end(ap); +#endif + + return; + } + + if ((entry = free_entries)) + free_entries = entry->next; + else if (entries_alloced < max_logs && (entry = malloc(sizeof(struct log_entry)))) + entries_alloced++; + + if (!entry) + entries_lost++; + else + { + /* add to end of list, consumed from the start */ + entry->next = NULL; + if (!entries) + entries = entry; + else + { + struct log_entry *tmp; + for (tmp = entries; tmp->next; tmp = tmp->next); + tmp->next = entry; + } + + time(&time_now); + p = entry->payload; + if (!log_to_file) + p += sprintf(p, "<%d>", priority | log_fac); + + /* Omit timestamp for default daemontools situation */ + if (!log_stderr || !option_bool(OPT_NO_FORK)) + p += sprintf(p, "%.15s ", ctime(&time_now) + 4); + + p += sprintf(p, "dnsmasq%s[%d]: ", func, (int)pid); + + len = p - entry->payload; + va_start(ap, format); + len += vsnprintf(p, MAX_MESSAGE - len, format, ap) + 1; /* include zero-terminator */ + va_end(ap); + entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len; + entry->offset = 0; + entry->pid = pid; + + /* replace terminator with \n */ + if (log_to_file) + entry->payload[entry->length - 1] = '\n'; + } + + /* almost always, logging won't block, so try and write this now, + to save collecting too many log messages during a select loop. */ + log_write(); + + /* Since we're doing things asynchronously, a cache-dump, for instance, + can now generate log lines very fast. With a small buffer (desirable), + that means it can overflow the log-buffer very quickly, + so that the cache dump becomes mainly a count of how many lines + overflowed. To avoid this, we delay here, the delay is controlled + by queue-occupancy, and grows exponentially. The delay is limited to (2^8)ms. + The scaling stuff ensures that when the queue is bigger than 8, the delay + only occurs for the last 8 entries. Once the queue is full, we stop delaying + to preserve performance. + */ + + if (entries && max_logs != 0) + { + int d; + + for (d = 0,entry = entries; entry; entry = entry->next, d++); + + if (d == max_logs) + d = 0; + else if (max_logs > 8) + d -= max_logs - 8; + + if (d > 0) + { + struct timespec waiter; + waiter.tv_sec = 0; + waiter.tv_nsec = 1000000 << (d - 1); /* 1 ms */ + nanosleep(&waiter, NULL); + + /* Have another go now */ + log_write(); + } + } +} + +void set_log_writer(fd_set *set, int *maxfdp) +{ + if (entries && log_fd != -1 && connection_good) + { + FD_SET(log_fd, set); + bump_maxfd(log_fd, maxfdp); + } +} + +void check_log_writer(fd_set *set) +{ + if (log_fd != -1 && (!set || FD_ISSET(log_fd, set))) + log_write(); +} + +void flush_log(void) +{ + /* write until queue empty, but don't loop forever if there's + no connection to the syslog in existance */ + while (log_fd != -1) + { + struct timespec waiter; + log_write(); + if (!entries || !connection_good) + { + close(log_fd); + break; + } + waiter.tv_sec = 0; + waiter.tv_nsec = 1000000; /* 1 ms */ + nanosleep(&waiter, NULL); + } +} + +void die(char *message, char *arg1, int exit_code) +{ + char *errmess = strerror(errno); + + if (!arg1) + arg1 = errmess; + + if (!log_stderr) + { + echo_stderr = 1; /* print as well as log when we die.... */ + fputc('\n', stderr); /* prettyfy startup-script message */ + } + my_syslog(LOG_CRIT, message, arg1, errmess); + echo_stderr = 0; + my_syslog(LOG_CRIT, _("FAILED to start up")); + flush_log(); + + exit(exit_code); +} diff --git a/src/netlink.c b/src/netlink.c new file mode 100644 index 0000000..f6da7db --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,326 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_LINUX_NETWORK + +#include <linux/types.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +/* linux 2.6.19 buggers up the headers, patch it up here. */ +#ifndef IFA_RTA +# define IFA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) + +# include <linux/if_addr.h> +#endif + +#ifndef NDA_RTA +# define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#endif + +static struct iovec iov; +static u32 netlink_pid; + +static void nl_err(struct nlmsghdr *h); +static void nl_routechange(struct nlmsghdr *h); + +void netlink_init(void) +{ + struct sockaddr_nl addr; + socklen_t slen = sizeof(addr); + + addr.nl_family = AF_NETLINK; + addr.nl_pad = 0; + addr.nl_pid = 0; /* autobind */ +#ifdef HAVE_IPV6 + addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; +#else + addr.nl_groups = RTMGRP_IPV4_ROUTE; +#endif + + /* May not be able to have permission to set multicast groups don't die in that case */ + if ((daemon->netlinkfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != -1) + { + if (bind(daemon->netlinkfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + addr.nl_groups = 0; + if (errno != EPERM || bind(daemon->netlinkfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + daemon->netlinkfd = -1; + } + } + + if (daemon->netlinkfd == -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() */ + netlink_pid = addr.nl_pid; + + iov.iov_len = 100; + iov.iov_base = safe_malloc(iov.iov_len); +} + +static ssize_t netlink_recv(void) +{ + struct msghdr msg; + struct sockaddr_nl nladdr; + ssize_t rc; + + while (1) + { + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + while ((rc = recvmsg(daemon->netlinkfd, &msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); + + /* make buffer big enough */ + if (rc != -1 && (msg.msg_flags & MSG_TRUNC)) + { + /* Very new Linux kernels return the actual size needed, older ones always return truncated size */ + if ((size_t)rc == iov.iov_len) + { + if (expand_buf(&iov, rc + 100)) + continue; + } + else + expand_buf(&iov, rc); + } + + /* read it for real */ + msg.msg_flags = 0; + while ((rc = recvmsg(daemon->netlinkfd, &msg, 0)) == -1 && errno == EINTR); + + /* Make sure this is from the kernel */ + if (rc == -1 || nladdr.nl_pid == 0) + break; + } + + /* discard stuff which is truncated at this point (expand_buf() may fail) */ + if (msg.msg_flags & MSG_TRUNC) + { + rc = -1; + errno = ENOMEM; + } + + return rc; +} + + +/* family = AF_UNSPEC finds ARP table entries. */ +int iface_enumerate(int family, void *parm, int (*callback)()) +{ + struct sockaddr_nl addr; + struct nlmsghdr *h; + ssize_t len; + static unsigned int seq = 0; + + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + + addr.nl_family = AF_NETLINK; + addr.nl_pad = 0; + addr.nl_groups = 0; + addr.nl_pid = 0; /* address to kernel */ + + again: + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = family == AF_UNSPEC ? RTM_GETNEIGH : RTM_GETADDR; + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = ++seq; + req.g.rtgen_family = family; + + /* Don't block in recvfrom if send fails */ + while((len = sendto(daemon->netlinkfd, (void *)&req, sizeof(req), 0, + (struct sockaddr *)&addr, sizeof(addr))) == -1 && retry_send()); + + if (len == -1) + return 0; + + while (1) + { + if ((len = netlink_recv()) == -1) + { + if (errno == ENOBUFS) + { + sleep(1); + goto again; + } + return 0; + } + + 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) + nl_routechange(h); /* May be multicast arriving async */ + else if (h->nlmsg_type == NLMSG_ERROR) + nl_err(h); + else if (h->nlmsg_type == NLMSG_DONE) + return 1; + else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC) + { + struct ifaddrmsg *ifa = NLMSG_DATA(h); + struct rtattr *rta = IFA_RTA(ifa); + unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)); + + if (ifa->ifa_family == family) + { + if (ifa->ifa_family == AF_INET) + { + struct in_addr netmask, addr, broadcast; + + netmask.s_addr = htonl(0xffffffff << (32 - ifa->ifa_prefixlen)); + addr.s_addr = 0; + broadcast.s_addr = 0; + + while (RTA_OK(rta, len1)) + { + if (rta->rta_type == IFA_LOCAL) + addr = *((struct in_addr *)(rta+1)); + else if (rta->rta_type == IFA_BROADCAST) + broadcast = *((struct in_addr *)(rta+1)); + + rta = RTA_NEXT(rta, len1); + } + + if (addr.s_addr) + if (!((*callback)(addr, ifa->ifa_index, netmask, broadcast, parm))) + return 0; + } +#ifdef HAVE_IPV6 + else if (ifa->ifa_family == AF_INET6) + { + struct in6_addr *addrp = NULL; + while (RTA_OK(rta, len1)) + { + if (rta->rta_type == IFA_ADDRESS) + addrp = ((struct in6_addr *)(rta+1)); + + rta = RTA_NEXT(rta, len1); + } + + if (addrp) + if (!((*callback)(addrp, ifa->ifa_index, ifa->ifa_index, parm))) + return 0; + } +#endif + } + } + else if (h->nlmsg_type == RTM_NEWNEIGH && family == AF_UNSPEC) + { + struct ndmsg *neigh = NLMSG_DATA(h); + struct rtattr *rta = NDA_RTA(neigh); + unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh)); + size_t maclen = 0; + char *inaddr = NULL, *mac = NULL; + + while (RTA_OK(rta, len1)) + { + if (rta->rta_type == NDA_DST) + inaddr = (char *)(rta+1); + else if (rta->rta_type == NDA_LLADDR) + { + maclen = rta->rta_len - sizeof(struct rtattr); + mac = (char *)(rta+1); + } + + rta = RTA_NEXT(rta, len1); + } + + if (inaddr && mac) + if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm))) + return 0; + } + } +} + +void netlink_multicast(void) +{ + ssize_t len; + struct nlmsghdr *h; + int flags; + + /* don't risk blocking reading netlink messages here. */ + if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || + fcntl(daemon->netlinkfd, F_SETFL, flags | O_NONBLOCK) == -1) + return; + + if ((len = netlink_recv()) != -1) + { + for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) + if (h->nlmsg_type == NLMSG_ERROR) + nl_err(h); + else + nl_routechange(h); + } + + /* restore non-blocking status */ + fcntl(daemon->netlinkfd, F_SETFL, flags); +} + +static void nl_err(struct nlmsghdr *h) +{ + struct nlmsgerr *err = NLMSG_DATA(h); + + if (err->error != 0) + my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); +} + +/* We arrange to receive netlink multicast messages whenever the network route is added. + If this happens and we still have a DNS packet in the buffer, we re-send it. + This helps on DoD links, where frequently the packet which triggers dialling is + a DNS query, which then gets lost. By re-sending, we can avoid the lookup + failing. Note that we only accept these messages from the kernel (pid == 0) */ +static void nl_routechange(struct nlmsghdr *h) +{ + if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) + { + struct rtmsg *rtm = NLMSG_DATA(h); + int fd; + + if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK) + return; + + /* Force re-reading resolv file right now, for luck. */ + daemon->last_resolv = 0; + + if (daemon->srv_save) + { + if (daemon->srv_save->sfd) + fd = daemon->srv_save->sfd->fd; + else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) + fd = daemon->rfd_save->fd; + else + return; + + while(sendto(fd, daemon->packet, daemon->packet_len, 0, + &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send()); + } + } +} + +#endif + + diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..7b2e905 --- /dev/null +++ b/src/network.c @@ -0,0 +1,973 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_LINUX_NETWORK + +int indextoname(int fd, int index, char *name) +{ + struct ifreq ifr; + + if (index == 0) + return 0; + + ifr.ifr_ifindex = index; + if (ioctl(fd, SIOCGIFNAME, &ifr) == -1) + return 0; + + strncpy(name, ifr.ifr_name, IF_NAMESIZE); + + return 1; +} + + +#elif defined(HAVE_SOLARIS_NETWORK) + +#include <zone.h> +#include <alloca.h> +#ifndef LIFC_UNDER_IPMP +# define LIFC_UNDER_IPMP 0 +#endif + +int indextoname(int fd, int index, char *name) +{ + int64_t lifc_flags; + struct lifnum lifn; + int numifs, bufsize, i; + struct lifconf lifc; + struct lifreq *lifrp; + + if (index == 0) + return 0; + + if (getzoneid() == GLOBAL_ZONEID) + { + if (!if_indextoname(index, name)) + return 0; + return 1; + } + + lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES | LIFC_UNDER_IPMP; + lifn.lifn_family = AF_UNSPEC; + lifn.lifn_flags = lifc_flags; + if (ioctl(fd, SIOCGLIFNUM, &lifn) < 0) + return 0; + + numifs = lifn.lifn_count; + bufsize = numifs * sizeof(struct lifreq); + + lifc.lifc_family = AF_UNSPEC; + lifc.lifc_flags = lifc_flags; + lifc.lifc_len = bufsize; + lifc.lifc_buf = alloca(bufsize); + + if (ioctl(fd, SIOCGLIFCONF, &lifc) < 0) + return 0; + + lifrp = lifc.lifc_req; + for (i = lifc.lifc_len / sizeof(struct lifreq); i; i--, lifrp++) + { + struct lifreq lifr; + strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE); + if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0) + return 0; + + if (lifr.lifr_index == index) { + strncpy(name, lifr.lifr_name, IF_NAMESIZE); + return 1; + } + } + return 0; +} + + +#else + +int indextoname(int fd, int index, char *name) +{ + if (index == 0 || !if_indextoname(index, name)) + return 0; + + return 1; +} + +#endif + +int iface_check(int family, struct all_addr *addr, char *name, int *indexp) +{ + struct iname *tmp; + int ret = 1; + + /* Note: have to check all and not bail out early, so that we set the + "used" flags. */ + + if (daemon->if_names || (addr && daemon->if_addrs)) + { +#ifdef HAVE_DHCP + struct dhcp_context *range; +#endif + + ret = 0; + +#ifdef HAVE_DHCP + for (range = daemon->dhcp; range; range = range->next) + if (range->interface && strcmp(range->interface, name) == 0) + ret = 1; +#endif + + for (tmp = daemon->if_names; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, name) == 0)) + ret = tmp->used = 1; + + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (addr && tmp->addr.sa.sa_family == family) + { + if (family == AF_INET && + tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + ret = tmp->used = 1; +#ifdef HAVE_IPV6 + else if (family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, + &addr->addr.addr6)) + ret = tmp->used = 1; +#endif + } + } + + for (tmp = daemon->if_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, name) == 0)) + ret = 0; + + if (indexp) + { + /* One form of bridging on BSD has the property that packets + can be recieved on bridge interfaces which do not have an IP address. + We allow these to be treated as aliases of another interface which does have + an IP address with --dhcp-bridge=interface,alias,alias */ + struct dhcp_bridge *bridge, *alias; + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (strncmp(name, alias->iface, IF_NAMESIZE) == 0) + { + int newindex; + + if (!(newindex = if_nametoindex(bridge->iface))) + { + my_syslog(LOG_WARNING, _("unknown interface %s in bridge-interface"), name); + return 0; + } + else + { + *indexp = newindex; + strncpy(name, bridge->iface, IF_NAMESIZE); + break; + } + } + if (alias) + break; + } + } + + return ret; +} + +static int iface_allowed(struct irec **irecp, int if_index, + union mysockaddr *addr, struct in_addr netmask) +{ + struct irec *iface; + int fd, mtu = 0, loopback; + struct ifreq ifr; + int tftp_ok = daemon->tftp_unlimited; +#ifdef HAVE_DHCP + struct iname *tmp; +#endif + struct interface_list *ir = NULL; + + /* check whether the interface IP has been added already + we call this routine multiple times. */ + for (iface = *irecp; iface; iface = iface->next) + if (sockaddr_isequal(&iface->addr, addr)) + return 1; + + if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1 || + !indextoname(fd, if_index, ifr.ifr_name) || + ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) + { + if (fd != -1) + { + int errsave = errno; + close(fd); + errno = errsave; + } + return 0; + } + + loopback = ifr.ifr_flags & IFF_LOOPBACK; + + if (ioctl(fd, SIOCGIFMTU, &ifr) != -1) + mtu = ifr.ifr_mtu; + + close(fd); + + /* If we are restricting the set of interfaces to use, make + sure that loopback interfaces are in that set. */ + if (daemon->if_names && loopback) + { + struct iname *lo; + for (lo = daemon->if_names; lo; lo = lo->next) + if (lo->name && strcmp(lo->name, ifr.ifr_name) == 0) + { + lo->isloop = 1; + break; + } + + if (!lo && + (lo = whine_malloc(sizeof(struct iname))) && + (lo->name = whine_malloc(strlen(ifr.ifr_name)+1))) + { + strcpy(lo->name, ifr.ifr_name); + lo->isloop = lo->used = 1; + lo->next = daemon->if_names; + daemon->if_names = lo; + } + } + +#ifdef HAVE_TFTP + /* implement wierd TFTP service rules */ + for (ir = daemon->tftp_interfaces; ir; ir = ir->next) + if (strcmp(ir->interface, ifr.ifr_name) == 0) + { + tftp_ok = 1; + break; + } +#endif + + if (!ir) + { + if (addr->sa.sa_family == AF_INET && + !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, ifr.ifr_name, NULL)) + return 1; + +#ifdef HAVE_DHCP + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) + tftp_ok = 0; +#endif + +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && + !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, ifr.ifr_name, NULL)) + return 1; +#endif + } + + /* add to list */ + if ((iface = whine_malloc(sizeof(struct irec)))) + { + iface->addr = *addr; + iface->netmask = netmask; + iface->tftp_ok = tftp_ok; + iface->mtu = mtu; + if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1))) + strcpy(iface->name, ifr.ifr_name); + iface->next = *irecp; + *irecp = iface; + return 1; + } + + errno = ENOMEM; + return 0; +} + +#ifdef HAVE_IPV6 +static int iface_allowed_v6(struct in6_addr *local, + int scope, int if_index, void *vparam) +{ + union mysockaddr addr; + struct in_addr netmask; /* dummy */ + + netmask.s_addr = 0; + + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(addr.in6); +#endif + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_addr = *local; + addr.in6.sin6_port = htons(daemon->port); + addr.in6.sin6_scope_id = scope; + + return iface_allowed((struct irec **)vparam, if_index, &addr, netmask); +} +#endif + +static int iface_allowed_v4(struct in_addr local, int if_index, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + union mysockaddr addr; + + 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); + + return iface_allowed((struct irec **)vparam, if_index, &addr, netmask); +} + +int enumerate_interfaces(void) +{ +#ifdef HAVE_IPV6 + if (!iface_enumerate(AF_INET6, &daemon->interfaces, iface_allowed_v6)) + return 0; +#endif + + return iface_enumerate(AF_INET, &daemon->interfaces, iface_allowed_v4); +} + +/* set NONBLOCK bit on fd: See Stevens 16.6 */ +int fix_fd(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + return 0; + + return 1; +} + +static int make_sock(union mysockaddr *addr, int type) +{ + int family = addr->sa.sa_family; + int fd, rc, opt = 1; +#ifdef HAVE_IPV6 + static int dad_count = 0; +#endif + + if ((fd = socket(family, type, 0)) == -1) + { + int port; + + /* No error if the kernel just doesn't support this IP flavour */ + if (errno == EPROTONOSUPPORT || + errno == EAFNOSUPPORT || + errno == EINVAL) + return -1; + + err: + port = prettyprint_addr(addr, daemon->namebuff); + if (!option_bool(OPT_NOWILD)) + sprintf(daemon->namebuff, "port %d", port); + die(_("failed to create listening socket for %s: %s"), + daemon->namebuff, EC_BADNET); + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd)) + goto err; + +#ifdef HAVE_IPV6 + if (family == AF_INET6 && setsockopt(fd, IPV6_LEVEL, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) + goto err; +#endif + + while (1) + { + if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) != -1) + break; + +#ifdef HAVE_IPV6 + /* An interface may have an IPv6 address which is still undergoing DAD. + If so, the bind will fail until the DAD completes, so we try over 20 seconds + before failing. */ + if (family == AF_INET6 && + (errno == ENODEV || errno == EADDRNOTAVAIL) && + dad_count++ < DAD_WAIT) + { + sleep(1); + continue; + } +#endif + break; + } + + if (rc == -1) + goto err; + + if (type == SOCK_STREAM) + { + if (listen(fd, 5) == -1) + goto err; + } + else if (!option_bool(OPT_NOWILD)) + { + if (family == AF_INET) + { +#if defined(HAVE_LINUX_NETWORK) + if (setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1) + goto err; +#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)) == -1) + goto err; +#endif + } +#ifdef HAVE_IPV6 + else + { + /* The API changed around Linux 2.6.14 but the old ABI is still supported: + handle all combinations of headers and kernel. + OpenWrt note that this fixes the problem addressed by your very broken patch. */ + daemon->v6pktinfo = IPV6_PKTINFO; + +# ifdef IPV6_RECVPKTINFO +# ifdef IPV6_2292PKTINFO + if (setsockopt(fd, IPV6_LEVEL, IPV6_RECVPKTINFO, &opt, sizeof(opt)) == -1) + { + if (errno == ENOPROTOOPT && setsockopt(fd, IPV6_LEVEL, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1) + daemon->v6pktinfo = IPV6_2292PKTINFO; + else + goto err; + } +# else + if (setsockopt(fd, IPV6_LEVEL, IPV6_RECVPKTINFO, &opt, sizeof(opt)) == -1) + goto err; +# endif +# else + if (setsockopt(fd, IPV6_LEVEL, IPV6_PKTINFO, &opt, sizeof(opt)) == -1) + goto err; +# endif + } +#endif + } + + return fd; +} + +static struct listener *create_listeners(union mysockaddr *addr, int do_tftp) +{ + struct listener *l = NULL; + int fd = -1, tcpfd = -1, tftpfd = -1; + + if (daemon->port != 0) + { + fd = make_sock(addr, SOCK_DGRAM); + tcpfd = make_sock(addr, SOCK_STREAM); + } + +#ifdef HAVE_TFTP + if (do_tftp) + { + if (addr->sa.sa_family == AF_INET) + { + /* port must be restored to DNS port for TCP code */ + short save = addr->in.sin_port; + addr->in.sin_port = htons(TFTP_PORT); + tftpfd = make_sock(addr, SOCK_DGRAM); + addr->in.sin_port = save; + } +# ifdef HAVE_IPV6 + else + { + short save = addr->in6.sin6_port; + addr->in6.sin6_port = htons(TFTP_PORT); + tftpfd = make_sock(addr, SOCK_DGRAM); + addr->in6.sin6_port = save; + } +# endif + } +#endif + + if (fd != -1 || tcpfd != -1 || tftpfd != -1) + { + l = safe_malloc(sizeof(struct listener)); + l->next = NULL; + l->family = addr->sa.sa_family; + l->fd = fd; + l->tcpfd = tcpfd; + l->tftpfd = tftpfd; + } + + return l; +} + +struct listener *create_wildcard_listeners(void) +{ + union mysockaddr addr; + struct listener *l; + int tftp_enabled = daemon->tftp_unlimited || daemon->tftp_interfaces; + + 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.s_addr = INADDR_ANY; + addr.in.sin_port = htons(daemon->port); + + l = create_listeners(&addr, tftp_enabled); + +#ifdef HAVE_IPV6 + memset(&addr, 0, sizeof(addr)); +# ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(addr.in6); +# endif + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_addr = in6addr_any; + addr.in6.sin6_port = htons(daemon->port); + + if (l) + l->next = create_listeners(&addr, tftp_enabled); + else + l = create_listeners(&addr, tftp_enabled); +#endif + + return l; +} + +struct listener *create_bound_listeners(void) +{ + struct listener *new, *listeners = NULL; + struct irec *iface; + + for (iface = daemon->interfaces; iface; iface = iface->next) + if ((new = create_listeners(&iface->addr, iface->tftp_ok))) + { + new->iface = iface; + new->next = listeners; + listeners = new; + } + + return listeners; +} + + +/* return a UDP socket bound to a random port, have to cope with straying into + occupied port nos and reserved ones. */ +int random_sock(int family) +{ + int fd; + + if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) + { + union mysockaddr addr; + unsigned int ports_avail = 65536u - (unsigned short)daemon->min_port; + int tries = ports_avail < 30 ? 3 * ports_avail : 100; + + memset(&addr, 0, sizeof(addr)); + addr.sa.sa_family = family; + + /* don't loop forever if all ports in use. */ + + 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))); + + if (family == AF_INET) + { + addr.in.sin_addr.s_addr = INADDR_ANY; + addr.in.sin_port = port; +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); +#endif + } +#ifdef HAVE_IPV6 + else + { + addr.in6.sin6_addr = in6addr_any; + addr.in6.sin6_port = port; +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + } +#endif + + if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) + return fd; + + if (errno != EADDRINUSE && errno != EACCES) + break; + } + + close(fd); + } + + return -1; +} + + +int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp) +{ + union mysockaddr addr_copy = *addr; + + /* cannot set source _port_ for TCP connections. */ + if (is_tcp) + { + if (addr_copy.sa.sa_family == AF_INET) + addr_copy.in.sin_port = 0; +#ifdef HAVE_IPV6 + else + addr_copy.in6.sin6_port = 0; +#endif + } + + if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1) + return 0; + +#if defined(SO_BINDTODEVICE) + if (intname[0] != 0 && + setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE) == -1) + return 0; +#endif + + return 1; +} + +static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) +{ + struct serverfd *sfd; + int errsave; + + /* when using random ports, servers which would otherwise use + the INADDR_ANY/port0 socket have sfd set to NULL */ + if (!daemon->osport && intname[0] == 0) + { + errno = 0; + + if (addr->sa.sa_family == AF_INET && + addr->in.sin_addr.s_addr == INADDR_ANY && + addr->in.sin_port == htons(0)) + return NULL; + +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && + memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && + addr->in6.sin6_port == htons(0)) + return NULL; +#endif + } + + /* 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) + return sfd; + + /* need to make a new one. */ + errno = ENOMEM; /* in case malloc fails. */ + if (!(sfd = whine_malloc(sizeof(struct serverfd)))) + return NULL; + + if ((sfd->fd = socket(addr->sa.sa_family, SOCK_DGRAM, 0)) == -1) + { + free(sfd); + return NULL; + } + + if (!local_bind(sfd->fd, addr, intname, 0) || !fix_fd(sfd->fd)) + { + errsave = errno; /* save error from bind. */ + close(sfd->fd); + free(sfd); + errno = errsave; + return NULL; + } + + strcpy(sfd->interface, intname); + sfd->source_addr = *addr; + sfd->next = daemon->sfds; + daemon->sfds = sfd; + return sfd; +} + +/* create upstream sockets during startup, before root is dropped which may be needed + this allows query_port to be a low port and interface binding */ +void pre_allocate_sfds(void) +{ + struct server *srv; + + if (daemon->query_port != 0) + { + union mysockaddr addr; + memset(&addr, 0, sizeof(addr)); + addr.in.sin_family = AF_INET; + addr.in.sin_addr.s_addr = INADDR_ANY; + addr.in.sin_port = htons(daemon->query_port); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); +#endif + allocate_sfd(&addr, ""); +#ifdef HAVE_IPV6 + memset(&addr, 0, sizeof(addr)); + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_addr = in6addr_any; + addr.in6.sin6_port = htons(daemon->query_port); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + allocate_sfd(&addr, ""); +#endif + } + + for (srv = daemon->servers; srv; srv = srv->next) + if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && + !allocate_sfd(&srv->source_addr, srv->interface) && + errno != 0 && + option_bool(OPT_NOWILD)) + { + prettyprint_addr(&srv->source_addr, daemon->namebuff); + if (srv->interface[0] != 0) + { + strcat(daemon->namebuff, " "); + strcat(daemon->namebuff, srv->interface); + } + die(_("failed to bind server socket for %s: %s"), + daemon->namebuff, EC_BADNET); + } +} + + +void check_servers(void) +{ + struct irec *iface; + struct server *new, *tmp, *ret = NULL; + int port = 0; + + /* interface may be new since startup */ + if (!option_bool(OPT_NOWILD)) + enumerate_interfaces(); + + for (new = daemon->servers; new; new = tmp) + { + tmp = new->next; + + if (!(new->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) + { + port = prettyprint_addr(&new->addr, daemon->namebuff); + + /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ + if (new->addr.sa.sa_family == AF_INET && + new->addr.in.sin_addr.s_addr == 0) + { + free(new); + continue; + } + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&new->addr, &iface->addr)) + break; + if (iface) + { + my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff); + free(new); + continue; + } + + /* Do we need a socket set? */ + if (!new->sfd && + !(new->sfd = allocate_sfd(&new->source_addr, new->interface)) && + errno != 0) + { + my_syslog(LOG_WARNING, + _("ignoring nameserver %s - cannot make/bind socket: %s"), + daemon->namebuff, strerror(errno)); + free(new); + continue; + } + } + + /* reverse order - gets it right. */ + new->next = ret; + ret = new; + + if (!(new->flags & SERV_NO_REBIND)) + { + if (new->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) + { + char *s1, *s2; + if (!(new->flags & SERV_HAS_DOMAIN)) + s1 = _("unqualified"), s2 = _("names"); + else if (strlen(new->domain) == 0) + s1 = _("default"), s2 = ""; + else + s1 = _("domain"), s2 = new->domain; + + if (new->flags & SERV_NO_ADDR) + my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2); + else if (new->flags & SERV_USE_RESOLV) + my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2); + else if (!(new->flags & SERV_LITERAL_ADDRESS)) + my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2); + } + else if (new->interface[0] != 0) + my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, new->interface); + else + my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); + } + } + + daemon->servers = ret; +} + +/* Return zero if no servers found, in that case we keep polling. + This is a protection against an update-time/write race on resolv.conf */ +int reload_servers(char *fname) +{ + FILE *f; + char *line; + struct server *old_servers = NULL; + struct server *new_servers = NULL; + struct server *serv; + int gotone = 0; + + /* buff happens to be MAXDNAME long... */ + if (!(f = fopen(fname, "r"))) + { + my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno)); + return 0; + } + + /* move old servers to free list - we can reuse the memory + and not risk malloc if there are the same or fewer new servers. + Servers which were specced on the command line go to the new list. */ + for (serv = daemon->servers; serv;) + { + struct server *tmp = serv->next; + if (serv->flags & SERV_FROM_RESOLV) + { + serv->next = old_servers; + old_servers = serv; + /* forward table rules reference servers, so have to blow them away */ + server_gone(serv); + } + else + { + serv->next = new_servers; + new_servers = serv; + } + serv = tmp; + } + + while ((line = fgets(daemon->namebuff, MAXDNAME, f))) + { + union mysockaddr addr, source_addr; + char *token = strtok(line, " \t\n\r"); + + if (!token) + continue; + if (strcmp(token, "nameserver") != 0 && strcmp(token, "server") != 0) + continue; + if (!(token = strtok(NULL, " \t\n\r"))) + continue; + + memset(&addr, 0, sizeof(addr)); + memset(&source_addr, 0, sizeof(source_addr)); + + if ((addr.in.sin_addr.s_addr = inet_addr(token)) != (in_addr_t) -1) + { +#ifdef HAVE_SOCKADDR_SA_LEN + source_addr.in.sin_len = addr.in.sin_len = sizeof(source_addr.in); +#endif + source_addr.in.sin_family = addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(NAMESERVER_PORT); + source_addr.in.sin_addr.s_addr = INADDR_ANY; + source_addr.in.sin_port = htons(daemon->query_port); + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, token, &addr.in6.sin6_addr) > 0) + { +#ifdef HAVE_SOCKADDR_SA_LEN + source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(source_addr.in6); +#endif + source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(NAMESERVER_PORT); + source_addr.in6.sin6_addr = in6addr_any; + source_addr.in6.sin6_port = htons(daemon->query_port); + } +#endif /* IPV6 */ + else + continue; + + if (old_servers) + { + serv = old_servers; + old_servers = old_servers->next; + } + else if (!(serv = whine_malloc(sizeof (struct server)))) + continue; + + /* this list is reverse ordered: + it gets reversed again in check_servers */ + serv->next = new_servers; + new_servers = serv; + serv->addr = addr; + serv->source_addr = source_addr; + serv->domain = NULL; + serv->interface[0] = 0; + serv->sfd = NULL; + serv->flags = SERV_FROM_RESOLV; + serv->queries = serv->failed_queries = 0; + gotone = 1; + } + + /* Free any memory not used. */ + while (old_servers) + { + struct server *tmp = old_servers->next; + free(old_servers); + old_servers = tmp; + } + + daemon->servers = new_servers; + fclose(f); + + return gotone; +} + + +/* Use an IPv4 listener socket for ioctling */ +struct in_addr get_ifaddr(char *intr) +{ + struct listener *l; + struct ifreq ifr; + struct sockaddr_in ret; + + ret.sin_addr.s_addr = -1; + + for (l = daemon->listeners; + l && (l->family != AF_INET || l->fd == -1); + l = l->next); + + strncpy(ifr.ifr_name, intr, IF_NAMESIZE); + ifr.ifr_addr.sa_family = AF_INET; + + if (l && ioctl(l->fd, SIOCGIFADDR, &ifr) != -1) + memcpy(&ret, &ifr.ifr_addr, sizeof(ret)); + + return ret.sin_addr; +} + + + diff --git a/src/option.c b/src/option.c new file mode 100644 index 0000000..4cee0a2 --- /dev/null +++ b/src/option.c @@ -0,0 +1,3416 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +/* define this to get facilitynames */ +#define SYSLOG_NAMES +#include "dnsmasq.h" +#include <setjmp.h> + +static volatile int mem_recover = 0; +static jmp_buf mem_jmp; +static void one_file(char *file, int hard_opt); + +/* Solaris headers don't have facility names. */ +#ifdef HAVE_SOLARIS_NETWORK +static const struct { + char *c_name; + unsigned int c_val; +} facilitynames[] = { + { "kern", LOG_KERN }, + { "user", LOG_USER }, + { "mail", LOG_MAIL }, + { "daemon", LOG_DAEMON }, + { "auth", LOG_AUTH }, + { "syslog", LOG_SYSLOG }, + { "lpr", LOG_LPR }, + { "news", LOG_NEWS }, + { "uucp", LOG_UUCP }, + { "audit", LOG_AUDIT }, + { "cron", LOG_CRON }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL, 0 } +}; +#endif + +#ifndef HAVE_GETOPT_LONG +struct myoption { + const char *name; + int has_arg; + int *flag; + int val; +}; +#endif + +#define OPTSTRING "951yZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:W:Y:2:4:6:7:8:0:3:" + +/* options which don't have a one-char version */ +#define LOPT_RELOAD 256 +#define LOPT_NO_NAMES 257 +#define LOPT_TFTP 258 +#define LOPT_SECURE 259 +#define LOPT_PREFIX 260 +#define LOPT_PTR 261 +#define LOPT_BRIDGE 262 +#define LOPT_TFTP_MAX 263 +#define LOPT_FORCE 264 +#define LOPT_NOBLOCK 265 +#define LOPT_LOG_OPTS 266 +#define LOPT_MAX_LOGS 267 +#define LOPT_CIRCUIT 268 +#define LOPT_REMOTE 269 +#define LOPT_SUBSCR 270 +#define LOPT_INTNAME 271 +#define LOPT_BANK 272 +#define LOPT_DHCP_HOST 273 +#define LOPT_APREF 274 +#define LOPT_OVERRIDE 275 +#define LOPT_TFTPPORTS 276 +#define LOPT_REBIND 277 +#define LOPT_NOLAST 278 +#define LOPT_OPTS 279 +#define LOPT_DHCP_OPTS 280 +#define LOPT_MATCH 281 +#define LOPT_BROADCAST 282 +#define LOPT_NEGTTL 283 +#define LOPT_ALTPORT 284 +#define LOPT_SCRIPTUSR 285 +#define LOPT_LOCAL 286 +#define LOPT_NAPTR 287 +#define LOPT_MINPORT 288 +#define LOPT_DHCP_FQDN 289 +#define LOPT_CNAME 290 +#define LOPT_PXE_PROMT 291 +#define LOPT_PXE_SERV 292 +#define LOPT_TEST 293 +#define LOPT_TAG_IF 294 +#define LOPT_PROXY 295 +#define LOPT_GEN_NAMES 296 +#define LOPT_MAXTTL 297 +#define LOPT_NO_REBIND 298 +#define LOPT_LOC_REBND 299 +#define LOPT_ADD_MAC 300 +#define LOPT_DNSSEC 301 + +#ifdef HAVE_GETOPT_LONG +static const struct option opts[] = +#else +static const struct myoption opts[] = +#endif + { + { "version", 0, 0, 'v' }, + { "no-hosts", 0, 0, 'h' }, + { "no-poll", 0, 0, 'n' }, + { "help", 0, 0, 'w' }, + { "no-daemon", 0, 0, 'd' }, + { "log-queries", 0, 0, 'q' }, + { "user", 2, 0, 'u' }, + { "group", 2, 0, 'g' }, + { "resolv-file", 2, 0, 'r' }, + { "mx-host", 1, 0, 'm' }, + { "mx-target", 1, 0, 't' }, + { "cache-size", 2, 0, 'c' }, + { "port", 1, 0, 'p' }, + { "dhcp-leasefile", 2, 0, 'l' }, + { "dhcp-lease", 1, 0, 'l' }, + { "dhcp-host", 1, 0, 'G' }, + { "dhcp-range", 1, 0, 'F' }, + { "dhcp-option", 1, 0, 'O' }, + { "dhcp-boot", 1, 0, 'M' }, + { "domain", 1, 0, 's' }, + { "domain-suffix", 1, 0, 's' }, + { "interface", 1, 0, 'i' }, + { "listen-address", 1, 0, 'a' }, + { "bogus-priv", 0, 0, 'b' }, + { "bogus-nxdomain", 1, 0, 'B' }, + { "selfmx", 0, 0, 'e' }, + { "filterwin2k", 0, 0, 'f' }, + { "pid-file", 2, 0, 'x' }, + { "strict-order", 0, 0, 'o' }, + { "server", 1, 0, 'S' }, + { "local", 1, 0, LOPT_LOCAL }, + { "address", 1, 0, 'A' }, + { "conf-file", 2, 0, 'C' }, + { "no-resolv", 0, 0, 'R' }, + { "expand-hosts", 0, 0, 'E' }, + { "localmx", 0, 0, 'L' }, + { "local-ttl", 1, 0, 'T' }, + { "no-negcache", 0, 0, 'N' }, + { "addn-hosts", 1, 0, 'H' }, + { "query-port", 1, 0, 'Q' }, + { "except-interface", 1, 0, 'I' }, + { "no-dhcp-interface", 1, 0, '2' }, + { "domain-needed", 0, 0, 'D' }, + { "dhcp-lease-max", 1, 0, 'X' }, + { "bind-interfaces", 0, 0, 'z' }, + { "read-ethers", 0, 0, 'Z' }, + { "alias", 1, 0, 'V' }, + { "dhcp-vendorclass", 1, 0, 'U' }, + { "dhcp-userclass", 1, 0, 'j' }, + { "dhcp-ignore", 1, 0, 'J' }, + { "edns-packet-max", 1, 0, 'P' }, + { "keep-in-foreground", 0, 0, 'k' }, + { "dhcp-authoritative", 0, 0, 'K' }, + { "srv-host", 1, 0, 'W' }, + { "localise-queries", 0, 0, 'y' }, + { "txt-record", 1, 0, 'Y' }, + { "enable-dbus", 0, 0, '1' }, + { "bootp-dynamic", 2, 0, '3' }, + { "dhcp-mac", 1, 0, '4' }, + { "no-ping", 0, 0, '5' }, + { "dhcp-script", 1, 0, '6' }, + { "conf-dir", 1, 0, '7' }, + { "log-facility", 1, 0 ,'8' }, + { "leasefile-ro", 0, 0, '9' }, + { "dns-forward-max", 1, 0, '0' }, + { "clear-on-reload", 0, 0, LOPT_RELOAD }, + { "dhcp-ignore-names", 2, 0, LOPT_NO_NAMES }, + { "enable-tftp", 2, 0, LOPT_TFTP }, + { "tftp-secure", 0, 0, LOPT_SECURE }, + { "tftp-unique-root", 0, 0, LOPT_APREF }, + { "tftp-root", 1, 0, LOPT_PREFIX }, + { "tftp-max", 1, 0, LOPT_TFTP_MAX }, + { "ptr-record", 1, 0, LOPT_PTR }, + { "naptr-record", 1, 0, LOPT_NAPTR }, + { "bridge-interface", 1, 0 , LOPT_BRIDGE }, + { "dhcp-option-force", 1, 0, LOPT_FORCE }, + { "tftp-no-blocksize", 0, 0, LOPT_NOBLOCK }, + { "log-dhcp", 0, 0, LOPT_LOG_OPTS }, + { "log-async", 2, 0, LOPT_MAX_LOGS }, + { "dhcp-circuitid", 1, 0, LOPT_CIRCUIT }, + { "dhcp-remoteid", 1, 0, LOPT_REMOTE }, + { "dhcp-subscrid", 1, 0, LOPT_SUBSCR }, + { "interface-name", 1, 0, LOPT_INTNAME }, + { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST }, + { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS }, + { "dhcp-no-override", 0, 0, LOPT_OVERRIDE }, + { "tftp-port-range", 1, 0, LOPT_TFTPPORTS }, + { "stop-dns-rebind", 0, 0, LOPT_REBIND }, + { "rebind-domain-ok", 1, 0, LOPT_NO_REBIND }, + { "all-servers", 0, 0, LOPT_NOLAST }, + { "dhcp-match", 1, 0, LOPT_MATCH }, + { "dhcp-broadcast", 2, 0, LOPT_BROADCAST }, + { "neg-ttl", 1, 0, LOPT_NEGTTL }, + { "max-ttl", 1, 0, LOPT_MAXTTL }, + { "dhcp-alternate-port", 2, 0, LOPT_ALTPORT }, + { "dhcp-scriptuser", 1, 0, LOPT_SCRIPTUSR }, + { "min-port", 1, 0, LOPT_MINPORT }, + { "dhcp-fqdn", 0, 0, LOPT_DHCP_FQDN }, + { "cname", 1, 0, LOPT_CNAME }, + { "pxe-prompt", 1, 0, LOPT_PXE_PROMT }, + { "pxe-service", 1, 0, LOPT_PXE_SERV }, + { "test", 0, 0, LOPT_TEST }, + { "tag-if", 1, 0, LOPT_TAG_IF }, + { "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 }, + { "proxy-dnssec", 0, 0, LOPT_DNSSEC }, + { NULL, 0, 0, 0 } + }; + + +#define ARG_DUP OPT_LAST +#define ARG_ONE OPT_LAST + 1 +#define ARG_USED_CL OPT_LAST + 2 +#define ARG_USED_FILE OPT_LAST + 3 + +static struct { + int opt; + unsigned int rept; + char * const flagdesc; + char * const desc; + char * const arg; +} usage[] = { + { 'a', ARG_DUP, "ipaddr", gettext_noop("Specify local address(es) to listen on."), NULL }, + { 'A', ARG_DUP, "/domain/ipaddr", gettext_noop("Return ipaddr for all hosts in specified domains."), NULL }, + { 'b', OPT_BOGUSPRIV, NULL, gettext_noop("Fake reverse lookups for RFC1918 private address ranges."), NULL }, + { 'B', ARG_DUP, "ipaddr", gettext_noop("Treat ipaddr as NXDOMAIN (defeats Verisign wildcard)."), NULL }, + { 'c', ARG_ONE, "cachesize", gettext_noop("Specify the size of the cache in entries (defaults to %s)."), "$" }, + { 'C', ARG_DUP, "path", gettext_noop("Specify configuration file (defaults to %s)."), CONFFILE }, + { 'd', OPT_DEBUG, NULL, gettext_noop("Do NOT fork into the background: run in debug mode."), NULL }, + { 'D', OPT_NODOTS_LOCAL, NULL, gettext_noop("Do NOT forward queries with no domain part."), NULL }, + { 'e', OPT_SELFMX, NULL, gettext_noop("Return self-pointing MX records for local hosts."), NULL }, + { 'E', OPT_EXPAND, NULL, gettext_noop("Expand simple names in /etc/hosts with domain-suffix."), NULL }, + { 'f', OPT_FILTER, NULL, gettext_noop("Don't forward spurious DNS requests from Windows hosts."), NULL }, + { 'F', ARG_DUP, "ipaddr,ipaddr,time", gettext_noop("Enable DHCP in the range given with lease duration."), NULL }, + { 'g', ARG_ONE, "groupname", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP }, + { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL }, + { LOPT_DHCP_HOST, ARG_DUP, "<filename>", gettext_noop("Read DHCP host specs from file."), NULL }, + { LOPT_DHCP_OPTS, ARG_DUP, "<filename>", gettext_noop("Read DHCP option specs from file."), NULL }, + { LOPT_TAG_IF, ARG_DUP, "tag-expression", gettext_noop("Evaluate conditional tag expression."), NULL }, + { 'h', OPT_NO_HOSTS, NULL, gettext_noop("Do NOT load %s file."), HOSTSFILE }, + { 'H', ARG_DUP, "path", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE }, + { 'i', ARG_DUP, "interface", gettext_noop("Specify interface(s) to listen on."), NULL }, + { 'I', ARG_DUP, "int", gettext_noop("Specify interface(s) NOT to listen on.") , NULL }, + { 'j', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP user class to tag."), NULL }, + { LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL }, + { LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL }, + { LOPT_SUBSCR, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3993 subscriber-id to tag."), NULL }, + { 'J', ARG_DUP, "tag:<tag>...", gettext_noop("Don't do DHCP for hosts with tag set."), NULL }, + { LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL }, + { 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL }, + { 'K', OPT_AUTHORITATIVE, NULL, gettext_noop("Assume we are the only DHCP server on the local network."), NULL }, + { 'l', ARG_ONE, "path", gettext_noop("Specify where to store DHCP leases (defaults to %s)."), LEASEFILE }, + { 'L', OPT_LOCALMX, NULL, gettext_noop("Return MX records for local hosts."), NULL }, + { 'm', ARG_DUP, "host_name,target,pref", gettext_noop("Specify an MX record."), NULL }, + { 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL }, + { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, + { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL }, + { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, + { 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, + { LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, + { 'p', ARG_ONE, "number", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL }, + { 'P', ARG_ONE, "<size>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, + { 'q', OPT_LOG, NULL, gettext_noop("Log DNS queries."), NULL }, + { 'Q', ARG_ONE, "number", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, + { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL }, + { 'r', ARG_DUP, "path", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, + { 'S', ARG_DUP, "/domain/ipaddr", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL }, + { LOPT_LOCAL, ARG_DUP, "/domain/", gettext_noop("Never forward queries to specified domains."), NULL }, + { 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL }, + { 't', ARG_ONE, "host_name", gettext_noop("Specify default target in an MX record."), NULL }, + { 'T', ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for replies from /etc/hosts."), NULL }, + { LOPT_NEGTTL, ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for negative caching."), NULL }, + { LOPT_MAXTTL, ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL }, + { 'u', ARG_ONE, "username", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, + { 'U', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP vendor class to tag."), NULL }, + { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL }, + { 'V', ARG_DUP, "addr,addr,mask", 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 }, + { 'x', ARG_ONE, "path", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE }, + { 'X', ARG_ONE, "number", 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 }, + { 'Y', ARG_DUP, "name,txt....", gettext_noop("Specify TXT DNS record."), NULL }, + { LOPT_PTR, ARG_DUP, "name,target", gettext_noop("Specify PTR DNS record."), NULL }, + { LOPT_INTNAME, ARG_DUP, "name,interface", gettext_noop("Give DNS name to IPv4 address of interface."), NULL }, + { 'z', OPT_NOWILD, NULL, gettext_noop("Bind only to interfaces in use."), NULL }, + { 'Z', OPT_ETHERS, NULL, gettext_noop("Read DHCP static host information from %s."), ETHERSFILE }, + { '1', OPT_DBUS, NULL, gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL }, + { '2', ARG_DUP, "interface", gettext_noop("Do not provide DHCP on this interface, only provide DNS."), NULL }, + { '3', ARG_DUP, "[=tag:<tag>]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL }, + { '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL }, + { LOPT_BRIDGE, ARG_DUP, "iface,alias,..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL }, + { '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL }, + { '6', ARG_ONE, "path", gettext_noop("Script to run on DHCP lease creation and destruction."), 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 }, + { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL }, + { '0', ARG_ONE, "<queries>", 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 }, + { LOPT_NO_NAMES, ARG_DUP, "[=tag:<tag>]...", gettext_noop("Ignore hostnames provided by DHCP clients."), NULL }, + { LOPT_OVERRIDE, OPT_NO_OVERRIDE, NULL, gettext_noop("Do NOT reuse filename and server fields for extra DHCP options."), NULL }, + { LOPT_TFTP, ARG_DUP, "[=<interface>]", gettext_noop("Enable integrated read-only TFTP server."), NULL }, + { LOPT_PREFIX, ARG_ONE, "<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_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL }, + { LOPT_TFTP_MAX, ARG_ONE, "<connections>", gettext_noop("Maximum number of conncurrent TFTP transfers (defaults to %s)."), "#" }, + { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL }, + { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL }, + { LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL }, + { LOPT_MAX_LOGS, ARG_ONE, "[=<log lines>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL }, + { LOPT_REBIND, OPT_NO_REBIND, NULL, gettext_noop("Stop DNS rebinding. Filter private IP ranges when resolving."), NULL }, + { LOPT_LOC_REBND, OPT_LOCAL_REBIND, NULL, gettext_noop("Allow rebinding of 127.0.0.0/8, for RBL servers."), NULL }, + { LOPT_NO_REBIND, ARG_DUP, "/domain/", gettext_noop("Inhibit DNS-rebind protection on this domain."), NULL }, + { LOPT_NOLAST, OPT_ALL_SERVERS, NULL, gettext_noop("Always perform DNS queries to all servers."), NULL }, + { LOPT_MATCH, ARG_DUP, "set:<tag>,<optspec>", gettext_noop("Set tag if client includes matching option in request."), NULL }, + { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL }, + { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change script as this user."), 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_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, "[=<ip_address>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL }, + { LOPT_CNAME, ARG_DUP, "<alias>,<target>", 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_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers"), NULL }, + { 0, 0, NULL, NULL, NULL } +}; + +#ifdef HAVE_DHCP +#define OT_ADDR_LIST 0x80 +#define OT_RFC1035_NAME 0x40 +#define OT_INTERNAL 0x20 +#define OT_NAME 0x10 + + +static const struct { + char *name; + unsigned char val, size; +} opttab[] = { + { "netmask", 1, OT_ADDR_LIST }, + { "time-offset", 2, 4 }, + { "router", 3, OT_ADDR_LIST }, + { "dns-server", 6, OT_ADDR_LIST }, + { "log-server", 7, OT_ADDR_LIST }, + { "lpr-server", 9, OT_ADDR_LIST }, + { "hostname", 12, OT_INTERNAL | OT_NAME }, + { "boot-file-size", 13, 2 }, + { "domain-name", 15, OT_NAME }, + { "swap-server", 16, OT_ADDR_LIST }, + { "root-path", 17, OT_NAME }, + { "extension-path", 18, OT_NAME }, + { "ip-forward-enable", 19, 1 }, + { "non-local-source-routing", 20, 1 }, + { "policy-filter", 21, OT_ADDR_LIST }, + { "max-datagram-reassembly", 22, 2 }, + { "default-ttl", 23, 1 }, + { "mtu", 26, 2 }, + { "all-subnets-local", 27, 1 }, + { "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST }, + { "router-discovery", 31, 1 }, + { "router-solicitation", 32, OT_ADDR_LIST }, + { "static-route", 33, OT_ADDR_LIST }, + { "trailer-encapsulation", 34, 1 }, + { "arp-timeout", 35, 4 }, + { "ethernet-encap", 36, 1 }, + { "tcp-ttl", 37, 1 }, + { "tcp-keepalive", 38, 4 }, + { "nis-domain", 40, OT_NAME }, + { "nis-server", 41, OT_ADDR_LIST }, + { "ntp-server", 42, OT_ADDR_LIST }, + { "vendor-encap", 43, OT_INTERNAL }, + { "netbios-ns", 44, OT_ADDR_LIST }, + { "netbios-dd", 45, OT_ADDR_LIST }, + { "netbios-nodetype", 46, 1 }, + { "netbios-scope", 47, 0 }, + { "x-windows-fs", 48, OT_ADDR_LIST }, + { "x-windows-dm", 49, OT_ADDR_LIST }, + { "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST }, + { "lease-time", 51, OT_INTERNAL }, + { "option-overload", 52, OT_INTERNAL }, + { "message-type", 53, OT_INTERNAL, }, + { "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST }, + { "parameter-request", 55, OT_INTERNAL }, + { "message", 56, OT_INTERNAL }, + { "max-message-size", 57, OT_INTERNAL }, + { "T1", 58, OT_INTERNAL }, + { "T2", 59, OT_INTERNAL }, + { "vendor-class", 60, 0 }, + { "client-id", 61,OT_INTERNAL }, + { "nis+-domain", 64, OT_NAME }, + { "nis+-server", 65, OT_ADDR_LIST }, + { "tftp-server", 66, OT_NAME }, + { "bootfile-name", 67, OT_NAME }, + { "mobile-ip-home", 68, OT_ADDR_LIST }, + { "smtp-server", 69, OT_ADDR_LIST }, + { "pop3-server", 70, OT_ADDR_LIST }, + { "nntp-server", 71, OT_ADDR_LIST }, + { "irc-server", 74, OT_ADDR_LIST }, + { "user-class", 77, 0 }, + { "FQDN", 81, OT_INTERNAL }, + { "agent-id", 82, OT_INTERNAL }, + { "client-arch", 93, 2 }, + { "client-interface-id", 94, 0 }, + { "client-machine-id", 97, 0 }, + { "subnet-select", 118, OT_INTERNAL }, + { "domain-search", 119, OT_RFC1035_NAME }, + { "sip-server", 120, 0 }, + { "classless-static-route", 121, 0 }, + { "vendor-id-encap", 125, 0 }, + { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */ + { NULL, 0, 0 } +}; + +char *option_string(unsigned char opt, int *is_ip, int *is_name) +{ + int i; + + for (i = 0; opttab[i].name; i++) + if (opttab[i].val == opt) + { + if (is_ip) + *is_ip = !!(opttab[i].size & OT_ADDR_LIST); + if (is_name) + *is_name = !!(opttab[i].size & OT_NAME); + return opttab[i].name; + } + + return NULL; +} + +#endif + +/* We hide metacharaters 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. + The transformation gets undone by opt_canonicalise, atoi_check and opt_string_alloc, and a + couple of other places. + Note that space is included here so that + --dhcp-option=3, string + has five characters, whilst + --dhcp-option=3," string" + has six. +*/ + +static const char meta[] = "\000123456 \b\t\n78\r90abcdefABCDE\033F:,."; + +static char hide_meta(char c) +{ + unsigned int i; + + for (i = 0; i < (sizeof(meta) - 1); i++) + if (c == meta[i]) + return (char)i; + + return c; +} + +static char unhide_meta(char cr) +{ + unsigned int c = cr; + + if (c < (sizeof(meta) - 1)) + cr = meta[c]; + + return cr; +} + +static void unhide_metas(char *cp) +{ + if (cp) + for(; *cp; cp++) + *cp = unhide_meta(*cp); +} + +static void *opt_malloc(size_t size) +{ + void *ret; + + if (mem_recover) + { + ret = whine_malloc(size); + if (!ret) + longjmp(mem_jmp, 1); + } + else + ret = safe_malloc(size); + + return ret; +} + +static char *opt_string_alloc(char *cp) +{ + char *ret = NULL; + + if (cp && strlen(cp) != 0) + { + ret = opt_malloc(strlen(cp)+1); + strcpy(ret, cp); + + /* restore hidden metachars */ + unhide_metas(ret); + } + + return ret; +} + + +/* find next comma, split string with zero and eliminate spaces. + return start of string following comma */ + +static char *split_chr(char *s, char c) +{ + char *comma, *p; + + if (!s || !(comma = strchr(s, c))) + return NULL; + + p = comma; + *comma = ' '; + + for (; *comma == ' '; comma++); + + for (; (p >= s) && *p == ' '; p--) + *p = 0; + + return comma; +} + +static char *split(char *s) +{ + return split_chr(s, ','); +} + +static char *canonicalise_opt(char *s) +{ + char *ret; + int nomem; + + if (!s) + return 0; + + unhide_metas(s); + if (!(ret = canonicalise(s, &nomem)) && nomem) + { + if (mem_recover) + longjmp(mem_jmp, 1); + else + die(_("could not get memory"), NULL, EC_NOMEM); + } + + return ret; +} + +static int atoi_check(char *a, int *res) +{ + char *p; + + if (!a) + return 0; + + unhide_metas(a); + + for (p = a; *p; p++) + if (*p < '0' || *p > '9') + return 0; + + *res = atoi(a); + return 1; +} + +static int atoi_check16(char *a, int *res) +{ + if (!(atoi_check(a, res)) || + *res < 0 || + *res > 0xffff) + return 0; + + return 1; +} + +static void add_txt(char *name, char *txt) +{ + size_t len = strlen(txt); + struct txt_record *r = opt_malloc(sizeof(struct txt_record)); + + r->name = opt_string_alloc(name); + r->next = daemon->txt; + daemon->txt = r; + r->class = C_CHAOS; + r->txt = opt_malloc(len+1); + r->len = len+1; + *(r->txt) = len; + memcpy((r->txt)+1, txt, len); +} + +static void do_usage(void) +{ + char buff[100]; + int i, j; + + struct { + char handle; + int val; + } tab[] = { + { '$', CACHESIZ }, + { '*', EDNS_PKTSZ }, + { '&', MAXLEASES }, + { '!', FTABSIZ }, + { '#', TFTP_MAX_CONNECTIONS }, + { '\0', 0 } + }; + + printf(_("Usage: dnsmasq [options]\n\n")); +#ifndef HAVE_GETOPT_LONG + printf(_("Use short options only on the command line.\n")); +#endif + printf(_("Valid options are:\n")); + + for (i = 0; usage[i].opt != 0; i++) + { + char *desc = usage[i].flagdesc; + char *eq = "="; + + if (!desc || *desc == '[') + eq = ""; + + if (!desc) + desc = ""; + + for ( j = 0; opts[j].name; j++) + if (opts[j].val == usage[i].opt) + break; + if (usage[i].opt < 256) + sprintf(buff, "-%c, ", usage[i].opt); + else + sprintf(buff, " "); + + sprintf(buff+4, "--%s%s%s", opts[j].name, eq, desc); + printf("%-40.40s", buff); + + if (usage[i].arg) + { + strcpy(buff, usage[i].arg); + for (j = 0; tab[j].handle; j++) + if (tab[j].handle == *(usage[i].arg)) + sprintf(buff, "%d", tab[j].val); + } + printf(_(usage[i].desc), buff); + printf("\n"); + } +} + +#ifdef HAVE_DHCP +static void display_opts(void) +{ + int i; + + printf(_("Known DHCP options:\n")); + + for (i = 0; opttab[i].name; i++) + if (!(opttab[i].size & OT_INTERNAL)) + printf("%3d %s\n", opttab[i].val, opttab[i].name); +} + +static int is_tag_prefix(char *arg) +{ + if (arg && (strstr(arg, "net:") == arg || strstr(arg, "tag:") == arg)) + return 1; + + return 0; +} + +static char *set_prefix(char *arg) +{ + if (strstr(arg, "set:") == arg) + return arg+4; + + return arg; +} + +/* This is too insanely large to keep in-line in the switch */ +static char *parse_dhcp_opt(char *arg, int flags) +{ + struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt)); + char lenchar = 0, *cp; + int i, addrs, digs, is_addr, is_hex, is_dec, is_string, dots; + char *comma = NULL, *problem = NULL; + struct dhcp_netid *np = NULL; + unsigned char opt_len = 0; + + new->len = 0; + new->flags = flags; + new->netid = NULL; + new->val = NULL; + new->opt = 0; + + while (arg) + { + comma = split(arg); + + for (cp = arg; *cp; cp++) + if (*cp < '0' || *cp > '9') + break; + + if (!*cp) + { + new->opt = atoi(arg); + opt_len = 0; + break; + } + + if (strstr(arg, "option:") == arg) + { + for (i = 0; opttab[i].name; i++) + if (!(opttab[i].size & OT_INTERNAL) && + strcasecmp(opttab[i].name, arg+7) == 0) + { + new->opt = opttab[i].val; + opt_len = opttab[i].size; + break; + } + /* option:<optname> must follow tag and vendor string. */ + break; + } + else if (strstr(arg, "vendor:") == arg) + { + new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7); + new->flags |= DHOPT_VENDOR; + } + else if (strstr(arg, "encap:") == arg) + { + new->u.encap = atoi(arg+6); + new->flags |= DHOPT_ENCAPSULATE; + } + else if (strstr(arg, "vi-encap:") == arg) + { + new->u.encap = atoi(arg+9); + new->flags |= DHOPT_RFC3925; + if (flags == DHOPT_MATCH) + { + new->opt = 1; /* avoid error below */ + break; + } + } + else + { + new->netid = opt_malloc(sizeof (struct dhcp_netid)); + /* allow optional "net:" or "tag:" for consistency */ + if (is_tag_prefix(arg)) + new->netid->net = opt_string_alloc(arg+4); + else + new->netid->net = opt_string_alloc(set_prefix(arg)); + new->netid->next = np; + np = new->netid; + } + + arg = comma; + } + + if (opt_len == 0 && + !(new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925))) + for (i = 0; opttab[i].name; i++) + if (new->opt == opttab[i].val) + { + opt_len = opttab[i].size; + if (opt_len & OT_INTERNAL) + opt_len = 0; + break; + } + + /* option may be missing with rfc3925 match */ + if (new->opt == 0) + problem = _("bad dhcp-option"); + else if (comma) + { + /* characterise the value */ + char c; + int found_dig = 0; + is_addr = is_hex = is_dec = is_string = 1; + addrs = digs = 1; + dots = 0; + for (cp = comma; (c = *cp); cp++) + if (c == ',') + { + addrs++; + is_dec = is_hex = 0; + } + else if (c == ':') + { + digs++; + is_dec = is_addr = 0; + } + else if (c == '/') + { + is_dec = is_hex = 0; + if (cp == comma) /* leading / means a pathname */ + is_addr = 0; + } + else if (c == '.') + { + is_dec = is_hex = 0; + dots++; + } + else if (c == '-') + is_hex = is_addr = 0; + else if (c == ' ') + is_dec = is_hex = 0; + else if (!(c >='0' && c <= '9')) + { + is_addr = 0; + if (cp[1] == 0 && is_dec && + (c == 'b' || c == 's' || c == 'i')) + { + lenchar = c; + *cp = 0; + } + else + is_dec = 0; + if (!((c >='A' && c <= 'F') || + (c >='a' && c <= 'f') || + (c == '*' && (flags & DHOPT_MATCH)))) + is_hex = 0; + } + else + found_dig = 1; + + if (!found_dig) + is_dec = is_addr = 0; + + /* We know that some options take addresses */ + if (opt_len & OT_ADDR_LIST) + { + is_string = is_dec = is_hex = 0; + if (!is_addr || dots == 0) + problem = _("bad IP address"); + } + /* or names */ + else if (opt_len & (OT_NAME | OT_RFC1035_NAME)) + is_addr = is_dec = is_hex = 0; + + if (is_hex && digs > 1) + { + new->len = digs; + new->val = opt_malloc(new->len); + parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL); + new->flags |= DHOPT_HEX; + } + else if (is_dec) + { + int i, val = atoi(comma); + /* assume numeric arg is 1 byte except for + options where it is known otherwise. + For vendor class option, we have to hack. */ + if (opt_len != 0) + new->len = opt_len; + else if (val & 0xffff0000) + new->len = 4; + else if (val & 0xff00) + new->len = 2; + else + new->len = 1; + + if (lenchar == 'b') + new->len = 1; + else if (lenchar == 's') + new->len = 2; + else if (lenchar == 'i') + new->len = 4; + + new->val = opt_malloc(new->len); + for (i=0; i<new->len; i++) + new->val[i] = val>>((new->len - i - 1)*8); + } + else if (is_addr) + { + struct in_addr in; + unsigned char *op; + char *slash; + /* max length of address/subnet descriptor is five bytes, + add one for the option 120 enc byte too */ + new->val = op = opt_malloc((5 * addrs) + 1); + new->flags |= DHOPT_ADDR; + + if (!(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) && + new->opt == OPTION_SIP_SERVER) + { + *(op++) = 1; /* RFC 3361 "enc byte" */ + new->flags &= ~DHOPT_ADDR; + } + while (addrs--) + { + cp = comma; + comma = split(cp); + slash = split_chr(cp, '/'); + in.s_addr = inet_addr(cp); + if (!slash) + { + memcpy(op, &in, INADDRSZ); + op += INADDRSZ; + } + else + { + unsigned char *p = (unsigned char *)∈ + int netsize = atoi(slash); + *op++ = netsize; + if (netsize > 0) + *op++ = *p++; + if (netsize > 8) + *op++ = *p++; + if (netsize > 16) + *op++ = *p++; + if (netsize > 24) + *op++ = *p++; + new->flags &= ~DHOPT_ADDR; /* cannot re-write descriptor format */ + } + } + new->len = op - new->val; + } + else if (is_string) + { + /* text arg */ + if ((new->opt == OPTION_DOMAIN_SEARCH || new->opt == OPTION_SIP_SERVER) && + !(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) + { + /* dns search, RFC 3397, or SIP, RFC 3361 */ + unsigned char *q, *r, *tail; + unsigned char *p, *m = NULL, *newp; + size_t newlen, len = 0; + int header_size = (new->opt == OPTION_DOMAIN_SEARCH) ? 0 : 1; + + arg = comma; + comma = split(arg); + + while (arg && *arg) + { + char *in, *dom = NULL; + size_t domlen = 1; + /* Allow "." as an empty domain */ + if (strcmp (arg, ".") != 0) + { + if (!(dom = canonicalise_opt(arg))) + { + problem = _("bad domain in dhcp-option"); + break; + } + domlen = strlen(dom) + 2; + } + + newp = opt_malloc(len + domlen + header_size); + if (m) + { + memcpy(newp, m, header_size + len); + free(m); + } + m = newp; + p = m + header_size; + q = p + len; + + /* add string on the end in RFC1035 format */ + for (in = dom; in && *in;) + { + unsigned char *cp = q++; + int j; + for (j = 0; *in && (*in != '.'); in++, j++) + *q++ = *in; + *cp = j; + if (*in) + in++; + } + *q++ = 0; + free(dom); + + /* Now tail-compress using earlier names. */ + newlen = q - p; + for (tail = p + len; *tail; tail += (*tail) + 1) + for (r = p; r - p < (int)len; r += (*r) + 1) + if (strcmp((char *)r, (char *)tail) == 0) + { + PUTSHORT((r - p) | 0xc000, tail); + newlen = tail - p; + goto end; + } + end: + len = newlen; + + arg = comma; + comma = split(arg); + } + + /* RFC 3361, enc byte is zero for names */ + if (new->opt == OPTION_SIP_SERVER) + m[0] = 0; + new->len = (int) len + header_size; + new->val = m; + } + else + { + new->len = strlen(comma); + /* keep terminating zero on string */ + new->val = (unsigned char *)opt_string_alloc(comma); + new->flags |= DHOPT_STRING; + } + } + } + + if ((new->len > 255) || + (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) || + (new->len > 250 && (new->flags & DHOPT_RFC3925))) + problem = _("dhcp-option too long"); + + if (!problem) + { + if (flags == DHOPT_MATCH) + { + if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || + !new->netid || + new->netid->next) + problem = _("illegal dhcp-match"); + else + { + new->next = daemon->dhcp_match; + daemon->dhcp_match = new; + } + } + else + { + new->next = daemon->dhcp_opts; + daemon->dhcp_opts = new; + } + } + + return problem; +} + +#endif + +void set_option_bool(unsigned int opt) +{ + if (opt < 32) + daemon->options |= 1u << opt; + else + daemon->options2 |= 1u << (opt - 32); +} + +static char *one_opt(int option, char *arg, char *gen_prob, int command_line) +{ + int i; + char *comma, *problem = NULL;; + + if (option == '?') + return gen_prob; + + for (i=0; usage[i].opt != 0; i++) + if (usage[i].opt == option) + { + int rept = usage[i].rept; + + if (command_line) + { + /* command line */ + if (rept == ARG_USED_CL) + return _("illegal repeated flag"); + if (rept == ARG_ONE) + usage[i].rept = ARG_USED_CL; + } + else + { + /* allow file to override command line */ + if (rept == ARG_USED_FILE) + return _("illegal repeated keyword"); + if (rept == ARG_USED_CL || rept == ARG_ONE) + usage[i].rept = ARG_USED_FILE; + } + + if (rept != ARG_DUP && rept != ARG_ONE && rept != ARG_USED_CL) + { + set_option_bool(rept); + return NULL; + } + + break; + } + + switch (option) + { + case 'C': /* --conf-file */ + { + char *file = opt_string_alloc(arg); + if (file) + { + one_file(file, 0); + free(file); + } + break; + } + + case '7': /* --conf-dir */ + { + DIR *dir_stream; + struct dirent *ent; + char *directory, *path; + struct list { + char *suffix; + struct list *next; + } *ignore_suffix = NULL, *li; + + comma = split(arg); + if (!(directory = opt_string_alloc(arg))) + break; + + for (arg = comma; arg; arg = comma) + { + comma = split(arg); + li = opt_malloc(sizeof(struct list)); + li->next = ignore_suffix; + ignore_suffix = li; + /* Have to copy: buffer is overwritten */ + li->suffix = opt_string_alloc(arg); + }; + + if (!(dir_stream = opendir(directory))) + die(_("cannot access directory %s: %s"), directory, EC_FILE); + + while ((ent = readdir(dir_stream))) + { + size_t len = strlen(ent->d_name); + struct stat buf; + + /* ignore emacs backups and dotfiles */ + if (len == 0 || + ent->d_name[len - 1] == '~' || + (ent->d_name[0] == '#' && ent->d_name[len - 1] == '#') || + ent->d_name[0] == '.') + continue; + + for (li = ignore_suffix; li; li = li->next) + { + /* check for proscribed suffices */ + size_t ls = strlen(li->suffix); + if (len > ls && + strcmp(li->suffix, &ent->d_name[len - ls]) == 0) + break; + } + if (li) + continue; + + path = opt_malloc(strlen(directory) + len + 2); + strcpy(path, directory); + strcat(path, "/"); + strcat(path, ent->d_name); + + if (stat(path, &buf) == -1) + die(_("cannot access %s: %s"), path, EC_FILE); + /* only reg files allowed. */ + if (!S_ISREG(buf.st_mode)) + continue; + + /* files must be readable */ + one_file(path, 0); + free(path); + } + + closedir(dir_stream); + free(directory); + for(; ignore_suffix; ignore_suffix = li) + { + li = ignore_suffix->next; + free(ignore_suffix->suffix); + free(ignore_suffix); + } + + break; + } + + case '8': /* --log-facility */ + /* may be a filename */ + if (strchr(arg, '/') || strcmp (arg, "-") == 0) + daemon->log_file = opt_string_alloc(arg); + else + { +#ifdef __ANDROID__ + problem = _("setting log facility is not possible under Android"); +#else + for (i = 0; facilitynames[i].c_name; i++) + if (hostname_isequal((char *)facilitynames[i].c_name, arg)) + break; + + if (facilitynames[i].c_name) + daemon->log_fac = facilitynames[i].c_val; + else + problem = _("bad log facility"); +#endif + } + break; + + case 'x': /* --pid-file */ + daemon->runfile = opt_string_alloc(arg); + break; + + case 'r': /* --resolv-file */ + { + char *name = opt_string_alloc(arg); + struct resolvc *new, *list = daemon->resolv_files; + + if (list && list->is_default) + { + /* replace default resolv file - possibly with nothing */ + if (name) + { + list->is_default = 0; + list->name = name; + } + else + list = NULL; + } + else if (name) + { + new = opt_malloc(sizeof(struct resolvc)); + new->next = list; + new->name = name; + new->is_default = 0; + new->mtime = 0; + new->logged = 0; + list = new; + } + daemon->resolv_files = list; + break; + } + + case 'm': /* --mx-host */ + { + int pref = 1; + struct mx_srv_record *new; + char *name, *target = NULL; + + if ((comma = split(arg))) + { + char *prefstr; + if ((prefstr = split(comma)) && !atoi_check16(prefstr, &pref)) + problem = _("bad MX preference"); + } + + if (!(name = canonicalise_opt(arg)) || + (comma && !(target = canonicalise_opt(comma)))) + problem = _("bad MX name"); + + new = opt_malloc(sizeof(struct mx_srv_record)); + new->next = daemon->mxnames; + daemon->mxnames = new; + new->issrv = 0; + new->name = name; + new->target = target; /* may be NULL */ + new->weight = pref; + break; + } + + case 't': /* --mx-target */ + if (!(daemon->mxtarget = canonicalise_opt(arg))) + problem = _("bad MX target"); + break; + +#ifdef HAVE_DHCP + case 'l': /* --dhcp-leasefile */ + daemon->lease_file = opt_string_alloc(arg); + break; + + case '6': /* --dhcp-script */ +# if defined(NO_FORK) + problem = _("cannot run scripts under uClinux"); +# elif !defined(HAVE_SCRIPT) + problem = _("recompile with HAVE_SCRIPT defined to enable lease-change scripts"); +# else + daemon->lease_change_command = opt_string_alloc(arg); +# endif + break; +#endif + + case LOPT_DHCP_HOST: /* --dhcp-hostfile */ + case LOPT_DHCP_OPTS: /* --dhcp-optsfile */ + case 'H': /* --addn-hosts */ + { + struct hostsfile *new = opt_malloc(sizeof(struct hostsfile)); + static int hosts_index = 1; + new->fname = opt_string_alloc(arg); + new->index = hosts_index++; + new->flags = 0; + if (option == 'H') + { + new->next = daemon->addn_hosts; + daemon->addn_hosts = new; + } + else if (option == LOPT_DHCP_HOST) + { + new->next = daemon->dhcp_hosts_file; + daemon->dhcp_hosts_file = new; + } + else if (option == LOPT_DHCP_OPTS) + { + new->next = daemon->dhcp_opts_file; + daemon->dhcp_opts_file = new; + } + break; + } + + case 's': /* --domain */ + if (strcmp (arg, "#") == 0) + set_option_bool(OPT_RESOLV_DOMAIN); + else + { + char *d; + comma = split(arg); + if (!(d = canonicalise_opt(arg))) + option = '?'; + else + { + if (comma) + { + struct cond_domain *new = safe_malloc(sizeof(struct cond_domain)); + char *netpart; + + unhide_metas(comma); + if ((netpart = split_chr(comma, '/'))) + { + int msize, mask; + arg = split(netpart); + if ((new->start.s_addr = inet_addr(comma)) == (in_addr_t)-1 || + !atoi_check(netpart, &msize)) + option = '?'; + else + { + mask = (1 << (32 - msize)) - 1; + new->start.s_addr = ntohl(htonl(new->start.s_addr) & ~mask); + new->end.s_addr = new->start.s_addr | htonl(mask); + if (arg) + { + /* generate the equivalent of + local=/<domain>/ + local=/xxx.yyy.zzz.in-addr.arpa/ */ + + if (strcmp(arg, "local") != 0 || + (msize != 8 && msize != 16 && msize != 24)) + option = '?'; + else + { + struct server *serv = opt_malloc(sizeof(struct server)); + in_addr_t a = ntohl(new->start.s_addr) >> 8; + char *p; + + memset(serv, 0, sizeof(struct server)); + serv->domain = d; + serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; + serv->next = daemon->servers; + daemon->servers = serv; + + serv = opt_malloc(sizeof(struct server)); + 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); + + serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; + serv->next = daemon->servers; + daemon->servers = serv; + } + } + } + } + else if ((arg = split(comma))) + { + if ((new->start.s_addr = inet_addr(comma)) == (in_addr_t)-1 || + (new->end.s_addr = inet_addr(arg)) == (in_addr_t)-1) + option = '?'; + } + else if ((new->start.s_addr = new->end.s_addr = inet_addr(comma)) == (in_addr_t)-1) + option = '?'; + + new->domain = d; + new->next = daemon->cond_domain; + daemon->cond_domain = new; + } + else + daemon->domain_suffix = d; + } + } + break; + + case 'u': /* --user */ + daemon->username = opt_string_alloc(arg); + break; + + case 'g': /* --group */ + daemon->groupname = opt_string_alloc(arg); + daemon->group_set = 1; + break; + +#ifdef HAVE_DHCP + case LOPT_SCRIPTUSR: /* --scriptuser */ + daemon->scriptuser = opt_string_alloc(arg); + break; +#endif + + case 'i': /* --interface */ + do { + struct iname *new = opt_malloc(sizeof(struct iname)); + comma = split(arg); + new->next = daemon->if_names; + daemon->if_names = new; + /* new->name may be NULL if someone does + "interface=" to disable all interfaces except loop. */ + new->name = opt_string_alloc(arg); + new->isloop = new->used = 0; + arg = comma; + } while (arg); + break; + + case 'I': /* --except-interface */ + case '2': /* --no-dhcp-interface */ + do { + struct iname *new = opt_malloc(sizeof(struct iname)); + comma = split(arg); + new->name = opt_string_alloc(arg); + if (option == 'I') + { + new->next = daemon->if_except; + daemon->if_except = new; + } + else + { + new->next = daemon->dhcp_except; + daemon->dhcp_except = new; + } + arg = comma; + } while (arg); + break; + + case 'B': /* --bogus-nxdomain */ + { + struct in_addr addr; + unhide_metas(arg); + if (arg && (addr.s_addr = inet_addr(arg)) != (in_addr_t)-1) + { + struct bogus_addr *baddr = opt_malloc(sizeof(struct bogus_addr)); + baddr->next = daemon->bogus_addr; + daemon->bogus_addr = baddr; + baddr->addr = addr; + } + else + option = '?'; /* error */ + break; + } + + case 'a': /* --listen-address */ + do { + struct iname *new = opt_malloc(sizeof(struct iname)); + comma = split(arg); + unhide_metas(arg); + new->next = daemon->if_addrs; + if (arg && (new->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t)-1) + { + new->addr.sa.sa_family = AF_INET; +#ifdef HAVE_SOCKADDR_SA_LEN + new->addr.in.sin_len = sizeof(new->addr.in); +#endif + } +#ifdef HAVE_IPV6 + else if (arg && inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0) + { + new->addr.sa.sa_family = AF_INET6; + new->addr.in6.sin6_flowinfo = 0; + new->addr.in6.sin6_scope_id = 0; +#ifdef HAVE_SOCKADDR_SA_LEN + new->addr.in6.sin6_len = sizeof(new->addr.in6); +#endif + } +#endif + else + { + option = '?'; /* error */ + break; + } + + daemon->if_addrs = new; + arg = comma; + } while (arg); + break; + + case 'S': /* --server */ + case LOPT_LOCAL: /* --local */ + case 'A': /* --address */ + case LOPT_NO_REBIND: /* --rebind-domain-ok */ + { + struct server *serv, *newlist = NULL; + + unhide_metas(arg); + + if (arg && (*arg == '/' || option == LOPT_NO_REBIND)) + { + int rebind = !(*arg == '/'); + char *end = NULL; + if (!rebind) + arg++; + while (rebind || (end = split_chr(arg, '/'))) + { + char *domain = NULL; + /* elide leading dots - they are implied in the search algorithm */ + while (*arg == '.') arg++; + /* # matches everything and becomes a zero length domain string */ + if (strcmp(arg, "#") == 0) + domain = ""; + else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg))) + option = '?'; + serv = opt_malloc(sizeof(struct server)); + memset(serv, 0, sizeof(struct server)); + serv->next = newlist; + newlist = serv; + serv->domain = domain; + serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS; + arg = end; + if (rebind) + break; + } + if (!newlist) + { + option = '?'; + break; + } + + } + else + { + newlist = opt_malloc(sizeof(struct server)); + memset(newlist, 0, sizeof(struct server)); + } + + if (option == 'A') + { + newlist->flags |= SERV_LITERAL_ADDRESS; + if (!(newlist->flags & SERV_TYPE)) + option = '?'; + } + else if (option == LOPT_NO_REBIND) + newlist->flags |= SERV_NO_REBIND; + + if (!arg || !*arg) + { + if (!(newlist->flags & SERV_NO_REBIND)) + newlist->flags |= SERV_NO_ADDR; /* no server */ + if (newlist->flags & SERV_LITERAL_ADDRESS) + option = '?'; + } + + else if (strcmp(arg, "#") == 0) + { + newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ + if (newlist->flags & SERV_LITERAL_ADDRESS) + option = '?'; + } + else + { + int source_port = 0, serv_port = NAMESERVER_PORT; + char *portno, *source; + + if ((source = split_chr(arg, '@')) && /* is there a source. */ + (portno = split_chr(source, '#')) && + !atoi_check16(portno, &source_port)) + problem = _("bad port"); + + if ((portno = split_chr(arg, '#')) && /* is there a port no. */ + !atoi_check16(portno, &serv_port)) + problem = _("bad port"); + + if ((newlist->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t) -1) + { + newlist->addr.in.sin_port = htons(serv_port); + newlist->source_addr.in.sin_port = htons(source_port); + newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET; +#ifdef HAVE_SOCKADDR_SA_LEN + newlist->source_addr.in.sin_len = newlist->addr.in.sin_len = sizeof(struct sockaddr_in); +#endif + if (source) + { + newlist->flags |= SERV_HAS_SOURCE; + if ((newlist->source_addr.in.sin_addr.s_addr = inet_addr(source)) == (in_addr_t) -1) + { +#if defined(SO_BINDTODEVICE) + newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY; + strncpy(newlist->interface, source, IF_NAMESIZE - 1); +#else + problem = _("interface binding not supported"); +#endif + } + } + else + newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY; + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &newlist->addr.in6.sin6_addr) > 0) + { + newlist->addr.in6.sin6_port = htons(serv_port); + newlist->source_addr.in6.sin6_port = htons(source_port); + newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET6; +#ifdef HAVE_SOCKADDR_SA_LEN + newlist->addr.in6.sin6_len = newlist->source_addr.in6.sin6_len = sizeof(newlist->addr.in6); +#endif + if (source) + { + newlist->flags |= SERV_HAS_SOURCE; + if (inet_pton(AF_INET6, source, &newlist->source_addr.in6.sin6_addr) == 0) + { +#if defined(SO_BINDTODEVICE) + newlist->source_addr.in6.sin6_addr = in6addr_any; + strncpy(newlist->interface, source, IF_NAMESIZE - 1); +#else + problem = _("interface binding not supported"); +#endif + } + } + else + newlist->source_addr.in6.sin6_addr = in6addr_any; + } +#endif + else + option = '?'; /* error */ + + } + + serv = newlist; + while (serv->next) + { + serv->next->flags = serv->flags; + serv->next->addr = serv->addr; + serv->next->source_addr = serv->source_addr; + serv = serv->next; + } + serv->next = daemon->servers; + daemon->servers = newlist; + break; + } + + case 'c': /* --cache-size */ + { + int size; + + if (!atoi_check(arg, &size)) + option = '?'; + else + { + /* zero is OK, and means no caching. */ + + if (size < 0) + size = 0; + else if (size > 10000) + size = 10000; + + daemon->cachesize = size; + } + break; + } + + case 'p': /* --port */ + if (!atoi_check16(arg, &daemon->port)) + option = '?'; + break; + + case LOPT_MINPORT: /* --min-port */ + if (!atoi_check16(arg, &daemon->min_port)) + option = '?'; + break; + + case '0': /* --dns-forward-max */ + if (!atoi_check(arg, &daemon->ftabsize)) + option = '?'; + break; + + case LOPT_MAX_LOGS: /* --log-async */ + daemon->max_logs = LOG_MAX; /* default */ + if (arg && !atoi_check(arg, &daemon->max_logs)) + option = '?'; + else if (daemon->max_logs > 100) + daemon->max_logs = 100; + break; + + case 'P': /* --edns-packet-max */ + { + int i; + if (!atoi_check(arg, &i)) + option = '?'; + daemon->edns_pktsz = (unsigned short)i; + break; + } + + case 'Q': /* --query-port */ + if (!atoi_check16(arg, &daemon->query_port)) + option = '?'; + /* if explicitly set to zero, use single OS ephemeral port + and disable random ports */ + if (daemon->query_port == 0) + daemon->osport = 1; + break; + + case 'T': /* --local-ttl */ + case LOPT_NEGTTL: /* --neg-ttl */ + case LOPT_MAXTTL: /* --max-ttl */ + { + int ttl; + if (!atoi_check(arg, &ttl)) + option = '?'; + else if (option == LOPT_NEGTTL) + daemon->neg_ttl = (unsigned long)ttl; + else if (option == LOPT_MAXTTL) + daemon->max_ttl = (unsigned long)ttl; + else + daemon->local_ttl = (unsigned long)ttl; + break; + } + +#ifdef HAVE_DHCP + case 'X': /* --dhcp-lease-max */ + if (!atoi_check(arg, &daemon->dhcp_max)) + option = '?'; + break; +#endif + +#ifdef HAVE_TFTP + case LOPT_TFTP: /* --enable-tftp */ + if (arg) + { + struct interface_list *new = opt_malloc(sizeof(struct interface_list)); + new->interface = opt_string_alloc(arg); + new->next = daemon->tftp_interfaces; + daemon->tftp_interfaces = new; + } + else + daemon->tftp_unlimited = 1; + break; + + case LOPT_TFTP_MAX: /* --tftp-max */ + if (!atoi_check(arg, &daemon->tftp_max)) + option = '?'; + break; + + case LOPT_PREFIX: /* --tftp-prefix */ + comma = split(arg); + if (comma) + { + struct tftp_prefix *new = opt_malloc(sizeof(struct tftp_prefix)); + new->interface = opt_string_alloc(comma); + new->prefix = opt_string_alloc(arg); + new->next = daemon->if_prefix; + daemon->if_prefix = new; + } + else + daemon->tftp_prefix = opt_string_alloc(arg); + break; + + case LOPT_TFTPPORTS: /* --tftp-port-range */ + if (!(comma = split(arg)) || + !atoi_check16(arg, &daemon->start_tftp_port) || + !atoi_check16(comma, &daemon->end_tftp_port)) + problem = _("bad port range"); + + if (daemon->start_tftp_port > daemon->end_tftp_port) + { + int tmp = daemon->start_tftp_port; + daemon->start_tftp_port = daemon->end_tftp_port; + daemon->end_tftp_port = tmp; + } + + break; +#endif + + case LOPT_BRIDGE: /* --bridge-interface */ + { + struct dhcp_bridge *new = opt_malloc(sizeof(struct dhcp_bridge)); + if (!(comma = split(arg)) || strlen(arg) > IF_NAMESIZE - 1 ) + { + problem = _("bad bridge-interface"); + break; + } + + strcpy(new->iface, arg); + new->alias = NULL; + new->next = daemon->bridges; + daemon->bridges = new; + + do { + arg = comma; + comma = split(arg); + if (strlen(arg) != 0 && strlen(arg) <= IF_NAMESIZE - 1) + { + struct dhcp_bridge *b = opt_malloc(sizeof(struct dhcp_bridge)); + b->next = new->alias; + new->alias = b; + strcpy(b->iface, arg); + } + } while (comma); + + break; + } + +#ifdef HAVE_DHCP + case 'F': /* --dhcp-range */ + { + int k, leasepos = 2; + char *cp, *a[5] = { NULL, NULL, NULL, NULL, NULL }; + struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context)); + + new->next = daemon->dhcp; + new->lease_time = DEFLEASE; + new->addr_epoch = 0; + new->netmask.s_addr = 0; + new->broadcast.s_addr = 0; + new->router.s_addr = 0; + new->netid.net = NULL; + new->filter = NULL; + new->flags = 0; + new->interface = NULL; + + gen_prob = _("bad dhcp-range"); + + if (!arg) + { + option = '?'; + break; + } + + while(1) + { + for (cp = arg; *cp; cp++) + if (!(*cp == ' ' || *cp == '.' || (*cp >='0' && *cp <= '9'))) + break; + + if (*cp != ',' && (comma = split(arg))) + { + if (strstr(arg, "interface:") == arg) + new->interface = opt_string_alloc(arg+10); + else if (is_tag_prefix(arg)) + { + struct dhcp_netid *tt = opt_malloc(sizeof (struct dhcp_netid)); + tt->net = opt_string_alloc(arg+4); + tt->next = new->filter; + new->filter = tt; + } + else + { + if (new->netid.net) + problem = _("only one tag allowed"); + else if (strstr(arg, "set:") == arg) + new->netid.net = opt_string_alloc(arg+4); + else + new->netid.net = opt_string_alloc(arg); + } + arg = comma; + } + else + { + a[0] = arg; + break; + } + } + + for (k = 1; k < 5; k++) + if (!(a[k] = split(a[k-1]))) + break; + + if ((k < 2) || ((new->start.s_addr = inet_addr(a[0])) == (in_addr_t)-1)) + option = '?'; + else if (strcmp(a[1], "static") == 0) + { + new->end = new->start; + new->flags |= CONTEXT_STATIC; + } + else if (strcmp(a[1], "proxy") == 0) + { + new->end = new->start; + new->flags |= CONTEXT_PROXY; + } + else if ((new->end.s_addr = inet_addr(a[1])) == (in_addr_t)-1) + option = '?'; + + if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr)) + { + struct in_addr tmp = new->start; + new->start = new->end; + new->end = tmp; + } + + if (option != '?' && k >= 3 && strchr(a[2], '.') && + ((new->netmask.s_addr = inet_addr(a[2])) != (in_addr_t)-1)) + { + new->flags |= CONTEXT_NETMASK; + leasepos = 3; + if (!is_same_net(new->start, new->end, new->netmask)) + problem = _("inconsistent DHCP range"); + } + daemon->dhcp = new; + + if (k >= 4 && strchr(a[3], '.') && + ((new->broadcast.s_addr = inet_addr(a[3])) != (in_addr_t)-1)) + { + new->flags |= CONTEXT_BRDCAST; + leasepos = 4; + } + + if (k >= leasepos+1) + { + if (strcmp(a[leasepos], "infinite") == 0) + new->lease_time = 0xffffffff; + else + { + int fac = 1; + if (strlen(a[leasepos]) > 0) + { + switch (a[leasepos][strlen(a[leasepos]) - 1]) + { + case 'd': + case 'D': + fac *= 24; + /* fall though */ + case 'h': + case 'H': + fac *= 60; + /* fall through */ + case 'm': + case 'M': + fac *= 60; + /* fall through */ + case 's': + case 'S': + a[leasepos][strlen(a[leasepos]) - 1] = 0; + } + + new->lease_time = atoi(a[leasepos]) * fac; + /* Leases of a minute or less confuse + some clients, notably Apple's */ + if (new->lease_time < 120) + new->lease_time = 120; + } + } + } + break; + } + + case LOPT_BANK: + case 'G': /* --dhcp-host */ + { + int j, k = 0; + char *a[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; + struct dhcp_config *new; + struct in_addr in; + + new = opt_malloc(sizeof(struct dhcp_config)); + + new->next = daemon->dhcp_conf; + new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0; + new->hwaddr = NULL; + new->netid = NULL; + + if ((a[0] = arg)) + for (k = 1; k < 6; k++) + if (!(a[k] = split(a[k-1]))) + break; + + for (j = 0; j < k; j++) + if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */ + { + char *arg = a[j]; + + if ((arg[0] == 'i' || arg[0] == 'I') && + (arg[1] == 'd' || arg[1] == 'D') && + arg[2] == ':') + { + if (arg[3] == '*') + new->flags |= CONFIG_NOCLID; + else + { + int len; + arg += 3; /* dump id: */ + if (strchr(arg, ':')) + len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); + else + { + unhide_metas(arg); + len = (int) strlen(arg); + } + + if (len == -1) + problem = _("bad hex constant"); + else if ((new->clid = opt_malloc(len))) + { + new->flags |= CONFIG_CLID; + new->clid_len = len; + memcpy(new->clid, arg, len); + } + } + } + /* dhcp-host has strange backwards-compat needs. */ + else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) + { + struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); + struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); + newtag->net = opt_malloc(strlen(arg + 4) + 1); + newlist->next = new->netid; + new->netid = newlist; + newlist->list = newtag; + strcpy(newtag->net, arg+4); + unhide_metas(newtag->net); + } + else + { + struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); + if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, + &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) + problem = _("bad hex constant"); + else + { + + newhw->next = new->hwaddr; + new->hwaddr = newhw; + } + } + } + else if (strchr(a[j], '.') && (in.s_addr = inet_addr(a[j])) != (in_addr_t)-1) + { + new->addr = in; + new->flags |= CONFIG_ADDR; + } + else + { + char *cp, *lastp = NULL, last = 0; + int fac = 1; + + if (strlen(a[j]) > 1) + { + lastp = a[j] + strlen(a[j]) - 1; + last = *lastp; + switch (last) + { + case 'd': + case 'D': + fac *= 24; + /* fall through */ + case 'h': + case 'H': + fac *= 60; + /* fall through */ + case 'm': + case 'M': + fac *= 60; + /* fall through */ + case 's': + case 'S': + *lastp = 0; + } + } + + for (cp = a[j]; *cp; cp++) + if (!isdigit((unsigned char)*cp) && *cp != ' ') + break; + + if (*cp) + { + if (lastp) + *lastp = last; + if (strcmp(a[j], "infinite") == 0) + { + new->lease_time = 0xffffffff; + new->flags |= CONFIG_TIME; + } + else if (strcmp(a[j], "ignore") == 0) + new->flags |= CONFIG_DISABLE; + else + { + if (!(new->hostname = canonicalise_opt(a[j])) || + !legal_hostname(new->hostname)) + problem = _("bad DHCP host name"); + else + new->flags |= CONFIG_NAME; + new->domain = NULL; + } + } + else + { + new->lease_time = atoi(a[j]) * fac; + /* Leases of a minute or less confuse + some clients, notably Apple's */ + if (new->lease_time < 120) + new->lease_time = 120; + new->flags |= CONFIG_TIME; + } + } + + daemon->dhcp_conf = new; + break; + } + + case LOPT_TAG_IF: /* --tag-if */ + { + struct tag_if *new = opt_malloc(sizeof(struct tag_if)); + + new->tag = NULL; + new->set = NULL; + new->next = NULL; + + /* preserve order */ + if (!daemon->tag_if) + daemon->tag_if = new; + else + { + struct tag_if *tmp; + for (tmp = daemon->tag_if; tmp->next; tmp = tmp->next); + tmp->next = new; + } + + while (arg) + { + size_t len; + + comma = split(arg); + len = strlen(arg); + + if (len < 5) + { + new->set = NULL; + break; + } + else + { + struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); + newtag->net = opt_malloc(len - 3); + strcpy(newtag->net, arg+4); + unhide_metas(newtag->net); + + if (strstr(arg, "set:") == arg) + { + struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); + newlist->next = new->set; + new->set = newlist; + newlist->list = newtag; + } + else if (strstr(arg, "tag:") == arg) + { + newtag->next = new->tag; + new->tag = newtag; + } + else + { + new->set = NULL; + break; + } + } + + arg = comma; + } + + if (!new->set) + problem = _("bad tag-if"); + + break; + } + + + case 'O': /* --dhcp-option */ + case LOPT_FORCE: /* --dhcp-option-force */ + case LOPT_OPTS: + case LOPT_MATCH: /* --dhcp-match */ + problem = parse_dhcp_opt(arg, + option == LOPT_FORCE ? DHOPT_FORCE : + (option == LOPT_MATCH ? DHOPT_MATCH : + (option == LOPT_OPTS ? DHOPT_BANK : 0))); + break; + + case 'M': /* --dhcp-boot */ + { + 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) + option = '?'; + else + { + char *dhcp_file, *dhcp_sname = NULL; + struct in_addr dhcp_next_server; + comma = split(arg); + dhcp_file = opt_string_alloc(arg); + dhcp_next_server.s_addr = 0; + if (comma) + { + arg = comma; + comma = split(arg); + dhcp_sname = opt_string_alloc(arg); + if (comma) + { + unhide_metas(comma); + if ((dhcp_next_server.s_addr = inet_addr(comma)) == (in_addr_t)-1) + option = '?'; + } + } + if (option != '?') + { + struct dhcp_boot *new = opt_malloc(sizeof(struct dhcp_boot)); + new->file = dhcp_file; + new->sname = dhcp_sname; + new->next_server = dhcp_next_server; + new->netid = id; + new->next = daemon->boot_config; + daemon->boot_config = 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 */ + + while (is_tag_prefix(arg)) + { + struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); + comma = split(arg); + nn->next = new->netid; + new->netid = nn; + nn->net = opt_string_alloc(arg+4); + arg = comma; + } + + if (!arg) + option = '?'; + else + { + comma = split(arg); + unhide_metas(arg); + new->len = strlen(arg) + 1; + new->val = opt_malloc(new->len); + memcpy(new->val + 1, arg, new->len - 1); + + new->u.vendor_class = (unsigned char *)"PXEClient"; + new->flags = DHOPT_VENDOR; + + if (comma && atoi_check(comma, &timeout)) + *(new->val) = timeout; + else + *(new->val) = 255; + + new->next = daemon->dhcp_opts; + daemon->dhcp_opts = new; + daemon->enable_pxe = 1; + } + + break; + } + + case LOPT_PXE_SERV: /* --pxe-service */ + { + 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 }; + static int boottype = 32768; + + new->netid = NULL; + new->server.s_addr = 0; + + while (is_tag_prefix(arg)) + { + struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); + comma = split(arg); + nn->next = new->netid; + new->netid = nn; + nn->net = opt_string_alloc(arg+4); + arg = comma; + } + + if (arg && (comma = split(arg))) + { + for (i = 0; CSA[i]; i++) + if (strcasecmp(CSA[i], arg) == 0) + break; + + if (CSA[i] || atoi_check(arg, &i)) + { + arg = comma; + comma = split(arg); + + new->CSA = i; + new->menu = opt_string_alloc(arg); + + if (!comma) + { + new->type = 0; /* local boot */ + new->basename = NULL; + } + else + { + arg = comma; + comma = split(arg); + if (atoi_check(arg, &i)) + { + new->type = i; + new->basename = NULL; + } + else + { + new->type = boottype++; + new->basename = opt_string_alloc(arg); + } + + if (comma && (new->server.s_addr = inet_addr(comma)) == (in_addr_t)-1) + option = '?'; + } + + /* Order matters */ + new->next = NULL; + if (!daemon->pxe_services) + daemon->pxe_services = new; + else + { + struct pxe_service *s; + for (s = daemon->pxe_services; s->next; s = s->next); + s->next = new; + } + + daemon->enable_pxe = 1; + break; + + } + } + + option = '?'; + break; + } + + case '4': /* --dhcp-mac */ + { + if (!(comma = split(arg))) + option = '?'; + else + { + struct dhcp_mac *new = opt_malloc(sizeof(struct dhcp_mac)); + new->netid.net = opt_string_alloc(set_prefix(arg)); + unhide_metas(comma); + new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type); + if (new->hwaddr_len == -1) + option = '?'; + else + { + new->next = daemon->dhcp_macs; + daemon->dhcp_macs = new; + } + } + } + break; + + case 'U': /* --dhcp-vendorclass */ + case 'j': /* --dhcp-userclass */ + case LOPT_CIRCUIT: /* --dhcp-circuitid */ + case LOPT_REMOTE: /* --dhcp-remoteid */ + case LOPT_SUBSCR: /* --dhcp-subscrid */ + { + if (!(comma = split(arg))) + option = '?'; + else + { + unsigned char *p; + int dig = 0; + struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); + new->netid.net = opt_string_alloc(set_prefix(arg)); + /* check for hex string - must digits may include : must not have nothing else, + only allowed for agent-options. */ + for (p = (unsigned char *)comma; *p; p++) + if (isxdigit(*p)) + dig = 1; + else if (*p != ':') + break; + unhide_metas(comma); + if (option == 'U' || option == 'j' || *p || !dig) + { + new->len = strlen(comma); + new->data = opt_malloc(new->len); + memcpy(new->data, comma, new->len); + } + else + { + new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL); + new->data = opt_malloc(new->len); + memcpy(new->data, comma, new->len); + } + + switch (option) + { + case 'j': + new->match_type = MATCH_USER; + break; + case 'U': + new->match_type = MATCH_VENDOR; + break; + case LOPT_CIRCUIT: + new->match_type = MATCH_CIRCUIT; + break; + case LOPT_REMOTE: + new->match_type = MATCH_REMOTE; + break; + case LOPT_SUBSCR: + new->match_type = MATCH_SUBSCRIBER; + break; + } + new->next = daemon->dhcp_vendors; + daemon->dhcp_vendors = new; + } + break; + } + + case LOPT_ALTPORT: /* --dhcp-alternate-port */ + if (!arg) + { + daemon->dhcp_server_port = DHCP_SERVER_ALTPORT; + daemon->dhcp_client_port = DHCP_CLIENT_ALTPORT; + } + else + { + comma = split(arg); + if (!atoi_check16(arg, &daemon->dhcp_server_port) || + (comma && !atoi_check16(comma, &daemon->dhcp_client_port))) + problem = _("invalid port number"); + if (!comma) + daemon->dhcp_client_port = daemon->dhcp_server_port+1; + } + break; + + case 'J': /* --dhcp-ignore */ + case LOPT_NO_NAMES: /* --dhcp-ignore-names */ + case LOPT_BROADCAST: /* --dhcp-broadcast */ + case '3': /* --bootp-dynamic */ + case LOPT_GEN_NAMES: /* --dhcp-generate-names */ + { + struct dhcp_netid_list *new = opt_malloc(sizeof(struct dhcp_netid_list)); + struct dhcp_netid *list = NULL; + if (option == 'J') + { + new->next = daemon->dhcp_ignore; + daemon->dhcp_ignore = new; + } + else if (option == LOPT_BROADCAST) + { + new->next = daemon->force_broadcast; + daemon->force_broadcast = new; + } + else if (option == '3') + { + new->next = daemon->bootp_dynamic; + daemon->bootp_dynamic = new; + } + else if (option == LOPT_GEN_NAMES) + { + new->next = daemon->dhcp_gen_names; + daemon->dhcp_gen_names = new; + } + else + { + new->next = daemon->dhcp_ignore_names; + daemon->dhcp_ignore_names = new; + } + + while (arg) { + struct dhcp_netid *member = opt_malloc(sizeof(struct dhcp_netid)); + comma = split(arg); + member->next = list; + list = member; + if (is_tag_prefix(arg)) + member->net = opt_string_alloc(arg+4); + else + member->net = opt_string_alloc(arg); + arg = comma; + } + + new->list = list; + break; + } + + case LOPT_PROXY: /* --dhcp-proxy */ + daemon->override = 1; + while (arg) { + struct addr_list *new = opt_malloc(sizeof(struct addr_list)); + comma = split(arg); + if ((new->addr.s_addr = inet_addr(arg)) == (in_addr_t)-1) + problem = _("bad dhcp-proxy address"); + new->next = daemon->override_relays; + daemon->override_relays = new; + arg = comma; + } + break; +#endif + + case 'V': /* --alias */ + { + char *dash, *a[3] = { NULL, NULL, NULL }; + int k = 0; + struct doctor *new = opt_malloc(sizeof(struct doctor)); + new->next = daemon->doctors; + daemon->doctors = new; + new->mask.s_addr = 0xffffffff; + new->end.s_addr = 0; + + if ((a[0] = arg)) + for (k = 1; k < 3; k++) + { + if (!(a[k] = split(a[k-1]))) + break; + unhide_metas(a[k]); + } + + dash = split_chr(a[0], '-'); + + if ((k < 2) || + ((new->in.s_addr = inet_addr(a[0])) == (in_addr_t)-1) || + ((new->out.s_addr = inet_addr(a[1])) == (in_addr_t)-1)) + option = '?'; + + if (k == 3) + new->mask.s_addr = inet_addr(a[2]); + + if (dash && + ((new->end.s_addr = inet_addr(dash)) == (in_addr_t)-1 || + !is_same_net(new->in, new->end, new->mask) || + ntohl(new->in.s_addr) > ntohl(new->end.s_addr))) + problem = _("invalid alias range"); + + break; + } + + case LOPT_INTNAME: /* --interface-name */ + { + struct interface_name *new, **up; + char *domain = NULL; + + comma = split(arg); + + if (!comma || !(domain = canonicalise_opt(arg))) + problem = _("bad interface name"); + + new = opt_malloc(sizeof(struct interface_name)); + new->next = NULL; + /* Add to the end of the list, so that first name + of an interface is used for PTR lookups. */ + for (up = &daemon->int_names; *up; up = &((*up)->next)); + *up = new; + new->name = domain; + new->intr = opt_string_alloc(comma); + break; + } + + case LOPT_CNAME: /* --cname */ + { + struct cname *new; + + if (!(comma = split(arg))) + option = '?'; + else + { + char *alias = canonicalise_opt(arg); + char *target = canonicalise_opt(comma); + + if (!alias || !target) + problem = _("bad CNAME"); + else + { + for (new = daemon->cnames; new; new = new->next) + if (hostname_isequal(new->alias, arg)) + problem = _("duplicate CNAME"); + new = opt_malloc(sizeof(struct cname)); + new->next = daemon->cnames; + daemon->cnames = new; + new->alias = alias; + new->target = target; + } + } + break; + } + + case LOPT_PTR: /* --ptr-record */ + { + struct ptr_record *new; + char *dom, *target = NULL; + + comma = split(arg); + + if (!(dom = canonicalise_opt(arg)) || + (comma && !(target = canonicalise_opt(comma)))) + problem = _("bad PTR record"); + else + { + new = opt_malloc(sizeof(struct ptr_record)); + new->next = daemon->ptr; + daemon->ptr = new; + new->name = dom; + new->ptr = target; + } + break; + } + + case LOPT_NAPTR: /* --naptr-record */ + { + char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + int k = 0; + struct naptr *new; + int order, pref; + char *name, *replace = NULL; + + if ((a[0] = arg)) + for (k = 1; k < 7; k++) + if (!(a[k] = split(a[k-1]))) + break; + + + if (k < 6 || + !(name = canonicalise_opt(a[0])) || + !atoi_check16(a[1], &order) || + !atoi_check16(a[2], &pref) || + (k == 7 && !(replace = canonicalise_opt(a[6])))) + problem = _("bad NAPTR record"); + else + { + new = opt_malloc(sizeof(struct naptr)); + new->next = daemon->naptr; + daemon->naptr = new; + new->name = name; + new->flags = opt_string_alloc(a[3]); + new->services = opt_string_alloc(a[4]); + new->regexp = opt_string_alloc(a[5]); + new->replace = replace; + new->order = order; + new->pref = pref; + } + break; + } + + case 'Y': /* --txt-record */ + { + struct txt_record *new; + unsigned char *p, *cnt; + size_t len; + + comma = split(arg); + + new = opt_malloc(sizeof(struct txt_record)); + new->next = daemon->txt; + daemon->txt = new; + new->class = C_IN; + + if (!(new->name = canonicalise_opt(arg))) + { + problem = _("bad TXT record"); + break; + } + + len = comma ? strlen(comma) : 0; + len += (len/255) + 1; /* room for extra counts */ + new->txt = p = opt_malloc(len); + + cnt = p++; + *cnt = 0; + + while (comma && *comma) + { + unsigned char c = (unsigned char)*comma++; + + if (c == ',' || *cnt == 255) + { + if (c != ',') + comma--; + cnt = p++; + *cnt = 0; + } + else + { + *p++ = unhide_meta(c); + (*cnt)++; + } + } + + new->len = p - new->txt; + + break; + } + + case 'W': /* --srv-host */ + { + int port = 1, priority = 0, weight = 0; + char *name, *target = NULL; + struct mx_srv_record *new; + + comma = split(arg); + + if (!(name = canonicalise_opt(arg))) + problem = _("bad SRV record"); + + if (comma) + { + arg = comma; + comma = split(arg); + if (!(target = canonicalise_opt(arg)) +) problem = _("bad SRV target"); + + if (comma) + { + arg = comma; + comma = split(arg); + if (!atoi_check16(arg, &port)) + problem = _("invalid port number"); + + if (comma) + { + arg = comma; + comma = split(arg); + if (!atoi_check16(arg, &priority)) + problem = _("invalid priority"); + + if (comma) + { + arg = comma; + comma = split(arg); + if (!atoi_check16(arg, &weight)) + problem = _("invalid weight"); + } + } + } + } + + new = opt_malloc(sizeof(struct mx_srv_record)); + new->next = daemon->mxnames; + daemon->mxnames = new; + new->issrv = 1; + new->name = name; + new->target = target; + new->srvport = port; + new->priority = priority; + new->weight = weight; + break; + } + + default: + return _("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"); + + } + + if (problem) + return problem; + + if (option == '?') + return gen_prob; + + return NULL; +} + +static void read_file(char *file, FILE *f, int hard_opt) +{ + volatile int lineno = 0; + char *buff = daemon->namebuff; + + while (fgets(buff, MAXDNAME, f)) + { + int white, i, option; ; + char *errmess, *p, *arg, *start; + size_t len; + + /* Memory allocation failure longjmps here if mem_recover == 1 */ + if (hard_opt) + { + if (setjmp(mem_jmp)) + continue; + mem_recover = 1; + } + + lineno++; + errmess = NULL; + + /* Implement quotes, inside quotes we allow \\ \" \n and \t + metacharacters get hidden also strip comments */ + for (white = 1, p = buff; *p; p++) + { + if (*p == '"') + { + memmove(p, p+1, strlen(p+1)+1); + + for(; *p && *p != '"'; p++) + { + if (*p == '\\' && strchr("\"tnebr\\", p[1])) + { + if (p[1] == 't') + p[1] = '\t'; + else if (p[1] == 'n') + p[1] = '\n'; + else if (p[1] == 'b') + p[1] = '\b'; + else if (p[1] == 'r') + p[1] = '\r'; + else if (p[1] == 'e') /* escape */ + p[1] = '\033'; + memmove(p, p+1, strlen(p+1)+1); + } + *p = hide_meta(*p); + } + + if (*p == 0) + { + errmess = _("missing \""); + goto oops; + } + + memmove(p, p+1, strlen(p+1)+1); + } + + if (isspace(*p)) + { + *p = ' '; + white = 1; + } + else + { + if (white && *p == '#') + { + *p = 0; + break; + } + white = 0; + } + } + + + /* strip leading spaces */ + for (start = buff; *start && *start == ' '; start++); + + /* strip trailing spaces */ + for (len = strlen(start); (len != 0) && (start[len-1] == ' '); len--); + + if (len == 0) + continue; + else + start[len] = 0; + + if (hard_opt != 0) + arg = start; + else if ((p=strchr(start, '='))) + { + /* allow spaces around "=" */ + for (arg = p+1; *arg == ' '; arg++); + for (; p >= start && (*p == ' ' || *p == '='); p--) + *p = 0; + } + else + arg = NULL; + + if (hard_opt != 0) + option = hard_opt; + else + { + for (option = 0, i = 0; opts[i].name; i++) + if (strcmp(opts[i].name, start) == 0) + { + option = opts[i].val; + break; + } + + if (!option) + errmess = _("bad option"); + else if (opts[i].has_arg == 0 && arg) + errmess = _("extraneous parameter"); + else if (opts[i].has_arg == 1 && !arg) + errmess = _("missing parameter"); + } + + if (!errmess) + errmess = one_opt(option, arg, _("error"), 0); + + if (errmess) + { + oops: + sprintf(buff, _("%s at line %d of %%s"), errmess, lineno); + if (hard_opt != 0) + my_syslog(LOG_ERR, buff, file); + else + die(buff, file, EC_BADCONF); + } + } + + mem_recover = 0; + fclose(f); +} + +static void one_file(char *file, int hard_opt) +{ + FILE *f; + int nofile_ok = 0; + static int read_stdin = 0; + static struct fileread { + dev_t dev; + ino_t ino; + struct fileread *next; + } *filesread = NULL; + + if (hard_opt == '7') + { + /* default conf-file reading */ + hard_opt = 0; + nofile_ok = 1; + } + + if (hard_opt == 0 && strcmp(file, "-") == 0) + { + if (read_stdin == 1) + return; + read_stdin = 1; + file = "stdin"; + f = stdin; + } + else + { + /* ignore repeated files. */ + struct stat statbuf; + + if (hard_opt == 0 && stat(file, &statbuf) == 0) + { + struct fileread *r; + + for (r = filesread; r; r = r->next) + if (r->dev == statbuf.st_dev && r->ino == statbuf.st_ino) + return; + + r = safe_malloc(sizeof(struct fileread)); + r->next = filesread; + filesread = r; + r->dev = statbuf.st_dev; + r->ino = statbuf.st_ino; + } + + if (!(f = fopen(file, "r"))) + { + if (errno == ENOENT && nofile_ok) + return; /* No conffile, all done. */ + else + { + char *str = _("cannot read %s: %s"); + if (hard_opt != 0) + { + my_syslog(LOG_ERR, str, file, strerror(errno)); + return; + } + else + die(str, file, EC_FILE); + } + } + } + + read_file(file, f, hard_opt); +} + +/* expand any name which is a directory */ +struct hostsfile *expand_filelist(struct hostsfile *list) +{ + int i; + struct hostsfile *ah; + + for (i = 0, ah = list; ah; ah = ah->next) + { + if (i <= ah->index) + i = ah->index + 1; + + if (ah->flags & AH_DIR) + ah->flags |= AH_INACTIVE; + else + ah->flags &= ~AH_INACTIVE; + } + + for (ah = list; ah; ah = ah->next) + if (!(ah->flags & AH_INACTIVE)) + { + struct stat buf; + if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode)) + { + DIR *dir_stream; + struct dirent *ent; + + /* don't read this as a file */ + ah->flags |= AH_INACTIVE; + + if (!(dir_stream = opendir(ah->fname))) + my_syslog(LOG_ERR, _("cannot access directory %s: %s"), + ah->fname, strerror(errno)); + else + { + while ((ent = readdir(dir_stream))) + { + size_t lendir = strlen(ah->fname); + size_t lenfile = strlen(ent->d_name); + struct hostsfile *ah1; + char *path; + + /* ignore emacs backups and dotfiles */ + if (lenfile == 0 || + ent->d_name[lenfile - 1] == '~' || + (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || + ent->d_name[0] == '.') + continue; + + /* see if we have an existing record. + dir is ah->fname + file is ent->d_name + path to match is ah1->fname */ + + for (ah1 = list; ah1; ah1 = ah1->next) + { + if (lendir < strlen(ah1->fname) && + strstr(ah1->fname, ah->fname) == ah1->fname && + ah1->fname[lendir] == '/' && + strcmp(ah1->fname + lendir + 1, ent->d_name) == 0) + { + ah1->flags &= ~AH_INACTIVE; + break; + } + } + + /* make new record */ + if (!ah1) + { + if (!(ah1 = whine_malloc(sizeof(struct hostsfile)))) + continue; + + if (!(path = whine_malloc(lendir + lenfile + 2))) + { + free(ah1); + continue; + } + + strcpy(path, ah->fname); + strcat(path, "/"); + strcat(path, ent->d_name); + ah1->fname = path; + ah1->index = i++; + ah1->flags = AH_DIR; + ah1->next = list; + list = ah1; + } + + /* inactivate record if not regular file */ + if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 && !S_ISREG(buf.st_mode)) + ah1->flags |= AH_INACTIVE; + + } + closedir(dir_stream); + } + } + } + + return list; +} + + +#ifdef HAVE_DHCP +void reread_dhcp(void) +{ + struct hostsfile *hf; + + if (daemon->dhcp_hosts_file) + { + struct dhcp_config *configs, *cp, **up; + + /* remove existing... */ + for (up = &daemon->dhcp_conf, configs = daemon->dhcp_conf; configs; configs = cp) + { + cp = configs->next; + + if (configs->flags & CONFIG_BANK) + { + struct hwaddr_config *mac, *tmp; + struct dhcp_netid_list *list, *tmplist; + + for (mac = configs->hwaddr; mac; mac = tmp) + { + 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; + } + + 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)) + { + 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)) + { + one_file(hf->fname, LOPT_OPTS); + my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); + } + } +} +#endif + +void read_opts(int argc, char **argv, char *compile_opts) +{ + char *buff = opt_malloc(MAXDNAME); + int option, conffile_opt = '7', testmode = 0; + char *errmess, *arg, *conffile = CONFFILE; + + opterr = 0; + + daemon = opt_malloc(sizeof(struct daemon)); + memset(daemon, 0, sizeof(struct daemon)); + daemon->namebuff = buff; + + /* Set defaults - everything else is zero or NULL */ + daemon->cachesize = CACHESIZ; + daemon->ftabsize = FTABSIZ; + daemon->port = NAMESERVER_PORT; + daemon->dhcp_client_port = DHCP_CLIENT_PORT; + daemon->dhcp_server_port = DHCP_SERVER_PORT; + daemon->default_resolv.is_default = 1; + daemon->default_resolv.name = RESOLVFILE; + daemon->resolv_files = &daemon->default_resolv; + daemon->username = CHUSER; + daemon->runfile = RUNFILE; + daemon->dhcp_max = MAXLEASES; + daemon->tftp_max = TFTP_MAX_CONNECTIONS; + daemon->edns_pktsz = EDNS_PKTSZ; + daemon->log_fac = -1; + add_txt("version.bind", "dnsmasq-" VERSION ); + add_txt("authors.bind", "Simon Kelley"); + add_txt("copyright.bind", COPYRIGHT); + + while (1) + { +#ifdef HAVE_GETOPT_LONG + option = getopt_long(argc, argv, OPTSTRING, opts, NULL); +#else + option = getopt(argc, argv, OPTSTRING); +#endif + + if (option == -1) + { + for (; optind < argc; optind++) + { + unsigned char *c = (unsigned char *)argv[optind]; + for (; *c != 0; c++) + if (!isspace(*c)) + die(_("junk found in command line"), NULL, EC_BADCONF); + } + break; + } + + /* Copy optarg so that argv doesn't get changed */ + if (optarg) + { + strncpy(buff, optarg, MAXDNAME); + buff[MAXDNAME-1] = 0; + arg = buff; + } + else + arg = NULL; + + /* command-line only stuff */ + if (option == LOPT_TEST) + testmode = 1; + else if (option == 'w') + { + if (argc != 3 || strcmp(argv[2], "dhcp") != 0) + do_usage(); +#ifdef HAVE_DHCP + else + display_opts(); +#endif + exit(0); + } + else if (option == 'v') + { + printf(_("Dnsmasq version %s %s\n"), VERSION, COPYRIGHT); + printf(_("Compile time options %s\n\n"), compile_opts); + printf(_("This software comes with ABSOLUTELY NO WARRANTY.\n")); + printf(_("Dnsmasq is free software, and you are welcome to redistribute it\n")); + printf(_("under the terms of the GNU General Public License, version 2 or 3.\n")); + exit(0); + } + else if (option == 'C') + { + conffile_opt = 0; /* file must exist */ + conffile = opt_string_alloc(arg); + } + else + { +#ifdef HAVE_GETOPT_LONG + errmess = one_opt(option, arg, _("try --help"), 1); +#else + errmess = one_opt(option, arg, _("try -w"), 1); +#endif + if (errmess) + die(_("bad command line options: %s"), errmess, EC_BADCONF); + } + } + + if (conffile) + one_file(conffile, conffile_opt); + + /* port might not be known when the address is parsed - fill in here */ + if (daemon->servers) + { + struct server *tmp; + for (tmp = daemon->servers; tmp; tmp = tmp->next) + 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); +#endif + } + } + + if (daemon->if_addrs) + { + struct iname *tmp; + for(tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (tmp->addr.sa.sa_family == AF_INET) + tmp->addr.in.sin_port = htons(daemon->port); +#ifdef HAVE_IPV6 + else if (tmp->addr.sa.sa_family == AF_INET6) + tmp->addr.in6.sin6_port = htons(daemon->port); +#endif /* IPv6 */ + } + + /* only one of these need be specified: the other defaults to the host-name */ + if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget) + { + struct mx_srv_record *mx; + + if (gethostname(buff, MAXDNAME) == -1) + die(_("cannot get host-name: %s"), NULL, EC_MISC); + + for (mx = daemon->mxnames; mx; mx = mx->next) + if (!mx->issrv && hostname_isequal(mx->name, buff)) + break; + + if ((daemon->mxtarget || option_bool(OPT_LOCALMX)) && !mx) + { + mx = opt_malloc(sizeof(struct mx_srv_record)); + mx->next = daemon->mxnames; + mx->issrv = 0; + mx->target = NULL; + mx->name = opt_string_alloc(buff); + daemon->mxnames = mx; + } + + if (!daemon->mxtarget) + daemon->mxtarget = opt_string_alloc(buff); + + for (mx = daemon->mxnames; mx; mx = mx->next) + if (!mx->issrv && !mx->target) + mx->target = daemon->mxtarget; + } + + if (!option_bool(OPT_NO_RESOLV) && + daemon->resolv_files && + daemon->resolv_files->next && + option_bool(OPT_NO_POLL)) + die(_("only one resolv.conf file allowed in no-poll mode."), NULL, EC_BADCONF); + + if (option_bool(OPT_RESOLV_DOMAIN)) + { + char *line; + FILE *f; + + if (option_bool(OPT_NO_RESOLV) || + !daemon->resolv_files || + (daemon->resolv_files)->next) + die(_("must have exactly one resolv.conf to read domain from."), NULL, EC_BADCONF); + + if (!(f = fopen((daemon->resolv_files)->name, "r"))) + die(_("failed to read %s: %s"), (daemon->resolv_files)->name, EC_FILE); + + while ((line = fgets(buff, MAXDNAME, f))) + { + char *token = strtok(line, " \t\n\r"); + + if (!token || strcmp(token, "search") != 0) + continue; + + if ((token = strtok(NULL, " \t\n\r")) && + (daemon->domain_suffix = canonicalise_opt(token))) + break; + } + + fclose(f); + + if (!daemon->domain_suffix) + die(_("no search directive found in %s"), (daemon->resolv_files)->name, EC_MISC); + } + + if (daemon->domain_suffix) + { + /* add domain for any srv record without one. */ + struct mx_srv_record *srv; + + for (srv = daemon->mxnames; srv; srv = srv->next) + if (srv->issrv && + strchr(srv->name, '.') && + strchr(srv->name, '.') == strrchr(srv->name, '.')) + { + strcpy(buff, srv->name); + strcat(buff, "."); + strcat(buff, daemon->domain_suffix); + free(srv->name); + srv->name = opt_string_alloc(buff); + } + } + else if (option_bool(OPT_DHCP_FQDN)) + die(_("there must be a default domain when --dhcp-fqdn is set"), NULL, EC_BADCONF); + + if (testmode) + { + fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK")); + exit(0); + } +} diff --git a/src/rfc1035.c b/src/rfc1035.c new file mode 100644 index 0000000..889c1f0 --- /dev/null +++ b/src/rfc1035.c @@ -0,0 +1,1815 @@ +/* dnsmasq is Copyright (c) 2000-2011 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" + +static int add_resource_record(struct dns_header *header, char *limit, int *truncp, + unsigned int nameoffset, unsigned char **pp, + unsigned long ttl, unsigned int *offset, unsigned short type, + unsigned short class, char *format, ...); + +#define CHECK_LEN(header, pp, plen, len) \ + ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) + +#define ADD_RDLEN(header, pp, plen, len) \ + (!CHECK_LEN(header, pp, plen, len) ? 0 : (long)((pp) += (len)), 1) + +static int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes) +{ + unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL; + unsigned int j, l, hops = 0; + int retvalue = 1; + + if (isExtract) + *cp = 0; + + while (1) + { + unsigned int label_type; + + if (!CHECK_LEN(header, p, plen, 1)) + return 0; + + 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)) + return 0; + + if (isExtract) + { + if (cp != (unsigned char *)name) + cp--; + *cp = 0; /* terminate: lose final period */ + } + else if (*cp != 0) + retvalue = 2; + + if (p1) /* we jumped via compression */ + *pp = p1; + else + *pp = p; + + return retvalue; + } + + label_type = l & 0xc0; + + if (label_type == 0xc0) /* pointer */ + { + if (!CHECK_LEN(header, p, plen, 1)) + return 0; + + /* get offset */ + l = (l&0x3f) << 8; + l |= *p++; + + if (!p1) /* first jump, save location to go back to */ + p1 = p; + + hops++; /* break malicious infinite loops */ + if (hops > 255) + return 0; + + p = l + (unsigned char *)header; + } + else if (label_type == 0x80) + return 0; /* reserved */ + else if (label_type == 0x40) + { /* ELT */ + unsigned int count, digs; + + if ((l & 0x3f) != 1) + return 0; /* we only understand bitstrings */ + + if (!isExtract) + return 0; /* Cannot compare bitsrings */ + + count = *p++; + if (count == 0) + count = 256; + digs = ((count-1)>>2)+1; + + /* output is \[x<hex>/siz]. which is digs+9 chars */ + if (cp - (unsigned char *)name + digs + 9 >= MAXDNAME) + return 0; + if (!CHECK_LEN(header, p, plen, (count-1)>>3)) + return 0; + + *cp++ = '\\'; + *cp++ = '['; + *cp++ = 'x'; + for (j=0; j<digs; j++) + { + unsigned int dig; + if (j%2 == 0) + dig = *p >> 4; + else + dig = *p++ & 0x0f; + + *cp++ = dig < 10 ? dig + '0' : dig + 'A' - 10; + } + cp += sprintf((char *)cp, "/%d]", count); + /* do this here to overwrite the zero char from sprintf */ + *cp++ = '.'; + } + else + { /* label_type = 0 -> label. */ + if (cp - (unsigned char *)name + l + 1 >= MAXDNAME) + return 0; + if (!CHECK_LEN(header, p, plen, l)) + return 0; + + for(j=0; j<l; j++, p++) + if (isExtract) + { + unsigned char c = *p; + if (isascii(c) && !iscntrl(c) && c != '.') + *cp++ = *p; + else + return 0; + } + else + { + unsigned char c1 = *cp, c2 = *p; + + if (c1 == 0) + retvalue = 2; + else + { + cp++; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + retvalue = 2; + } + } + + if (isExtract) + *cp++ = '.'; + else if (*cp != 0 && *cp++ != '.') + retvalue = 2; + } + } +} + +/* Max size of input string (for IPv6) is 75 chars.) */ +#define MAXARPANAME 75 +static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) +{ + int j; + char name[MAXARPANAME+1], *cp1; + unsigned char *addr = (unsigned char *)addrp; + char *lastchunk = NULL, *penchunk = NULL; + + if (strlen(namein) > MAXARPANAME) + return 0; + + memset(addrp, 0, sizeof(struct all_addr)); + + /* turn name into a series of asciiz strings */ + /* j counts no of labels */ + for(j = 1,cp1 = name; *namein; cp1++, namein++) + if (*namein == '.') + { + penchunk = lastchunk; + lastchunk = cp1 + 1; + *cp1 = 0; + j++; + } + else + *cp1 = *namein; + + *cp1 = 0; + + if (j<3) + return 0; + + if (hostname_isequal(lastchunk, "arpa") && hostname_isequal(penchunk, "in-addr")) + { + /* IP v4 */ + /* address arives 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. */ + for (cp1 = name; cp1 != penchunk; cp1 += strlen(cp1)+1) + { + /* check for digits only (weeds out things like + 50.0/24.67.28.64.in-addr.arpa which are used + as CNAME targets according to RFC 2317 */ + char *cp; + for (cp = cp1; *cp; cp++) + if (!isdigit((unsigned char)*cp)) + return 0; + + addr[3] = addr[2]; + addr[2] = addr[1]; + addr[1] = addr[0]; + addr[0] = atoi(cp1); + } + + return F_IPV4; + } +#ifdef HAVE_IPV6 + else if (hostname_isequal(penchunk, "ip6") && + (hostname_isequal(lastchunk, "int") || hostname_isequal(lastchunk, "arpa"))) + { + /* IP v6: + 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 + left-over from the many DNS-for-IPv6 wars. We support all the formats + that we can since there is no reason not to. + */ + + if (*name == '\\' && *(name+1) == '[' && + (*(name+2) == 'x' || *(name+2) == 'X')) + { + for (j = 0, cp1 = name+3; *cp1 && isxdigit((unsigned char) *cp1) && j < 32; cp1++, j++) + { + char xdig[2]; + xdig[0] = *cp1; + xdig[1] = 0; + if (j%2) + addr[j/2] |= strtol(xdig, NULL, 16); + else + addr[j/2] = strtol(xdig, NULL, 16) << 4; + } + + if (*cp1 == '/' && j == 32) + return F_IPV6; + } + else + { + for (cp1 = name; cp1 != penchunk; cp1 += strlen(cp1)+1) + { + if (*(cp1+1) || !isxdigit((unsigned char)*cp1)) + return 0; + + for (j = sizeof(struct all_addr)-1; j>0; j--) + addr[j] = (addr[j] >> 4) | (addr[j-1] << 4); + addr[0] = (addr[0] >> 4) | (strtol(cp1, NULL, 16) << 4); + } + + return F_IPV6; + } + } +#endif + + return 0; +} + +static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes) +{ + while(1) + { + unsigned int label_type; + + if (!CHECK_LEN(header, ansp, plen, 1)) + return NULL; + + label_type = (*ansp) & 0xc0; + + if (label_type == 0xc0) + { + /* pointer for compression. */ + ansp += 2; + break; + } + else if (label_type == 0x80) + return NULL; /* reserved */ + else if (label_type == 0x40) + { + /* Extended label type */ + unsigned int count; + + if (!CHECK_LEN(header, ansp, plen, 2)) + return NULL; + + if (((*ansp++) & 0x3f) != 1) + return NULL; /* 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 NULL; + + if (len == 0) + break; /* zero length label marks the end. */ + } + } + + if (!CHECK_LEN(header, ansp, plen, extrabytes)) + return NULL; + + return ansp; +} + +static unsigned char *skip_questions(struct dns_header *header, size_t plen) +{ + int q; + unsigned char *ansp = (unsigned char *)(header+1); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + if (!(ansp = skip_name(ansp, header, plen, 4))) + return NULL; + ansp += 4; /* class and type */ + } + + return ansp; +} + +static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen) +{ + int i, rdlen; + + for (i = 0; i < count; i++) + { + if (!(ansp = skip_name(ansp, header, plen, 10))) + return NULL; + ansp += 8; /* type, class, TTL */ + GETSHORT(rdlen, ansp); + if (!ADD_RDLEN(header, ansp, plen, rdlen)) + return NULL; + } + + return ansp; +} + +/* CRC the question section. This is used to safely detect query + retransmision 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 + if there is not question section. */ +unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned int crc = 0xffffffff; + unsigned char *p1, *p = (unsigned char *)(header+1); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + if (!extract_name(header, plen, &p, name, 1, 4)) + return crc; /* bad packet */ + + for (p1 = (unsigned char *)name; *p1; p1++) + { + int i = 8; + char c = *p1; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + crc ^= c << 24; + while (i--) + crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; + } + + /* CRC the class and type as well */ + for (p1 = p; p1 < p+4; p1++) + { + int i = 8; + crc ^= *p1 << 24; + while (i--) + crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; + } + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + return crc; /* bad packet */ + } + + return crc; +} + + +size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) +{ + unsigned char *ansp = skip_questions(header, plen); + + /* if packet is malformed, just return as-is. */ + if (!ansp) + return plen; + + if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), + header, plen))) + return plen; + + /* restore pseudoheader */ + if (pheader && ntohs(header->arcount) == 0) + { + /* must use memmove, may overlap */ + memmove(ansp, pheader, hlen); + header->arcount = htons(1); + ansp += hlen; + } + + 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_SIG || type == T_TSIG)) + *is_sign = 1; + } + + return ret; +} + +struct macparm { + unsigned char *limit; + struct dns_header *header; + size_t plen; + union mysockaddr *l3; +}; + +static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + struct macparm *parm = parmv; + int match = 0; + unsigned short rdlen; + struct dns_header *header = parm->header; + unsigned char *lenp, *datap, *p; + + 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 */ + + if (ntohs(header->arcount) == 0) + { + /* We are adding the pseudoheader */ + if (!(p = skip_questions(header, parm->plen)) || + !(p = skip_section(p, + ntohs(header->ancount) + ntohs(header->nscount), + header, parm->plen))) + return 0; + *p++ = 0; /* empty name */ + PUTSHORT(T_OPT, p); + PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */ + PUTLONG(0, p); /* extended RCODE */ + lenp = p; + PUTSHORT(0, p); /* RDLEN */ + rdlen = 0; + if (((ssize_t)maclen) > (parm->limit - (p + 4))) + return 0; /* Too big */ + header->arcount = htons(1); + datap = p; + } + else + { + int i, is_sign; + unsigned short code, len; + + if (ntohs(header->arcount) != 1 || + !(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) || + is_sign || + (!(p = skip_name(p, header, parm->plen, 10)))) + return 0; + + p += 8; /* skip UDP length and RCODE */ + + lenp = p; + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, parm->plen, rdlen)) + return 0; /* bad packet */ + datap = p; + + /* check if option already there */ + for (i = 0; i + 4 < rdlen; i += len + 4) + { + GETSHORT(code, p); + GETSHORT(len, p); + if (code == EDNS0_OPTION_MAC) + return 0; + p += len; + } + + if (((ssize_t)maclen) > (parm->limit - (p + 4))) + return 0; /* Too big */ + } + + PUTSHORT(EDNS0_OPTION_MAC, p); + PUTSHORT(maclen, p); + memcpy(p, mac, maclen); + p += maclen; + + PUTSHORT(p - datap, lenp); + parm->plen = p - (unsigned char *)header; + + 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; +} + + +/* is addr in the non-globally-routed IP space? */ +static 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) == 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) */ ; +} + +static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name) +{ + int i, qtype, qclass, rdlen; + unsigned long ttl; + + for (i = count; i != 0; i--) + { + if (name && option_bool(OPT_LOG)) + { + if (!extract_name(header, qlen, &p, name, 1, 10)) + return 0; + } + else if (!(p = skip_name(p, header, qlen, 10))) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (qclass == C_IN && qtype == T_A) + { + struct doctor *doctor; + struct in_addr addr; + + if (!CHECK_LEN(header, p, qlen, INADDRSZ)) + return 0; + + /* alignment */ + memcpy(&addr, p, INADDRSZ); + + for (doctor = daemon->doctors; doctor; doctor = doctor->next) + { + if (doctor->end.s_addr == 0) + { + if (!is_same_net(doctor->in, addr, doctor->mask)) + continue; + } + else if (ntohl(doctor->in.s_addr) > ntohl(addr.s_addr) || + ntohl(doctor->end.s_addr) < ntohl(addr.s_addr)) + continue; + + addr.s_addr &= ~doctor->mask.s_addr; + addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); + /* Since we munged the data, the server it came from is no longer authoritative */ + header->hb3 &= ~HB3_AA; + memcpy(p, &addr, INADDRSZ); + break; + } + } + else if (qtype == T_TXT && name && option_bool(OPT_LOG)) + { + unsigned char *p1 = p; + if (!CHECK_LEN(header, p1, qlen, rdlen)) + return 0; + while ((p1 - p) < rdlen) + { + unsigned int i, len = *p1; + unsigned char *p2 = p1; + /* make counted string zero-term and sanitise */ + for (i = 0; i < len; i++) + if (isprint(*(p2+1))) + { + *p2 = *(p2+1); + p2++; + } + *p2 = 0; + my_syslog(LOG_INFO, "reply %s is %s", name, p1); + /* restore */ + memmove(p1 + 1, p1, len); + *p1 = len; + p1 += len+1; + } + } + + if (!ADD_RDLEN(header, p, qlen, rdlen)) + return 0; /* bad packet */ + } + + return p; +} + +static int find_soa(struct dns_header *header, size_t qlen, char *name) +{ + unsigned char *p; + int qtype, qclass, rdlen; + unsigned long ttl, minttl = ULONG_MAX; + int i, found_soa = 0; + + /* first move to NS section and find TTL from any SOA section */ + if (!(p = skip_questions(header, qlen)) || + !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name))) + return 0; /* bad packet */ + + for (i = ntohs(header->nscount); i != 0; i--) + { + if (!(p = skip_name(p, header, qlen, 10))) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if ((qclass == C_IN) && (qtype == T_SOA)) + { + found_soa = 1; + if (ttl < minttl) + minttl = ttl; + + /* MNAME */ + if (!(p = skip_name(p, header, qlen, 0))) + return 0; + /* RNAME */ + if (!(p = skip_name(p, header, qlen, 20))) + return 0; + p += 16; /* SERIAL REFRESH RETRY EXPIRE */ + + GETLONG(ttl, p); /* minTTL */ + if (ttl < minttl) + minttl = ttl; + } + else if (!ADD_RDLEN(header, p, qlen, rdlen)) + return 0; /* bad packet */ + } + + /* rewrite addresses in additioal section too */ + if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL)) + return 0; + + if (!found_soa) + minttl = daemon->neg_ttl; + + return minttl; +} + +/* Note that the following code can create CNAME chains that don't point to a real record, + either because of lack of memory, or lack of SOA records. These are treated by the cache code as + 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, + int is_sign, int check_rebind, int checking_disabled) +{ + unsigned char *p, *p1, *endrr, *namep; + int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; + unsigned long ttl = 0; + struct all_addr addr; + + cache_start_insert(); + + /* find_soa is needed for dns_doctor and logging side-effects, so don't call it lazily if there are any. */ + if (daemon->doctors || option_bool(OPT_LOG)) + { + searched_soa = 1; + ttl = find_soa(header, qlen, name); + } + + /* go through the questions. */ + p = (unsigned char *)(header+1); + + for (i = ntohs(header->qdcount); i != 0; i--) + { + int found = 0, cname_count = 5; + struct crec *cpp = NULL; + int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; + unsigned long cttl = ULONG_MAX, attl; + + namep = p; + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qclass != C_IN) + continue; + + /* PTRs: we chase CNAMEs here, since we have no way to + represent them in the cache. */ + if (qtype == T_PTR) + { + int name_encoding = in_arpa_name_2_addr(name, &addr); + + if (!name_encoding) + continue; + + if (!(flags & F_NXDOMAIN)) + { + cname_loop: + if (!(p1 = skip_questions(header, qlen))) + return 0; + + for (j = ntohs(header->ancount); j != 0; j--) + { + 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) || + !(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ + + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) + { + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); + } + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + /* TTL of record is minimum of CNAMES and PTR */ + if (attl < cttl) + cttl = attl; + + if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR)) + { + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + + if (aqtype == T_CNAME) + { + if (!cname_count--) + return 0; /* looped CNAMES */ + goto cname_loop; + } + + cache_insert(name, &addr, now, cttl, name_encoding | F_REVERSE); + found = 1; + } + + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ + } + } + + if (!found && !option_bool(OPT_NO_NEG)) + { + if (!searched_soa) + { + searched_soa = 1; + ttl = find_soa(header, qlen, NULL); + } + if (ttl) + cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags); + } + } + else + { + /* everything other than PTR */ + struct crec *newc; + int addrlen; + + if (qtype == T_A) + { + addrlen = INADDRSZ; + flags |= F_IPV4; + } +#ifdef HAVE_IPV6 + else if (qtype == T_AAAA) + { + addrlen = IN6ADDRSZ; + flags |= F_IPV6; + } +#endif + else + continue; + + if (!(flags & F_NXDOMAIN)) + { + cname_loop1: + if (!(p1 = skip_questions(header, qlen))) + return 0; + + for (j = ntohs(header->ancount); j != 0; j--) + { + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ + + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) + { + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); + } + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + { + if (aqtype == T_CNAME) + { + if (!cname_count--) + return 0; /* looped CNAMES */ + newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD); + if (newc && cpp) + { + cpp->addr.cname.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + + cpp = newc; + if (attl < cttl) + cttl = attl; + + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + goto cname_loop1; + } + else + { + found = 1; + + /* copy address into aligned storage */ + if (!CHECK_LEN(header, p1, qlen, addrlen)) + return 0; /* bad packet */ + memcpy(&addr, p1, addrlen); + + /* check for returned address in private space */ + if (check_rebind && + (flags & F_IPV4) && + private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND))) + return 1; + + newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD); + if (newc && cpp) + { + cpp->addr.cname.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + cpp = NULL; + } + } + + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ + } + } + + if (!found && !option_bool(OPT_NO_NEG)) + { + if (!searched_soa) + { + searched_soa = 1; + ttl = find_soa(header, qlen, NULL); + } + /* If there's no SOA to get the TTL from, but there is a CNAME + pointing at this, inherit its TTL */ + if (ttl || cpp) + { + newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags); + if (newc && cpp) + { + cpp->addr.cname.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + } + } + } + } + + /* Don't put stuff from a truncated packet into the cache, + also don't cache replies where DNSSEC validation was turned off, either + the upstream server told us so, or the original query specified it. */ + if (!(header->hb3 & HB3_TC) && !(header->hb4 & HB4_CD) && !checking_disabled) + cache_end_insert(); + + return 0; +} + +/* If the packet holds exactly one query + return F_IPV4 or F_IPV6 and leave the name from the query in name */ + +unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) +{ + unsigned char *p = (unsigned char *)(header+1); + int qtype, qclass; + + if (typep) + *typep = 0; + + if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY) + return 0; /* must be exactly one query. */ + + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (typep) + *typep = qtype; + + if (qclass == C_IN) + { + if (qtype == T_A) + return F_IPV4; + if (qtype == T_AAAA) + return F_IPV6; + if (qtype == T_ANY) + return F_IPV4 | F_IPV6; + if (qtype == T_NS || qtype == T_SOA) + return F_QUERY | F_NSRR; + } + + return F_QUERY; +} + + +size_t setup_reply(struct dns_header *header, size_t qlen, + struct all_addr *addrp, unsigned int flags, unsigned long ttl) +{ + unsigned char *p = skip_questions(header, qlen); + + /* clear authoritative and truncated flags, set QR flag */ + header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; + /* set RA flag */ + header->hb4 |= HB4_RA; + + 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) + SET_RCODE(header, NOERROR); /* empty domain */ + else if (flags == F_NXDOMAIN) + SET_RCODE(header, NXDOMAIN); + else if (p && flags == F_IPV4) + { /* we know the address */ + SET_RCODE(header, NOERROR); + header->ancount = htons(1); + header->hb3 |= HB3_AA; + add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); + } +#ifdef HAVE_IPV6 + else if (p && flags == F_IPV6) + { + SET_RCODE(header, NOERROR); + header->ancount = htons(1); + header->hb3 |= HB3_AA; + add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_AAAA, C_IN, "6", addrp); + } +#endif + else /* nowhere to forward to */ + SET_RCODE(header, REFUSED); + + return p - (unsigned char *)header; +} + +/* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */ +int check_for_local_domain(char *name, time_t now) +{ + struct crec *crecp; + struct mx_srv_record *mx; + struct txt_record *txt; + struct interface_name *intr; + struct ptr_record *ptr; + + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)) && + (crecp->flags & (F_HOSTS | F_DHCP))) + return 1; + + for (mx = daemon->mxnames; mx; mx = mx->next) + if (hostname_isequal(name, mx->name)) + return 1; + + for (txt = daemon->txt; txt; txt = txt->next) + if (hostname_isequal(name, txt->name)) + return 1; + + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + return 1; + + for (ptr = daemon->ptr; ptr; ptr = ptr->next) + if (hostname_isequal(name, ptr->name)) + return 1; + + return 0; +} + +/* Is the packet a reply with the answer address equal to addr? + If so mung is into an NXDOMAIN reply and also put that information + in the cache. */ +int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, + struct bogus_addr *baddr, time_t now) +{ + unsigned char *p; + int i, qtype, qclass, rdlen; + unsigned long ttl; + struct bogus_addr *baddrp; + + /* skip over questions */ + if (!(p = skip_questions(header, qlen))) + return 0; /* bad packet */ + + for (i = ntohs(header->ancount); i != 0; i--) + { + if (!extract_name(header, qlen, &p, name, 1, 10)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (qclass == C_IN && qtype == T_A) + { + if (!CHECK_LEN(header, p, qlen, INADDRSZ)) + return 0; + + for (baddrp = baddr; baddrp; baddrp = baddrp->next) + if (memcmp(&baddrp->addr, p, INADDRSZ) == 0) + { + /* Found a bogus address. Insert that info here, since there no SOA record + to get the ttl from in the normal processing */ + cache_start_insert(); + cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN | F_CONFIG); + cache_end_insert(); + + return 1; + } + } + + if (!ADD_RDLEN(header, p, qlen, rdlen)) + return 0; + } + + return 0; +} + +static int add_resource_record(struct dns_header *header, char *limit, int *truncp, unsigned int nameoffset, unsigned char **pp, + unsigned long ttl, unsigned int *offset, unsigned short type, unsigned short class, char *format, ...) +{ + va_list ap; + unsigned char *sav, *p = *pp; + int j; + unsigned short usval; + long lval; + char *sval; + + if (truncp && *truncp) + return 0; + + PUTSHORT(nameoffset | 0xc000, p); + PUTSHORT(type, p); + PUTSHORT(class, p); + PUTLONG(ttl, p); /* TTL */ + + sav = p; /* Save pointer to RDLength field */ + PUTSHORT(0, p); /* Placeholder RDLength */ + + va_start(ap, format); /* make ap point to 1st unamed argument */ + + for (; *format; format++) + switch (*format) + { +#ifdef HAVE_IPV6 + case '6': + sval = va_arg(ap, char *); + memcpy(p, sval, IN6ADDRSZ); + p += IN6ADDRSZ; + break; +#endif + + case '4': + sval = va_arg(ap, char *); + memcpy(p, sval, INADDRSZ); + p += INADDRSZ; + break; + + case 's': + usval = va_arg(ap, int); + PUTSHORT(usval, p); + break; + + case 'l': + lval = va_arg(ap, long); + PUTLONG(lval, p); + break; + + case 'd': + /* get domain-name answer arg and store it in RDATA field */ + if (offset) + *offset = p - (unsigned char *)header; + p = do_rfc1035_name(p, va_arg(ap, char *)); + *p++ = 0; + break; + + case 't': + usval = va_arg(ap, int); + sval = va_arg(ap, char *); + memcpy(p, sval, usval); + p += usval; + break; + + case 'z': + sval = va_arg(ap, char *); + usval = sval ? strlen(sval) : 0; + if (usval > 255) + usval = 255; + *p++ = (unsigned char)usval; + memcpy(p, sval, usval); + p += usval; + break; + } + + va_end(ap); /* clean up variable argument pointer */ + + j = p - sav - 2; + PUTSHORT(j, sav); /* Now, store real RDLength */ + + /* check for overflow of buffer */ + if (limit && ((unsigned char *)limit - p) < 0) + { + if (truncp) + *truncp = 1; + return 0; + } + + *pp = p; + return 1; +} + +static unsigned long crec_ttl(struct crec *crecp, time_t now) +{ + /* Return 0 ttl for DHCP entries, which might change + before the lease expires. */ + + if (crecp->flags & (F_IMMORTAL | F_DHCP)) + return daemon->local_ttl; + + /* Return the Max TTL value if it is lower then the actual TTL */ + if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl)) + return crecp->ttd - now; + else + return daemon->max_ttl; +} + + +/* 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) +{ + char *name = daemon->namebuff; + unsigned char *p, *ansp, *pheader; + int qtype, qclass; + struct all_addr addr; + unsigned int nameoffset; + unsigned short flag; + int q, ans, anscount = 0, addncount = 0; + int dryrun = 0, sec_reqd = 0; + int is_sign; + struct crec *crecp; + int nxdomain = 0, auth = 1, trunc = 0; + struct mx_srv_record *rec; + + /* If there is an RFC2671 pseudoheader 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. */ + + if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign)) + { + unsigned short udpsz, ext_rcode, flags; + unsigned char *psave = pheader; + + GETSHORT(udpsz, pheader); + GETSHORT(ext_rcode, pheader); + GETSHORT(flags, pheader); + + sec_reqd = flags & 0x8000; /* do bit */ + + /* If our client is advertising a larger UDP packet size + than we allow, trim it so that we don't get an overlarge + response from upstream */ + + if (!is_sign && (udpsz > daemon->edns_pktsz)) + PUTSHORT(daemon->edns_pktsz, psave); + + dryrun = 1; + } + + if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) + return 0; + + for (rec = daemon->mxnames; rec; rec = rec->next) + rec->offset = 0; + + rerun: + /* determine end of question section (we put answers there) */ + if (!(ansp = skip_questions(header, qlen))) + return 0; /* bad packet */ + + /* now process each question, answers go in RRs after the question */ + p = (unsigned char *)(header+1); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + /* save pointer to name for copying into answers */ + nameoffset = p - (unsigned char *)header; + + /* now extract name as .-concatenated string into name */ + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + ans = 0; /* have we answered this question */ + + if (qtype == T_TXT || qtype == T_ANY) + { + struct txt_record *t; + for(t = daemon->txt; t ; t = t->next) + { + if (t->class == qclass && hostname_isequal(name, t->name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_TXT, t->class, "t", t->len, t->txt)) + anscount++; + + } + } + } + } + + if (qclass == C_IN) + { + if (qtype == T_PTR || qtype == T_ANY) + { + /* see if it's w.z.y.z.in-addr.arpa format */ + int is_arpa = in_arpa_name_2_addr(name, &addr); + struct ptr_record *ptr; + struct interface_name* intr = NULL; + + for (ptr = daemon->ptr; ptr; ptr = ptr->next) + if (hostname_isequal(name, ptr->name)) + break; + + if (is_arpa == F_IPV4) + for (intr = daemon->int_names; intr; intr = intr->next) + { + if (addr.addr.addr4.s_addr == get_ifaddr(intr->intr).s_addr) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } + + if (intr) + { + ans = 1; + if (!dryrun) + { + log_query(F_IPV4 | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", intr->name)) + anscount++; + } + } + else if (ptr) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>"); + for (ptr = daemon->ptr; ptr; ptr = ptr->next) + if (hostname_isequal(name, ptr->name) && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", ptr->ptr)) + anscount++; + + } + } + else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) + do + { + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + continue; + + 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) + { + ans = 1; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + if (!dryrun) + { + log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, + T_PTR, C_IN, "d", cache_get_name(crecp))) + anscount++; + } + } + } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + else if (is_arpa == F_IPV4 && + option_bool(OPT_BOGUSPRIV) && + 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); + } + } + + for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) + { + unsigned short type = T_A; + + if (flag == F_IPV6) +#ifdef HAVE_IPV6 + type = T_AAAA; +#else + break; +#endif + + 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 */ + if (qtype == T_A) + { + struct interface_name *intr; + + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + break; + + if (intr) + { + ans = 1; + if (!dryrun) + { + if ((addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr == (in_addr_t) -1) + log_query(F_FORWARD | F_CONFIG | F_IPV4 | F_NEG, name, NULL, NULL); + else + { + 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; + } + } + + cname_restart: + if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME))) + { + int localise = 0; + + /* See if a putative address is on the network from which we recieved + the query, is so we'll filter other answers. */ + if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4) + { + struct crec *save = crecp; + do { + if ((crecp->flags & F_HOSTS) && + is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + { + localise = 1; + break; + } + } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + crecp = save; + } + + do + { + /* don't answer wildcard queries with data not from /etc/hosts + or DHCP leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + break; + + if (crecp->flags & F_CNAME) + { + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cache_get_name(crecp->addr.cname.cache))) + anscount++; + } + + strcpy(name, cache_get_name(crecp->addr.cname.cache)); + goto cname_restart; + } + + if (crecp->flags & F_NEG) + { + ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags, name, NULL, NULL); + } + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) + { + /* If we are returning local answers depending on network, + filter here. */ + if (localise && + (crecp->flags & F_HOSTS) && + !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + continue; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + ans = 1; + if (!dryrun) + { + log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, type, C_IN, + type == T_A ? "4" : "6", &crecp->addr)) + anscount++; + } + } + } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + } + } + + if (qtype == T_MX || qtype == T_ANY) + { + int found = 0; + for (rec = daemon->mxnames; rec; rec = rec->next) + if (!rec->issrv && hostname_isequal(name, rec->name)) + { + ans = found = 1; + if (!dryrun) + { + unsigned int offset; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) + { + anscount++; + if (rec->target) + rec->offset = offset; + } + } + } + + if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && + cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, + T_MX, C_IN, "sd", 1, + option_bool(OPT_SELFMX) ? name : daemon->mxtarget)) + anscount++; + } + } + } + + if (qtype == T_SRV || qtype == T_ANY) + { + int found = 0; + struct mx_srv_record *move = NULL, **up = &daemon->mxnames; + + for (rec = daemon->mxnames; rec; rec = rec->next) + if (rec->issrv && hostname_isequal(name, rec->name)) + { + found = ans = 1; + if (!dryrun) + { + unsigned int offset; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + &offset, T_SRV, C_IN, "sssd", + rec->priority, rec->weight, rec->srvport, rec->target)) + { + anscount++; + if (rec->target) + rec->offset = offset; + } + } + + /* unlink first SRV record found */ + if (!move) + { + move = rec; + *up = rec->next; + } + else + up = &rec->next; + } + else + up = &rec->next; + + /* put first SRV record back at the end. */ + if (move) + { + *up = move; + move->next = NULL; + } + + if (!found && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_')))) + { + ans = 1; + if (!dryrun) + log_query(F_CONFIG | F_NEG, name, NULL, NULL); + } + } + + if (qtype == T_NAPTR || qtype == T_ANY) + { + struct naptr *na; + for (na = daemon->naptr; na; na = na->next) + if (hostname_isequal(name, na->name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + NULL, T_NAPTR, C_IN, "sszzzd", + na->order, na->pref, na->flags, na->services, na->regexp, na->replace)) + anscount++; + } + } + } + + if (qtype == T_MAILB) + ans = 1, nxdomain = 1; + + if (qtype == T_SOA && option_bool(OPT_FILTER)) + { + ans = 1; + if (!dryrun) + log_query(F_CONFIG | F_NEG, name, &addr, NULL); + } + } + + if (!ans) + return 0; /* failed to answer a question */ + } + + if (dryrun) + { + dryrun = 0; + goto rerun; + } + + /* create an additional data section, for stuff in SRV and MX record replies. */ + for (rec = daemon->mxnames; rec; rec = rec->next) + if (rec->offset != 0) + { + /* squash dupes */ + struct mx_srv_record *tmp; + for (tmp = rec->next; tmp; tmp = tmp->next) + if (tmp->offset != 0 && hostname_isequal(rec->target, tmp->target)) + tmp->offset = 0; + + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, rec->target, now, F_IPV4 | F_IPV6))) + { +#ifdef HAVE_IPV6 + int type = crecp->flags & F_IPV4 ? T_A : T_AAAA; +#else + int type = T_A; +#endif + if (crecp->flags & F_NEG) + continue; + + if (add_resource_record(header, limit, NULL, rec->offset, &ansp, + crec_ttl(crecp, now), NULL, type, C_IN, + crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr)) + addncount++; + } + } + + /* done all questions, set up header and return length of result */ + /* clear authoritative and truncated flags, set QR flag */ + header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; + /* set RA flag */ + header->hb4 |= HB4_RA; + + /* authoritive - only hosts and DHCP derived names. */ + if (auth) + header->hb3 |= HB3_AA; + + /* truncation */ + if (trunc) + header->hb3 |= HB3_TC; + + if (anscount == 0 && nxdomain) + SET_RCODE(header, NXDOMAIN); + else + SET_RCODE(header, NOERROR); /* no error */ + header->ancount = htons(anscount); + header->nscount = htons(0); + header->arcount = htons(addncount); + return ansp - (unsigned char *)header; +} + + + + + diff --git a/src/rfc2131.c b/src/rfc2131.c new file mode 100644 index 0000000..4f942b5 --- /dev/null +++ b/src/rfc2131.c @@ -0,0 +1,2512 @@ +/* dnsmasq is Copyright (c) 2000-2011 Simon Kelley + Copyright (c) 2012, 2013 Samsung Electronices Co., Ltd. + + 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/>. + + * Modifications by Samsung Electronices Co., Ltd. + 1. ACTION_CONNECT is added to notify IP address assignment +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP + +#define have_config(config, mask) ((config) && ((config)->flags & (mask))) +#define option_len(opt) ((int)(((unsigned char *)(opt))[1])) +#define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)])) + +#ifdef HAVE_SCRIPT +static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim); +static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt); +#endif + +static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len); +static int sanitise(unsigned char *opt, char *buf); +static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback); +static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt); +static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val); +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 struct in_addr option_addr_arr(unsigned char *opt, int offset); +static unsigned int option_uint(unsigned char *opt, int i, int size); +static void log_packet(char *type, void *addr, unsigned char *ext_mac, + int mac_len, char *interface, char *string, u32 xid); +static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize); +static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize); +static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid, + unsigned char *agent_id, unsigned char *real_end); +static void clear_packet(struct dhcp_packet *mess, unsigned char *end); +static void do_options(struct dhcp_context *context, + struct dhcp_packet *mess, + unsigned char *real_end, + unsigned char *req_options, + char *hostname, + char *domain, char *config_domain, + struct dhcp_netid *netid, + struct in_addr subnet_addr, + unsigned char fqdn_flags, + int null_term, int pxearch, + unsigned char *uuid, + int vendor_class_len); + + +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 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); +struct dhcp_boot *find_boot(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) +{ + unsigned char *opt, *clid = NULL; + struct dhcp_lease *ltmp, *lease = NULL; + struct dhcp_vendor *vendor; + struct dhcp_mac *mac; + struct dhcp_netid_list *id_list; + int clid_len = 0, ignore = 0, do_classes = 0, selecting = 0, pxearch = -1; + struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; + unsigned char *end = (unsigned char *)(mess + 1); + unsigned char *real_end = (unsigned char *)(mess + 1); + char *hostname = NULL, *offer_hostname = NULL, *client_hostname = NULL, *domain = NULL; + int hostname_auth = 0, borken_opt = 0; + unsigned char *req_options = NULL; + char *message = NULL; + unsigned int time; + struct dhcp_config *config; + struct dhcp_netid *netid, *tagif_netid; + struct in_addr subnet_addr, fallback, override; + unsigned short fuzz = 0; + unsigned int mess_type = 0; + unsigned char fqdn_flags = 0; + unsigned char *agent_id = NULL, *uuid = NULL; + unsigned char *emac = NULL; + int vendor_class_len = 0, emac_len = 0; + struct dhcp_netid known_id, iface_id, cpewan_id; + struct dhcp_opt *o; + unsigned char pxe_uuid[17]; + unsigned char *oui = NULL, *serial = NULL, *class = NULL; + + subnet_addr.s_addr = override.s_addr = 0; + + /* set tag with name == interface */ + iface_id.net = iface_name; + iface_id.next = NULL; + netid = &iface_id; + + if (mess->op != BOOTREQUEST || mess->hlen > DHCP_CHADDR_MAX) + return 0; + + if (mess->htype == 0 && mess->hlen != 0) + return 0; + + /* check for DHCP rather than BOOTP */ + if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE, 1))) + { + u32 cookie = htonl(DHCP_COOKIE); + + /* only insist on a cookie for DHCP. */ + if (memcmp(mess->options, &cookie, sizeof(u32)) != 0) + return 0; + + mess_type = option_uint(opt, 0, 1); + + /* two things to note here: expand_buf may move the packet, + so reassign mess from daemon->packet. Also, the size + sent includes the IP and UDP headers, hence the magic "-28" */ + if ((opt = option_find(mess, sz, OPTION_MAXMESSAGE, 2))) + { + size_t size = (size_t)option_uint(opt, 0, 2) - 28; + + if (size > DHCP_PACKET_MAX) + size = DHCP_PACKET_MAX; + else if (size < sizeof(struct dhcp_packet)) + size = sizeof(struct dhcp_packet); + + if (expand_buf(&daemon->dhcp_packet, size)) + { + mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; + real_end = end = ((unsigned char *)mess) + size; + } + } + + /* Some buggy clients set ciaddr when they shouldn't, so clear that here since + it can affect the context-determination code. */ + if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER)) + mess->ciaddr.s_addr = 0; + + /* search for device identity from CPEWAN devices, we pass this through to the script */ + if ((opt = option_find(mess, sz, OPTION_VENDOR_IDENT_OPT, 5))) + { + unsigned int elen, offset, len = option_len(opt); + + 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) + { + unsigned char *x = option_ptr(opt, offset + 5); + unsigned char *y = option_ptr(opt, offset + elen + 5); + oui = option_find1(x, y, 1, 1); + serial = option_find1(x, y, 2, 1); + class = option_find1(x, y, 3, 1); + + /* If TR069-id is present set the tag "cpewan-id" to facilitate echoing + the gateway id back. Note that the device class is optional */ + if (oui && serial) + { + cpewan_id.net = "cpewan-id"; + cpewan_id.next = netid; + netid = &cpewan_id; + } + break; + } + } + } + + if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1))) + { + /* Any agent-id needs to be copied back out, verbatim, as the last option + in the packet. Here, we shift it to the very end of the buffer, if it doesn't + get overwritten, then it will be shuffled back at the end of processing. + Note that the incoming options must not be overwritten here, so there has to + 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); + if (last_opt && last_opt < end - total) + { + end -= total; + agent_id = end; + memcpy(agent_id, opt, total); + } + + /* look for RFC3527 Link selection sub-option */ + if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBNET_SELECT, INADDRSZ))) + subnet_addr = option_addr(sopt); + + /* look for RFC5107 server-identifier-override */ + if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SERVER_OR, INADDRSZ))) + override = option_addr(sopt); + + /* if a circuit-id or remote-is option is provided, exact-match to options. */ + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + { + int search; + + if (vendor->match_type == MATCH_CIRCUIT) + search = SUBOPT_CIRCUIT_ID; + else if (vendor->match_type == MATCH_REMOTE) + search = SUBOPT_REMOTE_ID; + else if (vendor->match_type == MATCH_SUBSCRIBER) + search = SUBOPT_SUBSCR_ID; + else + continue; + + if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), search, 1)) && + vendor->len == option_len(sopt) && + memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0) + { + vendor->netid.next = netid; + netid = &vendor->netid; + } + } + } + + /* Check for RFC3011 subnet selector - only if RFC3527 one not present */ + if (subnet_addr.s_addr == 0 && (opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ))) + subnet_addr = option_addr(opt); + + /* If there is no client identifier option, use the hardware address */ + if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1))) + { + clid_len = option_len(opt); + clid = option_ptr(opt, 0); + } + + /* do we have a lease in store? */ + lease = lease_find_by_client(mess->chaddr, mess->hlen, mess->htype, clid, clid_len); + + /* If this request is missing a clid, but we've seen one before, + use it again for option matching etc. */ + if (lease && !clid && lease->clid) + { + clid_len = lease->clid_len; + clid = lease->clid; + } + + /* find mac to use for logging and hashing */ + emac = extended_hwaddr(mess->htype, mess->hlen, mess->chaddr, clid_len, clid, &emac_len); + } + + for (mac = daemon->dhcp_macs; mac; mac = mac->next) + if (mac->hwaddr_len == mess->hlen && + (mac->hwaddr_type == mess->htype || mac->hwaddr_type == 0) && + memcmp_masked(mac->hwaddr, mess->chaddr, mess->hlen, mac->mask)) + { + mac->netid.next = netid; + netid = &mac->netid; + } + + /* Determine network for this packet. Our caller will have already linked all the + contexts which match the addresses of the receiving interface but if the + machine has an address already, or came via a relay, or we have a subnet selector, + we search again. If we don't have have a giaddr or explicit subnet selector, + use the ciaddr. This is necessary because a machine which got a lease via a + relay won't use the relay to renew. If matching a ciaddr fails but we have a context + from the physical network, continue using that to allow correct DHCPNAK generation later. */ + if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr) + { + struct dhcp_context *context_tmp, *context_new = NULL; + struct in_addr addr; + int force = 0; + + if (subnet_addr.s_addr) + { + addr = subnet_addr; + force = 1; + } + else if (mess->giaddr.s_addr) + { + addr = mess->giaddr; + force = 1; + } + else + { + /* If ciaddr is in the hardware derived set of contexts, leave that unchanged */ + addr = mess->ciaddr; + for (context_tmp = context; context_tmp; context_tmp = context_tmp->current) + if (context_tmp->netmask.s_addr && + is_same_net(addr, context_tmp->start, context_tmp->netmask) && + is_same_net(addr, context_tmp->end, context_tmp->netmask)) + { + context_new = context; + break; + } + } + + if (!context_new) + for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) + if (context_tmp->netmask.s_addr && + is_same_net(addr, context_tmp->start, context_tmp->netmask) && + is_same_net(addr, context_tmp->end, context_tmp->netmask)) + { + context_tmp->current = context_new; + context_new = context_tmp; + } + + if (context_new || force) + context = context_new; + + } + + if (!context) + { + my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"), + subnet_addr.s_addr ? _("with subnet selector") : _("via"), + subnet_addr.s_addr ? inet_ntoa(subnet_addr) : (mess->giaddr.s_addr ? inet_ntoa(mess->giaddr) : iface_name)); + return 0; + } + + /* keep _a_ local address available. */ + fallback = context->local; + + if (option_bool(OPT_LOG_OPTS)) + { + struct dhcp_context *context_tmp; + for (context_tmp = context; context_tmp; context_tmp = context_tmp->current) + { + strcpy(daemon->namebuff, inet_ntoa(context_tmp->start)); + if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"), + ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->netmask)); + else + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), + ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->end)); + } + } + + mess->op = BOOTREPLY; + + config = find_config(daemon->dhcp_conf, context, clid, clid_len, + mess->chaddr, mess->hlen, mess->htype, NULL); + + /* set "known" tag for known hosts */ + if (config) + { + known_id.net = "known"; + known_id.next = netid; + netid = &known_id; + } + + if (mess_type == 0 && !pxe) + { + /* BOOTP request */ + struct dhcp_netid id, bootp_id; + struct in_addr *logaddr = NULL; + + /* must have a MAC addr for bootp */ + if (mess->htype == 0 || mess->hlen == 0 || (context->flags & CONTEXT_PROXY)) + return 0; + + if (have_config(config, CONFIG_DISABLE)) + message = _("disabled"); + + end = mess->options + 64; /* BOOTP vend area is only 64 bytes */ + + if (have_config(config, CONFIG_NAME)) + { + hostname = config->hostname; + domain = config->domain; + } + + if (config) + { + struct dhcp_netid_list *list; + + for (list = config->netid; list; list = list->next) + { + list->list->next = netid; + netid = list->list; + } + } + + /* Match incoming filename field as a netid. */ + if (mess->file[0]) + { + memcpy(daemon->dhcp_buff2, mess->file, sizeof(mess->file)); + daemon->dhcp_buff2[sizeof(mess->file) + 1] = 0; /* ensure zero term. */ + id.net = (char *)daemon->dhcp_buff2; + id.next = netid; + netid = &id; + } + + /* Add "bootp" as a tag to allow different options, address ranges etc + for BOOTP clients */ + bootp_id.net = "bootp"; + bootp_id.next = netid; + netid = &bootp_id; + + tagif_netid = run_tag_if(netid); + + for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next) + if (match_netid(id_list->list, tagif_netid, 0)) + message = _("ignored"); + + if (!message) + { + int nailed = 0; + + if (have_config(config, CONFIG_ADDR)) + { + nailed = 1; + logaddr = &config->addr; + mess->yiaddr = config->addr; + if ((lease = lease_find_by_addr(config->addr)) && + (lease->hwaddr_len != mess->hlen || + lease->hwaddr_type != mess->htype || + memcmp(lease->hwaddr, mess->chaddr, lease->hwaddr_len) != 0)) + message = _("address in use"); + } + else + { + if (!(lease = lease_find_by_client(mess->chaddr, mess->hlen, mess->htype, NULL, 0)) || + !address_available(context, lease->addr, tagif_netid)) + { + if (lease) + { + /* lease exists, wrong network. */ + lease_prune(lease, now); + lease = NULL; + } + if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now)) + message = _("no address available"); + } + else + mess->yiaddr = lease->addr; + } + + if (!message && !(context = narrow_context(context, mess->yiaddr, netid))) + message = _("wrong network"); + else if (context->netid.net) + { + context->netid.next = netid; + netid = &context->netid; + tagif_netid = run_tag_if(netid); + } + + if (!message && !nailed) + { + for (id_list = daemon->bootp_dynamic; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0)) + break; + if (!id_list) + message = _("no address configured"); + } + + if (!message && + !lease && + (!(lease = lease_allocate(mess->yiaddr)))) + message = _("no leases left"); + + if (!message) + { + logaddr = &mess->yiaddr; + + lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0); + if (hostname) + lease_set_hostname(lease, hostname, 1); + /* infinite lease unless nailed in dhcp-host line. */ + lease_set_expires(lease, + have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, + now); + lease_set_interface(lease, int_index); + + clear_packet(mess, end); + do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), + domain, tagif_netid, subnet_addr, 0, 0, 0, NULL, 0); + } + } + + log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, message, mess->xid); + + return message ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + } + + if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4))) + { + /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */ + int len = option_len(opt); + char *pq = daemon->dhcp_buff; + unsigned char *pp, *op = option_ptr(opt, 0); + + fqdn_flags = *op; + len -= 3; + op += 3; + pp = op; + + /* Always force update, since the client has no way to do it itself. */ + if (!(fqdn_flags & 0x01)) + fqdn_flags |= 0x02; + + fqdn_flags &= ~0x08; + fqdn_flags |= 0x01; + + if (fqdn_flags & 0x04) + while (*op != 0 && ((op + (*op) + 1) - pp) < len) + { + memcpy(pq, op+1, *op); + pq += *op; + op += (*op)+1; + *(pq++) = '.'; + } + else + { + memcpy(pq, op, len); + if (len > 0 && op[len-1] == 0) + borken_opt = 1; + pq += len + 1; + } + + if (pq != daemon->dhcp_buff) + pq--; + + *pq = 0; + + if (legal_hostname(daemon->dhcp_buff)) + offer_hostname = client_hostname = daemon->dhcp_buff; + } + else if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1))) + { + int len = option_len(opt); + memcpy(daemon->dhcp_buff, option_ptr(opt, 0), len); + /* Microsoft clients are broken, and need zero-terminated strings + in options. We detect this state here, and do the same in + any options we send */ + if (len > 0 && daemon->dhcp_buff[len-1] == 0) + borken_opt = 1; + else + daemon->dhcp_buff[len] = 0; + if (legal_hostname(daemon->dhcp_buff)) + client_hostname = daemon->dhcp_buff; + } + + if (client_hostname && option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname); + + if (have_config(config, CONFIG_NAME)) + { + hostname = config->hostname; + domain = config->domain; + hostname_auth = 1; + /* be careful not to send an OFFER with a hostname not matching the DISCOVER. */ + if (fqdn_flags != 0 || !client_hostname || hostname_isequal(hostname, client_hostname)) + offer_hostname = hostname; + } + else if (client_hostname) + { + domain = strip_hostname(client_hostname); + + if (strlen(client_hostname) != 0) + { + hostname = client_hostname; + if (!config) + { + /* Search again now we have a hostname. + Only accept configs without CLID and HWADDR here, (they won't match) + to avoid impersonation by name. */ + struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, + mess->chaddr, mess->hlen, + mess->htype, hostname); + if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) + { + config = new; + /* set "known" tag for known hosts */ + known_id.net = "known"; + known_id.next = netid; + netid = &known_id; + } + } + } + } + + if (config) + { + struct dhcp_netid_list *list; + + for (list = config->netid; list; list = list->next) + { + list->list->next = netid; + netid = list->list; + } + } + + /* 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 + rfc3925 V-I classes too. */ + for (o = daemon->dhcp_match; o; o = o->next) + { + unsigned int len, elen, match = 0; + size_t offset, o2; + + if (o->flags & DHOPT_RFC3925) + { + if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5))) + continue; + + for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5) + { + 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))) && + (option_uint(opt, offset, 4) == (unsigned int)o->u.encap)) + for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) + { + elen = option_uint(opt, o2, 1); + if ((o2 + elen + 1 <= option_len(opt)) && + (match = match_bytes(o, option_ptr(opt, o2 + 1), elen))) + break; + } + if (match) + break; + } + } + else + { + if (!(opt = option_find(mess, sz, o->opt, 1))) + continue; + + match = match_bytes(o, option_ptr(opt, 0), option_len(opt)); + } + + if (match) + { + o->netid->next = netid; + netid = o->netid; + } + } + + /* user-class options are, according to RFC3004, supposed to contain + a set of counted strings. Here we check that this is so (by seeing + if the counts are consistent with the overall option length) and if + so zero the counts so that we don't get spurious matches between + the vendor string and the counts. If the lengths don't add up, we + assume that the option is a single string and non RFC3004 compliant + and just do the substring match. dhclient provides these broken options. + The code, later, which sends user-class data to the lease-change script + relies on the transformation done here. + */ + + if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) + { + unsigned char *ucp = option_ptr(opt, 0); + int tmp, j; + for (j = 0; j < option_len(opt); j += ucp[j] + 1); + if (j == option_len(opt)) + for (j = 0; j < option_len(opt); j = tmp) + { + tmp = j + ucp[j] + 1; + ucp[j] = 0; + } + } + + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + { + int mopt; + + if (vendor->match_type == MATCH_VENDOR) + mopt = OPTION_VENDOR_ID; + else if (vendor->match_type == MATCH_USER) + mopt = OPTION_USER_CLASS; + else + continue; + + if ((opt = option_find(mess, sz, mopt, 1))) + { + int i; + for (i = 0; i <= (option_len(opt) - vendor->len); i++) + if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0) + { + vendor->netid.next = netid; + netid = &vendor->netid; + break; + } + } + } + + /* mark vendor-encapsulated options which match the client-supplied vendor class, + save client-supplied vendor class */ + if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1))) + { + memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt)); + vendor_class_len = option_len(opt); + } + match_vendor_opts(opt, daemon->dhcp_opts); + + if (option_bool(OPT_LOG_OPTS)) + { + if (sanitise(opt, daemon->namebuff)) + my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff); + if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff)) + my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff); + } + + tagif_netid = run_tag_if(netid); + + /* if all the netids in the ignore list are present, ignore this client */ + for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next) + if (match_netid(id_list->list, tagif_netid, 0)) + ignore = 1; + + /* If configured, we can override the server-id to be the address of the relay, + so that all traffic goes via the relay and can pick up agent-id info. This can be + configured for all relays, or by address. */ + if (daemon->override && mess->giaddr.s_addr != 0 && override.s_addr == 0) + { + if (!daemon->override_relays) + override = mess->giaddr; + else + { + struct addr_list *l; + for (l = daemon->override_relays; l; l = l->next) + if (l->addr.s_addr == mess->giaddr.s_addr) + break; + if (l) + override = mess->giaddr; + } + } + + /* Can have setting to ignore the client ID for a particular MAC address or hostname */ + if (have_config(config, CONFIG_NOCLID)) + clid = NULL; + + /* Check if client is PXE client. */ + if (daemon->enable_pxe && + (opt = option_find(mess, sz, OPTION_VENDOR_ID, 9)) && + strncmp(option_ptr(opt, 0), "PXEClient", 9) == 0) + { + if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17))) + { + memcpy(pxe_uuid, option_ptr(opt, 0), 17); + uuid = pxe_uuid; + } + + /* Check if this is really a PXE bootserver request, and handle specially if so. */ + if ((mess_type == DHCPREQUEST || mess_type == DHCPINFORM) && + (opt = option_find(mess, sz, OPTION_VENDOR_CLASS_OPT, 1)) && + (opt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_PXE_BOOT_ITEM, 4))) + { + struct pxe_service *service; + int type = option_uint(opt, 0, 2); + int layer = option_uint(opt, 2, 2); + unsigned char save71[4]; + struct dhcp_opt opt71; + + if (ignore) + return 0; + + if (layer & 0x8000) + { + my_syslog(MS_DHCP | LOG_ERR, _("PXE BIS not supported")); + return 0; + } + + memcpy(save71, option_ptr(opt, 0), 4); + + for (service = daemon->pxe_services; service; service = service->next) + if (service->type == type) + break; + + if (!service || !service->basename) + return 0; + + clear_packet(mess, end); + + mess->yiaddr = mess->ciaddr; + mess->ciaddr.s_addr = 0; + if (service->server.s_addr != 0) + mess->siaddr = service->server; + else + mess->siaddr = context->local; + + 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); + + prune_vendor_opts(tagif_netid); + opt71.val = save71; + opt71.opt = SUBOPT_PXE_BOOT_ITEM; + opt71.len = 4; + opt71.flags = DHOPT_VENDOR_MATCH; + opt71.netid = NULL; + opt71.next = daemon->dhcp_opts; + do_encap_opts(&opt71, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + + log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, mess->xid); + return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + } + + if ((opt = option_find(mess, sz, OPTION_ARCH, 2))) + { + pxearch = option_uint(opt, 0, 2); + + /* proxy DHCP here. */ + if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST))) + { + struct dhcp_context *tmp; + + for (tmp = context; tmp; tmp = tmp->current) + if ((tmp->flags & CONTEXT_PROXY) && + match_netid(tmp->filter, tagif_netid, 1)) + break; + + if (tmp) + { + struct dhcp_boot *boot = find_boot(tagif_netid); + + mess->yiaddr.s_addr = 0; + if (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0) + { + mess->ciaddr.s_addr = 0; + mess->flags |= htons(0x8000); /* broadcast */ + } + + 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) + { + if (boot->next_server.s_addr) + mess->siaddr = boot->next_server; + + if (boot->file) + strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + } + + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, + mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); + pxe_misc(mess, end, uuid); + prune_vendor_opts(tagif_netid); + do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + + log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", mess->xid); + return ignore ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + } + } + } + } + + /* if we're just a proxy server, go no further */ + if ((context->flags & CONTEXT_PROXY) || pxe) + return 0; + + if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0))) + { + req_options = (unsigned char *)daemon->dhcp_buff2; + memcpy(req_options, option_ptr(opt, 0), option_len(opt)); + req_options[option_len(opt)] = OPTION_END; + } + + switch (mess_type) + { + case DHCPDECLINE: + if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) || + option_addr(opt).s_addr != server_id(context, override, fallback).s_addr) + return 0; + + /* sanitise any message. Paranoid? Moi? */ + sanitise(option_find(mess, sz, OPTION_MESSAGE, 1), daemon->dhcp_buff); + + if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) + return 0; + + log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, daemon->dhcp_buff, mess->xid); + + if (lease && lease->addr.s_addr == option_addr(opt).s_addr) + lease_prune(lease, now); + + if (have_config(config, CONFIG_ADDR) && + config->addr.s_addr == option_addr(opt).s_addr) + { + prettyprint_time(daemon->dhcp_buff, DECLINE_BACKOFF); + my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), + inet_ntoa(config->addr), daemon->dhcp_buff); + config->flags |= CONFIG_DECLINED; + config->decline_time = now; + } + else + /* make sure this host gets a different address next time. */ + for (; context; context = context->current) + context->addr_epoch++; + + return 0; + + case DHCPRELEASE: + if (!(context = narrow_context(context, mess->ciaddr, tagif_netid)) || + !(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) || + option_addr(opt).s_addr != server_id(context, override, fallback).s_addr) + return 0; + + if (lease && lease->addr.s_addr == mess->ciaddr.s_addr) + lease_prune(lease, now); + else + message = _("unknown lease"); + + log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid); + + return 0; + + case DHCPDISCOVER: + if (ignore || have_config(config, CONFIG_DISABLE)) + { + message = _("ignored"); + opt = NULL; + } + else + { + struct in_addr addr, conf; + + addr.s_addr = conf.s_addr = 0; + + if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) + addr = option_addr(opt); + + if (have_config(config, CONFIG_ADDR)) + { + char *addrs = inet_ntoa(config->addr); + + if ((ltmp = lease_find_by_addr(config->addr)) && + ltmp != lease && + !config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type)) + { + int len; + unsigned char *mac = extended_hwaddr(ltmp->hwaddr_type, ltmp->hwaddr_len, + ltmp->hwaddr, ltmp->clid_len, ltmp->clid, &len); + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s"), + addrs, print_mac(daemon->namebuff, mac, len)); + } + else + { + struct dhcp_context *tmp; + for (tmp = context; tmp; tmp = tmp->current) + if (context->router.s_addr == config->addr.s_addr) + break; + if (tmp) + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is in use by the server or relay"), addrs); + else if (have_config(config, CONFIG_DECLINED) && + difftime(now, config->decline_time) < (float)DECLINE_BACKOFF) + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), addrs); + else + conf = config->addr; + } + } + + if (conf.s_addr) + mess->yiaddr = conf; + else if (lease && + address_available(context, lease->addr, tagif_netid) && + !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)) + 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)) + message = _("no address available"); + } + + log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, message, mess->xid); + + if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid))) + return 0; + + log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid); + + if (context->netid.net) + { + context->netid.next = netid; + netid = &context->netid; + tagif_netid = run_tag_if(netid); + } + + time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); + clear_packet(mess, end); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); + option_put(mess, end, OPTION_LEASE_TIME, 4, time); + /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */ + if (time != 0xffffffff) + { + option_put(mess, end, OPTION_T1, 4, (time/2)); + option_put(mess, end, OPTION_T2, 4, (time*7)/8); + } + do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), + domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + + return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + + case DHCPREQUEST: + if (ignore || have_config(config, CONFIG_DISABLE)) + return 0; + if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) + { + /* SELECTING or INIT_REBOOT */ + mess->yiaddr = option_addr(opt); + + /* send vendor and user class info for new or recreated lease */ + do_classes = 1; + + if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ))) + { + /* SELECTING */ + selecting = 1; + + if (override.s_addr != 0) + { + if (option_addr(opt).s_addr != override.s_addr) + return 0; + } + else + { + for (; context; context = context->current) + if (context->local.s_addr == option_addr(opt).s_addr) + break; + + if (!context) + { + /* In auth mode, a REQUEST sent to the wrong server + should be faulted, so that the client establishes + communication with us, otherwise, silently ignore. */ + if (!option_bool(OPT_AUTHORITATIVE)) + return 0; + message = _("wrong server-ID"); + } + } + + /* If a lease exists for this host and another address, squash it. */ + if (lease && lease->addr.s_addr != mess->yiaddr.s_addr) + { + lease_prune(lease, now); + lease = NULL; + } + } + else + { + /* INIT-REBOOT */ + if (!lease && !option_bool(OPT_AUTHORITATIVE)) + return 0; + + if (lease && lease->addr.s_addr != mess->yiaddr.s_addr) + message = _("wrong address"); + } + } + else + { + /* RENEWING or REBINDING */ + /* Check existing lease for this address. + We allow it to be missing if dhcp-authoritative mode + as long as we can allocate the lease now - checked below. + This makes for a smooth recovery from a lost lease DB */ + if ((lease && mess->ciaddr.s_addr != lease->addr.s_addr) || + (!lease && !option_bool(OPT_AUTHORITATIVE))) + { + /* A client rebinding will broadcast the request, so we may see it even + if the lease is held by another server. Just ignore it in that case. + If the request is unicast to us, then somethings wrong, NAK */ + if (!unicast_dest) + return 0; + message = _("lease not found"); + /* ensure we broadcast NAK */ + unicast_dest = 0; + } + + /* desynchronise renewals */ + fuzz = rand16(); + mess->yiaddr = mess->ciaddr; + } + + log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid); + + if (!message) + { + struct dhcp_config *addr_config; + struct dhcp_context *tmp = NULL; + + if (have_config(config, CONFIG_ADDR)) + for (tmp = context; tmp; tmp = tmp->current) + if (context->router.s_addr == config->addr.s_addr) + break; + + if (!(context = narrow_context(context, mess->yiaddr, tagif_netid))) + { + /* If a machine moves networks whilst it has a lease, we catch that here. */ + message = _("wrong network"); + /* ensure we broadcast NAK */ + unicast_dest = 0; + } + + /* Check for renewal of a lease which is outside the allowed range. */ + else if (!address_available(context, mess->yiaddr, tagif_netid) && + (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) + message = _("address not available"); + + /* Check if a new static address has been configured. Be very sure that + when the client does DISCOVER, it will get the static address, otherwise + an endless protocol loop will ensue. */ + else if (!tmp && !selecting && + have_config(config, CONFIG_ADDR) && + (!have_config(config, CONFIG_DECLINED) || + difftime(now, config->decline_time) > (float)DECLINE_BACKOFF) && + config->addr.s_addr != mess->yiaddr.s_addr && + (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease)) + message = _("static lease available"); + + /* Check to see if the address is reserved as a static address for another host */ + else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config) + message = _("address reserved"); + + else if (!lease && (ltmp = lease_find_by_addr(mess->yiaddr))) + { + /* If a host is configured with more than one MAC address, it's OK to 'nix + a lease from one of it's MACs to give the address to another. */ + if (config && config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type)) + { + my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"), + print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len), + inet_ntoa(ltmp->addr)); + lease = ltmp; + } + else + message = _("address in use"); + } + + if (!message) + { + if (emac_len == 0) + message = _("no unique-id"); + + else if (!lease) + { + if ((lease = lease_allocate(mess->yiaddr))) + do_classes = 1; + else + message = _("no leases left"); + } + } + } + + if (message) + { + log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, message, mess->xid); + + mess->yiaddr.s_addr = 0; + clear_packet(mess, end); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); + option_put_string(mess, end, OPTION_MESSAGE, message, borken_opt); + /* This fixes a problem with the DHCP spec, broadcasting a NAK to a host on + a distant subnet which unicast a REQ to us won't work. */ + if (!unicast_dest || mess->giaddr.s_addr != 0 || + mess->ciaddr.s_addr == 0 || is_same_net(context->local, mess->ciaddr, context->netmask)) + { + mess->flags |= htons(0x8000); /* broadcast */ + mess->ciaddr.s_addr = 0; + } + } + else + { + if (context->netid.net) + { + context->netid.next = netid; + netid = &context->netid; + tagif_netid = run_tag_if(netid); + } + +#ifdef HAVE_SCRIPT + if (do_classes && daemon->lease_change_command) + { + struct dhcp_netid *n; + + if (mess->giaddr.s_addr) + lease->giaddr = mess->giaddr; + + lease->changed = 1; + free(lease->extradata); + lease->extradata = NULL; + lease->extradata_size = lease->extradata_len = 0; + + add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1)); + add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1)); + add_extradata_opt(lease, oui); + add_extradata_opt(lease, serial); + add_extradata_opt(lease, class); + + /* space-concat tag set */ + if (!tagif_netid) + add_extradata_opt(lease, NULL); + else + for (n = tagif_netid; n; n = n->next) + add_extradata_data(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); + + if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) + { + int len = option_len(opt); + unsigned char *ucp = option_ptr(opt, 0); + /* If the user-class option started as counted strings, the first byte will be zero. */ + if (len != 0 && ucp[0] == 0) + ucp++, len--; + add_extradata_data(lease, ucp, len, 0); + } + } +#endif + + if (!hostname_auth && (client_hostname = host_from_dns(mess->yiaddr))) + { + domain = get_domain(mess->yiaddr); + hostname = client_hostname; + hostname_auth = 1; + } + + time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); + lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len); + + /* if all the netids in the ignore_name list are present, ignore client-supplied name */ + if (!hostname_auth) + { + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0)) + break; + if (id_list) + hostname = NULL; + } + + /* Last ditch, if configured, generate hostname from mac address */ + if (!hostname && emac_len != 0) + { + for (id_list = daemon->dhcp_gen_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0)) + break; + if (id_list) + { + int i; + + hostname = daemon->dhcp_buff; + /* buffer is 256 bytes, 3 bytes per octet */ + for (i = 0; (i < emac_len) && (i < 80); i++) + hostname += sprintf(hostname, "%.2x%s", emac[i], (i == emac_len - 1) ? "" : "-"); + hostname = daemon->dhcp_buff; + } + } + + if (hostname) + lease_set_hostname(lease, hostname, hostname_auth); + + lease_set_expires(lease, time, now); + lease_set_interface(lease, int_index); + + if (override.s_addr != 0) + lease->override = override; + else + override = lease->override; + + log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, mess->xid); + emit_dbus_signal(ACTION_CONNECT, lease, hostname); + + clear_packet(mess, end); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); + option_put(mess, end, OPTION_LEASE_TIME, 4, time); + if (time != 0xffffffff) + { + while (fuzz > (time/16)) + fuzz = fuzz/2; + option_put(mess, end, OPTION_T1, 4, (time/2) - fuzz); + option_put(mess, end, OPTION_T2, 4, ((time/8)*7) - fuzz); + } + do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), + domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + } + + return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + + case DHCPINFORM: + if (ignore || have_config(config, CONFIG_DISABLE)) + message = _("ignored"); + + log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid); + + if (message || mess->ciaddr.s_addr == 0) + return 0; + + /* For DHCPINFORM only, cope without a valid context */ + context = narrow_context(context, mess->ciaddr, tagif_netid); + + /* Find a least based on IP address if we didn't + get one from MAC address/client-d */ + if (!lease && + (lease = lease_find_by_addr(mess->ciaddr)) && + lease->hostname) + hostname = lease->hostname; + + if (!hostname && (hostname = host_from_dns(mess->ciaddr))) + domain = get_domain(mess->ciaddr); + + log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, mess->xid); + + if (context && context->netid.net) + { + context->netid.next = netid; + netid = &context->netid; + tagif_netid = run_tag_if(netid); + } + + if (lease) + { + if (override.s_addr != 0) + lease->override = override; + else + override = lease->override; + } + + clear_packet(mess, end); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); + + if (lease) + { + if (lease->expires == 0) + time = 0xffffffff; + else + time = (unsigned int)difftime(lease->expires, now); + option_put(mess, end, OPTION_LEASE_TIME, 4, time); + lease_set_interface(lease, int_index); + } + + do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr), + domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + + *is_inform = 1; /* handle reply differently */ + return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + } + + return 0; +} + +static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len) +{ + int i; + + if (o->len > len) + return 0; + + if (o->len == 0) + return 1; + + if (o->flags & DHOPT_HEX) + { + if (memcmp_masked(o->val, p, o->len, o->u.wildcard_mask)) + return 1; + } + else + for (i = 0; i <= (len - o->len); ) + { + if (memcmp(o->val, p + i, o->len) == 0) + return 1; + + if (o->flags & DHOPT_STRING) + i++; + else + i += o->len; + } + + return 0; +} + + +/* find a good value to use as MAC address for logging and address-allocation hashing. + This is normally just the chaddr field from the DHCP packet, + but eg Firewire will have hlen == 0 and use the client-id instead. + This could be anything, but will normally be EUI64 for Firewire. + We assume that if the first byte of the client-id equals the htype byte + then the client-id is using the usual encoding and use the rest of the + client-id: if not we can use the whole client-id. This should give + sane MAC address logs. */ +unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, + int clid_len, unsigned char *clid, int *len_out) +{ + if (hwlen == 0 && clid && clid_len > 3) + { + if (clid[0] == hwtype) + { + *len_out = clid_len - 1 ; + return clid + 1; + } + +#if defined(ARPHRD_EUI64) && defined(ARPHRD_IEEE1394) + if (clid[0] == ARPHRD_EUI64 && hwtype == ARPHRD_IEEE1394) + { + *len_out = clid_len - 1 ; + return clid + 1; + } +#endif + + *len_out = clid_len; + return clid; + } + + *len_out = hwlen; + return hwaddr; +} + +static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt) +{ + unsigned int time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time; + + if (opt) + { + unsigned int req_time = option_uint(opt, 0, 4); + if (req_time < 120 ) + req_time = 120; /* sanity */ + if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time)) + time = req_time; + } + + return time; +} + +static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback) +{ + if (override.s_addr != 0) + return override; + else if (context) + return context->local; + else + return fallback; +} + +static int sanitise(unsigned char *opt, char *buf) +{ + char *p; + int i; + + *buf = 0; + + if (!opt) + return 0; + + p = option_ptr(opt, 0); + + for (i = option_len(opt); i > 0; i--) + { + char c = *p++; + if (isprint((int)c)) + *buf++ = c; + } + *buf = 0; /* add terminator */ + + return 1; +} + +#ifdef HAVE_SCRIPT +static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim) +{ + if ((lease->extradata_size - lease->extradata_len) < (len + 1)) + { + size_t newsz = lease->extradata_len + len + 100; + unsigned char *new = whine_malloc(newsz); + + if (!new) + return; + + if (lease->extradata) + { + memcpy(new, lease->extradata, lease->extradata_len); + free(lease->extradata); + } + + lease->extradata = new; + lease->extradata_size = newsz; + } + + if (len != 0) + memcpy(lease->extradata + lease->extradata_len, data, len); + lease->extradata[lease->extradata_len + len] = delim; + lease->extradata_len += len + 1; +} + +static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt) +{ + if (!opt) + add_extradata_data(lease, NULL, 0, 0); + else + { + size_t i, len = option_len(opt); + unsigned char *ucp = option_ptr(opt, 0); + + /* check for embeded NULLs */ + for (i = 0; i < len; i++) + if (ucp[i] == 0) + { + len = i; + break; + } + + add_extradata_data(lease, ucp, len, 0); + } +} +#endif + +static void log_packet(char *type, void *addr, unsigned char *ext_mac, + int mac_len, char *interface, char *string, u32 xid) +{ + struct in_addr a; + + /* addr may be misaligned */ + if (addr) + memcpy(&a, addr, sizeof(a)); + + print_mac(daemon->namebuff, ext_mac, mac_len); + + if(option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s", + ntohl(xid), + type, + interface, + addr ? inet_ntoa(a) : "", + addr ? " " : "", + daemon->namebuff, + string ? string : ""); + else + my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s", + type, + interface, + addr ? inet_ntoa(a) : "", + addr ? " " : "", + daemon->namebuff, + string ? string : ""); +} + +static void log_options(unsigned char *start, u32 xid) +{ + while (*start != OPTION_END) + { + int is_ip, is_name, i; + char *text = option_string(start[0], &is_ip, &is_name); + unsigned char trunc = option_len(start); + + if (is_ip) + for (daemon->namebuff[0]= 0, i = 0; i <= trunc - INADDRSZ; i += INADDRSZ) + { + if (i != 0) + strncat(daemon->namebuff, ", ", 256 - strlen(daemon->namebuff)); + strncat(daemon->namebuff, inet_ntoa(option_addr_arr(start, i)), 256 - strlen(daemon->namebuff)); + } + else if (!is_name || !sanitise(start, daemon->namebuff)) + { + if (trunc > 13) + trunc = 13; + print_mac(daemon->namebuff, option_ptr(start, 0), trunc); + } + + my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d%s%s%s%s%s", + ntohl(xid), option_len(start), start[0], + text ? ":" : "", text ? text : "", + trunc == 0 ? "" : " ", + trunc == 0 ? "" : daemon->namebuff, + trunc == option_len(start) ? "" : "..."); + start += start[1] + 2; + } +} + +static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize) +{ + while (1) + { + if (p > end) + return NULL; + else if (*p == OPTION_END) + return opt == OPTION_END ? p : NULL; + else if (*p == OPTION_PAD) + p++; + else + { + int opt_len; + if (p > end - 2) + return NULL; /* malformed packet */ + opt_len = option_len(p); + if (p > end - (2 + opt_len)) + return NULL; /* malformed packet */ + if (*p == opt && opt_len >= minsize) + return p; + p += opt_len + 2; + } + } +} + +static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize) +{ + unsigned char *ret, *overload; + + /* skip over DHCP cookie; */ + if ((ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, minsize))) + return ret; + + /* look for overload option. */ + if (!(overload = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, OPTION_OVERLOAD, 1))) + return NULL; + + /* Can we look in filename area ? */ + if ((overload[2] & 1) && + (ret = option_find1(&mess->file[0], &mess->file[128], opt_type, minsize))) + return ret; + + /* finally try sname area */ + if ((overload[2] & 2) && + (ret = option_find1(&mess->sname[0], &mess->sname[64], opt_type, minsize))) + return ret; + + return NULL; +} + +static struct in_addr option_addr_arr(unsigned char *opt, int offset) +{ + /* this worries about unaligned data in the option. */ + /* struct in_addr is network byte order */ + struct in_addr ret; + + memcpy(&ret, option_ptr(opt, offset), INADDRSZ); + + return ret; +} + +static struct in_addr option_addr(unsigned char *opt) +{ + return option_addr_arr(opt, 0); +} + +static unsigned int option_uint(unsigned char *opt, int offset, int size) +{ + /* this worries about unaligned data and byte order */ + unsigned int ret = 0; + int i; + unsigned char *p = option_ptr(opt, offset); + + for (i = 0; i < size; i++) + ret = (ret << 8) | *p++; + + return ret; +} + +static unsigned char *dhcp_skip_opts(unsigned char *start) +{ + while (*start != 0) + start += start[1] + 2; + return start; +} + +/* only for use when building packet: doesn't check for bad data. */ +static unsigned char *find_overload(struct dhcp_packet *mess) +{ + unsigned char *p = &mess->options[0] + sizeof(u32); + + while (*p != 0) + { + if (*p == OPTION_OVERLOAD) + return p; + p += p[1] + 2; + } + return NULL; +} + +static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid, + unsigned char *agent_id, unsigned char *real_end) +{ + unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32)); + unsigned char *overload; + size_t ret; + struct dhcp_netid_list *id_list; + struct dhcp_netid *n; + + /* move agent_id back down to the end of the packet */ + if (agent_id) + { + memmove(p, agent_id, real_end - agent_id); + p += real_end - agent_id; + memset(p, 0, real_end - p); /* in case of overlap */ + } + + /* We do logging too */ + if (netid && option_bool(OPT_LOG_OPTS)) + { + char *s = daemon->namebuff; + for (*s = 0; netid; netid = netid->next) + { + /* kill dupes. */ + for (n = netid->next; n; n = n->next) + if (strcmp(netid->net, n->net) == 0) + break; + + if (!n) + { + strncat (s, netid->net, (MAXDNAME-1) - strlen(s)); + if (netid->next) + strncat (s, ", ", (MAXDNAME-1) - strlen(s)); + } + } + my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), ntohl(mess->xid), s); + } + + /* add END options to the regions. */ + overload = find_overload(mess); + + if (overload && (option_uint(overload, 0, 1) & 1)) + { + *dhcp_skip_opts(mess->file) = OPTION_END; + if (option_bool(OPT_LOG_OPTS)) + log_options(mess->file, mess->xid); + } + else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->file) != 0) + my_syslog(MS_DHCP | LOG_INFO, _("%u bootfile name: %s"), ntohl(mess->xid), (char *)mess->file); + + if (overload && (option_uint(overload, 0, 1) & 2)) + { + *dhcp_skip_opts(mess->sname) = OPTION_END; + if (option_bool(OPT_LOG_OPTS)) + log_options(mess->sname, mess->xid); + } + else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->sname) != 0) + my_syslog(MS_DHCP | LOG_INFO, _("%u server name: %s"), ntohl(mess->xid), (char *)mess->sname); + + + *p++ = OPTION_END; + + for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, netid, 0)) + break; + if (id_list) + mess->flags |= htons(0x8000); /* force broadcast */ + + if (option_bool(OPT_LOG_OPTS)) + { + if (mess->siaddr.s_addr != 0) + my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), inet_ntoa(mess->siaddr)); + + if ((mess->flags & htons(0x8000)) && mess->ciaddr.s_addr == 0) + my_syslog(MS_DHCP | LOG_INFO, _("%u broadcast response"), ntohl(mess->xid)); + + log_options(&mess->options[0] + sizeof(u32), mess->xid); + } + + ret = (size_t)(p - (unsigned char *)mess); + + if (ret < MIN_PACKETSZ) + ret = MIN_PACKETSZ; + + return ret; +} + +static unsigned char *free_space(struct dhcp_packet *mess, unsigned char *end, int opt, int len) +{ + unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32)); + + if (p + len + 3 >= end) + /* not enough space in options area, try and use overload, if poss */ + { + unsigned char *overload; + + if (!(overload = find_overload(mess)) && + (mess->file[0] == 0 || mess->sname[0] == 0)) + { + /* attempt to overload fname and sname areas, we've reserved space for the + overflow option previuously. */ + overload = p; + *(p++) = OPTION_OVERLOAD; + *(p++) = 1; + } + + p = NULL; + + /* using filename field ? */ + if (overload) + { + if (mess->file[0] == 0) + overload[2] |= 1; + + if (overload[2] & 1) + { + p = dhcp_skip_opts(mess->file); + if (p + len + 3 >= mess->file + sizeof(mess->file)) + p = NULL; + } + + if (!p) + { + /* try to bring sname into play (it may be already) */ + if (mess->sname[0] == 0) + overload[2] |= 2; + + if (overload[2] & 2) + { + p = dhcp_skip_opts(mess->sname); + if (p + len + 3 >= mess->sname + sizeof(mess->file)) + p = NULL; + } + } + } + + if (!p) + my_syslog(MS_DHCP | LOG_WARNING, _("cannot send DHCP/BOOTP option %d: no space left in packet"), opt); + } + + if (p) + { + *(p++) = opt; + *(p++) = len; + } + + return p; +} + +static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val) +{ + int i; + unsigned char *p = free_space(mess, end, opt, len); + + if (p) + for (i = 0; i < len; i++) + *(p++) = val >> (8 * (len - (i + 1))); +} + +static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, + char *string, int null_term) +{ + unsigned char *p; + size_t len = strlen(string); + + if (null_term && len != 255) + len++; + + if ((p = free_space(mess, end, opt, len))) + memcpy(p, string, len); +} + +/* return length, note this only does the data part */ +static int do_opt(struct dhcp_opt *opt, unsigned char *p, struct dhcp_context *context, int null_term) +{ + int len = opt->len; + + if ((opt->flags & DHOPT_STRING) && null_term && len != 255) + len++; + + if (p && len != 0) + { + if (context && (opt->flags & DHOPT_ADDR)) + { + int j; + struct in_addr *a = (struct in_addr *)opt->val; + for (j = 0; j < opt->len; j+=INADDRSZ, a++) + { + /* zero means "self" (but not in vendorclass options.) */ + if (a->s_addr == 0) + memcpy(p, &context->local, INADDRSZ); + else + memcpy(p, a, INADDRSZ); + p += INADDRSZ; + } + } + else + memcpy(p, opt->val, len); + } + return len; +} + +static int in_list(unsigned char *list, int opt) +{ + int i; + + /* If no requested options, send everything, not nothing. */ + if (!list) + return 1; + + for (i = 0; list[i] != OPTION_END; i++) + if (opt == list[i]) + return 1; + + return 0; +} + +static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt) +{ + struct dhcp_opt *tmp; + for (tmp = opts; tmp; tmp = tmp->next) + if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) + if (match_netid(tmp->netid, netid, 0)) + return tmp; + + /* No match, look for one without a netid */ + for (tmp = opts; tmp; tmp = tmp->next) + if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) + if (match_netid(tmp->netid, netid, 1)) + return tmp; + + return NULL; +} + +/* mark vendor-encapsulated options which match the client-supplied or + config-supplied vendor class */ +static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt) +{ + for (; dopt; dopt = dopt->next) + { + dopt->flags &= ~DHOPT_VENDOR_MATCH; + if (opt && (dopt->flags & DHOPT_VENDOR)) + { + int i, len = 0; + if (dopt->u.vendor_class) + len = strlen((char *)dopt->u.vendor_class); + for (i = 0; i <= (option_len(opt) - len); i++) + if (len == 0 || memcmp(dopt->u.vendor_class, option_ptr(opt, i), len) == 0) + { + dopt->flags |= DHOPT_VENDOR_MATCH; + break; + } + } + } +} + +static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, + struct dhcp_packet *mess, unsigned char *end, int null_term) +{ + int len, enc_len, ret = 0; + struct dhcp_opt *start; + unsigned char *p; + + /* find size in advance */ + for (enc_len = 0, start = opt; opt; opt = opt->next) + if (opt->flags & flag) + { + int new = do_opt(opt, NULL, NULL, null_term) + 2; + ret = 1; + if (enc_len + new <= 255) + enc_len += new; + else + { + p = free_space(mess, end, encap, enc_len); + for (; start && start != opt; start = start->next) + if (p && (start->flags & flag)) + { + len = do_opt(start, p + 2, NULL, null_term); + *(p++) = start->opt; + *(p++) = len; + p += len; + } + enc_len = new; + start = opt; + } + } + + if (enc_len != 0 && + (p = free_space(mess, end, encap, enc_len + 1))) + { + for (; start; start = start->next) + if (start->flags & flag) + { + len = do_opt(start, p + 2, NULL, null_term); + *(p++) = start->opt; + *(p++) = len; + p += len; + } + *p = OPTION_END; + } + + return ret; +} + +static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid) +{ + unsigned char *p; + + option_put_string(mess, end, OPTION_VENDOR_ID, "PXEClient", 0); + if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17))) + memcpy(p, uuid, 17); +} + +static int prune_vendor_opts(struct dhcp_netid *netid) +{ + int force = 0; + struct dhcp_opt *opt; + + /* prune vendor-encapsulated options based on netid, and look if we're forcing them to be sent */ + for (opt = daemon->dhcp_opts; opt; opt = opt->next) + if (opt->flags & DHOPT_VENDOR_MATCH) + { + if (!match_netid(opt->netid, netid, 1)) + opt->flags &= ~DHOPT_VENDOR_MATCH; + else if (opt->flags & DHOPT_FORCE) + force = 1; + } + return force; +} + +static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local) +{ +#define NUM_OPTS 4 + + unsigned char *p, *q; + struct pxe_service *service; + static struct dhcp_opt *o, *ret; + int i, j = NUM_OPTS - 1; + struct in_addr boot_server; + + /* We pass back references to these, hence they are declared static */ + static unsigned char discovery_control; + static unsigned char fake_prompt[] = { 0, 'P', 'X', 'E' }; + static struct dhcp_opt *fake_opts = NULL; + + /* Disable multicast, since we don't support it, and broadcast + unless we need it */ + discovery_control = 3; + + ret = daemon->dhcp_opts; + + if (!fake_opts && !(fake_opts = whine_malloc(NUM_OPTS * sizeof(struct dhcp_opt)))) + return ret; + + for (i = 0; i < NUM_OPTS; i++) + { + fake_opts[i].flags = DHOPT_VENDOR_MATCH; + fake_opts[i].netid = NULL; + fake_opts[i].next = i == (NUM_OPTS - 1) ? ret : &fake_opts[i+1]; + } + + /* create the data for the PXE_MENU and PXE_SERVERS options. */ + p = (unsigned char *)daemon->dhcp_buff; + q = (unsigned char *)daemon->dhcp_buff3; + + for (i = 0, service = daemon->pxe_services; service; service = service->next) + if (pxe_arch == service->CSA && match_netid(service->netid, netid, 1)) + { + size_t len = strlen(service->menu); + /* opt 43 max size is 255. encapsulated option has type and length + bytes, so its max size is 253. */ + if (p - (unsigned char *)daemon->dhcp_buff + len + 3 < 253) + { + *(p++) = service->type >> 8; + *(p++) = service->type; + *(p++) = len; + memcpy(p, service->menu, len); + p += len; + i++; + } + else + { + toobig: + my_syslog(MS_DHCP | LOG_ERR, _("PXE menu too large")); + return daemon->dhcp_opts; + } + + boot_server = service->basename ? local : service->server; + + if (boot_server.s_addr != 0) + { + if (q - (unsigned char *)daemon->dhcp_buff3 + 3 + INADDRSZ >= 253) + goto toobig; + + /* Boot service with known address - give it */ + *(q++) = service->type >> 8; + *(q++) = service->type; + *(q++) = 1; + /* dest misaligned */ + memcpy(q, &boot_server.s_addr, INADDRSZ); + q += INADDRSZ; + } + else if (service->type != 0) + /* We don't know the server for a service type, so we'll + allow the client to broadcast for it */ + discovery_control = 2; + } + + /* if no prompt, wait forever if there's a choice */ + fake_prompt[0] = (i > 1) ? 255 : 0; + + if (i == 0) + discovery_control = 8; /* no menu - just use use mess->filename */ + else + { + ret = &fake_opts[j--]; + ret->len = p - (unsigned char *)daemon->dhcp_buff; + ret->val = (unsigned char *)daemon->dhcp_buff; + ret->opt = SUBOPT_PXE_MENU; + + if (q - (unsigned char *)daemon->dhcp_buff3 != 0) + { + ret = &fake_opts[j--]; + ret->len = q - (unsigned char *)daemon->dhcp_buff3; + ret->val = (unsigned char *)daemon->dhcp_buff3; + ret->opt = SUBOPT_PXE_SERVERS; + } + } + + for (o = daemon->dhcp_opts; o; o = o->next) + if ((o->flags & DHOPT_VENDOR_MATCH) && o->opt == SUBOPT_PXE_MENU_PROMPT) + break; + + if (!o) + { + ret = &fake_opts[j--]; + ret->len = sizeof(fake_prompt); + ret->val = fake_prompt; + ret->opt = SUBOPT_PXE_MENU_PROMPT; + } + + ret = &fake_opts[j--]; + ret->len = 1; + ret->opt = SUBOPT_PXE_DISCOVERY; + ret->val= &discovery_control; + + return ret; +} + +static void clear_packet(struct dhcp_packet *mess, unsigned char *end) +{ + memset(mess->sname, 0, sizeof(mess->sname)); + memset(mess->file, 0, sizeof(mess->file)); + memset(&mess->options[0] + sizeof(u32), 0, end - (&mess->options[0] + sizeof(u32))); + mess->siaddr.s_addr = 0; +} + +struct dhcp_boot *find_boot(struct dhcp_netid *netid) +{ + struct dhcp_boot *boot; + + /* decide which dhcp-boot option we're using */ + for (boot = daemon->boot_config; boot; boot = boot->next) + if (match_netid(boot->netid, netid, 0)) + break; + if (!boot) + /* No match, look for one without a netid */ + for (boot = daemon->boot_config; boot; boot = boot->next) + if (match_netid(boot->netid, netid, 1)) + break; + + return boot; +} + +static void do_options(struct dhcp_context *context, + struct dhcp_packet *mess, + unsigned char *end, + unsigned char *req_options, + char *hostname, + char *domain, char *config_domain, + struct dhcp_netid *netid, + struct in_addr subnet_addr, + unsigned char fqdn_flags, + int null_term, int pxe_arch, + unsigned char *uuid, + int vendor_class_len) +{ + struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; + struct dhcp_boot *boot; + unsigned char *p; + int i, len, force_encap = 0; + unsigned char f0 = 0, s0 = 0; + int done_file = 0, done_server = 0; + int done_vendor_class = 0; + + if (config_domain && (!domain || !hostname_isequal(domain, config_domain))) + my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring domain %s for DHCP host name %s"), config_domain, hostname); + + /* logging */ + if (option_bool(OPT_LOG_OPTS) && req_options) + { + char *q = daemon->namebuff; + for (i = 0; req_options[i] != OPTION_END; i++) + { + char *s = option_string(req_options[i], NULL, NULL); + q += snprintf(q, MAXDNAME - (q - daemon->namebuff), + "%d%s%s%s", + req_options[i], + s ? ":" : "", + s ? s : "", + req_options[i+1] == OPTION_END ? "" : ", "); + if (req_options[i+1] == OPTION_END || (q - daemon->namebuff) > 40) + { + q = daemon->namebuff; + my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), ntohl(mess->xid), daemon->namebuff); + } + } + } + + if (context) + mess->siaddr = context->local; + + /* 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. + Some PXE ROMs have bugs (surprise!) and need zero-terminated + names, so we always send those. */ + if ((boot = find_boot(netid))) + { + if (boot->sname) + { + if (!option_bool(OPT_NO_OVERRIDE) && + req_options && + in_list(req_options, OPTION_SNAME)) + option_put_string(mess, end, OPTION_SNAME, boot->sname, 1); + else + strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)-1); + } + + if (boot->file) + { + if (!option_bool(OPT_NO_OVERRIDE) && + req_options && + in_list(req_options, OPTION_FILENAME)) + option_put_string(mess, end, OPTION_FILENAME, boot->file, 1); + else + strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + } + + if (boot->next_server.s_addr) + mess->siaddr = boot->next_server; + } + else + /* Use the values of the relevant options if no dhcp-boot given and + they're not explicitly asked for as options. OPTION_END is used + as an internal way to specify siaddr without using dhcp-boot, for use in + dhcp-optsfile. */ + { + if ((!req_options || !in_list(req_options, OPTION_FILENAME)) && + (opt = option_find2(netid, config_opts, OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) + { + strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1); + done_file = 1; + } + + if ((!req_options || !in_list(req_options, OPTION_SNAME)) && + (opt = option_find2(netid, config_opts, OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE)) + { + strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1); + done_server = 1; + } + + if ((opt = option_find2(netid, config_opts, OPTION_END))) + mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr; + } + + /* We don't want to do option-overload for BOOTP, so make the file and sname + fields look like they are in use, even when they aren't. This gets restored + at the end of this function. */ + + if (!req_options || option_bool(OPT_NO_OVERRIDE)) + { + f0 = mess->file[0]; + mess->file[0] = 1; + s0 = mess->sname[0]; + mess->sname[0] = 1; + } + + /* At this point, if mess->sname or mess->file are zeroed, they are available + for option overload, reserve space for the overload option. */ + if (mess->file[0] == 0 || mess->sname[0] == 0) + end -= 3; + + /* rfc3011 says this doesn't need to be in the requested options list. */ + if (subnet_addr.s_addr) + option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr)); + + /* replies to DHCPINFORM may not have a valid context */ + if (context) + { + if (!option_find2(netid, config_opts, OPTION_NETMASK)) + option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr)); + + /* May not have a "guessed" broadcast address if we got no packets via a relay + from this net yet (ie just unicast renewals after a restart */ + if (context->broadcast.s_addr && + !option_find2(netid, config_opts, OPTION_BROADCAST)) + option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr)); + + /* Same comments as broadcast apply, and also may not be able to get a sensible + default when using subnet select. User must configure by steam in that case. */ + if (context->router.s_addr && + in_list(req_options, OPTION_ROUTER) && + !option_find2(netid, config_opts, OPTION_ROUTER)) + option_put(mess, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr)); + + if (in_list(req_options, OPTION_DNSSERVER) && + !option_find2(netid, config_opts, OPTION_DNSSERVER)) + option_put(mess, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr)); + } + + if (domain && in_list(req_options, OPTION_DOMAINNAME) && + !option_find2(netid, config_opts, OPTION_DOMAINNAME)) + option_put_string(mess, end, OPTION_DOMAINNAME, domain, null_term); + + /* Note that we ignore attempts to set the fqdn using --dhc-option=81,<name> */ + if (hostname) + { + if (in_list(req_options, OPTION_HOSTNAME) && + !option_find2(netid, config_opts, OPTION_HOSTNAME)) + option_put_string(mess, end, OPTION_HOSTNAME, hostname, null_term); + + if (fqdn_flags != 0) + { + len = strlen(hostname) + 3; + + if (fqdn_flags & 0x04) + len += 2; + else if (null_term) + len++; + + if (domain) + len += strlen(domain) + 1; + + if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len))) + { + *(p++) = fqdn_flags; + *(p++) = 255; + *(p++) = 255; + + if (fqdn_flags & 0x04) + { + p = do_rfc1035_name(p, hostname); + if (domain) + p = do_rfc1035_name(p, domain); + *p++ = 0; + } + else + { + memcpy(p, hostname, strlen(hostname)); + p += strlen(hostname); + if (domain) + { + *(p++) = '.'; + memcpy(p, domain, strlen(domain)); + p += strlen(domain); + } + if (null_term) + *(p++) = 0; + } + } + } + } + + for (opt = config_opts; opt; opt = opt->next) + { + int optno = opt->opt; + + /* was it asked for, or are we sending it anyway? */ + if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno)) + continue; + + /* prohibit some used-internally options */ + if (optno == OPTION_CLIENT_FQDN || + optno == OPTION_MAXMESSAGE || + optno == OPTION_OVERLOAD || + optno == OPTION_PAD || + optno == OPTION_END) + continue; + + if (optno == OPTION_SNAME && done_server) + continue; + + if (optno == OPTION_FILENAME && done_file) + continue; + + /* netids match and not encapsulated? */ + if (opt != option_find2(netid, config_opts, optno)) + continue; + + /* For the options we have default values on + dhc-option=<optionno> means "don't include this option" + not "include a zero-length option" */ + if (opt->len == 0 && + (optno == OPTION_NETMASK || + optno == OPTION_BROADCAST || + optno == OPTION_ROUTER || + optno == OPTION_DNSSERVER || + optno == OPTION_DOMAINNAME || + optno == OPTION_HOSTNAME)) + continue; + + /* vendor-class comes from elsewhere for PXE */ + if (pxe_arch != -1 && optno == OPTION_VENDOR_ID) + continue; + + /* always force null-term for filename and servername - buggy PXE again. */ + len = do_opt(opt, NULL, context, + (optno == OPTION_SNAME || optno == OPTION_FILENAME) ? 1 : null_term); + + if ((p = free_space(mess, end, optno, len))) + { + do_opt(opt, p, context, + (optno == OPTION_SNAME || optno == OPTION_FILENAME) ? 1 : null_term); + + /* If we send a vendor-id, revisit which vendor-ops we consider + it appropriate to send. */ + if (optno == OPTION_VENDOR_ID) + { + match_vendor_opts(p - 2, config_opts); + done_vendor_class = 1; + } + } + } + + /* Now send options to be encapsulated in arbitrary options, + eg dhcp-option=encap:172,17,....... + Also hand vendor-identifying vendor-encapsulated options, + dhcp-option = rfc3925-encap:13,17,....... + The may be more that one "outer" to do, so group + all the options which match each outer in turn. */ + for (opt = config_opts; opt; opt = opt->next) + opt->flags &= ~DHOPT_ENCAP_DONE; + + for (opt = config_opts; opt; opt = opt->next) + { + int flags; + + if ((flags = (opt->flags & (DHOPT_ENCAPSULATE | DHOPT_RFC3925)))) + { + int found = 0; + struct dhcp_opt *o; + + if (opt->flags & DHOPT_ENCAP_DONE) + continue; + + for (len = 0, o = config_opts; o; o = o->next) + { + int outer = flags & DHOPT_ENCAPSULATE ? o->u.encap : OPTION_VENDOR_IDENT_OPT; + + o->flags &= ~DHOPT_ENCAP_MATCH; + + if (!(o->flags & flags) || opt->u.encap != o->u.encap) + continue; + + o->flags |= DHOPT_ENCAP_DONE; + if (match_netid(o->netid, netid, 1) && + ((o->flags & DHOPT_FORCE) || in_list(req_options, outer))) + { + o->flags |= DHOPT_ENCAP_MATCH; + found = 1; + len += do_opt(o, NULL, NULL, 0) + 2; + } + } + + if (found) + { + if (flags & DHOPT_ENCAPSULATE) + do_encap_opts(config_opts, opt->u.encap, DHOPT_ENCAP_MATCH, mess, end, null_term); + else if (len > 250) + my_syslog(MS_DHCP | LOG_WARNING, _("cannot send RFC3925 option: too many options for enterprise number %d"), opt->u.encap); + else if ((p = free_space(mess, end, OPTION_VENDOR_IDENT_OPT, len + 5))) + { + int swap_ent = htonl(opt->u.encap); + memcpy(p, &swap_ent, 4); + p += 4; + *(p++) = len; + for (o = config_opts; o; o = o->next) + if (o->flags & DHOPT_ENCAP_MATCH) + { + len = do_opt(o, p + 2, NULL, 0); + *(p++) = o->opt; + *(p++) = len; + p += len; + } + } + } + } + } + + force_encap = prune_vendor_opts(netid); + + if (context && pxe_arch != -1) + { + pxe_misc(mess, end, uuid); + config_opts = pxe_opts(pxe_arch, netid, context->local); + } + + if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) && + do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) && + pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 && + (p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len))) + /* If we send vendor encapsulated options, and haven't already sent option 60, + echo back the value we got from the client. */ + memcpy(p, daemon->dhcp_buff3, vendor_class_len); + + /* restore BOOTP anti-overload hack */ + if (!req_options || option_bool(OPT_NO_OVERRIDE)) + { + mess->file[0] = f0; + mess->sname[0] = s0; + } +} + +#endif + + + + + + + diff --git a/src/tftp.c b/src/tftp.c new file mode 100644 index 0000000..789c444 --- /dev/null +++ b/src/tftp.c @@ -0,0 +1,711 @@ +/* dnsmasq is Copyright (c) 2000-2011 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_TFTP + +static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special); +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_oops(char *packet, char *file); +static ssize_t get_block(char *packet, struct tftp_transfer *transfer); +static char *next(char **p, char *end); + +#define OP_RRQ 1 +#define OP_WRQ 2 +#define OP_DATA 3 +#define OP_ACK 4 +#define OP_ERR 5 +#define OP_OACK 6 + +#define ERR_NOTDEF 0 +#define ERR_FNF 1 +#define ERR_PERM 2 +#define ERR_FULL 3 +#define ERR_ILL 4 + +void tftp_request(struct listener *listen, time_t now) +{ + ssize_t len; + char *packet = daemon->packet; + char *filename, *mode, *p, *end, *opt; + union mysockaddr addr, peer; + struct msghdr msg; + struct iovec iov; + struct ifreq ifr; + int is_err = 1, if_index = 0, mtu = 0, special = 0; +#ifdef HAVE_DHCP + struct iname *tmp; +#endif + struct tftp_transfer *transfer; + int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */ +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + int mtuflag = IP_PMTUDISC_DONT; +#endif + char namebuff[IF_NAMESIZE]; + char pretty_addr[ADDRSTRLEN]; + char *name; + char *prefix = daemon->tftp_prefix; + struct tftp_prefix *pref; + struct interface_list *ir; + + union { + struct cmsghdr align; /* this ensures alignment */ +#ifdef HAVE_IPV6 + char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#endif +#if defined(HAVE_LINUX_NETWORK) + char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#elif defined(HAVE_SOLARIS_NETWORK) + char control[CMSG_SPACE(sizeof(unsigned int))]; +#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) + char control[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#endif + } control_u; + + msg.msg_controllen = sizeof(control_u); + msg.msg_control = control_u.control; + msg.msg_flags = 0; + msg.msg_name = &peer; + msg.msg_namelen = sizeof(peer); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + iov.iov_base = packet; + iov.iov_len = daemon->packet_buff_sz; + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; + + if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) + return; + + if (option_bool(OPT_NOWILD)) + { + addr = listen->iface->addr; + mtu = listen->iface->mtu; + name = listen->iface->name; + } + else + { + struct cmsghdr *cmptr; + int check; + struct interface_list *ir; + + if (msg.msg_controllen < sizeof(struct cmsghdr)) + return; + + addr.sa.sa_family = listen->family; + +#if defined(HAVE_LINUX_NETWORK) + if (listen->family == AF_INET) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + { + union { + unsigned char *c; + struct in_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + addr.in.sin_addr = p.p->ipi_spec_dst; + if_index = p.p->ipi_ifindex; + } + +#elif defined(HAVE_SOLARIS_NETWORK) + if (listen->family == AF_INET) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + { + union { + unsigned char *c; + struct in_addr *a; + unsigned int *i; + } p; + p.c = CMSG_DATA(cmptr); + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) + addr.in.sin_addr = *(p.a); + else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) + if_index = *(p.i); + } + +#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) + if (listen->family == AF_INET) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + { + union { + unsigned char *c; + struct in_addr *a; + struct sockaddr_dl *s; + } p; + p.c = CMSG_DATA(cmptr); + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) + addr.in.sin_addr = *(p.a); + else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) + if_index = p.s->sdl_index; + } + +#endif + +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + { + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo) + { + union { + unsigned char *c; + struct in6_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + + addr.in6.sin6_addr = p.p->ipi6_addr; + if_index = p.p->ipi6_ifindex; + } + } +#endif + + if (!indextoname(listen->tftpfd, if_index, namebuff)) + return; + + name = namebuff; + +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + check = iface_check(AF_INET6, (struct all_addr *)&addr.in6.sin6_addr, name, &if_index); + else +#endif + check = iface_check(AF_INET, (struct all_addr *)&addr.in.sin_addr, name, &if_index); + + /* wierd TFTP service override */ + for (ir = daemon->tftp_interfaces; ir; ir = ir->next) + if (strcmp(ir->interface, name) == 0) + break; + + if (!ir) + { + if (!daemon->tftp_unlimited || !check) + return; + +#ifdef HAVE_DHCP + /* allowed interfaces are the same as for DHCP */ + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, name) == 0)) + return; +#endif + } + + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) + mtu = ifr.ifr_mtu; + } + + /* check for per-interface prefix */ + for (pref = daemon->if_prefix; pref; pref = pref->next) + if (strcmp(pref->interface, name) == 0) + prefix = pref->prefix; + + /* wierd TFTP interfaces disable special options. */ + for (ir = daemon->tftp_interfaces; ir; ir = ir->next) + if (strcmp(ir->interface, name) == 0) + special = 1; + +#ifdef HAVE_SOCKADDR_SA_LEN + addr.sa.sa_len = sa_len(&addr); +#endif + + if (listen->family == AF_INET) + addr.in.sin_port = htons(port); +#ifdef HAVE_IPV6 + else + { + addr.in6.sin6_port = htons(port); + addr.in6.sin6_flowinfo = 0; + } +#endif + + if (!(transfer = whine_malloc(sizeof(struct tftp_transfer)))) + return; + + if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1) + { + free(transfer); + return; + } + + transfer->peer = peer; + transfer->timeout = now + 2; + transfer->backoff = 1; + transfer->block = 1; + transfer->blocksize = 512; + transfer->offset = 0; + transfer->file = NULL; + transfer->opt_blocksize = transfer->opt_transize = 0; + transfer->netascii = transfer->carrylf = 0; + + prettyprint_addr(&peer, pretty_addr); + + /* if we have a nailed-down range, iterate until we find a free one. */ + while (1) + { + if (bind(transfer->sockfd, &addr.sa, sizeof(addr)) == -1 || +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + setsockopt(transfer->sockfd, SOL_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 || +#endif + !fix_fd(transfer->sockfd)) + { + if (errno == EADDRINUSE && daemon->start_tftp_port != 0) + { + if (++port <= daemon->end_tftp_port) + { + if (listen->family == AF_INET) + addr.in.sin_port = htons(port); +#ifdef HAVE_IPV6 + else + addr.in6.sin6_port = htons(port); +#endif + continue; + } + my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP")); + } + free_transfer(transfer); + return; + } + break; + } + + p = packet + 2; + end = packet + len; + + if (ntohs(*((unsigned short *)packet)) != OP_RRQ || + !(filename = next(&p, end)) || + !(mode = next(&p, end)) || + (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0)) + len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), pretty_addr); + else + { + if (strcasecmp(mode, "netascii") == 0) + transfer->netascii = 1; + + while ((opt = next(&p, end))) + { + if (strcasecmp(opt, "blksize") == 0) + { + if ((opt = next(&p, end)) && + (special || !option_bool(OPT_TFTP_NOBLOCK))) + { + 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; + transfer->opt_blocksize = 1; + transfer->block = 0; + } + } + else if (strcasecmp(opt, "tsize") == 0 && next(&p, end) && !transfer->netascii) + { + transfer->opt_transize = 1; + transfer->block = 0; + } + } + + /* cope with backslashes from windows boxen. */ + while ((p = strchr(filename, '\\'))) + *p = '/'; + + strcpy(daemon->namebuff, "/"); + if (prefix) + { + if (prefix[0] == '/') + daemon->namebuff[0] = 0; + strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff)); + if (prefix[strlen(prefix)-1] != '/') + strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); + + if (!special && option_bool(OPT_TFTP_APREF)) + { + size_t oldlen = strlen(daemon->namebuff); + struct stat statbuf; + + strncat(daemon->namebuff, pretty_addr, (MAXDNAME-1) - strlen(daemon->namebuff)); + strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); + + /* 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] == '/') + { + if (strstr(filename, daemon->namebuff) == filename) + daemon->namebuff[0] = 0; + else + filename++; + } + } + 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, special))) + { + if ((len = get_block(packet, transfer)) == -1) + len = tftp_err_oops(packet, daemon->namebuff); + else + is_err = 0; + } + } + + while (sendto(transfer->sockfd, packet, len, 0, + (struct sockaddr *)&peer, sizeof(peer)) == -1 && errno == EINTR); + + if (is_err) + free_transfer(transfer); + else + { + transfer->next = daemon->tftp_trans; + daemon->tftp_trans = transfer; + } +} + +static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special) +{ + char *packet = daemon->packet, *namebuff = daemon->namebuff; + struct tftp_file *file; + struct tftp_transfer *t; + uid_t uid = geteuid(); + struct stat statbuf; + int fd = -1; + + /* trick to ban moving out of the subtree */ + if (prefix && strstr(namebuff, "/../")) + goto perm; + + if ((fd = open(namebuff, O_RDONLY)) == -1) + { + if (errno == ENOENT) + { + *len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff); + return NULL; + } + else if (errno == EACCES) + goto perm; + else + goto oops; + } + + /* stat the file descriptor to avoid stat->open races */ + if (fstat(fd, &statbuf) == -1) + goto oops; + + /* running as root, must be world-readable */ + if (uid == 0) + { + if (!(statbuf.st_mode & S_IROTH)) + goto perm; + } + /* in secure mode, must be owned by user running dnsmasq */ + else if (!special && option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) + goto perm; + + /* If we're doing many tranfers 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 + this keeps error messages sane. */ + for (t = daemon->tftp_trans; t; t = t->next) + if (t->file->dev == statbuf.st_dev && + t->file->inode == statbuf.st_ino && + strcmp(t->file->filename, namebuff) == 0) + { + close(fd); + t->file->refcount++; + return t->file; + } + + if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1))) + { + errno = ENOMEM; + goto oops; + } + + file->fd = fd; + file->size = statbuf.st_size; + file->dev = statbuf.st_dev; + file->inode = statbuf.st_ino; + file->refcount = 1; + strcpy(file->filename, namebuff); + return file; + + perm: + errno = EACCES; + *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff); + if (fd != -1) + close(fd); + return NULL; + + oops: + *len = tftp_err_oops(packet, namebuff); + if (fd != -1) + close(fd); + return NULL; +} + +void check_tftp_listeners(fd_set *rset, time_t now) +{ + struct tftp_transfer *transfer, *tmp, **up; + ssize_t len; + char pretty_addr[ADDRSTRLEN]; + + struct ack { + unsigned short op, block; + } *mess = (struct ack *)daemon->packet; + + /* Check for activity on any existing transfers */ + for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) + { + tmp = transfer->next; + + if (FD_ISSET(transfer->sockfd, rset)) + { + /* we overwrote the buffer... */ + daemon->srv_save = NULL; + + prettyprint_addr(&transfer->peer, pretty_addr); + + if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) + { + if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) + { + /* Got ack, ensure we take the (re)transmit path */ + transfer->timeout = now; + transfer->backoff = 0; + if (transfer->block++ != 0) + transfer->offset += transfer->blocksize - transfer->expansion; + } + else if (ntohs(mess->op) == OP_ERR) + { + char *p = daemon->packet + sizeof(struct ack); + char *end = daemon->packet + len; + char *err = next(&p, end); + + /* Sanitise error message */ + if (!err) + err = ""; + else + { + unsigned char *q, *r; + for (q = r = (unsigned char *)err; *r; r++) + if (isprint(*r)) + *(q++) = *r; + *q = 0; + } + + my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"), + (int)ntohs(mess->block), err, + pretty_addr); + + /* Got err, ensure we take abort */ + transfer->timeout = now; + transfer->backoff = 100; + } + } + } + + if (difftime(now, transfer->timeout) >= 0.0) + { + int endcon = 0; + + /* timeout, retransmit */ + transfer->timeout += 1 + (1<<transfer->backoff); + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; + + if ((len = get_block(daemon->packet, transfer)) == -1) + { + len = tftp_err_oops(daemon->packet, transfer->file->filename); + endcon = 1; + } + else if (++transfer->backoff > 5) + { + /* don't complain about timeout when we're awaiting the last + ACK, some clients never send it */ + if (len != 0) + { + my_syslog(MS_TFTP | LOG_ERR, _("failed sending %s to %s"), + transfer->file->filename, pretty_addr); + len = 0; + endcon = 1; + } + } + + if (len != 0) + while(sendto(transfer->sockfd, daemon->packet, len, 0, + (struct sockaddr *)&transfer->peer, sizeof(transfer->peer)) == -1 && errno == EINTR); + + if (endcon || len == 0) + { + if (!endcon) + my_syslog(MS_TFTP | LOG_INFO, _("sent %s to %s"), transfer->file->filename, pretty_addr); + /* unlink */ + *up = tmp; + free_transfer(transfer); + continue; + } + } + + up = &transfer->next; + } +} + +static void free_transfer(struct tftp_transfer *transfer) +{ + close(transfer->sockfd); + if (transfer->file && (--transfer->file->refcount) == 0) + { + close(transfer->file->fd); + free(transfer->file); + } + free(transfer); +} + +static char *next(char **p, char *end) +{ + char *ret = *p; + size_t len; + + if (*(end-1) != 0 || + *p == end || + (len = strlen(ret)) == 0) + return NULL; + + *p += len + 1; + return ret; +} + +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; + char *errstr = strerror(errno); + + mess->op = htons(OP_ERR); + mess->err = htons(err); + ret += (snprintf(mess->message, 500, message, file, errstr) + 1); + my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); + + return ret; +} + +static ssize_t tftp_err_oops(char *packet, char *file) +{ + return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), file); +} + +/* return -1 for error, zero for done. */ +static ssize_t get_block(char *packet, struct tftp_transfer *transfer) +{ + if (transfer->block == 0) + { + /* send OACK */ + char *p; + struct oackmess { + unsigned short op; + char data[]; + } *mess = (struct oackmess *)packet; + + p = mess->data; + mess->op = htons(OP_OACK); + if (transfer->opt_blocksize) + { + p += (sprintf(p, "blksize") + 1); + p += (sprintf(p, "%d", transfer->blocksize) + 1); + } + if (transfer->opt_transize) + { + p += (sprintf(p,"tsize") + 1); + p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1); + } + + return p - packet; + } + else + { + /* send data packet */ + struct datamess { + unsigned short op, block; + unsigned char data[]; + } *mess = (struct datamess *)packet; + + size_t size = transfer->file->size - transfer->offset; + + if (transfer->offset > transfer->file->size) + return 0; /* finished */ + + if (size > transfer->blocksize) + size = transfer->blocksize; + + mess->op = htons(OP_DATA); + mess->block = htons((unsigned short)(transfer->block)); + + if (lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 || + !read_write(transfer->file->fd, mess->data, size, 1)) + return -1; + + transfer->expansion = 0; + + /* Map '\n' to CR-LF in netascii mode */ + if (transfer->netascii) + { + size_t i; + int newcarrylf; + + for (i = 0, newcarrylf = 0; i < size; i++) + if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf)) + { + if (size == transfer->blocksize) + { + transfer->expansion++; + if (i == size - 1) + newcarrylf = 1; /* don't expand LF again if it moves to the next block */ + } + else + size++; /* room in this block */ + + /* make space and insert CR */ + memmove(&mess->data[i+1], &mess->data[i], size - (i + 1)); + mess->data[i] = '\r'; + + i++; + } + transfer->carrylf = newcarrylf; + + } + + return size + 4; + } +} + +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..e64f1a6 --- /dev/null +++ b/src/util.c @@ -0,0 +1,516 @@ +/* dnsmasq is Copyright (c) 2000-2011 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/>. +*/ + +/* The SURF random number generator was taken from djbdns-1.05, by + Daniel J Bernstein, which is public domain. */ + + +#include "dnsmasq.h" + +#ifdef HAVE_BROKEN_RTC +#include <sys/times.h> +#endif + +#if defined(LOCALEDIR) || defined(HAVE_IDN) +#include <idna.h> +#endif + +#ifdef HAVE_ARC4RANDOM +void rand_init(void) +{ + return; +} + +unsigned short rand16(void) +{ + return (unsigned short) (arc4random() >> 15); +} + +#else + +/* SURF random number generator */ + +static u32 seed[32]; +static u32 in[12]; +static u32 out[8]; + +void rand_init() +{ + int fd = open(RANDFILE, O_RDONLY); + + if (fd == -1 || + !read_write(fd, (unsigned char *)&seed, sizeof(seed), 1) || + !read_write(fd, (unsigned char *)&in, sizeof(in), 1)) + die(_("failed to seed the random number generator: %s"), NULL, EC_MISC); + + close(fd); +} + +#define ROTATE(x,b) (((x) << (b)) | ((x) >> (32 - (b)))) +#define MUSH(i,b) x = t[i] += (((x ^ seed[i]) + sum) ^ ROTATE(x,b)); + +static void surf(void) +{ + u32 t[12]; u32 x; u32 sum = 0; + int r; int i; int loop; + + for (i = 0;i < 12;++i) t[i] = in[i] ^ seed[12 + i]; + for (i = 0;i < 8;++i) out[i] = seed[24 + i]; + x = t[11]; + for (loop = 0;loop < 2;++loop) { + for (r = 0;r < 16;++r) { + sum += 0x9e3779b9; + MUSH(0,5) MUSH(1,7) MUSH(2,9) MUSH(3,13) + MUSH(4,5) MUSH(5,7) MUSH(6,9) MUSH(7,13) + MUSH(8,5) MUSH(9,7) MUSH(10,9) MUSH(11,13) + } + for (i = 0;i < 8;++i) out[i] ^= t[i + 4]; + } +} + +unsigned short rand16(void) +{ + static int outleft = 0; + + if (!outleft) { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + return (unsigned short) out[--outleft]; +} + +#endif + +static int check_name(char *in) +{ + /* remove trailing . + also fail empty string and label > 63 chars */ + size_t dotgap = 0, l = strlen(in); + char c; + int nowhite = 0; + + if (l == 0 || l > MAXDNAME) return 0; + + if (in[l-1] == '.') + { + if (l == 1) return 0; + in[l-1] = 0; + } + + for (; (c = *in); in++) + { + if (c == '.') + dotgap = 0; + else if (++dotgap > MAXLABEL) + return 0; + 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) + else if (!isascii((unsigned char)c)) + return 0; +#endif + else if (c != ' ') + nowhite = 1; + } + + if (!nowhite) + return 0; + + return 1; +} + +/* Hostnames have a more limited valid charset than domain names + so check for legal char a-z A-Z 0-9 - _ + Note that this may receive a FQDN, so only check the first label + for the tighter criteria. */ +int legal_hostname(char *name) +{ + char c; + + if (!check_name(name)) + return 0; + + for (; (c = *name); name++) + /* check for legal char a-z A-Z 0-9 - _ . */ + { + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || c == '_') + continue; + + /* end of hostname part */ + if (c == '.') + return 1; + + return 0; + } + + return 1; +} + +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)) + return NULL; + +#if defined(LOCALEDIR) || defined(HAVE_IDN) + if ((rc = idna_to_ascii_lz(in, &ret, 0)) != IDNA_SUCCESS) + { + 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; + } +#else + if ((ret = whine_malloc(strlen(in)+1))) + strcpy(ret, in); + else if (nomem) + *nomem = 1; +#endif + + return ret; +} + +unsigned char *do_rfc1035_name(unsigned char *p, char *sval) +{ + int j; + + while (sval && *sval) + { + unsigned char *cp = p++; + for (j = 0; *sval && (*sval != '.'); sval++, j++) + *p++ = *sval; + *cp = j; + if (*sval) + sval++; + } + return p; +} + +/* for use during startup */ +void *safe_malloc(size_t size) +{ + void *ret = malloc(size); + + if (!ret) + die(_("could not get memory"), NULL, EC_NOMEM); + + return ret; +} + +void safe_pipe(int *fd, int read_noblock) +{ + if (pipe(fd) == -1 || + !fix_fd(fd[1]) || + (read_noblock && !fix_fd(fd[0]))) + die(_("cannot create pipe: %s"), NULL, EC_MISC); +} + +void *whine_malloc(size_t size) +{ + void *ret = malloc(size); + + if (!ret) + my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size); + + return ret; +} + +int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) +{ + if (s1->sa.sa_family == s2->sa.sa_family) + { + if (s1->sa.sa_family == AF_INET && + s1->in.sin_port == s2->in.sin_port && + s1->in.sin_addr.s_addr == s2->in.sin_addr.s_addr) + return 1; +#ifdef HAVE_IPV6 + if (s1->sa.sa_family == AF_INET6 && + s1->in6.sin6_port == s2->in6.sin6_port && + IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr)) + return 1; +#endif + } + return 0; +} + +int sa_len(union mysockaddr *addr) +{ +#ifdef HAVE_SOCKADDR_SA_LEN + return addr->sa.sa_len; +#else +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6) + return sizeof(addr->in6); + else +#endif + return sizeof(addr->in); +#endif +} + +/* don't use strcasecmp and friends here - they may be messed up by LOCALE */ +int hostname_isequal(char *a, char *b) +{ + unsigned int c1, c2; + + do { + c1 = (unsigned char) *a++; + c2 = (unsigned char) *b++; + + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + return 0; + } while (c1); + + return 1; +} + +time_t dnsmasq_time(void) +{ +#ifdef HAVE_BROKEN_RTC + struct tms dummy; + static long tps = 0; + + if (tps == 0) + tps = sysconf(_SC_CLK_TCK); + + return (time_t)(times(&dummy)/tps); +#else + return time(NULL); +#endif +} + +int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) +{ + return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); +} + +/* returns port number from address */ +int prettyprint_addr(union mysockaddr *addr, char *buf) +{ + int port = 0; + +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET) + { + inet_ntop(AF_INET, &addr->in.sin_addr, buf, ADDRSTRLEN); + port = ntohs(addr->in.sin_port); + } + else if (addr->sa.sa_family == AF_INET6) + { + inet_ntop(AF_INET6, &addr->in6.sin6_addr, buf, ADDRSTRLEN); + port = ntohs(addr->in6.sin6_port); + } +#else + strcpy(buf, inet_ntoa(addr->in.sin_addr)); + port = ntohs(addr->in.sin_port); +#endif + + return port; +} + +void prettyprint_time(char *buf, unsigned int t) +{ + if (t == 0xffffffff) + sprintf(buf, _("infinite")); + else + { + unsigned int x, p = 0; + if ((x = t/86400)) + p += sprintf(&buf[p], "%dd", x); + if ((x = (t/3600)%24)) + p += sprintf(&buf[p], "%dh", x); + if ((x = (t/60)%60)) + p += sprintf(&buf[p], "%dm", x); + if ((x = t%60)) + p += sprintf(&buf[p], "%ds", x); + } +} + + +/* in may equal out, when maxlen may be -1 (No max len). + Return -1 for extraneous no-hex chars found. */ +int parse_hex(char *in, unsigned char *out, int maxlen, + unsigned int *wildcard_mask, int *mac_type) +{ + int mask = 0, i = 0; + char *r; + + if (mac_type) + *mac_type = 0; + + while (maxlen == -1 || i < maxlen) + { + for (r = in; *r != 0 && *r != ':' && *r != '-'; r++) + if (*r != '*' && !isxdigit((unsigned char)*r)) + return -1; + + if (*r == 0) + maxlen = i; + + if (r != in ) + { + if (*r == '-' && i == 0 && mac_type) + { + *r = 0; + *mac_type = strtol(in, NULL, 16); + mac_type = NULL; + } + else + { + *r = 0; + mask = mask << 1; + if (strcmp(in, "*") == 0) + mask |= 1; + else + out[i] = strtol(in, NULL, 16); + i++; + } + } + in = r+1; + } + + if (wildcard_mask) + *wildcard_mask = mask; + + return i; +} + +/* return 0 for no match, or (no matched octets) + 1 */ +int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask) +{ + int i, count; + for (count = 1, i = len - 1; i >= 0; i--, mask = mask >> 1) + if (!(mask & 1)) + { + if (a[i] == b[i]) + count++; + else + return 0; + } + return count; +} + +/* _note_ may copy buffer */ +int expand_buf(struct iovec *iov, size_t size) +{ + void *new; + + if (size <= (size_t)iov->iov_len) + return 1; + + if (!(new = whine_malloc(size))) + { + errno = ENOMEM; + return 0; + } + + if (iov->iov_base) + { + memcpy(new, iov->iov_base, iov->iov_len); + free(iov->iov_base); + } + + iov->iov_base = new; + iov->iov_len = size; + + return 1; +} + +char *print_mac(char *buff, unsigned char *mac, int len) +{ + char *p = buff; + int i; + + if (len == 0) + sprintf(p, "<null>"); + else + for (i = 0; i < len; i++) + p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":"); + + return buff; +} + +void bump_maxfd(int fd, int *max) +{ + if (fd > *max) + *max = fd; +} + +int retry_send(void) +{ + struct timespec waiter; + if (errno == EAGAIN) + { + waiter.tv_sec = 0; + waiter.tv_nsec = 10000; + nanosleep(&waiter, NULL); + return 1; + } + + if (errno == EINTR) + return 1; + + return 0; +} + +int read_write(int fd, unsigned char *packet, int size, int rw) +{ + ssize_t n, done; + + for (done = 0; done < size; done += n) + { + retry: + if (rw) + n = read(fd, &packet[done], (size_t)(size - done)); + else + n = write(fd, &packet[done], (size_t)(size - done)); + + if (n == 0) + return 0; + else if (n == -1) + { + if (retry_send() || errno == ENOMEM || errno == ENOBUFS) + goto retry; + else + return 0; + } + } + return 1; +} + |