summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSeungyoun Ju <sy39.ju@samsung.com>2012-08-21 18:23:32 +0900
committerSeungyoun Ju <sy39.ju@samsung.com>2012-08-21 18:23:32 +0900
commit94186e188aad231cd6cf81e06a84d2b9eab05ccc (patch)
treea5813abb98e201a3cac4f3b5eba5069bfd2cb397 /src
parente5a7e6adba9219ecf8bb5668382fab628f63bf6e (diff)
downloaddnsmasq-2.0alpha.tar.gz
dnsmasq-2.0alpha.tar.bz2
dnsmasq-2.0alpha.zip
Diffstat (limited to 'src')
-rw-r--r--src/bpf.c321
-rw-r--r--src/cache.c1226
-rw-r--r--src/config.h280
-rw-r--r--src/dbus.c440
-rw-r--r--src/dhcp.c1094
-rw-r--r--src/dhcp_protocol.h91
-rw-r--r--src/dns_protocol.h111
-rw-r--r--src/dnsmasq.c1325
-rw-r--r--src/dnsmasq.h941
-rw-r--r--src/forward.c1182
-rw-r--r--src/helper.c410
-rw-r--r--src/lease.c615
-rw-r--r--src/log.c466
-rw-r--r--src/netlink.c326
-rw-r--r--src/network.c973
-rw-r--r--src/option.c3416
-rw-r--r--src/rfc1035.c1815
-rw-r--r--src/rfc2131.c2512
-rw-r--r--src/tftp.c711
-rw-r--r--src/util.c516
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 = &ether;
+ 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 *)&in;
+ 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..9218765
--- /dev/null
+++ b/src/rfc2131.c
@@ -0,0 +1,2512 @@
+/* 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
+
+#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;
+
+ static time_t old_time = 0;
+
+ 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);
+ if (difftime(now, old_time) > 7)
+ emit_dbus_signal(ACTION_CONNECT, lease, hostname);
+ old_time = now;
+
+ 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;
+}
+