[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit
Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c
new file mode 100755
index 0000000..1e4aad2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c
@@ -0,0 +1,232 @@
+/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+/* Time between forced re-loads from kernel. */
+#define INTERVAL 90
+
+#define ARP_MARK 0
+#define ARP_FOUND 1 /* Confirmed */
+#define ARP_NEW 2 /* Newly created */
+#define ARP_EMPTY 3 /* No MAC addr */
+
+struct arp_record {
+ unsigned short hwlen, status;
+ int family;
+ unsigned char hwaddr[DHCP_CHADDR_MAX];
+ union all_addr addr;
+ struct arp_record *next;
+};
+
+static struct arp_record *arps = NULL, *old = NULL, *freelist = NULL;
+static time_t last = 0;
+
+static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
+{
+ struct arp_record *arp;
+
+ (void)parmv;
+
+ if (maclen > DHCP_CHADDR_MAX)
+ return 1;
+
+ /* Look for existing entry */
+ for (arp = arps; arp; arp = arp->next)
+ {
+ if (family != arp->family || arp->status == ARP_NEW)
+ continue;
+
+ if (family == AF_INET)
+ {
+ if (arp->addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr)
+ continue;
+ }
+ else
+ {
+ if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, (struct in6_addr *)addrp))
+ continue;
+ }
+
+ if (arp->status == ARP_EMPTY)
+ {
+ /* existing address, was negative. */
+ arp->status = ARP_NEW;
+ arp->hwlen = maclen;
+ memcpy(arp->hwaddr, mac, maclen);
+ }
+ else if (arp->hwlen == maclen && memcmp(arp->hwaddr, mac, maclen) == 0)
+ /* Existing entry matches - confirm. */
+ arp->status = ARP_FOUND;
+ else
+ continue;
+
+ break;
+ }
+
+ if (!arp)
+ {
+ /* New entry */
+ if (freelist)
+ {
+ arp = freelist;
+ freelist = freelist->next;
+ }
+ else if (!(arp = whine_malloc(sizeof(struct arp_record))))
+ return 1;
+
+ arp->next = arps;
+ arps = arp;
+ arp->status = ARP_NEW;
+ arp->hwlen = maclen;
+ arp->family = family;
+ memcpy(arp->hwaddr, mac, maclen);
+ if (family == AF_INET)
+ arp->addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr;
+ else
+ memcpy(&arp->addr.addr6, addrp, IN6ADDRSZ);
+ }
+
+ return 1;
+}
+
+/* If in lazy mode, we cache absence of ARP entries. */
+int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
+{
+ struct arp_record *arp, *tmp, **up;
+ int updated = 0;
+
+ again:
+
+ /* If the database is less then INTERVAL old, look in there */
+ if (difftime(now, last) < INTERVAL)
+ {
+ /* addr == NULL -> just make cache up-to-date */
+ if (!addr)
+ return 0;
+
+ for (arp = arps; arp; arp = arp->next)
+ {
+ if (addr->sa.sa_family != arp->family)
+ continue;
+
+ if (arp->family == AF_INET &&
+ arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
+ continue;
+
+ if (arp->family == AF_INET6 &&
+ !IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
+ continue;
+
+ /* Only accept positive entries unless in lazy mode. */
+ if (arp->status != ARP_EMPTY || lazy || updated)
+ {
+ if (mac && arp->hwlen != 0)
+ memcpy(mac, arp->hwaddr, arp->hwlen);
+ return arp->hwlen;
+ }
+ }
+ }
+
+ /* Not found, try the kernel */
+ if (!updated)
+ {
+ updated = 1;
+ last = now;
+
+ /* Mark all non-negative entries */
+ for (arp = arps; arp; arp = arp->next)
+ if (arp->status != ARP_EMPTY)
+ arp->status = ARP_MARK;
+
+ iface_enumerate(AF_UNSPEC, NULL, filter_mac);
+
+ /* Remove all unconfirmed entries to old list. */
+ for (arp = arps, up = &arps; arp; arp = tmp)
+ {
+ tmp = arp->next;
+
+ if (arp->status == ARP_MARK)
+ {
+ *up = arp->next;
+ arp->next = old;
+ old = arp;
+ }
+ else
+ up = &arp->next;
+ }
+
+ goto again;
+ }
+
+ /* record failure, so we don't consult the kernel each time
+ we're asked for this address */
+ if (freelist)
+ {
+ arp = freelist;
+ freelist = freelist->next;
+ }
+ else
+ arp = whine_malloc(sizeof(struct arp_record));
+
+ if (arp)
+ {
+ arp->next = arps;
+ arps = arp;
+ arp->status = ARP_EMPTY;
+ arp->family = addr->sa.sa_family;
+ arp->hwlen = 0;
+
+ if (addr->sa.sa_family == AF_INET)
+ arp->addr.addr4.s_addr = addr->in.sin_addr.s_addr;
+ else
+ memcpy(&arp->addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ);
+ }
+
+ return 0;
+}
+
+int do_arp_script_run(void)
+{
+ struct arp_record *arp;
+
+ /* Notify any which went, then move to free list */
+ if (old)
+ {
+#ifdef HAVE_SCRIPT
+ if (option_bool(OPT_SCRIPT_ARP))
+ queue_arp(ACTION_ARP_DEL, old->hwaddr, old->hwlen, old->family, &old->addr);
+#endif
+ arp = old;
+ old = arp->next;
+ arp->next = freelist;
+ freelist = arp;
+ return 1;
+ }
+
+ for (arp = arps; arp; arp = arp->next)
+ if (arp->status == ARP_NEW)
+ {
+#ifdef HAVE_SCRIPT
+ if (option_bool(OPT_SCRIPT_ARP))
+ queue_arp(ACTION_ARP, arp->hwaddr, arp->hwlen, arp->family, &arp->addr);
+#endif
+ arp->status = ARP_FOUND;
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c b/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c
new file mode 100755
index 0000000..172a4b2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c
@@ -0,0 +1,906 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_AUTH
+
+static struct addrlist *find_addrlist(struct addrlist *list, int flag, union all_addr *addr_u)
+{
+ do {
+ if (!(list->flags & ADDRLIST_IPV6))
+ {
+ struct in_addr netmask, addr = addr_u->addr4;
+
+ if (!(flag & F_IPV4))
+ continue;
+
+ netmask.s_addr = htonl(~(in_addr_t)0 << (32 - list->prefixlen));
+
+ if (is_same_net(addr, list->addr.addr4, netmask))
+ return list;
+ }
+ else if (is_same_net6(&(addr_u->addr6), &list->addr.addr6, list->prefixlen))
+ return list;
+
+ } while ((list = list->next));
+
+ return NULL;
+}
+
+static struct addrlist *find_subnet(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+ if (!zone->subnet)
+ return NULL;
+
+ return find_addrlist(zone->subnet, flag, addr_u);
+}
+
+static struct addrlist *find_exclude(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+ if (!zone->exclude)
+ return NULL;
+
+ return find_addrlist(zone->exclude, flag, addr_u);
+}
+
+static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+ if (find_exclude(zone, flag, addr_u))
+ return 0;
+
+ /* No subnets specified, no filter */
+ if (!zone->subnet)
+ return 1;
+
+ return find_subnet(zone, flag, addr_u) != NULL;
+}
+
+int in_zone(struct auth_zone *zone, char *name, char **cut)
+{
+ size_t namelen = strlen(name);
+ size_t domainlen = strlen(zone->domain);
+
+ if (cut)
+ *cut = NULL;
+
+ if (namelen >= domainlen &&
+ hostname_isequal(zone->domain, &name[namelen - domainlen]))
+ {
+
+ if (namelen == domainlen)
+ return 1;
+
+ if (name[namelen - domainlen - 1] == '.')
+ {
+ if (cut)
+ *cut = &name[namelen - domainlen - 1];
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr,
+ int local_query, int do_bit, int have_pseudoheader)
+{
+ char *name = daemon->namebuff;
+ unsigned char *p, *ansp;
+ int qtype, qclass, rc;
+ int nameoffset, axfroffset = 0;
+ int q, anscount = 0, authcount = 0;
+ struct crec *crecp;
+ int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0, out_of_zone = 0;
+ struct auth_zone *zone = NULL;
+ struct addrlist *subnet = NULL;
+ char *cut;
+ struct mx_srv_record *rec, *move, **up;
+ struct txt_record *txt;
+ struct interface_name *intr;
+ struct naptr *na;
+ union all_addr addr;
+ struct cname *a, *candidate;
+ unsigned int wclen;
+
+ if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
+ return 0;
+
+ /* 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--)
+ {
+ unsigned int flag = 0;
+ int found = 0;
+ int cname_wildcard = 0;
+
+ /* 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);
+
+ if (qclass != C_IN)
+ {
+ auth = 0;
+ out_of_zone = 1;
+ continue;
+ }
+
+ if ((qtype == T_PTR || qtype == T_SOA || qtype == T_NS) &&
+ (flag = in_arpa_name_2_addr(name, &addr)) &&
+ !local_query)
+ {
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ if ((subnet = find_subnet(zone, flag, &addr)))
+ break;
+
+ if (!zone)
+ {
+ out_of_zone = 1;
+ auth = 0;
+ continue;
+ }
+ else if (qtype == T_SOA)
+ soa = 1, found = 1;
+ else if (qtype == T_NS)
+ ns = 1, found = 1;
+ }
+
+ if (qtype == T_PTR && flag)
+ {
+ intr = NULL;
+
+ if (flag == F_IPV4)
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ struct addrlist *addrlist;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr)
+ break;
+
+ if (addrlist)
+ break;
+ else
+ while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+ intr = intr->next;
+ }
+ else if (flag == F_IPV6)
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ struct addrlist *addrlist;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6))
+ break;
+
+ if (addrlist)
+ break;
+ else
+ while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+ intr = intr->next;
+ }
+
+ if (intr)
+ {
+ if (local_query || in_zone(zone, intr->name, NULL))
+ {
+ found = 1;
+ log_query(flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL);
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL,
+ T_PTR, C_IN, "d", intr->name))
+ anscount++;
+ }
+ }
+
+ if ((crecp = cache_find_by_addr(NULL, &addr, now, flag)))
+ do {
+ strcpy(name, cache_get_name(crecp));
+
+ if (crecp->flags & F_DHCP && !option_bool(OPT_DHCP_FQDN))
+ {
+ char *p = strchr(name, '.');
+ if (p)
+ *p = 0; /* must be bare name */
+
+ /* add external domain */
+ if (zone)
+ {
+ strcat(name, ".");
+ strcat(name, zone->domain);
+ }
+ log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid));
+ found = 1;
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL,
+ T_PTR, C_IN, "d", name))
+ anscount++;
+ }
+ else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || in_zone(zone, name, NULL)))
+ {
+ log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid));
+ found = 1;
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL,
+ T_PTR, C_IN, "d", name))
+ anscount++;
+ }
+ else
+ continue;
+
+ } while ((crecp = cache_find_by_addr(crecp, &addr, now, flag)));
+
+ if (!found && is_rev_synth(flag, &addr, name) && (local_query || in_zone(zone, name, NULL)))
+ {
+ log_query(F_CONFIG | F_REVERSE | flag, name, &addr, NULL);
+ found = 1;
+
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL,
+ T_PTR, C_IN, "d", name))
+ anscount++;
+ }
+
+ if (found)
+ nxdomain = 0;
+ else
+ log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | (auth ? F_AUTH : 0), NULL, &addr, NULL);
+
+ continue;
+ }
+
+ cname_restart:
+ if (found)
+ /* NS and SOA .arpa requests have set found above. */
+ cut = NULL;
+ else
+ {
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ if (in_zone(zone, name, &cut))
+ break;
+
+ if (!zone)
+ {
+ out_of_zone = 1;
+ auth = 0;
+ continue;
+ }
+ }
+
+ for (rec = daemon->mxnames; rec; rec = rec->next)
+ if (!rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
+ {
+ nxdomain = 0;
+
+ if (rc == 2 && qtype == T_MX)
+ {
+ found = 1;
+ log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+ NULL, T_MX, C_IN, "sd", rec->weight, rec->target))
+ anscount++;
+ }
+ }
+
+ for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next)
+ if (rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
+ {
+ nxdomain = 0;
+
+ if (rc == 2 && qtype == T_SRV)
+ {
+ found = 1;
+ log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>");
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+ NULL, T_SRV, C_IN, "sssd",
+ rec->priority, rec->weight, rec->srvport, rec->target))
+
+ anscount++;
+ }
+
+ /* 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;
+ }
+
+ for (txt = daemon->rr; txt; txt = txt->next)
+ if ((rc = hostname_issubdomain(name, txt->name)))
+ {
+ nxdomain = 0;
+ if (rc == 2 && txt->class == qtype)
+ {
+ found = 1;
+ log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, txt->class));
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+ NULL, txt->class, C_IN, "t", txt->len, txt->txt))
+ anscount++;
+ }
+ }
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ if (txt->class == C_IN && (rc = hostname_issubdomain(name, txt->name)))
+ {
+ nxdomain = 0;
+ if (rc == 2 && qtype == T_TXT)
+ {
+ found = 1;
+ log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+ NULL, T_TXT, C_IN, "t", txt->len, txt->txt))
+ anscount++;
+ }
+ }
+
+ for (na = daemon->naptr; na; na = na->next)
+ if ((rc = hostname_issubdomain(name, na->name)))
+ {
+ nxdomain = 0;
+ if (rc == 2 && qtype == T_NAPTR)
+ {
+ found = 1;
+ log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>");
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+ NULL, T_NAPTR, C_IN, "sszzzd",
+ na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
+ anscount++;
+ }
+ }
+
+ if (qtype == T_A)
+ flag = F_IPV4;
+
+ if (qtype == T_AAAA)
+ flag = F_IPV6;
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if ((rc = hostname_issubdomain(name, intr->name)))
+ {
+ struct addrlist *addrlist;
+
+ nxdomain = 0;
+
+ if (rc == 2 && flag)
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == qtype &&
+ (local_query || filter_zone(zone, flag, &addrlist->addr)))
+ {
+ if (addrlist->flags & ADDRLIST_REVONLY)
+ continue;
+
+ found = 1;
+ log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL);
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL, qtype, C_IN,
+ qtype == T_A ? "4" : "6", &addrlist->addr))
+ anscount++;
+ }
+ }
+
+ if (!found && is_name_synthetic(flag, name, &addr) )
+ {
+ found = 1;
+ nxdomain = 0;
+
+ log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL);
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL, qtype, C_IN, qtype == T_A ? "4" : "6", &addr))
+ anscount++;
+ }
+
+ if (!cut)
+ {
+ nxdomain = 0;
+
+ if (qtype == T_SOA)
+ {
+ auth = soa = 1; /* inhibits auth section */
+ found = 1;
+ log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>");
+ }
+ else if (qtype == T_AXFR)
+ {
+ struct iname *peers;
+
+ if (peer_addr->sa.sa_family == AF_INET)
+ peer_addr->in.sin_port = 0;
+ else
+ {
+ peer_addr->in6.sin6_port = 0;
+ peer_addr->in6.sin6_scope_id = 0;
+ }
+
+ for (peers = daemon->auth_peers; peers; peers = peers->next)
+ if (sockaddr_isequal(peer_addr, &peers->addr))
+ break;
+
+ /* Refuse all AXFR unless --auth-sec-servers or auth-peers is set */
+ if ((!daemon->secondary_forward_server && !daemon->auth_peers) ||
+ (daemon->auth_peers && !peers))
+ {
+ if (peer_addr->sa.sa_family == AF_INET)
+ inet_ntop(AF_INET, &peer_addr->in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ inet_ntop(AF_INET6, &peer_addr->in6.sin6_addr, daemon->addrbuff, ADDRSTRLEN);
+
+ my_syslog(LOG_WARNING, _("ignoring zone transfer request from %s"), daemon->addrbuff);
+ return 0;
+ }
+
+ auth = 1;
+ soa = 1; /* inhibits auth section */
+ ns = 1; /* ensure we include NS records! */
+ axfr = 1;
+ found = 1;
+ axfroffset = nameoffset;
+ log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<AXFR>");
+ }
+ else if (qtype == T_NS)
+ {
+ auth = 1;
+ ns = 1; /* inhibits auth section */
+ found = 1;
+ log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>");
+ }
+ }
+
+ if (!option_bool(OPT_DHCP_FQDN) && cut)
+ {
+ *cut = 0; /* remove domain part */
+
+ if (!strchr(name, '.') && (crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
+ {
+ if (crecp->flags & F_DHCP)
+ do
+ {
+ nxdomain = 0;
+ if ((crecp->flags & flag) &&
+ (local_query || filter_zone(zone, flag, &(crecp->addr))))
+ {
+ *cut = '.'; /* restore domain part */
+ log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
+ *cut = 0; /* remove domain part */
+ found = 1;
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL, qtype, C_IN,
+ qtype == T_A ? "4" : "6", &crecp->addr))
+ anscount++;
+ }
+ } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6)));
+ }
+
+ *cut = '.'; /* restore domain part */
+ }
+
+ if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
+ {
+ if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
+ do
+ {
+ nxdomain = 0;
+ if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
+ {
+ log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
+ found = 1;
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, NULL, qtype, C_IN,
+ qtype == T_A ? "4" : "6", &crecp->addr))
+ anscount++;
+ }
+ } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6)));
+ }
+
+ /* Only supply CNAME if no record for any type is known. */
+ if (nxdomain)
+ {
+ /* Check for possible wildcard match against *.domain
+ return length of match, to get longest.
+ Note that if return length of wildcard section, so
+ we match b.simon to _both_ *.simon and b.simon
+ but return a longer (better) match to b.simon.
+ */
+ for (wclen = 0, candidate = NULL, a = daemon->cnames; a; a = a->next)
+ if (a->alias[0] == '*')
+ {
+ char *test = name;
+
+ while ((test = strchr(test+1, '.')))
+ {
+ if (hostname_isequal(test, &(a->alias[1])))
+ {
+ if (strlen(test) > wclen && !cname_wildcard)
+ {
+ wclen = strlen(test);
+ candidate = a;
+ cname_wildcard = 1;
+ }
+ break;
+ }
+ }
+
+ }
+ else if (hostname_isequal(a->alias, name) && strlen(a->alias) > wclen)
+ {
+ /* Simple case, no wildcard */
+ wclen = strlen(a->alias);
+ candidate = a;
+ }
+
+ if (candidate)
+ {
+ log_query(F_CONFIG | F_CNAME, name, NULL, NULL);
+ strcpy(name, candidate->target);
+ if (!strchr(name, '.'))
+ {
+ strcat(name, ".");
+ strcat(name, zone->domain);
+ }
+ found = 1;
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->auth_ttl, &nameoffset,
+ T_CNAME, C_IN, "d", name))
+ anscount++;
+
+ goto cname_restart;
+ }
+ else if (cache_find_non_terminal(name, now))
+ nxdomain = 0;
+
+ log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL);
+ }
+
+ }
+
+ /* Add auth section */
+ if (auth && zone)
+ {
+ char *authname;
+ int newoffset, offset = 0;
+
+ if (!subnet)
+ authname = zone->domain;
+ else
+ {
+ /* handle NS and SOA for PTR records */
+
+ authname = name;
+
+ if (!(subnet->flags & ADDRLIST_IPV6))
+ {
+ in_addr_t a = ntohl(subnet->addr.addr4.s_addr) >> 8;
+ char *p = name;
+
+ if (subnet->prefixlen >= 24)
+ p += sprintf(p, "%u.", a & 0xff);
+ a = a >> 8;
+ if (subnet->prefixlen >= 16 )
+ p += sprintf(p, "%u.", a & 0xff);
+ a = a >> 8;
+ p += sprintf(p, "%u.in-addr.arpa", a & 0xff);
+
+ }
+ else
+ {
+ char *p = name;
+ int i;
+
+ for (i = subnet->prefixlen-1; i >= 0; i -= 4)
+ {
+ int dig = ((unsigned char *)&subnet->addr.addr6)[i>>3];
+ p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
+ }
+ p += sprintf(p, "ip6.arpa");
+
+ }
+ }
+
+ /* handle NS and SOA in auth section or for explicit queries */
+ newoffset = ansp - (unsigned char *)header;
+ if (((anscount == 0 && !ns) || soa) &&
+ add_resource_record(header, limit, &trunc, 0, &ansp,
+ daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
+ authname, daemon->authserver, daemon->hostmaster,
+ daemon->soa_sn, daemon->soa_refresh,
+ daemon->soa_retry, daemon->soa_expiry,
+ daemon->auth_ttl))
+ {
+ offset = newoffset;
+ if (soa)
+ anscount++;
+ else
+ authcount++;
+ }
+
+ if (anscount != 0 || ns)
+ {
+ struct name_list *secondary;
+
+ /* Only include the machine running dnsmasq if it's acting as an auth server */
+ if (daemon->authinterface)
+ {
+ newoffset = ansp - (unsigned char *)header;
+ if (add_resource_record(header, limit, &trunc, -offset, &ansp,
+ daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver))
+ {
+ if (offset == 0)
+ offset = newoffset;
+ if (ns)
+ anscount++;
+ else
+ authcount++;
+ }
+ }
+
+ if (!subnet)
+ for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next)
+ if (add_resource_record(header, limit, &trunc, offset, &ansp,
+ daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name))
+ {
+ if (ns)
+ anscount++;
+ else
+ authcount++;
+ }
+ }
+
+ if (axfr)
+ {
+ for (rec = daemon->mxnames; rec; rec = rec->next)
+ if (in_zone(zone, rec->name, &cut))
+ {
+ if (cut)
+ *cut = 0;
+
+ if (rec->issrv)
+ {
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+ NULL, T_SRV, C_IN, "sssd", cut ? rec->name : NULL,
+ rec->priority, rec->weight, rec->srvport, rec->target))
+
+ anscount++;
+ }
+ else
+ {
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+ NULL, T_MX, C_IN, "sd", cut ? rec->name : NULL, rec->weight, rec->target))
+ anscount++;
+ }
+
+ /* restore config data */
+ if (cut)
+ *cut = '.';
+ }
+
+ for (txt = daemon->rr; txt; txt = txt->next)
+ if (in_zone(zone, txt->name, &cut))
+ {
+ if (cut)
+ *cut = 0;
+
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+ NULL, txt->class, C_IN, "t", cut ? txt->name : NULL, txt->len, txt->txt))
+ anscount++;
+
+ /* restore config data */
+ if (cut)
+ *cut = '.';
+ }
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ if (txt->class == C_IN && in_zone(zone, txt->name, &cut))
+ {
+ if (cut)
+ *cut = 0;
+
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+ NULL, T_TXT, C_IN, "t", cut ? txt->name : NULL, txt->len, txt->txt))
+ anscount++;
+
+ /* restore config data */
+ if (cut)
+ *cut = '.';
+ }
+
+ for (na = daemon->naptr; na; na = na->next)
+ if (in_zone(zone, na->name, &cut))
+ {
+ if (cut)
+ *cut = 0;
+
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+ NULL, T_NAPTR, C_IN, "sszzzd", cut ? na->name : NULL,
+ na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
+ anscount++;
+
+ /* restore config data */
+ if (cut)
+ *cut = '.';
+ }
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (in_zone(zone, intr->name, &cut))
+ {
+ struct addrlist *addrlist;
+
+ if (cut)
+ *cut = 0;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (!(addrlist->flags & ADDRLIST_IPV6) &&
+ (local_query || filter_zone(zone, F_IPV4, &addrlist->addr)) &&
+ add_resource_record(header, limit, &trunc, -axfroffset, &ansp,
+ daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr))
+ anscount++;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if ((addrlist->flags & ADDRLIST_IPV6) &&
+ (local_query || filter_zone(zone, F_IPV6, &addrlist->addr)) &&
+ add_resource_record(header, limit, &trunc, -axfroffset, &ansp,
+ daemon->auth_ttl, NULL, T_AAAA, C_IN, "6", cut ? intr->name : NULL, &addrlist->addr))
+ anscount++;
+
+ /* restore config data */
+ if (cut)
+ *cut = '.';
+ }
+
+ for (a = daemon->cnames; a; a = a->next)
+ if (in_zone(zone, a->alias, &cut))
+ {
+ strcpy(name, a->target);
+ if (!strchr(name, '.'))
+ {
+ strcat(name, ".");
+ strcat(name, zone->domain);
+ }
+
+ if (cut)
+ *cut = 0;
+
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp,
+ daemon->auth_ttl, NULL,
+ T_CNAME, C_IN, "d", cut ? a->alias : NULL, name))
+ anscount++;
+ }
+
+ cache_enumerate(1);
+ while ((crecp = cache_enumerate(0)))
+ {
+ if ((crecp->flags & (F_IPV4 | F_IPV6)) &&
+ !(crecp->flags & (F_NEG | F_NXDOMAIN)) &&
+ (crecp->flags & F_FORWARD))
+ {
+ if ((crecp->flags & F_DHCP) && !option_bool(OPT_DHCP_FQDN))
+ {
+ char *cache_name = cache_get_name(crecp);
+ if (!strchr(cache_name, '.') &&
+ (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))) &&
+ add_resource_record(header, limit, &trunc, -axfroffset, &ansp,
+ daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN,
+ (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr))
+ anscount++;
+ }
+
+ if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
+ {
+ strcpy(name, cache_get_name(crecp));
+ if (in_zone(zone, name, &cut) &&
+ (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))))
+ {
+ if (cut)
+ *cut = 0;
+
+ if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp,
+ daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN,
+ (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr))
+ anscount++;
+ }
+ }
+ }
+ }
+
+ /* repeat SOA as last record */
+ if (add_resource_record(header, limit, &trunc, axfroffset, &ansp,
+ daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
+ daemon->authserver, daemon->hostmaster,
+ daemon->soa_sn, daemon->soa_refresh,
+ daemon->soa_retry, daemon->soa_expiry,
+ daemon->auth_ttl))
+ anscount++;
+
+ }
+
+ }
+
+ /* 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;
+
+ if (local_query)
+ {
+ /* set RA flag */
+ header->hb4 |= HB4_RA;
+ }
+ else
+ {
+ /* clear RA flag */
+ header->hb4 &= ~HB4_RA;
+ }
+
+ /* data is never DNSSEC signed. */
+ header->hb4 &= ~HB4_AD;
+
+ /* authoritative */
+ if (auth)
+ header->hb3 |= HB3_AA;
+
+ /* truncation */
+ if (trunc)
+ header->hb3 |= HB3_TC;
+
+ if ((auth || local_query) && nxdomain)
+ SET_RCODE(header, NXDOMAIN);
+ else
+ SET_RCODE(header, NOERROR); /* no error */
+
+ header->ancount = htons(anscount);
+ header->nscount = htons(authcount);
+ header->arcount = htons(0);
+
+ if (!local_query && out_of_zone)
+ {
+ SET_RCODE(header, REFUSED);
+ header->ancount = htons(0);
+ header->nscount = htons(0);
+ addr.log.rcode = REFUSED;
+ addr.log.ede = EDE_NOT_AUTH;
+ log_query(F_UPSTREAM | F_RCODE, "error", &addr, NULL);
+ return resize_packet(header, ansp - (unsigned char *)header, NULL, 0);
+ }
+
+ /* Advertise our packet size limit in our reply */
+ if (have_pseudoheader)
+ return add_pseudoheader(header, ansp - (unsigned char *)header, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+
+ return ansp - (unsigned char *)header;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c b/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c
new file mode 100755
index 0000000..f7740b5
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c
@@ -0,0 +1,176 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 blockdata *keyblock_free;
+static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
+
+static void blockdata_expand(int n)
+{
+ struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
+
+ if (new)
+ {
+ int i;
+
+ new[n-1].next = keyblock_free;
+ keyblock_free = new;
+
+ for (i = 0; i < n - 1; i++)
+ new[i].next = &new[i+1];
+
+ blockdata_alloced += n;
+ }
+}
+
+/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */
+void blockdata_init(void)
+{
+ keyblock_free = NULL;
+ blockdata_alloced = 0;
+ blockdata_count = 0;
+ blockdata_hwm = 0;
+
+ /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */
+ if (option_bool(OPT_DNSSEC_VALID))
+ blockdata_expand(daemon->cachesize);
+}
+
+void blockdata_report(void)
+{
+ my_syslog(LOG_INFO, _("pool memory in use %u, max %u, allocated %u"),
+ blockdata_count * sizeof(struct blockdata),
+ blockdata_hwm * sizeof(struct blockdata),
+ blockdata_alloced * sizeof(struct blockdata));
+}
+
+static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
+{
+ struct blockdata *block, *ret = NULL;
+ struct blockdata **prev = &ret;
+ size_t blen;
+
+ while (len > 0)
+ {
+ if (!keyblock_free)
+ blockdata_expand(50);
+
+ if (keyblock_free)
+ {
+ block = keyblock_free;
+ keyblock_free = block->next;
+ blockdata_count++;
+ }
+ else
+ {
+ /* failed to alloc, free partial chain */
+ blockdata_free(ret);
+ return NULL;
+ }
+
+ if (blockdata_hwm < blockdata_count)
+ blockdata_hwm = blockdata_count;
+
+ blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ if (data)
+ {
+ memcpy(block->key, data, blen);
+ data += blen;
+ }
+ else if (!read_write(fd, block->key, blen, 1))
+ {
+ /* failed read free partial chain */
+ blockdata_free(ret);
+ return NULL;
+ }
+ len -= blen;
+ *prev = block;
+ prev = &block->next;
+ block->next = NULL;
+ }
+
+ return ret;
+}
+
+struct blockdata *blockdata_alloc(char *data, size_t len)
+{
+ return blockdata_alloc_real(0, data, len);
+}
+
+void blockdata_free(struct blockdata *blocks)
+{
+ struct blockdata *tmp;
+
+ if (blocks)
+ {
+ for (tmp = blocks; tmp->next; tmp = tmp->next)
+ blockdata_count--;
+ tmp->next = keyblock_free;
+ keyblock_free = blocks;
+ blockdata_count--;
+ }
+}
+
+/* if data == NULL, return pointer to static block of sufficient size */
+void *blockdata_retrieve(struct blockdata *block, size_t len, void *data)
+{
+ size_t blen;
+ struct blockdata *b;
+ void *new, *d;
+
+ static unsigned int buff_len = 0;
+ static unsigned char *buff = NULL;
+
+ if (!data)
+ {
+ if (len > buff_len)
+ {
+ if (!(new = whine_malloc(len)))
+ return NULL;
+ if (buff)
+ free(buff);
+ buff = new;
+ }
+ data = buff;
+ }
+
+ for (d = data, b = block; len > 0 && b; b = b->next)
+ {
+ blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ memcpy(d, b->key, blen);
+ d += blen;
+ len -= blen;
+ }
+
+ return data;
+}
+
+
+void blockdata_write(struct blockdata *block, size_t len, int fd)
+{
+ for (; len > 0 && block; block = block->next)
+ {
+ size_t blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ read_write(fd, block->key, blen, 0);
+ len -= blen;
+ }
+}
+
+struct blockdata *blockdata_read(int fd, size_t len)
+{
+ return blockdata_alloc_real(fd, NULL, len);
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c b/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c
new file mode 100755
index 0000000..15c42fc
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c
@@ -0,0 +1,442 @@
+/* dnsmasq is Copyright (c) 2000-2021 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)
+#include <ifaddrs.h>
+
+#include <sys/param.h>
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+#include <sys/sysctl.h>
+#endif
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/if_ether.h>
+#if defined(__FreeBSD__)
+# include <net/if_var.h>
+#endif
+#include <netinet/in_var.h>
+#include <netinet6/in6_var.h>
+
+#ifndef SA_SIZE
+#define SA_SIZE(sa) \
+ ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \
+ sizeof(long) : \
+ 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) )
+#endif
+
+#ifdef HAVE_BSD_NETWORK
+static int del_family = 0;
+static union all_addr del_addr;
+#endif
+
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+
+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;
+ struct iovec buff;
+ int rc;
+
+ buff.iov_base = NULL;
+ buff.iov_len = 0;
+
+ 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(&buff, needed))
+ return 0;
+ if ((rc = sysctl(mib, 6, buff.iov_base, &needed, NULL, 0)) == 0 ||
+ errno != ENOMEM)
+ break;
+ needed += needed / 8;
+ }
+ if (rc == -1)
+ return 0;
+
+ for (next = buff.iov_base ; next < (char *)buff.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 /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */
+
+
+int iface_enumerate(int family, void *parm, int (*callback)())
+{
+ struct ifaddrs *head, *addrs;
+ int errsave, fd = -1, ret = 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
+
+ /* AF_LINK doesn't exist in Linux, so we can't use it in our API */
+ if (family == AF_LOCAL)
+ family = AF_LINK;
+
+ if (getifaddrs(&head) == -1)
+ return 0;
+
+#if defined(HAVE_BSD_NETWORK)
+ if (family == AF_INET6)
+ fd = socket(PF_INET6, SOCK_DGRAM, 0);
+#endif
+
+ for (addrs = head; addrs; addrs = addrs->ifa_next)
+ {
+ if (addrs->ifa_addr->sa_family == family)
+ {
+ int iface_index = if_nametoindex(addrs->ifa_name);
+
+ if (iface_index == 0 || !addrs->ifa_addr ||
+ (!addrs->ifa_netmask && family != AF_LINK))
+ continue;
+
+ if (family == AF_INET)
+ {
+ struct in_addr addr, netmask, broadcast;
+ addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
+#ifdef HAVE_BSD_NETWORK
+ if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
+ continue;
+#endif
+ netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
+ if (addrs->ifa_broadaddr)
+ broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr;
+ else
+ broadcast.s_addr = 0;
+ if (!((*callback)(addr, iface_index, NULL, netmask, broadcast, parm)))
+ goto err;
+ }
+ else if (family == AF_INET6)
+ {
+ struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
+ unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
+ int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
+ int i, j, prefix = 0;
+ u32 valid = 0xffffffff, preferred = 0xffffffff;
+ int flags = 0;
+#ifdef HAVE_BSD_NETWORK
+ if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
+ continue;
+#endif
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+ struct in6_ifreq ifr6;
+
+ memset(&ifr6, 0, sizeof(ifr6));
+ safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
+
+ ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
+ if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
+ {
+ if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
+ flags |= IFACE_TENTATIVE;
+
+ if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
+ flags |= IFACE_DEPRECATED;
+
+#ifdef IN6_IFF_TEMPORARY
+ if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
+ flags |= IFACE_PERMANENT;
+#endif
+
+#ifdef IN6_IFF_PRIVACY
+ if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
+ flags |= IFACE_PERMANENT;
+#endif
+ }
+
+ ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
+ if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
+ {
+ valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
+ preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
+ }
+#endif
+
+ for (i = 0; i < IN6ADDRSZ; i++, prefix += 8)
+ if (netmask[i] != 0xff)
+ break;
+
+ if (i != IN6ADDRSZ && netmask[i])
+ for (j = 7; j > 0; j--, prefix++)
+ if ((netmask[i] & (1 << j)) == 0)
+ break;
+
+ /* 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, prefix, scope_id, iface_index, flags,
+ (int) preferred, (int)valid, parm)))
+ goto err;
+ }
+
+#ifdef HAVE_DHCP6
+ else if (family == AF_LINK)
+ {
+ /* Assume ethernet again here */
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
+ if (sdl->sdl_alen != 0 &&
+ !((*callback)(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm)))
+ goto err;
+ }
+#endif
+ }
+ }
+
+ ret = 1;
+
+ err:
+ errsave = errno;
+ freeifaddrs(head);
+ if (fd != -1)
+ close(fd);
+ errno = errsave;
+
+ return ret;
+}
+#endif /* defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK) */
+
+
+#if defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP)
+#include <net/bpf.h>
+
+void init_bpf(void)
+{
+ int i = 0;
+
+ while (1)
+ {
+ sprintf(daemon->dhcp_buff, "/dev/bpf%d", i++);
+ if ((daemon->dhcp_raw_fd = open(daemon->dhcp_buff, O_RDWR, 0)) != -1)
+ return;
+
+ if (errno != EBUSY)
+ die(_("cannot create DHCP BPF socket: %s"), NULL, EC_BADNET);
+ }
+}
+
+void send_via_bpf(struct dhcp_packet *mess, size_t len,
+ struct in_addr iface_addr, struct ifreq *ifr)
+{
+ /* Hairy stuff, packet either has to go to the
+ net broadcast or the destination can't reply to ARP yet,
+ but we do know the physical address.
+ Build the packet by steam, and send directly, bypassing
+ the kernel IP stack */
+
+ struct ether_header ether;
+ struct ip ip;
+ struct udphdr {
+ u16 uh_sport; /* source port */
+ u16 uh_dport; /* destination port */
+ u16 uh_ulen; /* udp length */
+ u16 uh_sum; /* udp checksum */
+ } udp;
+
+ u32 i, sum;
+ struct iovec iov[4];
+
+ /* Only know how to do ethernet on *BSD */
+ if (mess->htype != ARPHRD_ETHER || mess->hlen != ETHER_ADDR_LEN)
+ {
+ my_syslog(MS_DHCP | LOG_WARNING, _("DHCP request for unsupported hardware type (%d) received on %s"),
+ mess->htype, ifr->ifr_name);
+ return;
+ }
+
+ ifr->ifr_addr.sa_family = AF_LINK;
+ if (ioctl(daemon->dhcpfd, SIOCGIFADDR, ifr) < 0)
+ return;
+
+ memcpy(ether.ether_shost, LLADDR((struct sockaddr_dl *)&ifr->ifr_addr), ETHER_ADDR_LEN);
+ ether.ether_type = htons(ETHERTYPE_IP);
+
+ if (ntohs(mess->flags) & 0x8000)
+ {
+ memset(ether.ether_dhost, 255, ETHER_ADDR_LEN);
+ ip.ip_dst.s_addr = INADDR_BROADCAST;
+ }
+ else
+ {
+ memcpy(ether.ether_dhost, mess->chaddr, ETHER_ADDR_LEN);
+ ip.ip_dst.s_addr = mess->yiaddr.s_addr;
+ }
+
+ ip.ip_p = IPPROTO_UDP;
+ ip.ip_src.s_addr = iface_addr.s_addr;
+ ip.ip_len = htons(sizeof(struct ip) +
+ sizeof(struct udphdr) +
+ len) ;
+ ip.ip_hl = sizeof(struct ip) / 4;
+ ip.ip_v = IPVERSION;
+ ip.ip_tos = 0;
+ ip.ip_id = htons(0);
+ ip.ip_off = htons(0x4000); /* don't fragment */
+ ip.ip_ttl = IPDEFTTL;
+ ip.ip_sum = 0;
+ for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
+ sum += ((u16 *)&ip)[i];
+ while (sum>>16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
+
+ udp.uh_sport = htons(daemon->dhcp_server_port);
+ udp.uh_dport = htons(daemon->dhcp_client_port);
+ if (len & 1)
+ ((char *)mess)[len] = 0; /* for checksum, in case length is odd. */
+ udp.uh_sum = 0;
+ udp.uh_ulen = sum = htons(sizeof(struct udphdr) + len);
+ sum += htons(IPPROTO_UDP);
+ sum += ip.ip_src.s_addr & 0xffff;
+ sum += (ip.ip_src.s_addr >> 16) & 0xffff;
+ sum += ip.ip_dst.s_addr & 0xffff;
+ sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
+ for (i = 0; i < sizeof(struct udphdr)/2; i++)
+ sum += ((u16 *)&udp)[i];
+ for (i = 0; i < (len + 1) / 2; i++)
+ sum += ((u16 *)mess)[i];
+ while (sum>>16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
+
+ ioctl(daemon->dhcp_raw_fd, BIOCSETIF, ifr);
+
+ iov[0].iov_base = ðer;
+ iov[0].iov_len = sizeof(ether);
+ iov[1].iov_base = &ip;
+ iov[1].iov_len = sizeof(ip);
+ iov[2].iov_base = &udp;
+ iov[2].iov_len = sizeof(udp);
+ iov[3].iov_base = mess;
+ iov[3].iov_len = len;
+
+ while (retry_send(writev(daemon->dhcp_raw_fd, iov, 4)));
+}
+
+#endif /* defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP) */
+
+
+#ifdef HAVE_BSD_NETWORK
+
+void route_init(void)
+{
+ /* AF_UNSPEC: all addr families */
+ daemon->routefd = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
+
+ if (daemon->routefd == -1 || !fix_fd(daemon->routefd))
+ die(_("cannot create PF_ROUTE socket: %s"), NULL, EC_BADNET);
+}
+
+void route_sock(void)
+{
+ struct if_msghdr *msg;
+ int rc = recv(daemon->routefd, daemon->packet, daemon->packet_buff_sz, 0);
+
+ if (rc < 4)
+ return;
+
+ msg = (struct if_msghdr *)daemon->packet;
+
+ if (rc < msg->ifm_msglen)
+ return;
+
+ if (msg->ifm_version != RTM_VERSION)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_WARNING, _("Unknown protocol version from route socket"));
+ warned = 1;
+ }
+ }
+ else if (msg->ifm_type == RTM_NEWADDR)
+ {
+ del_family = 0;
+ queue_event(EVENT_NEWADDR);
+ }
+ else if (msg->ifm_type == RTM_DELADDR)
+ {
+ /* There's a race in the kernel, such that if we run iface_enumerate() immediately
+ we get a DELADDR event, the deleted address still appears. Here we store the deleted address
+ in a static variable, and omit it from the set returned by iface_enumerate() */
+ int mask = ((struct ifa_msghdr *)msg)->ifam_addrs;
+ int maskvec[] = { RTA_DST, RTA_GATEWAY, RTA_NETMASK, RTA_GENMASK,
+ RTA_IFP, RTA_IFA, RTA_AUTHOR, RTA_BRD };
+ int of;
+ unsigned int i;
+
+ for (i = 0, of = sizeof(struct ifa_msghdr); of < rc && i < sizeof(maskvec)/sizeof(maskvec[0]); i++)
+ if (mask & maskvec[i])
+ {
+ struct sockaddr *sa = (struct sockaddr *)((char *)msg + of);
+ size_t diff = (sa->sa_len != 0) ? sa->sa_len : sizeof(long);
+
+ if (maskvec[i] == RTA_IFA)
+ {
+ del_family = sa->sa_family;
+ if (del_family == AF_INET)
+ del_addr.addr4 = ((struct sockaddr_in *)sa)->sin_addr;
+ else if (del_family == AF_INET6)
+ del_addr.addr6 = ((struct sockaddr_in6 *)sa)->sin6_addr;
+ else
+ del_family = 0;
+ }
+
+ of += diff;
+ /* round up as needed */
+ if (diff & (sizeof(long) - 1))
+ of += sizeof(long) - (diff & (sizeof(long) - 1));
+ }
+
+ queue_event(EVENT_NEWADDR);
+ }
+}
+
+#endif /* HAVE_BSD_NETWORK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
new file mode 100755
index 0000000..8add610
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
@@ -0,0 +1,2031 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 insert_error;
+static union bigname *big_free = NULL;
+static int bignames_left, hash_size;
+
+static void make_non_terminals(struct crec *source);
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags);
+
+/* 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" },
+ { 29, "LOC" },
+ { 33, "SRV" },
+ { 35, "NAPTR" },
+ { 36, "KX" },
+ { 37, "CERT" },
+ { 38, "A6" },
+ { 39, "DNAME" },
+ { 41, "OPT" },
+ { 43, "DS" },
+ { 46, "RRSIG" },
+ { 47, "NSEC" },
+ { 48, "DNSKEY" },
+ { 50, "NSEC3" },
+ { 51, "NSEC3PARAM" },
+ { 52, "TLSA" },
+ { 53, "SMIMEA" },
+ { 55, "HIP" },
+ { 249, "TKEY" },
+ { 250, "TSIG" },
+ { 251, "IXFR" },
+ { 252, "AXFR" },
+ { 253, "MAILB" },
+ { 254, "MAILA" },
+ { 255, "ANY" },
+ { 257, "CAA" }
+};
+
+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 next_uid(struct crec *crecp)
+{
+ static unsigned int uid = 0;
+
+ if (crecp->uid == UID_NONE)
+ {
+ uid++;
+
+ /* uid == 0 used to indicate CNAME to interface name. */
+ if (uid == UID_NONE)
+ uid++;
+
+ crecp->uid = uid;
+ }
+}
+
+void cache_init(void)
+{
+ struct crec *crecp;
+ int i;
+
+ 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_NONE;
+ }
+ }
+
+ /* 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_blockdata_free(struct crec *crecp)
+{
+ if (!(crecp->flags & F_NEG))
+ {
+ if (crecp->flags & F_SRV)
+ blockdata_free(crecp->addr.srv.target);
+#ifdef HAVE_DNSSEC
+ else if (crecp->flags & F_DNSKEY)
+ blockdata_free(crecp->addr.key.keydata);
+ else if (crecp->flags & F_DS)
+ blockdata_free(crecp->addr.ds.keydata);
+#endif
+ }
+}
+
+static void cache_free(struct crec *crecp)
+{
+ crecp->flags &= ~F_FORWARD;
+ crecp->flags &= ~F_REVERSE;
+ crecp->uid = UID_NONE; /* 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;
+ }
+
+ cache_blockdata_free(crecp);
+}
+
+/* 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;
+}
+
+char *cache_get_cname_target(struct crec *crecp)
+{
+ if (crecp->addr.cname.is_name_ptr)
+ return crecp->addr.cname.target.name;
+ else
+ return cache_get_name(crecp->addr.cname.target.cache);
+}
+
+
+
+struct crec *cache_enumerate(int init)
+{
+ static int bucket;
+ static struct crec *cache;
+
+ if (init)
+ {
+ bucket = 0;
+ cache = NULL;
+ }
+ else if (cache && cache->hash_next)
+ cache = cache->hash_next;
+ else
+ {
+ cache = NULL;
+ while (bucket < hash_size)
+ if ((cache = hash_table[bucket++]))
+ break;
+ }
+
+ return cache;
+}
+
+static int is_outdated_cname_pointer(struct crec *crecp)
+{
+ if (!(crecp->flags & F_CNAME) || crecp->addr.cname.is_name_ptr)
+ return 0;
+
+ /* NB. record may be reused as DS or DNSKEY, where uid is
+ overloaded for something completely different */
+ if (crecp->addr.cname.target.cache &&
+ !(crecp->addr.cname.target.cache->flags & (F_DNSKEY | F_DS)) &&
+ crecp->addr.cname.uid == crecp->addr.cname.target.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 struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now,
+ unsigned int flags, struct crec **target_crec, unsigned int *target_uid)
+{
+ /* 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 a non-NULL pointer
+ to a cache entry 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.
+
+ If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+ This entry will get re-used with the same name, to preserve CNAMEs. */
+
+ struct crec *crecp, **up;
+
+ (void)class;
+
+ if (flags & F_FORWARD)
+ {
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
+ {
+ if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+ {
+ /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+ if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV)) ||
+ (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ return crecp;
+ *up = crecp->hash_next;
+ /* If this record is for the name we're inserting and is the target
+ of a CNAME record. Make the new record for the same name, in the same
+ crec, with the same uid to avoid breaking the existing CNAME. */
+ if (crecp->uid != UID_NONE)
+ {
+ if (target_crec)
+ *target_crec = crecp;
+ if (target_uid)
+ *target_uid = crecp->uid;
+ }
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+
+#ifdef HAVE_DNSSEC
+ /* Deletion has to be class-sensitive for DS and DNSKEY */
+ if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == class)
+ {
+ if (crecp->flags & F_CONFIG)
+ return crecp;
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+#endif
+ }
+
+ if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ continue;
+ }
+
+ up = &crecp->hash_next;
+ }
+ }
+ else
+ {
+ int i;
+ int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ 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 | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+ else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
+ (flags & crecp->flags & F_REVERSE) &&
+ (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
+ memcmp(&crecp->addr, addr, addrlen) == 0)
+ {
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ return NULL;
+}
+
+/* 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, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DNSKEY | F_DS))
+ {
+ /* The DNSSEC validation process works by getting needed records into the
+ cache, then retrying the validation until they are all in place.
+ This can be messed up by very short TTLs, and _really_ messed up by
+ zero TTLs, so we force the TTL to be at least long enough to do a validation.
+ Ideally, we should use some kind of reference counting so that records are
+ locked until the validation that asked for them is complete, but this
+ is much easier, and just as effective. */
+ if (ttl < DNSSEC_MIN_TTL)
+ ttl = DNSSEC_MIN_TTL;
+ }
+ else
+#endif
+ {
+ if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
+ ttl = daemon->max_cache_ttl;
+ if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
+ ttl = daemon->min_cache_ttl;
+ }
+
+ return really_insert(name, addr, class, now, ttl, flags);
+}
+
+
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+ struct crec *new, *target_crec = NULL;
+ union bigname *big_name = NULL;
+ int freed_all = flags & F_REVERSE;
+ int free_avail = 0;
+ unsigned int target_uid;
+
+ /* if previous insertion failed give up now. */
+ if (insert_error)
+ return NULL;
+
+ /* we don't cache zero-TTL records. */
+ if (ttl == 0)
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* First remove any expired entries and entries for the name/address we
+ are currently inserting. */
+ if ((new = cache_scan_free(name, addr, class, now, flags, &target_crec, &target_uid)))
+ {
+ /* We're trying to insert a record over one from
+ /etc/hosts or DHCP, or other config. If the
+ existing record is for an A or AAAA or CNAME and
+ the record we're trying to insert is the same,
+ just drop the insert, but don't error the whole process. */
+ if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD) && addr)
+ {
+ if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
+ new->addr.addr4.s_addr == addr->addr4.s_addr)
+ return new;
+ else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
+ IN6_ARE_ADDR_EQUAL(&new->addr.addr6, &addr->addr6))
+ return new;
+ }
+
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Now get a cache entry from the end of the LRU list */
+ if (!target_crec)
+ while (1) {
+ if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Free entry at end of LRU list, use it. */
+ if (!(new->flags & (F_FORWARD | F_REVERSE)))
+ break;
+
+ /* 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 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)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_ERR, _("Internal error in cache."));
+ warned = 1;
+ }
+ insert_error = 1;
+ return NULL;
+ }
+
+ if (freed_all)
+ {
+ /* For DNSSEC records, uid holds class. */
+ free_avail = 1; /* Must be free space now. */
+ cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL);
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++;
+ }
+ else
+ {
+ cache_scan_free(NULL, NULL, class, now, 0, NULL, NULL);
+ freed_all = 1;
+ }
+ }
+
+ /* Check if we need to and can allocate extra memory for a long name.
+ If that fails, give up now, always succeed for DNSSEC records. */
+ if (name && (strlen(name) > SMALLDNAME-1))
+ {
+ if (big_free)
+ {
+ big_name = big_free;
+ big_free = big_free->next;
+ }
+ else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+ !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+ {
+ insert_error = 1;
+ return NULL;
+ }
+ else if (bignames_left != 0)
+ bignames_left--;
+
+ }
+
+ /* If we freed a cache entry for our name which was a CNAME target, use that.
+ and preserve the uid, so that existing CNAMES are not broken. */
+ if (target_crec)
+ {
+ new = target_crec;
+ new->uid = target_uid;
+ }
+
+ /* Got the rest: finally grab entry. */
+ cache_unlink(new);
+
+ 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;
+
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DS | F_DNSKEY))
+ new->uid = class;
+#endif
+
+ if (addr)
+ new->addr = *addr;
+
+ 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);
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED]++;
+
+ /* If we're a child process, send this cache entry up the pipe to the master.
+ The marshalling process is rather nasty. */
+ if (daemon->pipe_to_parent != -1)
+ {
+ char *name = cache_get_name(new_chain);
+ ssize_t m = strlen(name);
+ unsigned int flags = new_chain->flags;
+#ifdef HAVE_DNSSEC
+ u16 class = new_chain->uid;
+#endif
+
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
+
+ if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+ if (flags & F_SRV)
+ {
+ /* A negative SRV entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG))
+ blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent);
+ }
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
+ }
+ else if (flags & F_DS)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ /* A negative DS entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG))
+ blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
+ }
+#endif
+ }
+ }
+
+ new_chain = tmp;
+ }
+
+ /* signal end of cache insert in master process */
+ if (daemon->pipe_to_parent != -1)
+ {
+ ssize_t m = -1;
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ }
+
+ new_chain = NULL;
+}
+
+
+/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
+int cache_recv_insert(time_t now, int fd)
+{
+ ssize_t m;
+ union all_addr addr;
+ unsigned long ttl;
+ time_t ttd;
+ unsigned int flags;
+ struct crec *crecp = NULL;
+
+ cache_start_insert();
+
+ while(1)
+ {
+
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+
+ if (m == -1)
+ {
+ cache_end_insert();
+ return 1;
+ }
+
+ if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
+ !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
+ !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1))
+ return 0;
+
+ daemon->namebuff[m] = 0;
+
+ ttl = difftime(ttd, now);
+
+ if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+ {
+ unsigned short class = C_IN;
+
+ if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+ return 0;
+
+ if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen)))
+ return 0;
+
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
+ return 0;
+ }
+ else if (flags & F_DS)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
+ return 0;
+ }
+#endif
+
+ crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
+ }
+ else if (flags & F_CNAME)
+ {
+ struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
+ /* This relies on the fact that the target of a CNAME immediately precedes
+ it because of the order of extraction in extract_addresses, and
+ the order reversal on the new_chain. */
+ if (newc)
+ {
+ newc->addr.cname.is_name_ptr = 0;
+
+ if (!crecp)
+ newc->addr.cname.target.cache = NULL;
+ else
+ {
+ next_uid(crecp);
+ newc->addr.cname.target.cache = crecp;
+ newc->addr.cname.uid = crecp->uid;
+ }
+ }
+ }
+ }
+}
+
+int cache_find_non_terminal(char *name, time_t now)
+{
+ struct crec *crecp;
+
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ !is_expired(now, crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ !(crecp->flags & F_NXDOMAIN) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ return 1;
+
+ return 0;
+}
+
+struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int no_rr = prot & F_NO_RR;
+
+ prot &= ~F_NO_RR;
+
+ 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 int 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 | F_CONFIG))
+ {
+ *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 && !no_rr)
+ {
+ 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 | F_CONFIG)))
+ {
+ 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, union all_addr *addr,
+ time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ 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, addrlen) == 0)
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ {
+ *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 | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+
+ *chainp = cache_head;
+ }
+
+ if (ans &&
+ (ans->flags & F_REVERSE) &&
+ (ans->flags & prot) &&
+ memcmp(&ans->addr, addr, addrlen) == 0)
+ return ans;
+
+ return NULL;
+}
+
+static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen,
+ unsigned int index, struct crec **rhash, int hashsz)
+{
+ struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
+ int i;
+ unsigned int j;
+
+ /* Remove duplicates in hosts files. */
+ if (lookup && (lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ free(cache);
+ return;
+ }
+
+ /* Ensure there is only one address -> name mapping (first one trumps)
+ We do this by steam here, The entries are kept in hash chains, linked
+ by ->next (which is unused at this point) held in hash buckets in
+ the array rhash, hashed on address. Note that rhash and the values
+ in ->next are only valid whilst reading hosts files: the buckets are
+ then freed, and the ->next pointer used for other things.
+
+ Only insert each unique address once into this hashing structure.
+
+ This complexity avoids O(n^2) divergent CPU use whilst reading
+ large (10000 entry) hosts files.
+
+ Note that we only do this process when bulk-reading hosts files,
+ for incremental reads, rhash is NULL, and we use cache lookups
+ instead.
+ */
+
+ if (rhash)
+ {
+ /* hash address */
+ for (j = 0, i = 0; i < addrlen; i++)
+ j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
+
+ for (lookup = rhash[j]; lookup; lookup = lookup->next)
+ if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
+ memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ cache->flags &= ~F_REVERSE;
+ break;
+ }
+
+ /* maintain address hash chain, insert new unique address */
+ if (!lookup)
+ {
+ cache->next = rhash[j];
+ rhash[j] = cache;
+ }
+ }
+ else
+ {
+ /* incremental read, lookup in cache */
+ lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
+ if (lookup && lookup->flags & F_HOSTS)
+ cache->flags &= ~F_REVERSE;
+ }
+
+ cache->uid = index;
+ memcpy(&cache->addr, addr, addrlen);
+ cache_hash(cache);
+ make_non_terminals(cache);
+}
+
+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++;
+ }
+}
+
+static int gettok(FILE *f, char *token)
+{
+ int c, count = 0;
+
+ while (1)
+ {
+ if ((c = getc(f)) == EOF)
+ return (count == 0) ? -1 : 1;
+
+ if (isspace(c) || c == '#')
+ {
+ ungetc(c, f);
+ return eatspace(f);
+ }
+
+ if (count < (MAXDNAME - 1))
+ {
+ token[count++] = c;
+ token[count] = 0;
+ }
+ }
+}
+
+int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
+{
+ FILE *f = fopen(filename, "r");
+ char *token = daemon->namebuff, *domain_suffix = NULL;
+ int addr_count = 0, name_count = cache_size, lineno = 1;
+ unsigned int flags = 0;
+ union all_addr addr;
+ int atnl, addrlen = 0;
+
+ if (!f)
+ {
+ my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
+ return cache_size;
+ }
+
+ lineno += eatspace(f);
+
+ while ((atnl = gettok(f, token)) != -1)
+ {
+ 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.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 = get_domain6(&addr.addr6);
+ }
+ else
+ {
+ my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
+ while (atnl == 0)
+ atnl = gettok(f, token);
+ lineno += atnl;
+ continue;
+ }
+
+ addr_count++;
+
+ /* rehash every 1000 names. */
+ if (rhash && ((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)) == -1)
+ 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_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
+ {
+ strcpy(cache->name.sname, canon);
+ strcat(cache->name.sname, ".");
+ strcat(cache->name.sname, domain_suffix);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ }
+ if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
+ {
+ strcpy(cache->name.sname, canon);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ }
+ free(canon);
+
+ }
+ else if (!nomem)
+ my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno);
+ }
+
+ lineno += atnl;
+ }
+
+ fclose(f);
+
+ if (rhash)
+ 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 revhashsz, i, total_size = daemon->cachesize;
+ struct hostsfile *ah;
+ struct host_record *hr;
+ struct name_list *nl;
+ struct cname *a;
+ struct crec lrec;
+ struct mx_srv_record *mx;
+ struct txt_record *txt;
+ struct interface_name *intr;
+ struct ptr_record *ptr;
+ struct naptr *naptr;
+#ifdef HAVE_DNSSEC
+ struct ds_config *ds;
+#endif
+
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED] = 0;
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED] = 0;
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
+ {
+ cache_blockdata_free(cache);
+
+ tmp = cache->hash_next;
+ if (cache->flags & (F_HOSTS | F_CONFIG))
+ {
+ *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;
+ }
+
+ /* Add locally-configured CNAMEs to the cache */
+ for (a = daemon->cnames; a; a = a->next)
+ if (a->alias[1] != '*' &&
+ ((cache = whine_malloc(SIZEOF_POINTER_CREC))))
+ {
+ cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+ cache->ttd = a->ttl;
+ cache->name.namep = a->alias;
+ cache->addr.cname.target.name = a->target;
+ cache->addr.cname.is_name_ptr = 1;
+ cache->uid = UID_NONE;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+
+#ifdef HAVE_DNSSEC
+ for (ds = daemon->ds; ds; ds = ds->next)
+ if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
+ (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
+ {
+ cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+ cache->ttd = daemon->local_ttl;
+ cache->name.namep = ds->name;
+ cache->addr.ds.keylen = ds->digestlen;
+ cache->addr.ds.algo = ds->algo;
+ cache->addr.ds.keytag = ds->keytag;
+ cache->addr.ds.digest = ds->digest_type;
+ cache->uid = ds->class;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+#endif
+
+ /* borrow the packet buffer for a temporary by-address hash */
+ memset(daemon->packet, 0, daemon->packet_buff_sz);
+ revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+
+ /* Do host_records in config. */
+ for (hr = daemon->host_records; hr; hr = hr->next)
+ for (nl = hr->names; nl; nl = nl->next)
+ {
+ if ((hr->flags & HR_4) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ if ((hr->flags & HR_6) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+ }
+
+ if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
+ {
+ if (daemon->cachesize > 0)
+ my_syslog(LOG_INFO, _("cleared cache"));
+ }
+ else
+ {
+ if (!option_bool(OPT_NO_HOSTS))
+ total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+
+ 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, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ /* Make non-terminal records for all locally-define RRs */
+ lrec.flags = F_FORWARD | F_CONFIG | F_NAMEP | F_IMMORTAL;
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ {
+ lrec.name.namep = txt->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+ {
+ lrec.name.namep = naptr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (mx = daemon->mxnames; mx; mx = mx->next)
+ {
+ lrec.name.namep = mx->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ lrec.name.namep = intr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+ {
+ lrec.name.namep = ptr->name;
+ make_non_terminals(&lrec);
+ }
+
+#ifdef HAVE_INOTIFY
+ set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+
+}
+
+#ifdef HAVE_DHCP
+struct in_addr a_record_from_hosts(char *name, time_t now)
+{
+ struct crec *crecp = NULL;
+ struct in_addr ret;
+
+ while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4)))
+ if (crecp->flags & F_HOSTS)
+ return crecp->addr.addr4;
+
+ my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name);
+
+ ret.s_addr = 0;
+ return ret;
+}
+
+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, int prot,
+ union all_addr *host_address, time_t ttd)
+{
+ struct crec *crec = NULL, *fail_crec = NULL;
+ unsigned int flags = F_IPV4;
+ int in_hosts = 0;
+ size_t addrlen = sizeof(struct in_addr);
+
+ if (prot == AF_INET6)
+ {
+ flags = F_IPV6;
+ addrlen = sizeof(struct in6_addr);
+ }
+
+ inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
+
+ while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
+ {
+ /* check all addresses associated with name */
+ if (crec->flags & (F_HOSTS | F_CONFIG))
+ {
+ 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, daemon->addrbuff);
+ else if (memcmp(&crec->addr, host_address, addrlen) == 0)
+ in_hosts = 1;
+ else
+ fail_crec = crec;
+ }
+ else if (!(crec->flags & F_DHCP))
+ {
+ cache_scan_free(host_name, NULL, C_IN, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
+ /* scan_free deletes all addresses associated with name */
+ break;
+ }
+ }
+
+ /* if in hosts, don't need DHCP record */
+ if (in_hosts)
+ return;
+
+ /* Name in hosts, address doesn't match */
+ if (fail_crec)
+ {
+ inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
+ 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, daemon->addrbuff,
+ record_source(fail_crec->uid), daemon->namebuff);
+ return;
+ }
+
+ if ((crec = cache_find_by_addr(NULL, (union all_addr *)host_address, 0, flags)))
+ {
+ if (crec->flags & F_NEG)
+ {
+ flags |= F_REVERSE;
+ cache_scan_free(NULL, (union all_addr *)host_address, C_IN, 0, flags, NULL, NULL);
+ }
+ }
+ else
+ flags |= F_REVERSE;
+
+ if ((crec = dhcp_spare))
+ dhcp_spare = dhcp_spare->next;
+ else /* need new one */
+ crec = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crec) /* malloc may fail */
+ {
+ crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
+ if (ttd == 0)
+ crec->flags |= F_IMMORTAL;
+ else
+ crec->ttd = ttd;
+ crec->addr = *host_address;
+ crec->name.namep = host_name;
+ crec->uid = UID_NONE;
+ cache_hash(crec);
+ make_non_terminals(crec);
+ }
+}
+#endif
+
+/* Called when we put a local or DHCP name into the cache.
+ Creates empty cache entries for subnames (ie,
+ for three.two.one, for two.one and one), without
+ F_IPV4 or F_IPV6 or F_CNAME set. These convert
+ NXDOMAIN answers to NoData ones. */
+static void make_non_terminals(struct crec *source)
+{
+ char *name = cache_get_name(source);
+ struct crec *crecp, *tmp, **up;
+ int type = F_HOSTS | F_CONFIG;
+#ifdef HAVE_DHCP
+ if (source->flags & F_DHCP)
+ type = F_DHCP;
+#endif
+
+ /* First delete any empty entries for our new real name. Note that
+ we only delete empty entries deriving from DHCP for a new DHCP-derived
+ entry and vice-versa for HOSTS and CONFIG. This ensures that
+ non-terminals from DHCP go when we reload DHCP and
+ for HOSTS/CONFIG when we re-read. */
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
+ {
+ tmp = crecp->hash_next;
+
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ {
+ *up = crecp->hash_next;
+#ifdef HAVE_DHCP
+ if (type & F_DHCP)
+ {
+ crecp->next = dhcp_spare;
+ dhcp_spare = crecp;
+ }
+ else
+#endif
+ free(crecp);
+ break;
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ while ((name = strchr(name, '.')))
+ {
+ name++;
+
+ /* Look for one existing, don't need another */
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ break;
+
+ if (crecp)
+ {
+ /* If the new name expires later, transfer that time to
+ empty non-terminal entry. */
+ if (!(crecp->flags & F_IMMORTAL))
+ {
+ if (source->flags & F_IMMORTAL)
+ crecp->flags |= F_IMMORTAL;
+ else if (difftime(crecp->ttd, source->ttd) < 0)
+ crecp->ttd = source->ttd;
+ }
+ continue;
+ }
+
+#ifdef HAVE_DHCP
+ if ((source->flags & F_DHCP) && dhcp_spare)
+ {
+ crecp = dhcp_spare;
+ dhcp_spare = dhcp_spare->next;
+ }
+ else
+#endif
+ crecp = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crecp)
+ {
+ crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE);
+ crecp->ttd = source->ttd;
+ crecp->name.namep = name;
+
+ cache_hash(crecp);
+ }
+ }
+}
+
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t)
+{
+ static char *buff = NULL;
+ static int bufflen = 60;
+ int len;
+ struct server *serv, *serv1;
+ char *p;
+
+ if (!buff && !(buff = whine_malloc(60)))
+ return 0;
+
+ p = buff;
+
+ switch (t->stat)
+ {
+ case TXT_STAT_CACHESIZE:
+ sprintf(buff+1, "%d", daemon->cachesize);
+ break;
+
+ case TXT_STAT_INSERTS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ break;
+
+ case TXT_STAT_EVICTIONS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+ break;
+
+ case TXT_STAT_MISSES:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+ break;
+
+ case TXT_STAT_HITS:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+ break;
+
+#ifdef HAVE_AUTH
+ case TXT_STAT_AUTH:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+ break;
+#endif
+
+ case TXT_STAT_SERVERS:
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ char *new, *lenp;
+ int port, newlen, bytes_avail, bytes_needed;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ lenp = p++; /* length */
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ if (bytes_needed >= bytes_avail)
+ {
+ /* expand buffer if necessary */
+ newlen = bytes_needed + 1 + bufflen - bytes_avail;
+ if (!(new = whine_malloc(newlen)))
+ return 0;
+ memcpy(new, buff, bufflen);
+ free(buff);
+ p = new + (p - buff);
+ lenp = p - 1;
+ buff = new;
+ bufflen = newlen;
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ }
+ *lenp = bytes_needed;
+ p += bytes_needed;
+ }
+ t->txt = (unsigned char *)buff;
+ t->len = p - buff;
+
+ return 1;
+ }
+
+ len = strlen(buff+1);
+ t->txt = (unsigned char *)buff;
+ t->len = len + 1;
+ *buff = len;
+ return 1;
+}
+#endif
+
+/* There can be names in the cache containing control chars, don't
+ mess up logging or open security holes. */
+static char *sanitise(char *name)
+{
+ unsigned char *r;
+ if (name)
+ for (r = (unsigned char *)name; *r; r++)
+ if (!isprint((int)*r))
+ return "<name unprintable>";
+
+ return name;
+}
+
+
+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, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
+ daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+#ifdef HAVE_AUTH
+ my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+
+ blockdata_report();
+
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ int port;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->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 *t = " ";
+ char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
+ *a = 0;
+ if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
+ n = "<Root>";
+ p += sprintf(p, "%-30.30s ", sanitise(n));
+ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+ a = sanitise(cache_get_cname_target(cache));
+ else if ((cache->flags & F_SRV) && !(cache->flags & F_NEG))
+ {
+ int targetlen = cache->addr.srv.targetlen;
+ ssize_t len = sprintf(a, "%u %u %u ", cache->addr.srv.priority,
+ cache->addr.srv.weight, cache->addr.srv.srvport);
+
+ if (targetlen > (40 - len))
+ targetlen = 40 - len;
+ blockdata_retrieve(cache->addr.srv.target, targetlen, a + len);
+ a[len + targetlen] = 0;
+ }
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ {
+ if (!(cache->flags & F_NEG))
+ sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+ cache->addr.ds.algo, cache->addr.ds.digest);
+ }
+ else if (cache->flags & F_DNSKEY)
+ sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+ cache->addr.key.algo, cache->addr.key.flags);
+#endif
+ else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
+ {
+ a = daemon->addrbuff;
+ if (cache->flags & F_IPV4)
+ inet_ntop(AF_INET, &cache->addr, a, ADDRSTRLEN);
+ else if (cache->flags & F_IPV6)
+ inet_ntop(AF_INET6, &cache->addr, a, ADDRSTRLEN);
+ }
+
+ if (cache->flags & F_IPV4)
+ t = "4";
+ else if (cache->flags & F_IPV6)
+ t = "6";
+ else if (cache->flags & F_CNAME)
+ t = "C";
+ else if (cache->flags & F_SRV)
+ t = "V";
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ t = "S";
+ else if (cache->flags & F_DNSKEY)
+ t = "K";
+#endif
+ p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s ", a, t,
+ 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" : " ",
+ cache->flags & F_DNSSECOK ? "V" : " ");
+#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, "%s", daemon->namebuff);
+ }
+ }
+}
+
+char *record_source(unsigned int index)
+{
+ struct hostsfile *ah;
+
+ if (index == SRC_CONFIG)
+ return "config";
+ else if (index == SRC_HOSTS)
+ return HOSTSFILE;
+
+ for (ah = daemon->addn_hosts; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+
+#ifdef HAVE_INOTIFY
+ for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+#endif
+
+ return "<unknown>";
+}
+
+char *querystr(char *desc, unsigned short type)
+{
+ unsigned int i;
+ int len = 10; /* strlen("type=xxxxx") */
+ const char *types = NULL;
+ static char *buff = NULL;
+ static int bufflen = 0;
+
+ for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+ if (typestr[i].type == type)
+ {
+ types = typestr[i].name;
+ len = strlen(types);
+ break;
+ }
+
+ if (desc)
+ {
+ len += 2; /* braces */
+ len += strlen(desc);
+ }
+ len++; /* terminator */
+
+ if (!buff || bufflen < len)
+ {
+ if (buff)
+ free(buff);
+ else if (len < 20)
+ len = 20;
+
+ buff = whine_malloc(len);
+ bufflen = len;
+ }
+
+ if (buff)
+ {
+ if (desc)
+ {
+ if (types)
+ sprintf(buff, "%s[%s]", desc, types);
+ else
+ sprintf(buff, "%s[type=%d]", desc, type);
+ }
+ else
+ {
+ if (types)
+ sprintf(buff, "<%s>", types);
+ else
+ sprintf(buff, "<type=%d>", type);
+ }
+ }
+
+ return buff ? buff : "";
+}
+
+static char *edestr(int ede)
+{
+ switch (ede)
+ {
+ case EDE_OTHER: return "other";
+ case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm";
+ case EDE_USUPDS: return "unsupported DS digest";
+ case EDE_STALE: return "stale answer";
+ case EDE_FORGED: return "forged";
+ case EDE_DNSSEC_IND: return "DNSSEC indeterminate";
+ case EDE_DNSSEC_BOGUS: return "DNSSEC bogus";
+ case EDE_SIG_EXP: return "DNSSEC signature expired";
+ case EDE_SIG_NYV: return "DNSSEC sig not yet valid";
+ case EDE_NO_DNSKEY: return "DNSKEY missing";
+ case EDE_NO_RRSIG: return "RRSIG missing";
+ case EDE_NO_ZONEKEY: return "no zone key bit set";
+ case EDE_NO_NSEC: return "NSEC(3) missing";
+ case EDE_CACHED_ERR: return "cached error";
+ case EDE_NOT_READY: return "not ready";
+ case EDE_BLOCKED: return "blocked";
+ case EDE_CENSORED: return "censored";
+ case EDE_FILTERED: return "filtered";
+ case EDE_PROHIBITED: return "prohibited";
+ case EDE_STALE_NXD: return "stale NXDOMAIN";
+ case EDE_NOT_AUTH: return "not authoritative";
+ case EDE_NOT_SUP: return "not supported";
+ case EDE_NO_AUTH: return "no reachable authority";
+ case EDE_NETERR: return "network error";
+ case EDE_INVALID_DATA: return "invalid data";
+ default: return "unknown";
+ }
+}
+
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
+{
+ char *source, *dest = arg;
+ char *verb = "is";
+ char *extra = "";
+
+ if (!option_bool(OPT_LOG))
+ return;
+
+#ifdef HAVE_DNSSEC
+ if ((flags & F_DNSSECOK) && option_bool(OPT_EXTRALOG))
+ extra = " (DNSSEC signed)";
+#endif
+
+ name = sanitise(name);
+
+ if (addr)
+ {
+ dest = daemon->addrbuff;
+
+ if (flags & F_KEYTAG)
+ sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
+ else if (flags & F_RCODE)
+ {
+ unsigned int rcode = addr->log.rcode;
+
+ if (rcode == SERVFAIL)
+ dest = "SERVFAIL";
+ else if (rcode == REFUSED)
+ dest = "REFUSED";
+ else if (rcode == NOTIMP)
+ dest = "not implemented";
+ else
+ sprintf(daemon->addrbuff, "%u", rcode);
+
+ if (addr->log.ede != EDE_UNSET)
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ }
+ else if (flags & (F_IPV4 | F_IPV6))
+ inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+ addr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ dest = arg;
+ }
+
+ if (flags & F_REVERSE)
+ {
+ dest = name;
+ name = daemon->addrbuff;
+ }
+
+ if (flags & F_NEG)
+ {
+ if (flags & F_NXDOMAIN)
+ 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_SRV)
+ dest = "<SRV>";
+ 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_SECSTAT)
+ {
+ if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG))
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ source = "validation";
+ dest = arg;
+ }
+ else if (flags & F_AUTH)
+ source = "auth";
+ else if (flags & F_SERVER)
+ {
+ source = "forwarded";
+ verb = "to";
+ }
+ else if (flags & F_QUERY)
+ {
+ source = arg;
+ verb = "from";
+ }
+ else if (flags & F_DNSSEC)
+ {
+ source = arg;
+ verb = "to";
+ }
+ else if (flags & F_IPSET)
+ {
+ source = "ipset add";
+ dest = name;
+ name = arg;
+ verb = daemon->addrbuff;
+ }
+ else
+ source = "cached";
+
+ if (strlen(name) == 0)
+ name = ".";
+
+ if (option_bool(OPT_EXTRALOG))
+ {
+ if (flags & F_NOEXTRA)
+ my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra);
+ else
+ {
+ int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
+ my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra);
+ }
+ }
+ else
+ my_syslog(LOG_INFO, "%s %s %s %s%s", source, name, verb, dest, extra);
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/config.h b/ap/app/dnsmasq/dnsmasq-2.86/src/config.h
new file mode 100755
index 0000000..26fcaf0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/config.h
@@ -0,0 +1,452 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 FTABSIZ 150 /* max number of outstanding requests (default) */
+#define MAX_PROCS 2 /* max no children for TCP requests */
+#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
+#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
+#define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */
+//#define EDNS_PKTSZ 4096 CVE-2023-28450/* default max EDNS.0 UDP packet from RFC5625 */
+#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
+#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
+#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
+#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
+#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
+#define FORWARD_TEST 50 /* try all servers every 50 queries */
+#define FORWARD_TIME 20 /* or 20 seconds */
+#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */
+#define SERVERS_LOGGED 30 /* Only log this many servers when logging state */
+#define LOCALS_LOGGED 8 /* Only log this many local addresses when logging state */
+#define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */
+#define CACHESIZ 150 /* default cache size */
+#define TTL_FLOOR_LIMIT 3600 /* don't allow --min-cache-ttl to raise TTL above this under any circumstances */
+#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 50 /* most domain names are smaller than this */
+#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */
+#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */
+#define HOSTSFILE "/etc/hosts"
+#define ETHERSFILE "/etc/ethers"
+#define DEFLEASE 3600 /* default DHCPv4 lease time, one hour */
+#define DEFLEASE6 (3600*24) /* default lease time for DHCPv6. One day. */
+#define CHUSER "nobody"
+#define CHGRP "dip"
+#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
+#define LOG_MAX 5 /* log-queue length */
+#define RANDFILE "/dev/urandom"
+#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */
+#define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq"
+#define DNSMASQ_UBUS_NAME "dnsmasq" /* Default - may be overridden by config */
+#define AUTH_TTL 600 /* default TTL for auth DNS */
+#define SOA_REFRESH 1200 /* SOA refresh default */
+#define SOA_RETRY 180 /* SOA retry default */
+#define SOA_EXPIRY 1209600 /* SOA expiry default */
+#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */
+#define LOOP_TEST_TYPE T_TXT
+
+/* compile-time options: uncomment below to enable or do eg.
+ make COPTS=-DHAVE_BROKEN_RTC
+
+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 DHCPv4 server.
+
+HAVE_DHCP6
+ define this to get dnsmasq's DHCPv6 server. (implies HAVE_DHCP).
+
+HAVE_SCRIPT
+ define this to get the ability to call scripts on lease-change.
+
+HAVE_LUASCRIPT
+ define this to get the ability to call Lua script on lease-change. (implies HAVE_SCRIPT)
+
+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_UBUS
+ define this if you want to link against libubus
+
+HAVE_IDN
+ define this if you want international domain name 2003 support.
+
+HAVE_LIBIDN2
+ define this if you want international domain name 2008 support.
+
+HAVE_CONNTRACK
+ define this to include code which propagates conntrack marks from
+ incoming DNS queries to the corresponding upstream queries. This adds
+ a build-dependency on libnetfilter_conntrack, but the resulting binary will
+ still run happily on a kernel without conntrack support.
+
+HAVE_IPSET
+ define this to include the ability to selectively add resolved ip addresses
+ to given ipsets.
+
+HAVE_AUTH
+ define this to include the facility to act as an authoritative DNS
+ server for one or more zones.
+
+HAVE_CRYPTOHASH
+ include just hash function from crypto library, but no DNSSEC.
+
+HAVE_DNSSEC
+ include DNSSEC validator.
+
+HAVE_DUMPFILE
+ include code to dump packets to a libpcap-format file for debugging.
+
+HAVE_LOOP
+ include functionality to probe for and remove DNS forwarding loops.
+
+HAVE_INOTIFY
+ use the Linux inotify facility to efficiently re-read configuration files.
+
+NO_ID
+ Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
+NO_TFTP
+NO_DHCP
+NO_DHCP6
+NO_SCRIPT
+NO_LARGEFILE
+NO_AUTH
+NO_DUMPFILE
+NO_LOOP
+NO_INOTIFY
+ these are available to explicitly disable compile time options which would
+ otherwise be enabled automatically or which are enabled by default
+ in the distributed source tree. Building dnsmasq
+ with something like "make COPTS=-DNO_SCRIPT" will do the trick.
+NO_GMP
+ Don't use and link against libgmp, Useful if nettle is built with --enable-mini-gmp.
+
+LEASEFILE
+CONFFILE
+RESOLVFILE
+ the default locations of these files are determined below, but may be overridden
+ in a build command line using COPTS.
+
+*/
+
+/* Defining this builds a binary which handles time differently and works better on a system without a
+ stable RTC (it uses uptime, not epoch time) and writes the DHCP leases file less often to avoid flash wear.
+*/
+
+/* #define HAVE_BROKEN_RTC */
+
+/* The default set of options to build. Built with these options, dnsmasq
+ has no library dependencies other than libc */
+
+#define HAVE_DHCP
+#define HAVE_DHCP6
+#define HAVE_TFTP
+#define HAVE_SCRIPT
+#define HAVE_AUTH
+#define HAVE_IPSET
+#define HAVE_LOOP
+#define HAVE_DUMPFILE
+
+/* Build options which require external libraries.
+
+ Defining HAVE_<opt>_STATIC as _well_ as HAVE_<opt> will link the library statically.
+
+ You can use "make COPTS=-DHAVE_<opt>" instead of editing these.
+*/
+
+/* #define HAVE_LUASCRIPT */
+/* #define HAVE_DBUS */
+/* #define HAVE_IDN */
+/* #define HAVE_LIBIDN2 */
+/* #define HAVE_CONNTRACK */
+/* #define HAVE_CRYPTOHASH */
+/* #define HAVE_DNSSEC */
+
+
+/* Default locations for important system files. */
+
+#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
+
+#ifndef RESOLVFILE
+# if defined(__uClinux__)
+# define RESOLVFILE "/etc/config/resolv.conf"
+# else
+# define RESOLVFILE "/etc/resolv.conf"
+# endif
+#endif
+
+#ifndef RUNFILE
+# if defined(__ANDROID__)
+# define RUNFILE "/data/dnsmasq.pid"
+# else
+# define RUNFILE "/var/run/dnsmasq.pid"
+# endif
+#endif
+
+/* platform dependent options: these are determined automatically below
+
+HAVE_LINUX_NETWORK
+HAVE_BSD_NETWORK
+HAVE_SOLARIS_NETWORK
+ define exactly one of these to alter interaction with kernel networking.
+
+HAVE_GETOPT_LONG
+ defined when GNU-style getopt_long available.
+
+HAVE_SOCKADDR_SA_LEN
+ defined if struct sockaddr has sa_len field (*BSD)
+*/
+
+#if 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_SOCKADDR_SA_LEN
+#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_SOCKADDR_SA_LEN
+
+#elif defined(__FreeBSD__) || \
+ defined(__OpenBSD__) || \
+ defined(__DragonFly__) || \
+ defined(__FreeBSD_kernel__)
+#define HAVE_BSD_NETWORK
+/* Later versions of FreeBSD have getopt_long() */
+#if defined(optional_argument) && defined(required_argument)
+# define HAVE_GETOPT_LONG
+#endif
+#define HAVE_SOCKADDR_SA_LEN
+
+#elif defined(__APPLE__)
+#define HAVE_BSD_NETWORK
+#define HAVE_GETOPT_LONG
+#define HAVE_SOCKADDR_SA_LEN
+#define NO_IPSET
+/* Define before sys/socket.h is included so we get socklen_t */
+#define _BSD_SOCKLEN_T_
+/* Select the RFC_3542 version of the IPv6 socket API.
+ Define before netinet6/in6.h is included. */
+#define __APPLE_USE_RFC_3542
+/* Required for Mojave. */
+#ifndef SOL_TCP
+# define SOL_TCP IPPROTO_TCP
+#endif
+#define NO_IPSET
+
+#elif defined(__NetBSD__)
+#define HAVE_BSD_NETWORK
+#define HAVE_GETOPT_LONG
+#define HAVE_SOCKADDR_SA_LEN
+
+#elif defined(__sun) || defined(__sun__)
+#define HAVE_SOLARIS_NETWORK
+#define HAVE_GETOPT_LONG
+#undef HAVE_SOCKADDR_SA_LEN
+#define ETHER_ADDR_LEN 6
+
+#endif
+
+/* rules to implement compile-time option dependencies and
+ the NO_XXX flags */
+
+#ifdef NO_TFTP
+#undef HAVE_TFTP
+#endif
+
+#ifdef NO_DHCP
+#undef HAVE_DHCP
+#undef HAVE_DHCP6
+#endif
+
+#if defined(NO_DHCP6)
+#undef HAVE_DHCP6
+#endif
+
+/* DHCP6 needs DHCP too */
+#ifdef HAVE_DHCP6
+#define HAVE_DHCP
+#endif
+
+#if defined(NO_SCRIPT)
+#undef HAVE_SCRIPT
+#undef HAVE_LUASCRIPT
+#endif
+
+/* Must HAVE_SCRIPT to HAVE_LUASCRIPT */
+#ifdef HAVE_LUASCRIPT
+#define HAVE_SCRIPT
+#endif
+
+#ifdef NO_AUTH
+#undef HAVE_AUTH
+#endif
+
+#if defined(NO_IPSET)
+#undef HAVE_IPSET
+#endif
+
+#ifdef NO_LOOP
+#undef HAVE_LOOP
+#endif
+
+#ifdef NO_DUMPFILE
+#undef HAVE_DUMPFILE
+#endif
+
+#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY)
+#define HAVE_INOTIFY
+#endif
+
+/* Define a string indicating which options are in use.
+ DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */
+
+#ifdef DNSMASQ_COMPILE_OPTS
+
+static char *compile_opts =
+"IPv6 "
+#ifndef HAVE_GETOPT_LONG
+"no-"
+#endif
+"GNU-getopt "
+#ifdef HAVE_BROKEN_RTC
+"no-RTC "
+#endif
+#ifndef HAVE_DBUS
+"no-"
+#endif
+"DBus "
+#ifndef HAVE_UBUS
+"no-"
+#endif
+"UBus "
+#ifndef LOCALEDIR
+"no-"
+#endif
+"i18n "
+#if defined(HAVE_LIBIDN2)
+"IDN2 "
+#else
+ #if !defined(HAVE_IDN)
+"no-"
+ #endif
+"IDN "
+#endif
+#ifndef HAVE_DHCP
+"no-"
+#endif
+"DHCP "
+#if defined(HAVE_DHCP)
+# if !defined (HAVE_DHCP6)
+ "no-"
+# endif
+ "DHCPv6 "
+#endif
+#if !defined(HAVE_SCRIPT)
+ "no-scripts "
+#else
+# if !defined(HAVE_LUASCRIPT)
+ "no-"
+# endif
+ "Lua "
+#endif
+#ifndef HAVE_TFTP
+"no-"
+#endif
+"TFTP "
+#ifndef HAVE_CONNTRACK
+"no-"
+#endif
+"conntrack "
+#ifndef HAVE_IPSET
+"no-"
+#endif
+"ipset "
+#ifndef HAVE_AUTH
+"no-"
+#endif
+"auth "
+#if !defined(HAVE_CRYPTOHASH) && !defined(HAVE_DNSSEC)
+"no-"
+#endif
+"cryptohash "
+#ifndef HAVE_DNSSEC
+"no-"
+#endif
+"DNSSEC "
+#ifdef NO_ID
+"no-ID "
+#endif
+#ifndef HAVE_LOOP
+"no-"
+#endif
+"loop-detect "
+#ifndef HAVE_INOTIFY
+"no-"
+#endif
+"inotify "
+#ifndef HAVE_DUMPFILE
+"no-"
+#endif
+"dumpfile";
+
+#endif /* defined(HAVE_DHCP) */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c b/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c
new file mode 100755
index 0000000..745a2a3
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c
@@ -0,0 +1,85 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_CONNTRACK
+
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+static int gotit = 0; /* yuck */
+
+static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data);
+
+int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr, int istcp, unsigned int *markp)
+{
+ struct nf_conntrack *ct;
+ struct nfct_handle *h;
+
+ gotit = 0;
+
+ if ((ct = nfct_new()))
+ {
+ nfct_set_attr_u8(ct, ATTR_L4PROTO, istcp ? IPPROTO_TCP : IPPROTO_UDP);
+ nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(daemon->port));
+
+ if (peer_addr->sa.sa_family == AF_INET6)
+ {
+ nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(ct, ATTR_IPV6_SRC, peer_addr->in6.sin6_addr.s6_addr);
+ nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in6.sin6_port);
+ nfct_set_attr(ct, ATTR_IPV6_DST, local_addr->addr6.s6_addr);
+ }
+ else
+ {
+ nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(ct, ATTR_IPV4_SRC, peer_addr->in.sin_addr.s_addr);
+ nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in.sin_port);
+ nfct_set_attr_u32(ct, ATTR_IPV4_DST, local_addr->addr4.s_addr);
+ }
+
+
+ if ((h = nfct_open(CONNTRACK, 0)))
+ {
+ nfct_callback_register(h, NFCT_T_ALL, callback, (void *)markp);
+ if (nfct_query(h, NFCT_Q_GET, ct) == -1)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_ERR, _("Conntrack connection mark retrieval failed: %s"), strerror(errno));
+ warned = 1;
+ }
+ }
+ nfct_close(h);
+ }
+ nfct_destroy(ct);
+ }
+
+ return gotit;
+}
+
+static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data)
+{
+ unsigned int *ret = (unsigned int *)data;
+ *ret = nfct_get_attr_u32(ct, ATTR_MARK);
+ (void)type; /* eliminate warning */
+ gotit = 1;
+
+ return NFCT_CB_CONTINUE;
+}
+
+#endif /* HAVE_CONNTRACK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c b/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c
new file mode 100755
index 0000000..4009569
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c
@@ -0,0 +1,499 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DNSSEC) || defined(HAVE_CRYPTOHASH)
+
+/* Minimal version of nettle */
+
+/* bignum.h includes version.h and works on
+ earlier releases of nettle which don't have version.h */
+#include <nettle/bignum.h>
+#if !defined(NETTLE_VERSION_MAJOR)
+# define NETTLE_VERSION_MAJOR 2
+# define NETTLE_VERSION_MINOR 0
+#endif
+#define MIN_VERSION(major, minor) ((NETTLE_VERSION_MAJOR == (major) && NETTLE_VERSION_MINOR >= (minor)) || \
+ (NETTLE_VERSION_MAJOR > (major)))
+
+#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */
+
+#if defined(HAVE_DNSSEC)
+#include <nettle/rsa.h>
+#include <nettle/ecdsa.h>
+#include <nettle/ecc-curve.h>
+#if MIN_VERSION(3, 1)
+#include <nettle/eddsa.h>
+#endif
+#if MIN_VERSION(3, 6)
+# include <nettle/gostdsa.h>
+#endif
+
+#if MIN_VERSION(3, 1)
+/* Implement a "hash-function" to the nettle API, which simply returns
+ the input data, concatenated into a single, statically maintained, buffer.
+
+ Used for the EdDSA sigs, which operate on the whole message, rather
+ than a digest. */
+
+struct null_hash_digest
+{
+ uint8_t *buff;
+ size_t len;
+};
+
+struct null_hash_ctx
+{
+ size_t len;
+};
+
+static size_t null_hash_buff_sz = 0;
+static uint8_t *null_hash_buff = NULL;
+#define BUFF_INCR 128
+
+static void null_hash_init(void *ctx)
+{
+ ((struct null_hash_ctx *)ctx)->len = 0;
+}
+
+static void null_hash_update(void *ctxv, size_t length, const uint8_t *src)
+{
+ struct null_hash_ctx *ctx = ctxv;
+ size_t new_len = ctx->len + length;
+
+ if (new_len > null_hash_buff_sz)
+ {
+ uint8_t *new;
+
+ if (!(new = whine_malloc(new_len + BUFF_INCR)))
+ return;
+
+ if (null_hash_buff)
+ {
+ if (ctx->len != 0)
+ memcpy(new, null_hash_buff, ctx->len);
+ free(null_hash_buff);
+ }
+
+ null_hash_buff_sz = new_len + BUFF_INCR;
+ null_hash_buff = new;
+ }
+
+ memcpy(null_hash_buff + ctx->len, src, length);
+ ctx->len += length;
+}
+
+static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
+{
+ (void)length;
+
+ ((struct null_hash_digest *)dst)->buff = null_hash_buff;
+ ((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
+}
+
+static struct nettle_hash null_hash = {
+ "null_hash",
+ sizeof(struct null_hash_ctx),
+ sizeof(struct null_hash_digest),
+ 0,
+ (nettle_hash_init_func *) null_hash_init,
+ (nettle_hash_update_func *) null_hash_update,
+ (nettle_hash_digest_func *) null_hash_digest
+};
+
+#endif /* MIN_VERSION(3, 1) */
+
+/* expand ctx and digest memory allocations if necessary and init hash function */
+int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp)
+{
+ static void *ctx = NULL;
+ static unsigned char *digest = NULL;
+ static unsigned int ctx_sz = 0;
+ static unsigned int digest_sz = 0;
+
+ void *new;
+
+ if (ctx_sz < hash->context_size)
+ {
+ if (!(new = whine_malloc(hash->context_size)))
+ return 0;
+ if (ctx)
+ free(ctx);
+ ctx = new;
+ ctx_sz = hash->context_size;
+ }
+
+ if (digest_sz < hash->digest_size)
+ {
+ if (!(new = whine_malloc(hash->digest_size)))
+ return 0;
+ if (digest)
+ free(digest);
+ digest = new;
+ digest_sz = hash->digest_size;
+ }
+
+ *ctxp = ctx;
+ *digestp = digest;
+
+ hash->init(ctx);
+
+ return 1;
+}
+
+static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+ unsigned char *p;
+ size_t exp_len;
+
+ static struct rsa_public_key *key = NULL;
+ static mpz_t sig_mpz;
+
+ (void)digest_len;
+
+ if (key == NULL)
+ {
+ if (!(key = whine_malloc(sizeof(struct rsa_public_key))))
+ return 0;
+
+ nettle_rsa_public_key_init(key);
+ mpz_init(sig_mpz);
+ }
+
+ if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL)))
+ return 0;
+
+ key_len--;
+ if ((exp_len = *p++) == 0)
+ {
+ GETSHORT(exp_len, p);
+ key_len -= 2;
+ }
+
+ if (exp_len >= key_len)
+ return 0;
+
+ key->size = key_len - exp_len;
+ mpz_import(key->e, exp_len, 1, 1, 0, 0, p);
+ mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len);
+
+ mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig);
+
+ switch (algo)
+ {
+ case 5: case 7:
+ return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz);
+ case 8:
+ return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz);
+ case 10:
+ return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz);
+ }
+
+ return 0;
+}
+
+static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len,
+ unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+ unsigned char *p;
+ unsigned int t;
+ struct ecc_point *key;
+
+ static struct ecc_point *key_256 = NULL, *key_384 = NULL;
+ static mpz_t x, y;
+ static struct dsa_signature *sig_struct;
+#if !MIN_VERSION(3, 4)
+#define nettle_get_secp_256r1() (&nettle_secp_256r1)
+#define nettle_get_secp_384r1() (&nettle_secp_384r1)
+#endif
+
+ if (!sig_struct)
+ {
+ if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))
+ return 0;
+
+ nettle_dsa_signature_init(sig_struct);
+ mpz_init(x);
+ mpz_init(y);
+ }
+
+ switch (algo)
+ {
+ case 13:
+ if (!key_256)
+ {
+ if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))
+ return 0;
+
+ nettle_ecc_point_init(key_256, nettle_get_secp_256r1());
+ }
+
+ key = key_256;
+ t = 32;
+ break;
+
+ case 14:
+ if (!key_384)
+ {
+ if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))
+ return 0;
+
+ nettle_ecc_point_init(key_384, nettle_get_secp_384r1());
+ }
+
+ key = key_384;
+ t = 48;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (sig_len != 2*t || key_len != 2*t ||
+ !(p = blockdata_retrieve(key_data, key_len, NULL)))
+ return 0;
+
+ mpz_import(x, t , 1, 1, 0, 0, p);
+ mpz_import(y, t , 1, 1, 0, 0, p + t);
+
+ if (!ecc_point_set(key, x, y))
+ return 0;
+
+ mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);
+ mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);
+
+ return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);
+}
+
+#if MIN_VERSION(3, 6)
+static int dnsmasq_gostdsa_verify(struct blockdata *key_data, unsigned int key_len,
+ unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+ unsigned char *p;
+
+ static struct ecc_point *gost_key = NULL;
+ static mpz_t x, y;
+ static struct dsa_signature *sig_struct;
+
+ if (algo != 12 ||
+ sig_len != 64 || key_len != 64 ||
+ !(p = blockdata_retrieve(key_data, key_len, NULL)))
+ return 0;
+
+ if (!sig_struct)
+ {
+ if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||
+ !(gost_key = whine_malloc(sizeof(struct ecc_point))))
+ return 0;
+
+ nettle_dsa_signature_init(sig_struct);
+ nettle_ecc_point_init(gost_key, nettle_get_gost_gc256b());
+ mpz_init(x);
+ mpz_init(y);
+ }
+
+ mpz_import(x, 32 , 1, 1, 0, 0, p);
+ mpz_import(y, 32 , 1, 1, 0, 0, p + 32);
+
+ if (!ecc_point_set(gost_key, x, y))
+ return 0;
+
+ mpz_import(sig_struct->r, 32, 1, 1, 0, 0, sig);
+ mpz_import(sig_struct->s, 32, 1, 1, 0, 0, sig + 32);
+
+ return nettle_gostdsa_verify(gost_key, digest_len, digest, sig_struct);
+}
+#endif
+
+#if MIN_VERSION(3, 1)
+static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len,
+ unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+ unsigned char *p;
+
+ if (digest_len != sizeof(struct null_hash_digest) ||
+ !(p = blockdata_retrieve(key_data, key_len, NULL)))
+ return 0;
+
+ /* The "digest" returned by the null_hash function is simply a struct null_hash_digest
+ which has a pointer to the actual data and a length, because the buffer
+ may need to be extended during "hashing". */
+
+ switch (algo)
+ {
+ case 15:
+ if (key_len != ED25519_KEY_SIZE ||
+ sig_len != ED25519_SIGNATURE_SIZE)
+ return 0;
+
+ return ed25519_sha512_verify(p,
+ ((struct null_hash_digest *)digest)->len,
+ ((struct null_hash_digest *)digest)->buff,
+ sig);
+
+#if MIN_VERSION(3, 6)
+ case 16:
+ if (key_len != ED448_KEY_SIZE ||
+ sig_len != ED448_SIGNATURE_SIZE)
+ return 0;
+
+ return ed448_shake256_verify(p,
+ ((struct null_hash_digest *)digest)->len,
+ ((struct null_hash_digest *)digest)->buff,
+ sig);
+#endif
+
+ }
+
+ return 0;
+}
+#endif
+
+static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+
+ /* Ensure at runtime that we have support for this digest */
+ if (!hash_find(algo_digest_name(algo)))
+ return NULL;
+
+ /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */
+ switch (algo)
+ {
+ case 5: case 7: case 8: case 10:
+ return dnsmasq_rsa_verify;
+
+#if MIN_VERSION(3, 6)
+ case 12:
+ return dnsmasq_gostdsa_verify;
+#endif
+
+ case 13: case 14:
+ return dnsmasq_ecdsa_verify;
+
+#if MIN_VERSION(3, 1)
+ case 15: case 16:
+ return dnsmasq_eddsa_verify;
+#endif
+ }
+
+ return NULL;
+}
+
+int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo)
+{
+
+ int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo);
+
+ func = verify_func(algo);
+
+ if (!func)
+ return 0;
+
+ return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo);
+}
+
+/* Note the ds_digest_name(), algo_digest_name() and nsec3_digest_name()
+ define which algo numbers we support. If algo_digest_name() returns
+ non-NULL for an algorithm number, we assume that algorithm is
+ supported by verify(). */
+
+/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */
+char *ds_digest_name(int digest)
+{
+ switch (digest)
+ {
+ case 1: return "sha1";
+ case 2: return "sha256";
+ case 3: return "gosthash94";
+ case 4: return "sha384";
+ default: return NULL;
+ }
+}
+
+/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
+char *algo_digest_name(int algo)
+{
+ switch (algo)
+ {
+ case 1: return NULL; /* RSA/MD5 - Must Not Implement. RFC 6944 para 2.3. */
+ case 2: return NULL; /* Diffie-Hellman */
+ case 3: return NULL; ; /* DSA/SHA1 - Must Not Implement. RFC 8624 section 3.1 */
+ case 5: return "sha1"; /* RSA/SHA1 */
+ case 6: return NULL; /* DSA-NSEC3-SHA1 - Must Not Implement. RFC 8624 section 3.1 */
+ case 7: return "sha1"; /* RSASHA1-NSEC3-SHA1 */
+ case 8: return "sha256"; /* RSA/SHA-256 */
+ case 10: return "sha512"; /* RSA/SHA-512 */
+ case 12: return "gosthash94"; /* ECC-GOST */
+ case 13: return "sha256"; /* ECDSAP256SHA256 */
+ case 14: return "sha384"; /* ECDSAP384SHA384 */
+ case 15: return "null_hash"; /* ED25519 */
+ case 16: return "null_hash"; /* ED448 */
+ default: return NULL;
+ }
+}
+
+/* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */
+char *nsec3_digest_name(int digest)
+{
+ switch (digest)
+ {
+ case 1: return "sha1";
+ default: return NULL;
+ }
+}
+
+#endif /* defined(HAVE_DNSSEC) */
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+/* Find pointer to correct hash function in nettle library */
+const struct nettle_hash *hash_find(char *name)
+{
+ if (!name)
+ return NULL;
+
+#if MIN_VERSION(3,1) && defined(HAVE_DNSSEC)
+ /* We provide a "null" hash which returns the input data as digest. */
+ if (strcmp(null_hash.name, name) == 0)
+ return &null_hash;
+#endif
+
+ /* libnettle >= 3.4 provides nettle_lookup_hash() which avoids nasty ABI
+ incompatibilities if sizeof(nettle_hashes) changes between library
+ versions. */
+#if MIN_VERSION(3, 4)
+ return nettle_lookup_hash(name);
+#else
+ {
+ int i;
+
+ for (i = 0; nettle_hashes[i]; i++)
+ if (strcmp(nettle_hashes[i]->name, name) == 0)
+ return nettle_hashes[i];
+ }
+
+ return NULL;
+#endif
+}
+
+#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c
new file mode 100755
index 0000000..cbdce9c
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c
@@ -0,0 +1,880 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_template =
+"<!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=\"%s\">\n"
+" <method name=\"ClearCache\">\n"
+" </method>\n"
+" <method name=\"GetVersion\">\n"
+" <arg name=\"version\" direction=\"out\" type=\"s\"/>\n"
+" </method>\n"
+#ifdef HAVE_LOOP
+" <method name=\"GetLoopServers\">\n"
+" <arg name=\"server\" direction=\"out\" type=\"as\"/>\n"
+" </method>\n"
+#endif
+" <method name=\"SetServers\">\n"
+" <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n"
+" </method>\n"
+" <method name=\"SetDomainServers\">\n"
+" <arg name=\"servers\" direction=\"in\" type=\"as\"/>\n"
+" </method>\n"
+" <method name=\"SetServersEx\">\n"
+" <arg name=\"servers\" direction=\"in\" type=\"aas\"/>\n"
+" </method>\n"
+" <method name=\"SetFilterWin2KOption\">\n"
+" <arg name=\"filterwin2k\" direction=\"in\" type=\"b\"/>\n"
+" </method>\n"
+" <method name=\"SetBogusPrivOption\">\n"
+" <arg name=\"boguspriv\" direction=\"in\" type=\"b\"/>\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"
+#ifdef HAVE_DHCP
+" <method name=\"AddDhcpLease\">\n"
+" <arg name=\"ipaddr\" type=\"s\"/>\n"
+" <arg name=\"hwaddr\" type=\"s\"/>\n"
+" <arg name=\"hostname\" type=\"ay\"/>\n"
+" <arg name=\"clid\" type=\"ay\"/>\n"
+" <arg name=\"lease_duration\" type=\"u\"/>\n"
+" <arg name=\"ia_id\" type=\"u\"/>\n"
+" <arg name=\"is_temporary\" type=\"b\"/>\n"
+" </method>\n"
+" <method name=\"DeleteDhcpLease\">\n"
+" <arg name=\"ipaddr\" type=\"s\"/>\n"
+" <arg name=\"success\" type=\"b\" direction=\"out\"/>\n"
+" </method>\n"
+#endif
+" <method name=\"GetMetrics\">\n"
+" <arg name=\"metrics\" direction=\"out\" type=\"a{su}\"/>\n"
+" </method>\n"
+" </interface>\n"
+"</node>\n";
+
+static char *introspection_xml = NULL;
+
+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, *tmp;
+
+ for (up = &(daemon->watches), w = daemon->watches; w; w = tmp)
+ {
+ tmp = w->next;
+ if (w->watch == watch)
+ {
+ *up = tmp;
+ free(w);
+ }
+ else
+ up = &(w->next);
+ }
+
+ w = data; /* no warning */
+}
+
+static void dbus_read_servers(DBusMessage *message)
+{
+ DBusMessageIter iter;
+ union mysockaddr addr, source_addr;
+ char *domain;
+
+ dbus_message_iter_init(message, &iter);
+
+ mark_servers(SERV_FROM_DBUS);
+
+ 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)
+ {
+ i++;
+ break;
+ }
+ }
+
+ if (i == sizeof(struct in6_addr))
+ {
+ 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;
+ }
+ }
+ else
+ /* At the end */
+ break;
+
+ /* process each domain */
+ 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)
+ add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain, NULL);
+
+ } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
+ }
+
+ /* unlink and free anything still marked. */
+ cleanup_servers();
+}
+
+#ifdef HAVE_LOOP
+static DBusMessage *dbus_reply_server_loop(DBusMessage *message)
+{
+ DBusMessageIter args, args_iter;
+ struct server *serv;
+ DBusMessage *reply = dbus_message_new_method_return(message);
+
+ dbus_message_iter_init_append (reply, &args);
+ dbus_message_iter_open_container (&args, DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING, &args_iter);
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (serv->flags & SERV_LOOP)
+ {
+ (void)prettyprint_addr(&serv->addr, daemon->addrbuff);
+ dbus_message_iter_append_basic (&args_iter, DBUS_TYPE_STRING, &daemon->addrbuff);
+ }
+
+ dbus_message_iter_close_container (&args, &args_iter);
+
+ return reply;
+}
+#endif
+
+static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
+{
+ DBusMessageIter iter, array_iter, string_iter;
+ DBusMessage *error = NULL;
+ const char *addr_err;
+ char *dup = NULL;
+
+ if (!dbus_message_iter_init(message, &iter))
+ {
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Failed to initialize dbus message iter");
+ }
+
+ /* check that the message contains an array of arrays */
+ if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+ (dbus_message_iter_get_element_type(&iter) != (strings ? DBUS_TYPE_STRING : DBUS_TYPE_ARRAY)))
+ {
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ strings ? "Expected array of string" : "Expected array of string arrays");
+ }
+
+ mark_servers(SERV_FROM_DBUS);
+
+ /* array_iter points to each "as" element in the outer array */
+ dbus_message_iter_recurse(&iter, &array_iter);
+ while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID)
+ {
+ const char *str = NULL;
+ union mysockaddr addr, source_addr;
+ u16 flags = 0;
+ char interface[IF_NAMESIZE];
+ char *str_addr, *str_domain = NULL;
+
+ if (strings)
+ {
+ dbus_message_iter_get_basic(&array_iter, &str);
+ if (!str || !strlen (str))
+ {
+ error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Empty string");
+ break;
+ }
+
+ /* dup the string because it gets modified during parsing */
+ if (dup)
+ free(dup);
+ if (!(dup = str_domain = whine_malloc(strlen(str)+1)))
+ break;
+
+ strcpy(str_domain, str);
+
+ /* point to address part of old string for error message */
+ if ((str_addr = strrchr(str, '/')))
+ str = str_addr+1;
+
+ if ((str_addr = strrchr(str_domain, '/')))
+ {
+ if (*str_domain != '/' || str_addr == str_domain)
+ {
+ error = dbus_message_new_error_printf(message,
+ DBUS_ERROR_INVALID_ARGS,
+ "No domain terminator '%s'",
+ str);
+ break;
+ }
+ *str_addr++ = 0;
+ str_domain++;
+ }
+ else
+ {
+ str_addr = str_domain;
+ str_domain = NULL;
+ }
+
+
+ }
+ else
+ {
+ /* check the types of the struct and its elements */
+ if ((dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_ARRAY) ||
+ (dbus_message_iter_get_element_type(&array_iter) != DBUS_TYPE_STRING))
+ {
+ error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected inner array of strings");
+ break;
+ }
+
+ /* string_iter points to each "s" element in the inner array */
+ dbus_message_iter_recurse(&array_iter, &string_iter);
+ if (dbus_message_iter_get_arg_type(&string_iter) != DBUS_TYPE_STRING)
+ {
+ /* no IP address given */
+ error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected IP address");
+ break;
+ }
+
+ dbus_message_iter_get_basic(&string_iter, &str);
+ if (!str || !strlen (str))
+ {
+ error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Empty IP address");
+ break;
+ }
+
+ /* dup the string because it gets modified during parsing */
+ if (dup)
+ free(dup);
+ if (!(dup = str_addr = whine_malloc(strlen(str)+1)))
+ break;
+
+ strcpy(str_addr, str);
+ }
+
+ /* parse the IP address */
+ if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
+ {
+ error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+ "Invalid IP address '%s': %s",
+ str, addr_err);
+ break;
+ }
+
+ /* 0.0.0.0 for server address == NULL, for Dbus */
+ if (addr.in.sin_family == AF_INET &&
+ addr.in.sin_addr.s_addr == 0)
+ flags |= SERV_LITERAL_ADDRESS;
+
+ if (strings)
+ {
+ char *p;
+
+ do {
+ if (str_domain)
+ {
+ if ((p = strchr(str_domain, '/')))
+ *p++ = 0;
+ }
+ else
+ p = NULL;
+
+ add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL);
+ } while ((str_domain = p));
+ }
+ else
+ {
+ /* jump past the address to the domain list (if any) */
+ dbus_message_iter_next (&string_iter);
+
+ /* parse domains and add each server/domain pair to the list */
+ do {
+ str = NULL;
+ if (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING)
+ dbus_message_iter_get_basic(&string_iter, &str);
+ dbus_message_iter_next (&string_iter);
+
+ add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str, NULL);
+ } while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
+ }
+
+ /* jump to next element in outer array */
+ dbus_message_iter_next(&array_iter);
+ }
+
+ cleanup_servers();
+
+ if (dup)
+ free(dup);
+
+ return error;
+}
+
+static DBusMessage *dbus_set_bool(DBusMessage *message, int flag, char *name)
+{
+ DBusMessageIter iter;
+ dbus_bool_t enabled;
+
+ if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, "Expected boolean argument");
+
+ dbus_message_iter_get_basic(&iter, &enabled);
+
+ if (enabled)
+ {
+ my_syslog(LOG_INFO, _("Enabling --%s option from D-Bus"), name);
+ set_option_bool(flag);
+ }
+ else
+ {
+ my_syslog(LOG_INFO, _("Disabling --%s option from D-Bus"), name);
+ reset_option_bool(flag);
+ }
+
+ return NULL;
+}
+
+#ifdef HAVE_DHCP
+static DBusMessage *dbus_add_lease(DBusMessage* message)
+{
+ struct dhcp_lease *lease;
+ const char *ipaddr, *hwaddr, *hostname, *tmp;
+ const unsigned char* clid;
+ int clid_len, hostname_len, hw_len, hw_type;
+ dbus_uint32_t expires, ia_id;
+ dbus_bool_t is_temporary;
+ union all_addr addr;
+ time_t now = dnsmasq_time();
+ unsigned char dhcp_chaddr[DHCP_CHADDR_MAX];
+
+ DBusMessageIter iter, array_iter;
+ if (!dbus_message_iter_init(message, &iter))
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Failed to initialize dbus message iter");
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected string as first argument");
+
+ dbus_message_iter_get_basic(&iter, &ipaddr);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected string as second argument");
+
+ dbus_message_iter_get_basic(&iter, &hwaddr);
+ dbus_message_iter_next(&iter);
+
+ if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+ (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE))
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected byte array as third argument");
+
+ dbus_message_iter_recurse(&iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &hostname, &hostname_len);
+ tmp = memchr(hostname, '\0', hostname_len);
+ if (tmp)
+ {
+ if (tmp == &hostname[hostname_len - 1])
+ hostname_len--;
+ else
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Hostname contains an embedded NUL character");
+ }
+ dbus_message_iter_next(&iter);
+
+ if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+ (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE))
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected byte array as fourth argument");
+
+ dbus_message_iter_recurse(&iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &clid, &clid_len);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected uint32 as fifth argument");
+
+ dbus_message_iter_get_basic(&iter, &expires);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected uint32 as sixth argument");
+
+ dbus_message_iter_get_basic(&iter, &ia_id);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected uint32 as sixth argument");
+
+ dbus_message_iter_get_basic(&iter, &is_temporary);
+
+ if (inet_pton(AF_INET, ipaddr, &addr.addr4))
+ {
+ if (ia_id != 0 || is_temporary)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "ia_id and is_temporary must be zero for IPv4 lease");
+
+ if (!(lease = lease_find_by_addr(addr.addr4)))
+ lease = lease4_allocate(addr.addr4);
+ }
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
+ {
+ if (!(lease = lease6_find_by_addr(&addr.addr6, 128, 0)))
+ lease = lease6_allocate(&addr.addr6,
+ is_temporary ? LEASE_TA : LEASE_NA);
+ lease_set_iaid(lease, ia_id);
+ }
+#endif
+ else
+ return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+ "Invalid IP address '%s'", ipaddr);
+
+ hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, &hw_type);
+ if (hw_type == 0 && hw_len != 0)
+ hw_type = ARPHRD_ETHER;
+
+ lease_set_hwaddr(lease, dhcp_chaddr, clid, hw_len, hw_type,
+ clid_len, now, 0);
+ lease_set_expires(lease, expires, now);
+ if (hostname_len != 0)
+ lease_set_hostname(lease, hostname, 0, get_domain(lease->addr), NULL);
+
+ lease_update_file(now);
+ lease_update_dns(0);
+
+ return NULL;
+}
+
+static DBusMessage *dbus_del_lease(DBusMessage* message)
+{
+ struct dhcp_lease *lease;
+ DBusMessageIter iter;
+ const char *ipaddr;
+ DBusMessage *reply;
+ union all_addr addr;
+ dbus_bool_t ret = 1;
+ time_t now = dnsmasq_time();
+
+ if (!dbus_message_iter_init(message, &iter))
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Failed to initialize dbus message iter");
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+ "Expected string as first argument");
+
+ dbus_message_iter_get_basic(&iter, &ipaddr);
+
+ if (inet_pton(AF_INET, ipaddr, &addr.addr4))
+ lease = lease_find_by_addr(addr.addr4);
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
+ lease = lease6_find_by_addr(&addr.addr6, 128, 0);
+#endif
+ else
+ return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+ "Invalid IP address '%s'", ipaddr);
+
+ if (lease)
+ {
+ lease_prune(lease, now);
+ lease_update_file(now);
+ lease_update_dns(0);
+ }
+ else
+ ret = 0;
+
+ if ((reply = dbus_message_new_method_return(message)))
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &ret,
+ DBUS_TYPE_INVALID);
+
+
+ return reply;
+}
+#endif
+
+static DBusMessage *dbus_get_metrics(DBusMessage* message)
+{
+ DBusMessage *reply = dbus_message_new_method_return(message);
+ DBusMessageIter array, dict, iter;
+ int i;
+
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{su}", &array);
+
+ for (i = 0; i < __METRIC_MAX; i++) {
+ const char *key = get_metric_name(i);
+ dbus_uint32_t value = daemon->metrics[i];
+
+ dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
+ dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &key);
+ dbus_message_iter_append_basic(&dict, DBUS_TYPE_UINT32, &value);
+ dbus_message_iter_close_container(&array, &dict);
+ }
+
+ dbus_message_iter_close_container(&iter, &array);
+
+ return reply;
+}
+
+DBusHandlerResult message_handler(DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ char *method = (char *)dbus_message_get_member(message);
+ DBusMessage *reply = NULL;
+ int clear_cache = 0, new_servers = 0;
+
+ if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect"))
+ {
+ /* string length: "%s" provides space for termination zero */
+ if (!introspection_xml &&
+ (introspection_xml = whine_malloc(strlen(introspection_xml_template) + strlen(daemon->dbus_name))))
+ sprintf(introspection_xml, introspection_xml_template, daemon->dbus_name);
+
+ if (introspection_xml)
+ {
+ reply = dbus_message_new_method_return(message);
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID);
+ }
+ }
+ else if (strcmp(method, "GetVersion") == 0)
+ {
+ char *v = VERSION;
+ reply = dbus_message_new_method_return(message);
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID);
+ }
+#ifdef HAVE_LOOP
+ else if (strcmp(method, "GetLoopServers") == 0)
+ {
+ reply = dbus_reply_server_loop(message);
+ }
+#endif
+ else if (strcmp(method, "SetServers") == 0)
+ {
+ dbus_read_servers(message);
+ new_servers = 1;
+ }
+ else if (strcmp(method, "SetServersEx") == 0)
+ {
+ reply = dbus_read_servers_ex(message, 0);
+ new_servers = 1;
+ }
+ else if (strcmp(method, "SetDomainServers") == 0)
+ {
+ reply = dbus_read_servers_ex(message, 1);
+ new_servers = 1;
+ }
+ else if (strcmp(method, "SetFilterWin2KOption") == 0)
+ {
+ reply = dbus_set_bool(message, OPT_FILTER, "filterwin2k");
+ }
+ else if (strcmp(method, "SetBogusPrivOption") == 0)
+ {
+ reply = dbus_set_bool(message, OPT_BOGUSPRIV, "bogus-priv");
+ }
+#ifdef HAVE_DHCP
+ else if (strcmp(method, "AddDhcpLease") == 0)
+ {
+ reply = dbus_add_lease(message);
+ }
+ else if (strcmp(method, "DeleteDhcpLease") == 0)
+ {
+ reply = dbus_del_lease(message);
+ }
+#endif
+ else if (strcmp(method, "GetMetrics") == 0)
+ {
+ reply = dbus_get_metrics(message);
+ }
+ else if (strcmp(method, "ClearCache") == 0)
+ clear_cache = 1;
+ else
+ return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
+
+ if (new_servers)
+ {
+ my_syslog(LOG_INFO, _("setting upstream servers from DBus"));
+ check_servers(0);
+ if (option_bool(OPT_RELOAD))
+ clear_cache = 1;
+ }
+
+ if (clear_cache)
+ clear_cache_and_reload(dnsmasq_time());
+
+ method = user_data; /* no warning */
+
+ /* If no reply or no error, return nothing */
+ if (!reply)
+ reply = dbus_message_new_method_return(message);
+
+ if (reply)
+ {
+ dbus_connection_send (connection, reply, NULL);
+ dbus_message_unref (reply);
+ }
+
+ 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, daemon->dbus_name, 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, daemon->dbus_name, "Up")))
+ {
+ dbus_connection_send(connection, message, NULL);
+ dbus_message_unref(message);
+ }
+
+ return NULL;
+}
+
+
+void set_dbus_listeners(void)
+{
+ 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);
+
+ if (flags & DBUS_WATCH_READABLE)
+ poll_listen(fd, POLLIN);
+
+ if (flags & DBUS_WATCH_WRITABLE)
+ poll_listen(fd, POLLOUT);
+
+ poll_listen(fd, POLLERR);
+ }
+}
+
+void check_dbus_listeners()
+{
+ 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 (poll_check(fd, POLLIN))
+ flags |= DBUS_WATCH_READABLE;
+
+ if (poll_check(fd, POLLOUT))
+ flags |= DBUS_WATCH_WRITABLE;
+
+ if (poll_check(fd, POLLERR))
+ 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, *mac = daemon->namebuff;
+ unsigned char *p;
+ int i;
+
+ if (!connection)
+ return;
+
+ if (!hostname)
+ hostname = "";
+
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ {
+ print_mac(mac, lease->clid, lease->clid_len);
+ inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+ }
+ else
+#endif
+ {
+ p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len,
+ lease->hwaddr, lease->clid_len, lease->clid, &i);
+ print_mac(mac, p, i);
+ inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
+ }
+
+ if (action == ACTION_DEL)
+ action_str = "DhcpLeaseDeleted";
+ else if (action == ACTION_ADD)
+ action_str = "DhcpLeaseAdded";
+ else if (action == ACTION_OLD)
+ action_str = "DhcpLeaseUpdated";
+ else
+ return;
+
+ if (!(message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, action_str)))
+ return;
+
+ dbus_message_iter_init_append(message, &args);
+
+ if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &daemon->addrbuff) &&
+ 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/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c
new file mode 100755
index 0000000..1f91ca0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c
@@ -0,0 +1,997 @@
+/* dnsmasq is Copyright (c) 2000-2021 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
+
+void dhcp_common_init(void)
+{
+ /* These each hold a DHCP option max size 255
+ and get a terminating zero added */
+ daemon->dhcp_buff = safe_malloc(DHCP_BUFF_SZ);
+ daemon->dhcp_buff2 = safe_malloc(DHCP_BUFF_SZ);
+ daemon->dhcp_buff3 = safe_malloc(DHCP_BUFF_SZ);
+
+ /* dhcp_packet is used by v4 and v6, outpacket only by v6
+ sizeof(struct dhcp_packet) is as good an initial size as any,
+ even for v6 */
+ expand_buf(&daemon->dhcp_packet, sizeof(struct dhcp_packet));
+#ifdef HAVE_DHCP6
+ if (daemon->dhcp6)
+ expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet));
+#endif
+}
+
+ssize_t recv_dhcp_packet(int fd, struct msghdr *msg)
+{
+ ssize_t sz, new_sz;
+
+ while (1)
+ {
+ msg->msg_flags = 0;
+ while ((sz = recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR);
+
+ if (sz == -1)
+ return -1;
+
+ 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 == msg->msg_iov->iov_len)
+ {
+ if (!expand_buf(msg->msg_iov, sz + 100))
+ return -1;
+ }
+ else
+ {
+ expand_buf(msg->msg_iov, sz);
+ break;
+ }
+ }
+
+ while ((new_sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR);
+
+ /* Some kernels seem to ignore MSG_PEEK, and dequeue the packet anyway.
+ If that happens we get EAGAIN here because the socket is non-blocking.
+ Use the result of the original testing recvmsg as long as the buffer
+ was big enough. There's a small race here that may lose the odd packet,
+ but it's UDP anyway. */
+
+ if (new_sz == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
+ new_sz = sz;
+
+ return (msg->msg_flags & MSG_TRUNC) ? -1 : new_sz;
+}
+
+/* like match_netid() except that the check can have a trailing * for wildcard */
+/* started as a direct copy of match_netid() */
+int match_netid_wild(struct dhcp_netid *check, struct dhcp_netid *pool)
+{
+ struct dhcp_netid *tmp1;
+
+ for (; check; check = check->next)
+ {
+ const int check_len = strlen(check->net);
+ const int is_wc = (check->net[check_len - 1] == '*');
+
+ /* '#' for not is for backwards compat. */
+ if (check->net[0] != '!' && check->net[0] != '#')
+ {
+ for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+ if (is_wc ? (strncmp(check->net, tmp1->net, check_len-1) == 0) :
+ (strcmp(check->net, tmp1->net) == 0))
+ break;
+ if (!tmp1)
+ return 0;
+ }
+ else
+ for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+ if (is_wc ? (strncmp((check->net)+1, tmp1->net, check_len-2) == 0) :
+ (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;
+
+ /* this now uses match_netid_wild() above so that tag_if can
+ * be used to set a 'group of interfaces' tag.
+ */
+ for (exprs = daemon->tag_if; exprs; exprs = exprs->next)
+ if (match_netid_wild(exprs->tag, tags))
+ for (list = exprs->set; list; list = list->next)
+ {
+ list->list->next = tags;
+ tags = list->list;
+ }
+
+ return tags;
+}
+
+
+struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts)
+{
+ struct dhcp_netid *tagif = run_tag_if(tags);
+ struct dhcp_opt *opt;
+ struct dhcp_opt *tmp;
+
+ /* flag options which are valid with the current tag set (sans context tags) */
+ for (opt = opts; opt; opt = opt->next)
+ {
+ opt->flags &= ~DHOPT_TAGOK;
+ if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) &&
+ match_netid(opt->netid, tagif, 0))
+ opt->flags |= DHOPT_TAGOK;
+ }
+
+ /* now flag options which are valid, including the context tags,
+ otherwise valid options are inhibited if we found a higher priority one above */
+ if (context_tags)
+ {
+ struct dhcp_netid *last_tag;
+
+ for (last_tag = context_tags; last_tag->next; last_tag = last_tag->next);
+ last_tag->next = tags;
+ tagif = run_tag_if(context_tags);
+
+ /* reset stuff with tag:!<tag> which now matches. */
+ for (opt = opts; opt; opt = opt->next)
+ if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) &&
+ (opt->flags & DHOPT_TAGOK) &&
+ !match_netid(opt->netid, tagif, 0))
+ opt->flags &= ~DHOPT_TAGOK;
+
+ for (opt = opts; opt; opt = opt->next)
+ if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) &&
+ match_netid(opt->netid, tagif, 0))
+ {
+ struct dhcp_opt *tmp;
+ for (tmp = opts; tmp; tmp = tmp->next)
+ if (tmp->opt == opt->opt && opt->netid && (tmp->flags & DHOPT_TAGOK))
+ break;
+ if (!tmp)
+ opt->flags |= DHOPT_TAGOK;
+ }
+ }
+
+ /* now flag untagged options which are not overridden by tagged ones */
+ for (opt = opts; opt; opt = opt->next)
+ if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && !opt->netid)
+ {
+ for (tmp = opts; tmp; tmp = tmp->next)
+ if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK))
+ break;
+ if (!tmp)
+ opt->flags |= DHOPT_TAGOK;
+ else if (!tmp->netid)
+ my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring duplicate dhcp-option %d"), tmp->opt);
+ }
+
+ /* Finally, eliminate duplicate options later in the chain, and therefore earlier in the config file. */
+ for (opt = opts; opt; opt = opt->next)
+ if (opt->flags & DHOPT_TAGOK)
+ for (tmp = opt->next; tmp; tmp = tmp->next)
+ if (tmp->opt == opt->opt)
+ tmp->flags &= ~DHOPT_TAGOK;
+
+ return tagif;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+void log_tags(struct dhcp_netid *netid, u32 xid)
+{
+ if (netid && option_bool(OPT_LOG_OPTS))
+ {
+ char *s = daemon->namebuff;
+ for (*s = 0; netid; netid = netid->next)
+ {
+ /* kill dupes. */
+ struct dhcp_netid *n;
+
+ 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"), xid, s);
+ }
+}
+
+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;
+}
+
+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;
+}
+
+static int is_config_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 | CONFIG_ADDR6)))
+ return 1;
+
+#ifdef HAVE_DHCP6
+ if (context->flags & CONTEXT_V6)
+ {
+ struct addrlist *addr_list;
+
+ if (config->flags & CONFIG_ADDR6)
+ for (; context; context = context->current)
+ for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+ {
+ if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+ return 1;
+
+ if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix))
+ return 1;
+ }
+ }
+ else
+#endif
+ {
+ for (; context; context = context->current)
+ if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask))
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct dhcp_config *find_config_match(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,
+ struct dhcp_netid *tags, int tag_not_needed)
+{
+ 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_config_in_context(context, config) &&
+ match_netid(config->filter, tags, tag_not_needed))
+
+ return config;
+
+ /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and
+ cope with that here. This is IPv4 only. context==NULL implies IPv4,
+ see lease_update_from_configs() */
+ if ((!context || !(context->flags & CONTEXT_V6)) && *clid == 0 && config->clid_len == clid_len-1 &&
+ memcmp(config->clid, clid+1, clid_len-1) == 0 &&
+ is_config_in_context(context, config) &&
+ match_netid(config->filter, tags, tag_not_needed))
+ return config;
+ }
+
+
+ if (hwaddr)
+ for (config = configs; config; config = config->next)
+ if (config_has_mac(config, hwaddr, hw_len, hw_type) &&
+ is_config_in_context(context, config) &&
+ match_netid(config->filter, tags, tag_not_needed))
+ return config;
+
+ if (hostname && context)
+ for (config = configs; config; config = config->next)
+ if ((config->flags & CONFIG_NAME) &&
+ hostname_isequal(config->hostname, hostname) &&
+ is_config_in_context(context, config) &&
+ match_netid(config->filter, tags, tag_not_needed))
+ return config;
+
+
+ if (!hwaddr)
+ return NULL;
+
+ /* use match with fewest wildcard octets */
+ for (candidate = NULL, count = 0, config = configs; config; config = config->next)
+ if (is_config_in_context(context, config) &&
+ match_netid(config->filter, tags, tag_not_needed))
+ 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;
+}
+
+/* Find tagged configs first. */
+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, struct dhcp_netid *tags)
+{
+ struct dhcp_config *ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 0);
+
+ if (!ret)
+ ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 1);
+
+ return ret;
+}
+
+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, *conf_tmp;
+ struct crec *crec;
+ int prot = AF_INET;
+
+ for (config = configs; config; config = config->next)
+ {
+ if (config->flags & CONFIG_ADDR_HOSTS)
+ config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS);
+#ifdef HAVE_DHCP6
+ if (config->flags & CONFIG_ADDR6_HOSTS)
+ config->flags &= ~(CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS);
+#endif
+ }
+
+#ifdef HAVE_DHCP6
+ again:
+#endif
+
+ if (daemon->port != 0)
+ for (config = configs; config; config = config->next)
+ {
+ int conflags = CONFIG_ADDR;
+ int cacheflags = F_IPV4;
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6)
+ {
+ conflags = CONFIG_ADDR6;
+ cacheflags = F_IPV6;
+ }
+#endif
+ if (!(config->flags & conflags) &&
+ (config->flags & CONFIG_NAME) &&
+ (crec = cache_find_by_name(NULL, config->hostname, 0, cacheflags)) &&
+ (crec->flags & F_HOSTS))
+ {
+ if (cache_find_by_name(crec, config->hostname, 0, cacheflags))
+ {
+ /* use primary (first) address */
+ while (crec && !(crec->flags & F_REVERSE))
+ crec = cache_find_by_name(crec, config->hostname, 0, cacheflags);
+ if (!crec)
+ continue; /* should be never */
+ inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"),
+ config->hostname, daemon->addrbuff);
+ }
+
+ if (prot == AF_INET &&
+ (!(conf_tmp = config_find_by_address(configs, crec->addr.addr4)) || conf_tmp == config))
+ {
+ config->addr = crec->addr.addr4;
+ config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS;
+ continue;
+ }
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6 &&
+ (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config))
+ {
+ /* host must have exactly one address if comming from /etc/hosts. */
+ if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist))))
+ {
+ config->addr6->next = NULL;
+ config->addr6->flags = 0;
+ }
+
+ if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX)))
+ {
+ memcpy(&config->addr6->addr.addr6, &crec->addr.addr6, IN6ADDRSZ);
+ config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS;
+ }
+
+ continue;
+ }
+#endif
+
+ inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"),
+ daemon->addrbuff, config->hostname);
+
+
+ }
+ }
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET)
+ {
+ prot = AF_INET6;
+ goto again;
+ }
+#endif
+
+}
+
+#ifdef HAVE_LINUX_NETWORK
+char *whichdevice(void)
+{
+ /* If we are doing DHCP on exactly one interface, and running linux, do SO_BINDTODEVICE
+ to that device. This is for the use case of (eg) OpenStack, which runs a new
+ dnsmasq instance for each VLAN interface it creates. Without the BINDTODEVICE,
+ individual processes don't always see the packets they should.
+ SO_BINDTODEVICE is only available Linux.
+
+ Note that if wildcards are used in --interface, or --interface is not used at all,
+ or a configured interface doesn't yet exist, then more interfaces may arrive later,
+ so we can't safely assert there is only one interface and proceed.
+*/
+
+ struct irec *iface, *found;
+ struct iname *if_tmp;
+
+ if (!daemon->if_names)
+ return NULL;
+
+ for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+ if (if_tmp->name && (!if_tmp->used || strchr(if_tmp->name, '*')))
+ return NULL;
+
+ for (found = NULL, iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->dhcp_ok)
+ {
+ if (!found)
+ found = iface;
+ else if (strcmp(found->name, iface->name) != 0)
+ return NULL; /* more than one. */
+ }
+
+ if (found)
+ return found->name;
+
+ return NULL;
+}
+
+void bindtodevice(char *device, int fd)
+{
+ size_t len = strlen(device)+1;
+ if (len > IFNAMSIZ)
+ len = IFNAMSIZ;
+ /* only allowed by root. */
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, len) == -1 &&
+ errno != EPERM)
+ die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET);
+}
+#endif
+
+static const struct opttab_t {
+ char *name;
+ u16 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 | OT_DEC },
+ { "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 | OT_DEC },
+ { "default-ttl", 23, 1 | OT_DEC },
+ { "mtu", 26, 2 | OT_DEC },
+ { "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 | OT_DEC },
+ { "ethernet-encap", 36, 1 },
+ { "tcp-ttl", 37, 1 },
+ { "tcp-keepalive", 38, 4 | OT_DEC },
+ { "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 | OT_TIME },
+ { "option-overload", 52, OT_INTERNAL },
+ { "message-type", 53, OT_INTERNAL | OT_DEC },
+ { "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_TIME},
+ { "T2", 59, OT_TIME},
+ { "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 },
+ { "rapid-commit", 80, 0 },
+ { "FQDN", 81, OT_INTERNAL },
+ { "agent-id", 82, OT_INTERNAL },
+ { "client-arch", 93, 2 | OT_DEC },
+ { "client-interface-id", 94, 0 },
+ { "client-machine-id", 97, 0 },
+ { "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
+ { "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
+ { "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 },
+ { "tftp-server-address", 150, OT_ADDR_LIST },
+ { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */
+ { NULL, 0, 0 }
+};
+
+#ifdef HAVE_DHCP6
+static const struct opttab_t opttab6[] = {
+ { "client-id", 1, OT_INTERNAL },
+ { "server-id", 2, OT_INTERNAL },
+ { "ia-na", 3, OT_INTERNAL },
+ { "ia-ta", 4, OT_INTERNAL },
+ { "iaaddr", 5, OT_INTERNAL },
+ { "oro", 6, OT_INTERNAL },
+ { "preference", 7, OT_INTERNAL | OT_DEC },
+ { "unicast", 12, OT_INTERNAL },
+ { "status", 13, OT_INTERNAL },
+ { "rapid-commit", 14, OT_INTERNAL },
+ { "user-class", 15, OT_INTERNAL | OT_CSTRING },
+ { "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
+ { "vendor-opts", 17, OT_INTERNAL },
+ { "sip-server-domain", 21, OT_RFC1035_NAME },
+ { "sip-server", 22, OT_ADDR_LIST },
+ { "dns-server", 23, OT_ADDR_LIST },
+ { "domain-search", 24, OT_RFC1035_NAME },
+ { "nis-server", 27, OT_ADDR_LIST },
+ { "nis+-server", 28, OT_ADDR_LIST },
+ { "nis-domain", 29, OT_RFC1035_NAME },
+ { "nis+-domain", 30, OT_RFC1035_NAME },
+ { "sntp-server", 31, OT_ADDR_LIST },
+ { "information-refresh-time", 32, OT_TIME },
+ { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME },
+ { "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
+ { "bootfile-url", 59, OT_NAME },
+ { "bootfile-param", 60, OT_CSTRING },
+ { NULL, 0, 0 }
+};
+#endif
+
+
+
+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);
+}
+
+#ifdef HAVE_DHCP6
+void display_opts6(void)
+{
+ int i;
+ printf(_("Known DHCPv6 options:\n"));
+
+ for (i = 0; opttab6[i].name; i++)
+ if (!(opttab6[i].size & OT_INTERNAL))
+ printf("%3d %s\n", opttab6[i].val, opttab6[i].name);
+}
+#endif
+
+int lookup_dhcp_opt(int prot, char *name)
+{
+ const struct opttab_t *t;
+ int i;
+
+ (void)prot;
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6)
+ t = opttab6;
+ else
+#endif
+ t = opttab;
+
+ for (i = 0; t[i].name; i++)
+ if (strcasecmp(t[i].name, name) == 0)
+ return t[i].val;
+
+ return -1;
+}
+
+int lookup_dhcp_len(int prot, int val)
+{
+ const struct opttab_t *t;
+ int i;
+
+ (void)prot;
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6)
+ t = opttab6;
+ else
+#endif
+ t = opttab;
+
+ for (i = 0; t[i].name; i++)
+ if (val == t[i].val)
+ return t[i].size & ~OT_DEC;
+
+ return 0;
+}
+
+char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, char *buf, int buf_len)
+{
+ int o, i, j, nodecode = 0;
+ const struct opttab_t *ot = opttab;
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6)
+ ot = opttab6;
+#endif
+
+ for (o = 0; ot[o].name; o++)
+ if (ot[o].val == opt)
+ {
+ if (buf)
+ {
+ memset(buf, 0, buf_len);
+
+ if (ot[o].size & OT_ADDR_LIST)
+ {
+ union all_addr addr;
+ int addr_len = INADDRSZ;
+
+#ifdef HAVE_DHCP6
+ if (prot == AF_INET6)
+ addr_len = IN6ADDRSZ;
+#endif
+ for (buf[0]= 0, i = 0; i <= opt_len - addr_len; i += addr_len)
+ {
+ if (i != 0)
+ strncat(buf, ", ", buf_len - strlen(buf));
+ /* align */
+ memcpy(&addr, &val[i], addr_len);
+ inet_ntop(prot, &val[i], daemon->addrbuff, ADDRSTRLEN);
+ strncat(buf, daemon->addrbuff, buf_len - strlen(buf));
+ }
+ }
+ else if (ot[o].size & OT_NAME)
+ for (i = 0, j = 0; i < opt_len && j < buf_len ; i++)
+ {
+ char c = val[i];
+ if (isprint((int)c))
+ buf[j++] = c;
+ }
+#ifdef HAVE_DHCP6
+ /* We don't handle compressed rfc1035 names, so no good in IPv4 land */
+ else if ((ot[o].size & OT_RFC1035_NAME) && prot == AF_INET6)
+ {
+ i = 0, j = 0;
+ while (i < opt_len && val[i] != 0)
+ {
+ int k, l = i + val[i] + 1;
+ for (k = i + 1; k < opt_len && k < l && j < buf_len ; k++)
+ {
+ char c = val[k];
+ if (isprint((int)c))
+ buf[j++] = c;
+ }
+ i = l;
+ if (val[i] != 0 && j < buf_len)
+ buf[j++] = '.';
+ }
+ }
+ else if ((ot[o].size & OT_CSTRING))
+ {
+ int k, len;
+ unsigned char *p;
+
+ i = 0, j = 0;
+ while (1)
+ {
+ p = &val[i];
+ GETSHORT(len, p);
+ for (k = 0; k < len && j < buf_len; k++)
+ {
+ char c = *p++;
+ if (isprint((int)c))
+ buf[j++] = c;
+ }
+ i += len +2;
+ if (i >= opt_len)
+ break;
+
+ if (j < buf_len)
+ buf[j++] = ',';
+ }
+ }
+#endif
+ else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
+ {
+ unsigned int dec = 0;
+
+ for (i = 0; i < opt_len; i++)
+ dec = (dec << 8) | val[i];
+
+ if (ot[o].size & OT_TIME)
+ prettyprint_time(buf, dec);
+ else
+ sprintf(buf, "%u", dec);
+ }
+ else
+ nodecode = 1;
+ }
+ break;
+ }
+
+ if (opt_len != 0 && buf && (!ot[o].name || nodecode))
+ {
+ int trunc = 0;
+ if (opt_len > 14)
+ {
+ trunc = 1;
+ opt_len = 14;
+ }
+ print_mac(buf, val, opt_len);
+ if (trunc)
+ strncat(buf, "...", buf_len - strlen(buf));
+
+
+ }
+
+ return ot[o].name ? ot[o].name : "";
+
+}
+
+void log_context(int family, struct dhcp_context *context)
+{
+ /* Cannot use dhcp_buff* for RA contexts */
+
+ void *start = &context->start;
+ void *end = &context->end;
+ char *template = "", *p = daemon->namebuff;
+
+ *p = 0;
+
+#ifdef HAVE_DHCP6
+ if (family == AF_INET6)
+ {
+ struct in6_addr subnet = context->start6;
+ if (!(context->flags & CONTEXT_TEMPLATE))
+ setaddr6part(&subnet, 0);
+ inet_ntop(AF_INET6, &subnet, daemon->addrbuff, ADDRSTRLEN);
+ start = &context->start6;
+ end = &context->end6;
+ }
+#endif
+
+ if (family != AF_INET && (context->flags & CONTEXT_DEPRECATE))
+ strcpy(daemon->namebuff, _(", prefix deprecated"));
+ else
+ {
+ p += sprintf(p, _(", lease time "));
+ prettyprint_time(p, context->lease_time);
+ p += strlen(p);
+ }
+
+#ifdef HAVE_DHCP6
+ if (context->flags & CONTEXT_CONSTRUCTED)
+ {
+ char ifrn_name[IFNAMSIZ];
+
+ template = p;
+ p += sprintf(p, ", ");
+
+ if (indextoname(daemon->icmp6fd, context->if_index, ifrn_name))
+ sprintf(p, "%s for %s", (context->flags & CONTEXT_OLD) ? "old prefix" : "constructed", ifrn_name);
+ }
+ else if (context->flags & CONTEXT_TEMPLATE && !(context->flags & CONTEXT_RA_STATELESS))
+ {
+ template = p;
+ p += sprintf(p, ", ");
+
+ sprintf(p, "template for %s", context->template_interface);
+ }
+#endif
+
+ if (!(context->flags & CONTEXT_OLD) &&
+ ((context->flags & CONTEXT_DHCP) || family == AF_INET))
+ {
+#ifdef HAVE_DHCP6
+ if (context->flags & CONTEXT_RA_STATELESS)
+ {
+ if (context->flags & CONTEXT_TEMPLATE)
+ strncpy(daemon->dhcp_buff, context->template_interface, DHCP_BUFF_SZ);
+ else
+ strcpy(daemon->dhcp_buff, daemon->addrbuff);
+ }
+ else
+#endif
+ inet_ntop(family, start, daemon->dhcp_buff, DHCP_BUFF_SZ);
+ inet_ntop(family, end, daemon->dhcp_buff3, DHCP_BUFF_SZ);
+ my_syslog(MS_DHCP | LOG_INFO,
+ (context->flags & CONTEXT_RA_STATELESS) ?
+ _("%s stateless on %s%.0s%.0s%s") :
+ (context->flags & CONTEXT_STATIC) ?
+ _("%s, static leases only on %.0s%s%s%.0s") :
+ (context->flags & CONTEXT_PROXY) ?
+ _("%s, proxy on subnet %.0s%s%.0s%.0s") :
+ _("%s, IP range %s -- %s%s%.0s"),
+ (family != AF_INET) ? "DHCPv6" : "DHCP",
+ daemon->dhcp_buff, daemon->dhcp_buff3, daemon->namebuff, template);
+ }
+
+#ifdef HAVE_DHCP6
+ if (context->flags & CONTEXT_TEMPLATE)
+ {
+ strcpy(daemon->addrbuff, context->template_interface);
+ template = "";
+ }
+
+ if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD))
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCPv4-derived IPv6 names on %s%s"), daemon->addrbuff, template);
+
+ if ((context->flags & CONTEXT_RA) || (option_bool(OPT_RA) && (context->flags & CONTEXT_DHCP) && family == AF_INET6))
+ my_syslog(MS_DHCP | LOG_INFO, _("router advertisement on %s%s"), daemon->addrbuff, template);
+#endif
+
+}
+
+void log_relay(int family, struct dhcp_relay *relay)
+{
+ inet_ntop(family, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(family, &relay->server, daemon->namebuff, ADDRSTRLEN);
+
+ if (relay->interface)
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface);
+ else
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff);
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h
new file mode 100755
index 0000000..6ff3ffa
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h
@@ -0,0 +1,101 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+#define DHCP_SERVER_ALTPORT 1067
+#define DHCP_CLIENT_ALTPORT 1068
+#define PXE_PORT 4011
+
+/* These each hold a DHCP option max size 255
+ and get a terminating zero added */
+#define DHCP_BUFF_SZ 256
+
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+#define DHCP_COOKIE 0x63825363
+
+/* 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_RAPID_COMMIT 80
+#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/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c
new file mode 100755
index 0000000..e500bc2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c
@@ -0,0 +1,1135 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 dhcp_context *current;
+ struct dhcp_relay *relay;
+ struct in_addr relay_local;
+ int ind;
+};
+
+struct match_param {
+ int ind, matched;
+ struct in_addr netmask, broadcast, addr;
+};
+
+static int complete_context(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam);
+static int check_listen_addrs(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam);
+static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index);
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
+
+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 defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+ int tos = IPTOS_CLASS_CS6;
+#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, IPPROTO_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 ||
+#endif
+#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+ setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1 ||
+#endif
+#if defined(HAVE_LINUX_NETWORK)
+ setsockopt(fd, IPPROTO_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 dnsmasq
+ instance binding port 67. That's OK if they serve different networks.
+ Need to set REUSEADDR|REUSEPORT to make this possible.
+ Handle the case that REUSEPORT is defined, but the kernel doesn't
+ support it. This handles the introduction of REUSEPORT on Linux. */
+ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
+ {
+ int rc = 0;
+
+#ifdef SO_REUSEPORT
+ if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 &&
+ errno == ENOPROTOOPT)
+ rc = 0;
+#endif
+
+ if (rc != -1)
+ rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt));
+
+ 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
+}
+
+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 dhcp_relay *relay;
+ int is_relay_reply = 0;
+ 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, loopback = 0;
+ int rcvd_iface_index;
+ struct in_addr iface_addr;
+ struct iface_param parm;
+ time_t recvtime = now;
+#ifdef HAVE_LINUX_NETWORK
+ struct arpreq arp_req;
+ struct timeval tv;
+#endif
+
+ union {
+ 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;
+ struct dhcp_bridge *bridge, *alias;
+
+ msg.msg_controllen = sizeof(control_u);
+ msg.msg_control = control_u.control;
+ msg.msg_name = &dest;
+ msg.msg_namelen = sizeof(dest);
+ msg.msg_iov = &daemon->dhcp_packet;
+ msg.msg_iovlen = 1;
+
+ if ((sz = recv_dhcp_packet(fd, &msg)) == -1 ||
+ (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options))))
+ return;
+
+ #if defined (HAVE_LINUX_NETWORK)
+ if (ioctl(fd, SIOCGSTAMP, &tv) == 0)
+ recvtime = tv.tv_sec;
+
+ if (msg.msg_controllen >= sizeof(struct cmsghdr))
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+ {
+ 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) ||
+ ioctl(daemon->dhcpfd, SIOCGIFFLAGS, &ifr) != 0)
+ return;
+
+ mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+ loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK);
+
+#ifdef HAVE_LINUX_NETWORK
+ /* ARP fiddling uses original interface even if we pretend to use a different one. */
+ safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
+#endif
+
+ /* If the interface on which the DHCP request was received is an
+ alias of some other interface (as specified by the
+ --bridge-interface option), change ifr.ifr_name so that we look
+ for DHCP contexts associated with the aliased interface instead
+ of with the aliasing one. */
+ rcvd_iface_index = iface_index;
+ for (bridge = daemon->bridges; bridge; bridge = bridge->next)
+ {
+ for (alias = bridge->alias; alias; alias = alias->next)
+ if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE))
+ {
+ if (!(iface_index = if_nametoindex(bridge->iface)))
+ {
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("unknown interface %s in bridge-interface"),
+ bridge->iface);
+ return;
+ }
+ else
+ {
+ safe_strncpy(ifr.ifr_name, bridge->iface, sizeof(ifr.ifr_name));
+ break;
+ }
+ }
+
+ if (alias)
+ break;
+ }
+
+#ifdef MSG_BCAST
+ /* OpenBSD tells us when a packet was broadcast */
+ if (!(msg.msg_flags & MSG_BCAST))
+ unicast_dest = 1;
+#endif
+
+ if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
+ {
+ /* Reply from server, using us as relay. */
+ rcvd_iface_index = relay->iface_index;
+ if (!indextoname(daemon->dhcpfd, rcvd_iface_index, ifr.ifr_name))
+ return;
+ is_relay_reply = 1;
+ iov.iov_len = sz;
+#ifdef HAVE_LINUX_NETWORK
+ safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
+#endif
+ }
+ else
+ {
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
+ iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
+ else
+ {
+ if (iface_check(AF_INET, NULL, ifr.ifr_name, NULL))
+ my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
+ return;
+ }
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ return;
+
+ /* unlinked contexts/relays are marked by context->current == context */
+ for (context = daemon->dhcp; context; context = context->next)
+ context->current = context;
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ relay->current = relay;
+
+ parm.current = NULL;
+ parm.relay = NULL;
+ parm.relay_local.s_addr = 0;
+ parm.ind = iface_index;
+
+ if (!iface_check(AF_INET, (union all_addr *)&iface_addr, ifr.ifr_name, NULL))
+ {
+ /* If we failed to match the primary address of the interface, see if we've got a --listen-address
+ for a secondary */
+ struct match_param match;
+
+ match.matched = 0;
+ match.ind = iface_index;
+
+ if (!daemon->if_addrs ||
+ !iface_enumerate(AF_INET, &match, check_listen_addrs) ||
+ !match.matched)
+ return;
+
+ iface_addr = match.addr;
+ /* make sure secondary address gets priority in case
+ there is more than one address on the interface in the same subnet */
+ complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
+ }
+
+ if (!iface_enumerate(AF_INET, &parm, complete_context))
+ return;
+
+ /* We're relaying this request */
+ if (parm.relay_local.s_addr != 0 &&
+ relay_upstream4(parm.relay, mess, (size_t)sz, iface_index))
+ return;
+
+ /* May have configured relay, but not DHCP server */
+ if (!daemon->dhcp)
+ 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, loopback, &is_inform, pxe_fd, iface_addr, recvtime);
+ lease_update_file(now);
+ lease_update_dns(0);
+
+ if (iov.iov_len == 0)
+ return;
+ }
+
+ msg.msg_name = &dest;
+ msg.msg_namelen = sizeof(dest);
+ msg.msg_control = NULL;
+ 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 && !is_relay_reply)
+ {
+ /* 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 || is_relay_reply)
+ {
+ dest.sin_port = htons(daemon->dhcp_client_port);
+ dest.sin_addr = mess->ciaddr;
+ }
+ }
+#if defined(HAVE_LINUX_NETWORK)
+ else
+ {
+ /* fill cmsg for outbound interface (both broadcast & unicast) */
+ 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 = rcvd_iface_index;
+ pkt->ipi_spec_dst.s_addr = 0;
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+ cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ cmptr->cmsg_level = IPPROTO_IP;
+ cmptr->cmsg_type = IP_PKTINFO;
+
+ 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) */
+ 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;
+ if (ioctl(daemon->dhcpfd, SIOCSARP, &arp_req) == -1)
+ my_syslog(MS_DHCP | LOG_ERR, _("ARP-cache injection failed: %s"), strerror(errno));
+ }
+ }
+#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(retry_send(sendmsg(fd, &msg, 0)));
+
+ /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */
+ if (errno != 0)
+ {
+ inet_ntop(AF_INET, &dest.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"),
+ daemon->addrbuff, strerror(errno));
+ }
+}
+
+/* check against secondary interface addresses */
+static int check_listen_addrs(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+ struct match_param *param = vparam;
+ struct iname *tmp;
+
+ (void) label;
+
+ if (if_index == param->ind)
+ {
+ for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+ if ( tmp->addr.sa.sa_family == AF_INET &&
+ tmp->addr.in.sin_addr.s_addr == local.s_addr)
+ {
+ param->matched = 1;
+ param->addr = local;
+ param->netmask = netmask;
+ param->broadcast = broadcast;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+/* 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 superseded later for configured hosts or those coming via gateways. */
+
+static void guess_range_netmask(struct in_addr addr, struct in_addr netmask)
+{
+ struct dhcp_context *context;
+
+ for (context = daemon->dhcp; context; context = context->next)
+ if (!(context->flags & CONTEXT_NETMASK) &&
+ (is_same_net(addr, context->start, netmask) ||
+ is_same_net(addr, context->end, netmask)))
+ {
+ if (context->netmask.s_addr != netmask.s_addr &&
+ !(is_same_net(addr, context->start, netmask) &&
+ is_same_net(addr, context->end, netmask)))
+ {
+ inet_ntop(AF_INET, &context->start, daemon->dhcp_buff, DHCP_BUFF_SZ);
+ inet_ntop(AF_INET, &context->end, daemon->dhcp_buff2, DHCP_BUFF_SZ);
+ inet_ntop(AF_INET, &netmask, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"),
+ daemon->dhcp_buff, daemon->dhcp_buff2, daemon->addrbuff);
+ }
+ context->netmask = netmask;
+ }
+}
+
+static int complete_context(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+ struct dhcp_context *context;
+ struct dhcp_relay *relay;
+ struct iface_param *param = vparam;
+ struct shared_network *share;
+
+ (void)label;
+
+ for (share = daemon->shared_networks; share; share = share->next)
+ {
+
+#ifdef HAVE_DHCP6
+ if (share->shared_addr.s_addr == 0)
+ continue;
+#endif
+
+ if (share->if_index != 0)
+ {
+ if (share->if_index != if_index)
+ continue;
+ }
+ else
+ {
+ if (share->match_addr.s_addr != local.s_addr)
+ continue;
+ }
+
+ for (context = daemon->dhcp; context; context = context->next)
+ {
+ if (context->netmask.s_addr != 0 &&
+ is_same_net(share->shared_addr, context->start, context->netmask) &&
+ is_same_net(share->shared_addr, context->end, context->netmask))
+ {
+ /* link it onto the current chain if we've not seen it before */
+ if (context->current == context)
+ {
+ /* For a shared network, we have no way to guess what the default route should be. */
+ context->router.s_addr = 0;
+ context->local = local; /* Use configured address for Server Identifier */
+ context->current = param->current;
+ param->current = context;
+ }
+
+ if (!(context->flags & CONTEXT_BRDCAST))
+ context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr;
+ }
+ }
+ }
+
+ guess_range_netmask(local, netmask);
+
+ for (context = daemon->dhcp; context; context = context->next)
+ {
+ if (context->netmask.s_addr != 0 &&
+ 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;
+ }
+ }
+ }
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ if (if_index == param->ind && relay->local.addr4.s_addr == local.s_addr && relay->current == relay &&
+ (param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr))
+ {
+ relay->current = param->relay;
+ param->relay = relay;
+ param->relay_local = local;
+ }
+
+ 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 | CONTEXT_PROXY)) &&
+ 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 corresponding to that
+ address. Note that none may fit, if the address came a dhcp-host and is outside
+ any dhcp-range. In that case we return a static range if possible, or failing that,
+ any context on the correct subnet. (If there's more than one, this is a dodgy
+ 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) &&
+ !(tmp->flags & CONTEXT_PROXY))
+ 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;
+}
+
+/* Check if and address is in use by sending ICMP ping.
+ This wrapper handles a cache and load-limiting.
+ Return is NULL is address in use, or a pointer to a cache entry
+ recording that it isn't. */
+struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopback)
+{
+ static struct ping_result dummy;
+ struct ping_result *r, *victim = NULL;
+ int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/
+ ((float)PING_WAIT)));
+
+ /* check if we failed to ping addr sometime in the last
+ PING_CACHE_TIME seconds. If so, assume the same situation still exists.
+ This avoids problems when a stupid client bangs
+ on us repeatedly. As a final check, if we did more
+ than 60% of the possible ping checks in the last
+ PING_CACHE_TIME, we are in high-load mode, so don't do any more. */
+ for (count = 0, r = daemon->ping_results; r; r = r->next)
+ if (difftime(now, r->time) > (float)PING_CACHE_TIME)
+ victim = r; /* old record */
+ else
+ {
+ count++;
+ if (r->addr.s_addr == addr.s_addr)
+ return r;
+ }
+
+ /* didn't find cached entry */
+ if ((count >= max) || option_bool(OPT_NO_PING) || loopback)
+ {
+ /* overloaded, or configured not to check, loopback interface, return "not in use" */
+ dummy.hash = hash;
+ return &dummy;
+ }
+ else if (icmp_ping(addr))
+ return NULL; /* address in use. */
+ else
+ {
+ /* at this point victim may hold an expired record */
+ if (!victim)
+ {
+ if ((victim = whine_malloc(sizeof(struct ping_result))))
+ {
+ victim->next = daemon->ping_results;
+ daemon->ping_results = victim;
+ }
+ }
+
+ /* record that this address is OK for 30s
+ without more ping checks */
+ if (victim)
+ {
+ victim->addr = addr;
+ victim->time = now;
+ victim->hash = hash;
+ }
+ return victim;
+ }
+}
+
+int address_allocate(struct dhcp_context *context,
+ struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
+ struct dhcp_netid *netids, time_t now, int loopback)
+{
+ /* 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;
+
+ /* j == 0 is marker */
+ if (j == 0)
+ j = 1;
+
+ for (pass = 0; pass <= 1; pass++)
+ for (c = context; c; c = c->current)
+ if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+ continue;
+ else if (!match_netid(c->filter, netids, pass))
+ continue;
+ else
+ {
+ if (option_bool(OPT_CONSEC_ADDR))
+ /* seed is largest extant lease addr in this context */
+ start = lease_find_max_addr(c);
+ else
+ /* pick a seed based on hwaddr */
+ start.s_addr = htonl(ntohl(c->start.s_addr) +
+ ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr))));
+
+ /* iterate until we find a free address. */
+ addr = start;
+
+ 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))))
+ {
+ /* in consec-ip mode, skip addresses equal to
+ the number of addresses rejected by clients. This
+ should avoid the same client being offered the same
+ address after it has rjected it. */
+ if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch)
+ c->addr_epoch--;
+ else
+ {
+ struct ping_result *r;
+
+ if ((r = do_icmp_ping(now, addr, j, loopback)))
+ {
+ /* consec-ip mode: we offered this address for another client
+ (different hash) recently, don't offer it to this one. */
+ if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j)
+ {
+ *addrp = addr;
+ return 1;
+ }
+ }
+ else
+ {
+ /* address in use: perturb address selection so that we are
+ less likely to try this address again. */
+ if (!option_bool(OPT_CONSEC_ADDR))
+ c->addr_epoch++;
+ }
+ }
+ }
+
+ addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
+
+ if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1))
+ addr = c->start;
+
+ } while (addr.s_addr != start.s_addr);
+ }
+
+ return 0;
+}
+
+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 (inet_pton(AF_INET, ip, &addr.s_addr) < 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);
+}
+
+
+/* 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, (union 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;
+
+ safe_strncpy(daemon->dhcp_buff, hostname, 256);
+ strip_hostname(daemon->dhcp_buff);
+
+ return daemon->dhcp_buff;
+ }
+
+ return NULL;
+}
+
+static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index)
+{
+ /* ->local is same value for all relays on ->current chain */
+ union all_addr from;
+
+ if (mess->op != BOOTREQUEST)
+ return 0;
+
+ /* source address == relay address */
+ from.addr4 = relay->local.addr4;
+
+ /* already gatewayed ? */
+ if (mess->giaddr.s_addr)
+ {
+ /* if so check if by us, to stomp on loops. */
+ if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
+ return 1;
+ }
+ else
+ {
+ /* plug in our address */
+ mess->giaddr.s_addr = relay->local.addr4.s_addr;
+ }
+
+ if ((mess->hops++) > 20)
+ return 1;
+
+ for (; relay; relay = relay->current)
+ {
+ union mysockaddr to;
+
+ to.sa.sa_family = AF_INET;
+ to.in.sin_addr = relay->server.addr4;
+ to.in.sin_port = htons(daemon->dhcp_server_port);
+
+ send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0);
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ);
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2);
+ }
+
+ /* Save this for replies */
+ relay->iface_index = iface_index;
+ }
+
+ return 1;
+}
+
+
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface)
+{
+ struct dhcp_relay *relay;
+
+ if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY)
+ return NULL;
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ {
+ if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
+ {
+ if (!relay->interface || wildcard_match(relay->interface, arrival_interface))
+ return relay->iface_index != 0 ? relay : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h
new file mode 100755
index 0000000..ebe8dfd
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h
@@ -0,0 +1,74 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 DHCPV6_SERVER_PORT 547
+#define DHCPV6_CLIENT_PORT 546
+
+#define ALL_SERVERS "FF05::1:3"
+#define ALL_RELAY_AGENTS_AND_SERVERS "FF02::1:2"
+
+#define DHCP6SOLICIT 1
+#define DHCP6ADVERTISE 2
+#define DHCP6REQUEST 3
+#define DHCP6CONFIRM 4
+#define DHCP6RENEW 5
+#define DHCP6REBIND 6
+#define DHCP6REPLY 7
+#define DHCP6RELEASE 8
+#define DHCP6DECLINE 9
+#define DHCP6RECONFIGURE 10
+#define DHCP6IREQ 11
+#define DHCP6RELAYFORW 12
+#define DHCP6RELAYREPL 13
+
+#define OPTION6_CLIENT_ID 1
+#define OPTION6_SERVER_ID 2
+#define OPTION6_IA_NA 3
+#define OPTION6_IA_TA 4
+#define OPTION6_IAADDR 5
+#define OPTION6_ORO 6
+#define OPTION6_PREFERENCE 7
+#define OPTION6_ELAPSED_TIME 8
+#define OPTION6_RELAY_MSG 9
+#define OPTION6_AUTH 11
+#define OPTION6_UNICAST 12
+#define OPTION6_STATUS_CODE 13
+#define OPTION6_RAPID_COMMIT 14
+#define OPTION6_USER_CLASS 15
+#define OPTION6_VENDOR_CLASS 16
+#define OPTION6_VENDOR_OPTS 17
+#define OPTION6_INTERFACE_ID 18
+#define OPTION6_RECONFIGURE_MSG 19
+#define OPTION6_RECONF_ACCEPT 20
+#define OPTION6_DNS_SERVER 23
+#define OPTION6_DOMAIN_SEARCH 24
+#define OPTION6_REFRESH_TIME 32
+#define OPTION6_REMOTE_ID 37
+#define OPTION6_SUBSCRIBER_ID 38
+#define OPTION6_FQDN 39
+#define OPTION6_NTP_SERVER 56
+#define OPTION6_CLIENT_MAC 79
+
+#define NTP_SUBOPTION_SRV_ADDR 1
+#define NTP_SUBOPTION_MC_ADDR 2
+#define NTP_SUBOPTION_SRV_FQDN 3
+
+#define DHCP6SUCCESS 0
+#define DHCP6UNSPEC 1
+#define DHCP6NOADDRS 2
+#define DHCP6NOBINDING 3
+#define DHCP6NOTONLINK 4
+#define DHCP6USEMULTI 5
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c
new file mode 100755
index 0000000..2be877f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c
@@ -0,0 +1,828 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP6
+
+#include <netinet/icmp6.h>
+
+struct iface_param {
+ struct dhcp_context *current;
+ struct dhcp_relay *relay;
+ struct in6_addr fallback, relay_local, ll_addr, ula_addr;
+ int ind, addr_match;
+};
+
+
+static int complete_context6(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ unsigned int preferred, unsigned int valid, void *vparam);
+static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+
+void dhcp6_init(void)
+{
+ int fd;
+ struct sockaddr_in6 saddr;
+#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
+ int class = IPTOS_CLASS_CS6;
+#endif
+ int oneopt = 1;
+
+ if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 ||
+#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
+ setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
+#endif
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &oneopt, sizeof(oneopt)) == -1 ||
+ !fix_fd(fd) ||
+ !set_ipv6pktinfo(fd))
+ die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET);
+
+ /* When bind-interfaces is set, there might be more than one dnsmasq
+ instance binding port 547. That's OK if they serve different networks.
+ Need to set REUSEADDR|REUSEPORT to make this possible.
+ Handle the case that REUSEPORT is defined, but the kernel doesn't
+ support it. This handles the introduction of REUSEPORT on Linux. */
+ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
+ {
+ int rc = 0;
+
+#ifdef SO_REUSEPORT
+ if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 &&
+ errno == ENOPROTOOPT)
+ rc = 0;
+#endif
+
+ if (rc != -1)
+ rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt));
+
+ if (rc == -1)
+ die(_("failed to set SO_REUSE{ADDR|PORT} on DHCPv6 socket: %s"), NULL, EC_BADNET);
+ }
+
+ memset(&saddr, 0, sizeof(saddr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ saddr.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ saddr.sin6_family = AF_INET6;
+ saddr.sin6_addr = in6addr_any;
+ saddr.sin6_port = htons(DHCPV6_SERVER_PORT);
+
+ if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)))
+ die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET);
+
+ daemon->dhcp6fd = fd;
+}
+
+void dhcp6_packet(time_t now)
+{
+ struct dhcp_context *context;
+ struct dhcp_relay *relay;
+ struct iface_param parm;
+ struct cmsghdr *cmptr;
+ struct msghdr msg;
+ int if_index = 0;
+ union {
+ struct cmsghdr align; /* this ensures alignment */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } control_u;
+ struct sockaddr_in6 from;
+ ssize_t sz;
+ struct ifreq ifr;
+ struct iname *tmp;
+ unsigned short port;
+ struct in6_addr dst_addr;
+
+ memset(&dst_addr, 0, sizeof(dst_addr));
+
+ msg.msg_control = control_u.control6;
+ msg.msg_controllen = sizeof(control_u);
+ msg.msg_flags = 0;
+ msg.msg_name = &from;
+ msg.msg_namelen = sizeof(from);
+ msg.msg_iov = &daemon->dhcp_packet;
+ msg.msg_iovlen = 1;
+
+ if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1)
+ return;
+
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
+ {
+ union {
+ unsigned char *c;
+ struct in6_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+
+ if_index = p.p->ipi6_ifindex;
+ dst_addr = p.p->ipi6_addr;
+ }
+
+ if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
+ return;
+
+ if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0)
+ {
+ from.sin6_port = htons(port);
+ while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base,
+ save_counter(-1), 0, (struct sockaddr *)&from,
+ sizeof(from))));
+ }
+ else
+ {
+ struct dhcp_bridge *bridge, *alias;
+
+ for (tmp = daemon->if_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ return;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ return;
+
+ parm.current = NULL;
+ parm.relay = NULL;
+ memset(&parm.relay_local, 0, IN6ADDRSZ);
+ parm.ind = if_index;
+ parm.addr_match = 0;
+ memset(&parm.fallback, 0, IN6ADDRSZ);
+ memset(&parm.ll_addr, 0, IN6ADDRSZ);
+ memset(&parm.ula_addr, 0, IN6ADDRSZ);
+
+ /* If the interface on which the DHCPv6 request was received is
+ an alias of some other interface (as specified by the
+ --bridge-interface option), change parm.ind so that we look
+ for DHCPv6 contexts associated with the aliased interface
+ instead of with the aliasing one. */
+ for (bridge = daemon->bridges; bridge; bridge = bridge->next)
+ {
+ for (alias = bridge->alias; alias; alias = alias->next)
+ if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE))
+ {
+ parm.ind = if_nametoindex(bridge->iface);
+ if (!parm.ind)
+ {
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("unknown interface %s in bridge-interface"),
+ bridge->iface);
+ return;
+ }
+ break;
+ }
+ if (alias)
+ break;
+ }
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
+ {
+ /* wildcard context for DHCP-stateless only */
+ parm.current = context;
+ context->current = NULL;
+ }
+ else
+ {
+ /* unlinked contexts are marked by context->current == context */
+ context->current = context;
+ memset(&context->local6, 0, IN6ADDRSZ);
+ }
+
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ relay->current = relay;
+
+ if (!iface_enumerate(AF_INET6, &parm, complete_context6))
+ return;
+
+ if (daemon->if_names || daemon->if_addrs)
+ {
+
+ for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ break;
+
+ if (!tmp && !parm.addr_match)
+ return;
+ }
+
+ if (parm.relay)
+ {
+ /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
+ we're listening there for DHCPv6 server reasons. */
+ struct in6_addr all_servers;
+
+ inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
+
+ if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
+ relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id, now);
+ return;
+ }
+
+ /* May have configured relay, but not DHCP server */
+ if (!daemon->doing_dhcp6)
+ return;
+
+ lease_prune(NULL, now); /* lose any expired leases */
+
+ port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
+ &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
+
+ /* The port in the source address of the original request should
+ be correct, but at least once client sends from the server port,
+ so we explicitly send to the client port to a client, and the
+ server port to a relay. */
+ if (port != 0)
+ {
+ from.sin6_port = htons(port);
+ while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base,
+ save_counter(-1), 0, (struct sockaddr *)&from,
+ sizeof(from))));
+ }
+
+ /* These need to be called _after_ we send DHCPv6 packet, since lease_update_file()
+ may trigger sending an RA packet, which overwrites our buffer. */
+ lease_update_file(now);
+ lease_update_dns(0);
+ }
+}
+
+void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now)
+{
+ /* Receiving a packet from a host does not populate the neighbour
+ cache, so we send a neighbour discovery request if we can't
+ find the sender. Repeat a few times in case of packet loss. */
+
+ struct neigh_packet neigh;
+ union mysockaddr addr;
+ int i, maclen;
+
+ neigh.type = ND_NEIGHBOR_SOLICIT;
+ neigh.code = 0;
+ neigh.reserved = 0;
+ neigh.target = *client;
+ /* RFC4443 section-2.3: checksum has to be zero to be calculated */
+ neigh.checksum = 0;
+
+ memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ addr.in6.sin6_family = AF_INET6;
+ addr.in6.sin6_port = htons(IPPROTO_ICMPV6);
+ addr.in6.sin6_addr = *client;
+ addr.in6.sin6_scope_id = iface;
+
+ for (i = 0; i < 5; i++)
+ {
+ struct timespec ts;
+
+ if ((maclen = find_mac(&addr, mac, 0, now)) != 0)
+ break;
+
+ sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr));
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100000000; /* 100ms */
+ nanosleep(&ts, NULL);
+ }
+
+ *maclenp = maclen;
+ *mactypep = ARPHRD_ETHER;
+}
+
+static int complete_context6(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags, unsigned int preferred,
+ unsigned int valid, void *vparam)
+{
+ struct dhcp_context *context;
+ struct shared_network *share;
+ struct dhcp_relay *relay;
+ struct iface_param *param = vparam;
+ struct iname *tmp;
+
+ (void)scope; /* warning */
+
+ if (if_index != param->ind)
+ return 1;
+
+ if (IN6_IS_ADDR_LINKLOCAL(local))
+ param->ll_addr = *local;
+ else if (IN6_IS_ADDR_ULA(local))
+ param->ula_addr = *local;
+
+ if (IN6_IS_ADDR_LOOPBACK(local) ||
+ IN6_IS_ADDR_LINKLOCAL(local) ||
+ IN6_IS_ADDR_MULTICAST(local))
+ return 1;
+
+ /* if we have --listen-address config, see if the
+ arrival interface has a matching address. */
+ for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+ if (tmp->addr.sa.sa_family == AF_INET6 &&
+ IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
+ param->addr_match = 1;
+
+ /* Determine a globally address on the arrival interface, even
+ if we have no matching dhcp-context, because we're only
+ allocating on remote subnets via relays. This
+ is used as a default for the DNS server option. */
+ param->fallback = *local;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if ((context->flags & CONTEXT_DHCP) &&
+ !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ prefix <= context->prefix &&
+ context->current == context)
+ {
+ if (is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix))
+ {
+ struct dhcp_context *tmp, **up;
+
+ /* use interface values only for constructed contexts */
+ if (!(context->flags & CONTEXT_CONSTRUCTED))
+ preferred = valid = 0xffffffff;
+ else if (flags & IFACE_DEPRECATED)
+ preferred = 0;
+
+ if (context->flags & CONTEXT_DEPRECATE)
+ preferred = 0;
+
+ /* order chain, longest preferred time first */
+ for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current)
+ if (tmp->preferred <= preferred)
+ break;
+ else
+ up = &tmp->current;
+
+ context->current = *up;
+ *up = context;
+ context->local6 = *local;
+ context->preferred = preferred;
+ context->valid = valid;
+ }
+ else
+ {
+ for (share = daemon->shared_networks; share; share = share->next)
+ {
+ /* IPv4 shared_address - ignore */
+ if (share->shared_addr.s_addr != 0)
+ continue;
+
+ if (share->if_index != 0)
+ {
+ if (share->if_index != if_index)
+ continue;
+ }
+ else
+ {
+ if (!IN6_ARE_ADDR_EQUAL(&share->match_addr6, local))
+ continue;
+ }
+
+ if (is_same_net6(&share->shared_addr6, &context->start6, context->prefix) &&
+ is_same_net6(&share->shared_addr6, &context->end6, context->prefix))
+ {
+ context->current = param->current;
+ param->current = context;
+ context->local6 = *local;
+ context->preferred = context->flags & CONTEXT_DEPRECATE ? 0 :0xffffffff;
+ context->valid = 0xffffffff;
+ }
+ }
+ }
+ }
+
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay &&
+ (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local)))
+ {
+ relay->current = param->relay;
+ param->relay = relay;
+ param->relay_local = *local;
+ }
+
+ return 1;
+}
+
+struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, struct in6_addr *addr)
+{
+ struct dhcp_config *config;
+
+ for (config = configs; config; config = config->next)
+ if (config->flags & CONFIG_ADDR6)
+ {
+ struct addrlist *addr_list;
+
+ for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+ if ((!net || is_same_net6(&addr_list->addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) &&
+ is_same_net6(&addr_list->addr.addr6, addr, (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128))
+ return config;
+ }
+
+ return NULL;
+}
+
+struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr,
+ unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans)
+{
+ /* 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.
+
+ Note that we assume the address prefix lengths are 64 or greater, so we can
+ get by with 64 bit arithmetic.
+*/
+
+ u64 start, addr;
+ struct dhcp_context *c, *d;
+ int i, pass;
+ u64 j;
+
+ /* hash hwaddr: use the SDBM hashing algorithm. This works
+ for MAC addresses, let's see how it manages with client-ids!
+ For temporary addresses, we generate a new random one each time. */
+ if (temp_addr)
+ j = rand64();
+ else
+ for (j = iaid, i = 0; i < clid_len; i++)
+ j = clid[i] + (j << 6) + (j << 16) - j;
+
+ for (pass = 0; pass <= plain_range ? 1 : 0; pass++)
+ for (c = context; c; c = c->current)
+ if (c->flags & (CONTEXT_DEPRECATE | CONTEXT_STATIC | CONTEXT_RA_STATELESS | CONTEXT_USED))
+ continue;
+ else if (!match_netid(c->filter, netids, pass))
+ continue;
+ else
+ {
+ if (!temp_addr && option_bool(OPT_CONSEC_ADDR))
+ {
+ /* seed is largest extant lease addr in this context,
+ skip addresses equal to the number of addresses rejected
+ by clients. This should avoid the same client being offered the same
+ address after it has rjected it. */
+ start = lease_find_max_addr6(c) + 1 + serial + c->addr_epoch;
+ if (c->addr_epoch)
+ c->addr_epoch--;
+ }
+ else
+ {
+ u64 range = 1 + addr6part(&c->end6) - addr6part(&c->start6);
+ u64 offset = j + c->addr_epoch;
+
+ /* don't divide by zero if range is whole 2^64 */
+ if (range != 0)
+ offset = offset % range;
+
+ start = addr6part(&c->start6) + offset;
+ }
+
+ /* iterate until we find a free address. */
+ addr = start;
+
+ do {
+ /* eliminate addresses in use by the server. */
+ for (d = context; d; d = d->current)
+ if (addr == addr6part(&d->local6))
+ break;
+
+ *ans = c->start6;
+ setaddr6part (ans, addr);
+
+ if (!d &&
+ !lease6_find_by_addr(&c->start6, c->prefix, addr) &&
+ !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans))
+ return c;
+
+ addr++;
+
+ if (addr == addr6part(&c->end6) + 1)
+ addr = addr6part(&c->start6);
+
+ } while (addr != start);
+ }
+
+ return NULL;
+}
+
+/* can dynamically allocate addr */
+struct dhcp_context *address6_available(struct dhcp_context *context,
+ struct in6_addr *taddr,
+ struct dhcp_netid *netids,
+ int plain_range)
+{
+ u64 start, end, addr = addr6part(taddr);
+ struct dhcp_context *tmp;
+
+ for (tmp = context; tmp; tmp = tmp->current)
+ {
+ start = addr6part(&tmp->start6);
+ end = addr6part(&tmp->end6);
+
+ if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_RA_STATELESS)) &&
+ is_same_net6(&tmp->start6, taddr, tmp->prefix) &&
+ is_same_net6(&tmp->end6, taddr, tmp->prefix) &&
+ addr >= start &&
+ addr <= end &&
+ match_netid(tmp->filter, netids, plain_range))
+ return tmp;
+ }
+
+ return NULL;
+}
+
+/* address OK if configured */
+struct dhcp_context *address6_valid(struct dhcp_context *context,
+ struct in6_addr *taddr,
+ struct dhcp_netid *netids,
+ int plain_range)
+{
+ struct dhcp_context *tmp;
+
+ for (tmp = context; tmp; tmp = tmp->current)
+ if (is_same_net6(&tmp->start6, taddr, tmp->prefix) &&
+ match_netid(tmp->filter, netids, plain_range))
+ return tmp;
+
+ return NULL;
+}
+
+void make_duid(time_t now)
+{
+ (void)now;
+
+ if (daemon->duid_config)
+ {
+ unsigned char *p;
+
+ daemon->duid = p = safe_malloc(daemon->duid_config_len + 6);
+ daemon->duid_len = daemon->duid_config_len + 6;
+ PUTSHORT(2, p); /* DUID_EN */
+ PUTLONG(daemon->duid_enterprise, p);
+ memcpy(p, daemon->duid_config, daemon->duid_config_len);
+ }
+ else
+ {
+ time_t newnow = 0;
+
+ /* If we have no persistent lease database, or a non-stable RTC, use DUID_LL (newnow == 0) */
+#ifndef HAVE_BROKEN_RTC
+ /* rebase epoch to 1/1/2000 */
+ if (!option_bool(OPT_LEASE_RO) || daemon->lease_change_command)
+ newnow = now - 946684800;
+#endif
+
+ iface_enumerate(AF_LOCAL, &newnow, make_duid1);
+
+ if(!daemon->duid)
+ die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC);
+ }
+}
+
+static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+ /* create DUID as specified in RFC3315. We use the MAC of the
+ first interface we find that isn't loopback or P-to-P and
+ has address-type < 256. Address types above 256 are things like
+ tunnels which don't have usable MAC addresses. */
+
+ unsigned char *p;
+ (void)index;
+ (void)parm;
+ time_t newnow = *((time_t *)parm);
+
+ if (type >= 256)
+ return 1;
+
+ if (newnow == 0)
+ {
+ daemon->duid = p = safe_malloc(maclen + 4);
+ daemon->duid_len = maclen + 4;
+ PUTSHORT(3, p); /* DUID_LL */
+ PUTSHORT(type, p); /* address type */
+ }
+ else
+ {
+ daemon->duid = p = safe_malloc(maclen + 8);
+ daemon->duid_len = maclen + 8;
+ PUTSHORT(1, p); /* DUID_LLT */
+ PUTSHORT(type, p); /* address type */
+ PUTLONG(*((time_t *)parm), p); /* time */
+ }
+
+ memcpy(p, mac, maclen);
+
+ return 0;
+}
+
+struct cparam {
+ time_t now;
+ int newone, newname;
+};
+
+static int construct_worker(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ int preferred, int valid, void *vparam)
+{
+ char ifrn_name[IFNAMSIZ];
+ struct in6_addr start6, end6;
+ struct dhcp_context *template, *context;
+ struct iname *tmp;
+
+ (void)scope;
+ (void)flags;
+ (void)valid;
+ (void)preferred;
+
+ struct cparam *param = vparam;
+
+ if (IN6_IS_ADDR_LOOPBACK(local) ||
+ IN6_IS_ADDR_LINKLOCAL(local) ||
+ IN6_IS_ADDR_MULTICAST(local))
+ return 1;
+
+ if (!(flags & IFACE_PERMANENT))
+ return 1;
+
+ if (flags & IFACE_DEPRECATED)
+ return 1;
+
+ /* Ignore interfaces where we're not doing RA/DHCP6 */
+ if (!indextoname(daemon->icmp6fd, if_index, ifrn_name) ||
+ !iface_check(AF_LOCAL, NULL, ifrn_name, NULL))
+ return 1;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifrn_name))
+ return 1;
+
+ for (template = daemon->dhcp6; template; template = template->next)
+ if (!(template->flags & (CONTEXT_TEMPLATE | CONTEXT_CONSTRUCTED)))
+ {
+ /* non-template entries, just fill in interface and local addresses */
+ if (prefix <= template->prefix &&
+ is_same_net6(local, &template->start6, template->prefix) &&
+ is_same_net6(local, &template->end6, template->prefix))
+ {
+ /* First time found, do fast RA. */
+ if (template->if_index == 0)
+ {
+ ra_start_unsolicited(param->now, template);
+ param->newone = 1;
+ }
+
+ template->if_index = if_index;
+ template->local6 = *local;
+ }
+
+ }
+ else if (wildcard_match(template->template_interface, ifrn_name) &&
+ template->prefix >= prefix)
+ {
+ start6 = *local;
+ setaddr6part(&start6, addr6part(&template->start6));
+ end6 = *local;
+ setaddr6part(&end6, addr6part(&template->end6));
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (!(context->flags & CONTEXT_TEMPLATE) &&
+ IN6_ARE_ADDR_EQUAL(&start6, &context->start6) &&
+ IN6_ARE_ADDR_EQUAL(&end6, &context->end6))
+ {
+ /* If there's an absolute address context covering this address
+ then don't construct one as well. */
+ if (!(context->flags & CONTEXT_CONSTRUCTED))
+ break;
+
+ if (context->if_index == if_index)
+ {
+ int cflags = context->flags;
+ context->flags &= ~(CONTEXT_GC | CONTEXT_OLD);
+ if (cflags & CONTEXT_OLD)
+ {
+ /* address went, now it's back, and on the same interface */
+ log_context(AF_INET6, context);
+ /* fast RAs for a while */
+ ra_start_unsolicited(param->now, context);
+ param->newone = 1;
+ /* Add address to name again */
+ if (context->flags & CONTEXT_RA_NAME)
+ param->newname = 1;
+
+ }
+ break;
+ }
+ }
+
+ if (!context && (context = whine_malloc(sizeof (struct dhcp_context))))
+ {
+ *context = *template;
+ context->start6 = start6;
+ context->end6 = end6;
+ context->flags &= ~CONTEXT_TEMPLATE;
+ context->flags |= CONTEXT_CONSTRUCTED;
+ context->if_index = if_index;
+ context->local6 = *local;
+ context->saved_valid = 0;
+
+ context->next = daemon->dhcp6;
+ daemon->dhcp6 = context;
+
+ ra_start_unsolicited(param->now, context);
+ /* we created a new one, need to call
+ lease_update_file to get periodic functions called */
+ param->newone = 1;
+
+ /* Will need to add new putative SLAAC addresses to existing leases */
+ if (context->flags & CONTEXT_RA_NAME)
+ param->newname = 1;
+
+ log_context(AF_INET6, context);
+ }
+ }
+
+ return 1;
+}
+
+void dhcp_construct_contexts(time_t now)
+{
+ struct dhcp_context *context, *tmp, **up;
+ struct cparam param;
+ param.newone = 0;
+ param.newname = 0;
+ param.now = now;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (context->flags & CONTEXT_CONSTRUCTED)
+ context->flags |= CONTEXT_GC;
+
+ iface_enumerate(AF_INET6, ¶m, construct_worker);
+
+ for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
+ {
+
+ tmp = context->next;
+
+ if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD))
+ {
+ if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA))
+ {
+ /* previously constructed context has gone. advertise it's demise */
+ context->flags |= CONTEXT_OLD;
+ context->address_lost_time = now;
+ /* Apply same ceiling of configured lease time as in radv.c */
+ if (context->saved_valid > context->lease_time)
+ context->saved_valid = context->lease_time;
+ /* maximum time is 2 hours, from RFC */
+ if (context->saved_valid > 7200) /* 2 hours */
+ context->saved_valid = 7200;
+ ra_start_unsolicited(now, context);
+ param.newone = 1; /* include deletion */
+
+ if (context->flags & CONTEXT_RA_NAME)
+ param.newname = 1;
+
+ log_context(AF_INET6, context);
+
+ up = &context->next;
+ }
+ else
+ {
+ /* we were never doing RA for this, so free now */
+ *up = context->next;
+ free(context);
+ }
+ }
+ else
+ up = &context->next;
+ }
+
+ if (param.newone)
+ {
+ if (daemon->dhcp || daemon->doing_dhcp6)
+ {
+ if (param.newname)
+ lease_update_slaac(now);
+ lease_update_file(now);
+ }
+ else
+ /* Not doing DHCP, so no lease system, manage alarms for ra only */
+ send_alarm(periodic_ra(now), now);
+ }
+}
+
+#endif /* HAVE_DHCP6 */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h
new file mode 100755
index 0000000..496a4bb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h
@@ -0,0 +1,190 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 NAMESERVER_PORT 53
+#define TFTP_PORT 69
+#define MIN_PORT 1024 /* first non-reserved port */
+#define MAX_PORT 65535u
+
+#define IN6ADDRSZ 16
+#define INADDRSZ 4
+
+#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_HESIOD 4 /* hesiod */
+#define C_ANY 255 /* wildcard match */
+
+#define T_A 1
+#define T_NS 2
+#define T_MD 3
+#define T_MF 4
+#define T_CNAME 5
+#define T_SOA 6
+#define T_MB 7
+#define T_MG 8
+#define T_MR 9
+#define T_PTR 12
+#define T_MINFO 14
+#define T_MX 15
+#define T_TXT 16
+#define T_RP 17
+#define T_AFSDB 18
+#define T_RT 21
+#define T_SIG 24
+#define T_PX 26
+#define T_AAAA 28
+#define T_NXT 30
+#define T_SRV 33
+#define T_NAPTR 35
+#define T_KX 36
+#define T_DNAME 39
+#define T_OPT 41
+#define T_DS 43
+#define T_RRSIG 46
+#define T_NSEC 47
+#define T_DNSKEY 48
+#define T_NSEC3 50
+#define T_TKEY 249
+#define T_TSIG 250
+#define T_AXFR 252
+#define T_MAILB 253
+#define T_ANY 255
+#define T_CAA 257
+
+#define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */
+#define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */
+#define EDNS0_OPTION_EDE 15 /* IANA - RFC 8914 */
+#define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */
+#define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */
+#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */
+
+/* RFC-8914 extended errors, negative values are our definitions */
+#define EDE_UNSET -1 /* No extended DNS error available */
+#define EDE_OTHER 0 /* Other */
+#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */
+#define EDE_USUPDS 2 /* Unsupported DS Digest */
+#define EDE_STALE 3 /* Stale answer */
+#define EDE_FORGED 4 /* Forged answer */
+#define EDE_DNSSEC_IND 5 /* DNSSEC Indeterminate */
+#define EDE_DNSSEC_BOGUS 6 /* DNSSEC Bogus */
+#define EDE_SIG_EXP 7 /* Signature Expired */
+#define EDE_SIG_NYV 8 /* Signature Not Yet Valid */
+#define EDE_NO_DNSKEY 9 /* DNSKEY missing */
+#define EDE_NO_RRSIG 10 /* RRSIGs missing */
+#define EDE_NO_ZONEKEY 11 /* No Zone Key Bit Set */
+#define EDE_NO_NSEC 12 /* NSEC Missing */
+#define EDE_CACHED_ERR 13 /* Cached Error */
+#define EDE_NOT_READY 14 /* Not Ready */
+#define EDE_BLOCKED 15 /* Blocked */
+#define EDE_CENSORED 16 /* Censored */
+#define EDE_FILTERED 17 /* Filtered */
+#define EDE_PROHIBITED 18 /* Prohibited */
+#define EDE_STALE_NXD 19 /* Stale NXDOMAIN */
+#define EDE_NOT_AUTH 20 /* Not Authoritative */
+#define EDE_NOT_SUP 21 /* Not Supported */
+#define EDE_NO_AUTH 22 /* No Reachable Authority */
+#define EDE_NETERR 23 /* Network error */
+#define EDE_INVALID_DATA 24 /* Invalid Data */
+
+
+
+
+struct dns_header {
+ u16 id;
+ u8 hb3,hb4;
+ u16 qdcount,ancount,nscount,arcount;
+};
+
+#define HB3_QR 0x80 /* Query */
+#define HB3_OPCODE 0x78
+#define HB3_AA 0x04 /* Authoritative Answer */
+#define HB3_TC 0x02 /* TrunCated */
+#define HB3_RD 0x01 /* Recursion Desired */
+
+#define HB4_RA 0x80 /* Recursion Available */
+#define HB4_AD 0x20 /* Authenticated Data */
+#define HB4_CD 0x10 /* Checking Disabled */
+#define HB4_RCODE 0x0f
+
+#define OPCODE(x) (((x)->hb3 & HB3_OPCODE) >> 3)
+#define SET_OPCODE(x, code) (x)->hb3 = ((x)->hb3 & ~HB3_OPCODE) | code
+
+#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; \
+}
+
+#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 : (((pp) += (len)), 1))
+
+/* Escape character in our presentation format for names.
+ Cannot be '.' or /000 and must be !isprint().
+ Note that escaped chars are stored as
+ <NAME_ESCAPE> <orig-char+1>
+ to ensure that the escaped form of /000 doesn't include /000
+*/
+#define NAME_ESCAPE 1
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c
new file mode 100755
index 0000000..0b455b8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c
@@ -0,0 +1,2166 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* Declare static char *compiler_opts in config.h */
+#define DNSMASQ_COMPILE_OPTS
+
+#include "dnsmasq.h"
+
+struct daemon *daemon;
+
+static volatile pid_t pid = 0;
+static volatile int pipewrite;
+
+static void set_dns_listeners(void);
+static void check_dns_listeners(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, char *msg);
+static int read_event(int fd, struct event_desc *evp, char **msg);
+static void poll_resolv(int force, int do_reload, time_t now);
+
+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_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;
+ int chown_warn = 0;
+#if defined(HAVE_LINUX_NETWORK)
+ cap_user_header_t hdr = NULL;
+ cap_user_data_t data = NULL;
+ int need_cap_net_admin = 0;
+ int need_cap_net_raw = 0;
+ int need_cap_net_bind_service = 0;
+ char *bound_device = NULL;
+ int did_bind = 0;
+ struct server *serv;
+ char *netlink_warn;
+#endif
+#if defined(HAVE_DHCP) || defined(HAVE_DHCP6)
+ struct dhcp_context *context;
+ struct dhcp_relay *relay;
+#endif
+#ifdef HAVE_TFTP
+ int tftp_prefix_missing = 0;
+#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);
+ sigaction(SIGINT, &sigact, NULL);
+
+ /* ignore SIGPIPE */
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sigact, NULL);
+
+ umask(022); /* known umask, create leases and pid files as 0644 */
+
+ rand_init(); /* Must precede read_opts() */
+
+ read_opts(argc, argv, compile_opts);
+
+#ifdef HAVE_LINUX_NETWORK
+ daemon->kernel_version = kernel_version();
+#endif
+
+ if (daemon->edns_pktsz < PACKETSZ)
+ daemon->edns_pktsz = PACKETSZ;
+
+ /* Min buffer size: we check after adding each record, so there must be
+ memory for the largest packet, and the largest record so the
+ min for DNS is PACKETSZ+MAXDNAME+RRFIXEDSZ which is < 1000.
+ This might be increased is EDNS packet size if greater than the minimum. */
+ daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ;
+ daemon->packet = safe_malloc(daemon->packet_buff_sz);
+
+ if (option_bool(OPT_EXTRALOG))
+ daemon->addrbuff2 = safe_malloc(ADDRSTRLEN);
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ /* Note that both /000 and '.' are allowed within labels. These get
+ represented in presentation format using NAME_ESCAPE as an escape
+ character when in DNSSEC mode.
+ In theory, if all the characters in a name were /000 or
+ '.' or NAME_ESCAPE then all would have to be escaped, so the
+ presentation format would be twice as long as the spec.
+
+ daemon->namebuff was previously allocated by the option-reading
+ code before we knew if we're in DNSSEC mode, so reallocate here. */
+ free(daemon->namebuff);
+ daemon->namebuff = safe_malloc(MAXDNAME * 2);
+ daemon->keyname = safe_malloc(MAXDNAME * 2);
+ daemon->workspacename = safe_malloc(MAXDNAME * 2);
+ /* one char flag per possible RR in answer section (may get extended). */
+ daemon->rr_status_sz = 64;
+ daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
+ }
+#endif
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+ /* CONNTRACK UBUS code uses this buffer, so if not allocated above,
+ we need to allocate it here. */
+ if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
+ daemon->workspacename = safe_malloc(MAXDNAME);
+#endif
+
+#ifdef HAVE_DHCP
+ if (!daemon->lease_file)
+ {
+ if (daemon->dhcp || daemon->dhcp6)
+ daemon->lease_file = LEASEFILE;
+ }
+#endif
+
+ /* Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist,
+ otherwise file descriptors we create can end up being 0, 1, or 2
+ and then get accidentally closed later when we make 0, 1, and 2
+ open to /dev/null. Normally we'll be started with 0, 1 and 2 open,
+ but it's not guaranteed. By opening /dev/null three times, we
+ ensure that we're not using those fds for real stuff. */
+ for (i = 0; i < 3; i++)
+ open("/dev/null", O_RDWR);
+
+ /* Close any file descriptors we inherited apart from std{in|out|err} */
+ close_fds(max_fd, -1, -1, -1);
+
+#ifndef HAVE_LINUX_NETWORK
+# if !(defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR))
+ if (!option_bool(OPT_NOWILD))
+ {
+ bind_fallback = 1;
+ set_option_bool(OPT_NOWILD);
+ }
+# endif
+
+ /* -- bind-dynamic not supported on !Linux, fall back to --bind-interfaces */
+ if (option_bool(OPT_CLEVERBIND))
+ {
+ bind_fallback = 1;
+ set_option_bool(OPT_NOWILD);
+ reset_option_bool(OPT_CLEVERBIND);
+ }
+#endif
+
+#ifndef HAVE_INOTIFY
+ if (daemon->dynamic_dirs)
+ die(_("dhcp-hostsdir, dhcp-optsdir and hostsdir are not supported on this platform"), NULL, EC_BADCONF);
+#endif
+
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+#ifdef HAVE_DNSSEC
+ struct ds_config *ds;
+
+ /* Must have at least a root trust anchor, or the DNSSEC code
+ can loop forever. */
+ for (ds = daemon->ds; ds; ds = ds->next)
+ if (ds->name[0] == 0)
+ break;
+
+ if (!ds)
+ die(_("no root trust anchor provided for DNSSEC"), NULL, EC_BADCONF);
+
+ if (daemon->cachesize < CACHESIZ)
+ die(_("cannot reduce cache size from default when DNSSEC enabled"), NULL, EC_BADCONF);
+#else
+ die(_("DNSSEC not available: set HAVE_DNSSEC in src/config.h"), NULL, EC_BADCONF);
+#endif
+ }
+
+#ifndef HAVE_TFTP
+ if (option_bool(OPT_TFTP))
+ die(_("TFTP server not available: set HAVE_TFTP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifdef HAVE_CONNTRACK
+ if (option_bool(OPT_CONNTRACK))
+ {
+ if (daemon->query_port != 0 || daemon->osport)
+ die (_("cannot use --conntrack AND --query-port"), NULL, EC_BADCONF);
+
+ need_cap_net_admin = 1;
+ }
+#else
+ if (option_bool(OPT_CONNTRACK))
+ die(_("conntrack support not available: set HAVE_CONNTRACK in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifdef HAVE_SOLARIS_NETWORK
+ if (daemon->max_logs != 0)
+ die(_("asynchronous logging is not available under Solaris"), NULL, EC_BADCONF);
+#endif
+
+#ifdef __ANDROID__
+ if (daemon->max_logs != 0)
+ die(_("asynchronous logging is not available under Android"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_AUTH
+ if (daemon->auth_zones)
+ die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_LOOP
+ if (option_bool(OPT_LOOP_DETECT))
+ die(_("loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_UBUS
+ if (option_bool(OPT_UBUS))
+ die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+ /* Handle only one of min_port/max_port being set. */
+ if (daemon->min_port != 0 && daemon->max_port == 0)
+ daemon->max_port = MAX_PORT;
+
+ if (daemon->max_port != 0 && daemon->min_port == 0)
+ daemon->min_port = MIN_PORT;
+
+ if (daemon->max_port < daemon->min_port)
+ die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF);
+
+ now = dnsmasq_time();
+
+ if (daemon->auth_zones)
+ {
+ if (!daemon->authserver)
+ die(_("--auth-server required when an auth zone is defined."), NULL, EC_BADCONF);
+
+ /* Create a serial at startup if not configured. */
+#ifdef HAVE_BROKEN_RTC
+ if (daemon->soa_sn == 0)
+ die(_("zone serial must be configured in --auth-soa"), NULL, EC_BADCONF);
+#else
+ if (daemon->soa_sn == 0)
+ daemon->soa_sn = now;
+#endif
+ }
+
+#ifdef HAVE_DHCP6
+ if (daemon->dhcp6)
+ {
+ daemon->doing_ra = option_bool(OPT_RA);
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ {
+ if (context->flags & CONTEXT_DHCP)
+ daemon->doing_dhcp6 = 1;
+ if (context->flags & CONTEXT_RA)
+ daemon->doing_ra = 1;
+#if !defined(HAVE_LINUX_NETWORK) && !defined(HAVE_BSD_NETWORK)
+ if (context->flags & CONTEXT_TEMPLATE)
+ die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
+#endif
+ }
+ }
+#endif
+
+#ifdef HAVE_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. We need to call common_init
+ before lease_init to allocate buffers it uses.
+ The script subsystem relies on DHCP buffers, hence the last two
+ conditions below. */
+ if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 ||
+ daemon->relay6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP))
+ {
+ dhcp_common_init();
+ if (daemon->dhcp || daemon->doing_dhcp6)
+ lease_init(now);
+ }
+
+ if (daemon->dhcp || daemon->relay4)
+ {
+ dhcp_init();
+# ifdef HAVE_LINUX_NETWORK
+ if (!option_bool(OPT_NO_PING))
+ need_cap_net_raw = 1;
+ need_cap_net_admin = 1;
+# endif
+ }
+
+# ifdef HAVE_DHCP6
+ if (daemon->doing_ra || daemon->doing_dhcp6 || daemon->relay6)
+ {
+ ra_init(now);
+# ifdef HAVE_LINUX_NETWORK
+ need_cap_net_raw = 1;
+ need_cap_net_admin = 1;
+# endif
+ }
+
+ if (daemon->doing_dhcp6 || daemon->relay6)
+ dhcp6_init();
+# endif
+
+#endif
+
+#ifdef HAVE_IPSET
+ if (daemon->ipsets)
+ {
+ ipset_init();
+# ifdef HAVE_LINUX_NETWORK
+ need_cap_net_admin = 1;
+# endif
+ }
+#endif
+
+#if defined(HAVE_LINUX_NETWORK)
+ netlink_warn = netlink_init();
+#elif defined(HAVE_BSD_NETWORK)
+ route_init();
+#endif
+
+ if (option_bool(OPT_NOWILD) && option_bool(OPT_CLEVERBIND))
+ die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF);
+
+ if (!enumerate_interfaces(1) || !enumerate_interfaces(0))
+ die(_("failed to find list of interfaces: %s"), NULL, EC_MISC);
+
+ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
+ {
+ create_bound_listeners(1);
+
+ if (!option_bool(OPT_CLEVERBIND))
+ 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);
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
+ /* after enumerate_interfaces() */
+ bound_device = whichdevice();
+
+ if (daemon->dhcp)
+ {
+ if (!daemon->relay4 && bound_device)
+ {
+ bindtodevice(bound_device, daemon->dhcpfd);
+ did_bind = 1;
+ }
+ if (daemon->enable_pxe && bound_device)
+ {
+ bindtodevice(bound_device, daemon->pxefd);
+ did_bind = 1;
+ }
+ }
+#endif
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
+ if (daemon->doing_dhcp6 && !daemon->relay6 && bound_device)
+ {
+ bindtodevice(bound_device, daemon->dhcp6fd);
+ did_bind = 1;
+ }
+#endif
+ }
+ else
+ create_wildcard_listeners();
+
+#ifdef HAVE_DHCP6
+ /* after enumerate_interfaces() */
+ if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+ join_multicast(1);
+
+ /* After netlink_init() and before create_helper() */
+ lease_make_duid(now);
+#endif
+
+ if (daemon->port != 0)
+ {
+ cache_init();
+ blockdata_init();
+ hash_questions_init();
+
+ /* Scale random socket pool by ftabsize, but
+ limit it based on available fds. */
+ daemon->numrrand = daemon->ftabsize/2;
+ if (daemon->numrrand > max_fd/3)
+ daemon->numrrand = max_fd/3;
+ /* safe_malloc returns zero'd memory */
+ daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd));
+ }
+
+#ifdef HAVE_INOTIFY
+ if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6)
+ && (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs))
+ inotify_dnsmasq_init();
+ else
+ daemon->inotifyfd = -1;
+#endif
+
+ if (daemon->dump_file)
+#ifdef HAVE_DUMPFILE
+ dump_init();
+ else
+ daemon->dumpfd = -1;
+#else
+ die(_("Packet dumps not available: set HAVE_DUMP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+ if (option_bool(OPT_DBUS))
+#ifdef HAVE_DBUS
+ {
+ char *err;
+ 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 (option_bool(OPT_UBUS))
+#ifdef HAVE_UBUS
+ {
+ char *err;
+ if ((err = ubus_init()))
+ die(_("UBus error: %s"), err, EC_MISC);
+ }
+#else
+ die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+ if (daemon->port != 0)
+ pre_allocate_sfds();
+
+#if defined(HAVE_SCRIPT)
+ /* Note getpwnam returns static storage */
+ if ((daemon->dhcp || daemon->dhcp6) &&
+ daemon->scriptuser &&
+ (daemon->lease_change_command || daemon->luascript))
+ {
+ struct passwd *scr_pw;
+
+ if ((scr_pw = getpwnam(daemon->scriptuser)))
+ {
+ script_uid = scr_pw->pw_uid;
+ script_gid = scr_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)
+ /* We keep CAP_NETADMIN (for ARP-injection) and
+ CAP_NET_RAW (for icmp) if we're doing dhcp,
+ if we have yet to bind ports because of DAD,
+ or we're doing it dynamically, we need CAP_NET_BIND_SERVICE. */
+ if ((is_dad_listeners() || option_bool(OPT_CLEVERBIND)) &&
+ (option_bool(OPT_TFTP) || (daemon->port != 0 && daemon->port <= 1024)))
+ need_cap_net_bind_service = 1;
+
+ /* usptream servers which bind to an interface call SO_BINDTODEVICE
+ for each TCP connection, so need CAP_NET_RAW */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (serv->interface[0] != 0)
+ need_cap_net_raw = 1;
+
+ /* If we're doing Dbus or UBus, the above can be set dynamically,
+ (as can ports) so always (potentially) needed. */
+#ifdef HAVE_DBUS
+ if (option_bool(OPT_DBUS))
+ {
+ need_cap_net_bind_service = 1;
+ need_cap_net_raw = 1;
+ }
+#endif
+
+#ifdef HAVE_UBUS
+ if (option_bool(OPT_UBUS))
+ {
+ need_cap_net_bind_service = 1;
+ need_cap_net_raw = 1;
+ }
+#endif
+
+ /* determine capability API version here, while we can still
+ call safe_malloc */
+ int capsize = 1; /* for header version 1 */
+ char *fail = NULL;
+
+ 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);
+ capget(hdr, data); /* Get current values, for verification */
+
+ if (need_cap_net_admin && !(data->permitted & (1 << CAP_NET_ADMIN)))
+ fail = "NET_ADMIN";
+ else if (need_cap_net_raw && !(data->permitted & (1 << CAP_NET_RAW)))
+ fail = "NET_RAW";
+ else if (need_cap_net_bind_service && !(data->permitted & (1 << CAP_NET_BIND_SERVICE)))
+ fail = "NET_BIND_SERVICE";
+
+ if (fail)
+ die(_("process is missing required capability %s"), fail, EC_MISC);
+
+ /* Now set bitmaps to set caps after daemonising */
+ memset(data, 0, sizeof(*data) * capsize);
+
+ if (need_cap_net_admin)
+ data->effective |= (1 << CAP_NET_ADMIN);
+ if (need_cap_net_raw)
+ data->effective |= (1 << CAP_NET_RAW);
+ if (need_cap_net_bind_service)
+ data->effective |= (1 << CAP_NET_BIND_SERVICE);
+
+ data->permitted = data->effective;
+#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_INIT, 0, NULL);
+
+ 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);
+
+ 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, NULL);
+
+ if (pid != 0)
+ {
+ struct event_desc ev;
+ char *msg;
+
+ /* close our copy of write-end */
+ close(err_pipe[1]);
+
+ /* check for errors after the fork */
+ if (read_event(err_pipe[0], &ev, &msg))
+ fatal_event(&ev, msg);
+
+ _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, NULL);
+
+ if (pid != 0)
+ _exit(0);
+ }
+
+ /* write pidfile _after_ forking ! */
+ if (daemon->runfile)
+ {
+ int fd, err = 0;
+
+ sprintf(daemon->namebuff, "%d\n", (int) getpid());
+
+ /* Explanation: Some installations of dnsmasq (eg Debian/Ubuntu) locate the pid-file
+ in a directory which is writable by the non-privileged user that dnsmasq runs as. This
+ allows the daemon to delete the file as part of its shutdown. This is a security hole to the
+ extent that an attacker running as the unprivileged user could replace the pidfile with a
+ symlink, and have the target of that symlink overwritten as root next time dnsmasq starts.
+
+ The following code first deletes any existing file, and then opens it with the O_EXCL flag,
+ ensuring that the open() fails should there be any existing file (because the unlink() failed,
+ or an attacker exploited the race between unlink() and open()). This ensures that no symlink
+ attack can succeed.
+
+ Any compromise of the non-privileged user still theoretically allows the pid-file to be
+ replaced whilst dnsmasq is running. The worst that could allow is that the usual
+ "shutdown dnsmasq" shell command could be tricked into stopping any other process.
+
+ Note that if dnsmasq is started as non-root (eg for testing) it silently ignores
+ failure to write the pid-file.
+ */
+
+ unlink(daemon->runfile);
+
+ if ((fd = open(daemon->runfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1)
+ {
+ /* only complain if started as root */
+ if (getuid() == 0)
+ err = 1;
+ }
+ else
+ {
+ /* We're still running as root here. Change the ownership of the PID file
+ to the user we will be running as. Note that this is not to allow
+ us to delete the file, since that depends on the permissions
+ of the directory containing the file. That directory will
+ need to by owned by the dnsmasq user, and the ownership of the
+ file has to match, to keep systemd >273 happy. */
+ if (getuid() == 0 && ent_pw && ent_pw->pw_uid != 0 && fchown(fd, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
+ chown_warn = errno;
+
+ if (!read_write(fd, (unsigned char *)daemon->namebuff, strlen(daemon->namebuff), 0))
+ err = 1;
+ else
+ {
+ if (close(fd) == -1)
+ err = 1;
+ }
+ }
+
+ if (err)
+ {
+ send_event(err_pipe[1], EVENT_PIDFILE, errno, daemon->runfile);
+ _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);
+ if (nullfd != -1)
+ {
+ dup2(nullfd, STDOUT_FILENO);
+ dup2(nullfd, STDERR_FILENO);
+ dup2(nullfd, STDIN_FILENO);
+ close(nullfd);
+ }
+ }
+
+ /* if we are to run scripts, we need to fork a helper before dropping root. */
+ daemon->helperfd = -1;
+#ifdef HAVE_SCRIPT
+ if ((daemon->dhcp || daemon->dhcp6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) &&
+ (daemon->lease_change_command || daemon->luascript))
+ daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd);
+#endif
+
+ if (!option_bool(OPT_DEBUG) && getuid() == 0)
+ {
+ int bad_capabilities = 0;
+ gid_t dummy;
+
+ /* remove all supplementary groups */
+ if (gp &&
+ (setgroups(0, &dummy) == -1 ||
+ setgid(gp->gr_gid) == -1))
+ {
+ send_event(err_pipe[1], EVENT_GROUP_ERR, errno, daemon->groupname);
+ _exit(0);
+ }
+
+ if (ent_pw && ent_pw->pw_uid != 0)
+ {
+#if defined(HAVE_LINUX_NETWORK)
+ /* Need to be able to drop root. */
+ data->effective |= (1 << CAP_SETUID);
+ data->permitted |= (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, NULL);
+ _exit(0);
+ }
+
+ /* finally drop root */
+ if (setuid(ent_pw->pw_uid) == -1)
+ {
+ send_event(err_pipe[1], EVENT_USER_ERR, errno, daemon->username);
+ _exit(0);
+ }
+
+#ifdef HAVE_LINUX_NETWORK
+ data->effective &= ~(1 << CAP_SETUID);
+ data->permitted &= ~(1 << CAP_SETUID);
+
+ /* lose the setuid capability */
+ if (capset(hdr, data) == -1)
+ {
+ send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL);
+ _exit(0);
+ }
+#endif
+
+ }
+ }
+
+#ifdef HAVE_LINUX_NETWORK
+ free(hdr);
+ free(data);
+ if (option_bool(OPT_DEBUG))
+ prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+#endif
+
+#ifdef HAVE_TFTP
+ if (option_bool(OPT_TFTP))
+ {
+ DIR *dir;
+ struct tftp_prefix *p;
+
+ if (daemon->tftp_prefix)
+ {
+ if (!((dir = opendir(daemon->tftp_prefix))))
+ {
+ tftp_prefix_missing = 1;
+ if (!option_bool(OPT_TFTP_NO_FAIL))
+ {
+ send_event(err_pipe[1], EVENT_TFTP_ERR, errno, daemon->tftp_prefix);
+ _exit(0);
+ }
+ }
+ else
+ closedir(dir);
+ }
+
+ for (p = daemon->if_prefix; p; p = p->next)
+ {
+ p->missing = 0;
+ if (!((dir = opendir(p->prefix))))
+ {
+ p->missing = 1;
+ if (!option_bool(OPT_TFTP_NO_FAIL))
+ {
+ send_event(err_pipe[1], EVENT_TFTP_ERR, errno, p->prefix);
+ _exit(0);
+ }
+ }
+ else
+ closedir(dir);
+ }
+ }
+#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);
+ if (daemon->cachesize > 10000)
+ my_syslog(LOG_WARNING, _("cache size greater than 10000 may cause performance issues, and is unlikely to be useful."));
+ }
+ else
+ my_syslog(LOG_INFO, _("started, version %s cache disabled"), VERSION);
+
+ if (option_bool(OPT_LOCAL_SERVICE))
+ my_syslog(LOG_INFO, _("DNS service limited to local subnets"));
+ }
+
+ my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts);
+
+ if (chown_warn != 0)
+ my_syslog(LOG_WARNING, "chown of PID file %s failed: %s", daemon->runfile, strerror(chown_warn));
+
+#ifdef HAVE_DBUS
+ if (option_bool(OPT_DBUS))
+ {
+ 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
+
+#ifdef HAVE_UBUS
+ if (option_bool(OPT_UBUS))
+ {
+ if (daemon->ubus)
+ my_syslog(LOG_INFO, _("UBus support enabled: connected to system bus"));
+ else
+ my_syslog(LOG_INFO, _("UBus support enabled: bus connection pending"));
+ }
+#endif
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ int rc;
+ struct ds_config *ds;
+
+ /* Delay creating the timestamp file until here, after we've changed user, so that
+ it has the correct owner to allow updating the mtime later.
+ This means we have to report fatal errors via the pipe. */
+ if ((rc = setup_timestamp()) == -1)
+ {
+ send_event(err_pipe[1], EVENT_TIME_ERR, errno, daemon->timestamp_file);
+ _exit(0);
+ }
+
+ if (option_bool(OPT_DNSSEC_IGN_NS))
+ my_syslog(LOG_INFO, _("DNSSEC validation enabled but all unsigned answers are trusted"));
+ else
+ my_syslog(LOG_INFO, _("DNSSEC validation enabled"));
+
+ daemon->dnssec_no_time_check = option_bool(OPT_DNSSEC_TIME);
+ if (option_bool(OPT_DNSSEC_TIME) && !daemon->back_to_the_future)
+ my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until receipt of SIGINT"));
+
+ if (rc == 1)
+ my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid"));
+
+ for (ds = daemon->ds; ds; ds = ds->next)
+ my_syslog(LOG_INFO, _("configured with trust anchor for %s keytag %u"),
+ ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
+ }
+#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))
+ warn_bound_listeners();
+ else if (!option_bool(OPT_CLEVERBIND))
+ warn_wild_labels();
+
+ warn_int_names();
+
+ 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
+ for (context = daemon->dhcp; context; context = context->next)
+ log_context(AF_INET, context);
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ log_relay(AF_INET, relay);
+
+# ifdef HAVE_DHCP6
+ for (context = daemon->dhcp6; context; context = context->next)
+ log_context(AF_INET6, context);
+
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ log_relay(AF_INET6, relay);
+
+ if (daemon->doing_dhcp6 || daemon->doing_ra)
+ dhcp_construct_contexts(now);
+
+ if (option_bool(OPT_RA))
+ my_syslog(MS_DHCP | LOG_INFO, _("IPv6 router advertisement enabled"));
+# endif
+
+# ifdef HAVE_LINUX_NETWORK
+ if (did_bind)
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP, sockets bound exclusively to interface %s"), bound_device);
+
+ if (netlink_warn)
+ my_syslog(LOG_WARNING, netlink_warn);
+# endif
+
+ /* after dhcp_construct_contexts */
+ if (daemon->dhcp || daemon->doing_dhcp6)
+ lease_find_interfaces(now);
+#endif
+
+#ifdef HAVE_TFTP
+ if (option_bool(OPT_TFTP))
+ {
+ struct tftp_prefix *p;
+
+ my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s %s",
+ daemon->tftp_prefix ? _("root is ") : _("enabled"),
+ daemon->tftp_prefix ? daemon->tftp_prefix : "",
+ option_bool(OPT_TFTP_SECURE) ? _("secure mode") : "",
+ option_bool(OPT_SINGLE_PORT) ? _("single port mode") : "");
+
+ if (tftp_prefix_missing)
+ my_syslog(MS_TFTP | LOG_WARNING, _("warning: %s inaccessible"), daemon->tftp_prefix);
+
+ for (p = daemon->if_prefix; p; p = p->next)
+ if (p->missing)
+ my_syslog(MS_TFTP | LOG_WARNING, _("warning: TFTP directory %s inaccessible"), p->prefix);
+
+ /* 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 + daemon->numrrand; /* use other than TFTP */
+
+ if (max_fd < 0)
+ max_fd = 5;
+ else if (max_fd < 100 && !option_bool(OPT_SINGLE_PORT))
+ 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(0);
+
+ pid = getpid();
+
+ daemon->pipe_to_parent = -1;
+ for (i = 0; i < MAX_PROCS; i++)
+ daemon->tcp_pipes[i] = -1;
+
+#ifdef HAVE_INOTIFY
+ /* Using inotify, have to select a resolv file at startup */
+ poll_resolv(1, 0, now);
+#endif
+
+ while (1)
+ {
+ int timeout = -1;
+
+ poll_reset();
+
+ /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
+ if (daemon->tftp_trans ||
+ (option_bool(OPT_DBUS) && !daemon->dbus))
+ timeout = 250;
+
+ /* Wake every second whilst waiting for DAD to complete */
+ else if (is_dad_listeners())
+ timeout = 1000;
+
+ set_dns_listeners();
+
+#ifdef HAVE_DBUS
+ if (option_bool(OPT_DBUS))
+ set_dbus_listeners();
+#endif
+
+#ifdef HAVE_UBUS
+ if (option_bool(OPT_UBUS))
+ set_ubus_listeners();
+#endif
+
+#ifdef HAVE_DHCP
+ if (daemon->dhcp || daemon->relay4)
+ {
+ poll_listen(daemon->dhcpfd, POLLIN);
+ if (daemon->pxefd != -1)
+ poll_listen(daemon->pxefd, POLLIN);
+ }
+#endif
+
+#ifdef HAVE_DHCP6
+ if (daemon->doing_dhcp6 || daemon->relay6)
+ poll_listen(daemon->dhcp6fd, POLLIN);
+
+ if (daemon->doing_ra)
+ poll_listen(daemon->icmp6fd, POLLIN);
+#endif
+
+#ifdef HAVE_INOTIFY
+ if (daemon->inotifyfd != -1)
+ poll_listen(daemon->inotifyfd, POLLIN);
+#endif
+
+#if defined(HAVE_LINUX_NETWORK)
+ poll_listen(daemon->netlinkfd, POLLIN);
+#elif defined(HAVE_BSD_NETWORK)
+ poll_listen(daemon->routefd, POLLIN);
+#endif
+
+ poll_listen(piperead, POLLIN);
+
+#ifdef HAVE_SCRIPT
+# ifdef HAVE_DHCP
+ while (helper_buf_empty() && do_script_run(now));
+# endif
+
+ /* Refresh cache */
+ if (option_bool(OPT_SCRIPT_ARP))
+ find_mac(NULL, NULL, 0, now);
+ while (helper_buf_empty() && do_arp_script_run());
+
+# ifdef HAVE_TFTP
+ while (helper_buf_empty() && do_tftp_script_run());
+# endif
+
+ if (!helper_buf_empty())
+ poll_listen(daemon->helperfd, POLLOUT);
+#else
+ /* need this for other side-effects */
+# ifdef HAVE_DHCP
+ while (do_script_run(now));
+# endif
+
+ while (do_arp_script_run());
+
+# ifdef HAVE_TFTP
+ while (do_tftp_script_run());
+# endif
+
+#endif
+
+
+ /* must do this just before do_poll(), when we know no
+ more calls to my_syslog() can occur */
+ set_log_writer();
+
+ if (do_poll(timeout) < 0)
+ continue;
+
+ now = dnsmasq_time();
+
+ check_log_writer(0);
+
+ /* prime. */
+ enumerate_interfaces(1);
+
+ /* Check the interfaces to see if any have exited DAD state
+ and if so, bind the address. */
+ if (is_dad_listeners())
+ {
+ enumerate_interfaces(0);
+ /* NB, is_dad_listeners() == 1 --> we're binding interfaces */
+ create_bound_listeners(0);
+ warn_bound_listeners();
+ }
+
+#if defined(HAVE_LINUX_NETWORK)
+ if (poll_check(daemon->netlinkfd, POLLIN))
+ netlink_multicast();
+#elif defined(HAVE_BSD_NETWORK)
+ if (poll_check(daemon->routefd, POLLIN))
+ route_sock();
+#endif
+
+#ifdef HAVE_INOTIFY
+ if (daemon->inotifyfd != -1 && poll_check(daemon->inotifyfd, POLLIN) && inotify_check(now))
+ {
+ if (daemon->port != 0 && !option_bool(OPT_NO_POLL))
+ poll_resolv(1, 1, now);
+ }
+#else
+ /* 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;
+ }
+#endif
+
+ if (poll_check(piperead, POLLIN))
+ async_event(piperead, now);
+
+#ifdef HAVE_DBUS
+ /* if we didn't create a DBus connection, retry now. */
+ if (option_bool(OPT_DBUS))
+ {
+ if (!daemon->dbus)
+ {
+ char *err = dbus_init();
+
+ if (daemon->dbus)
+ my_syslog(LOG_INFO, _("connected to system DBus"));
+ else if (err)
+ {
+ my_syslog(LOG_ERR, _("DBus error: %s"), err);
+ reset_option_bool(OPT_DBUS); /* fatal error, stop trying. */
+ }
+ }
+
+ check_dbus_listeners();
+ }
+#endif
+
+#ifdef HAVE_UBUS
+ /* if we didn't create a UBus connection, retry now. */
+ if (option_bool(OPT_UBUS))
+ {
+ if (!daemon->ubus)
+ {
+ char *err = ubus_init();
+
+ if (daemon->ubus)
+ my_syslog(LOG_INFO, _("connected to system UBus"));
+ else if (err)
+ {
+ my_syslog(LOG_ERR, _("UBus error: %s"), err);
+ reset_option_bool(OPT_UBUS); /* fatal error, stop trying. */
+ }
+ }
+
+ check_ubus_listeners();
+ }
+#endif
+
+ check_dns_listeners(now);
+
+#ifdef HAVE_TFTP
+ check_tftp_listeners(now);
+#endif
+
+#ifdef HAVE_DHCP
+ if (daemon->dhcp || daemon->relay4)
+ {
+ if (poll_check(daemon->dhcpfd, POLLIN))
+ dhcp_packet(now, 0);
+ if (daemon->pxefd != -1 && poll_check(daemon->pxefd, POLLIN))
+ dhcp_packet(now, 1);
+ }
+
+#ifdef HAVE_DHCP6
+ if ((daemon->doing_dhcp6 || daemon->relay6) && poll_check(daemon->dhcp6fd, POLLIN))
+ dhcp6_packet(now);
+
+ if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN))
+ icmp6_packet(now);
+#endif
+
+# ifdef HAVE_SCRIPT
+ if (daemon->helperfd != -1 && poll_check(daemon->helperfd, POLLOUT))
+ 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 || sig == SIGINT)
+ 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 if (sig == SIGINT)
+ {
+ /* Handle SIGINT normally in debug mode, so
+ ctrl-c continues to operate. */
+ if (option_bool(OPT_DEBUG))
+ exit(EC_MISC);
+ else
+ event = EVENT_TIME;
+ }
+ else
+ return;
+
+ send_event(pipewrite, event, 0, NULL);
+ errno = errsave;
+ }
+}
+
+/* now == 0 -> queue immediate callback */
+void send_alarm(time_t event, time_t now)
+{
+ if (now == 0 || event != 0)
+ {
+ /* alarm(0) or alarm(-ve) doesn't do what we want.... */
+ if ((now == 0 || difftime(event, now) <= 0.0))
+ send_event(pipewrite, EVENT_ALARM, 0, NULL);
+ else
+ alarm((unsigned)difftime(event, now));
+ }
+}
+
+void queue_event(int event)
+{
+ send_event(pipewrite, event, 0, NULL);
+}
+
+void send_event(int fd, int event, int data, char *msg)
+{
+ struct event_desc ev;
+ struct iovec iov[2];
+
+ ev.event = event;
+ ev.data = data;
+ ev.msg_sz = msg ? strlen(msg) : 0;
+
+ iov[0].iov_base = &ev;
+ iov[0].iov_len = sizeof(ev);
+ iov[1].iov_base = msg;
+ iov[1].iov_len = ev.msg_sz;
+
+ /* error pipe, debug mode. */
+ if (fd == -1)
+ fatal_event(&ev, msg);
+ else
+ /* pipe is non-blocking and struct event_desc is smaller than
+ PIPE_BUF, so this either fails or writes everything */
+ while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR);
+}
+
+/* NOTE: the memory used to return msg is leaked: use msgs in events only
+ to describe fatal errors. */
+static int read_event(int fd, struct event_desc *evp, char **msg)
+{
+ char *buf;
+
+ if (!read_write(fd, (unsigned char *)evp, sizeof(struct event_desc), 1))
+ return 0;
+
+ *msg = NULL;
+
+ if (evp->msg_sz != 0 &&
+ (buf = malloc(evp->msg_sz + 1)) &&
+ read_write(fd, (unsigned char *)buf, evp->msg_sz, 1))
+ {
+ buf[evp->msg_sz] = 0;
+ *msg = buf;
+ }
+
+ return 1;
+}
+
+static void fatal_event(struct event_desc *ev, char *msg)
+{
+ errno = ev->data;
+
+ switch (ev->event)
+ {
+ case EVENT_DIE:
+ exit(0);
+
+ case EVENT_FORK_ERR:
+ die(_("cannot fork into background: %s"), NULL, EC_MISC);
+
+ /* fall through */
+ case EVENT_PIPE_ERR:
+ die(_("failed to create helper: %s"), NULL, EC_MISC);
+
+ /* fall through */
+ case EVENT_CAP_ERR:
+ die(_("setting capabilities failed: %s"), NULL, EC_MISC);
+
+ /* fall through */
+ case EVENT_USER_ERR:
+ die(_("failed to change user-id to %s: %s"), msg, EC_MISC);
+
+ /* fall through */
+ case EVENT_GROUP_ERR:
+ die(_("failed to change group-id to %s: %s"), msg, EC_MISC);
+
+ /* fall through */
+ case EVENT_PIDFILE:
+ die(_("failed to open pidfile %s: %s"), msg, EC_FILE);
+
+ /* fall through */
+ case EVENT_LOG_ERR:
+ die(_("cannot open log %s: %s"), msg, EC_FILE);
+
+ /* fall through */
+ case EVENT_LUA_ERR:
+ die(_("failed to load Lua script: %s"), msg, EC_MISC);
+
+ /* fall through */
+ case EVENT_TFTP_ERR:
+ die(_("TFTP directory %s inaccessible: %s"), msg, EC_FILE);
+
+ /* fall through */
+ case EVENT_TIME_ERR:
+ die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF);
+ }
+}
+
+static void async_event(int pipe, time_t now)
+{
+ pid_t p;
+ struct event_desc ev;
+ int i, check = 0;
+ char *msg;
+
+ /* NOTE: the memory used to return msg is leaked: use msgs in events only
+ to describe fatal errors. */
+
+ if (read_event(pipe, &ev, &msg))
+ switch (ev.event)
+ {
+ case EVENT_RELOAD:
+ daemon->soa_sn++; /* Bump zone serial, as it may have changed. */
+
+ /* fall through */
+
+ case EVENT_INIT:
+ clear_cache_and_reload(now);
+
+ if (daemon->port != 0)
+ {
+ if (daemon->resolv_files && option_bool(OPT_NO_POLL))
+ {
+ reload_servers(daemon->resolv_files->name);
+ check = 1;
+ }
+
+ if (daemon->servers_file)
+ {
+ read_servers_file();
+ check = 1;
+ }
+
+ if (check)
+ check_servers(0);
+ }
+
+#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 || daemon->doing_dhcp6)
+ {
+ lease_prune(NULL, now);
+ lease_update_file(now);
+ }
+#ifdef HAVE_DHCP6
+ else if (daemon->doing_ra)
+ /* Not doing DHCP, so no lease system, manage alarms for ra only */
+ send_alarm(periodic_ra(now), now);
+#endif
+#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;
+
+#if defined(HAVE_SCRIPT)
+ case EVENT_KILLED:
+ my_syslog(LOG_WARNING, _("script process killed by signal %d"), ev.data);
+ break;
+
+ case EVENT_EXITED:
+ my_syslog(LOG_WARNING, _("script 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;
+
+ case EVENT_SCRIPT_LOG:
+ my_syslog(MS_SCRIPT | LOG_DEBUG, "%s", msg ? msg : "");
+ free(msg);
+ msg = NULL;
+ break;
+
+ /* necessary for fatal errors in helper */
+ case EVENT_USER_ERR:
+ case EVENT_DIE:
+ case EVENT_LUA_ERR:
+ fatal_event(&ev, msg);
+ break;
+#endif
+
+ case EVENT_REOPEN:
+ /* Note: this may leave TCP-handling processes with the old file still open.
+ 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_NEWADDR:
+ newaddress(now);
+ break;
+
+ case EVENT_NEWROUTE:
+ resend_query();
+ /* Force re-reading resolv file right now, for luck. */
+ poll_resolv(0, 1, now);
+ break;
+
+ case EVENT_TIME:
+#ifdef HAVE_DNSSEC
+ if (daemon->dnssec_no_time_check && option_bool(OPT_DNSSEC_VALID) && option_bool(OPT_DNSSEC_TIME))
+ {
+ my_syslog(LOG_INFO, _("now checking DNSSEC signature timestamps"));
+ daemon->dnssec_no_time_check = 0;
+ clear_cache_and_reload(now);
+ }
+#endif
+ break;
+
+ case EVENT_TERM:
+ /* Knock all our children on the head. */
+ for (i = 0; i < MAX_PROCS; i++)
+ if (daemon->tcp_pids[i] != 0)
+ kill(daemon->tcp_pids[i], SIGALRM);
+
+#if defined(HAVE_SCRIPT) && defined(HAVE_DHCP)
+ /* 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);
+
+#ifdef HAVE_DNSSEC
+ /* update timestamp file on TERM if time is considered valid */
+ if (daemon->back_to_the_future)
+ {
+ if (utimes(daemon->timestamp_file, NULL) == -1)
+ my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+ }
+#endif
+
+ if (daemon->runfile)
+ unlink(daemon->runfile);
+
+#ifdef HAVE_DUMPFILE
+ if (daemon->dumpfd != -1)
+ close(daemon->dumpfd);
+#endif
+
+ my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM"));
+ flush_log();
+ exit(EC_GOOD);
+ }
+}
+
+static 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(0);
+ 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)
+{
+ (void)now;
+
+ if (daemon->port != 0)
+ cache_reload();
+
+#ifdef HAVE_DHCP
+ if (daemon->dhcp || daemon->doing_dhcp6)
+ {
+ if (option_bool(OPT_ETHERS))
+ dhcp_read_ethers();
+ reread_dhcp();
+ dhcp_update_configs(daemon->dhcp_conf);
+ lease_update_from_configs();
+ lease_update_file(now);
+ lease_update_dns(1);
+ }
+#ifdef HAVE_DHCP6
+ else if (daemon->doing_ra)
+ /* Not doing DHCP, so no lease system, manage
+ alarms for ra only */
+ send_alarm(periodic_ra(now), now);
+#endif
+#endif
+}
+
+static void set_dns_listeners(void)
+{
+ struct serverfd *serverfdp;
+ struct listener *listener;
+ struct randfd_list *rfl;
+ int i;
+
+#ifdef HAVE_TFTP
+ int tftp = 0;
+ struct tftp_transfer *transfer;
+ if (!option_bool(OPT_SINGLE_PORT))
+ for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
+ {
+ tftp++;
+ poll_listen(transfer->sockfd, POLLIN);
+ }
+#endif
+
+ for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+ poll_listen(serverfdp->fd, POLLIN);
+
+ for (i = 0; i < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount != 0)
+ poll_listen(daemon->randomsocks[i].fd, POLLIN);
+
+ /* Check overflow random sockets too. */
+ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
+ poll_listen(rfl->rfd->fd, POLLIN);
+
+ /* check to see if we have free tcp process slots. */
+ for (i = MAX_PROCS - 1; i >= 0; i--)
+ if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
+ break;
+
+ for (listener = daemon->listeners; listener; listener = listener->next)
+ {
+ if (listener->fd != -1)
+ poll_listen(listener->fd, POLLIN);
+
+ /* Only listen for TCP connections when a process slot
+ is available. Death of a child goes through the select loop, so
+ we don't need to explicitly arrange to wake up here,
+ we'll be called again when a slot becomes available. */
+ if (listener->tcpfd != -1 && i >= 0)
+ poll_listen(listener->tcpfd, POLLIN);
+
+#ifdef HAVE_TFTP
+ /* tftp == 0 in single-port mode. */
+ if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
+ poll_listen(listener->tftpfd, POLLIN);
+#endif
+ }
+
+ if (!option_bool(OPT_DEBUG))
+ for (i = 0; i < MAX_PROCS; i++)
+ if (daemon->tcp_pipes[i] != -1)
+ poll_listen(daemon->tcp_pipes[i], POLLIN);
+}
+
+static void check_dns_listeners(time_t now)
+{
+ struct serverfd *serverfdp;
+ struct listener *listener;
+ struct randfd_list *rfl;
+ int i;
+ int pipefd[2];
+
+ for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+ if (poll_check(serverfdp->fd, POLLIN))
+ reply_query(serverfdp->fd, now);
+
+ for (i = 0; i < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount != 0 &&
+ poll_check(daemon->randomsocks[i].fd, POLLIN))
+ reply_query(daemon->randomsocks[i].fd, now);
+
+ /* Check overflow random sockets too. */
+ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
+ if (poll_check(rfl->rfd->fd, POLLIN))
+ reply_query(rfl->rfd->fd, now);
+
+ /* Races. The child process can die before we read all of the data from the
+ pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the
+ process, and tcp_pipes to -1 and close the FD when we read the last
+ of the data - indicated by cache_recv_insert returning zero.
+ The order of these events is indeterminate, and both are needed
+ to free the process slot. Once the child process has gone, poll()
+ returns POLLHUP, not POLLIN, so have to check for both here. */
+ if (!option_bool(OPT_DEBUG))
+ for (i = 0; i < MAX_PROCS; i++)
+ if (daemon->tcp_pipes[i] != -1 &&
+ poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) &&
+ !cache_recv_insert(now, daemon->tcp_pipes[i]))
+ {
+ close(daemon->tcp_pipes[i]);
+ daemon->tcp_pipes[i] = -1;
+ }
+
+ for (listener = daemon->listeners; listener; listener = listener->next)
+ {
+ if (listener->fd != -1 && poll_check(listener->fd, POLLIN))
+ receive_query(listener, now);
+
+#ifdef HAVE_TFTP
+ if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN))
+ tftp_request(listener, now);
+#endif
+
+ /* check to see if we have a free tcp process slot.
+ Note that we can't assume that because we had
+ at least one a poll() time, that we still do.
+ There may be more waiting connections after
+ poll() returns then free process slots. */
+ for (i = MAX_PROCS - 1; i >= 0; i--)
+ if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
+ break;
+
+ if (listener->tcpfd != -1 && i >= 0 && poll_check(listener->tcpfd, POLLIN))
+ {
+ int confd, client_ok = 1;
+ struct irec *iface = NULL;
+ pid_t p;
+ union mysockaddr tcp_addr;
+ socklen_t tcp_len = sizeof(union mysockaddr);
+
+ while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
+
+ if (confd == -1)
+ continue;
+
+ if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
+ {
+ close(confd);
+ continue;
+ }
+
+ /* Make sure that the interface list is up-to-date.
+
+ We do this here as we may need the results below, and
+ the DNS code needs them for --interface-name stuff.
+
+ Multiple calls to enumerate_interfaces() per select loop are
+ inhibited, so calls to it in the child process (which doesn't select())
+ have no effect. This avoids two processes reading from the same
+ netlink fd and screwing the pooch entirely.
+ */
+
+ enumerate_interfaces(0);
+
+ if (option_bool(OPT_NOWILD))
+ iface = listener->iface; /* May be NULL */
+ else
+ {
+ int if_index;
+ char intr_name[IF_NAMESIZE];
+
+ /* if we can find the arrival interface, check it's one that's allowed */
+ if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
+ indextoname(listener->tcpfd, if_index, intr_name))
+ {
+ union all_addr addr;
+
+ if (tcp_addr.sa.sa_family == AF_INET6)
+ addr.addr6 = tcp_addr.in6.sin6_addr;
+ else
+ addr.addr4 = tcp_addr.in.sin_addr;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->index == if_index &&
+ iface->addr.sa.sa_family == tcp_addr.sa.sa_family)
+ break;
+
+ if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
+ client_ok = 0;
+ }
+
+ if (option_bool(OPT_CLEVERBIND))
+ iface = listener->iface; /* May be NULL */
+ else
+ {
+ /* 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. */
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (sockaddr_isequal(&iface->addr, &tcp_addr))
+ break;
+
+ if (!iface)
+ client_ok = 0;
+ }
+ }
+
+ if (!client_ok)
+ {
+ shutdown(confd, SHUT_RDWR);
+ close(confd);
+ }
+ else if (!option_bool(OPT_DEBUG) && pipe(pipefd) == 0 && (p = fork()) != 0)
+ {
+ close(pipefd[1]); /* parent needs read pipe end. */
+ if (p == -1)
+ close(pipefd[0]);
+ else
+ {
+#ifdef HAVE_LINUX_NETWORK
+ /* The child process inherits the netlink socket,
+ which it never uses, but when the parent (us)
+ uses it in the future, the answer may go to the
+ child, resulting in the parent blocking
+ forever awaiting the result. To avoid this
+ the child closes the netlink socket, but there's
+ a nasty race, since the parent may use netlink
+ before the child has done the close.
+
+ To avoid this, the parent blocks here until a
+ single byte comes back up the pipe, which
+ is sent by the child after it has closed the
+ netlink socket. */
+
+ unsigned char a;
+ read_write(pipefd[0], &a, 1, 1);
+#endif
+
+ /* i holds index of free slot */
+ daemon->tcp_pids[i] = p;
+ daemon->tcp_pipes[i] = pipefd[0];
+ }
+ close(confd);
+
+ /* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */
+ daemon->log_id += TCP_MAX_QUERIES;
+ }
+ else
+ {
+ unsigned char *buff;
+ struct server *s;
+ int flags;
+ struct in_addr netmask;
+ int auth_dns;
+
+ if (iface)
+ {
+ netmask = iface->netmask;
+ auth_dns = iface->dns_auth;
+ }
+ else
+ {
+ netmask.s_addr = 0;
+ auth_dns = 0;
+ }
+
+ /* Arrange for SIGALRM after CHILD_LIFETIME seconds to
+ terminate the process. */
+ if (!option_bool(OPT_DEBUG))
+ {
+#ifdef HAVE_LINUX_NETWORK
+ /* See comment above re: netlink socket. */
+ unsigned char a = 0;
+
+ close(daemon->netlinkfd);
+ read_write(pipefd[1], &a, 1, 0);
+#endif
+ alarm(CHILD_LIFETIME);
+ close(pipefd[0]); /* close read end in child. */
+ daemon->pipe_to_parent = pipefd[1];
+ }
+
+ /* 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);
+
+ buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
+
+ 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);
+ }
+
+ if (!option_bool(OPT_DEBUG))
+ {
+ close(daemon->pipe_to_parent);
+ flush_log();
+ _exit(0);
+ }
+ }
+ }
+ }
+}
+
+#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. */
+
+ 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;
+
+#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 (retry_send(sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0,
+ (struct sockaddr *)&saddr, sizeof(saddr))));
+
+ gotreply = delay_dhcp(dnsmasq_time(), PING_WAIT, fd, addr.s_addr, id);
+
+#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;
+}
+
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
+{
+ /* Delay processing DHCP packets for "sec" seconds counting from "start".
+ If "fd" is not -1 it will stop waiting if an ICMP echo reply is received
+ from "addr" with ICMP ID "id" and return 1 */
+
+ /* Note that whilst waiting, we check for
+ (and service) events on the DNS and TFTP sockets, (so doing that
+ better not use any resources our caller has in use...)
+ but we remain deaf to signals or further DHCP packets. */
+
+ /* There can be a problem using dnsmasq_time() to end the loop, since
+ it's not monotonic, and can go backwards if the system clock is
+ tweaked, leading to the code getting stuck in this loop and
+ ignoring DHCP requests. To fix this, we check to see if select returned
+ as a result of a timeout rather than a socket becoming available. We
+ only allow this to happen as many times as it takes to get to the wait time
+ in quarter-second chunks. This provides a fallback way to end loop. */
+
+ int rc, timeout_count;
+ time_t now;
+
+ for (now = dnsmasq_time(), timeout_count = 0;
+ (difftime(now, start) <= (float)sec) && (timeout_count < sec * 4);)
+ {
+ poll_reset();
+ if (fd != -1)
+ poll_listen(fd, POLLIN);
+ set_dns_listeners();
+ set_log_writer();
+
+#ifdef HAVE_DHCP6
+ if (daemon->doing_ra)
+ poll_listen(daemon->icmp6fd, POLLIN);
+#endif
+
+ rc = do_poll(250);
+
+ if (rc < 0)
+ continue;
+ else if (rc == 0)
+ timeout_count++;
+
+ now = dnsmasq_time();
+
+ check_log_writer(0);
+ check_dns_listeners(now);
+
+#ifdef HAVE_DHCP6
+ if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN))
+ icmp6_packet(now);
+#endif
+
+#ifdef HAVE_TFTP
+ check_tftp_listeners(now);
+#endif
+
+ if (fd != -1)
+ {
+ struct {
+ struct ip ip;
+ struct icmp icmp;
+ } packet;
+ struct sockaddr_in faddr;
+ socklen_t len = sizeof(faddr);
+
+ if (poll_check(fd, POLLIN) &&
+ recvfrom(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&faddr, &len) == sizeof(packet) &&
+ addr == faddr.sin_addr.s_addr &&
+ packet.icmp.icmp_type == ICMP_ECHOREPLY &&
+ packet.icmp.icmp_seq == 0 &&
+ packet.icmp.icmp_id == id)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h
new file mode 100755
index 0000000..8674823
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h
@@ -0,0 +1,1777 @@
+/* dnsmasq is Copyright (c) 2000-2021 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-2021 Simon Kelley"
+
+/* We do defines that influence behavior of stdio.h, so complain
+ if included too early. */
+#ifdef _STDIO_H
+# error "Header file stdio.h included too early!"
+#endif
+
+#ifndef NO_LARGEFILE
+/* Ensure we can use files >2GB (log files may grow this big) */
+# 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
+
+#if (defined(__GNUC__) && __GNUC__ >= 3) || defined(__clang__)
+#define ATTRIBUTE_NORETURN __attribute__ ((noreturn))
+#else
+#define ATTRIBUTE_NORETURN
+#endif
+
+/* get these before config.h for IPv6 stuff... */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifdef __APPLE__
+/* Define before netinet/in.h to select API. OSX Lion onwards. */
+# define __APPLE_USE_RFC_3542
+#endif
+#include <netinet/in.h>
+
+/* Also needed before config.h. */
+#include <getopt.h>
+
+#include "config.h"
+#include "ip6addr.h"
+#include "metrics.h"
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+#define countof(x) (long)(sizeof(x) / sizeof(x[0]))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+#include "dns-protocol.h"
+#include "dhcp-protocol.h"
+#ifdef HAVE_DHCP6
+#include "dhcp6-protocol.h"
+#include "radv-protocol.h"
+#endif
+
+#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 <poll.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 <stdint.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/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.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/version.h>
+#include <linux/sockios.h>
+#include <linux/capability.h>
+/* There doesn't seem to be a universally-available
+ userspace header for these. */
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, cap_user_data_t data);
+#define LINUX_CAPABILITY_VERSION_1 0x19980330
+#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
+
+/* Backwards compat with 2.83 */
+#if defined(HAVE_NETTLEHASH)
+# define HAVE_CRYPTOHASH
+#endif
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+# include <nettle/nettle-meta.h>
+#endif
+
+/* daemon is function in the C library.... */
+#define daemon dnsmasq_daemon
+
+#define ADDRSTRLEN INET6_ADDRSTRLEN
+
+/* Async event queue */
+struct event_desc {
+ int event, data, msg_sz;
+};
+
+#define EVENT_RELOAD 1
+#define EVENT_DUMP 2
+#define EVENT_ALARM 3
+#define EVENT_TERM 4
+#define EVENT_CHILD 5
+#define EVENT_REOPEN 6
+#define EVENT_EXITED 7
+#define EVENT_KILLED 8
+#define EVENT_EXEC_ERR 9
+#define EVENT_PIPE_ERR 10
+#define EVENT_USER_ERR 11
+#define EVENT_CAP_ERR 12
+#define EVENT_PIDFILE 13
+#define EVENT_HUSER_ERR 14
+#define EVENT_GROUP_ERR 15
+#define EVENT_DIE 16
+#define EVENT_LOG_ERR 17
+#define EVENT_FORK_ERR 18
+#define EVENT_LUA_ERR 19
+#define EVENT_TFTP_ERR 20
+#define EVENT_INIT 21
+#define EVENT_NEWADDR 22
+#define EVENT_NEWROUTE 23
+#define EVENT_TIME_ERR 24
+#define EVENT_SCRIPT_LOG 25
+#define EVENT_TIME 26
+
+/* 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
+
+#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_IP 29
+#define OPT_NO_OVERRIDE 30
+#define OPT_NO_REBIND 31
+#define OPT_ADD_MAC 32
+#define OPT_DNSSEC_PROXY 33
+#define OPT_CONSEC_ADDR 34
+#define OPT_CONNTRACK 35
+#define OPT_FQDN_UPDATE 36
+#define OPT_RA 37
+#define OPT_TFTP_LC 38
+#define OPT_CLEVERBIND 39
+#define OPT_TFTP 40
+#define OPT_CLIENT_SUBNET 41
+#define OPT_QUIET_DHCP 42
+#define OPT_QUIET_DHCP6 43
+#define OPT_QUIET_RA 44
+#define OPT_DNSSEC_VALID 45
+#define OPT_DNSSEC_TIME 46
+#define OPT_DNSSEC_DEBUG 47
+#define OPT_DNSSEC_IGN_NS 48
+#define OPT_LOCAL_SERVICE 49
+#define OPT_LOOP_DETECT 50
+#define OPT_EXTRALOG 51
+#define OPT_TFTP_NO_FAIL 52
+#define OPT_SCRIPT_ARP 53
+#define OPT_MAC_B64 54
+#define OPT_MAC_HEX 55
+#define OPT_TFTP_APREF_MAC 56
+#define OPT_RAPID_COMMIT 57
+#define OPT_UBUS 58
+#define OPT_IGNORE_CLID 59
+#define OPT_SINGLE_PORT 60
+#define OPT_LEASE_RENEW 61
+#define OPT_LOG_DEBUG 62
+#define OPT_UMBRELLA 63
+#define OPT_UMBRELLA_DEVID 64
+#define OPT_CMARK_ALST_EN 65
+#define OPT_QUIET_TFTP 66
+#define OPT_LAST 67
+
+#define OPTION_BITS (sizeof(unsigned int)*8)
+#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
+#define option_var(x) (daemon->options[(x) / OPTION_BITS])
+#define option_val(x) ((1u) << ((x) % OPTION_BITS))
+#define option_bool(x) (option_var(x) & option_val(x))
+
+/* extra flags for my_syslog, we use facilities since they are known
+ not to occupy the same bits as priorities, no matter how syslog.h is set up.
+ MS_DEBUG messages are suppressed unless --log-debug is set. */
+#define MS_TFTP LOG_USER
+#define MS_DHCP LOG_DAEMON
+#define MS_SCRIPT LOG_MAIL
+#define MS_DEBUG LOG_NEWS
+
+/* Note that this is used widely as a container for IPv4/IPv6 addresses,
+ so for that reason, was well as to avoid wasting memory in almost every
+ cache entry, the other variants should not be larger than
+ sizeof(struct in6_addr) - 16 bytes.
+*/
+union all_addr {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ struct {
+ union {
+ struct crec *cache;
+ char *name;
+ } target;
+ unsigned int uid;
+ int is_name_ptr; /* disciminates target union */
+ } cname;
+ struct {
+ struct blockdata *keydata;
+ unsigned short keylen, flags, keytag;
+ unsigned char algo;
+ } key;
+ struct {
+ struct blockdata *keydata;
+ unsigned short keylen, keytag;
+ unsigned char algo;
+ unsigned char digest;
+ } ds;
+ struct {
+ struct blockdata *target;
+ unsigned short targetlen, srvport, priority, weight;
+ } srv;
+ /* for log_query */
+ struct {
+ unsigned short keytag, algo, digest, rcode;
+ int ede;
+ } log;
+};
+
+
+struct bogus_addr {
+ int is6, prefix;
+ union all_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;
+};
+
+#ifndef NO_ID
+#define TXT_STAT_CACHESIZE 1
+#define TXT_STAT_INSERTS 2
+#define TXT_STAT_EVICTIONS 3
+#define TXT_STAT_MISSES 4
+#define TXT_STAT_HITS 5
+#define TXT_STAT_AUTH 6
+#define TXT_STAT_SERVERS 7
+#endif
+
+struct txt_record {
+ char *name;
+ unsigned char *txt;
+ unsigned short class, len;
+ int stat;
+ struct txt_record *next;
+};
+
+struct ptr_record {
+ char *name, *ptr;
+ struct ptr_record *next;
+};
+
+struct cname {
+ int ttl, flag;
+ char *alias, *target;
+ struct cname *next, *targetp;
+};
+
+struct ds_config {
+ char *name, *digest;
+ int digestlen, class, algo, keytag, digest_type;
+ struct ds_config *next;
+};
+
+#define ADDRLIST_LITERAL 1
+#define ADDRLIST_IPV6 2
+#define ADDRLIST_REVONLY 4
+#define ADDRLIST_PREFIX 8
+#define ADDRLIST_WILDCARD 16
+#define ADDRLIST_DECLINED 32
+
+struct addrlist {
+ union all_addr addr;
+ int flags, prefixlen;
+ time_t decline_time;
+ struct addrlist *next;
+};
+
+#define AUTH6 1
+#define AUTH4 2
+
+struct auth_zone {
+ char *domain;
+ struct auth_name_list {
+ char *name;
+ int flags;
+ struct auth_name_list *next;
+ } *interface_names;
+ struct addrlist *subnet;
+ struct addrlist *exclude;
+ struct auth_zone *next;
+};
+
+#define HR_6 1
+#define HR_4 2
+
+struct host_record {
+ int ttl, flags;
+ struct name_list {
+ char *name;
+ struct name_list *next;
+ } *names;
+ struct in_addr addr;
+ struct in6_addr addr6;
+ struct host_record *next;
+};
+
+#define IN4 1
+#define IN6 2
+#define INP4 4
+#define INP6 8
+
+struct interface_name {
+ char *name; /* domain name */
+ char *intr; /* interface name */
+ int flags;
+ struct in_addr proto4;
+ struct in6_addr proto6;
+ struct addrlist *addr;
+ struct interface_name *next;
+};
+
+union bigname {
+ char name[MAXDNAME];
+ union bigname *next; /* freelist */
+};
+
+struct blockdata {
+ struct blockdata *next;
+ unsigned char key[KEYBLOCK_LEN];
+};
+
+struct crec {
+ struct crec *next, *prev, *hash_next;
+ union all_addr addr;
+ time_t ttd; /* time to die */
+ /* used as class if DNSKEY/DS, index to source for F_HOSTS */
+ unsigned int uid;
+ unsigned int flags;
+ union {
+ char sname[SMALLDNAME];
+ union bigname *bname;
+ char *namep;
+ } name;
+};
+
+#define SIZEOF_BARE_CREC (sizeof(struct crec) - SMALLDNAME)
+#define SIZEOF_POINTER_CREC (sizeof(struct crec) + sizeof(char *) - SMALLDNAME)
+
+#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_DNSKEY (1u<<12)
+#define F_CONFIG (1u<<13)
+#define F_DS (1u<<14)
+#define F_DNSSECOK (1u<<15)
+#define F_UPSTREAM (1u<<16)
+#define F_RRNAME (1u<<17)
+#define F_SERVER (1u<<18)
+#define F_QUERY (1u<<19)
+#define F_NOERR (1u<<20)
+#define F_AUTH (1u<<21)
+#define F_DNSSEC (1u<<22)
+#define F_KEYTAG (1u<<23)
+#define F_SECSTAT (1u<<24)
+#define F_NO_RR (1u<<25)
+#define F_IPSET (1u<<26)
+#define F_NOEXTRA (1u<<27)
+#define F_DOMAINSRV (1u<<28)
+#define F_RCODE (1u<<29)
+#define F_SRV (1u<<30)
+
+#define UID_NONE 0
+/* Values of uid in crecs with F_CONFIG bit set. */
+#define SRC_CONFIG 1
+#define SRC_HOSTS 2
+#define SRC_AH 3
+
+
+/* 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;
+ struct sockaddr_in6 in6;
+};
+
+/* bits in flag param to IPv6 callbacks from iface_enumerate() */
+#define IFACE_TENTATIVE 1
+#define IFACE_DEPRECATED 2
+#define IFACE_PERMANENT 4
+
+
+/* The actual values here matter, since we sort on them to get records in the order
+ IPv6 addr, IPv4 addr, all zero return, no-data return, send upstream. */
+#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next three flags */
+#define SERV_ALL_ZEROS 2 /* return all zeros for A and AAAA */
+#define SERV_4ADDR 4 /* addr is IPv4 */
+#define SERV_6ADDR 8 /* addr is IPv6 */
+#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 and log code */
+#define SERV_WILDCARD 512 /* domain has leading '*' */
+#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
+#define SERV_FROM_RESOLV 2048 /* 1 for servers from resolv, 0 for command line. */
+#define SERV_FROM_FILE 4096 /* read from --servers-file */
+#define SERV_LOOP 8192 /* server causes forwarding loop */
+#define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */
+#define SERV_GOT_TCP 32768 /* Got some data from the TCP connection */
+
+struct serverfd {
+ int fd;
+ union mysockaddr source_addr;
+ char interface[IF_NAMESIZE+1];
+ unsigned int ifindex, used, preallocated;
+ struct serverfd *next;
+};
+
+struct randfd {
+ struct server *serv;
+ int fd;
+ unsigned short refcount; /* refcount == 0xffff means overflow record. */
+};
+
+struct randfd_list {
+ struct randfd *rfd;
+ struct randfd_list *next;
+};
+
+
+struct server {
+ u16 flags, domain_len;
+ char *domain;
+ struct server *next;
+ int serial, arrayposn;
+ int last_server;
+ union mysockaddr addr, source_addr;
+ char interface[IF_NAMESIZE+1];
+ unsigned int ifindex; /* corresponding to interface, above */
+ struct serverfd *sfd;
+ int tcpfd, edns_pktsz;
+ time_t pktsz_reduced;
+ unsigned int queries, failed_queries;
+ time_t forwardtime;
+ int forwardcount;
+#ifdef HAVE_LOOP
+ u32 uid;
+#endif
+};
+
+/* First four fields must match struct server in next three definitions.. */
+struct serv_addr4 {
+ u16 flags, domain_len;
+ char *domain;
+ struct server *next;
+ struct in_addr addr;
+};
+
+struct serv_addr6 {
+ u16 flags, domain_len;
+ char *domain;
+ struct server *next;
+ struct in6_addr addr;
+};
+
+struct serv_local {
+ u16 flags, domain_len;
+ char *domain;
+ struct server *next;
+};
+
+struct ipsets {
+ char **sets;
+ char *domain;
+ struct ipsets *next;
+};
+
+struct allowlist {
+ u32 mark, mask;
+ char **patterns;
+ struct allowlist *next;
+};
+
+struct irec {
+ union mysockaddr addr;
+ struct in_addr netmask; /* only valid for IPv4 */
+ int tftp_ok, dhcp_ok, mtu, done, warned, dad, dns_auth, index, multicast_done, found, label;
+ char *name;
+ struct irec *next;
+};
+
+struct listener {
+ int fd, tcpfd, tftpfd, used;
+ union mysockaddr addr;
+ struct irec *iface; /* only sometimes valid for non-wildcard */
+ struct listener *next;
+};
+
+/* interface and address parms from command line. */
+struct iname {
+ char *name;
+ union mysockaddr addr;
+ int used;
+ struct iname *next;
+};
+
+/* subnet parameters from command line */
+struct mysubnet {
+ union mysockaddr addr;
+ int addr_used;
+ int mask;
+};
+
+/* resolv-file parms from command-line */
+struct resolvc {
+ struct resolvc *next;
+ int is_default, logged;
+ time_t mtime;
+ char *name;
+#ifdef HAVE_INOTIFY
+ int wd; /* inotify watch descriptor */
+ char *file; /* pointer to file part if path */
+#endif
+};
+
+/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
+#define AH_DIR 1
+#define AH_INACTIVE 2
+#define AH_WD_DONE 4
+#define AH_HOSTS 8
+#define AH_DHCP_HST 16
+#define AH_DHCP_OPT 32
+struct hostsfile {
+ struct hostsfile *next;
+ int flags;
+ char *fname;
+#ifdef HAVE_INOTIFY
+ int wd; /* inotify watch descriptor */
+#endif
+ unsigned int index; /* matches to cache entries for logging */
+};
+
+/* packet-dump flags */
+#define DUMP_QUERY 0x0001
+#define DUMP_REPLY 0x0002
+#define DUMP_UP_QUERY 0x0004
+#define DUMP_UP_REPLY 0x0008
+#define DUMP_SEC_QUERY 0x0010
+#define DUMP_SEC_REPLY 0x0020
+#define DUMP_BOGUS 0x0040
+#define DUMP_SEC_BOGUS 0x0080
+
+/* DNSSEC status values. */
+#define STAT_SECURE 0x10000
+#define STAT_INSECURE 0x20000
+#define STAT_BOGUS 0x30000
+#define STAT_NEED_DS 0x40000
+#define STAT_NEED_KEY 0x50000
+#define STAT_TRUNCATED 0x60000
+#define STAT_SECURE_WILDCARD 0x70000
+#define STAT_OK 0x80000
+#define STAT_ABANDONED 0x90000
+
+#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
+#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
+#define DNSSEC_FAIL_INDET 0x0004 /* indetermined */
+#define DNSSEC_FAIL_NOKEYSUP 0x0008 /* no supported key algo. */
+#define DNSSEC_FAIL_NOSIG 0x0010 /* No RRsigs */
+#define DNSSEC_FAIL_NOZONE 0x0020 /* No Zone bit set */
+#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
+#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
+#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
+
+#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
+
+#define FREC_NOREBIND 1
+#define FREC_CHECKING_DISABLED 2
+#define FREC_HAS_SUBNET 4
+#define FREC_DNSKEY_QUERY 8
+#define FREC_DS_QUERY 16
+#define FREC_AD_QUESTION 32
+#define FREC_DO_QUESTION 64
+#define FREC_ADDED_PHEADER 128
+#define FREC_TEST_PKTSZ 256
+#define FREC_HAS_EXTRADATA 512
+#define FREC_HAS_PHEADER 1024
+#define FREC_NO_CACHE 2048
+
+#define HASH_SIZE 32 /* SHA-256 digest size */
+
+struct frec {
+ struct frec_src {
+ union mysockaddr source;
+ union all_addr dest;
+ unsigned int iface, log_id;
+ int fd;
+ unsigned short orig_id;
+ struct frec_src *next;
+ } frec_src;
+ struct server *sentto; /* NULL means free */
+ struct randfd_list *rfds;
+ unsigned short new_id;
+ int forwardall, flags;
+ time_t time;
+ unsigned char *hash[HASH_SIZE];
+#ifdef HAVE_DNSSEC
+ int class, work_counter;
+ struct blockdata *stash; /* Saved reply, whilst we validate */
+ size_t stash_len;
+ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
+ struct frec *next_dependent; /* list of above. */
+ struct frec *blocking_query; /* Query which is blocking us. */
+#endif
+ struct frec *next;
+};
+
+/* flags in top of length field for DHCP-option tables */
+#define OT_ADDR_LIST 0x8000
+#define OT_RFC1035_NAME 0x4000
+#define OT_INTERNAL 0x2000
+#define OT_NAME 0x1000
+#define OT_CSTRING 0x0800
+#define OT_DEC 0x0400
+#define OT_TIME 0x0200
+
+/* 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_TFTP 5
+#define ACTION_ARP 6
+#define ACTION_ARP_DEL 7
+
+#define LEASE_NEW 1 /* newly created */
+#define LEASE_CHANGED 2 /* modified */
+#define LEASE_AUX_CHANGED 4 /* CLID or expiry changed */
+#define LEASE_AUTH_NAME 8 /* hostname came from config, not from client */
+#define LEASE_USED 16 /* used this DHCPv6 transaction */
+#define LEASE_NA 32 /* IPv6 no-temporary lease */
+#define LEASE_TA 64 /* IPv6 temporary lease */
+#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
+#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
+
+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 */
+ int flags;
+ 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;
+ int new_interface; /* save possible originated interface */
+ int new_prefixlen; /* and its prefix length */
+#ifdef HAVE_DHCP6
+ struct in6_addr addr6;
+ unsigned int iaid;
+ struct slaac_address {
+ struct in6_addr addr;
+ time_t ping_time;
+ int backoff; /* zero -> confirmed */
+ struct slaac_address *next;
+ } *slaac_address;
+ int vendorclass_count;
+#endif
+ 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 delay_config {
+ int delay;
+ struct dhcp_netid *netid;
+ struct delay_config *next;
+};
+
+struct hwaddr_config {
+ int hwaddr_len, hwaddr_type;
+ unsigned char hwaddr[DHCP_CHADDR_MAX];
+ 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 dhcp_netid *filter;
+#ifdef HAVE_DHCP6
+ struct addrlist *addr6;
+#endif
+ struct in_addr addr;
+ time_t decline_time;
+ unsigned int lease_time;
+ struct hwaddr_config *hwaddr;
+ struct dhcp_config *next;
+};
+
+#define have_config(config, mask) ((config) && ((config)->flags & (mask)))
+
+#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 */
+#define CONFIG_ADDR6 4096
+#define CONFIG_ADDR6_HOSTS 16384 /* address added by from /etc/hosts */
+
+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
+#define DHOPT_TAGOK 4096
+#define DHOPT_ADDR6 8192
+#define DHOPT_VENDOR_PXE 16384
+
+struct dhcp_boot {
+ char *file, *sname, *tftp_sname;
+ struct in_addr next_server;
+ struct dhcp_netid *netid;
+ struct dhcp_boot *next;
+};
+
+struct dhcp_match_name {
+ char *name;
+ int wildcard;
+ struct dhcp_netid *netid;
+ struct dhcp_match_name *next;
+};
+
+struct pxe_service {
+ unsigned short CSA, type;
+ char *menu, *basename, *sname;
+ struct in_addr server;
+ struct dhcp_netid *netid;
+ struct pxe_service *next;
+};
+
+#define DHCP_PXE_DEF_VENDOR "PXEClient"
+
+#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 circuit-id */
+struct dhcp_vendor {
+ int len, match_type;
+ unsigned int enterprise;
+ char *data;
+ struct dhcp_netid netid;
+ struct dhcp_vendor *next;
+};
+
+struct dhcp_pxe_vendor {
+ char *data;
+ struct dhcp_pxe_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, *prefix; /* prefix is text-prefix on domain name */
+ struct in_addr start, end;
+ struct in6_addr start6, end6;
+ int is6, indexed, prefixlen;
+ struct cond_domain *next;
+};
+
+struct ra_interface {
+ char *name;
+ char *mtu_name;
+ int interval, lifetime, prio, mtu;
+ struct ra_interface *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 */
+#ifdef HAVE_DHCP6
+ struct in6_addr start6, end6; /* range of available addresses */
+ struct in6_addr local6;
+ int prefix, if_index;
+ unsigned int valid, preferred, saved_valid;
+ time_t ra_time, ra_short_period_start, address_lost_time;
+ char *template_interface;
+#endif
+ int flags;
+ struct dhcp_netid netid, *filter;
+ struct dhcp_context *next, *current;
+};
+
+struct shared_network {
+ int if_index;
+ struct in_addr match_addr, shared_addr;
+#ifdef HAVE_DHCP6
+ /* shared_addr == 0 for IP6 entries. */
+ struct in6_addr match_addr6, shared_addr6;
+#endif
+ struct shared_network *next;
+};
+
+#define CONTEXT_STATIC (1u<<0)
+#define CONTEXT_NETMASK (1u<<1)
+#define CONTEXT_BRDCAST (1u<<2)
+#define CONTEXT_PROXY (1u<<3)
+#define CONTEXT_RA_ROUTER (1u<<4)
+#define CONTEXT_RA_DONE (1u<<5)
+#define CONTEXT_RA_NAME (1u<<6)
+#define CONTEXT_RA_STATELESS (1u<<7)
+#define CONTEXT_DHCP (1u<<8)
+#define CONTEXT_DEPRECATE (1u<<9)
+#define CONTEXT_TEMPLATE (1u<<10) /* create contexts using addresses */
+#define CONTEXT_CONSTRUCTED (1u<<11)
+#define CONTEXT_GC (1u<<12)
+#define CONTEXT_RA (1u<<13)
+#define CONTEXT_CONF_USED (1u<<14)
+#define CONTEXT_USED (1u<<15)
+#define CONTEXT_OLD (1u<<16)
+#define CONTEXT_V6 (1u<<17)
+#define CONTEXT_RA_OFF_LINK (1u<<18)
+#define CONTEXT_SETLEASE (1u<<19)
+
+struct ping_result {
+ struct in_addr addr;
+ time_t time;
+ unsigned int hash;
+ 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;
+ union all_addr source;
+ int if_index;
+ 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 tftp_prefix {
+ char *interface;
+ char *prefix;
+ int missing;
+ struct tftp_prefix *next;
+};
+
+struct dhcp_relay {
+ union all_addr local, server;
+ char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
+ int iface_index; /* working - interface in which requests arrived, for return */
+ struct dhcp_relay *current, *next;
+};
+
+extern struct daemon {
+ /* datastuctures representing the command-line and
+ config file arguments. All set (including defaults)
+ in option.c */
+
+ unsigned int options[OPTION_SIZE];
+ struct resolvc default_resolv, *resolv_files;
+ time_t last_resolv;
+ char *servers_file;
+ struct mx_srv_record *mxnames;
+ struct naptr *naptr;
+ struct txt_record *txt, *rr;
+ struct ptr_record *ptr;
+ struct host_record *host_records, *host_records_tail;
+ struct cname *cnames;
+ struct auth_zone *auth_zones;
+ struct interface_name *int_names;
+ char *mxtarget;
+ struct mysubnet *add_subnet4;
+ struct mysubnet *add_subnet6;
+ char *lease_file;
+ char *username, *groupname, *scriptuser;
+ char *luascript;
+ char *authserver, *hostmaster;
+ struct iname *authinterface;
+ struct name_list *secondary_forward_server;
+ int group_set, osport;
+ char *domain_suffix;
+ struct cond_domain *cond_domain, *synth_domains;
+ char *runfile;
+ char *lease_change_command;
+ struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
+ struct bogus_addr *bogus_addr, *ignore_addr;
+ struct server *servers, *local_domains, **serverarray, *no_rebind;
+ int server_has_wildcard;
+ int serverarraysz, serverarrayhwm;
+ struct ipsets *ipsets;
+ u32 allowlist_mask;
+ struct allowlist *allowlists;
+ 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, max_port;
+ unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl;
+ char *dns_client_id;
+ u32 umbrella_org;
+ u32 umbrella_asset;
+ u8 umbrella_device[8];
+ struct hostsfile *addn_hosts;
+ struct dhcp_context *dhcp, *dhcp6;
+ struct ra_interface *ra_interfaces;
+ struct dhcp_config *dhcp_conf;
+ struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
+ struct dhcp_match_name *dhcp_name_match;
+ struct dhcp_pxe_vendor *dhcp_pxe_vendors;
+ 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;
+ struct dhcp_relay *relay4, *relay6;
+ struct delay_config *delay_conf;
+ int override;
+ int enable_pxe;
+ int doing_ra, doing_dhcp6;
+ struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names;
+ struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
+ struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
+ int dhcp_max, tftp_max, tftp_mtu;
+ 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 */
+ unsigned int duid_enterprise, duid_config_len;
+ unsigned char *duid_config;
+ char *dbus_name;
+ char *ubus_name;
+ char *dump_file;
+ int dump_mask;
+ unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
+ u32 metrics[__METRIC_MAX];
+#ifdef HAVE_DNSSEC
+ struct ds_config *ds;
+ char *timestamp_file;
+#endif
+
+ /* globally used stuff for DNS */
+ char *packet; /* packet buffer */
+ int packet_buff_sz; /* size of above */
+ char *namebuff; /* MAXDNAME size buffer */
+#ifdef HAVE_DNSSEC
+ char *keyname; /* MAXDNAME size buffer */
+ char *workspacename; /* ditto */
+ unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
+ int rr_status_sz;
+ int dnssec_no_time_check;
+ int back_to_the_future;
+#endif
+ struct frec *frec_list;
+ struct frec_src *free_frec_src;
+ int frec_src_count;
+ struct serverfd *sfds;
+ struct irec *interfaces;
+ struct listener *listeners;
+ struct server *srv_save; /* Used for resend on DoD */
+ size_t packet_len; /* " " */
+ int fd_save; /* " " */
+ pid_t tcp_pids[MAX_PROCS];
+ int tcp_pipes[MAX_PROCS];
+ int pipe_to_parent;
+ int numrrand;
+ struct randfd *randomsocks;
+ struct randfd_list *rfl_spare, *rfl_poll;
+ int v6pktinfo;
+ struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
+ int log_id, log_display_id; /* ids of transactions for logging */
+ union mysockaddr *log_source_addr;
+
+ /* DHCP state */
+ int dhcpfd, helperfd, pxefd;
+#ifdef HAVE_INOTIFY
+ int inotifyfd;
+#endif
+#if defined(HAVE_LINUX_NETWORK)
+ int netlinkfd, kernel_version;
+#elif defined(HAVE_BSD_NETWORK)
+ int dhcp_raw_fd, dhcp_icmp_fd, routefd;
+#endif
+ struct iovec dhcp_packet;
+ char *dhcp_buff, *dhcp_buff2, *dhcp_buff3;
+ struct ping_result *ping_results;
+ FILE *lease_stream;
+ struct dhcp_bridge *bridges;
+ struct shared_network *shared_networks;
+#ifdef HAVE_DHCP6
+ int duid_len;
+ unsigned char *duid;
+ struct iovec outpacket;
+ int dhcp6fd, icmp6fd;
+#endif
+ /* DBus stuff */
+ /* void * here to avoid depending on dbus headers outside dbus.c */
+ void *dbus;
+#ifdef HAVE_DBUS
+ struct watch *watches;
+#endif
+ /* UBus stuff */
+#ifdef HAVE_UBUS
+ /* void * here to avoid depending on ubus headers outside ubus.c */
+ void *ubus;
+#endif
+
+ /* TFTP stuff */
+ struct tftp_transfer *tftp_trans, *tftp_done_trans;
+
+ /* utility string buffer, hold max sized IP address as string */
+ char *addrbuff;
+ char *addrbuff2; /* only allocated when OPT_EXTRALOG */
+
+#ifdef HAVE_DUMPFILE
+ /* file for packet dumps. */
+ int dumpfd;
+#endif
+} *daemon;
+
+/* cache.c */
+void cache_init(void);
+void next_uid(struct crec *crecp);
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg);
+char *record_source(unsigned int index);
+char *querystr(char *desc, unsigned short type);
+int cache_find_non_terminal(char *name, time_t now);
+struct crec *cache_find_by_addr(struct crec *crecp,
+ union all_addr *addr, time_t now,
+ unsigned int prot);
+struct crec *cache_find_by_name(struct crec *crecp,
+ char *name, time_t now, unsigned int prot);
+void cache_end_insert(void);
+void cache_start_insert(void);
+int cache_recv_insert(time_t now, int fd);
+struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags);
+void cache_reload(void);
+void cache_add_dhcp_entry(char *host_name, int prot, union all_addr *host_address, time_t ttd);
+struct in_addr a_record_from_hosts(char *name, time_t now);
+void cache_unhash_dhcp(void);
+void dump_cache(time_t now);
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t);
+#endif
+char *cache_get_name(struct crec *crecp);
+char *cache_get_cname_target(struct crec *crecp);
+struct crec *cache_enumerate(int init);
+int read_hostsfile(char *filename, unsigned int index, int cache_size,
+ struct crec **rhash, int hashsz);
+
+/* blockdata.c */
+void blockdata_init(void);
+void blockdata_report(void);
+struct blockdata *blockdata_alloc(char *data, size_t len);
+void *blockdata_retrieve(struct blockdata *block, size_t len, void *data);
+struct blockdata *blockdata_read(int fd, size_t len);
+void blockdata_write(struct blockdata *block, size_t len, int fd);
+void blockdata_free(struct blockdata *blocks);
+
+/* domain.c */
+char *get_domain(struct in_addr addr);
+char *get_domain6(struct in6_addr *addr);
+int is_name_synthetic(int flags, char *name, union all_addr *addr);
+int is_rev_synth(int flag, union all_addr *addr, char *name);
+
+/* rfc1035.c */
+int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
+ char *name, int isExtract, int extrabytes);
+unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
+unsigned char *skip_questions(struct dns_header *header, size_t plen);
+unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
+unsigned int extract_request(struct dns_header *header, size_t qlen,
+ char *name, unsigned short *typep);
+void setup_reply(struct dns_header *header, unsigned int flags, int ede);
+int extract_addresses(struct dns_header *header, size_t qlen, char *name,
+ time_t now, char **ipsets, int is_sign, int check_rebind,
+ int no_cache_dnssec, int secure, int *doctored);
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+void report_addresses(struct dns_header *header, size_t len, u32 mark);
+#endif
+size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+ struct in_addr local_addr, struct in_addr local_netmask,
+ time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
+int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
+ time_t now);
+int check_for_ignored_address(struct dns_header *header, size_t qlen);
+int check_for_local_domain(char *name, time_t now);
+size_t resize_packet(struct dns_header *header, size_t plen,
+ unsigned char *pheader, size_t hlen);
+int add_resource_record(struct dns_header *header, char *limit, int *truncp,
+ int nameoffset, unsigned char **pp, unsigned long ttl,
+ int *offset, unsigned short type, unsigned short class, char *format, ...);
+int in_arpa_name_2_addr(char *namein, union all_addr *addrp);
+int private_net(struct in_addr addr, int ban_localhost);
+
+/* auth.c */
+#ifdef HAVE_AUTH
+size_t answer_auth(struct dns_header *header, char *limit, size_t qlen,
+ time_t now, union mysockaddr *peer_addr, int local_query,
+ int do_bit, int have_pseudoheader);
+int in_zone(struct auth_zone *zone, char *name, char **cut);
+#endif
+
+/* dnssec.c */
+#ifdef HAVE_DNSSEC
+size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
+ int check_unsigned, int *neganswer, int *nons, int *nsec_ttl);
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
+size_t filter_rrsigs(struct dns_header *header, size_t plen);
+int setup_timestamp(void);
+int errflags_to_ede(int status);
+#endif
+
+/* hash_questions.c */
+void hash_questions_init(void);
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name);
+
+/* crypto.c */
+const struct nettle_hash *hash_find(char *name);
+int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp);
+int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+ unsigned char *digest, size_t digest_len, int algo);
+char *ds_digest_name(int digest);
+char *algo_digest_name(int algo);
+char *nsec3_digest_name(int digest);
+
+/* util.c */
+void rand_init(void);
+unsigned short rand16(void);
+u32 rand32(void);
+u64 rand64(void);
+int legal_hostname(char *name);
+char *canonicalise(char *in, int *nomem);
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
+void *safe_malloc(size_t size);
+void safe_strncpy(char *dest, const char *src, 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(const union mysockaddr *s1, const union mysockaddr *s2);
+int hostname_isequal(const char *a, const char *b);
+int hostname_issubdomain(char *a, char *b);
+time_t dnsmasq_time(void);
+int netmask_length(struct in_addr mask);
+int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask);
+int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix);
+int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen);
+u64 addr6part(struct in6_addr *addr);
+void setaddr6part(struct in6_addr *addr, u64 host);
+int retry_send(ssize_t rc);
+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);
+int read_write(int fd, unsigned char *packet, int size, int rw);
+void close_fds(long max_fd, int spare1, int spare2, int spare3);
+int wildcard_match(const char* wildcard, const char* match);
+int wildcard_matchn(const char* wildcard, const char* match, int num);
+#ifdef HAVE_LINUX_NETWORK
+int kernel_version(void);
+#endif
+
+/* log.c */
+void die(char *message, char *arg1, int exit_code) ATTRIBUTE_NORETURN;
+int log_start(struct passwd *ent_pw, int errfd);
+int log_reopen(char *log_file);
+
+void my_syslog(int priority, const char *format, ...);
+
+void set_log_writer(void);
+void check_log_writer(int force);
+void flush_log(void);
+
+/* option.c */
+void read_opts (int argc, char **argv, char *compile_opts);
+char *option_string(int prot, unsigned int opt, unsigned char *val,
+ int opt_len, char *buf, int buf_len);
+void reread_dhcp(void);
+void read_servers_file(void);
+void set_option_bool(unsigned int opt);
+void reset_option_bool(unsigned int opt);
+struct hostsfile *expand_filelist(struct hostsfile *list);
+char *parse_server(char *arg, union mysockaddr *addr,
+ union mysockaddr *source_addr, char *interface, u16 *flags);
+int option_read_dynfile(char *file, int flags);
+
+/* forward.c */
+void reply_query(int fd, time_t now);
+void receive_query(struct listener *listen, time_t now);
+unsigned char *tcp_request(int confd, time_t now,
+ union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
+void server_gone(struct server *server);
+int send_from(int fd, int nowild, char *packet, size_t len,
+ union mysockaddr *to, union all_addr *source,
+ unsigned int iface);
+void resend_query(void);
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv);
+void free_rfds(struct randfd_list **fdlp);
+
+/* network.c */
+int indextoname(int fd, int index, char *name);
+int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
+void pre_allocate_sfds(void);
+int reload_servers(char *fname);
+void check_servers(int no_loop_call);
+int enumerate_interfaces(int reset);
+void create_wildcard_listeners(void);
+void create_bound_listeners(int dienow);
+void warn_bound_listeners(void);
+void warn_wild_labels(void);
+void warn_int_names(void);
+int is_dad_listeners(void);
+int iface_check(int family, union all_addr *addr, char *name, int *auth);
+int loopback_exception(int fd, int family, union all_addr *addr, char *name);
+int label_exception(int index, int family, union all_addr *addr);
+int fix_fd(int fd);
+int tcp_interface(int fd, int af);
+int set_ipv6pktinfo(int fd);
+#ifdef HAVE_DHCP6
+void join_multicast(int dienow);
+#endif
+#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_BSD_NETWORK)
+void newaddress(time_t now);
+#endif
+
+
+/* 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 taddr,
+ struct dhcp_netid *netids);
+struct dhcp_context *narrow_context(struct dhcp_context *context,
+ struct in_addr taddr,
+ struct dhcp_netid *netids);
+struct ping_result *do_icmp_ping(time_t now, struct in_addr addr,
+ unsigned int hash, int loopback);
+int address_allocate(struct dhcp_context *context,
+ struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
+ struct dhcp_netid *netids, time_t now, int loopback);
+void dhcp_read_ethers(void);
+struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr);
+char *host_from_dns(struct in_addr addr);
+#endif
+
+/* lease.c */
+#ifdef HAVE_DHCP
+void lease_update_file(time_t now);
+void lease_update_dns(int force);
+void lease_init(time_t now);
+struct dhcp_lease *lease4_allocate(struct in_addr addr);
+#ifdef HAVE_DHCP6
+struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type);
+struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len,
+ int lease_type, unsigned int iaid, struct in6_addr *addr);
+void lease6_reset(void);
+struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
+ unsigned char *clid, int clid_len, unsigned int iaid);
+struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr);
+u64 lease_find_max_addr6(struct dhcp_context *context);
+void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface);
+void lease_update_slaac(time_t now);
+void lease_set_iaid(struct dhcp_lease *lease, unsigned int iaid);
+void lease_make_duid(time_t now);
+#endif
+void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
+ const unsigned char *clid, int hw_len, int hw_type,
+ int clid_len, time_t now, int force);
+void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain);
+void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now);
+void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now);
+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);
+struct in_addr lease_find_max_addr(struct dhcp_context *context);
+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);
+void lease_find_interfaces(time_t now);
+#ifdef HAVE_SCRIPT
+void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data,
+ unsigned int len, int delim);
+#endif
+#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 loopback,
+ int *is_inform, int pxe, struct in_addr fallback, time_t recvtime);
+unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
+ int clid_len, unsigned char *clid, int *len_out);
+#endif
+
+/* dnsmasq.c */
+#ifdef HAVE_DHCP
+int make_icmp_sock(void);
+int icmp_ping(struct in_addr addr);
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id);
+#endif
+void queue_event(int event);
+void send_alarm(time_t event, time_t now);
+void send_event(int fd, int event, int data, char *msg);
+void clear_cache_and_reload(time_t now);
+
+/* netlink.c */
+#ifdef HAVE_LINUX_NETWORK
+char *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);
+void route_init(void);
+void route_sock(void);
+#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(void);
+void set_dbus_listeners(void);
+# ifdef HAVE_DHCP
+void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname);
+# endif
+#endif
+
+/* ubus.c */
+#ifdef HAVE_UBUS
+char *ubus_init(void);
+void set_ubus_listeners(void);
+void check_ubus_listeners(void);
+void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
+# ifdef HAVE_CONNTRACK
+void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name);
+void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl);
+# endif
+#endif
+
+/* ipset.c */
+#ifdef HAVE_IPSET
+void ipset_init(void);
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
+#endif
+
+/* pattern.c */
+#ifdef HAVE_CONNTRACK
+int is_valid_dns_name(const char *value);
+int is_valid_dns_name_pattern(const char *value);
+int is_dns_name_matching_pattern(const char *name, const char *pattern);
+#endif
+
+/* helper.c */
+#if defined(HAVE_SCRIPT)
+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);
+#ifdef HAVE_TFTP
+void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer);
+#endif
+void queue_arp(int action, unsigned char *mac, int maclen,
+ int family, union all_addr *addr);
+int helper_buf_empty(void);
+#endif
+
+/* tftp.c */
+#ifdef HAVE_TFTP
+void tftp_request(struct listener *listen, time_t now);
+void check_tftp_listeners(time_t now);
+int do_tftp_script_run(void);
+#endif
+
+/* conntrack.c */
+#ifdef HAVE_CONNTRACK
+int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr,
+ int istcp, unsigned int *markp);
+#endif
+
+/* dhcp6.c */
+#ifdef HAVE_DHCP6
+void dhcp6_init(void);
+void dhcp6_packet(time_t now);
+struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr,
+ unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans);
+struct dhcp_context *address6_available(struct dhcp_context *context,
+ struct in6_addr *taddr,
+ struct dhcp_netid *netids,
+ int plain_range);
+struct dhcp_context *address6_valid(struct dhcp_context *context,
+ struct in6_addr *taddr,
+ struct dhcp_netid *netids,
+ int plain_range);
+struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net,
+ int prefix, struct in6_addr *addr);
+void make_duid(time_t now);
+void dhcp_construct_contexts(time_t now);
+void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
+ unsigned int *maclenp, unsigned int *mactypep, time_t now);
+#endif
+
+/* rfc3315.c */
+#ifdef HAVE_DHCP6
+unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
+ struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+ size_t sz, struct in6_addr *client_addr, time_t now);
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address,
+ u32 scope_id, time_t now);
+
+unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
+#endif
+
+/* dhcp-common.c */
+#ifdef HAVE_DHCP
+void dhcp_common_init(void);
+ssize_t recv_dhcp_packet(int fd, struct msghdr *msg);
+struct dhcp_netid *run_tag_if(struct dhcp_netid *tags);
+struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags,
+ struct dhcp_opt *opts);
+int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded);
+char *strip_hostname(char *hostname);
+void log_tags(struct dhcp_netid *netid, u32 xid);
+int match_bytes(struct dhcp_opt *o, unsigned char *p, int len);
+void dhcp_update_configs(struct dhcp_config *configs);
+void display_opts(void);
+int lookup_dhcp_opt(int prot, char *name);
+int lookup_dhcp_len(int prot, int val);
+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,
+ struct dhcp_netid *filter);
+int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type);
+#ifdef HAVE_LINUX_NETWORK
+char *whichdevice(void);
+void bindtodevice(char *device, int fd);
+#endif
+# ifdef HAVE_DHCP6
+void display_opts6(void);
+# endif
+void log_context(int family, struct dhcp_context *context);
+void log_relay(int family, struct dhcp_relay *relay);
+#endif
+
+/* outpacket.c */
+#ifdef HAVE_DHCP6
+void end_opt6(int container);
+void reset_counter(void);
+int save_counter(int newval);
+void *expand(size_t headroom);
+int new_opt6(int opt);
+void *put_opt6(void *data, size_t len);
+void put_opt6_long(unsigned int val);
+void put_opt6_short(unsigned int val);
+void put_opt6_char(unsigned int val);
+void put_opt6_string(char *s);
+#endif
+
+/* radv.c */
+#ifdef HAVE_DHCP6
+void ra_init(time_t now);
+void icmp6_packet(time_t now);
+time_t periodic_ra(time_t now);
+void ra_start_unsolicited(time_t now, struct dhcp_context *context);
+#endif
+
+/* slaac.c */
+#ifdef HAVE_DHCP6
+void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force);
+time_t periodic_slaac(time_t now, struct dhcp_lease *leases);
+void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases);
+#endif
+
+/* loop.c */
+#ifdef HAVE_LOOP
+void loop_send_probes(void);
+int detect_loop(char *query, int type);
+#endif
+
+/* inotify.c */
+#ifdef HAVE_INOTIFY
+void inotify_dnsmasq_init(void);
+int inotify_check(time_t now);
+void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz);
+#endif
+
+/* poll.c */
+void poll_reset(void);
+int poll_check(int fd, short event);
+void poll_listen(int fd, short event);
+int do_poll(int timeout);
+
+/* rrfilter.c */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode);
+u16 *rrfilter_desc(int type);
+int expand_workspace(unsigned char ***wkspc, int *szp, int new);
+
+/* edns0.c */
+unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
+ size_t *len, unsigned char **p, int *is_sign, int *is_last);
+size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
+ unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
+size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
+size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable);
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+
+/* arp.c */
+int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now);
+int do_arp_script_run(void);
+
+/* dump.c */
+#ifdef HAVE_DUMPFILE
+void dump_init(void);
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst);
+#endif
+
+/* domain-match.c */
+void build_server_array(void);
+int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
+int filter_servers(int seed, int flags, int *lowout, int *highout);
+int is_local_answer(time_t now, int first, char *name);
+size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
+ char *name, char *limit, int first, int last, int ede);
+int server_samegroup(struct server *a, struct server *b);
+#ifdef HAVE_DNSSEC
+int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
+#endif
+void mark_servers(int flag);
+void cleanup_servers(void);
+int add_update_server(int flags,
+ union mysockaddr *addr,
+ union mysockaddr *source_addr,
+ const char *interface,
+ const char *domain,
+ union all_addr *local_addr);
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c
new file mode 100755
index 0000000..153cac4
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c
@@ -0,0 +1,2199 @@
+/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+ and Copyright (c) 2012-2020 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
+#define SERIAL_UNDEF -100
+#define SERIAL_EQ 0
+#define SERIAL_LT -1
+#define SERIAL_GT 1
+
+/* Convert from presentation format to wire format, in place.
+ Also map UC -> LC.
+ Note that using extract_name to get presentation format
+ then calling to_wire() removes compression and maps case,
+ thus generating names in canonical form.
+ Calling to_wire followed by from_wire is almost an identity,
+ except that the UC remains mapped to LC.
+
+ Note that both /000 and '.' are allowed within labels. These get
+ represented in presentation format using NAME_ESCAPE as an escape
+ character. In theory, if all the characters in a name were /000 or
+ '.' or NAME_ESCAPE then all would have to be escaped, so the
+ presentation format would be twice as long as the spec (1024).
+ The buffers are all declared as 2049 (allowing for the trailing zero)
+ for this reason.
+*/
+static int to_wire(char *name)
+{
+ unsigned char *l, *p, *q, term;
+ int len;
+
+ for (l = (unsigned char*)name; *l != 0; l = p)
+ {
+ for (p = l; *p != '.' && *p != 0; p++)
+ if (*p >= 'A' && *p <= 'Z')
+ *p = *p - 'A' + 'a';
+ else if (*p == NAME_ESCAPE)
+ {
+ for (q = p; *q; q++)
+ *q = *(q+1);
+ (*p)--;
+ }
+ term = *p;
+
+ if ((len = p - l) != 0)
+ memmove(l+1, l, len);
+ *l = len;
+
+ p++;
+
+ if (term == 0)
+ *p = 0;
+ }
+
+ return l + 1 - (unsigned char *)name;
+}
+
+/* Note: no compression allowed in input. */
+static void from_wire(char *name)
+{
+ unsigned char *l, *p, *last;
+ int len;
+
+ for (last = (unsigned char *)name; *last != 0; last += *last+1);
+
+ for (l = (unsigned char *)name; *l != 0; l += len+1)
+ {
+ len = *l;
+ memmove(l, l+1, len);
+ for (p = l; p < l + len; p++)
+ if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
+ {
+ memmove(p+1, p, 1 + last - p);
+ len++;
+ *p++ = NAME_ESCAPE;
+ (*p)++;
+ }
+
+ l[len] = '.';
+ }
+
+ if ((char *)l != name)
+ *(l-1) = 0;
+}
+
+/* Input in presentation format */
+static int count_labels(char *name)
+{
+ int i;
+ char *p;
+
+ if (*name == 0)
+ return 0;
+
+ for (p = name, i = 0; *p; p++)
+ if (*p == '.')
+ i++;
+
+ /* Don't count empty first label. */
+ return *name == '.' ? i : i+1;
+}
+
+/* Implement RFC1982 wrapped compare for 32-bit numbers */
+static int serial_compare_32(u32 s1, u32 s2)
+{
+ if (s1 == s2)
+ return SERIAL_EQ;
+
+ if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) > (1UL<<31)))
+ return SERIAL_LT;
+ if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) < (1UL<<31)))
+ return SERIAL_GT;
+ return SERIAL_UNDEF;
+}
+
+/* Called at startup. If the timestamp file is configured and exists, put its mtime on
+ timestamp_time. If it doesn't exist, create it, and set the mtime to 1-1-2015.
+ return -1 -> Cannot create file.
+ 0 -> not using timestamp, or timestamp exists and is in past.
+ 1 -> timestamp exists and is in future.
+*/
+
+static time_t timestamp_time;
+
+int setup_timestamp(void)
+{
+ struct stat statbuf;
+
+ daemon->back_to_the_future = 0;
+
+ if (!daemon->timestamp_file)
+ return 0;
+
+ if (stat(daemon->timestamp_file, &statbuf) != -1)
+ {
+ timestamp_time = statbuf.st_mtime;
+ check_and_exit:
+ if (difftime(timestamp_time, time(0)) <= 0)
+ {
+ /* time already OK, update timestamp, and do key checking from the start. */
+ if (utimes(daemon->timestamp_file, NULL) == -1)
+ my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+ daemon->back_to_the_future = 1;
+ return 0;
+ }
+ return 1;
+ }
+
+ if (errno == ENOENT)
+ {
+ /* NB. for explanation of O_EXCL flag, see comment on pidfile in dnsmasq.c */
+ int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666);
+ if (fd != -1)
+ {
+ struct timeval tv[2];
+
+ close(fd);
+
+ timestamp_time = 1420070400; /* 1-1-2015 */
+ tv[0].tv_sec = tv[1].tv_sec = timestamp_time;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+ if (utimes(daemon->timestamp_file, tv) == 0)
+ goto check_and_exit;
+ }
+ }
+
+ return -1;
+}
+
+/* Check whether today/now is between date_start and date_end */
+static int is_check_date(unsigned long curtime)
+{
+ /* Checking timestamps may be temporarily disabled */
+
+ /* If the current time if _before_ the timestamp
+ on our persistent timestamp file, then assume the
+ time if not yet correct, and don't check the
+ key timestamps. As soon as the current time is
+ later then the timestamp, update the timestamp
+ and start checking keys */
+ if (daemon->timestamp_file)
+ {
+ if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)
+ {
+ if (utimes(daemon->timestamp_file, NULL) != 0)
+ my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+
+ my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps."));
+ daemon->back_to_the_future = 1;
+ daemon->dnssec_no_time_check = 0;
+ queue_event(EVENT_RELOAD); /* purge cache */
+ }
+
+ return daemon->back_to_the_future;
+ }
+ else
+ return !daemon->dnssec_no_time_check;
+}
+
+/* Return bytes of canonicalised rrdata one by one.
+ Init state->ip with the RR, and state->end with the end of same.
+ Init state->op to NULL.
+ Init state->desc to RR descriptor.
+ Init state->buff with a MAXDNAME * 2 buffer.
+
+ After each call which returns 1, state->op points to the next byte of data.
+ On returning 0, the end has been reached.
+*/
+struct rdata_state {
+ u16 *desc;
+ size_t c;
+ unsigned char *end, *ip, *op;
+ char *buff;
+};
+
+static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
+{
+ int d;
+
+ if (state->op && state->c != 1)
+ {
+ state->op++;
+ state->c--;
+ return 1;
+ }
+
+ while (1)
+ {
+ d = *(state->desc);
+
+ if (d == (u16)-1)
+ {
+ /* all the bytes to the end. */
+ if ((state->c = state->end - state->ip) != 0)
+ {
+ state->op = state->ip;
+ state->ip = state->end;;
+ }
+ else
+ return 0;
+ }
+ else
+ {
+ state->desc++;
+
+ if (d == (u16)0)
+ {
+ /* domain-name, canonicalise */
+ int len;
+
+ if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
+ (len = to_wire(state->buff)) == 0)
+ continue;
+
+ state->c = len;
+ state->op = (unsigned char *)state->buff;
+ }
+ else
+ {
+ /* plain data preceding a domain-name, don't run off the end of the data */
+ if ((state->end - state->ip) < d)
+ d = state->end - state->ip;
+
+ if (d == 0)
+ continue;
+
+ state->op = state->ip;
+ state->c = d;
+ state->ip += d;
+ }
+ }
+
+ return 1;
+ }
+}
+
+/* Bubble sort the RRset into the canonical order. */
+
+static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
+ unsigned char **rrset, char *buff1, char *buff2)
+{
+ int swap, i, j;
+
+ do
+ {
+ for (swap = 0, i = 0; i < rrsetidx-1; i++)
+ {
+ int rdlen1, rdlen2;
+ struct rdata_state state1, state2;
+
+ /* Note that these have been determined to be OK previously,
+ so we don't need to check for NULL return here. */
+ state1.ip = skip_name(rrset[i], header, plen, 10);
+ state2.ip = skip_name(rrset[i+1], header, plen, 10);
+ state1.op = state2.op = NULL;
+ state1.buff = buff1;
+ state2.buff = buff2;
+ state1.desc = state2.desc = rr_desc;
+
+ state1.ip += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen1, state1.ip);
+ if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
+ return rrsetidx; /* short packet */
+ state1.end = state1.ip + rdlen1;
+
+ state2.ip += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen2, state2.ip);
+ if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
+ return rrsetidx; /* short packet */
+ state2.end = state2.ip + rdlen2;
+
+ /* If the RR has no names in it then canonicalisation
+ is the identity function and we can compare
+ the RRs directly. If not we compare the
+ canonicalised RRs one byte at a time. */
+ if (*rr_desc == (u16)-1)
+ {
+ int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
+ int cmp = memcmp(state1.ip, state2.ip, rdmin);
+
+ if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin))
+ {
+ unsigned char *tmp = rrset[i+1];
+ rrset[i+1] = rrset[i];
+ rrset[i] = tmp;
+ swap = 1;
+ }
+ else if (cmp == 0 && (rdlen1 == rdlen2))
+ {
+ /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+ for (j = i+1; j < rrsetidx-1; j++)
+ rrset[j] = rrset[j+1];
+ rrsetidx--;
+ i--;
+ }
+ }
+ else
+ /* Comparing canonicalised RRs, byte-at-a-time. */
+ while (1)
+ {
+ int ok1, ok2;
+
+ ok1 = get_rdata(header, plen, &state1);
+ ok2 = get_rdata(header, plen, &state2);
+
+ if (!ok1 && !ok2)
+ {
+ /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+ for (j = i+1; j < rrsetidx-1; j++)
+ rrset[j] = rrset[j+1];
+ rrsetidx--;
+ i--;
+ break;
+ }
+ else if (ok1 && (!ok2 || *state1.op > *state2.op))
+ {
+ unsigned char *tmp = rrset[i+1];
+ rrset[i+1] = rrset[i];
+ rrset[i] = tmp;
+ swap = 1;
+ break;
+ }
+ else if (ok2 && (!ok1 || *state2.op > *state1.op))
+ break;
+
+ /* arrive here when bytes are equal, go round the loop again
+ and compare the next ones. */
+ }
+ }
+ } while (swap);
+
+ return rrsetidx;
+}
+
+static unsigned char **rrset = NULL, **sigs = NULL;
+
+/* Get pointers to RRset members and signature(s) for same.
+ Check signatures, and return keyname associated in keyname. */
+static int explore_rrset(struct dns_header *header, size_t plen, int class, int type,
+ char *name, char *keyname, int *sigcnt, int *rrcnt)
+{
+ static int rrset_sz = 0, sig_sz = 0;
+ unsigned char *p;
+ int rrsetidx, sigidx, j, rdlen, res;
+ int gotkey = 0;
+
+ if (!(p = skip_questions(header, plen)))
+ return 0;
+
+ /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
+ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
+ j != 0; j--)
+ {
+ unsigned char *pstart, *pdata;
+ int stype, sclass, type_covered;
+
+ pstart = p;
+
+ if (!(res = extract_name(header, plen, &p, name, 0, 10)))
+ return 0; /* bad packet */
+
+ GETSHORT(stype, p);
+ GETSHORT(sclass, p);
+
+ pdata = p;
+
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 0;
+
+ if (res == 1 && sclass == class)
+ {
+ if (stype == type)
+ {
+ if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
+ return 0;
+
+ rrset[rrsetidx++] = pstart;
+ }
+
+ if (stype == T_RRSIG)
+ {
+ if (rdlen < 18)
+ return 0; /* bad packet */
+
+ GETSHORT(type_covered, p);
+ p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */
+
+ if (gotkey)
+ {
+ /* If there's more than one SIG, ensure they all have same keyname */
+ if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
+ return 0;
+ }
+ else
+ {
+ gotkey = 1;
+
+ if (!extract_name(header, plen, &p, keyname, 1, 0))
+ return 0;
+
+ /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
+ the name of the zone containing the RRset. We can't tell that
+ for certain, but we can check that the RRset name is equal to
+ or encloses the signers name, which should be enough to stop
+ an attacker using signatures made with the key of an unrelated
+ zone he controls. Note that the root key is always allowed. */
+ if (*keyname != 0)
+ {
+ char *name_start;
+ for (name_start = name; !hostname_isequal(name_start, keyname); )
+ if ((name_start = strchr(name_start, '.')))
+ name_start++; /* chop a label off and try again */
+ else
+ return 0;
+ }
+ }
+
+
+ if (type_covered == type)
+ {
+ if (!expand_workspace(&sigs, &sig_sz, sigidx))
+ return 0;
+
+ sigs[sigidx++] = pdata;
+ }
+
+ p = pdata + 6; /* restore for ADD_RDLEN */
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return 0;
+ }
+
+ *sigcnt = sigidx;
+ *rrcnt = rrsetidx;
+
+ return 1;
+}
+
+/* Validate a single RRset (class, type, name) in the supplied DNS reply
+ Return code:
+ STAT_SECURE if it validates.
+ STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+ (In this case *wildcard_out points to the "body" of the wildcard within name.)
+ STAT_BOGUS signature is wrong, bad packet.
+ STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
+ STAT_NEED_DS need DS to complete validation (name is returned in keyname)
+
+ If key is non-NULL, use that key, which has the algo and tag given in the params of those names,
+ otherwise find the key in the cache.
+
+ Name is unchanged on exit. keyname is used as workspace and trashed.
+
+ Call explore_rrset first to find and count RRs and sigs.
+
+ ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
+*/
+static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
+ char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
+ int algo_in, int keytag_in, unsigned long *ttl_out)
+{
+ unsigned char *p;
+ int rdlen, j, name_labels, algo, labels, key_tag;
+ struct crec *crecp = NULL;
+ u16 *rr_desc = rrfilter_desc(type);
+ u32 sig_expiration, sig_inception;
+ int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
+
+ unsigned long curtime = time(0);
+ int time_check = is_check_date(curtime);
+
+ if (wildcard_out)
+ *wildcard_out = NULL;
+
+ name_labels = count_labels(name); /* For 4035 5.3.2 check */
+
+ /* Sort RRset records into canonical order.
+ Note that at this point keyname and daemon->workspacename buffs are
+ unused, and used as workspace by the sort. */
+ rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
+
+ /* Now try all the sigs to try and find one which validates */
+ for (j = 0; j <sigidx; j++)
+ {
+ unsigned char *psav, *sig, *digest;
+ int i, wire_len, sig_len;
+ const struct nettle_hash *hash;
+ void *ctx;
+ char *name_start;
+ u32 nsigttl, ttl, orig_ttl;
+
+ failflags &= ~DNSSEC_FAIL_NOSIG;
+
+ p = sigs[j];
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
+ psav = p;
+
+ p += 2; /* type_covered - already checked */
+ algo = *p++;
+ labels = *p++;
+ GETLONG(orig_ttl, p);
+ GETLONG(sig_expiration, p);
+ GETLONG(sig_inception, p);
+ GETSHORT(key_tag, p);
+
+ if (!extract_name(header, plen, &p, keyname, 1, 0))
+ return STAT_BOGUS;
+
+ if (!time_check)
+ failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
+ else
+ {
+ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+ if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NYV;
+
+ if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_EXP;
+ }
+
+ if (!(hash = hash_find(algo_digest_name(algo))))
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NOKEYSUP;
+
+ if (labels > name_labels ||
+ !hash_init(hash, &ctx, &digest))
+ continue;
+
+ /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
+ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
+ return STAT_NEED_KEY;
+
+ if (ttl_out)
+ {
+ /* 4035 5.3.3 rules on TTLs */
+ if (orig_ttl < ttl)
+ ttl = orig_ttl;
+
+ if (time_check && difftime(sig_expiration, curtime) < ttl)
+ ttl = difftime(sig_expiration, curtime);
+
+ *ttl_out = ttl;
+ }
+
+ sig = p;
+ sig_len = rdlen - (p - psav);
+
+ nsigttl = htonl(orig_ttl);
+
+ hash->update(ctx, 18, psav);
+ wire_len = to_wire(keyname);
+ hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
+ from_wire(keyname);
+
+#define RRBUFLEN 128 /* Most RRs are smaller than this. */
+
+ for (i = 0; i < rrsetidx; ++i)
+ {
+ int j;
+ struct rdata_state state;
+ u16 len;
+ unsigned char rrbuf[RRBUFLEN];
+
+ p = rrset[i];
+
+ if (!extract_name(header, plen, &p, name, 1, 10))
+ return STAT_BOGUS;
+
+ name_start = name;
+
+ /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
+ if (labels < name_labels)
+ {
+ for (j = name_labels - labels; j != 0; j--)
+ {
+ while (*name_start != '.' && *name_start != 0)
+ name_start++;
+ if (j != 1 && *name_start == '.')
+ name_start++;
+ }
+
+ if (wildcard_out)
+ *wildcard_out = name_start+1;
+
+ name_start--;
+ *name_start = '*';
+ }
+
+ wire_len = to_wire(name_start);
+ hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
+ hash->update(ctx, 4, p); /* class and type */
+ hash->update(ctx, 4, (unsigned char *)&nsigttl);
+
+ p += 8; /* skip type, class, ttl */
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return STAT_BOGUS;
+
+ /* Optimisation for RR types which need no cannonicalisation.
+ This includes DNSKEY DS NSEC and NSEC3, which are also long, so
+ it saves lots of calls to get_rdata, and avoids the pessimal
+ segmented insertion, even with a small rrbuf[].
+
+ If canonicalisation is not needed, a simple insertion into the hash works.
+ */
+ if (*rr_desc == (u16)-1)
+ {
+ len = htons(rdlen);
+ hash->update(ctx, 2, (unsigned char *)&len);
+ hash->update(ctx, rdlen, p);
+ }
+ else
+ {
+ /* canonicalise rdata and calculate length of same, use
+ name buffer as workspace for get_rdata. */
+ state.ip = p;
+ state.op = NULL;
+ state.desc = rr_desc;
+ state.buff = name;
+ state.end = p + rdlen;
+
+ for (j = 0; get_rdata(header, plen, &state); j++)
+ if (j < RRBUFLEN)
+ rrbuf[j] = *state.op;
+
+ len = htons((u16)j);
+ hash->update(ctx, 2, (unsigned char *)&len);
+
+ /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+ then we can just digest it now. If it exceeds RRBUFLEN we have to
+ go back to the start and do it in chunks. */
+ if (j >= RRBUFLEN)
+ {
+ state.ip = p;
+ state.op = NULL;
+ state.desc = rr_desc;
+
+ for (j = 0; get_rdata(header, plen, &state); j++)
+ {
+ rrbuf[j] = *state.op;
+
+ if (j == RRBUFLEN - 1)
+ {
+ hash->update(ctx, RRBUFLEN, rrbuf);
+ j = -1;
+ }
+ }
+ }
+
+ if (j != 0)
+ hash->update(ctx, j, rrbuf);
+ }
+ }
+
+ hash->digest(ctx, hash->digest_size, digest);
+
+ /* namebuff used for workspace above, restore to leave unchanged on exit */
+ p = (unsigned char*)(rrset[0]);
+ extract_name(header, plen, &p, name, 1, 0);
+
+ if (key)
+ {
+ if (algo_in == algo && keytag_in == key_tag &&
+ verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
+ return STAT_SECURE;
+ }
+ else
+ {
+ /* iterate through all possible keys 4035 5.3.1 */
+ for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
+ if (crecp->addr.key.algo == algo &&
+ crecp->addr.key.keytag == key_tag &&
+ crecp->uid == (unsigned int)class &&
+ verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
+ return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+ }
+ }
+
+ /* If we reach this point, no verifying key was found */
+ return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY;
+}
+
+
+/* The DNS packet is expected to contain the answer to a DNSKEY query.
+ Put all DNSKEYs in the answer which are valid into the cache.
+ return codes:
+ STAT_OK Done, key(s) in cache.
+ STAT_BOGUS No DNSKEYs found, which can be validated with DS,
+ or self-sign for DNSKEY RRset is not valid, bad packet.
+ STAT_NEED_DS DS records to validate a key not found, name in keyname
+ STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname
+*/
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+ unsigned char *psave, *p = (unsigned char *)(header+1);
+ struct crec *crecp, *recp1;
+ int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
+ unsigned long ttl, sig_ttl;
+ struct blockdata *key;
+ union all_addr a;
+ int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
+
+ if (ntohs(header->qdcount) != 1 ||
+ RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
+ !extract_name(header, plen, &p, name, 1, 4))
+ return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
+ return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+
+ /* See if we have cached a DS record which validates this key */
+ if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
+ {
+ strcpy(keyname, name);
+ return STAT_NEED_DS;
+ }
+
+ /* NOTE, we need to find ONE DNSKEY which matches the DS */
+ for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
+ {
+ /* Ensure we have type, class TTL and length */
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ return STAT_BOGUS; /* bad packet */
+
+ if (qclass != class || qtype != T_DNSKEY || rc == 2)
+ {
+ p += rdlen;
+ continue;
+ }
+
+ psave = p;
+
+ GETSHORT(flags, p);
+ if (*p++ != 3)
+ return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+ algo = *p++;
+ keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+ key = NULL;
+
+ /* key must have zone key flag set */
+ if (flags & 0x100)
+ {
+ key = blockdata_alloc((char*)p, rdlen - 4);
+ failflags &= ~DNSSEC_FAIL_NOZONE;
+ }
+
+ p = psave;
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ {
+ if (key)
+ blockdata_free(key);
+ return STAT_BOGUS; /* bad packet */
+ }
+
+ /* No zone key flag or malloc failure */
+ if (!key)
+ continue;
+
+ for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
+ {
+ void *ctx;
+ unsigned char *digest, *ds_digest;
+ const struct nettle_hash *hash;
+ int sigcnt, rrcnt;
+ int wire_len;
+
+ if (recp1->addr.ds.algo == algo &&
+ recp1->addr.ds.keytag == keytag &&
+ recp1->uid == (unsigned int)class)
+ {
+ failflags &= ~DNSSEC_FAIL_NOKEY;
+
+ if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NODSSUP;
+
+ if (!hash_init(hash, &ctx, &digest))
+ continue;
+
+ wire_len = to_wire(name);
+
+ /* Note that digest may be different between DSs, so
+ we can't move this outside the loop. */
+ hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
+ hash->update(ctx, (unsigned int)rdlen, psave);
+ hash->digest(ctx, hash->digest_size, digest);
+
+ from_wire(name);
+
+ if (!(recp1->flags & F_NEG) &&
+ recp1->addr.ds.keylen == (int)hash->digest_size &&
+ (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
+ memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
+ explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
+ rrcnt != 0)
+ {
+ if (sigcnt == 0)
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NOSIG;
+
+ rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
+ NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
+
+ failflags &= rc;
+
+ if (STAT_ISEQUAL(rc, STAT_SECURE))
+ {
+ valid = 1;
+ break;
+ }
+ }
+ }
+ }
+ blockdata_free(key);
+ }
+
+ if (valid)
+ {
+ /* DNSKEY RRset determined to be OK, now cache it. */
+ cache_start_insert();
+
+ p = skip_questions(header, plen);
+
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ /* Ensure we have type, class TTL and length */
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ /* TTL may be limited by sig. */
+ if (sig_ttl < ttl)
+ ttl = sig_ttl;
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return STAT_BOGUS; /* bad packet */
+
+ if (qclass == class && rc == 1)
+ {
+ psave = p;
+
+ if (qtype == T_DNSKEY)
+ {
+ if (rdlen < 4)
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(flags, p);
+ if (*p++ != 3)
+ return STAT_BOGUS;
+ algo = *p++;
+ keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+
+ if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+ {
+ a.key.keylen = rdlen - 4;
+ a.key.keydata = key;
+ a.key.algo = algo;
+ a.key.keytag = keytag;
+ a.key.flags = flags;
+
+ if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
+ {
+ blockdata_free(key);
+ return STAT_BOGUS;
+ }
+ else
+ {
+ a.log.keytag = keytag;
+ a.log.algo = algo;
+ if (algo_digest_name(algo))
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
+ else
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
+ }
+ }
+ }
+
+ p = psave;
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_BOGUS; /* bad packet */
+ }
+
+ /* commit cache insert. */
+ cache_end_insert();
+ return STAT_OK;
+ }
+
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
+ return STAT_BOGUS | failflags;
+}
+
+/* The DNS packet is expected to contain the answer to a DS query
+ Put all DSs in the answer which are valid into the cache.
+ Also handles replies which prove that there's no DS at this location,
+ either because the zone is unsigned or this isn't a zone cut. These are
+ cached too.
+ return codes:
+ STAT_OK At least one valid DS found and in cache.
+ STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
+ STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
+ STAT_NEED_DS DS record needed.
+*/
+
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+ unsigned char *p = (unsigned char *)(header+1);
+ int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0;
+ int aclass, atype, rdlen;
+ unsigned long ttl;
+ union all_addr a;
+
+ if (ntohs(header->qdcount) != 1 ||
+ !(p = skip_name(p, header, plen, 4)))
+ return STAT_BOGUS;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ if (qtype != T_DS || qclass != class)
+ rc = STAT_BOGUS;
+ else
+ rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
+
+ if (STAT_ISEQUAL(rc, STAT_INSECURE))
+ {
+ my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure");
+ return STAT_BOGUS | DNSSEC_FAIL_INDET;
+ }
+
+ p = (unsigned char *)(header+1);
+ extract_name(header, plen, &p, name, 1, 4);
+ p += 4; /* qtype, qclass */
+
+ /* If the key needed to validate the DS is on the same domain as the DS, we'll
+ loop getting nowhere. Stop that now. This can happen of the DS answer comes
+ from the DS's zone, and not the parent zone. */
+ if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
+ {
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
+ return STAT_BOGUS;
+ }
+
+ if (!STAT_ISEQUAL(rc, STAT_SECURE))
+ return rc;
+
+ if (!neganswer)
+ {
+ cache_start_insert();
+
+ for (i = 0; i < ntohs(header->ancount); i++)
+ {
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(atype, p);
+ GETSHORT(aclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return STAT_BOGUS; /* bad packet */
+
+ if (aclass == class && atype == T_DS && rc == 1)
+ {
+ int algo, digest, keytag;
+ unsigned char *psave = p;
+ struct blockdata *key;
+
+ if (rdlen < 4)
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(keytag, p);
+ algo = *p++;
+ digest = *p++;
+
+ if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+ {
+ a.ds.digest = digest;
+ a.ds.keydata = key;
+ a.ds.algo = algo;
+ a.ds.keytag = keytag;
+ a.ds.keylen = rdlen - 4;
+
+ if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))
+ {
+ blockdata_free(key);
+ return STAT_BOGUS;
+ }
+ else
+ {
+ a.log.keytag = keytag;
+ a.log.algo = algo;
+ a.log.digest = digest;
+ if (ds_digest_name(digest) && algo_digest_name(algo))
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");
+ else
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)");
+ }
+ }
+
+ p = psave;
+ }
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_BOGUS; /* bad packet */
+ }
+
+ cache_end_insert();
+
+ }
+ else
+ {
+ int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
+
+ if (RCODE(header) == NXDOMAIN)
+ flags |= F_NXDOMAIN;
+
+ /* We only cache validated DS records, DNSSECOK flag hijacked
+ to store presence/absence of NS. */
+ if (nons)
+ flags &= ~F_DNSSECOK;
+
+ cache_start_insert();
+
+ /* Use TTL from NSEC for negative cache entries */
+ if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
+ return STAT_BOGUS;
+
+ cache_end_insert();
+
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS");
+ }
+
+ return STAT_OK;
+}
+
+
+/* 4034 6.1 */
+static int hostname_cmp(const char *a, const char *b)
+{
+ char *sa, *ea, *ca, *sb, *eb, *cb;
+ unsigned char ac, bc;
+
+ sa = ea = (char *)a + strlen(a);
+ sb = eb = (char *)b + strlen(b);
+
+ while (1)
+ {
+ while (sa != a && *(sa-1) != '.')
+ sa--;
+
+ while (sb != b && *(sb-1) != '.')
+ sb--;
+
+ ca = sa;
+ cb = sb;
+
+ while (1)
+ {
+ if (ca == ea)
+ {
+ if (cb == eb)
+ break;
+
+ return -1;
+ }
+
+ if (cb == eb)
+ return 1;
+
+ ac = (unsigned char) *ca++;
+ bc = (unsigned char) *cb++;
+
+ if (ac >= 'A' && ac <= 'Z')
+ ac += 'a' - 'A';
+ if (bc >= 'A' && bc <= 'Z')
+ bc += 'a' - 'A';
+
+ if (ac < bc)
+ return -1;
+ else if (ac != bc)
+ return 1;
+ }
+
+
+ if (sa == a)
+ {
+ if (sb == b)
+ return 0;
+
+ return -1;
+ }
+
+ if (sb == b)
+ return 1;
+
+ ea = --sa;
+ eb = --sb;
+ }
+}
+
+static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
+ char *workspace1_in, char *workspace2, char *name, int type, int *nons)
+{
+ int i, rc, rdlen;
+ unsigned char *p, *psave;
+ int offset = (type & 0xff) >> 3;
+ int mask = 0x80 >> (type & 0x07);
+
+ if (nons)
+ *nons = 1;
+
+ /* Find NSEC record that proves name doesn't exist */
+ for (i = 0; i < nsec_count; i++)
+ {
+ char *workspace1 = workspace1_in;
+ int sig_labels, name_labels;
+
+ p = nsecs[i];
+ if (!extract_name(header, plen, &p, workspace1, 1, 10))
+ return 0;
+ p += 8; /* class, type, TTL */
+ GETSHORT(rdlen, p);
+ psave = p;
+ if (!extract_name(header, plen, &p, workspace2, 1, 10))
+ return 0;
+
+ /* If NSEC comes from wildcard expansion, use original wildcard
+ as name for computation. */
+ sig_labels = *labels[i];
+ name_labels = count_labels(workspace1);
+
+ if (sig_labels < name_labels)
+ {
+ int k;
+ for (k = name_labels - sig_labels; k != 0; k--)
+ {
+ while (*workspace1 != '.' && *workspace1 != 0)
+ workspace1++;
+ if (k != 1 && *workspace1 == '.')
+ workspace1++;
+ }
+
+ workspace1--;
+ *workspace1 = '*';
+ }
+
+ rc = hostname_cmp(workspace1, name);
+
+ if (rc == 0)
+ {
+ /* 4035 para 5.4. Last sentence */
+ if (type == T_NSEC || type == T_RRSIG)
+ return 1;
+
+ /* NSEC with the same name as the RR we're testing, check
+ that the type in question doesn't appear in the type map */
+ rdlen -= p - psave;
+ /* rdlen is now length of type map, and p points to it */
+
+ /* If we can prove that there's no NS record, return that information. */
+ if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
+ *nons = 0;
+
+ if (rdlen >= 2 && p[0] == 0)
+ {
+ /* A CNAME answer would also be valid, so if there's a CNAME is should
+ have been returned. */
+ if ((p[2] & (0x80 >> T_CNAME)) != 0)
+ return 0;
+
+ /* If the SOA bit is set for a DS record, then we have the
+ DS from the wrong side of the delegation. For the root DS,
+ this is expected. */
+ if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+ return 0;
+ }
+
+ while (rdlen >= 2)
+ {
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 0;
+
+ if (p[0] == type >> 8)
+ {
+ /* Does the NSEC say our type exists? */
+ if (offset < p[1] && (p[offset+2] & mask) != 0)
+ return 0;
+
+ break; /* finished checking */
+ }
+
+ rdlen -= p[1];
+ p += p[1];
+ }
+
+ return 1;
+ }
+ else if (rc == -1)
+ {
+ /* Normal case, name falls between NSEC name and next domain name,
+ wrap around case, name falls between NSEC name (rc == -1) and end */
+ if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
+ return 1;
+ }
+ else
+ {
+ /* wrap around case, name falls between start and next domain name */
+ if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* return digest length, or zero on error */
+static int hash_name(char *in, unsigned char **out, struct nettle_hash const *hash,
+ unsigned char *salt, int salt_len, int iterations)
+{
+ void *ctx;
+ unsigned char *digest;
+ int i;
+
+ if (!hash_init(hash, &ctx, &digest))
+ return 0;
+
+ hash->update(ctx, to_wire(in), (unsigned char *)in);
+ hash->update(ctx, salt_len, salt);
+ hash->digest(ctx, hash->digest_size, digest);
+
+ for(i = 0; i < iterations; i++)
+ {
+ hash->update(ctx, hash->digest_size, digest);
+ hash->update(ctx, salt_len, salt);
+ hash->digest(ctx, hash->digest_size, digest);
+ }
+
+ from_wire(in);
+
+ *out = digest;
+ return hash->digest_size;
+}
+
+/* Decode base32 to first "." or end of string */
+static int base32_decode(char *in, unsigned char *out)
+{
+ int oc, on, c, mask, i;
+ unsigned char *p = out;
+
+ for (c = *in, oc = 0, on = 0; c != 0 && c != '.'; c = *++in)
+ {
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else if (c >= 'a' && c <= 'v')
+ c -= 'a', c += 10;
+ else if (c >= 'A' && c <= 'V')
+ c -= 'A', c += 10;
+ else
+ return 0;
+
+ for (mask = 0x10, i = 0; i < 5; i++)
+ {
+ if (c & mask)
+ oc |= 1;
+ mask = mask >> 1;
+ if (((++on) & 7) == 0)
+ *p++ = oc;
+ oc = oc << 1;
+ }
+ }
+
+ if ((on & 7) != 0)
+ return 0;
+
+ return p - out;
+}
+
+static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
+ char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels)
+{
+ int i, hash_len, salt_len, base32_len, rdlen, flags;
+ unsigned char *p, *psave;
+
+ for (i = 0; i < nsec_count; i++)
+ if ((p = nsecs[i]))
+ {
+ if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+ !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+ return 0;
+
+ p += 8; /* class, type, TTL */
+ GETSHORT(rdlen, p);
+ psave = p;
+ p++; /* algo */
+ flags = *p++; /* flags */
+ p += 2; /* iterations */
+ salt_len = *p++; /* salt_len */
+ p += salt_len; /* salt */
+ hash_len = *p++; /* p now points to next hashed name */
+
+ if (!CHECK_LEN(header, p, plen, hash_len))
+ return 0;
+
+ if (digest_len == base32_len && hash_len == base32_len)
+ {
+ int rc = memcmp(workspace2, digest, digest_len);
+
+ if (rc == 0)
+ {
+ /* We found an NSEC3 whose hashed name exactly matches the query, so
+ we just need to check the type map. p points to the RR data for the record. */
+
+ int offset = (type & 0xff) >> 3;
+ int mask = 0x80 >> (type & 0x07);
+
+ p += hash_len; /* skip next-domain hash */
+ rdlen -= p - psave;
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 0;
+
+ if (rdlen >= 2 && p[0] == 0)
+ {
+ /* If we can prove that there's no NS record, return that information. */
+ if (nons && (p[2] & (0x80 >> T_NS)) != 0)
+ *nons = 0;
+
+ /* A CNAME answer would also be valid, so if there's a CNAME is should
+ have been returned. */
+ if ((p[2] & (0x80 >> T_CNAME)) != 0)
+ return 0;
+
+ /* If the SOA bit is set for a DS record, then we have the
+ DS from the wrong side of the delegation. For the root DS,
+ this is expected. */
+ if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+ return 0;
+ }
+
+ while (rdlen >= 2)
+ {
+ if (p[0] == type >> 8)
+ {
+ /* Does the NSEC3 say our type exists? */
+ if (offset < p[1] && (p[offset+2] & mask) != 0)
+ return 0;
+
+ break; /* finished checking */
+ }
+
+ rdlen -= p[1];
+ p += p[1];
+ }
+
+ return 1;
+ }
+ else if (rc < 0)
+ {
+ /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
+ wrap around case, name-hash falls between NSEC3 name-hash and end */
+ if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
+ {
+ if ((flags & 0x01) && nons) /* opt out */
+ *nons = 0;
+
+ return 1;
+ }
+ }
+ else
+ {
+ /* wrap around case, name falls between start and next domain name */
+ if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0)
+ {
+ if ((flags & 0x01) && nons) /* opt out */
+ *nons = 0;
+
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
+ char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
+{
+ unsigned char *salt, *p, *digest;
+ int digest_len, i, iterations, salt_len, base32_len, algo = 0;
+ struct nettle_hash const *hash;
+ char *closest_encloser, *next_closest, *wildcard;
+
+ if (nons)
+ *nons = 1;
+
+ /* Look though the NSEC3 records to find the first one with
+ an algorithm we support.
+
+ Take the algo, iterations, and salt of that record
+ as the ones we're going to use, and prune any
+ that don't match. */
+
+ for (i = 0; i < nsec_count; i++)
+ {
+ if (!(p = skip_name(nsecs[i], header, plen, 15)))
+ return 0; /* bad packet */
+
+ p += 10; /* type, class, TTL, rdlen */
+ algo = *p++;
+
+ if ((hash = hash_find(nsec3_digest_name(algo))))
+ break; /* known algo */
+ }
+
+ /* No usable NSEC3s */
+ if (i == nsec_count)
+ return 0;
+
+ p++; /* flags */
+
+ GETSHORT (iterations, p);
+ /* Upper-bound iterations, to avoid DoS.
+ Strictly, there are lower bounds for small keys, but
+ since we don't have key size info here, at least limit
+ to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
+ if (iterations > 2500)
+ return 0;
+
+ salt_len = *p++;
+ salt = p;
+ if (!CHECK_LEN(header, salt, plen, salt_len))
+ return 0; /* bad packet */
+
+ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
+ for (i = 0; i < nsec_count; i++)
+ {
+ unsigned char *nsec3p = nsecs[i];
+ int this_iter, flags;
+
+ nsecs[i] = NULL; /* Speculative, will be restored if OK. */
+
+ if (!(p = skip_name(nsec3p, header, plen, 15)))
+ return 0; /* bad packet */
+
+ p += 10; /* type, class, TTL, rdlen */
+
+ if (*p++ != algo)
+ continue;
+
+ flags = *p++; /* flags */
+
+ /* 5155 8.2 */
+ if (flags != 0 && flags != 1)
+ continue;
+
+ GETSHORT(this_iter, p);
+ if (this_iter != iterations)
+ continue;
+
+ if (salt_len != *p++)
+ continue;
+
+ if (!CHECK_LEN(header, p, plen, salt_len))
+ return 0; /* bad packet */
+
+ if (memcmp(p, salt, salt_len) != 0)
+ continue;
+
+ /* All match, put the pointer back */
+ nsecs[i] = nsec3p;
+ }
+
+ if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
+ return 0;
+
+ if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
+ return 1;
+
+ /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
+ or an answer inferred from a wildcard record. */
+ closest_encloser = name;
+ next_closest = NULL;
+
+ do
+ {
+ if (*closest_encloser == '.')
+ closest_encloser++;
+
+ if (wildname && hostname_isequal(closest_encloser, wildname))
+ break;
+
+ if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
+ return 0;
+
+ for (i = 0; i < nsec_count; i++)
+ if ((p = nsecs[i]))
+ {
+ if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+ !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+ return 0;
+
+ if (digest_len == base32_len &&
+ memcmp(digest, workspace2, digest_len) == 0)
+ break; /* Gotit */
+ }
+
+ if (i != nsec_count)
+ break;
+
+ next_closest = closest_encloser;
+ }
+ while ((closest_encloser = strchr(closest_encloser, '.')));
+
+ if (!closest_encloser || !next_closest)
+ return 0;
+
+ /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
+ if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
+ return 0;
+
+ if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
+ return 0;
+
+ /* Finally, check that there's no seat of wildcard synthesis */
+ if (!wildname)
+ {
+ if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
+ return 0;
+
+ wildcard--;
+ *wildcard = '*';
+
+ if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
+ return 0;
+
+ if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
+{
+ static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
+ static int nsecset_sz = 0, rrsig_labels_sz = 0;
+
+ int type_found = 0;
+ unsigned char *auth_start, *p = skip_questions(header, plen);
+ int type, class, rdlen, i, nsecs_found;
+ unsigned long ttl;
+
+ /* Move to NS section */
+ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
+ return 0;
+
+ auth_start = p;
+
+ for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
+ {
+ unsigned char *pstart = p;
+
+ if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
+ return 0;
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ if (class == qclass && (type == T_NSEC || type == T_NSEC3))
+ {
+ if (nsec_ttl)
+ {
+ /* Limit TTL with sig TTL */
+ if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
+ ttl = daemon->rr_status[ntohs(header->ancount) + i];
+ *nsec_ttl = ttl;
+ }
+
+ /* No mixed NSECing 'round here, thankyouverymuch */
+ if (type_found != 0 && type_found != type)
+ return 0;
+
+ type_found = type;
+
+ if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+ return 0;
+
+ if (type == T_NSEC)
+ {
+ /* If we're looking for NSECs, find the corresponding SIGs, to
+ extract the labels value, which we need in case the NSECs
+ are the result of wildcard expansion.
+ Note that the NSEC may not have been validated yet
+ so if there are multiple SIGs, make sure the label value
+ is the same in all, to avoid be duped by a rogue one.
+ If there are no SIGs, that's an error */
+ unsigned char *p1 = auth_start;
+ int res, j, rdlen1, type1, class1;
+
+ if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
+ return 0;
+
+ rrsig_labels[nsecs_found] = NULL;
+
+ for (j = ntohs(header->nscount); j != 0; j--)
+ {
+ if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
+ return 0;
+
+ GETSHORT(type1, p1);
+ GETSHORT(class1, p1);
+ p1 += 4; /* TTL */
+ GETSHORT(rdlen1, p1);
+
+ if (!CHECK_LEN(header, p1, plen, rdlen1))
+ return 0;
+
+ if (res == 1 && class1 == qclass && type1 == T_RRSIG)
+ {
+ int type_covered;
+ unsigned char *psav = p1;
+
+ if (rdlen1 < 18)
+ return 0; /* bad packet */
+
+ GETSHORT(type_covered, p1);
+
+ if (type_covered == T_NSEC)
+ {
+ p1++; /* algo */
+
+ /* labels field must be the same in every SIG we find. */
+ if (!rrsig_labels[nsecs_found])
+ rrsig_labels[nsecs_found] = p1;
+ else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
+ return 0;
+ }
+ p1 = psav;
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen1))
+ return 0;
+ }
+
+ /* Must have found at least one sig. */
+ if (!rrsig_labels[nsecs_found])
+ return 0;
+ }
+
+ nsecset[nsecs_found++] = pstart;
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return 0;
+ }
+
+ if (type_found == T_NSEC)
+ return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
+ else if (type_found == T_NSEC3)
+ return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+ else
+ return 0;
+}
+
+/* Check signing status of name.
+ returns:
+ STAT_SECURE zone is signed.
+ STAT_INSECURE zone proved unsigned.
+ STAT_NEED_DS require DS record of name returned in keyname.
+ STAT_NEED_KEY require DNSKEY record of name returned in keyname.
+ name returned unaltered.
+*/
+static int zone_status(char *name, int class, char *keyname, time_t now)
+{
+ int name_start = strlen(name); /* for when TA is root */
+ struct crec *crecp;
+ char *p;
+
+ /* First, work towards the root, looking for a trust anchor.
+ This can either be one configured, or one previously cached.
+ We can assume, if we don't find one first, that there is
+ a trust anchor at the root. */
+ for (p = name; p; p = strchr(p, '.'))
+ {
+ if (*p == '.')
+ p++;
+
+ if (cache_find_by_name(NULL, p, now, F_DS))
+ {
+ name_start = p - name;
+ break;
+ }
+ }
+
+ /* Now work away from the trust anchor */
+ while (1)
+ {
+ strcpy(keyname, &name[name_start]);
+
+ if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
+ return STAT_NEED_DS;
+
+ /* F_DNSSECOK misused in DS cache records to non-existence of NS record.
+ F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
+ but that's because there's no NS record either, ie this isn't the start
+ of a zone. We only prove that the DNS tree below a node is unsigned when
+ we prove that we're at a zone cut AND there's no DS record. */
+ if (crecp->flags & F_NEG)
+ {
+ if (crecp->flags & F_DNSSECOK)
+ return STAT_INSECURE; /* proved no DS here */
+ }
+ else
+ {
+ /* If all the DS records have digest and/or sig algos we don't support,
+ then the zone is insecure. Note that if an algo
+ appears in the DS, then RRSIGs for that algo MUST
+ exist for each RRset: 4035 para 2.2 So if we find
+ a DS here with digest and sig we can do, we're entitled
+ to assume we can validate the zone and if we can't later,
+ because an RRSIG is missing we return BOGUS.
+ */
+ do
+ {
+ if (crecp->uid == (unsigned int)class &&
+ ds_digest_name(crecp->addr.ds.digest) &&
+ algo_digest_name(crecp->addr.ds.algo))
+ break;
+ }
+ while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
+
+ if (!crecp)
+ return STAT_INSECURE;
+ }
+
+ if (name_start == 0)
+ break;
+
+ for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
+
+ if (p != name)
+ p++;
+
+ name_start = p - name;
+ }
+
+ return STAT_SECURE;
+}
+
+/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3)
+ Return code:
+ STAT_SECURE if it validates.
+ STAT_INSECURE at least one RRset not validated, because in unsigned zone.
+ STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
+ STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
+ STAT_NEED_DS need DS to complete validation (name is returned in keyname)
+
+ daemon->rr_status points to a char array which corressponds to the RRs in the
+ answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't.
+
+ When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
+ Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
+ is the nons argument is non-NULL.
+*/
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
+ int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl)
+{
+ static unsigned char **targets = NULL;
+ static int target_sz = 0;
+
+ unsigned char *ans_start, *p1, *p2;
+ int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
+ int i, j, rc = STAT_INSECURE;
+ int secure = STAT_SECURE;
+
+ /* extend rr_status if necessary */
+ if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
+ {
+ unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64));
+
+ if (!new)
+ return STAT_BOGUS;
+
+ free(daemon->rr_status);
+ daemon->rr_status = new;
+ daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64;
+ }
+
+ memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz);
+
+ if (neganswer)
+ *neganswer = 0;
+
+ if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1)
+ return STAT_BOGUS;
+
+ if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
+ return STAT_INSECURE;
+
+ p1 = (unsigned char *)(header+1);
+
+ /* Find all the targets we're looking for answers to.
+ The zeroth array element is for the query, subsequent ones
+ for CNAME targets, unless the query is for a CNAME or ANY. */
+
+ if (!expand_workspace(&targets, &target_sz, 0))
+ return STAT_BOGUS;
+
+ targets[0] = p1;
+ targetidx = 1;
+
+ if (!extract_name(header, plen, &p1, name, 1, 4))
+ return STAT_BOGUS;
+
+ GETSHORT(qtype, p1);
+ GETSHORT(qclass, p1);
+ ans_start = p1;
+
+ /* Can't validate an RRSIG query */
+ if (qtype == T_RRSIG)
+ return STAT_INSECURE;
+
+ if (qtype != T_CNAME && qtype != T_ANY)
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ if (!(p1 = skip_name(p1, header, plen, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type2, p1);
+ p1 += 6; /* class, TTL */
+ GETSHORT(rdlen2, p1);
+
+ if (type2 == T_CNAME)
+ {
+ if (!expand_workspace(&targets, &target_sz, targetidx))
+ return STAT_BOGUS;
+
+ targets[targetidx++] = p1; /* pointer to target name */
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen2))
+ return STAT_BOGUS;
+ }
+
+ for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+ {
+ if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
+ return STAT_BOGUS;
+
+ if (!extract_name(header, plen, &p1, name, 1, 10))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type1, p1);
+ GETSHORT(class1, p1);
+ p1 += 4; /* TTL */
+ GETSHORT(rdlen1, p1);
+
+ /* Don't try and validate RRSIGs! */
+ if (type1 == T_RRSIG)
+ continue;
+
+ /* Check if we've done this RRset already */
+ for (p2 = ans_start, j = 0; j < i; j++)
+ {
+ if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type2, p2);
+ GETSHORT(class2, p2);
+ p2 += 4; /* TTL */
+ GETSHORT(rdlen2, p2);
+
+ if (type2 == type1 && class2 == class1 && rc == 1)
+ break; /* Done it before: name, type, class all match. */
+
+ if (!ADD_RDLEN(header, p2, plen, rdlen2))
+ return STAT_BOGUS;
+ }
+
+ /* Done already: copy the validation status */
+ if (j != i)
+ daemon->rr_status[i] = daemon->rr_status[j];
+ else
+ {
+ /* Not done, validate now */
+ int sigcnt, rrcnt;
+ char *wildname;
+
+ if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+ return STAT_BOGUS;
+
+ /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
+ if (sigcnt == 0)
+ {
+ /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
+ if (type1 == T_NSEC || type1 == T_NSEC3)
+ rc = STAT_INSECURE;
+ else if (nons && i >= ntohs(header->ancount))
+ /* If we're validating a DS reply, rather than looking for the value of AD bit,
+ we only care that NSEC and NSEC3 RRs in the auth section are signed.
+ Return SECURE even if others (SOA....) are not. */
+ rc = STAT_SECURE;
+ else
+ {
+ /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
+ if (check_unsigned && i < ntohs(header->ancount))
+ {
+ rc = zone_status(name, class1, keyname, now);
+ if (STAT_ISEQUAL(rc, STAT_SECURE))
+ rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+ if (class)
+ *class = class1; /* Class for NEED_DS or NEED_KEY */
+ }
+ else
+ rc = STAT_INSECURE;
+
+ if (!STAT_ISEQUAL(rc, STAT_INSECURE))
+ return rc;
+ }
+ }
+ else
+ {
+ /* explore_rrset() gives us key name from sigs in keyname.
+ Can't overwrite name here. */
+ strcpy(daemon->workspacename, keyname);
+ rc = zone_status(daemon->workspacename, class1, keyname, now);
+
+ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
+ {
+ if (class)
+ *class = class1; /* Class for NEED_DS or NEED_KEY */
+ return rc;
+ }
+
+ /* Zone is insecure, don't need to validate RRset */
+ if (STAT_ISEQUAL(rc, STAT_SECURE))
+ {
+ unsigned long sig_ttl;
+ rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
+ rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
+
+ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
+ {
+ if (class)
+ *class = class1; /* Class for DS or DNSKEY */
+ return rc;
+ }
+
+ /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
+
+ /* Note that RR is validated */
+ daemon->rr_status[i] = sig_ttl;
+
+ /* Note if we've validated either the answer to the question
+ or the target of a CNAME. Any not noted will need NSEC or
+ to be in unsigned space. */
+ for (j = 0; j <targetidx; j++)
+ if ((p2 = targets[j]))
+ {
+ int rc1;
+ if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
+ targets[j] = NULL;
+ }
+
+ /* An attacker replay a wildcard answer with a different
+ answer and overlay a genuine RR. To prove this
+ hasn't happened, the answer must prove that
+ the genuine record doesn't exist. Check that here.
+ Note that we may not yet have validated the NSEC/NSEC3 RRsets.
+ That's not a problem since if the RRsets later fail
+ we'll return BOGUS then. */
+ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
+ !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
+ return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
+
+ rc = STAT_SECURE;
+ }
+ }
+ }
+
+ if (STAT_ISEQUAL(rc, STAT_INSECURE))
+ secure = STAT_INSECURE;
+ }
+
+ /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
+ if (STAT_ISEQUAL(secure, STAT_SECURE))
+ for (j = 0; j <targetidx; j++)
+ if ((p2 = targets[j]))
+ {
+ if (neganswer)
+ *neganswer = 1;
+
+ if (!extract_name(header, plen, &p2, name, 1, 10))
+ return STAT_BOGUS; /* bad packet */
+
+ /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
+
+ /* For anything other than a DS record, this situation is OK if either
+ the answer is in an unsigned zone, or there's a NSEC records. */
+ if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
+ {
+ /* Empty DS without NSECS */
+ if (qtype == T_DS)
+ return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
+
+ if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
+ {
+ if (class)
+ *class = qclass; /* Class for NEED_DS or NEED_KEY */
+ return rc;
+ }
+
+ return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
+ }
+ }
+
+ return secure;
+}
+
+
+/* Compute keytag (checksum to quickly index a key). See RFC4034 */
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
+{
+ if (alg == 1)
+ {
+ /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
+ See RFC4034, Appendix B.1 */
+ return key[keylen-4] * 256 + key[keylen-3];
+ }
+ else
+ {
+ unsigned long ac = flags + 0x300 + alg;
+ int i;
+
+ for (i = 0; i < keylen; ++i)
+ ac += (i & 1) ? key[i] : key[i] << 8;
+
+ ac += (ac >> 16) & 0xffff;
+ return ac & 0xffff;
+ }
+}
+
+size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class,
+ int type, int edns_pktsz)
+{
+ unsigned char *p;
+ size_t ret;
+
+ header->qdcount = htons(1);
+ header->ancount = htons(0);
+ header->nscount = htons(0);
+ header->arcount = htons(0);
+
+ header->hb3 = HB3_RD;
+ SET_OPCODE(header, QUERY);
+ /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+ this allows it to select auth servers when one is returning bad data. */
+ header->hb4 = option_bool(OPT_DNSSEC_DEBUG) ? HB4_CD : 0;
+
+ /* ID filled in later */
+
+ p = (unsigned char *)(header+1);
+
+ p = do_rfc1035_name(p, name, NULL);
+ *p++ = 0;
+ PUTSHORT(type, p);
+ PUTSHORT(class, p);
+
+ ret = add_do_bit(header, p - (unsigned char *)header, end);
+
+ if (find_pseudoheader(header, ret, NULL, &p, NULL, NULL))
+ PUTSHORT(edns_pktsz, p);
+
+ return ret;
+}
+
+int errflags_to_ede(int status)
+{
+ /* We can end up with more than one flag set for some errors,
+ so this encodes a rough priority so the (eg) No sig is reported
+ before no-unexpired-sig. */
+
+ if (status & DNSSEC_FAIL_NYV)
+ return EDE_SIG_NYV;
+ else if (status & DNSSEC_FAIL_EXP)
+ return EDE_SIG_EXP;
+ else if (status & DNSSEC_FAIL_NOKEYSUP)
+ return EDE_USUPDNSKEY;
+ else if (status & DNSSEC_FAIL_NOZONE)
+ return EDE_NO_ZONEKEY;
+ else if (status & DNSSEC_FAIL_NOKEY)
+ return EDE_NO_DNSKEY;
+ else if (status & DNSSEC_FAIL_NODSSUP)
+ return EDE_USUPDS;
+ else if (status & DNSSEC_FAIL_NONSEC)
+ return EDE_NO_NSEC;
+ else if (status & DNSSEC_FAIL_INDET)
+ return EDE_DNSSEC_IND;
+ else if (status & DNSSEC_FAIL_NOSIG)
+ return EDE_NO_RRSIG;
+ else
+ return EDE_UNSET;
+}
+#endif /* HAVE_DNSSEC */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c b/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c
new file mode 100755
index 0000000..f8e4796
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c
@@ -0,0 +1,698 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 order(char *qdomain, size_t qlen, struct server *serv);
+static int order_qsort(const void *a, const void *b);
+static int order_servers(struct server *s, struct server *s2);
+
+/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
+#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
+
+void build_server_array(void)
+{
+ struct server *serv;
+ int count = 0;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+#ifdef HAVE_LOOP
+ if (!(serv->flags & SERV_LOOP))
+#endif
+ {
+ count++;
+ if (serv->flags & SERV_WILDCARD)
+ daemon->server_has_wildcard = 1;
+ }
+
+ for (serv = daemon->local_domains; serv; serv = serv->next)
+ {
+ count++;
+ if (serv->flags & SERV_WILDCARD)
+ daemon->server_has_wildcard = 1;
+ }
+
+ daemon->serverarraysz = count;
+
+ if (count > daemon->serverarrayhwm)
+ {
+ struct server **new;
+
+ count += 10; /* A few extra without re-allocating. */
+
+ if ((new = whine_malloc(count * sizeof(struct server *))))
+ {
+ if (daemon->serverarray)
+ free(daemon->serverarray);
+
+ daemon->serverarray = new;
+ daemon->serverarrayhwm = count;
+ }
+ }
+
+ count = 0;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+#ifdef HAVE_LOOP
+ if (!(serv->flags & SERV_LOOP))
+#endif
+ {
+ daemon->serverarray[count] = serv;
+ serv->serial = count;
+ serv->last_server = -1;
+ count++;
+ }
+
+ for (serv = daemon->local_domains; serv; serv = serv->next, count++)
+ daemon->serverarray[count] = serv;
+
+ qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
+
+ /* servers need the location in the array to find all the whole
+ set of equivalent servers from a pointer to a single one. */
+ for (count = 0; count < daemon->serverarraysz; count++)
+ if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL))
+ daemon->serverarray[count]->arrayposn = count;
+}
+
+/* we're looking for the server whose domain is the longest exact match
+ to the RH end of qdomain, or a local address if the flags match.
+ Add '.' to the LHS of the query string so
+ server=/.example.com/ works.
+
+ A flag of F_SERVER returns an upstream server only.
+ A flag of F_DNSSECOK returns a DNSSEC capable server only and
+ also disables NODOTS servers from consideration.
+ A flag of F_DOMAINSRV returns a domain-specific server only.
+ A flag of F_CONFIG returns anything that generates a local
+ reply of IPv4 or IPV6.
+ return 0 if nothing found, 1 otherwise.
+*/
+int lookup_domain(char *domain, int flags, int *lowout, int *highout)
+{
+ int rc, crop_query, nodots;
+ ssize_t qlen;
+ int try, high, low = 0;
+ int nlow = 0, nhigh = 0;
+ char *cp, *qdomain = domain;
+
+ /* may be no configured servers. */
+ if (daemon->serverarraysz == 0)
+ return 0;
+
+ /* find query length and presence of '.' */
+ for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
+ if (*cp == '.')
+ nodots = 0;
+
+ /* Handle empty name, and searches for DNSSEC queries without
+ diverting to NODOTS servers. */
+ if (qlen == 0 || flags & F_DNSSECOK)
+ nodots = 0;
+
+ /* Search shorter and shorter RHS substrings for a match */
+ while (qlen >= 0)
+ {
+ /* Note that when we chop off a label, all the possible matches
+ MUST be at a larger index than the nearest failing match with one more
+ character, since the array is sorted longest to smallest. Hence
+ we don't reset low to zero here, we can go further below and crop the
+ search string to the size of the largest remaining server
+ when this match fails. */
+ high = daemon->serverarraysz;
+ crop_query = 1;
+
+ /* binary search */
+ while (1)
+ {
+ try = (low + high)/2;
+
+ if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0)
+ break;
+
+ if (rc < 0)
+ {
+ if (high == try)
+ {
+ /* qdomain is longer or same length as longest domain, and try == 0
+ crop the query to the longest domain. */
+ crop_query = qlen - daemon->serverarray[try]->domain_len;
+ break;
+ }
+ high = try;
+ }
+ else
+ {
+ if (low == try)
+ {
+ /* try now points to the last domain that sorts before the query, so
+ we know that a substring of the query shorter than it is required to match, so
+ find the largest domain that's shorter than try. Note that just going to
+ try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point
+ to aaa, since ccc sorts after bbb, but the first domain that has a chance to
+ match is bb. So find the length of the first domain later than try which is
+ is shorter than it.
+ There's a nasty edge case when qdomain sorts before _any_ of the
+ server domains, where try _doesn't point_ to the last domain that sorts
+ before the query, since no such domain exists. In that case, the loop
+ exits via the rc < 0 && high == try path above and this code is
+ not executed. */
+ ssize_t len, old = daemon->serverarray[try]->domain_len;
+ while (++try != daemon->serverarraysz)
+ {
+ if (old != (len = daemon->serverarray[try]->domain_len))
+ {
+ crop_query = qlen - len;
+ break;
+ }
+ }
+ break;
+ }
+ low = try;
+ }
+ };
+
+ if (rc == 0)
+ {
+ int found = 1;
+
+ if (daemon->server_has_wildcard)
+ {
+ /* if we have example.com and *example.com we need to check against *example.com,
+ but the binary search may have found either. Use the fact that example.com is sorted before *example.com
+ We favour example.com in the case that both match (ie www.example.com) */
+ while (try != 0 && order(qdomain, qlen, daemon->serverarray[try-1]) == 0)
+ try--;
+
+ if (!(qdomain == domain || *qdomain == 0 || *(qdomain-1) == '.'))
+ {
+ while (try < daemon->serverarraysz-1 && order(qdomain, qlen, daemon->serverarray[try+1]) == 0)
+ try++;
+
+ if (!(daemon->serverarray[try]->flags & SERV_WILDCARD))
+ found = 0;
+ }
+ }
+
+ if (found)
+ {
+ /* We've matched a setting which says to use servers without a domain.
+ Continue the search with empty query */
+ if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
+ crop_query = qlen;
+ else if (filter_servers(try, flags, &nlow, &nhigh))
+ /* We have a match, but it may only be (say) an IPv6 address, and
+ if the query wasn't for an AAAA record, it's no good, and we need
+ to continue generalising */
+ break;
+ }
+ }
+
+ /* crop_query must be at least one always. */
+ if (crop_query == 0)
+ crop_query = 1;
+
+ /* strip chars off the query based on the largest possible remaining match,
+ then continue to the start of the next label unless we have a wildcard
+ domain somewhere, in which case we have to go one at a time. */
+ qlen -= crop_query;
+ qdomain += crop_query;
+ if (!daemon->server_has_wildcard)
+ while (qlen > 0 && (*(qdomain-1) != '.'))
+ qlen--, qdomain++;
+ }
+
+ /* domain has no dots, and we have at least one server configured to handle such,
+ These servers always sort to the very end of the array.
+ A configured server eg server=/lan/ will take precdence. */
+ if (nodots &&
+ (daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
+ (nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
+ filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
+
+ if (lowout)
+ *lowout = nlow;
+
+ if (highout)
+ *highout = nhigh;
+
+ if (nlow == nhigh)
+ return 0;
+
+ return 1;
+}
+
+/* Return first server in group of equivalent servers; this is the "master" record. */
+int server_samegroup(struct server *a, struct server *b)
+{
+ return order_servers(a, b) == 0;
+}
+
+int filter_servers(int seed, int flags, int *lowout, int *highout)
+{
+ int nlow = seed, nhigh = seed;
+ int i;
+
+ /* expand nlow and nhigh to cover all the records with the same domain
+ nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers,
+ which can happen below. */
+ while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0)
+ nlow--;
+
+ while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
+ nhigh++;
+
+ nhigh++;
+
+#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
+
+ if (flags & F_CONFIG)
+ {
+ /* We're just lookin for any matches that return an RR. */
+ for (i = nlow; i < nhigh; i++)
+ if (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS)
+ break;
+
+ /* failed, return failure. */
+ if (i == nhigh)
+ nhigh = nlow;
+ }
+ else
+ {
+ /* Now the servers are on order between low and high, in the order
+ IPv6 addr, IPv4 addr, return zero for both, send upstream, no-data return.
+
+ See which of those match our query in that priority order and narrow (low, high) */
+
+ for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
+
+ if (i != nlow && (flags & F_IPV6))
+ nhigh = i;
+ else
+ {
+ nlow = i;
+
+ for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
+
+ if (i != nlow && (flags & F_IPV4))
+ nhigh = i;
+ else
+ {
+ nlow = i;
+
+ for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
+
+ if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
+ nhigh = i;
+ else
+ {
+ nlow = i;
+
+ /* now look for a server */
+ for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
+
+ if (i != nlow)
+ {
+ /* If we want a server that can do DNSSEC, and this one can't,
+ return nothing, similarly if were looking only for a server
+ for a particular domain. */
+ if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
+ nlow = nhigh;
+ else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
+ nlow = nhigh;
+ else
+ nhigh = i;
+ }
+ else
+ {
+ /* --local=/domain/, only return if we don't need a server. */
+ if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
+ nhigh = i;
+ }
+ }
+ }
+ }
+ }
+
+ *lowout = nlow;
+ *highout = nhigh;
+
+ return (nlow != nhigh);
+}
+
+int is_local_answer(time_t now, int first, char *name)
+{
+ int flags = 0;
+ int rc = 0;
+
+ if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
+ {
+ if (flags & SERV_4ADDR)
+ rc = F_IPV4;
+ else if (flags & SERV_6ADDR)
+ rc = F_IPV6;
+ else if (flags & SERV_ALL_ZEROS)
+ rc = F_IPV4 | F_IPV6;
+ else
+ {
+ /* argument first is the first struct server which matches the query type;
+ now roll back to the server which is just the same domain, to check if that
+ provides an answer of a different type. */
+
+ for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--);
+
+ if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) ||
+ check_for_local_domain(name, now))
+ rc = F_NOERR;
+ else
+ rc = F_NXDOMAIN;
+ }
+ }
+
+ return rc;
+}
+
+size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
+{
+ int trunc = 0;
+ unsigned char *p;
+ int start;
+ union all_addr addr;
+
+ if (flags & (F_NXDOMAIN | F_NOERR))
+ log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
+
+ setup_reply(header, flags, ede);
+
+ if (!(p = skip_questions(header, size)))
+ return 0;
+
+ if (flags & gotname & F_IPV4)
+ for (start = first; start != last; start++)
+ {
+ struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
+
+ if (srv->flags & SERV_ALL_ZEROS)
+ memset(&addr, 0, sizeof(addr));
+ else
+ addr.addr4 = srv->addr;
+
+ header->ancount = htons(ntohs(header->ancount) + 1);
+ add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
+ log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
+ }
+
+ if (flags & gotname & F_IPV6)
+ for (start = first; start != last; start++)
+ {
+ struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
+
+ if (srv->flags & SERV_ALL_ZEROS)
+ memset(&addr, 0, sizeof(addr));
+ else
+ addr.addr6 = srv->addr;
+
+ header->ancount = htons(ntohs(header->ancount) + 1);
+ add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
+ log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
+ }
+
+ if (trunc)
+ header->hb3 |= HB3_TC;
+
+ return p - (unsigned char *)header;
+}
+
+#ifdef HAVE_DNSSEC
+int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
+{
+ int first, last, index;
+
+ /* Find server to send DNSSEC query to. This will normally be the
+ same as for the original query, but may be another if
+ servers for domains are involved. */
+ if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
+ return -1;
+
+ for (index = first; index != last; index++)
+ if (daemon->serverarray[index] == server)
+ break;
+
+ /* No match to server used for original query.
+ Use newly looked up set. */
+ if (index == last)
+ index = daemon->serverarray[first]->last_server == -1 ?
+ first : daemon->serverarray[first]->last_server;
+
+ if (firstp)
+ *firstp = first;
+
+ if (lastp)
+ *lastp = last;
+
+ return index;
+}
+#endif
+
+/* order by size, then by dictionary order */
+static int order(char *qdomain, size_t qlen, struct server *serv)
+{
+ size_t dlen = 0;
+
+ /* servers for dotless names always sort last
+ searched for name is never dotless. */
+ if (serv->flags & SERV_FOR_NODOTS)
+ return -1;
+
+ dlen = serv->domain_len;
+
+ if (qlen < dlen)
+ return 1;
+
+ if (qlen > dlen)
+ return -1;
+
+ return strcmp(qdomain, serv->domain);
+}
+
+static int order_servers(struct server *s1, struct server *s2)
+{
+ int rc;
+
+ /* need full comparison of dotless servers in
+ order_qsort() and filter_servers() */
+
+ if (s1->flags & SERV_FOR_NODOTS)
+ return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
+
+ if ((rc = order(s1->domain, s1->domain_len, s2)) != 0)
+ return rc;
+
+ /* For identical domains, sort wildcard ones first */
+ if (s1->flags & SERV_WILDCARD)
+ return (s2->flags & SERV_WILDCARD) ? 0 : 1;
+
+ return (s2->flags & SERV_WILDCARD) ? -1 : 0;
+}
+
+static int order_qsort(const void *a, const void *b)
+{
+ int rc;
+
+ struct server *s1 = *((struct server **)a);
+ struct server *s2 = *((struct server **)b);
+
+ rc = order_servers(s1, s2);
+
+ /* Sort all literal NODATA and local IPV4 or IPV6 responses together,
+ in a very specific order. We flip the SERV_LITERAL_ADDRESS bit
+ so the order is IPv6 literal, IPv4 literal, all-zero literal,
+ upstream server, NXDOMAIN literal. */
+ if (rc == 0)
+ rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
+ ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
+
+ /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
+ if (rc == 0)
+ if (!(s1->flags & SERV_LITERAL_ADDRESS))
+ rc = s1->serial - s2->serial;
+
+ return rc;
+}
+
+void mark_servers(int flag)
+{
+ struct server *serv;
+
+ /* mark everything with argument flag */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (serv->flags & flag)
+ serv->flags |= SERV_MARK;
+ else
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->local_domains; serv; serv = serv->next)
+ if (serv->flags & flag)
+ serv->flags |= SERV_MARK;
+ else
+ serv->flags &= ~SERV_MARK;
+}
+
+void cleanup_servers(void)
+{
+ struct server *serv, *tmp, **up;
+
+ /* 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->domain);
+ free(serv);
+ }
+ else
+ up = &serv->next;
+ }
+
+ for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp)
+ {
+ tmp = serv->next;
+ if (serv->flags & SERV_MARK)
+ {
+ *up = serv->next;
+ free(serv->domain);
+ free(serv);
+ }
+ else
+ up = &serv->next;
+ }
+}
+
+int add_update_server(int flags,
+ union mysockaddr *addr,
+ union mysockaddr *source_addr,
+ const char *interface,
+ const char *domain,
+ union all_addr *local_addr)
+{
+ struct server *serv = NULL;
+ char *alloc_domain;
+
+ if (!domain)
+ domain = "";
+
+ /* .domain == domain, for historical reasons. */
+ if (*domain == '.')
+ while (*domain == '.') domain++;
+ else if (*domain == '*')
+ {
+ domain++;
+ if (*domain != 0)
+ flags |= SERV_WILDCARD;
+ }
+
+ if (*domain == 0)
+ alloc_domain = whine_malloc(1);
+ else if (!(alloc_domain = canonicalise((char *)domain, NULL)))
+ return 0;
+
+ /* See if there is a suitable candidate, and unmark
+ only do this for forwarding servers, not
+ address or local, to avoid delays on large numbers. */
+ if (flags & SERV_IS_LOCAL)
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if ((serv->flags & SERV_MARK) &&
+ hostname_isequal(alloc_domain, serv->domain))
+ break;
+
+ if (serv)
+ {
+ free(alloc_domain);
+ alloc_domain = serv->domain;
+ }
+ else
+ {
+ size_t size;
+
+ if (flags & SERV_LITERAL_ADDRESS)
+ {
+ if (flags & SERV_6ADDR)
+ size = sizeof(struct serv_addr6);
+ else if (flags & SERV_4ADDR)
+ size = sizeof(struct serv_addr4);
+ else
+ size = sizeof(struct serv_local);
+ }
+ else
+ size = sizeof(struct server);
+
+ if (!(serv = whine_malloc(size)))
+ return 0;
+
+ if (flags & SERV_IS_LOCAL)
+ {
+ serv->next = daemon->local_domains;
+ daemon->local_domains = serv;
+ }
+ else
+ {
+ struct server *s;
+ /* Add to the end of the chain, for order */
+ if (!daemon->servers)
+ daemon->servers = serv;
+ else
+ {
+ for (s = daemon->servers; s->next; s = s->next);
+ s->next = serv;
+ }
+
+ serv->next = NULL;
+ }
+ }
+
+ if (!(flags & SERV_IS_LOCAL))
+ memset(serv, 0, sizeof(struct server));
+
+ serv->flags = flags;
+ serv->domain = alloc_domain;
+ serv->domain_len = strlen(alloc_domain);
+
+ if (flags & SERV_4ADDR)
+ ((struct serv_addr4*)serv)->addr = local_addr->addr4;
+
+ if (flags & SERV_6ADDR)
+ ((struct serv_addr6*)serv)->addr = local_addr->addr6;
+
+ if (!(flags & SERV_IS_LOCAL))
+ {
+#ifdef HAVE_LOOP
+ serv->uid = rand32();
+#endif
+
+ if (interface)
+ safe_strncpy(serv->interface, interface, sizeof(serv->interface));
+ if (addr)
+ serv->addr = *addr;
+ if (source_addr)
+ serv->source_addr = *source_addr;
+ }
+
+ return 1;
+}
+
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c b/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c
new file mode 100755
index 0000000..91e0f22
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c
@@ -0,0 +1,297 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 cond_domain *search_domain(struct in_addr addr, struct cond_domain *c);
+static int match_domain(struct in_addr addr, struct cond_domain *c);
+static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c);
+static int match_domain6(struct in6_addr *addr, struct cond_domain *c);
+
+int is_name_synthetic(int flags, char *name, union all_addr *addr)
+{
+ char *p;
+ struct cond_domain *c = NULL;
+ int prot = (flags & F_IPV6) ? AF_INET6 : AF_INET;
+
+ for (c = daemon->synth_domains; c; c = c->next)
+ {
+ int found = 0;
+ char *tail, *pref;
+
+ for (tail = name, pref = c->prefix; *tail != 0 && pref && *pref != 0; tail++, pref++)
+ {
+ unsigned int c1 = (unsigned char) *pref;
+ unsigned int c2 = (unsigned char) *tail;
+
+ if (c1 >= 'A' && c1 <= 'Z')
+ c1 += 'a' - 'A';
+ if (c2 >= 'A' && c2 <= 'Z')
+ c2 += 'a' - 'A';
+
+ if (c1 != c2)
+ break;
+ }
+
+ if (pref && *pref != 0)
+ continue; /* prefix match fail */
+
+ if (c->indexed)
+ {
+ for (p = tail; *p; p++)
+ {
+ char c = *p;
+
+ if (c < '0' || c > '9')
+ break;
+ }
+
+ if (*p != '.')
+ continue;
+
+ *p = 0;
+
+ if (hostname_isequal(c->domain, p+1))
+ {
+ if (prot == AF_INET)
+ {
+ unsigned int index = atoi(tail);
+
+ if (!c->is6 &&
+ index <= ntohl(c->end.s_addr) - ntohl(c->start.s_addr))
+ {
+ addr->addr4.s_addr = htonl(ntohl(c->start.s_addr) + index);
+ found = 1;
+ }
+ }
+ else
+ {
+ u64 index = atoll(tail);
+
+ if (c->is6 &&
+ index <= addr6part(&c->end6) - addr6part(&c->start6))
+ {
+ u64 start = addr6part(&c->start6);
+ addr->addr6 = c->start6;
+ setaddr6part(&addr->addr6, start + index);
+ found = 1;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* NB, must not alter name if we return zero */
+ for (p = tail; *p; p++)
+ {
+ char c = *p;
+
+ if ((c >='0' && c <= '9') || c == '-')
+ continue;
+
+ if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f')))
+ continue;
+
+ break;
+ }
+
+ if (*p != '.')
+ continue;
+
+ *p = 0;
+
+ if (prot == AF_INET6 && strstr(tail, "--ffff-") == tail)
+ {
+ /* special hack for v4-mapped. */
+ memcpy(tail, "::ffff:", 7);
+ for (p = tail + 7; *p; p++)
+ if (*p == '-')
+ *p = '.';
+ }
+ else
+ {
+ /* swap . or : for - */
+ for (p = tail; *p; p++)
+ if (*p == '-')
+ {
+ if (prot == AF_INET)
+ *p = '.';
+ else
+ *p = ':';
+ }
+ }
+
+ if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr))
+ found = (prot == AF_INET) ? match_domain(addr->addr4, c) : match_domain6(&addr->addr6, c);
+ }
+
+ /* restore name */
+ for (p = tail; *p; p++)
+ if (*p == '.' || *p == ':')
+ *p = '-';
+
+ *p = '.';
+
+
+ if (found)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int is_rev_synth(int flag, union all_addr *addr, char *name)
+{
+ struct cond_domain *c;
+
+ if (flag & F_IPV4 && (c = search_domain(addr->addr4, daemon->synth_domains)))
+ {
+ char *p;
+
+ *name = 0;
+ if (c->indexed)
+ {
+ unsigned int index = ntohl(addr->addr4.s_addr) - ntohl(c->start.s_addr);
+ snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index);
+ }
+ else
+ {
+ if (c->prefix)
+ strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
+
+ inet_ntop(AF_INET, &addr->addr4, name + strlen(name), ADDRSTRLEN);
+ for (p = name; *p; p++)
+ if (*p == '.')
+ *p = '-';
+ }
+
+ strncat(name, ".", MAXDNAME);
+ strncat(name, c->domain, MAXDNAME);
+
+ return 1;
+ }
+
+ if ((flag & F_IPV6) && (c = search_domain6(&addr->addr6, daemon->synth_domains)))
+ {
+ char *p;
+
+ *name = 0;
+ if (c->indexed)
+ {
+ u64 index = addr6part(&addr->addr6) - addr6part(&c->start6);
+ snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index);
+ }
+ else
+ {
+ if (c->prefix)
+ strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
+
+ inet_ntop(AF_INET6, &addr->addr6, name + strlen(name), ADDRSTRLEN);
+
+ /* IPv6 presentation address can start with ":", but valid domain names
+ cannot start with "-" so prepend a zero in that case. */
+ if (!c->prefix && *name == ':')
+ {
+ *name = '0';
+ inet_ntop(AF_INET6, &addr->addr6, name+1, ADDRSTRLEN);
+ }
+
+ /* V4-mapped have periods.... */
+ for (p = name; *p; p++)
+ if (*p == ':' || *p == '.')
+ *p = '-';
+
+ }
+
+ strncat(name, ".", MAXDNAME);
+ strncat(name, c->domain, MAXDNAME);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static int match_domain(struct in_addr addr, struct cond_domain *c)
+{
+ if (!c->is6 &&
+ ntohl(addr.s_addr) >= ntohl(c->start.s_addr) &&
+ ntohl(addr.s_addr) <= ntohl(c->end.s_addr))
+ return 1;
+
+ return 0;
+}
+
+static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c)
+{
+ for (; c; c = c->next)
+ if (match_domain(addr, c))
+ return c;
+
+ return NULL;
+}
+
+char *get_domain(struct in_addr addr)
+{
+ struct cond_domain *c;
+
+ if ((c = search_domain(addr, daemon->cond_domain)))
+ return c->domain;
+
+ return daemon->domain_suffix;
+}
+
+static int match_domain6(struct in6_addr *addr, struct cond_domain *c)
+{
+ u64 addrpart = addr6part(addr);
+
+ if (c->is6)
+ {
+ if (c->prefixlen >= 64)
+ {
+ if (is_same_net6(addr, &c->start6, 64) &&
+ addrpart >= addr6part(&c->start6) &&
+ addrpart <= addr6part(&c->end6))
+ return 1;
+ }
+ else if (is_same_net6(addr, &c->start6, c->prefixlen))
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c)
+{
+ for (; c; c = c->next)
+ if (match_domain6(addr, c))
+ return c;
+
+ return NULL;
+}
+
+char *get_domain6(struct in6_addr *addr)
+{
+ struct cond_domain *c;
+
+ if (addr && (c = search_domain6(addr, daemon->cond_domain)))
+ return c->domain;
+
+ return daemon->domain_suffix;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c
new file mode 100755
index 0000000..c131953
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c
@@ -0,0 +1,211 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DUMPFILE
+
+static u32 packet_count;
+
+/* https://wiki.wireshark.org/Development/LibpcapFileFormat */
+struct pcap_hdr_s {
+ u32 magic_number; /* magic number */
+ u16 version_major; /* major version number */
+ u16 version_minor; /* minor version number */
+ u32 thiszone; /* GMT to local correction */
+ u32 sigfigs; /* accuracy of timestamps */
+ u32 snaplen; /* max length of captured packets, in octets */
+ u32 network; /* data link type */
+};
+
+struct pcaprec_hdr_s {
+ u32 ts_sec; /* timestamp seconds */
+ u32 ts_usec; /* timestamp microseconds */
+ u32 incl_len; /* number of octets of packet saved in file */
+ u32 orig_len; /* actual length of packet */
+};
+
+
+void dump_init(void)
+{
+ struct stat buf;
+ struct pcap_hdr_s header;
+ struct pcaprec_hdr_s pcap_header;
+
+ packet_count = 0;
+
+ if (stat(daemon->dump_file, &buf) == -1)
+ {
+ /* doesn't exist, create and add header */
+ header.magic_number = 0xa1b2c3d4;
+ header.version_major = 2;
+ header.version_minor = 4;
+ header.thiszone = 0;
+ header.sigfigs = 0;
+ header.snaplen = daemon->edns_pktsz + 200; /* slop for IP/UDP headers */
+ header.network = 101; /* DLT_RAW http://www.tcpdump.org/linktypes.html */
+
+ if (errno != ENOENT ||
+ (daemon->dumpfd = creat(daemon->dump_file, S_IRUSR | S_IWUSR)) == -1 ||
+ !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 0))
+ die(_("cannot create %s: %s"), daemon->dump_file, EC_FILE);
+ }
+ else if ((daemon->dumpfd = open(daemon->dump_file, O_APPEND | O_RDWR)) == -1 ||
+ !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 1))
+ die(_("cannot access %s: %s"), daemon->dump_file, EC_FILE);
+ else if (header.magic_number != 0xa1b2c3d4)
+ die(_("bad header in %s"), daemon->dump_file, EC_FILE);
+ else
+ {
+ /* count existing records */
+ while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 1))
+ {
+ lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR);
+ packet_count++;
+ }
+ }
+}
+
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst)
+{
+ struct ip ip;
+ struct ip6_hdr ip6;
+ int family;
+ struct udphdr {
+ u16 uh_sport; /* source port */
+ u16 uh_dport; /* destination port */
+ u16 uh_ulen; /* udp length */
+ u16 uh_sum; /* udp checksum */
+ } udp;
+ struct pcaprec_hdr_s pcap_header;
+ struct timeval time;
+ u32 i, sum;
+ void *iphdr;
+ size_t ipsz;
+ int rc;
+
+ if (daemon->dumpfd == -1 || !(mask & daemon->dump_mask))
+ return;
+
+ /* So wireshark can Id the packet. */
+ udp.uh_sport = udp.uh_dport = htons(NAMESERVER_PORT);
+
+ if (src)
+ family = src->sa.sa_family;
+ else
+ family = dst->sa.sa_family;
+
+ if (family == AF_INET6)
+ {
+ iphdr = &ip6;
+ ipsz = sizeof(ip6);
+ memset(&ip6, 0, sizeof(ip6));
+
+ ip6.ip6_vfc = 6 << 4;
+ ip6.ip6_plen = htons(sizeof(struct udphdr) + len);
+ ip6.ip6_nxt = IPPROTO_UDP;
+ ip6.ip6_hops = 64;
+
+ if (src)
+ {
+ memcpy(&ip6.ip6_src, &src->in6.sin6_addr, IN6ADDRSZ);
+ udp.uh_sport = src->in6.sin6_port;
+ }
+
+ if (dst)
+ {
+ memcpy(&ip6.ip6_dst, &dst->in6.sin6_addr, IN6ADDRSZ);
+ udp.uh_dport = dst->in6.sin6_port;
+ }
+
+ /* start UDP checksum */
+ for (sum = 0, i = 0; i < IN6ADDRSZ; i+=2)
+ {
+ sum += ip6.ip6_src.s6_addr[i] + (ip6.ip6_src.s6_addr[i+1] << 8) ;
+ sum += ip6.ip6_dst.s6_addr[i] + (ip6.ip6_dst.s6_addr[i+1] << 8) ;
+
+ }
+ }
+ else
+ {
+ iphdr = &ip;
+ ipsz = sizeof(ip);
+ memset(&ip, 0, sizeof(ip));
+
+ ip.ip_v = IPVERSION;
+ ip.ip_hl = sizeof(struct ip) / 4;
+ ip.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len);
+ ip.ip_ttl = IPDEFTTL;
+ ip.ip_p = IPPROTO_UDP;
+
+ if (src)
+ {
+ ip.ip_src = src->in.sin_addr;
+ udp.uh_sport = src->in.sin_port;
+ }
+
+ if (dst)
+ {
+ ip.ip_dst = dst->in.sin_addr;
+ udp.uh_dport = dst->in.sin_port;
+ }
+
+ 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;
+
+ /* start UDP checksum */
+ 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;
+ }
+
+ if (len & 1)
+ ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
+
+ udp.uh_sum = 0;
+ udp.uh_ulen = htons(sizeof(struct udphdr) + len);
+ sum += htons(IPPROTO_UDP);
+ sum += htons(sizeof(struct udphdr) + len);
+ for (i = 0; i < sizeof(struct udphdr)/2; i++)
+ sum += ((u16 *)&udp)[i];
+ for (i = 0; i < (len + 1) / 2; i++)
+ sum += ((u16 *)packet)[i];
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
+
+ rc = gettimeofday(&time, NULL);
+ pcap_header.ts_sec = time.tv_sec;
+ pcap_header.ts_usec = time.tv_usec;
+ pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
+
+ if (rc == -1 ||
+ !read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 0) ||
+ !read_write(daemon->dumpfd, iphdr, ipsz, 0) ||
+ !read_write(daemon->dumpfd, (void *)&udp, sizeof(udp), 0) ||
+ !read_write(daemon->dumpfd, (void *)packet, len, 0))
+ my_syslog(LOG_ERR, _("failed to write packet dump"));
+ else
+ my_syslog(LOG_INFO, _("dumping UDP packet %u mask 0x%04x"), ++packet_count, mask);
+
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c b/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c
new file mode 100755
index 0000000..7bd26b8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c
@@ -0,0 +1,519 @@
+/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t *len, unsigned char **p, int *is_sign, int *is_last)
+{
+ /* See if packet has an RFC2671 pseudoheader, and if so return a pointer to it.
+ also return length of pseudoheader in *len and pointer to the UDP size in *p
+ Finally, check to see if a packet is signed. If it is we cannot change a single bit before
+ forwarding. We look for TSIG in the addition section, and TKEY queries (for GSS-TSIG) */
+
+ int i, arcount = ntohs(header->arcount);
+ unsigned char *ansp = (unsigned char *)(header+1);
+ unsigned short rdlen, type, class;
+ unsigned char *ret = NULL;
+
+ if (is_sign)
+ {
+ *is_sign = 0;
+
+ if (OPCODE(header) == QUERY)
+ {
+ for (i = ntohs(header->qdcount); i != 0; i--)
+ {
+ if (!(ansp = skip_name(ansp, header, plen, 4)))
+ return NULL;
+
+ GETSHORT(type, ansp);
+ GETSHORT(class, ansp);
+
+ if (class == C_IN && type == T_TKEY)
+ *is_sign = 1;
+ }
+ }
+ }
+ else
+ {
+ if (!(ansp = skip_questions(header, plen)))
+ return NULL;
+ }
+
+ if (arcount == 0)
+ return NULL;
+
+ if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount), header, plen)))
+ return NULL;
+
+ for (i = 0; i < arcount; i++)
+ {
+ unsigned char *save, *start = ansp;
+ if (!(ansp = skip_name(ansp, header, plen, 10)))
+ return NULL;
+
+ GETSHORT(type, ansp);
+ save = ansp;
+ GETSHORT(class, ansp);
+ ansp += 4; /* TTL */
+ GETSHORT(rdlen, ansp);
+ if (!ADD_RDLEN(header, ansp, plen, rdlen))
+ return NULL;
+ if (type == T_OPT)
+ {
+ if (len)
+ *len = ansp - start;
+
+ if (p)
+ *p = save;
+
+ if (is_last)
+ *is_last = (i == arcount-1);
+
+ ret = start;
+ }
+ else if (is_sign &&
+ i == arcount - 1 &&
+ class == C_ANY &&
+ type == T_TSIG)
+ *is_sign = 1;
+ }
+
+ return ret;
+}
+
+
+/* replace == 2 ->delete existing option only. */
+size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
+ unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
+{
+ unsigned char *lenp, *datap, *p, *udp_len, *buff = NULL;
+ int rdlen = 0, is_sign, is_last;
+ unsigned short flags = set_do ? 0x8000 : 0, rcode = 0;
+
+ p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
+
+ if (is_sign)
+ return plen;
+
+ if (p)
+ {
+ /* Existing header */
+ int i;
+ unsigned short code, len;
+
+ p = udp_len;
+ GETSHORT(udp_sz, p);
+ GETSHORT(rcode, p);
+ GETSHORT(flags, p);
+
+ if (set_do)
+ {
+ p -= 2;
+ flags |= 0x8000;
+ PUTSHORT(flags, p);
+ }
+
+ lenp = p;
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return plen; /* bad packet */
+ datap = p;
+
+ /* no option to add */
+ if (optno == 0)
+ return plen;
+
+ /* check if option already there */
+ for (i = 0; i + 4 < rdlen;)
+ {
+ GETSHORT(code, p);
+ GETSHORT(len, p);
+
+ /* malformed option, delete the whole OPT RR and start again. */
+ if (i + 4 + len > rdlen)
+ {
+ rdlen = 0;
+ is_last = 0;
+ break;
+ }
+
+ if (code == optno)
+ {
+ if (replace == 0)
+ return plen;
+
+ /* delete option if we're to replace it. */
+ p -= 4;
+ rdlen -= len + 4;
+ memmove(p, p+len+4, rdlen - i);
+ PUTSHORT(rdlen, lenp);
+ lenp -= 2;
+ }
+ else
+ {
+ p += len;
+ i += len + 4;
+ }
+ }
+
+ /* If we're going to extend the RR, it has to be the last RR in the packet */
+ if (!is_last)
+ {
+ /* First, take a copy of the options. */
+ if (rdlen != 0 && (buff = whine_malloc(rdlen)))
+ memcpy(buff, datap, rdlen);
+
+ /* now, delete OPT RR */
+ plen = rrfilter(header, plen, 0);
+
+ /* Now, force addition of a new one */
+ p = NULL;
+ }
+ }
+
+ if (!p)
+ {
+ /* We are (re)adding the pseudoheader */
+ if (!(p = skip_questions(header, plen)) ||
+ !(p = skip_section(p,
+ ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount),
+ header, plen)))
+ {
+ free(buff);
+ return plen;
+ }
+ if (p + 11 > limit)
+ {
+ free(buff);
+ return plen; /* Too big */
+ }
+ *p++ = 0; /* empty name */
+ PUTSHORT(T_OPT, p);
+ PUTSHORT(udp_sz, p); /* max packet length, 512 if not given in EDNS0 header */
+ PUTSHORT(rcode, p); /* extended RCODE and version */
+ PUTSHORT(flags, p); /* DO flag */
+ lenp = p;
+ PUTSHORT(rdlen, p); /* RDLEN */
+ datap = p;
+ /* Copy back any options */
+ if (buff)
+ {
+ if (p + rdlen > limit)
+ {
+ free(buff);
+ return plen; /* Too big */
+ }
+ memcpy(p, buff, rdlen);
+ free(buff);
+ p += rdlen;
+ }
+
+ /* Only bump arcount if RR is going to fit */
+ if (((ssize_t)optlen) <= (limit - (p + 4)))
+ header->arcount = htons(ntohs(header->arcount) + 1);
+ }
+
+ if (((ssize_t)optlen) > (limit - (p + 4)))
+ return plen; /* Too big */
+
+ /* Add new option */
+ if (optno != 0 && replace != 2)
+ {
+ if (p + 4 > limit)
+ return plen; /* Too big */
+ PUTSHORT(optno, p);
+ PUTSHORT(optlen, p);
+ if (p + optlen > limit)
+ return plen; /* Too big */
+ memcpy(p, opt, optlen);
+ p += optlen;
+ PUTSHORT(p - datap, lenp);
+ }
+ return p - (unsigned char *)header;
+}
+
+size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit)
+{
+ return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1, 0);
+}
+
+static unsigned char char64(unsigned char c)
+{
+ return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f];
+}
+
+static void encoder(unsigned char *in, char *out)
+{
+ out[0] = char64(in[0]>>2);
+ out[1] = char64((in[0]<<4) | (in[1]>>4));
+ out[2] = char64((in[1]<<2) | (in[2]>>6));
+ out[3] = char64(in[2]);
+}
+
+static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *l3, time_t now, int *cacheablep)
+{
+ int maclen, replace = 2; /* can't get mac address, just delete any incoming. */
+ unsigned char mac[DHCP_CHADDR_MAX];
+ char encode[18]; /* handle 6 byte MACs */
+
+ if ((maclen = find_mac(l3, mac, 1, now)) == 6)
+ {
+ replace = 1;
+ *cacheablep = 0;
+
+ if (option_bool(OPT_MAC_HEX))
+ print_mac(encode, mac, maclen);
+ else
+ {
+ encoder(mac, encode);
+ encoder(mac+3, encode+4);
+ encode[8] = 0;
+ }
+ }
+
+ return add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace);
+}
+
+
+static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *l3, time_t now, int *cacheablep)
+{
+ int maclen;
+ unsigned char mac[DHCP_CHADDR_MAX];
+
+ if ((maclen = find_mac(l3, mac, 1, now)) != 0)
+ {
+ *cacheablep = 0;
+ plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0);
+ }
+
+ return plen;
+}
+
+struct subnet_opt {
+ u16 family;
+ u8 source_netmask, scope_netmask;
+ u8 addr[IN6ADDRSZ];
+};
+
+static void *get_addrp(union mysockaddr *addr, const short family)
+{
+ if (family == AF_INET6)
+ return &addr->in6.sin6_addr;
+
+ return &addr->in.sin_addr;
+}
+
+static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
+{
+ /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+
+ int len;
+ void *addrp = NULL;
+ int sa_family = source->sa.sa_family;
+ int cacheable = 0;
+
+ opt->source_netmask = 0;
+ opt->scope_netmask = 0;
+
+ if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
+ {
+ opt->source_netmask = daemon->add_subnet6->mask;
+ if (daemon->add_subnet6->addr_used)
+ {
+ sa_family = daemon->add_subnet6->addr.sa.sa_family;
+ addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
+ cacheable = 1;
+ }
+ else
+ addrp = &source->in6.sin6_addr;
+ }
+
+ if (source->sa.sa_family == AF_INET && daemon->add_subnet4)
+ {
+ opt->source_netmask = daemon->add_subnet4->mask;
+ if (daemon->add_subnet4->addr_used)
+ {
+ sa_family = daemon->add_subnet4->addr.sa.sa_family;
+ addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
+ cacheable = 1; /* Address is constant */
+ }
+ else
+ addrp = &source->in.sin_addr;
+ }
+
+ opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
+
+ if (addrp && opt->source_netmask != 0)
+ {
+ len = ((opt->source_netmask - 1) >> 3) + 1;
+ memcpy(opt->addr, addrp, len);
+ if (opt->source_netmask & 7)
+ opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
+ }
+ else
+ {
+ cacheable = 1; /* No address ever supplied. */
+ len = 0;
+ }
+
+ if (cacheablep)
+ *cacheablep = cacheable;
+
+ return len + 4;
+}
+
+static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+{
+ /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+
+ int len;
+ struct subnet_opt opt;
+
+ len = calc_subnet_opt(&opt, source, cacheable);
+ return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0);
+}
+
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
+{
+ /* Section 9.2, Check that subnet option in reply matches. */
+
+ int len, calc_len;
+ struct subnet_opt opt;
+ unsigned char *p;
+ int code, i, rdlen;
+
+ calc_len = calc_subnet_opt(&opt, peer, NULL);
+
+ if (!(p = skip_name(pseudoheader, header, plen, 10)))
+ return 1;
+
+ p += 8; /* skip UDP length and RCODE */
+
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 1; /* bad packet */
+
+ /* check if option there */
+ for (i = 0; i + 4 < rdlen; i += len + 4)
+ {
+ GETSHORT(code, p);
+ GETSHORT(len, p);
+ if (code == EDNS0_OPTION_CLIENT_SUBNET)
+ {
+ /* make sure this doesn't mismatch. */
+ opt.scope_netmask = p[3];
+ if (len != calc_len || memcmp(p, &opt, len) != 0)
+ return 0;
+ }
+ p += len;
+ }
+
+ return 1;
+}
+
+/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for
+ * detailed information on packet formating.
+ */
+#define UMBRELLA_VERSION 1
+#define UMBRELLA_TYPESZ 2
+
+#define UMBRELLA_ASSET 0x0004
+#define UMBRELLA_ASSETSZ sizeof(daemon->umbrella_asset)
+#define UMBRELLA_ORG 0x0008
+#define UMBRELLA_ORGSZ sizeof(daemon->umbrella_org)
+#define UMBRELLA_IPV4 0x0010
+#define UMBRELLA_IPV6 0x0020
+#define UMBRELLA_DEVICE 0x0040
+#define UMBRELLA_DEVICESZ sizeof(daemon->umbrella_device)
+
+struct umbrella_opt {
+ u8 magic[4];
+ u8 version;
+ u8 flags;
+ /* We have 4 possible fields since we'll never send both IPv4 and
+ * IPv6, so using the larger of the two to calculate max buffer size.
+ * Each field also has a type header. So the following accounts for
+ * the type headers and each field size to get a max buffer size.
+ */
+ u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
+};
+
+static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+{
+ *cacheable = 0;
+
+ struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {}};
+ u8 *u = &opt.fields[0];
+
+ if (daemon->umbrella_org) {
+ PUTSHORT(UMBRELLA_ORG, u);
+ PUTLONG(daemon->umbrella_org, u);
+ }
+
+ int family = source->sa.sa_family;
+ PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u);
+ int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
+ memcpy(u, get_addrp(source, family), size);
+ u += size;
+
+ if (option_bool(OPT_UMBRELLA_DEVID)) {
+ PUTSHORT(UMBRELLA_DEVICE, u);
+ memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ);
+ u += UMBRELLA_DEVICESZ;
+ }
+
+ if (daemon->umbrella_asset) {
+ PUTSHORT(UMBRELLA_ASSET, u);
+ PUTLONG(daemon->umbrella_asset, u);
+ }
+
+ int len = u - &opt.magic[0];
+ return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, len, 0, 1);
+}
+
+/* Set *check_subnet if we add a client subnet option, which needs to checked
+ in the reply. Set *cacheable to zero if we add an option which the answer
+ may depend on. */
+size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable)
+{
+ *check_subnet = 0;
+ *cacheable = 1;
+
+ if (option_bool(OPT_ADD_MAC))
+ plen = add_mac(header, plen, limit, source, now, cacheable);
+
+ if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX))
+ plen = add_dns_client(header, plen, limit, source, now, cacheable);
+
+ if (daemon->dns_client_id)
+ plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID,
+ (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
+
+ if (option_bool(OPT_UMBRELLA))
+ plen = add_umbrella_opt(header, plen, limit, source, cacheable);
+
+ if (option_bool(OPT_CLIENT_SUBNET))
+ {
+ plen = add_source_addr(header, plen, limit, source, cacheable);
+ *check_subnet = 1;
+ }
+
+ return plen;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c b/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c
new file mode 100755
index 0000000..f962f6f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c
@@ -0,0 +1,2654 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 *get_new_frec(time_t now, struct server *serv, int force);
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp);
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask);
+
+static unsigned short get_id(void);
+static void free_frec(struct frec *f);
+static void query_full(time_t now, char *domain);
+
+static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status);
+
+/* Send a UDP packet with its source address set as "source"
+ unless nowild is true, when we just send it with the kernel default */
+int send_from(int fd, int nowild, char *packet, size_t len,
+ union mysockaddr *to, union 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
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } 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->addr4;
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+ memcpy(CMSG_DATA(cmptr), &p, sizeof(p));
+ cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ cmptr->cmsg_level = IPPROTO_IP;
+ cmptr->cmsg_type = IP_PKTINFO;
+#elif defined(IP_SENDSRCADDR)
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
+ memcpy(CMSG_DATA(cmptr), &(source->addr4), sizeof(source->addr4));
+ cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+ cmptr->cmsg_level = IPPROTO_IP;
+ cmptr->cmsg_type = IP_SENDSRCADDR;
+#endif
+ }
+ else
+ {
+ struct in6_pktinfo p;
+ p.ipi6_ifindex = iface; /* Need iface for IPv6 to handle link-local addrs */
+ p.ipi6_addr = source->addr6;
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ memcpy(CMSG_DATA(cmptr), &p, sizeof(p));
+ cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ cmptr->cmsg_type = daemon->v6pktinfo;
+ cmptr->cmsg_level = IPPROTO_IPV6;
+ }
+ }
+
+ while (retry_send(sendmsg(fd, &msg, 0)));
+
+ if (errno != 0)
+ {
+#ifdef HAVE_LINUX_NETWORK
+ /* If interface is still in DAD, EINVAL results - ignore that. */
+ if (errno != EINVAL)
+ my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno));
+#endif
+ return 0;
+ }
+
+ return 1;
+}
+
+#ifdef HAVE_CONNTRACK
+static void set_outgoing_mark(struct frec *forward, int fd)
+{
+ /* Copy connection mark of incoming query to outgoing connection. */
+ unsigned int mark;
+ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
+ setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+}
+#endif
+
+static void log_query_mysockaddr(unsigned int flags, char *name, union mysockaddr *addr, char *arg)
+{
+ if (addr->sa.sa_family == AF_INET)
+ log_query(flags | F_IPV4, name, (union all_addr *)&addr->in.sin_addr, arg);
+ else
+ log_query(flags | F_IPV6, name, (union all_addr *)&addr->in6.sin6_addr, arg);
+}
+
+static void server_send(struct server *server, int fd,
+ const void *header, size_t plen, int flags)
+{
+ while (retry_send(sendto(fd, header, plen, flags,
+ &server->addr.sa,
+ sa_len(&server->addr))));
+}
+
+#ifdef HAVE_DNSSEC
+static void server_send_log(struct server *server, int fd,
+ const void *header, size_t plen, int dumpflags,
+ unsigned int logflags, char *name, char *arg)
+{
+#ifdef HAVE_DUMPFILE
+ dump_packet(dumpflags, (void *)header, (size_t)plen, NULL, &server->addr);
+#endif
+ log_query_mysockaddr(logflags, name, &server->addr, arg);
+ server_send(server, fd, header, plen, 0);
+}
+#endif
+
+static int domain_no_rebind(char *domain)
+{
+ struct server *serv;
+ int dlen = (int)strlen(domain);
+
+ for (serv = daemon->no_rebind; serv; serv = serv->next)
+ if (dlen >= serv->domain_len && strcmp(serv->domain, &domain[dlen - serv->domain_len]) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int forward_query(int udpfd, union mysockaddr *udpaddr,
+ union all_addr *dst_addr, unsigned int dst_iface,
+ struct dns_header *header, size_t plen, char *limit, time_t now,
+ struct frec *forward, int ad_reqd, int do_bit)
+{
+ unsigned int flags = 0;
+ unsigned int fwd_flags = 0;
+ int is_dnssec = forward && (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY));
+ struct server *master;
+ unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+ void *hash = hash_questions(header, plen, daemon->namebuff);
+ unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
+ int old_src = 0;
+ int first, last, start = 0;
+ int subnet, cacheable, forwarded = 0;
+ size_t edns0_len;
+ unsigned char *pheader;
+ int ede = EDE_UNSET;
+ (void)do_bit;
+
+ if (header->hb4 & HB4_CD)
+ fwd_flags |= FREC_CHECKING_DISABLED;
+ if (ad_reqd)
+ fwd_flags |= FREC_AD_QUESTION;
+ if (oph)
+ fwd_flags |= FREC_HAS_PHEADER;
+#ifdef HAVE_DNSSEC
+ if (do_bit)
+ fwd_flags |= FREC_DO_QUESTION;
+#endif
+
+ /* Check for retry on existing query.
+ FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
+ ensures that no frec created for internal DNSSEC query can be returned here.
+
+ Similarly FREC_NO_CACHE is never set in flags, so a query which is
+ contigent on a particular source address EDNS0 option will never be matched. */
+ if (forward)
+ old_src = 1;
+ else if ((forward = lookup_frec_by_query(hash, fwd_flags,
+ FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
+ FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
+ {
+ struct frec_src *src;
+
+ for (src = &forward->frec_src; src; src = src->next)
+ if (src->orig_id == ntohs(header->id) &&
+ sockaddr_isequal(&src->source, udpaddr))
+ break;
+
+ if (src)
+ old_src = 1;
+ else
+ {
+ /* Existing query, but from new source, just add this
+ client to the list that will get the reply.*/
+
+ /* Note whine_malloc() zeros memory. */
+ if (!daemon->free_frec_src &&
+ daemon->frec_src_count < daemon->ftabsize &&
+ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+ {
+ daemon->frec_src_count++;
+ daemon->free_frec_src->next = NULL;
+ }
+
+ /* If we've been spammed with many duplicates, return REFUSED. */
+ if (!daemon->free_frec_src)
+ {
+ query_full(now, NULL);
+ goto reply;
+ }
+
+ src = daemon->free_frec_src;
+ daemon->free_frec_src = src->next;
+ src->next = forward->frec_src.next;
+ forward->frec_src.next = src;
+ src->orig_id = ntohs(header->id);
+ src->source = *udpaddr;
+ src->dest = *dst_addr;
+ src->log_id = daemon->log_id;
+ src->iface = dst_iface;
+ src->fd = udpfd;
+
+ /* closely spaced identical queries cannot be a try and a retry, so
+ it's safe to wait for the reply from the first without
+ forwarding the second. */
+ if (difftime(now, forward->time) < 2)
+ return 0;
+ }
+ }
+
+ /* new query */
+ if (!forward)
+ {
+ if (lookup_domain(daemon->namebuff, gotname, &first, &last))
+ flags = is_local_answer(now, first, daemon->namebuff);
+ else
+ {
+ /* no available server. */
+ ede = EDE_NOT_READY;
+ flags = 0;
+ }
+
+ /* don't forward A or AAAA queries for simple names, except the empty name */
+ if (!flags &&
+ option_bool(OPT_NODOTS_LOCAL) &&
+ (gotname & (F_IPV4 | F_IPV6)) &&
+ !strchr(daemon->namebuff, '.') &&
+ strlen(daemon->namebuff) != 0)
+ flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN;
+
+ /* Configured answer. */
+ if (flags || ede == EDE_NOT_READY)
+ goto reply;
+
+ master = daemon->serverarray[first];
+
+ if (!(forward = get_new_frec(now, master, 0)))
+ goto reply;
+ /* table full - flags == 0, return REFUSED */
+
+ forward->frec_src.source = *udpaddr;
+ forward->frec_src.orig_id = ntohs(header->id);
+ forward->frec_src.dest = *dst_addr;
+ forward->frec_src.iface = dst_iface;
+ forward->frec_src.next = NULL;
+ forward->frec_src.fd = udpfd;
+ forward->new_id = get_id();
+ memcpy(forward->hash, hash, HASH_SIZE);
+ forward->forwardall = 0;
+ forward->flags = fwd_flags;
+ if (domain_no_rebind(daemon->namebuff))
+ forward->flags |= FREC_NOREBIND;
+ if (header->hb4 & HB4_CD)
+ forward->flags |= FREC_CHECKING_DISABLED;
+ if (ad_reqd)
+ forward->flags |= FREC_AD_QUESTION;
+#ifdef HAVE_DNSSEC
+ forward->work_counter = DNSSEC_WORK;
+ if (do_bit)
+ forward->flags |= FREC_DO_QUESTION;
+#endif
+
+ start = first;
+
+ if (option_bool(OPT_ALL_SERVERS))
+ forward->forwardall = 1;
+
+ if (!option_bool(OPT_ORDER))
+ {
+ if (master->forwardcount++ > FORWARD_TEST ||
+ difftime(now, master->forwardtime) > FORWARD_TIME ||
+ master->last_server == -1)
+ {
+ master->forwardtime = now;
+ master->forwardcount = 0;
+ forward->forwardall = 1;
+ }
+ else
+ start = master->last_server;
+ }
+ }
+ else
+ {
+ /* retry on existing query, from original source. Send to all available servers */
+#ifdef HAVE_DNSSEC
+ /* If we've already got an answer to this query, but we're awaiting keys for validation,
+ there's no point retrying the query, retry the key query instead...... */
+ if (forward->blocking_query)
+ {
+ int is_sign;
+ unsigned char *pheader;
+
+ while (forward->blocking_query)
+ forward = forward->blocking_query;
+
+ blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+ plen = forward->stash_len;
+ /* get query for logging. */
+ extract_request(header, plen, daemon->namebuff, NULL);
+
+ if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
+ PUTSHORT(SAFE_PKTSZ, pheader);
+
+ /* Find suitable servers: should never fail. */
+ if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last))
+ return 0;
+
+ is_dnssec = 1;
+ forward->forwardall = 1;
+ }
+ else
+#endif
+ {
+ /* retry on existing query, from original source. Send to all available servers */
+ forward->sentto->failed_queries++;
+
+ if (!filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last))
+ goto reply;
+
+ master = daemon->serverarray[first];
+
+ /* Forward to all available servers on retry of query from same host. */
+ if (!option_bool(OPT_ORDER) && old_src)
+ forward->forwardall = 1;
+ else
+ {
+ start = forward->sentto->arrayposn;
+
+ if (option_bool(OPT_ORDER))
+ {
+ /* In strict order mode, there must be a server later in the list
+ left to send to, otherwise without the forwardall mechanism,
+ code further on will cycle around the list forwever if they
+ all return REFUSED. If at the last, give up. */
+ if (++start == last)
+ goto reply;
+ }
+ }
+ }
+
+ /* If we didn't get an answer advertising a maximal packet in EDNS,
+ fall back to 1280, which should work everywhere on IPv6.
+ If that generates an answer, it will become the new default
+ for this server */
+ forward->flags |= FREC_TEST_PKTSZ;
+ }
+
+ /* If a query is retried, use the log_id for the retry when logging the answer. */
+ forward->frec_src.log_id = daemon->log_id;
+
+ /* We may be resending a DNSSEC query here, for which the below processing is not necessary. */
+ if (!is_dnssec)
+ {
+ header->id = htons(forward->new_id);
+
+ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
+
+ if (subnet)
+ forward->flags |= FREC_HAS_SUBNET;
+
+ if (!cacheable)
+ forward->flags |= FREC_NO_CACHE;
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
+ {
+ plen = add_do_bit(header, plen, ((unsigned char *) header) + PACKETSZ);
+
+ /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+ this allows it to select auth servers when one is returning bad data. */
+ if (option_bool(OPT_DNSSEC_DEBUG))
+ header->hb4 |= HB4_CD;
+
+ }
+#endif
+
+ if (find_pseudoheader(header, plen, &edns0_len, &pheader, NULL, NULL))
+ {
+ /* If there wasn't a PH before, and there is now, we added it. */
+ if (!oph)
+ forward->flags |= FREC_ADDED_PHEADER;
+
+ /* If we're sending an EDNS0 with any options, we can't recreate the query from a reply. */
+ if (edns0_len > 11)
+ forward->flags |= FREC_HAS_EXTRADATA;
+
+ /* Reduce udp size on retransmits. */
+ if (forward->flags & FREC_TEST_PKTSZ)
+ PUTSHORT(SAFE_PKTSZ, pheader);
+ }
+ }
+
+ if (forward->forwardall)
+ start = first;
+
+ forwarded = 0;
+
+ /* 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) */
+
+ while (1)
+ {
+ int fd;
+ struct server *srv = daemon->serverarray[start];
+
+ if ((fd = allocate_rfd(&forward->rfds, srv)) != -1)
+ {
+
+#ifdef HAVE_CONNTRACK
+ /* Copy connection mark of incoming query to outgoing connection. */
+ if (option_bool(OPT_CONNTRACK))
+ set_outgoing_mark(forward, fd);
+#endif
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER))
+ {
+ /* Difficult one here. If our client didn't send EDNS0, we will have set the UDP
+ packet size to 512. But that won't provide space for the RRSIGS in many cases.
+ The RRSIGS will be stripped out before the answer goes back, so the packet should
+ shrink again. So, if we added a do-bit, bump the udp packet size to the value
+ known to be OK for this server. We check returned size after stripping and set
+ the truncated bit if it's still too big. */
+ unsigned char *pheader;
+ int is_sign;
+ if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
+ PUTSHORT(srv->edns_pktsz, pheader);
+ }
+#endif
+
+ if (retry_send(sendto(fd, (char *)header, plen, 0,
+ &srv->addr.sa,
+ sa_len(&srv->addr))))
+ continue;
+
+ if (errno == 0)
+ {
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &srv->addr);
+#endif
+
+ /* Keep info in case we want to re-send this packet */
+ daemon->srv_save = srv;
+ daemon->packet_len = plen;
+ daemon->fd_save = fd;
+
+ if (!(forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
+ {
+ if (!gotname)
+ strcpy(daemon->namebuff, "query");
+ log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff,
+ &srv->addr, NULL);
+ }
+#ifdef HAVE_DNSSEC
+ else
+ log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, daemon->namebuff, &srv->addr,
+ querystr("dnssec-retry", (forward->flags & FREC_DNSKEY_QUERY) ? T_DNSKEY : T_DS));
+#endif
+
+ srv->queries++;
+ forwarded = 1;
+ forward->sentto = srv;
+ if (!forward->forwardall)
+ break;
+ forward->forwardall++;
+ }
+ }
+
+ if (++start == last)
+ break;
+ }
+
+ if (forwarded || is_dnssec)
+ return 1;
+
+ /* could not send on, prepare to return */
+ header->id = htons(forward->frec_src.orig_id);
+ free_frec(forward); /* cancel */
+ ede = EDE_NETERR;
+
+ reply:
+ if (udpfd != -1)
+ {
+ if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last, ede)))
+ return 0;
+
+ if (oph)
+ {
+ u16 swap = htons((u16)ede);
+
+ if (ede != EDE_UNSET)
+ plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+ else
+ plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+ }
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+ if (option_bool(OPT_CMARK_ALST_EN))
+ {
+ unsigned int mark;
+ int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
+ if (have_mark && ((u32)mark & daemon->allowlist_mask))
+ report_addresses(header, plen, mark);
+ }
+#endif
+
+ send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (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 no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader,
+ int check_subnet, union mysockaddr *query_source, unsigned char *limit, int ede)
+{
+ unsigned char *pheader, *sizep;
+ char **sets = 0;
+ int munged = 0, is_sign;
+ unsigned int rcode = RCODE(header);
+ size_t plen;
+
+ (void)ad_reqd;
+ (void)do_bit;
+ (void)bogusanswer;
+
+#ifdef HAVE_IPSET
+ if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
+ {
+ /* Similar algorithm to search_servers. */
+ struct ipsets *ipset_pos;
+ unsigned int namelen = strlen(daemon->namebuff);
+ unsigned int matchlen = 0;
+ for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next)
+ {
+ unsigned int domainlen = strlen(ipset_pos->domain);
+ char *matchstart = daemon->namebuff + namelen - domainlen;
+ if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
+ (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
+ domainlen >= matchlen)
+ {
+ matchlen = domainlen;
+ sets = ipset_pos->sets;
+ }
+ }
+ }
+#endif
+
+ if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
+ {
+ /* Get extended RCODE. */
+ rcode |= sizep[2] << 4;
+
+ if (check_subnet && !check_source(header, plen, pheader, query_source))
+ {
+ my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
+ return 0;
+ }
+
+ if (!is_sign)
+ {
+ if (added_pheader)
+ {
+ /* client didn't send EDNS0, we added one, strip it off before returning answer. */
+ n = rrfilter(header, n, 0);
+ pheader = NULL;
+ }
+ else
+ {
+ /* 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. */
+ unsigned short udpsz;
+ GETSHORT(udpsz, sizep);
+ if (udpsz > daemon->edns_pktsz)
+ {
+ sizep -= 2;
+ PUTSHORT(daemon->edns_pktsz, sizep);
+ }
+
+#ifdef HAVE_DNSSEC
+ /* If the client didn't set the do bit, but we did, reset it. */
+ if (option_bool(OPT_DNSSEC_VALID) && !do_bit)
+ {
+ unsigned short flags;
+ sizep += 2; /* skip RCODE */
+ GETSHORT(flags, sizep);
+ flags &= ~0x8000;
+ sizep -= 2;
+ PUTSHORT(flags, sizep);
+ }
+#endif
+ }
+ }
+ }
+
+ /* RFC 4035 sect 4.6 para 3 */
+ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
+ header->hb4 &= ~HB4_AD;
+
+ header->hb4 |= HB4_RA; /* recursion if available */
+
+ if (OPCODE(header) != QUERY)
+ return resize_packet(header, n, pheader, plen);
+
+ if (rcode != NOERROR && rcode != NXDOMAIN)
+ {
+ union all_addr a;
+ a.log.rcode = rcode;
+ a.log.ede = ede;
+ log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL);
+
+ return resize_packet(header, n, pheader, plen);
+ }
+
+ /* Complain loudly if the upstream server is non-recursive. */
+ if (!(header->hb4 & HB4_RA) && rcode == NOERROR &&
+ server && !(server->flags & SERV_WARNED_RECURSIVE))
+ {
+ (void)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 != NXDOMAIN &&
+ check_for_bogus_wildcard(header, n, daemon->namebuff, now))
+ {
+ munged = 1;
+ SET_RCODE(header, NXDOMAIN);
+ header->hb3 &= ~HB3_AA;
+ cache_secure = 0;
+ ede = EDE_BLOCKED;
+ }
+ else
+ {
+ int doctored = 0;
+
+ if (rcode == NXDOMAIN &&
+ extract_request(header, n, daemon->namebuff, NULL))
+ {
+ if (check_for_local_domain(daemon->namebuff, now) ||
+ lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))
+ {
+ /* 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);
+ cache_secure = 0;
+ }
+ }
+
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
+ {
+ my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
+ munged = 1;
+ cache_secure = 0;
+ ede = EDE_BLOCKED;
+ }
+
+ if (doctored)
+ cache_secure = 0;
+ }
+
+#ifdef HAVE_DNSSEC
+ if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG))
+ {
+ /* Bogus reply, turn into SERVFAIL */
+ SET_RCODE(header, SERVFAIL);
+ munged = 1;
+ }
+
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ header->hb4 &= ~HB4_AD;
+
+ if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
+ header->hb4 |= HB4_AD;
+
+ /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+ if (!do_bit)
+ n = rrfilter(header, n, 1);
+ }
+#endif
+
+ /* 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);
+ header->hb3 &= ~HB3_TC;
+ }
+
+ /* 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. */
+ n = resize_packet(header, n, pheader, plen);
+
+ if (pheader && ede != EDE_UNSET)
+ {
+ u16 swap = htons((u16)ede);
+ n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1);
+ }
+
+ return n;
+}
+
+#ifdef HAVE_DNSSEC
+static void dnssec_validate(struct frec *forward, struct dns_header *header,
+ ssize_t plen, int status, time_t now)
+{
+ daemon->log_display_id = forward->frec_src.log_id;
+
+ /* We've had a reply already, which we're validating. Ignore this duplicate */
+ if (forward->blocking_query)
+ return;
+
+ /* Truncated answer can't be validated.
+ If this is an answer to a DNSSEC-generated query, we still
+ need to get the client to retry over TCP, so return
+ an answer with the TC bit set, even if the actual answer fits.
+ */
+ if (header->hb3 & HB3_TC)
+ status = STAT_TRUNCATED;
+
+ /* If all replies to a query are REFUSED, give up. */
+ if (RCODE(header) == REFUSED)
+ status = STAT_ABANDONED;
+
+ /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
+ would invite infinite loops, since the answers to DNSKEY and DS queries
+ will not be cached, so they'll be repeated. */
+ if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
+ {
+ if (forward->flags & FREC_DNSKEY_QUERY)
+ status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
+ else if (forward->flags & FREC_DS_QUERY)
+ status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
+ else
+ status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
+ !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
+ NULL, NULL, NULL);
+#ifdef HAVE_DUMPFILE
+ if (STAT_ISEQUAL(status, STAT_BOGUS))
+ dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
+ header, (size_t)plen, &forward->sentto->addr, NULL);
+#endif
+ }
+
+ /* Can't validate, as we're missing key data. Put this
+ answer aside, whilst we get that. */
+ if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
+ {
+ struct frec *new = NULL;
+ int serverind;
+ struct blockdata *stash;
+
+ /* Now save reply pending receipt of key data */
+ if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
+ (stash = blockdata_alloc((char *)header, plen)))
+ {
+ struct server *server = daemon->serverarray[serverind];
+ struct frec *orig;
+ unsigned int flags;
+ void *hash;
+ size_t nn;
+
+ /* validate routines leave name of required record in daemon->keyname */
+ nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz,
+ daemon->keyname, forward->class,
+ STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
+
+ flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
+ hash = hash_questions(header, nn, daemon->namebuff);
+
+ if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
+ {
+ forward->next_dependent = new->dependent;
+ new->dependent = forward;
+ /* Make consistent, only replace query copy with unvalidated answer
+ when we set ->blocking_query. */
+ if (forward->stash)
+ blockdata_free(forward->stash);
+ forward->blocking_query = new;
+ forward->stash_len = plen;
+ forward->stash = stash;
+ return;
+ }
+
+ /* Find the original query that started it all.... */
+ for (orig = forward; orig->dependent; orig = orig->dependent);
+
+ /* Make sure we don't expire and free the orig frec during the
+ allocation of a new one: third arg of get_new_frec() does that. */
+ if (--orig->work_counter == 0 || !(new = get_new_frec(now, server, 1)))
+ blockdata_free(stash); /* don't leak this on failure. */
+ else
+ {
+ int fd;
+ struct frec *next = new->next;
+
+ *new = *forward; /* copy everything, then overwrite */
+ new->next = next;
+ new->blocking_query = NULL;
+
+ new->frec_src.log_id = daemon->log_display_id = ++daemon->log_id;
+ new->sentto = server;
+ new->rfds = NULL;
+ new->frec_src.next = NULL;
+ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
+ new->flags |= flags;
+ new->forwardall = 0;
+
+ forward->next_dependent = NULL;
+ new->dependent = forward; /* to find query awaiting new one. */
+
+ /* Make consistent, only replace query copy with unvalidated answer
+ when we set ->blocking_query. */
+ forward->blocking_query = new;
+ if (forward->stash)
+ blockdata_free(forward->stash);
+ forward->stash_len = plen;
+ forward->stash = stash;
+
+ memcpy(new->hash, hash, HASH_SIZE);
+ new->new_id = get_id();
+ header->id = htons(new->new_id);
+ /* Save query for retransmission */
+ new->stash = blockdata_alloc((char *)header, nn);
+ new->stash_len = nn;
+
+ /* Don't resend this. */
+ daemon->srv_save = NULL;
+
+ if ((fd = allocate_rfd(&new->rfds, server)) != -1)
+ {
+#ifdef HAVE_CONNTRACK
+ if (option_bool(OPT_CONNTRACK))
+ set_outgoing_mark(orig, fd);
+#endif
+ server_send_log(server, fd, header, nn, DUMP_SEC_QUERY,
+ F_NOEXTRA | F_DNSSEC, daemon->keyname,
+ querystr("dnssec-query", STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
+ server->queries++;
+ }
+
+ return;
+ }
+ }
+
+ /* sending DNSSEC query failed. */
+ status = STAT_ABANDONED;
+ }
+
+ /* Validated original answer, all done. */
+ if (!forward->dependent)
+ return_reply(now, forward, header, plen, status);
+ else
+ {
+ /* validated subsidiary query/queries, (and cached result)
+ pop that and return to the previous query/queries we were working on. */
+ struct frec *prev, *nxt = forward->dependent;
+
+ free_frec(forward);
+
+ while ((prev = nxt))
+ {
+ /* ->next_dependent will have changed after return from recursive call below. */
+ nxt = prev->next_dependent;
+ prev->blocking_query = NULL; /* already gone */
+ blockdata_retrieve(prev->stash, prev->stash_len, (void *)header);
+ dnssec_validate(prev, header, prev->stash_len, status, now);
+ }
+ }
+}
+#endif
+
+/* sets new last_server */
+void reply_query(int fd, 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->packet_buff_sz, 0, &serveraddr.sa, &addrlen);
+ struct server *server;
+ void *hash;
+ int first, last, c;
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+
+ /* Determine the address of the server replying so that we can mark that as good */
+ if (serveraddr.sa.sa_family == AF_INET6)
+ serveraddr.in6.sin6_flowinfo = 0;
+
+ header = (struct dns_header *)daemon->packet;
+
+ if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR))
+ return;
+
+ hash = hash_questions(header, n, daemon->namebuff);
+
+ if (!(forward = lookup_frec(ntohs(header->id), fd, hash, &first, &last)))
+ return;
+
+ /* spoof check: answer must come from known server, also
+ we may have sent the same query to multiple servers from
+ the same local socket, and would like to know which one has answered. */
+ for (c = first; c != last; c++)
+ if (sockaddr_isequal(&daemon->serverarray[c]->addr, &serveraddr))
+ break;
+
+ if (c == last)
+ return;
+
+ server = daemon->serverarray[c];
+
+ if (RCODE(header) != REFUSED)
+ daemon->serverarray[first]->last_server = c;
+ else if (daemon->serverarray[first]->last_server == c)
+ daemon->serverarray[first]->last_server = -1;
+
+ /* If sufficient time has elapsed, try and expand UDP buffer size again. */
+ if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME)
+ server->edns_pktsz = daemon->edns_pktsz;
+
+#ifdef HAVE_DUMPFILE
+ dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_REPLY : DUMP_UP_REPLY,
+ (void *)header, n, &serveraddr, NULL);
+#endif
+
+ /* log_query gets called indirectly all over the place, so
+ pass these in global variables - sorry. */
+ daemon->log_display_id = forward->frec_src.log_id;
+ daemon->log_source_addr = &forward->frec_src.source;
+
+ if (daemon->ignore_addr && RCODE(header) == NOERROR &&
+ check_for_ignored_address(header, n))
+ return;
+
+ /* Note: if we send extra options in the EDNS0 header, we can't recreate
+ the query from the reply. */
+ if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) &&
+ forward->forwardall == 0 &&
+ !(forward->flags & FREC_HAS_EXTRADATA))
+ /* for broken servers, attempt to send to another one. */
+ {
+ unsigned char *pheader, *udpsz;
+ unsigned short udp_size = PACKETSZ; /* default if no EDNS0 */
+ size_t plen;
+ int is_sign;
+ size_t nn = 0;
+
+#ifdef HAVE_DNSSEC
+ /* DNSSEC queries have a copy of the original query stashed.
+ The query MAY have got a good answer, and be awaiting
+ the results of further queries, in which case
+ The Stash contains something else and we don't need to retry anyway. */
+ if ((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && !forward->blocking_query)
+ {
+ blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+ nn = forward->stash_len;
+ udp_size = daemon->edns_pktsz;
+ }
+ else
+#endif
+ {
+ /* recreate query from reply */
+ if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL)))
+ GETSHORT(udp_size, udpsz);
+
+ /* If the client provides an EDNS0 UDP size, use that to limit our reply.
+ (bounded by the maximum configured). If no EDNS0, then it
+ defaults to 512 */
+ if (udp_size > daemon->edns_pktsz)
+ udp_size = daemon->edns_pktsz;
+ else if (udp_size < PACKETSZ)
+ udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
+
+ if (!is_sign &&
+ (nn = resize_packet(header, (size_t)n, pheader, plen)) &&
+ (forward->flags & FREC_DO_QUESTION))
+ add_do_bit(header, nn, (unsigned char *)pheader + plen);
+
+ header->ancount = htons(0);
+ header->nscount = htons(0);
+ header->arcount = htons(0);
+ header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC);
+ header->hb4 &= ~(HB4_RA | HB4_RCODE | HB4_CD | HB4_AD);
+ if (forward->flags & FREC_CHECKING_DISABLED)
+ header->hb4 |= HB4_CD;
+ if (forward->flags & FREC_AD_QUESTION)
+ header->hb4 |= HB4_AD;
+ }
+
+ if (nn)
+ {
+ forward_query(-1, NULL, NULL, 0, header, nn, ((char *) header) + udp_size, now, forward,
+ forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION);
+ return;
+ }
+ }
+
+ /* 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 */
+
+ /* decrement count of replies recieved if we sent to more than one server. */
+ if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED)
+ return;
+
+ /* We tried resending to this server with a smaller maximum size and got an answer.
+ Make that permanent. To avoid reduxing the packet size for a single dropped packet,
+ only do this when we get a truncated answer, or one larger than the safe size. */
+ if (server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) &&
+ ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ))
+ {
+ server->edns_pktsz = SAFE_PKTSZ;
+ server->pktsz_reduced = now;
+ (void)prettyprint_addr(&server->addr, daemon->addrbuff);
+ my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ);
+ }
+
+ forward->sentto = server;
+
+#ifdef HAVE_DNSSEC
+ if ((forward->sentto->flags & SERV_DO_DNSSEC) &&
+ option_bool(OPT_DNSSEC_VALID) &&
+ !(forward->flags & FREC_CHECKING_DISABLED))
+ dnssec_validate(forward, header, n, STAT_OK, now);
+ else
+#endif
+ return_reply(now, forward, header, n, STAT_OK);
+}
+
+static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status)
+{
+ int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+ size_t nn;
+ int ede = EDE_UNSET;
+
+ (void)status;
+
+ daemon->log_display_id = forward->frec_src.log_id;
+ daemon->log_source_addr = &forward->frec_src.source;
+
+ /* Don't cache replies where DNSSEC validation was turned off, either
+ the upstream server told us so, or the original query specified it. */
+ if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
+ no_cache_dnssec = 1;
+
+#ifdef HAVE_DNSSEC
+ if (!STAT_ISEQUAL(status, STAT_OK))
+ {
+ /* status is STAT_OK when validation not turned on. */
+ no_cache_dnssec = 0;
+
+ if (STAT_ISEQUAL(status, STAT_TRUNCATED))
+ header->hb3 |= HB3_TC;
+ else
+ {
+ char *result, *domain = "result";
+ union all_addr a;
+
+ a.log.ede = ede = errflags_to_ede(status);
+
+ if (STAT_ISEQUAL(status, STAT_ABANDONED))
+ {
+ result = "ABANDONED";
+ status = STAT_BOGUS;
+ }
+ else
+ result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
+
+ if (STAT_ISEQUAL(status, STAT_SECURE))
+ cache_secure = 1;
+ else if (STAT_ISEQUAL(status, STAT_BOGUS))
+ {
+ no_cache_dnssec = 1;
+ bogusanswer = 1;
+
+ if (extract_request(header, n, daemon->namebuff, NULL))
+ domain = daemon->namebuff;
+ }
+
+ log_query(F_SECSTAT, domain, &a, result);
+ }
+ }
+#endif
+
+ if (option_bool(OPT_NO_REBIND))
+ check_rebind = !(forward->flags & FREC_NOREBIND);
+
+ /* restore CD bit to the value in the query */
+ if (forward->flags & FREC_CHECKING_DISABLED)
+ header->hb4 |= HB4_CD;
+ else
+ header->hb4 &= ~HB4_CD;
+
+ /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+ since the cache is ignorant of such things. */
+ if (forward->flags & FREC_NO_CACHE)
+ no_cache_dnssec = 1;
+
+ if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
+ forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
+ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source,
+ ((unsigned char *)header) + daemon->edns_pktsz, ede)))
+ {
+ struct frec_src *src;
+
+ header->id = htons(forward->frec_src.orig_id);
+#ifdef HAVE_DNSSEC
+ /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
+ greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0
+ header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */
+ if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ))
+ {
+ header->ancount = htons(0);
+ header->nscount = htons(0);
+ header->arcount = htons(0);
+ header->hb3 |= HB3_TC;
+ nn = resize_packet(header, nn, NULL, 0);
+ }
+#endif
+
+ for (src = &forward->frec_src; src; src = src->next)
+ {
+ header->id = htons(src->orig_id);
+
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
+#endif
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+ if (option_bool(OPT_CMARK_ALST_EN))
+ {
+ unsigned int mark;
+ int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
+ if (have_mark && ((u32)mark & daemon->allowlist_mask))
+ report_addresses(header, nn, mark);
+ }
+#endif
+
+ send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
+ &src->source, &src->dest, src->iface);
+
+ if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
+ {
+ daemon->log_display_id = src->log_id;
+ daemon->log_source_addr = &src->source;
+ log_query(F_UPSTREAM, "query", NULL, "duplicate");
+ }
+ }
+ }
+
+ free_frec(forward); /* cancel */
+}
+
+
+#ifdef HAVE_CONNTRACK
+static int is_query_allowed_for_mark(u32 mark, const char *name)
+{
+ int is_allowable_name, did_validate_name = 0;
+ struct allowlist *allowlists;
+ char **patterns_pos;
+
+ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+ if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
+ for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++)
+ {
+ if (!strcmp(*patterns_pos, "*"))
+ return 1;
+ if (!did_validate_name)
+ {
+ is_allowable_name = name ? is_valid_dns_name(name) : 0;
+ did_validate_name = 1;
+ }
+ if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos))
+ return 1;
+ }
+ return 0;
+}
+
+static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name)
+{
+ unsigned char *p;
+ (void)name;
+ (void)mark;
+
+#ifdef HAVE_UBUS
+ if (name)
+ ubus_event_bcast_connmark_allowlist_refused(mark, name);
+#endif
+
+ setup_reply(header, /* flags: */ 0, EDE_BLOCKED);
+
+ if (!(p = skip_questions(header, qlen)))
+ return 0;
+ return p - (unsigned char *)header;
+}
+#endif
+
+void receive_query(struct listener *listen, time_t now)
+{
+ struct dns_header *header = (struct dns_header *)daemon->packet;
+ union mysockaddr source_addr;
+ unsigned char *pheader;
+ unsigned short type, udp_size = PACKETSZ; /* default if no EDNS0 */
+ union all_addr dst_addr;
+ struct in_addr netmask, dst_addr_4;
+ size_t m,plen;
+ ssize_t n,num;
+ int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0;
+#ifdef HAVE_CONNTRACK
+ unsigned int mark = 0;
+ int have_mark = 0;
+ int is_single_query = 0, allowed = 1;
+#endif
+#ifdef HAVE_AUTH
+ int local_auth = 0;
+#endif
+ struct iovec iov[1];
+ struct msghdr msg;
+ struct cmsghdr *cmptr;
+ union {
+ struct cmsghdr align; /* this ensures alignment */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#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;
+ int family = listen->addr.sa.sa_family;
+ /* Can always get recvd interface for IPv6 */
+ int check_dst = !option_bool(OPT_NOWILD) || family == AF_INET6;
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+
+ dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0;
+ netmask.s_addr = 0;
+
+ if (option_bool(OPT_NOWILD) && listen->iface)
+ {
+ auth_dns = listen->iface->dns_auth;
+
+ if (family == AF_INET)
+ {
+ dst_addr_4 = dst_addr.addr4 = listen->iface->addr.in.sin_addr;
+ netmask = listen->iface->netmask;
+ }
+ }
+
+ 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;
+ num=n;
+ /* Clear buffer beyond request to avoid risk of
+ information disclosure. */
+ memset(daemon->packet + n, 0, daemon->edns_pktsz - n);
+
+ source_addr.sa.sa_family = family;
+
+ if (family == AF_INET)
+ {
+ /* Source-port == 0 is an error, we can't send back to that.
+ http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */
+ if (source_addr.in.sin_port == 0)
+ return;
+ }
+ else
+ {
+ /* Source-port == 0 is an error, we can't send back to that. */
+ if (source_addr.in6.sin6_port == 0)
+ return;
+ source_addr.in6.sin6_flowinfo = 0;
+ }
+
+ /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *addr;
+
+ if (family == AF_INET6)
+ {
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ if ((addr->flags & ADDRLIST_IPV6) &&
+ is_same_net6(&addr->addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen))
+ break;
+ }
+ else
+ {
+ struct in_addr netmask;
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ {
+ netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen));
+ if (!(addr->flags & ADDRLIST_IPV6) &&
+ is_same_net(addr->addr.addr4, source_addr.in.sin_addr, netmask))
+ break;
+ }
+ }
+ if (!addr)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+ warned = 1;
+ }
+ return;
+ }
+ }
+
+ if (check_dst)
+ {
+ struct ifreq ifr;
+
+ if (msg.msg_controllen < sizeof(struct cmsghdr))
+ return;
+
+#if defined(HAVE_LINUX_NETWORK)
+ if (family == AF_INET)
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+ {
+ union {
+ unsigned char *c;
+ struct in_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+ dst_addr_4 = dst_addr.addr4 = p.p->ipi_spec_dst;
+ if_index = p.p->ipi_ifindex;
+ }
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+ if (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.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
+
+ if (family == AF_INET6)
+ {
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
+ {
+ union {
+ unsigned char *c;
+ struct in6_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+
+ dst_addr.addr6 = p.p->ipi6_addr;
+ if_index = p.p->ipi6_ifindex;
+ }
+ }
+
+ /* enforce available interface configuration */
+
+ if (!indextoname(listen->fd, if_index, ifr.ifr_name))
+ return;
+
+ if (!iface_check(family, &dst_addr, ifr.ifr_name, &auth_dns))
+ {
+ if (!option_bool(OPT_CLEVERBIND))
+ enumerate_interfaces(0);
+ if (!loopback_exception(listen->fd, family, &dst_addr, ifr.ifr_name) &&
+ !label_exception(if_index, family, &dst_addr))
+ return;
+ }
+
+ if (family == AF_INET && option_bool(OPT_LOCALISE))
+ {
+ struct irec *iface;
+
+ /* get the netmask of the interface which has the address we were sent to.
+ This is no necessarily the interface we arrived on. */
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->addr.sa.sa_family == AF_INET &&
+ iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr)
+ break;
+
+ /* interface may be new */
+ if (!iface && !option_bool(OPT_CLEVERBIND))
+ enumerate_interfaces(0);
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->addr.sa.sa_family == AF_INET &&
+ iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr)
+ break;
+
+ /* If we failed, abandon localisation */
+ if (iface)
+ netmask = iface->netmask;
+ else
+ dst_addr_4.s_addr = 0;
+ }
+ }
+
+ /* log_query gets called indirectly all over the place, so
+ pass these in global variables - sorry. */
+ daemon->log_display_id = ++daemon->log_id;
+ daemon->log_source_addr = &source_addr;
+
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
+#endif
+
+#ifdef HAVE_CONNTRACK
+ if (option_bool(OPT_CMARK_ALST_EN))
+ have_mark = get_incoming_mark(&source_addr, &dst_addr, /* istcp: */ 0, &mark);
+#endif
+
+ if (extract_request(header, (size_t)n, daemon->namebuff, &type))
+ {
+#ifdef HAVE_AUTH
+ struct auth_zone *zone;
+#endif
+ char *types = querystr(auth_dns ? "auth" : "query", type);
+
+ log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
+ &source_addr, types);
+
+#ifdef HAVE_CONNTRACK
+ is_single_query = 1;
+#endif
+
+#ifdef HAVE_AUTH
+ /* find queries for zones we're authoritative for, and answer them directly */
+ if (!auth_dns && !option_bool(OPT_LOCALISE))
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ if (in_zone(zone, daemon->namebuff, NULL))
+ {
+ auth_dns = 1;
+ local_auth = 1;
+ break;
+ }
+#endif
+
+#ifdef HAVE_LOOP
+ /* Check for forwarding loop */
+ if (detect_loop(daemon->namebuff, type))
+ return;
+#endif
+ }
+
+ if (find_pseudoheader(header, (size_t)n, NULL, &pheader, NULL, NULL))
+ {
+ unsigned short flags;
+
+ have_pseudoheader = 1;
+ GETSHORT(udp_size, pheader);
+ pheader += 2; /* ext_rcode */
+ GETSHORT(flags, pheader);
+
+ if (flags & 0x8000)
+ do_bit = 1;/* do bit */
+
+ /* If the client provides an EDNS0 UDP size, use that to limit our reply.
+ (bounded by the maximum configured). If no EDNS0, then it
+ defaults to 512 */
+ if (udp_size > daemon->edns_pktsz)
+ udp_size = daemon->edns_pktsz;
+ else if (udp_size < PACKETSZ)
+ udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
+ }
+#if 1
+ //printf("@!@dnsmasq %s len=%d\n",daemon->namebuff,strlen(daemon->namebuff));
+ if(strlen(daemon->namebuff) == 0)//nessus DNS Server Spoofed Request Amplification DDoS
+ return;
+ {
+ char cfg_buf[32];
+ memset(cfg_buf, 0x00, sizeof(cfg_buf));
+ sc_cfg_get("lan_domain_Enabled", cfg_buf, sizeof(cfg_buf));
+ if(0 == strcmp(cfg_buf, "1"))
+ {
+ static char name_buf[512];
+ memset(name_buf,0x00, sizeof(name_buf));
+ sc_cfg_get("LocalDomain", name_buf, sizeof(name_buf));
+ if(!strcmp(daemon->namebuff,name_buf))
+ {
+
+ if (type != T_A)
+ {
+ plen = (size_t)n;
+ setup_reply(daemon->packet, 0, 0);
+ unsigned char *p = skip_questions(header, plen);
+ if(p)
+ {
+ plen = p - (unsigned char *)header;
+ //send_from(listen->fd, daemon->options & OPT_NOWILD, daemon->packet, plen, &source_addr, &dst_addr, if_index);
+ send_from(listen->fd, option_bool(OPT_NOWILD), daemon->packet, plen, &source_addr, &dst_addr, if_index);
+ }
+ return;
+ }
+
+ daemon->packet[2] = daemon->packet[2]|0x80;
+ daemon->packet[7] = daemon->packet[7]|0x01;
+ daemon->packet[num] = 0xc0;
+ daemon->packet[num+1] = 0x0c;
+ daemon->packet[num+2] = 0x00;
+ daemon->packet[num+3] = 0x01;
+ daemon->packet[num+4] = 0x00;
+ daemon->packet[num+5] = 0x01;
+ daemon->packet[num+6] = 0x00;
+ daemon->packet[num+7] = 0x00;
+ daemon->packet[num+8] = 0x00;
+ daemon->packet[num+9] = 0x0a;
+ daemon->packet[num+10] = 0x00;
+ daemon->packet[num+11] = 0x04;
+ daemon->packet[num+12] = *((char *)(&(dst_addr.addr4)) + 0)& 0xff;
+ daemon->packet[num+13] = *((char *)(&(dst_addr.addr4)) + 1)& 0xff;
+ daemon->packet[num+14] = *((char *)(&(dst_addr.addr4)) + 2)& 0xff;
+ daemon->packet[num+15] = *((char *)(&(dst_addr.addr4)) + 3)& 0xff;
+ //send_from(listen->fd, daemon->options & OPT_NOWILD, daemon->packet, num+16, &source_addr, &dst_addr, if_index);
+ send_from(listen->fd, option_bool(OPT_NOWILD), daemon->packet, num+16, &source_addr, &dst_addr, if_index);
+ return;
+ }
+ }
+ {
+ char web_buf[16] = {0};
+ sc_cfg_get("DNS_proxy", web_buf, sizeof(web_buf));
+ if(0 == strcmp(web_buf, "disable"))
+ {
+ return;
+ }
+ }
+ }
+#endif
+#ifdef HAVE_CONNTRACK
+#ifdef HAVE_AUTH
+ if (!auth_dns || local_auth)
+#endif
+ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+ allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
+
+ if (0);
+#ifdef HAVE_CONNTRACK
+ else if (!allowed)
+ {
+ u16 swap = htons(EDE_BLOCKED);
+
+ m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+
+ if (have_pseudoheader && m != 0)
+ m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz,
+ EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+
+ if (m >= 1)
+ {
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+ send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+ (char *)header, m, &source_addr, &dst_addr, if_index);
+ daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+ }
+ }
+#endif
+#ifdef HAVE_AUTH
+ else if (auth_dns)
+ {
+ m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr,
+ local_auth, do_bit, have_pseudoheader);
+ if (m >= 1)
+ {
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+ if (local_auth)
+ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+ report_addresses(header, m, mark);
+#endif
+ send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+ (char *)header, m, &source_addr, &dst_addr, if_index);
+ daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
+ }
+ }
+#endif
+ else
+ {
+ int ad_reqd = do_bit;
+ /* RFC 6840 5.7 */
+ if (header->hb4 & HB4_AD)
+ ad_reqd = 1;
+
+ m = answer_request(header, ((char *) header) + udp_size, (size_t)n,
+ dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader);
+
+ if (m >= 1)
+ {
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+ report_addresses(header, m, mark);
+#endif
+ send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+ (char *)header, m, &source_addr, &dst_addr, if_index);
+ daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+ }
+ else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
+ header, (size_t)n, ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit))
+ daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
+ else
+ daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+ }
+}
+
+/* Send query in packet, qsize to a server determined by first,last,start and
+ get the reply. return reply size. */
+static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, size_t qsize,
+ int have_mark, unsigned int mark, struct server **servp)
+{
+ int firstsendto = -1;
+ u16 *length = (u16 *)packet;
+ unsigned char *payload = &packet[2];
+ struct dns_header *header = (struct dns_header *)payload;
+ unsigned char c1, c2;
+ unsigned char hash[HASH_SIZE];
+ unsigned int rsize;
+
+ (void)mark;
+ (void)have_mark;
+
+ memcpy(hash, hash_questions(header, (unsigned int)qsize, daemon->namebuff), HASH_SIZE);
+
+ while (1)
+ {
+ int data_sent = 0;
+ struct server *serv;
+
+ if (firstsendto == -1)
+ firstsendto = start;
+ else
+ {
+ start++;
+
+ if (start == last)
+ start = first;
+
+ if (start == firstsendto)
+ break;
+ }
+
+ serv = daemon->serverarray[start];
+
+ retry:
+ *length = htons(qsize);
+
+ if (serv->tcpfd == -1)
+ {
+ if ((serv->tcpfd = socket(serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
+ continue;
+
+#ifdef HAVE_CONNTRACK
+ /* Copy connection mark of incoming query to outgoing connection. */
+ if (have_mark)
+ setsockopt(serv->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+#endif
+
+ if ((!local_bind(serv->tcpfd, &serv->source_addr, serv->interface, 0, 1)))
+ {
+ close(serv->tcpfd);
+ serv->tcpfd = -1;
+ continue;
+ }
+
+#ifdef MSG_FASTOPEN
+ server_send(serv, serv->tcpfd, packet, qsize + sizeof(u16), MSG_FASTOPEN);
+
+ if (errno == 0)
+ data_sent = 1;
+#endif
+
+ if (!data_sent && connect(serv->tcpfd, &serv->addr.sa, sa_len(&serv->addr)) == -1)
+ {
+ close(serv->tcpfd);
+ serv->tcpfd = -1;
+ continue;
+ }
+
+ daemon->serverarray[first]->last_server = start;
+ serv->flags &= ~SERV_GOT_TCP;
+ }
+
+ if ((!data_sent && !read_write(serv->tcpfd, packet, qsize + sizeof(u16), 0)) ||
+ !read_write(serv->tcpfd, &c1, 1, 1) ||
+ !read_write(serv->tcpfd, &c2, 1, 1) ||
+ !read_write(serv->tcpfd, payload, (rsize = (c1 << 8) | c2), 1))
+ {
+ close(serv->tcpfd);
+ serv->tcpfd = -1;
+ /* We get data then EOF, reopen connection to same server,
+ else try next. This avoids DoS from a server which accepts
+ connections and then closes them. */
+ if (serv->flags & SERV_GOT_TCP)
+ goto retry;
+ else
+ continue;
+ }
+
+ /* If the hash 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.
+ Try another server, or give up */
+ if (memcmp(hash, hash_questions(header, rsize, daemon->namebuff), HASH_SIZE) != 0)
+ continue;
+
+ serv->flags |= SERV_GOT_TCP;
+
+ *servp = serv;
+ return rsize;
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_DNSSEC
+/* Recurse down the key hierarchy */
+static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
+ int class, char *name, char *keyname, struct server *server,
+ int have_mark, unsigned int mark, int *keycount)
+{
+ int first, last, start, new_status;
+ unsigned char *packet = NULL;
+ struct dns_header *new_header = NULL;
+
+ while (1)
+ {
+ size_t m;
+ int log_save;
+
+ /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
+ if (--(*keycount) == 0)
+ new_status = STAT_ABANDONED;
+ else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
+ new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
+ else if (STAT_ISEQUAL(status, STAT_NEED_DS))
+ new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+ else
+ new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
+ !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
+ NULL, NULL, NULL);
+
+ if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY))
+ break;
+
+ /* Can't validate because we need a key/DS whose name now in keyname.
+ Make query for same, and recurse to validate */
+ if (!packet)
+ {
+ packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+ new_header = (struct dns_header *)&packet[2];
+ }
+
+ if (!packet)
+ {
+ new_status = STAT_ABANDONED;
+ break;
+ }
+
+ m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class,
+ STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
+
+ if ((start = dnssec_server(server, daemon->keyname, &first, &last)) == -1 ||
+ (m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0)
+ {
+ new_status = STAT_ABANDONED;
+ break;
+ }
+
+ log_save = daemon->log_display_id;
+ daemon->log_display_id = ++daemon->log_id;
+
+ log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr,
+ querystr("dnssec-query", STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
+
+ new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
+
+ daemon->log_display_id = log_save;
+
+ if (!STAT_ISEQUAL(new_status, STAT_OK))
+ break;
+ }
+
+ if (packet)
+ free(packet);
+
+ return new_status;
+}
+#endif
+
+
+/* The daemon forks before calling this: it should deal with one connection,
+ blocking as necessary, and then return. Note, need to be a bit careful
+ about resources for debug mode, when the fork is suppressed: that's
+ done by the caller. */
+unsigned char *tcp_request(int confd, time_t now,
+ union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
+{
+ size_t size = 0;
+ int norebind;
+#ifdef HAVE_CONNTRACK
+ int is_single_query = 0, allowed = 1;
+#endif
+#ifdef HAVE_AUTH
+ int local_auth = 0;
+#endif
+ int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
+ int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+ size_t m;
+ unsigned short qtype;
+ unsigned int gotname;
+ /* Max TCP packet + slop + size */
+ unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+ unsigned char *payload = &packet[2];
+ unsigned char c1, c2;
+ /* largest field in header is 16-bits, so this is still sufficiently aligned */
+ struct dns_header *header = (struct dns_header *)payload;
+ u16 *length = (u16 *)packet;
+ struct server *serv;
+ struct in_addr dst_addr_4;
+ union mysockaddr peer_addr;
+ socklen_t peer_len = sizeof(union mysockaddr);
+ int query_count = 0;
+ unsigned char *pheader;
+ unsigned int mark = 0;
+ int have_mark = 0;
+ int first, last;
+ unsigned int flags = 0;
+
+ if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
+ return packet;
+
+#ifdef HAVE_CONNTRACK
+ /* Get connection mark of incoming query to set on outgoing connections. */
+ if (option_bool(OPT_CONNTRACK) || option_bool(OPT_CMARK_ALST_EN))
+ {
+ union all_addr local;
+
+ if (local_addr->sa.sa_family == AF_INET6)
+ local.addr6 = local_addr->in6.sin6_addr;
+ else
+ local.addr4 = local_addr->in.sin_addr;
+
+ have_mark = get_incoming_mark(&peer_addr, &local, 1, &mark);
+ }
+#endif
+
+ /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *addr;
+
+ if (peer_addr.sa.sa_family == AF_INET6)
+ {
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ if ((addr->flags & ADDRLIST_IPV6) &&
+ is_same_net6(&addr->addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen))
+ break;
+ }
+ else
+ {
+ struct in_addr netmask;
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ {
+ netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen));
+ if (!(addr->flags & ADDRLIST_IPV6) &&
+ is_same_net(addr->addr.addr4, peer_addr.in.sin_addr, netmask))
+ break;
+ }
+ }
+ if (!addr)
+ {
+ my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+ return packet;
+ }
+ }
+
+ while (1)
+ {
+ int ede = EDE_UNSET;
+
+ if (query_count == TCP_MAX_QUERIES ||
+ !packet ||
+ !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) ||
+ !(size = c1 << 8 | c2) ||
+ !read_write(confd, payload, size, 1))
+ return packet;
+
+ if (size < (int)sizeof(struct dns_header))
+ continue;
+
+ /* Clear buffer beyond request to avoid risk of
+ information disclosure. */
+ memset(payload + size, 0, 65536 - size);
+
+ query_count++;
+
+ /* log_query gets called indirectly all over the place, so
+ pass these in global variables - sorry. */
+ daemon->log_display_id = ++daemon->log_id;
+ daemon->log_source_addr = &peer_addr;
+
+ /* save state of "cd" flag in query */
+ if ((checking_disabled = header->hb4 & HB4_CD))
+ no_cache_dnssec = 1;
+
+ if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
+ {
+#ifdef HAVE_AUTH
+ struct auth_zone *zone;
+#endif
+ char *types = querystr(auth_dns ? "auth" : "query", qtype);
+
+ log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
+ &peer_addr, types);
+
+#ifdef HAVE_CONNTRACK
+ is_single_query = 1;
+#endif
+
+#ifdef HAVE_AUTH
+ /* find queries for zones we're authoritative for, and answer them directly */
+ if (!auth_dns && !option_bool(OPT_LOCALISE))
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ if (in_zone(zone, daemon->namebuff, NULL))
+ {
+ auth_dns = 1;
+ local_auth = 1;
+ break;
+ }
+#endif
+ }
+
+ norebind = domain_no_rebind(daemon->namebuff);
+
+ if (local_addr->sa.sa_family == AF_INET)
+ dst_addr_4 = local_addr->in.sin_addr;
+ else
+ dst_addr_4.s_addr = 0;
+
+ do_bit = 0;
+
+ if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
+ {
+ unsigned short flags;
+
+ have_pseudoheader = 1;
+ pheader += 4; /* udp_size, ext_rcode */
+ GETSHORT(flags, pheader);
+
+ if (flags & 0x8000)
+ do_bit = 1; /* do bit */
+ }
+
+#ifdef HAVE_CONNTRACK
+#ifdef HAVE_AUTH
+ if (!auth_dns || local_auth)
+#endif
+ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+ allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
+
+ if (0);
+#ifdef HAVE_CONNTRACK
+ else if (!allowed)
+ {
+ u16 swap = htons(EDE_BLOCKED);
+
+ m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+
+ if (have_pseudoheader && m != 0)
+ m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz,
+ EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+ }
+#endif
+#ifdef HAVE_AUTH
+ else if (auth_dns)
+ m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr,
+ local_auth, do_bit, have_pseudoheader);
+#endif
+ else
+ {
+ int ad_reqd = do_bit;
+ /* RFC 6840 5.7 */
+ if (header->hb4 & HB4_AD)
+ ad_reqd = 1;
+
+ /* m > 0 if answered from cache */
+ m = answer_request(header, ((char *) header) + 65536, (size_t)size,
+ dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader);
+
+ /* Do this by steam now we're not in the select() loop */
+ check_log_writer(1);
+
+ if (m == 0)
+ {
+ struct server *master;
+ int start;
+
+ if (lookup_domain(daemon->namebuff, gotname, &first, &last))
+ flags = is_local_answer(now, first, daemon->namebuff);
+ else
+ {
+ /* No configured servers */
+ ede = EDE_NOT_READY;
+ flags = 0;
+ }
+
+ /* don't forward A or AAAA queries for simple names, except the empty name */
+ if (!flags &&
+ option_bool(OPT_NODOTS_LOCAL) &&
+ (gotname & (F_IPV4 | F_IPV6)) &&
+ !strchr(daemon->namebuff, '.') &&
+ strlen(daemon->namebuff) != 0)
+ flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN;
+
+ if (!flags && ede != EDE_NOT_READY)
+ {
+ master = daemon->serverarray[first];
+
+ if (option_bool(OPT_ORDER) || master->last_server == -1)
+ start = first;
+ else
+ start = master->last_server;
+
+ size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable);
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
+ {
+ size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
+
+ /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+ this allows it to select auth servers when one is returning bad data. */
+ if (option_bool(OPT_DNSSEC_DEBUG))
+ header->hb4 |= HB4_CD;
+ }
+#endif
+
+ /* Check if we added a pheader on forwarding - may need to
+ strip it from the reply. */
+ if (!have_pseudoheader && find_pseudoheader(header, size, NULL, NULL, NULL, NULL))
+ added_pheader = 1;
+
+ /* Loop round available servers until we succeed in connecting to one. */
+ if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0)
+ {
+ ede = EDE_NETERR;
+ break;
+ }
+
+ /* get query name again for logging - may have been overwritten */
+ if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
+ strcpy(daemon->namebuff, "query");
+ log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL);
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
+ {
+ int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
+ int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
+ serv, have_mark, mark, &keycount);
+ char *result, *domain = "result";
+
+ union all_addr a;
+ a.log.ede = ede = errflags_to_ede(status);
+
+ if (STAT_ISEQUAL(status, STAT_ABANDONED))
+ {
+ result = "ABANDONED";
+ status = STAT_BOGUS;
+ }
+ else
+ result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
+
+ if (STAT_ISEQUAL(status, STAT_SECURE))
+ cache_secure = 1;
+ else if (STAT_ISEQUAL(status, STAT_BOGUS))
+ {
+ no_cache_dnssec = 1;
+ bogusanswer = 1;
+
+ if (extract_request(header, m, daemon->namebuff, NULL))
+ domain = daemon->namebuff;
+ }
+
+ log_query(F_SECSTAT, domain, &a, result);
+ }
+#endif
+
+ /* restore CD bit to the value in the query */
+ if (checking_disabled)
+ header->hb4 |= HB4_CD;
+ else
+ header->hb4 &= ~HB4_CD;
+
+ /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+ since the cache is ignorant of such things. */
+ if (!cacheable)
+ no_cache_dnssec = 1;
+
+ m = process_reply(header, now, serv, (unsigned int)m,
+ option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
+ ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr, ((unsigned char *)header) + 65536, ede);
+ }
+ }
+ }
+
+ /* In case of local answer or no connections made. */
+ if (m == 0)
+ {
+ if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff,
+ ((char *) header) + 65536, first, last, ede)))
+ break;
+
+ if (have_pseudoheader)
+ {
+ u16 swap = htons((u16)ede);
+
+ if (ede != EDE_UNSET)
+ m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+ else
+ m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+ }
+ }
+
+ check_log_writer(1);
+
+ *length = htons(m);
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+#ifdef HAVE_AUTH
+ if (!auth_dns || local_auth)
+#endif
+ if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+ report_addresses(header, m, mark);
+#endif
+ if (!read_write(confd, packet, m + sizeof(u16), 0))
+ break;
+ }
+
+ return packet;
+}
+
+/* return a UDP socket bound to a random port, have to cope with straying into
+ occupied port nos and reserved ones. */
+static int random_sock(struct server *s)
+{
+ int fd;
+
+ if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
+ {
+ if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
+ return fd;
+
+ if (s->interface[0] == 0)
+ (void)prettyprint_addr(&s->source_addr, daemon->namebuff);
+ else
+ strcpy(daemon->namebuff, s->interface);
+
+ my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"),
+ daemon->namebuff, strerror(errno));
+ close(fd);
+ }
+
+ return -1;
+}
+
+/* compare source addresses and interface, serv2 can be null. */
+static int server_isequal(const struct server *serv1,
+ const struct server *serv2)
+{
+ return (serv2 &&
+ serv2->ifindex == serv1->ifindex &&
+ sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) &&
+ strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0);
+}
+
+/* fdlp points to chain of randomfds already in use by transaction.
+ If there's already a suitable one, return it, else allocate a
+ new one and add it to the list.
+
+ Not leaking any resources in the face of allocation failures
+ is rather convoluted here.
+
+ Note that rfd->serv may be NULL, when a server goes away.
+*/
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
+{
+ static int finger = 0;
+ int i, j = 0;
+ struct randfd_list *rfl;
+ struct randfd *rfd = NULL;
+ int fd = 0;
+
+ /* If server has a pre-allocated fd, use that. */
+ if (serv->sfd)
+ return serv->sfd->fd;
+
+ /* existing suitable random port socket linked to this transaction? */
+ for (rfl = *fdlp; rfl; rfl = rfl->next)
+ if (server_isequal(serv, rfl->rfd->serv))
+ return rfl->rfd->fd;
+
+ /* No. need new link. */
+ if ((rfl = daemon->rfl_spare))
+ daemon->rfl_spare = rfl->next;
+ else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
+ return -1;
+
+ /* 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 < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount == 0)
+ {
+ if ((fd = random_sock(serv)) != -1)
+ {
+ rfd = &daemon->randomsocks[i];
+ rfd->serv = serv;
+ rfd->fd = fd;
+ rfd->refcount = 1;
+ }
+ break;
+ }
+
+ /* No free ones or cannot get new socket, grab an existing one */
+ if (!rfd)
+ for (j = 0; j < daemon->numrrand; j++)
+ {
+ i = (j + finger) % daemon->numrrand;
+ if (daemon->randomsocks[i].refcount != 0 &&
+ server_isequal(serv, daemon->randomsocks[i].serv) &&
+ daemon->randomsocks[i].refcount != 0xfffe)
+ {
+ finger = i + 1;
+ rfd = &daemon->randomsocks[i];
+ rfd->refcount++;
+ break;
+ }
+ }
+
+ if (j == daemon->numrrand)
+ {
+ struct randfd_list *rfl_poll;
+
+ /* there are no free slots, and non with the same parameters we can piggy-back on.
+ We're going to have to allocate a new temporary record, distinguished by
+ refcount == 0xffff. This will exist in the frec randfd list, never be shared,
+ and be freed when no longer in use. It will also be held on
+ the daemon->rfl_poll list so the poll system can find it. */
+
+ if ((rfl_poll = daemon->rfl_spare))
+ daemon->rfl_spare = rfl_poll->next;
+ else
+ rfl_poll = whine_malloc(sizeof(struct randfd_list));
+
+ if (!rfl_poll ||
+ !(rfd = whine_malloc(sizeof(struct randfd))) ||
+ (fd = random_sock(serv)) == -1)
+ {
+
+ /* Don't leak anything we may already have */
+ rfl->next = daemon->rfl_spare;
+ daemon->rfl_spare = rfl;
+
+ if (rfl_poll)
+ {
+ rfl_poll->next = daemon->rfl_spare;
+ daemon->rfl_spare = rfl_poll;
+ }
+
+ if (rfd)
+ free(rfd);
+
+ return -1; /* doom */
+ }
+
+ /* Note rfd->serv not set here, since it's not reused */
+ rfd->fd = fd;
+ rfd->refcount = 0xffff; /* marker for temp record */
+
+ rfl_poll->rfd = rfd;
+ rfl_poll->next = daemon->rfl_poll;
+ daemon->rfl_poll = rfl_poll;
+ }
+
+ rfl->rfd = rfd;
+ rfl->next = *fdlp;
+ *fdlp = rfl;
+
+ return rfl->rfd->fd;
+}
+
+void free_rfds(struct randfd_list **fdlp)
+{
+ struct randfd_list *tmp, *rfl, *poll, *next, **up;
+
+ for (rfl = *fdlp; rfl; rfl = tmp)
+ {
+ if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0)
+ close(rfl->rfd->fd);
+
+ /* temporary overflow record */
+ if (rfl->rfd->refcount == 0xffff)
+ {
+ free(rfl->rfd);
+
+ /* go through the link of all these by steam to delete.
+ This list is expected to be almost always empty. */
+ for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next)
+ {
+ next = poll->next;
+
+ if (poll->rfd == rfl->rfd)
+ {
+ *up = poll->next;
+ poll->next = daemon->rfl_spare;
+ daemon->rfl_spare = poll;
+ }
+ else
+ up = &poll->next;
+ }
+ }
+
+ tmp = rfl->next;
+ rfl->next = daemon->rfl_spare;
+ daemon->rfl_spare = rfl;
+ }
+
+ *fdlp = NULL;
+}
+
+static void free_frec(struct frec *f)
+{
+ struct frec_src *last;
+
+ /* add back to freelist if not the record builtin to every frec. */
+ for (last = f->frec_src.next; last && last->next; last = last->next) ;
+ if (last)
+ {
+ last->next = daemon->free_frec_src;
+ daemon->free_frec_src = f->frec_src.next;
+ }
+
+ f->frec_src.next = NULL;
+ free_rfds(&f->rfds);
+ f->sentto = NULL;
+ f->flags = 0;
+
+#ifdef HAVE_DNSSEC
+ if (f->stash)
+ {
+ blockdata_free(f->stash);
+ f->stash = NULL;
+ }
+
+ /* Anything we're waiting on is pointless now, too */
+ if (f->blocking_query)
+ {
+ struct frec *n, **up;
+
+ /* unlink outselves from the blocking query's dependents list. */
+ for (n = f->blocking_query->dependent, up = &f->blocking_query->dependent; n; n = n->next_dependent)
+ if (n == f)
+ {
+ *up = n->next_dependent;
+ break;
+ }
+ else
+ up = &n->next_dependent;
+
+ /* If we were the only/last dependent, free the blocking query too. */
+ if (!f->blocking_query->dependent)
+ free_frec(f->blocking_query);
+ }
+
+ f->blocking_query = NULL;
+ f->dependent = NULL;
+ f->next_dependent = NULL;
+#endif
+}
+
+
+
+/* Impose an absolute
+ limit of 4*TIMEOUT before we wipe things (for random sockets).
+ If force is set, always return a result, even if we have
+ to allocate above the limit, and don'y free any records.
+ This is set when allocating for DNSSEC to avoid cutting off
+ the branch we are sitting on. */
+static struct frec *get_new_frec(time_t now, struct server *master, int force)
+{
+ struct frec *f, *oldest, *target;
+ int count;
+
+ /* look for free records, garbage collect old records and count number in use by our server-group. */
+ for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next)
+ {
+ if (!f->sentto)
+ target = f;
+ else
+ {
+#ifdef HAVE_DNSSEC
+ /* Don't free DNSSEC sub-queries here, as we may end up with
+ dangling references to them. They'll go when their "real" query
+ is freed. */
+ if (!f->dependent && !force)
+#endif
+ {
+ if (difftime(now, f->time) >= 4*TIMEOUT)
+ {
+ free_frec(f);
+ target = f;
+ }
+ else if (!oldest || difftime(f->time, oldest->time) <= 0)
+ oldest = f;
+ }
+ }
+
+ if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master))
+ count++;
+ }
+
+ if (!force && count >= daemon->ftabsize)
+ {
+ query_full(now, master->domain);
+ return NULL;
+ }
+
+ if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
+ {
+ /* can't find empty one, use oldest if there is one and it's older than timeout */
+ free_frec(oldest);
+ target = oldest;
+ }
+
+ if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec))))
+ {
+ target->next = daemon->frec_list;
+ daemon->frec_list = target;
+ }
+
+ if (target)
+ target->time = now;
+
+ return target;
+}
+
+static void query_full(time_t now, char *domain)
+{
+ static time_t last_log = 0;
+
+ if ((int)difftime(now, last_log) > 5)
+ {
+ last_log = now;
+ if (!domain || strlen(domain) == 0)
+ my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
+ else
+ my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize);
+ }
+}
+
+
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp)
+{
+ struct frec *f;
+ struct server *s;
+ int first, last;
+ struct randfd_list *fdl;
+
+ for(f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->new_id == id &&
+ (memcmp(hash, f->hash, HASH_SIZE) == 0))
+ {
+ filter_servers(f->sentto->arrayposn, F_SERVER, firstp, lastp);
+
+ /* sent from random port */
+ for (fdl = f->rfds; fdl; fdl = fdl->next)
+ if (fdl->rfd->fd == fd)
+ return f;
+
+ /* Sent to upstream from socket associated with a server.
+ Note we have to iterate over all the possible servers, since they may
+ have different bound sockets. */
+ for (first = *firstp, last = *lastp; first != last; first++)
+ {
+ s = daemon->serverarray[first];
+ if (s->sfd && s->sfd->fd == fd)
+ return f;
+ }
+ }
+
+ return NULL;
+}
+
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask)
+{
+ struct frec *f;
+
+ for(f = daemon->frec_list; f; f = f->next)
+ if (f->sentto &&
+ (f->flags & flagmask) == flags &&
+ memcmp(hash, f->hash, HASH_SIZE) == 0)
+ return f;
+
+ return NULL;
+}
+
+/* Send query packet again, if we can. */
+void resend_query()
+{
+ if (daemon->srv_save)
+ server_send(daemon->srv_save, daemon->fd_save,
+ daemon->packet, daemon->packet_len, 0);
+}
+
+/* A server record is going away, remove references to it */
+void server_gone(struct server *server)
+{
+ struct frec *f;
+ int i;
+
+ for (f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->sentto == server)
+ free_frec(f);
+
+ /* If any random socket refers to this server, NULL the reference.
+ No more references to the socket will be created in the future. */
+ for (i = 0; i < daemon->numrrand; i++)
+ if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server)
+ daemon->randomsocks[i].serv = NULL;
+
+ if (daemon->srv_save == server)
+ daemon->srv_save = NULL;
+}
+
+/* return unique random ids. */
+static unsigned short get_id(void)
+{
+ unsigned short ret = 0;
+ struct frec *f;
+
+ while (1)
+ {
+ ret = rand16();
+
+ /* ensure id is unique. */
+ for (f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->new_id == ret)
+ break;
+
+ if (!f)
+ return ret;
+ }
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c b/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c
new file mode 100755
index 0000000..f41023b
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2012-2020 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/>.
+*/
+
+
+/* Hash the question section. This is used to safely detect query
+ retransmission and to detect answers to questions we didn't ask, which
+ might be poisoning attacks. Note that we decode the name rather
+ than CRC the raw bytes, since replies might be compressed differently.
+ We ignore case in the names for the same reason.
+
+ The hash used is SHA-256. If we're building with DNSSEC support,
+ we use the Nettle cypto library. If not, we prefer not to
+ add a dependency on Nettle, and use a stand-alone implementaion.
+*/
+
+#include "dnsmasq.h"
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+
+static const struct nettle_hash *hash;
+static void *ctx;
+static unsigned char *digest;
+
+void hash_questions_init(void)
+{
+ if (!(hash = hash_find("sha256")))
+ die(_("Failed to create SHA-256 hash object"), NULL, EC_MISC);
+
+ ctx = safe_malloc(hash->context_size);
+ digest = safe_malloc(hash->digest_size);
+}
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+ int q;
+ unsigned char *p = (unsigned char *)(header+1);
+
+ hash->init(ctx);
+
+ for (q = ntohs(header->qdcount); q != 0; q--)
+ {
+ char *cp, c;
+
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ break; /* bad packet */
+
+ for (cp = name; (c = *cp); cp++)
+ if (c >= 'A' && c <= 'Z')
+ *cp += 'a' - 'A';
+
+ hash->update(ctx, cp - name, (unsigned char *)name);
+ /* CRC the class and type as well */
+ hash->update(ctx, 4, p);
+
+ p += 4;
+ if (!CHECK_LEN(header, p, plen, 0))
+ break; /* bad packet */
+ }
+
+ hash->digest(ctx, hash->digest_size, digest);
+ return digest;
+}
+
+#else /* HAVE_DNSSEC || HAVE_CRYPTOHASH */
+
+#define SHA256_BLOCK_SIZE 32 /* SHA256 outputs a 32 byte digest */
+typedef unsigned char BYTE; /* 8-bit byte */
+typedef unsigned int WORD; /* 32-bit word, change to "long" for 16-bit machines */
+
+typedef struct {
+ BYTE data[64];
+ WORD datalen;
+ unsigned long long bitlen;
+ WORD state[8];
+} SHA256_CTX;
+
+static void sha256_init(SHA256_CTX *ctx);
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
+
+void hash_questions_init(void)
+{
+}
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+ int q;
+ unsigned char *p = (unsigned char *)(header+1);
+ SHA256_CTX ctx;
+ static BYTE digest[SHA256_BLOCK_SIZE];
+
+ sha256_init(&ctx);
+
+ for (q = ntohs(header->qdcount); q != 0; q--)
+ {
+ char *cp, c;
+
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ break; /* bad packet */
+
+ for (cp = name; (c = *cp); cp++)
+ if (c >= 'A' && c <= 'Z')
+ *cp += 'a' - 'A';
+
+ sha256_update(&ctx, (BYTE *)name, cp - name);
+ /* CRC the class and type as well */
+ sha256_update(&ctx, (BYTE *)p, 4);
+
+ p += 4;
+ if (!CHECK_LEN(header, p, plen, 0))
+ break; /* bad packet */
+ }
+
+ sha256_final(&ctx, digest);
+ return (unsigned char *)digest;
+}
+
+/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms
+ and was written by Brad Conte (brad@bradconte.com), to whom all credit is given.
+
+ This code is in the public domain, and the copyright notice at the head of this
+ file does not apply to it.
+*/
+
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+/**************************** VARIABLES *****************************/
+static const WORD k[64] = {
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+static void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
+{
+ WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+
+ for (i = 0, j = 0; i < 16; ++i, j += 4)
+ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
+ for ( ; i < 64; ++i)
+ m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
+
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
+
+ for (i = 0; i < 64; ++i)
+ {
+ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+ t2 = EP0(a) + MAJ(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
+}
+
+static void sha256_init(SHA256_CTX *ctx)
+{
+ ctx->datalen = 0;
+ ctx->bitlen = 0;
+ ctx->state[0] = 0x6a09e667;
+ ctx->state[1] = 0xbb67ae85;
+ ctx->state[2] = 0x3c6ef372;
+ ctx->state[3] = 0xa54ff53a;
+ ctx->state[4] = 0x510e527f;
+ ctx->state[5] = 0x9b05688c;
+ ctx->state[6] = 0x1f83d9ab;
+ ctx->state[7] = 0x5be0cd19;
+}
+
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
+{
+ WORD i;
+
+ for (i = 0; i < len; ++i)
+ {
+ ctx->data[ctx->datalen] = data[i];
+ ctx->datalen++;
+ if (ctx->datalen == 64) {
+ sha256_transform(ctx, ctx->data);
+ ctx->bitlen += 512;
+ ctx->datalen = 0;
+ }
+ }
+}
+
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[])
+{
+ WORD i;
+
+ i = ctx->datalen;
+
+ /* Pad whatever data is left in the buffer. */
+ if (ctx->datalen < 56)
+ {
+ ctx->data[i++] = 0x80;
+ while (i < 56)
+ ctx->data[i++] = 0x00;
+ }
+ else
+ {
+ ctx->data[i++] = 0x80;
+ while (i < 64)
+ ctx->data[i++] = 0x00;
+ sha256_transform(ctx, ctx->data);
+ memset(ctx->data, 0, 56);
+ }
+
+ /* Append to the padding the total message's length in bits and transform. */
+ ctx->bitlen += ctx->datalen * 8;
+ ctx->data[63] = ctx->bitlen;
+ ctx->data[62] = ctx->bitlen >> 8;
+ ctx->data[61] = ctx->bitlen >> 16;
+ ctx->data[60] = ctx->bitlen >> 24;
+ ctx->data[59] = ctx->bitlen >> 32;
+ ctx->data[58] = ctx->bitlen >> 40;
+ ctx->data[57] = ctx->bitlen >> 48;
+ ctx->data[56] = ctx->bitlen >> 56;
+ sha256_transform(ctx, ctx->data);
+
+ /* Since this implementation uses little endian byte ordering and SHA uses big endian,
+ reverse all the bytes when copying the final state to the output hash. */
+ for (i = 0; i < 4; ++i)
+ {
+ hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
+ }
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c b/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c
new file mode 100755
index 0000000..02340a0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c
@@ -0,0 +1,890 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_SCRIPT
+
+/* This file has code to fork a helper process which receives data via a pipe
+ shared with the main process and which is responsible for calling a script when
+ DHCP leases change.
+
+ 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.
+*/
+
+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);
+
+#ifdef HAVE_LUASCRIPT
+#define LUA_COMPAT_ALL
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#ifndef lua_open
+#define lua_open() luaL_newstate()
+#endif
+
+lua_State *lua;
+
+static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field);
+#endif
+
+
+struct script_data
+{
+ int flags;
+ int action, hwaddr_len, hwaddr_type;
+ int 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
+#ifdef HAVE_TFTP
+ off_t file_len;
+#endif
+ struct in6_addr addr6;
+#ifdef HAVE_DHCP6
+ int vendorclass_count;
+ unsigned int iaid;
+#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;
+ unsigned char *alloc_buff = NULL;
+
+ /* 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, NULL);
+ _exit(0);
+ }
+
+ if (pid != 0)
+ {
+ close(pipefd[0]); /* close reader side */
+ return pipefd[1];
+ }
+
+ /* ignore SIGTERM and SIGINT, so that we can clean up when the main process gets hit
+ and SIGALRM so that we can use sleep() */
+ sigact.sa_handler = SIG_IGN;
+ sigact.sa_flags = 0;
+ sigemptyset(&sigact.sa_mask);
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGALRM, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ if (!option_bool(OPT_DEBUG) && uid != 0)
+ {
+ 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_USER_ERR, errno, daemon->scriptuser);
+ else
+ {
+ /* kill daemon */
+ send_event(event_fd, EVENT_DIE, 0, NULL);
+ /* return error */
+ send_event(err_fd, EVENT_USER_ERR, errno, daemon->scriptuser);
+ }
+ _exit(0);
+ }
+ }
+
+ /* close all the sockets etc, we don't need them here.
+ Don't close err_fd, in case the lua-init fails.
+ Note that we have to do this before lua init
+ so we don't close any lua fds. */
+ close_fds(max_fd, pipefd[0], event_fd, err_fd);
+
+#ifdef HAVE_LUASCRIPT
+ if (daemon->luascript)
+ {
+ const char *lua_err = NULL;
+ lua = lua_open();
+ luaL_openlibs(lua);
+
+ /* get Lua to load our script file */
+ if (luaL_dofile(lua, daemon->luascript) != 0)
+ lua_err = lua_tostring(lua, -1);
+ else
+ {
+ lua_getglobal(lua, "lease");
+ if (lua_type(lua, -1) != LUA_TFUNCTION)
+ lua_err = _("lease() function missing in Lua script");
+ }
+
+ if (lua_err)
+ {
+ if (option_bool(OPT_NO_FORK) || option_bool(OPT_DEBUG))
+ /* send error to daemon process if no-fork */
+ send_event(event_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
+ else
+ {
+ /* kill daemon */
+ send_event(event_fd, EVENT_DIE, 0, NULL);
+ /* return error */
+ send_event(err_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
+ }
+ _exit(0);
+ }
+
+ lua_pop(lua, 1); /* remove nil from stack */
+ lua_getglobal(lua, "init");
+ if (lua_type(lua, -1) == LUA_TFUNCTION)
+ lua_call(lua, 0, 0);
+ else
+ lua_pop(lua, 1); /* remove nil from stack */
+ }
+#endif
+
+ /* All init done, close our copy of the error pipe, so that main process can return */
+ if (err_fd != -1)
+ close(err_fd);
+
+ /* loop here */
+ while(1)
+ {
+ struct script_data data;
+ char *p, *action_str, *hostname = NULL, *domain = NULL;
+ unsigned char *buf = (unsigned char *)daemon->namebuff;
+ unsigned char *end, *extradata;
+ int is6, err = 0;
+ int pipeout[2];
+
+ /* Free rarely-allocated memory from previous iteration. */
+ if (alloc_buff)
+ {
+ free(alloc_buff);
+ alloc_buff = NULL;
+ }
+
+ /* we read zero bytes when pipe closed: this is our signal to exit */
+ if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1))
+ {
+#ifdef HAVE_LUASCRIPT
+ if (daemon->luascript)
+ {
+ lua_getglobal(lua, "shutdown");
+ if (lua_type(lua, -1) == LUA_TFUNCTION)
+ lua_call(lua, 0, 0);
+ }
+#endif
+ _exit(0);
+ }
+
+ is6 = !!(data.flags & (LEASE_TA | LEASE_NA));
+
+ 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 if (data.action == ACTION_TFTP)
+ {
+ action_str = "tftp";
+ is6 = (data.flags != AF_INET);
+ }
+ else if (data.action == ACTION_ARP)
+ {
+ action_str = "arp-add";
+ is6 = (data.flags != AF_INET);
+ }
+ else if (data.action == ACTION_ARP_DEL)
+ {
+ action_str = "arp-del";
+ is6 = (data.flags != AF_INET);
+ data.action = ACTION_ARP;
+ }
+ else
+ continue;
+
+ /* 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, ":");
+ }
+
+ /* supplied data may just exceed normal buffer (unlikely) */
+ if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME &&
+ !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len)))
+ continue;
+
+ if (!read_write(pipefd[0], buf,
+ data.hostname_len + data.ed_len + data.clid_len, 1))
+ continue;
+
+ /* CLID into packet */
+ 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, ":");
+ }
+
+#ifdef HAVE_DHCP6
+ if (is6)
+ {
+ /* or IAID and server DUID for IPv6 */
+ sprintf(daemon->dhcp_buff3, "%s%u", data.flags & LEASE_TA ? "T" : "", data.iaid);
+ for (p = daemon->dhcp_packet.iov_base, i = 0; i < daemon->duid_len; i++)
+ {
+ p += sprintf(p, "%.2x", daemon->duid[i]);
+ if (i != daemon->duid_len - 1)
+ p += sprintf(p, ":");
+ }
+
+ }
+#endif
+
+ buf += data.clid_len;
+
+ if (data.hostname_len != 0)
+ {
+ char *dot;
+ hostname = (char *)buf;
+ hostname[data.hostname_len - 1] = 0;
+ if (data.action != ACTION_TFTP)
+ {
+ if (!legal_hostname(hostname))
+ hostname = NULL;
+ else if ((dot = strchr(hostname, '.')))
+ {
+ domain = dot+1;
+ *dot = 0;
+ }
+ }
+ }
+
+ extradata = buf + data.hostname_len;
+
+ if (!is6)
+ inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN);
+
+#ifdef HAVE_TFTP
+ /* file length */
+ if (data.action == ACTION_TFTP)
+ sprintf(is6 ? daemon->packet : daemon->dhcp_buff, "%lu", (unsigned long)data.file_len);
+#endif
+
+#ifdef HAVE_LUASCRIPT
+ if (daemon->luascript)
+ {
+ if (data.action == ACTION_TFTP)
+ {
+ lua_getglobal(lua, "tftp");
+ if (lua_type(lua, -1) != LUA_TFUNCTION)
+ lua_pop(lua, 1); /* tftp function optional */
+ else
+ {
+ lua_pushstring(lua, action_str); /* arg1 - action */
+ lua_newtable(lua); /* arg2 - data table */
+ lua_pushstring(lua, daemon->addrbuff);
+ lua_setfield(lua, -2, "destination_address");
+ lua_pushstring(lua, hostname);
+ lua_setfield(lua, -2, "file_name");
+ lua_pushstring(lua, is6 ? daemon->packet : daemon->dhcp_buff);
+ lua_setfield(lua, -2, "file_size");
+ lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
+ }
+ }
+ else if (data.action == ACTION_ARP)
+ {
+ lua_getglobal(lua, "arp");
+ if (lua_type(lua, -1) != LUA_TFUNCTION)
+ lua_pop(lua, 1); /* arp function optional */
+ else
+ {
+ lua_pushstring(lua, action_str); /* arg1 - action */
+ lua_newtable(lua); /* arg2 - data table */
+ lua_pushstring(lua, daemon->addrbuff);
+ lua_setfield(lua, -2, "client_address");
+ lua_pushstring(lua, daemon->dhcp_buff);
+ lua_setfield(lua, -2, "mac_address");
+ lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
+ }
+ }
+ else
+ {
+ lua_getglobal(lua, "lease"); /* function to call */
+ lua_pushstring(lua, action_str); /* arg1 - action */
+ lua_newtable(lua); /* arg2 - data table */
+
+ if (is6)
+ {
+ lua_pushstring(lua, daemon->packet);
+ lua_setfield(lua, -2, "client_duid");
+ lua_pushstring(lua, daemon->dhcp_packet.iov_base);
+ lua_setfield(lua, -2, "server_duid");
+ lua_pushstring(lua, daemon->dhcp_buff3);
+ lua_setfield(lua, -2, "iaid");
+ }
+
+ if (!is6 && data.clid_len != 0)
+ {
+ lua_pushstring(lua, daemon->packet);
+ lua_setfield(lua, -2, "client_id");
+ }
+
+ if (strlen(data.interface) != 0)
+ {
+ lua_pushstring(lua, data.interface);
+ lua_setfield(lua, -2, "interface");
+ }
+
+#ifdef HAVE_BROKEN_RTC
+ lua_pushnumber(lua, data.length);
+ lua_setfield(lua, -2, "lease_length");
+#else
+ lua_pushnumber(lua, data.expires);
+ lua_setfield(lua, -2, "lease_expires");
+#endif
+
+ if (hostname)
+ {
+ lua_pushstring(lua, hostname);
+ lua_setfield(lua, -2, "hostname");
+ }
+
+ if (domain)
+ {
+ lua_pushstring(lua, domain);
+ lua_setfield(lua, -2, "domain");
+ }
+
+ end = extradata + data.ed_len;
+ buf = extradata;
+
+ if (!is6)
+ buf = grab_extradata_lua(buf, end, "vendor_class");
+#ifdef HAVE_DHCP6
+ else if (data.vendorclass_count != 0)
+ {
+ sprintf(daemon->dhcp_buff2, "vendor_class_id");
+ buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
+ for (i = 0; i < data.vendorclass_count - 1; i++)
+ {
+ sprintf(daemon->dhcp_buff2, "vendor_class%i", i);
+ buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
+ }
+ }
+#endif
+
+ buf = grab_extradata_lua(buf, end, "supplied_hostname");
+
+ if (!is6)
+ {
+ buf = grab_extradata_lua(buf, end, "cpewan_oui");
+ buf = grab_extradata_lua(buf, end, "cpewan_serial");
+ buf = grab_extradata_lua(buf, end, "cpewan_class");
+ buf = grab_extradata_lua(buf, end, "circuit_id");
+ buf = grab_extradata_lua(buf, end, "subscriber_id");
+ buf = grab_extradata_lua(buf, end, "remote_id");
+ }
+
+ buf = grab_extradata_lua(buf, end, "tags");
+
+ if (is6)
+ buf = grab_extradata_lua(buf, end, "relay_address");
+ else if (data.giaddr.s_addr != 0)
+ {
+ inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN);
+ lua_pushstring(lua, daemon->addrbuff);
+ lua_setfield(lua, -2, "relay_address");
+ }
+
+ for (i = 0; buf; i++)
+ {
+ sprintf(daemon->dhcp_buff2, "user_class%i", i);
+ buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
+ }
+
+ if (data.action != ACTION_DEL && data.remaining_time != 0)
+ {
+ lua_pushnumber(lua, data.remaining_time);
+ lua_setfield(lua, -2, "time_remaining");
+ }
+
+ if (data.action == ACTION_OLD_HOSTNAME && hostname)
+ {
+ lua_pushstring(lua, hostname);
+ lua_setfield(lua, -2, "old_hostname");
+ }
+
+ if (!is6 || data.hwaddr_len != 0)
+ {
+ lua_pushstring(lua, daemon->dhcp_buff);
+ lua_setfield(lua, -2, "mac_address");
+ }
+
+ lua_pushstring(lua, daemon->addrbuff);
+ lua_setfield(lua, -2, "ip_address");
+
+ lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
+ }
+ }
+#endif
+
+ /* no script, just lua */
+ if (!daemon->lease_change_command)
+ continue;
+
+ /* Pipe to capture stdout and stderr from script */
+ if (!option_bool(OPT_DEBUG) && pipe(pipeout) == -1)
+ continue;
+
+ /* possible fork errors are all temporary resource problems */
+ while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
+ sleep(2);
+
+ if (pid == -1)
+ {
+ if (!option_bool(OPT_DEBUG))
+ {
+ close(pipeout[0]);
+ close(pipeout[1]);
+ }
+ continue;
+ }
+
+ /* wait for child to complete */
+ if (pid != 0)
+ {
+ if (!option_bool(OPT_DEBUG))
+ {
+ FILE *fp;
+
+ close(pipeout[1]);
+
+ /* Read lines sent to stdout/err by the script and pass them back to be logged */
+ if (!(fp = fdopen(pipeout[0], "r")))
+ close(pipeout[0]);
+ else
+ {
+ while (fgets(daemon->packet, daemon->packet_buff_sz, fp))
+ {
+ /* do not include new lines, log will append them */
+ size_t len = strlen(daemon->packet);
+ if (len > 0)
+ {
+ --len;
+ if (daemon->packet[len] == '\n')
+ daemon->packet[len] = 0;
+ }
+ send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet);
+ }
+ fclose(fp);
+ }
+ }
+
+ /* reap our children's children, if necessary */
+ while (1)
+ {
+ 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), NULL);
+ else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+ send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status), NULL);
+ break;
+ }
+
+ if (rc == -1 && errno != EINTR)
+ break;
+ }
+
+ continue;
+ }
+
+ if (!option_bool(OPT_DEBUG))
+ {
+ /* map stdout/stderr of script to pipeout */
+ close(pipeout[0]);
+ dup2(pipeout[1], STDOUT_FILENO);
+ dup2(pipeout[1], STDERR_FILENO);
+ close(pipeout[1]);
+ }
+
+ if (data.action != ACTION_TFTP && data.action != ACTION_ARP)
+ {
+#ifdef HAVE_DHCP6
+ my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err);
+ my_setenv("DNSMASQ_SERVER_DUID", is6 ? daemon->dhcp_packet.iov_base : NULL, &err);
+ my_setenv("DNSMASQ_MAC", is6 && data.hwaddr_len != 0 ? daemon->dhcp_buff : NULL, &err);
+#endif
+
+ my_setenv("DNSMASQ_CLIENT_ID", !is6 && data.clid_len != 0 ? daemon->packet : NULL, &err);
+ my_setenv("DNSMASQ_INTERFACE", strlen(data.interface) != 0 ? data.interface : NULL, &err);
+
+#ifdef HAVE_BROKEN_RTC
+ sprintf(daemon->dhcp_buff2, "%u", data.length);
+ my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
+#else
+ sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires);
+ my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err);
+#endif
+
+ my_setenv("DNSMASQ_DOMAIN", domain, &err);
+
+ end = extradata + data.ed_len;
+ buf = extradata;
+
+ if (!is6)
+ buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err);
+#ifdef HAVE_DHCP6
+ else
+ {
+ if (data.vendorclass_count != 0)
+ {
+ buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS_ID", &err);
+ for (i = 0; i < data.vendorclass_count - 1; i++)
+ {
+ sprintf(daemon->dhcp_buff2, "DNSMASQ_VENDOR_CLASS%i", i);
+ buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
+ }
+ }
+ }
+#endif
+
+ buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err);
+
+ if (!is6)
+ {
+ 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_CIRCUIT_ID", &err);
+ buf = grab_extradata(buf, end, "DNSMASQ_SUBSCRIBER_ID", &err);
+ buf = grab_extradata(buf, end, "DNSMASQ_REMOTE_ID", &err);
+ buf = grab_extradata(buf, end, "DNSMASQ_REQUESTED_OPTIONS", &err);
+ }
+
+ buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err);
+
+ if (is6)
+ buf = grab_extradata(buf, end, "DNSMASQ_RELAY_ADDRESS", &err);
+ else
+ {
+ const char *giaddr = NULL;
+ if (data.giaddr.s_addr != 0)
+ giaddr = inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN);
+ my_setenv("DNSMASQ_RELAY_ADDRESS", giaddr, &err);
+ }
+
+ for (i = 0; buf; i++)
+ {
+ sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i);
+ buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
+ }
+
+ sprintf(daemon->dhcp_buff2, "%u", data.remaining_time);
+ my_setenv("DNSMASQ_TIME_REMAINING", data.action != ACTION_DEL && data.remaining_time != 0 ? daemon->dhcp_buff2 : NULL, &err);
+
+ my_setenv("DNSMASQ_OLD_HOSTNAME", data.action == ACTION_OLD_HOSTNAME ? hostname : NULL, &err);
+ if (data.action == ACTION_OLD_HOSTNAME)
+ hostname = NULL;
+
+ my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err);
+ }
+
+ /* 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,
+ (is6 && data.action != ACTION_ARP) ? daemon->packet : daemon->dhcp_buff,
+ daemon->addrbuff, hostname, (char*)NULL);
+ err = errno;
+ }
+ /* failed, send event so the main process logs the problem */
+ send_event(event_fd, EVENT_EXEC_ERR, err, NULL);
+ _exit(0);
+ }
+}
+
+static void my_setenv(const char *name, const char *value, int *error)
+{
+ if (*error == 0)
+ {
+ if (!value)
+ unsetenv(name);
+ else if (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 = NULL;
+ char *val = NULL;
+
+ if (buf && (buf != end))
+ {
+ for (next = buf; ; next++)
+ if (next == end)
+ {
+ next = NULL;
+ break;
+ }
+ else if (*next == 0)
+ break;
+
+ if (next && (next != buf))
+ {
+ char *p;
+ /* No "=" in value */
+ if ((p = strchr((char *)buf, '=')))
+ *p = 0;
+ val = (char *)buf;
+ }
+ }
+
+ my_setenv(env, val, err);
+
+ return next ? next + 1 : NULL;
+}
+
+#ifdef HAVE_LUASCRIPT
+static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field)
+{
+ unsigned char *next;
+
+ if (!buf || (buf == end))
+ return NULL;
+
+ for (next = buf; *next != 0; next++)
+ if (next == end)
+ return NULL;
+
+ if (next != buf)
+ {
+ lua_pushstring(lua, (char *)buf);
+ lua_setfield(lua, -2, field);
+ }
+
+ return next + 1;
+}
+#endif
+
+static void buff_alloc(size_t size)
+{
+ 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;
+ }
+}
+
+/* pack up lease data into a buffer */
+void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
+{
+ unsigned char *p;
+ unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
+ int fd = daemon->dhcpfd;
+#ifdef HAVE_DHCP6
+ if (!daemon->dhcp)
+ fd = daemon->dhcp6fd;
+#endif
+
+ /* 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;
+
+ buff_alloc(sizeof(struct script_data) + clid_len + ed_len + hostname_len);
+
+ buf->action = action;
+ buf->flags = lease->flags;
+#ifdef HAVE_DHCP6
+ buf->vendorclass_count = lease->vendorclass_count;
+ buf->addr6 = lease->addr6;
+ buf->iaid = lease->iaid;
+#endif
+ 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, DHCP_CHADDR_MAX);
+ if (!indextoname(fd, lease->last_interface, buf->interface))
+ buf->interface[0] = 0;
+
+#ifdef HAVE_BROKEN_RTC
+ buf->length = lease->length;
+#else
+ buf->expires = lease->expires;
+#endif
+
+ if (lease->expires != 0)
+ buf->remaining_time = (unsigned int)difftime(lease->expires, now);
+ else
+ buf->remaining_time = 0;
+
+ 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;
+}
+
+#ifdef HAVE_TFTP
+/* This nastily re-uses DHCP-fields for TFTP stuff */
+void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer)
+{
+ unsigned int filename_len;
+
+ /* no script */
+ if (daemon->helperfd == -1)
+ return;
+
+ filename_len = strlen(filename) + 1;
+ buff_alloc(sizeof(struct script_data) + filename_len);
+ memset(buf, 0, sizeof(struct script_data));
+
+ buf->action = ACTION_TFTP;
+ buf->hostname_len = filename_len;
+ buf->file_len = file_len;
+
+ if ((buf->flags = peer->sa.sa_family) == AF_INET)
+ buf->addr = peer->in.sin_addr;
+ else
+ buf->addr6 = peer->in6.sin6_addr;
+
+ memcpy((unsigned char *)(buf+1), filename, filename_len);
+
+ bytes_in_buf = sizeof(struct script_data) + filename_len;
+}
+#endif
+
+void queue_arp(int action, unsigned char *mac, int maclen, int family, union all_addr *addr)
+{
+ /* no script */
+ if (daemon->helperfd == -1)
+ return;
+
+ buff_alloc(sizeof(struct script_data));
+ memset(buf, 0, sizeof(struct script_data));
+
+ buf->action = action;
+ buf->hwaddr_len = maclen;
+ buf->hwaddr_type = ARPHRD_ETHER;
+ if ((buf->flags = family) == AF_INET)
+ buf->addr = addr->addr4;
+ else
+ buf->addr6 = addr->addr6;
+
+ memcpy(buf->hwaddr, mac, maclen);
+
+ bytes_in_buf = sizeof(struct script_data);
+}
+
+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 /* HAVE_SCRIPT */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c b/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c
new file mode 100755
index 0000000..5776feb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c
@@ -0,0 +1,297 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_INOTIFY
+
+#include <sys/inotify.h>
+#include <sys/param.h> /* For MAXSYMLINKS */
+
+/* the strategy is to set an inotify on the directories containing
+ resolv files, for any files in the directory which are close-write
+ or moved into the directory.
+
+ When either of those happen, we look to see if the file involved
+ is actually a resolv-file, and if so, call poll-resolv with
+ the "force" argument, to ensure it's read.
+
+ This adds one new error condition: the directories containing
+ all specified resolv-files must exist at start-up, even if the actual
+ files don't.
+*/
+
+static char *inotify_buffer;
+#define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1)
+
+/* If path is a symbolic link, return the path it
+ points to, made absolute if relative.
+ If path doesn't exist or is not a symlink, return NULL.
+ Return value is malloc'ed */
+static char *my_readlink(char *path)
+{
+ ssize_t rc, size = 64;
+ char *buf;
+
+ while (1)
+ {
+ buf = safe_malloc(size);
+ rc = readlink(path, buf, (size_t)size);
+
+ if (rc == -1)
+ {
+ /* Not link or doesn't exist. */
+ if (errno == EINVAL || errno == ENOENT)
+ {
+ free(buf);
+ return NULL;
+ }
+ else
+ die(_("cannot access path %s: %s"), path, EC_MISC);
+ }
+ else if (rc < size-1)
+ {
+ char *d;
+
+ buf[rc] = 0;
+ if (buf[0] != '/' && ((d = strrchr(path, '/'))))
+ {
+ /* Add path to relative link */
+ char *new_buf = safe_malloc((d - path) + strlen(buf) + 2);
+ *(d+1) = 0;
+ strcpy(new_buf, path);
+ strcat(new_buf, buf);
+ free(buf);
+ buf = new_buf;
+ }
+ return buf;
+ }
+
+ /* Buffer too small, increase and retry */
+ size += 64;
+ free(buf);
+ }
+}
+
+void inotify_dnsmasq_init()
+{
+ struct resolvc *res;
+ inotify_buffer = safe_malloc(INOTIFY_SZ);
+ daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+
+ if (daemon->inotifyfd == -1)
+ die(_("failed to create inotify: %s"), NULL, EC_MISC);
+
+ if (option_bool(OPT_NO_RESOLV))
+ return;
+
+ for (res = daemon->resolv_files; res; res = res->next)
+ {
+ char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1);
+ int links = MAXSYMLINKS;
+
+ strcpy(path, res->name);
+
+ /* Follow symlinks until we reach a non-symlink, or a non-existent file. */
+ while ((new_path = my_readlink(path)))
+ {
+ if (links-- == 0)
+ die(_("too many symlinks following %s"), res->name, EC_MISC);
+ free(path);
+ path = new_path;
+ }
+
+ res->wd = -1;
+
+ if ((d = strrchr(path, '/')))
+ {
+ *d = 0; /* make path just directory */
+ res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO);
+
+ res->file = d+1; /* pointer to filename */
+ *d = '/';
+
+ if (res->wd == -1 && errno == ENOENT)
+ die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
+ }
+
+ if (res->wd == -1)
+ die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
+
+ }
+}
+
+
+/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
+void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
+{
+ struct hostsfile *ah;
+
+ for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+ {
+ DIR *dir_stream = NULL;
+ struct dirent *ent;
+ struct stat buf;
+
+ if (!(ah->flags & flag))
+ continue;
+
+ if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
+ {
+ my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"),
+ ah->fname, strerror(errno));
+ continue;
+ }
+
+ if (!(ah->flags & AH_WD_DONE))
+ {
+ ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
+ ah->flags |= AH_WD_DONE;
+ }
+
+ /* Read contents of dir _after_ calling add_watch, in the hope of avoiding
+ a race which misses files being added as we start */
+ if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
+ {
+ my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
+ ah->fname, strerror(errno));
+ continue;
+ }
+
+ while ((ent = readdir(dir_stream)))
+ {
+ size_t lendir = strlen(ah->fname);
+ size_t lenfile = strlen(ent->d_name);
+ 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;
+
+ if ((path = whine_malloc(lendir + lenfile + 2)))
+ {
+ strcpy(path, ah->fname);
+ strcat(path, "/");
+ strcat(path, ent->d_name);
+
+ /* ignore non-regular files */
+ if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
+ {
+ if (ah->flags & AH_HOSTS)
+ total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
+#ifdef HAVE_DHCP
+ else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT))
+ option_read_dynfile(path, ah->flags);
+#endif
+ }
+
+ free(path);
+ }
+ }
+
+ closedir(dir_stream);
+ }
+}
+
+int inotify_check(time_t now)
+{
+ int hit = 0;
+ struct hostsfile *ah;
+
+ while (1)
+ {
+ int rc;
+ char *p;
+ struct resolvc *res;
+ struct inotify_event *in;
+
+ while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
+
+ if (rc <= 0)
+ break;
+
+ for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len)
+ {
+ size_t namelen;
+
+ in = (struct inotify_event*)p;
+
+ /* ignore emacs backups and dotfiles */
+ if (in->len == 0 || (namelen = strlen(in->name)) == 0 ||
+ in->name[namelen - 1] == '~' ||
+ (in->name[0] == '#' && in->name[namelen - 1] == '#') ||
+ in->name[0] == '.')
+ continue;
+
+ for (res = daemon->resolv_files; res; res = res->next)
+ if (res->wd == in->wd && strcmp(res->file, in->name) == 0)
+ hit = 1;
+
+ for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+ if (ah->wd == in->wd)
+ {
+ size_t lendir = strlen(ah->fname);
+ char *path;
+
+ if ((path = whine_malloc(lendir + in->len + 2)))
+ {
+ strcpy(path, ah->fname);
+ strcat(path, "/");
+ strcat(path, in->name);
+
+ my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path);
+
+ if (ah->flags & AH_HOSTS)
+ {
+ read_hostsfile(path, ah->index, 0, NULL, 0);
+#ifdef HAVE_DHCP
+ if (daemon->dhcp || daemon->doing_dhcp6)
+ {
+ /* Propagate the consequences of loading a new dhcp-host */
+ dhcp_update_configs(daemon->dhcp_conf);
+ lease_update_from_configs();
+ lease_update_file(now);
+ lease_update_dns(1);
+ }
+#endif
+ }
+#ifdef HAVE_DHCP
+ else if (ah->flags & AH_DHCP_HST)
+ {
+ if (option_read_dynfile(path, AH_DHCP_HST))
+ {
+ /* Propagate the consequences of loading a new dhcp-host */
+ dhcp_update_configs(daemon->dhcp_conf);
+ lease_update_from_configs();
+ lease_update_file(now);
+ lease_update_dns(1);
+ }
+ }
+ else if (ah->flags & AH_DHCP_OPT)
+ option_read_dynfile(path, AH_DHCP_OPT);
+#endif
+
+ free(path);
+ }
+ }
+ }
+ }
+ return hit;
+}
+
+#endif /* INOTIFY */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h b/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h
new file mode 100755
index 0000000..6388c7d
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h
@@ -0,0 +1,33 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 IN6_IS_ADDR_ULA(a) \
+ ((((__const uint32_t *) (a))[0] & htonl (0xff000000)) \
+ == htonl (0xfd000000))
+
+#define IN6_IS_ADDR_ULA_ZERO(a) \
+ (((__const uint32_t *) (a))[0] == htonl (0xfd000000) \
+ && ((__const uint32_t *) (a))[1] == 0 \
+ && ((__const uint32_t *) (a))[2] == 0 \
+ && ((__const uint32_t *) (a))[3] == 0)
+
+#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \
+ (((__const uint32_t *) (a))[0] == htonl (0xfe800000) \
+ && ((__const uint32_t *) (a))[1] == 0 \
+ && ((__const uint32_t *) (a))[2] == 0 \
+ && ((__const uint32_t *) (a))[3] == 0)
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c b/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c
new file mode 100755
index 0000000..0c014cb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c
@@ -0,0 +1,216 @@
+/* ipset.c is Copyright (c) 2013 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+
+ 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_IPSET) && defined(HAVE_LINUX_NETWORK)
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+
+/* We want to be able to compile against old header files
+ Kernel version is handled at run-time. */
+
+#define NFNL_SUBSYS_IPSET 6
+
+#define IPSET_ATTR_DATA 7
+#define IPSET_ATTR_IP 1
+#define IPSET_ATTR_IPADDR_IPV4 1
+#define IPSET_ATTR_IPADDR_IPV6 2
+#define IPSET_ATTR_PROTOCOL 1
+#define IPSET_ATTR_SETNAME 2
+#define IPSET_CMD_ADD 9
+#define IPSET_CMD_DEL 10
+#define IPSET_MAXNAMELEN 32
+#define IPSET_PROTOCOL 6
+
+#ifndef NFNETLINK_V0
+#define NFNETLINK_V0 0
+#endif
+
+#ifndef NLA_F_NESTED
+#define NLA_F_NESTED (1 << 15)
+#endif
+
+#ifndef NLA_F_NET_BYTEORDER
+#define NLA_F_NET_BYTEORDER (1 << 14)
+#endif
+
+struct my_nlattr {
+ __u16 nla_len;
+ __u16 nla_type;
+};
+
+struct my_nfgenmsg {
+ __u8 nfgen_family; /* AF_xxx */
+ __u8 version; /* nfnetlink version */
+ __be16 res_id; /* resource id */
+};
+
+
+/* data structure size in here is fixed */
+#define BUFF_SZ 256
+
+#define NL_ALIGN(len) (((len)+3) & ~(3))
+static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
+static int ipset_sock, old_kernel;
+static char *buffer;
+
+static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data)
+{
+ struct my_nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len);
+ uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len;
+ attr->nla_type = type;
+ attr->nla_len = payload_len;
+ memcpy((void *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len);
+ nlh->nlmsg_len += NL_ALIGN(payload_len);
+}
+
+void ipset_init(void)
+{
+ old_kernel = (daemon->kernel_version < KERNEL_VERSION(2,6,32));
+
+ if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1)
+ return;
+
+ if (!old_kernel &&
+ (buffer = safe_malloc(BUFF_SZ)) &&
+ (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 &&
+ (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1))
+ return;
+
+ die (_("failed to create IPset control socket: %s"), NULL, EC_MISC);
+}
+
+static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr, int af, int remove)
+{
+ struct nlmsghdr *nlh;
+ struct my_nfgenmsg *nfg;
+ struct my_nlattr *nested[2];
+ uint8_t proto;
+ int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ;
+
+ if (strlen(setname) >= IPSET_MAXNAMELEN)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memset(buffer, 0, BUFF_SZ);
+
+ nlh = (struct nlmsghdr *)buffer;
+ nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr));
+ nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8);
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+
+ nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len);
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg));
+ nfg->nfgen_family = af;
+ nfg->version = NFNETLINK_V0;
+ nfg->res_id = htons(0);
+
+ proto = IPSET_PROTOCOL;
+ add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto);
+ add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname);
+ nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
+ nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA;
+ nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
+ nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP;
+ add_attr(nlh,
+ (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER,
+ addrsz, ipaddr);
+ nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1];
+ nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0];
+
+ while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0,
+ (struct sockaddr *)&snl, sizeof(snl))));
+
+ return errno == 0 ? 0 : -1;
+}
+
+
+static int old_add_to_ipset(const char *setname, const union all_addr *ipaddr, int remove)
+{
+ socklen_t size;
+ struct ip_set_req_adt_get {
+ unsigned op;
+ unsigned version;
+ union {
+ char name[IPSET_MAXNAMELEN];
+ uint16_t index;
+ } set;
+ char typename[IPSET_MAXNAMELEN];
+ } req_adt_get;
+ struct ip_set_req_adt {
+ unsigned op;
+ uint16_t index;
+ uint32_t ip;
+ } req_adt;
+
+ if (strlen(setname) >= sizeof(req_adt_get.set.name))
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ req_adt_get.op = 0x10;
+ req_adt_get.version = 3;
+ strcpy(req_adt_get.set.name, setname);
+ size = sizeof(req_adt_get);
+ if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0)
+ return -1;
+ req_adt.op = remove ? 0x102 : 0x101;
+ req_adt.index = req_adt_get.set.index;
+ req_adt.ip = ntohl(ipaddr->addr4.s_addr);
+ if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove)
+{
+ int ret = 0, af = AF_INET;
+
+ if (flags & F_IPV6)
+ {
+ af = AF_INET6;
+ /* old method only supports IPv4 */
+ if (old_kernel)
+ {
+ errno = EAFNOSUPPORT ;
+ ret = -1;
+ }
+ }
+
+ if (ret != -1)
+ ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove);
+
+ if (ret == -1)
+ my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno));
+
+ return ret;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c b/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c
new file mode 100755
index 0000000..1a9f1c6
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c
@@ -0,0 +1,1205 @@
+/* dnsmasq is Copyright (c) 2000-2021 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;
+
+static int read_leases(time_t now, FILE *leasestream)
+{
+ unsigned long ei;
+ union all_addr addr;
+ struct dhcp_lease *lease;
+ int clid_len, hw_len, hw_type;
+ int items;
+ char *domain = NULL;
+
+ *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
+
+ /* client-id max length is 255 which is 255*2 digits + 254 colons
+ borrow DNS packet buffer which is always larger than 1000 bytes
+
+ Check various buffers are big enough for the code below */
+
+#if (DHCP_BUFF_SZ < 255) || (MAXDNAME < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764)
+# error Buffer size breakage in leasefile parsing.
+#endif
+
+ while ((items=fscanf(leasestream, "%255s %255s", daemon->dhcp_buff3, daemon->dhcp_buff2)) == 2)
+ {
+ *daemon->namebuff = *daemon->dhcp_buff = *daemon->packet = '\0';
+ hw_len = hw_type = clid_len = 0;
+
+#ifdef HAVE_DHCP6
+ if (strcmp(daemon->dhcp_buff3, "duid") == 0)
+ {
+ daemon->duid_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, 130, NULL, NULL);
+ if (daemon->duid_len < 0)
+ return 0;
+ daemon->duid = safe_malloc(daemon->duid_len);
+ memcpy(daemon->duid, daemon->dhcp_buff2, daemon->duid_len);
+ continue;
+ }
+#endif
+
+ if (fscanf(leasestream, " %64s %255s %764s",
+ daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3)
+ {
+ my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database: %s %s %s %s ..."),
+ daemon->dhcp_buff3, daemon->dhcp_buff2,
+ daemon->namebuff, daemon->dhcp_buff);
+ continue;
+ }
+
+ if (inet_pton(AF_INET, daemon->namebuff, &addr.addr4))
+ {
+ if ((lease = lease4_allocate(addr.addr4)))
+ domain = get_domain(lease->addr);
+
+ hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type);
+ /* For backwards compatibility, no explicit MAC address type means ether. */
+ if (hw_type == 0 && hw_len != 0)
+ hw_type = ARPHRD_ETHER;
+ }
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr6))
+ {
+ char *s = daemon->dhcp_buff2;
+ int lease_type = LEASE_NA;
+
+ if (s[0] == 'T')
+ {
+ lease_type = LEASE_TA;
+ s++;
+ }
+
+ if ((lease = lease6_allocate(&addr.addr6, lease_type)))
+ {
+ lease_set_iaid(lease, strtoul(s, NULL, 10));
+ domain = get_domain6(&lease->addr6);
+ }
+ }
+#endif
+ else
+ {
+ my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database, bad address: %s"),
+ daemon->namebuff);
+ continue;
+ }
+
+
+ if (!lease)
+ die (_("too many stored leases"), NULL, EC_MISC);
+
+ if (strcmp(daemon->packet, "*") != 0)
+ clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
+
+ lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet,
+ hw_len, hw_type, clid_len, now, 0);
+
+ if (strcmp(daemon->dhcp_buff, "*") != 0)
+ lease_set_hostname(lease, daemon->dhcp_buff, 0, domain, NULL);
+
+ ei = atol(daemon->dhcp_buff3);
+
+#ifdef HAVE_BROKEN_RTC
+ if (ei != 0)
+ lease->expires = (time_t)ei + now;
+ 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
+
+ /* set these correctly: the "old" events are generated later from
+ the startup synthesised SIGHUP. */
+ lease->flags &= ~(LEASE_NEW | LEASE_CHANGED);
+
+ *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
+ }
+
+ return (items == 0 || items == EOF);
+}
+
+void lease_init(time_t now)
+{
+ FILE *leasestream;
+
+ leases_left = daemon->dhcp_max;
+
+ if (option_bool(OPT_LEASE_RO))
+ {
+ /* run "<lease_change_script> init" once to get the
+ initial state of the database. If leasefile-ro is
+ set without a script, we just do without any
+ lease database. */
+#ifdef HAVE_SCRIPT
+ if (daemon->lease_change_command)
+ {
+ strcpy(daemon->dhcp_buff, daemon->lease_change_command);
+ strcat(daemon->dhcp_buff, " init");
+ leasestream = popen(daemon->dhcp_buff, "r");
+ }
+ else
+#endif
+ {
+ file_dirty = dns_dirty = 0;
+ return;
+ }
+
+ }
+ else
+ {
+ /* NOTE: need a+ mode to create file if it doesn't exist */
+ leasestream = daemon->lease_stream = fopen(daemon->lease_file, "a+");
+
+ if (!leasestream)
+ die(_("cannot open or create lease file %s: %s"), daemon->lease_file, EC_FILE);
+
+ /* a+ mode leaves pointer at end. */
+ rewind(leasestream);
+ }
+
+ if (leasestream)
+ {
+ if (!read_leases(now, leasestream))
+ my_syslog(MS_DHCP | LOG_ERR, _("failed to parse lease database cleanly"));
+
+ if (ferror(leasestream))
+ die(_("failed to read lease file %s: %s"), daemon->lease_file, EC_FILE);
+ }
+
+#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 (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+ else if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len,
+ lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL, NULL)) &&
+ (config->flags & CONFIG_NAME) &&
+ (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr))
+ lease_set_hostname(lease, config->hostname, 1, get_domain(lease->addr), NULL);
+ else if ((name = host_from_dns(lease->addr)))
+ lease_set_hostname(lease, name, 1, get_domain(lease->addr), NULL); /* updates auth flag only */
+}
+
+static void ourprintf(int *errp, char *format, ...)
+{
+ va_list ap;
+
+ 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_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+#endif
+
+#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, ":");
+ }
+
+ inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
+
+ ourprintf(&err, " %s ", daemon->addrbuff);
+ 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");
+ }
+
+#ifdef HAVE_DHCP6
+ if (daemon->duid)
+ {
+ ourprintf(&err, "duid ");
+ for (i = 0; i < daemon->duid_len - 1; i++)
+ ourprintf(&err, "%.2x:", daemon->duid[i]);
+ ourprintf(&err, "%.2x\n", daemon->duid[i]);
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+
+ if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+ continue;
+
+#ifdef HAVE_BROKEN_RTC
+ ourprintf(&err, "%u ", lease->length);
+#else
+ ourprintf(&err, "%lu ", (unsigned long)lease->expires);
+#endif
+
+ inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+
+ ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "",
+ lease->iaid, daemon->addrbuff);
+ 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");
+ }
+ }
+#endif
+
+ 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. */
+ next_event = 0;
+
+#ifdef HAVE_DHCP6
+ /* do timed RAs and determine when the next is, also pings to potential SLAAC addresses */
+ if (daemon->doing_ra)
+ {
+ time_t event;
+
+ if ((event = periodic_slaac(now, leases)) != 0)
+ {
+ if (next_event == 0 || difftime(next_event, event) > 0.0)
+ next_event = event;
+ }
+
+ if ((event = periodic_ra(now)) != 0)
+ {
+ if (next_event == 0 || difftime(next_event, event) > 0.0)
+ next_event = event;
+ }
+ }
+#endif
+
+ for (lease = leases; lease; lease = lease->next)
+ if (lease->expires != 0 &&
+ (next_event == 0 || difftime(next_event, lease->expires) > 0.0))
+ next_event = lease->expires;
+
+ 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 %u s)"),
+ daemon->lease_file, strerror(err),
+ (unsigned int)difftime(next_event, now));
+ }
+
+ send_alarm(next_event, now);
+}
+
+
+static int find_interface_v4(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+ struct dhcp_lease *lease;
+ int prefix = netmask_length(netmask);
+
+ (void) label;
+ (void) broadcast;
+ (void) vparam;
+
+ for (lease = leases; lease; lease = lease->next)
+ if (!(lease->flags & (LEASE_TA | LEASE_NA)) &&
+ is_same_net(local, lease->addr, netmask) &&
+ prefix > lease->new_prefixlen)
+ {
+ lease->new_interface = if_index;
+ lease->new_prefixlen = prefix;
+ }
+
+ return 1;
+}
+
+#ifdef HAVE_DHCP6
+static int find_interface_v6(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ int preferred, int valid, void *vparam)
+{
+ struct dhcp_lease *lease;
+
+ (void)scope;
+ (void)flags;
+ (void)preferred;
+ (void)valid;
+ (void)vparam;
+
+ for (lease = leases; lease; lease = lease->next)
+ if ((lease->flags & (LEASE_TA | LEASE_NA)))
+ if (is_same_net6(local, &lease->addr6, prefix) && prefix > lease->new_prefixlen) {
+ /* save prefix length for comparison, as we might get shorter matching
+ * prefix in upcoming netlink GETADDR responses
+ * */
+ lease->new_interface = if_index;
+ lease->new_prefixlen = prefix;
+ }
+
+ return 1;
+}
+
+void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface)
+{
+ /* We may be doing RA but not DHCPv4, in which case the lease
+ database may not exist and we have nothing to do anyway */
+ if (daemon->dhcp)
+ slaac_ping_reply(sender, packet, interface, leases);
+}
+
+void lease_update_slaac(time_t now)
+{
+ /* Called when we construct a new RA-names context, to add putative
+ new SLAAC addresses to existing leases. */
+
+ struct dhcp_lease *lease;
+
+ if (daemon->dhcp)
+ for (lease = leases; lease; lease = lease->next)
+ slaac_add_addrs(lease, now, 0);
+}
+
+#endif
+
+
+/* Find interfaces associated with leases at start-up. This gets updated as
+ we do DHCP transactions, but information about directly-connected subnets
+ is useful from scrips and necessary for determining SLAAC addresses from
+ start-time. */
+void lease_find_interfaces(time_t now)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ lease->new_prefixlen = lease->new_interface = 0;
+
+ iface_enumerate(AF_INET, &now, find_interface_v4);
+#ifdef HAVE_DHCP6
+ iface_enumerate(AF_INET6, &now, find_interface_v6);
+#endif
+
+ for (lease = leases; lease; lease = lease->next)
+ if (lease->new_interface != 0)
+ lease_set_interface(lease, lease->new_interface, now);
+}
+
+#ifdef HAVE_DHCP6
+void lease_make_duid(time_t now)
+{
+ /* If we're not doing DHCPv6, and there are not v6 leases, don't add the DUID to the database */
+ if (!daemon->duid && daemon->doing_dhcp6)
+ {
+ file_dirty = 1;
+ make_duid(now);
+ }
+}
+#endif
+
+
+
+
+void lease_update_dns(int force)
+{
+ struct dhcp_lease *lease;
+
+ if (daemon->port != 0 && (dns_dirty || force))
+ {
+#ifndef HAVE_BROKEN_RTC
+ /* force transfer to authoritative secondaries */
+ daemon->soa_sn++;
+#endif
+
+ cache_unhash_dhcp();
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+ int prot = AF_INET;
+
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ prot = AF_INET6;
+ else if (lease->hostname || lease->fqdn)
+ {
+ struct slaac_address *slaac;
+
+ for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+ if (slaac->backoff == 0)
+ {
+ if (lease->fqdn)
+ cache_add_dhcp_entry(lease->fqdn, AF_INET6, (union all_addr *)&slaac->addr, lease->expires);
+ if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+ cache_add_dhcp_entry(lease->hostname, AF_INET6, (union all_addr *)&slaac->addr, lease->expires);
+ }
+ }
+
+ if (lease->fqdn)
+ cache_add_dhcp_entry(lease->fqdn, prot,
+ prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6,
+ lease->expires);
+
+ if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+ cache_add_dhcp_entry(lease->hostname, prot,
+ prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6,
+ lease->expires);
+
+#else
+ if (lease->fqdn)
+ cache_add_dhcp_entry(lease->fqdn, prot, (union all_addr *)&lease->addr, lease->expires);
+
+ if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+ cache_add_dhcp_entry(lease->hostname, prot, (union all_addr *)&lease->addr, lease->expires);
+#endif
+ }
+
+ 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;
+
+ daemon->metrics[lease->addr.s_addr ? METRIC_LEASES_PRUNED_4 : METRIC_LEASES_PRUNED_6]++;
+
+ *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)
+ {
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+#endif
+ if (lease->clid && clid_len == lease->clid_len &&
+ memcmp(clid, lease->clid, clid_len) == 0)
+ return lease;
+ }
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+#endif
+ 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)
+ {
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+#endif
+ if (lease->addr.s_addr == addr.s_addr)
+ return lease;
+ }
+
+ return NULL;
+}
+
+#ifdef HAVE_DHCP6
+/* find address for {CLID, IAID, address} */
+struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len,
+ int lease_type, unsigned int iaid,
+ struct in6_addr *addr)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+ if (!(lease->flags & lease_type) || lease->iaid != iaid)
+ continue;
+
+ if (!IN6_ARE_ADDR_EQUAL(&lease->addr6, addr))
+ continue;
+
+ if ((clid_len != lease->clid_len ||
+ memcmp(clid, lease->clid, clid_len) != 0))
+ continue;
+
+ return lease;
+ }
+
+ return NULL;
+}
+
+/* reset "USED flags */
+void lease6_reset(void)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ lease->flags &= ~LEASE_USED;
+}
+
+/* enumerate all leases belonging to {CLID, IAID} */
+struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
+ unsigned char *clid, int clid_len,
+ unsigned int iaid)
+{
+ struct dhcp_lease *lease;
+
+ if (!first)
+ first = leases;
+ else
+ first = first->next;
+
+ for (lease = first; lease; lease = lease->next)
+ {
+ if (lease->flags & LEASE_USED)
+ continue;
+
+ if (!(lease->flags & lease_type) || lease->iaid != iaid)
+ continue;
+
+ if ((clid_len != lease->clid_len ||
+ memcmp(clid, lease->clid, clid_len) != 0))
+ continue;
+
+ return lease;
+ }
+
+ return NULL;
+}
+
+struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+ if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+ continue;
+
+ if (is_same_net6(&lease->addr6, net, prefix) &&
+ (prefix == 128 || addr6part(&lease->addr6) == addr))
+ return lease;
+ }
+
+ return NULL;
+}
+
+/* Find largest assigned address in context */
+u64 lease_find_max_addr6(struct dhcp_context *context)
+{
+ struct dhcp_lease *lease;
+ u64 addr = addr6part(&context->start6);
+
+ if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY)))
+ for (lease = leases; lease; lease = lease->next)
+ {
+ if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+ continue;
+
+ if (is_same_net6(&lease->addr6, &context->start6, 64) &&
+ addr6part(&lease->addr6) > addr6part(&context->start6) &&
+ addr6part(&lease->addr6) <= addr6part(&context->end6) &&
+ addr6part(&lease->addr6) > addr)
+ addr = addr6part(&lease->addr6);
+ }
+
+ return addr;
+}
+
+#endif
+
+/* Find largest assigned address in context */
+struct in_addr lease_find_max_addr(struct dhcp_context *context)
+{
+ struct dhcp_lease *lease;
+ struct in_addr addr = context->start;
+
+ if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY)))
+ for (lease = leases; lease; lease = lease->next)
+ {
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ continue;
+#endif
+ if (((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(context->start.s_addr)) &&
+ ((unsigned)ntohl(lease->addr.s_addr)) <= ((unsigned)ntohl(context->end.s_addr)) &&
+ ((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(addr.s_addr)))
+ addr = lease->addr;
+ }
+
+ return addr;
+}
+
+static struct dhcp_lease *lease_allocate(void)
+{
+ struct dhcp_lease *lease;
+ if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease))))
+ return NULL;
+
+ memset(lease, 0, sizeof(struct dhcp_lease));
+ lease->flags = LEASE_NEW;
+ lease->expires = 1;
+#ifdef HAVE_BROKEN_RTC
+ lease->length = 0xffffffff; /* illegal value */
+#endif
+ lease->hwaddr_len = 256; /* illegal value */
+ lease->next = leases;
+ leases = lease;
+
+ file_dirty = 1;
+ leases_left--;
+
+ return lease;
+}
+
+struct dhcp_lease *lease4_allocate(struct in_addr addr)
+{
+ struct dhcp_lease *lease = lease_allocate();
+ if (lease)
+ {
+ lease->addr = addr;
+ daemon->metrics[METRIC_LEASES_ALLOCATED_4]++;
+ }
+
+ return lease;
+}
+
+#ifdef HAVE_DHCP6
+struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type)
+{
+ struct dhcp_lease *lease = lease_allocate();
+
+ if (lease)
+ {
+ lease->addr6 = *addrp;
+ lease->flags |= lease_type;
+ lease->iaid = 0;
+
+ daemon->metrics[METRIC_LEASES_ALLOCATED_6]++;
+ }
+
+ return lease;
+}
+#endif
+
+void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
+{
+ time_t exp;
+
+ if (len == 0xffffffff)
+ {
+ exp = 0;
+ len = 0;
+ }
+ else
+ {
+ exp = now + (time_t)len;
+ /* Check for 2038 overflow. Make the lease
+ infinite in that case, as the least disruptive
+ thing we can do. */
+ if (difftime(exp, now) <= 0.0)
+ exp = 0;
+ }
+
+ if (exp != lease->expires)
+ {
+ dns_dirty = 1;
+ lease->expires = exp;
+#ifndef HAVE_BROKEN_RTC
+ lease->flags |= LEASE_AUX_CHANGED | LEASE_EXP_CHANGED;
+ file_dirty = 1;
+#endif
+ }
+
+#ifdef HAVE_BROKEN_RTC
+ if (len != lease->length)
+ {
+ lease->length = len;
+ lease->flags |= LEASE_AUX_CHANGED;
+ file_dirty = 1;
+ }
+#endif
+}
+
+#ifdef HAVE_DHCP6
+void lease_set_iaid(struct dhcp_lease *lease, unsigned int iaid)
+{
+ if (lease->iaid != iaid)
+ {
+ lease->iaid = iaid;
+ lease->flags |= LEASE_CHANGED;
+ }
+}
+#endif
+
+void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
+ const unsigned char *clid, int hw_len, int hw_type,
+ int clid_len, time_t now, int force)
+{
+#ifdef HAVE_DHCP6
+ int change = force;
+ lease->flags |= LEASE_HAVE_HWADDR;
+#endif
+
+ (void)force;
+ (void)now;
+
+ if (hw_len != lease->hwaddr_len ||
+ hw_type != lease->hwaddr_type ||
+ (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0))
+ {
+ if (hw_len != 0)
+ memcpy(lease->hwaddr, hwaddr, hw_len);
+ lease->hwaddr_len = hw_len;
+ lease->hwaddr_type = hw_type;
+ lease->flags |= 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->flags |= LEASE_AUX_CHANGED;
+ file_dirty = 1;
+ free(lease->clid);
+ if (!(lease->clid = whine_malloc(clid_len)))
+ return;
+#ifdef HAVE_DHCP6
+ change = 1;
+#endif
+ }
+ else if (memcmp(lease->clid, clid, clid_len) != 0)
+ {
+ lease->flags |= LEASE_AUX_CHANGED;
+ file_dirty = 1;
+#ifdef HAVE_DHCP6
+ change = 1;
+#endif
+ }
+
+ lease->clid_len = clid_len;
+ memcpy(lease->clid, clid, clid_len);
+ }
+
+#ifdef HAVE_DHCP6
+ if (change)
+ slaac_add_addrs(lease, now, force);
+#endif
+}
+
+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 unqualified 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, const char *name, int auth, char *domain, char *config_domain)
+{
+ struct dhcp_lease *lease_tmp;
+ char *new_name = NULL, *new_fqdn = NULL;
+
+ 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, name);
+
+ if (lease->hostname && name && hostname_isequal(lease->hostname, name))
+ {
+ if (auth)
+ lease->flags |= LEASE_AUTH_NAME;
+ 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.
+ Note that IPv6 leases are different. All the leases to the same DUID are
+ allowed the same name.
+
+ 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)))
+ {
+ strcpy(new_name, name);
+ if (domain && (new_fqdn = whine_malloc(strlen(new_name) + strlen(domain) + 2)))
+ {
+ strcpy(new_fqdn, name);
+ strcat(new_fqdn, ".");
+ strcat(new_fqdn, domain);
+ }
+ }
+
+ /* 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->flags & (LEASE_TA | LEASE_NA))
+ {
+ if (!(lease_tmp->flags & (LEASE_TA | LEASE_NA)))
+ continue;
+
+ /* another lease for the same DUID is OK for IPv6 */
+ if (lease->clid_len == lease_tmp->clid_len &&
+ lease->clid && lease_tmp->clid &&
+ memcmp(lease->clid, lease_tmp->clid, lease->clid_len) == 0)
+ continue;
+ }
+ else if (lease_tmp->flags & (LEASE_TA | LEASE_NA))
+ continue;
+
+ if ((lease_tmp->flags & LEASE_AUTH_NAME) && !auth)
+ {
+ free(new_name);
+ free(new_fqdn);
+ return;
+ }
+
+ kill_name(lease_tmp);
+ lease_tmp->flags |= LEASE_CHANGED; /* run script on change */
+ break;
+ }
+ }
+
+ if (lease->hostname)
+ kill_name(lease);
+
+ lease->hostname = new_name;
+ lease->fqdn = new_fqdn;
+
+ if (auth)
+ lease->flags |= LEASE_AUTH_NAME;
+
+ file_dirty = 1;
+ dns_dirty = 1;
+ lease->flags |= LEASE_CHANGED; /* run script on change */
+}
+
+void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
+{
+ (void)now;
+
+ if (lease->last_interface == interface)
+ return;
+
+ lease->last_interface = interface;
+ lease->flags |= LEASE_CHANGED;
+
+#ifdef HAVE_DHCP6
+ slaac_add_addrs(lease, now, 0);
+#endif
+}
+
+void rerun_scripts(void)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ lease->flags |= LEASE_CHANGED;
+}
+
+/* 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;
+
+ (void)now;
+
+#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
+ {
+#ifdef HAVE_DHCP6
+ struct slaac_address *slaac, *tmp;
+ for (slaac = lease->slaac_address; slaac; slaac = tmp)
+ {
+ tmp = slaac->next;
+ free(slaac);
+ }
+#endif
+ 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->flags & (LEASE_NEW | LEASE_CHANGED)) ||
+ ((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO)) ||
+ ((lease->flags & LEASE_EXP_CHANGED) && option_bool(OPT_LEASE_RENEW)))
+ {
+#ifdef HAVE_SCRIPT
+ queue_script((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease,
+ lease->fqdn ? lease->fqdn : lease->hostname, now);
+#endif
+#ifdef HAVE_DBUS
+ emit_dbus_signal((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease,
+ lease->fqdn ? lease->fqdn : lease->hostname);
+#endif
+ lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED | LEASE_EXP_CHANGED);
+
+ /* 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 */
+}
+
+#ifdef HAVE_SCRIPT
+/* delim == -1 -> delim = 0, but embedded 0s, creating extra records, are OK. */
+void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim)
+{
+ unsigned int i;
+
+ if (delim == -1)
+ delim = 0;
+ else
+ /* check for embedded NULLs */
+ for (i = 0; i < len; i++)
+ if (data[i] == 0)
+ {
+ len = i;
+ break;
+ }
+
+ if ((lease->extradata_size - lease->extradata_len) < (len + 1))
+ {
+ size_t newsz = lease->extradata_len + len + 100;
+ 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;
+}
+#endif
+
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/log.c b/ap/app/dnsmasq/dnsmasq-2.86/src/log.c
new file mode 100755
index 0000000..1ec3447
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/log.c
@@ -0,0 +1,481 @@
+/* dnsmasq is Copyright (c) 2000-2021 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, daemon->log_file ? daemon->log_file : "");
+ _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)
+ {
+ /* The data in the payload is written with a terminating zero character
+ and the length reflects this. For a stream connection we need to
+ send the zero as a record terminator, but this isn't done for a
+ datagram connection, so treat the length as one less than reality
+ to elide the zero. If we're logging to a file, turn the zero into
+ a newline, and leave the length alone. */
+ int len_adjust = 0;
+
+ if (log_to_file)
+ entries->payload[entries->offset + entries->length - 1] = '\n';
+ else if (connection_type == SOCK_DGRAM)
+ len_adjust = 1;
+
+ /* 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 - len_adjust)) != -1)
+ {
+ entries->length -= rc;
+ entries->offset += rc;
+ if (entries->length == len_adjust)
+ {
+ 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 || errno == EWOULDBLOCK)
+ 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;
+ safe_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 ||
+ errno == EWOULDBLOCK)
+ {
+ /* 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.
+ If OR'd with MS_DEBUG, the messages are suppressed unless --log-debug is set. */
+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";
+ else if ((LOG_FACMASK & priority) == MS_SCRIPT)
+ func = "-script";
+ else if ((LOG_FACMASK & priority) == MS_DEBUG)
+ {
+ if (!option_bool(OPT_LOG_DEBUG))
+ return;
+ func = "-debug";
+ }
+
+#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;
+ }
+
+ /* 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(void)
+{
+ if (entries && log_fd != -1 && connection_good)
+ poll_listen(log_fd, POLLOUT);
+}
+
+void check_log_writer(int force)
+{
+ if (log_fd != -1 && (force || poll_check(log_fd, POLLOUT)))
+ log_write();
+}
+
+void flush_log(void)
+{
+ /* write until queue empty, but don't loop forever if there's
+ no connection to the syslog in existence */
+ 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/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c b/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c
new file mode 100755
index 0000000..01f0c28
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c
@@ -0,0 +1,113 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_LOOP
+static ssize_t loop_make_probe(u32 uid);
+
+void loop_send_probes()
+{
+ struct server *serv;
+ struct randfd_list *rfds = NULL;
+
+ if (!option_bool(OPT_LOOP_DETECT))
+ return;
+
+ /* Loop through all upstream servers not for particular domains, and send a query to that server which is
+ identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (strlen(serv->domain) == 0 &&
+ !(serv->flags & (SERV_FOR_NODOTS)))
+ {
+ ssize_t len = loop_make_probe(serv->uid);
+ int fd;
+
+ serv->flags &= ~SERV_LOOP;
+
+ if ((fd = allocate_rfd(&rfds, serv)) == -1)
+ continue;
+
+ while (retry_send(sendto(fd, daemon->packet, len, 0,
+ &serv->addr.sa, sa_len(&serv->addr))));
+ }
+
+ free_rfds(&rfds);
+}
+
+static ssize_t loop_make_probe(u32 uid)
+{
+ struct dns_header *header = (struct dns_header *)daemon->packet;
+ unsigned char *p = (unsigned char *)(header+1);
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+
+ header->id = rand16();
+ header->ancount = header->nscount = header->arcount = htons(0);
+ header->qdcount = htons(1);
+ header->hb3 = HB3_RD;
+ header->hb4 = 0;
+ SET_OPCODE(header, QUERY);
+
+ *p++ = 8;
+ sprintf((char *)p, "%.8x", uid);
+ p += 8;
+ *p++ = strlen(LOOP_TEST_DOMAIN);
+ strcpy((char *)p, LOOP_TEST_DOMAIN); /* Add terminating zero */
+ p += strlen(LOOP_TEST_DOMAIN) + 1;
+
+ PUTSHORT(LOOP_TEST_TYPE, p);
+ PUTSHORT(C_IN, p);
+
+ return p - (unsigned char *)header;
+}
+
+
+int detect_loop(char *query, int type)
+{
+ int i;
+ u32 uid;
+ struct server *serv;
+
+ if (!option_bool(OPT_LOOP_DETECT))
+ return 0;
+
+ if (type != LOOP_TEST_TYPE ||
+ strlen(LOOP_TEST_DOMAIN) + 9 != strlen(query) ||
+ strstr(query, LOOP_TEST_DOMAIN) != query + 9)
+ return 0;
+
+ for (i = 0; i < 8; i++)
+ if (!isxdigit(query[i]))
+ return 0;
+
+ uid = strtol(query, NULL, 16);
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (strlen(serv->domain) == 0 &&
+ !(serv->flags & SERV_LOOP) &&
+ uid == serv->uid)
+ {
+ serv->flags |= SERV_LOOP;
+ check_servers(1); /* log new state - don't send more probes. */
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c
new file mode 100755
index 0000000..fac5b00
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c
@@ -0,0 +1,44 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+const char * metric_names[] = {
+ "dns_cache_inserted",
+ "dns_cache_live_freed",
+ "dns_queries_forwarded",
+ "dns_auth_answered",
+ "dns_local_answered",
+ "bootp",
+ "pxe",
+ "dhcp_ack",
+ "dhcp_decline",
+ "dhcp_discover",
+ "dhcp_inform",
+ "dhcp_nak",
+ "dhcp_offer",
+ "dhcp_release",
+ "dhcp_request",
+ "noanswer",
+ "leases_allocated_4",
+ "leases_pruned_4",
+ "leases_allocated_6",
+ "leases_pruned_6",
+};
+
+const char* get_metric_name(int i) {
+ return metric_names[i];
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h
new file mode 100755
index 0000000..cecf8c8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h
@@ -0,0 +1,43 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* If you modify this list, please keep the labels in metrics.c in sync. */
+enum {
+ METRIC_DNS_CACHE_INSERTED,
+ METRIC_DNS_CACHE_LIVE_FREED,
+ METRIC_DNS_QUERIES_FORWARDED,
+ METRIC_DNS_AUTH_ANSWERED,
+ METRIC_DNS_LOCAL_ANSWERED,
+ METRIC_BOOTP,
+ METRIC_PXE,
+ METRIC_DHCPACK,
+ METRIC_DHCPDECLINE,
+ METRIC_DHCPDISCOVER,
+ METRIC_DHCPINFORM,
+ METRIC_DHCPNAK,
+ METRIC_DHCPOFFER,
+ METRIC_DHCPRELEASE,
+ METRIC_DHCPREQUEST,
+ METRIC_NOANSWER,
+ METRIC_LEASES_ALLOCATED_4,
+ METRIC_LEASES_PRUNED_4,
+ METRIC_LEASES_ALLOCATED_6,
+ METRIC_LEASES_PRUNED_6,
+
+ __METRIC_MAX,
+};
+
+const char* get_metric_name(int);
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c b/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c
new file mode 100755
index 0000000..7840ef9
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c
@@ -0,0 +1,405 @@
+/* dnsmasq is Copyright (c) 2000-2021 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>
+
+/* Blergh. Radv does this, so that's our excuse. */
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#ifndef NETLINK_NO_ENOBUFS
+#define NETLINK_NO_ENOBUFS 5
+#endif
+
+/* 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
+
+/* Used to request refresh of addresses or routes just once,
+ * when multiple changes might be announced. */
+enum async_states {
+ STATE_NEWADDR = (1 << 0),
+ STATE_NEWROUTE = (1 << 1),
+};
+
+
+static struct iovec iov;
+static u32 netlink_pid;
+
+static unsigned nl_async(struct nlmsghdr *h, unsigned state);
+static void nl_multicast_state(unsigned state);
+
+char *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 */
+ addr.nl_groups = RTMGRP_IPV4_ROUTE;
+ if (option_bool(OPT_CLEVERBIND))
+ addr.nl_groups |= RTMGRP_IPV4_IFADDR;
+ addr.nl_groups |= RTMGRP_IPV6_ROUTE;
+ if (option_bool(OPT_CLEVERBIND))
+ addr.nl_groups |= RTMGRP_IPV6_IFADDR;
+
+#ifdef HAVE_DHCP6
+ if (daemon->doing_ra || daemon->doing_dhcp6)
+ addr.nl_groups |= RTMGRP_IPV6_IFADDR;
+#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);
+
+ return NULL;
+}
+
+static ssize_t netlink_recv(int flags)
+{
+ 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, flags | 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, flags)) == -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.
+ family = AF_LOCAL finds MAC addresses.
+ returns 0 on failure, 1 on success, -1 when restart is required
+*/
+int iface_enumerate(int family, void *parm, int (*callback)())
+{
+ struct sockaddr_nl addr;
+ struct nlmsghdr *h;
+ ssize_t len;
+ static unsigned int seq = 0;
+ int callback_ok = 1;
+ unsigned state = 0;
+
+ struct {
+ struct nlmsghdr nlh;
+ struct rtgenmsg g;
+ } req;
+
+ memset(&req, 0, sizeof(req));
+ memset(&addr, 0, sizeof(addr));
+
+ addr.nl_family = AF_NETLINK;
+
+ if (family == AF_UNSPEC)
+ req.nlh.nlmsg_type = RTM_GETNEIGH;
+ else if (family == AF_LOCAL)
+ req.nlh.nlmsg_type = RTM_GETLINK;
+ else
+ req.nlh.nlmsg_type = RTM_GETADDR;
+
+ req.nlh.nlmsg_len = sizeof(req);
+ 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(retry_send(sendto(daemon->netlinkfd, (void *)&req, sizeof(req), 0,
+ (struct sockaddr *)&addr, sizeof(addr))));
+
+ if (errno != 0)
+ return 0;
+
+ while (1)
+ {
+ if ((len = netlink_recv(0)) == -1)
+ {
+ if (errno == ENOBUFS)
+ {
+ nl_multicast_state(state);
+ return -1;
+ }
+ return 0;
+ }
+
+ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
+ if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR)
+ {
+ /* May be multicast arriving async */
+ state = nl_async(h, state);
+ }
+ else if (h->nlmsg_seq != seq)
+ {
+ /* May be part of incomplete response to previous request after
+ ENOBUFS. Drop it. */
+ continue;
+ }
+ else if (h->nlmsg_type == NLMSG_DONE)
+ return callback_ok;
+ else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL)
+ {
+ 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;
+ char *label = NULL;
+
+ netmask.s_addr = htonl(~(in_addr_t)0 << (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));
+ else if (rta->rta_type == IFA_LABEL)
+ label = RTA_DATA(rta);
+
+ rta = RTA_NEXT(rta, len1);
+ }
+
+ if (addr.s_addr && callback_ok)
+ if (!((*callback)(addr, ifa->ifa_index, label, netmask, broadcast, parm)))
+ callback_ok = 0;
+ }
+ else if (ifa->ifa_family == AF_INET6)
+ {
+ struct in6_addr *addrp = NULL;
+ u32 valid = 0, preferred = 0;
+ int flags = 0;
+
+ while (RTA_OK(rta, len1))
+ {
+ if (rta->rta_type == IFA_ADDRESS)
+ addrp = ((struct in6_addr *)(rta+1));
+ else if (rta->rta_type == IFA_CACHEINFO)
+ {
+ struct ifa_cacheinfo *ifc = (struct ifa_cacheinfo *)(rta+1);
+ preferred = ifc->ifa_prefered;
+ valid = ifc->ifa_valid;
+ }
+ rta = RTA_NEXT(rta, len1);
+ }
+
+ if (ifa->ifa_flags & IFA_F_TENTATIVE)
+ flags |= IFACE_TENTATIVE;
+
+ if (ifa->ifa_flags & IFA_F_DEPRECATED)
+ flags |= IFACE_DEPRECATED;
+
+ if (!(ifa->ifa_flags & IFA_F_TEMPORARY))
+ flags |= IFACE_PERMANENT;
+
+ if (addrp && callback_ok)
+ if (!((*callback)(addrp, (int)(ifa->ifa_prefixlen), (int)(ifa->ifa_scope),
+ (int)(ifa->ifa_index), flags,
+ (int) preferred, (int)valid, parm)))
+ callback_ok = 0;
+ }
+ }
+ }
+ 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 (!(neigh->ndm_state & (NUD_NOARP | NUD_INCOMPLETE | NUD_FAILED)) &&
+ inaddr && mac && callback_ok)
+ if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm)))
+ callback_ok = 0;
+ }
+#ifdef HAVE_DHCP6
+ else if (h->nlmsg_type == RTM_NEWLINK && family == AF_LOCAL)
+ {
+ struct ifinfomsg *link = NLMSG_DATA(h);
+ struct rtattr *rta = IFLA_RTA(link);
+ unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*link));
+ char *mac = NULL;
+ size_t maclen = 0;
+
+ while (RTA_OK(rta, len1))
+ {
+ if (rta->rta_type == IFLA_ADDRESS)
+ {
+ maclen = rta->rta_len - sizeof(struct rtattr);
+ mac = (char *)(rta+1);
+ }
+
+ rta = RTA_NEXT(rta, len1);
+ }
+
+ if (mac && callback_ok && !((link->ifi_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) &&
+ !((*callback)((int)link->ifi_index, (unsigned int)link->ifi_type, mac, maclen, parm)))
+ callback_ok = 0;
+ }
+#endif
+ }
+}
+
+static void nl_multicast_state(unsigned state)
+{
+ ssize_t len;
+ struct nlmsghdr *h;
+
+ do {
+ /* don't risk blocking reading netlink messages here. */
+ while ((len = netlink_recv(MSG_DONTWAIT)) != -1)
+
+ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
+ state = nl_async(h, state);
+ } while (errno == ENOBUFS);
+}
+
+void netlink_multicast(void)
+{
+ unsigned state = 0;
+ nl_multicast_state(state);
+}
+
+
+static unsigned nl_async(struct nlmsghdr *h, unsigned state)
+{
+ if (h->nlmsg_type == NLMSG_ERROR)
+ {
+ struct nlmsgerr *err = NLMSG_DATA(h);
+ if (err->error != 0)
+ my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
+ }
+ else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE &&
+ (state & STATE_NEWROUTE)==0)
+ {
+ /* 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. */
+ struct rtmsg *rtm = NLMSG_DATA(h);
+
+ if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK &&
+ (rtm->rtm_table == RT_TABLE_MAIN ||
+ rtm->rtm_table == RT_TABLE_LOCAL))
+ {
+ queue_event(EVENT_NEWROUTE);
+ state |= STATE_NEWROUTE;
+ }
+ }
+ else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) &&
+ (state & STATE_NEWADDR)==0)
+ {
+ queue_event(EVENT_NEWADDR);
+ state |= STATE_NEWADDR;
+ }
+ return state;
+}
+#endif /* HAVE_LINUX_NETWORK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/network.c b/ap/app/dnsmasq/dnsmasq-2.86/src/network.c
new file mode 100755
index 0000000..3fc179d
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/network.c
@@ -0,0 +1,1761 @@
+/* dnsmasq is Copyright (c) 2000-2021 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;
+
+ safe_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;
+ safe_strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE);
+ if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0)
+ return 0;
+
+ if (lifr.lifr_index == index) {
+ safe_strncpy(name, lifr.lifr_name, IF_NAMESIZE);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+#else
+
+int indextoname(int fd, int index, char *name)
+{
+ (void)fd;
+
+ if (index == 0 || !if_indextoname(index, name))
+ return 0;
+
+ return 1;
+}
+
+#endif
+
+int iface_check(int family, union all_addr *addr, char *name, int *auth)
+{
+ struct iname *tmp;
+ int ret = 1, match_addr = 0;
+
+ /* Note: have to check all and not bail out early, so that we set the
+ "used" flags.
+
+ May be called with family == AF_LOCALto check interface by name only. */
+
+ if (auth)
+ *auth = 0;
+
+ if (daemon->if_names || daemon->if_addrs)
+ {
+ ret = 0;
+
+ for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, name))
+ ret = tmp->used = 1;
+
+ if (addr)
+ for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+ if (tmp->addr.sa.sa_family == family)
+ {
+ if (family == AF_INET &&
+ tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+ ret = match_addr = tmp->used = 1;
+ else if (family == AF_INET6 &&
+ IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr,
+ &addr->addr6))
+ ret = match_addr = tmp->used = 1;
+ }
+ }
+
+ if (!match_addr)
+ for (tmp = daemon->if_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, name))
+ ret = 0;
+
+
+ for (tmp = daemon->authinterface; tmp; tmp = tmp->next)
+ if (tmp->name)
+ {
+ if (strcmp(tmp->name, name) == 0 &&
+ (tmp->addr.sa.sa_family == 0 || tmp->addr.sa.sa_family == family))
+ break;
+ }
+ else if (addr && tmp->addr.sa.sa_family == AF_INET && family == AF_INET &&
+ tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+ break;
+ else if (addr && tmp->addr.sa.sa_family == AF_INET6 && family == AF_INET6 &&
+ IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr6))
+ break;
+
+ if (tmp && auth)
+ {
+ *auth = 1;
+ ret = 1;
+ }
+
+ return ret;
+}
+
+
+/* Fix for problem that the kernel sometimes reports the loopback interface as the
+ arrival interface when a packet originates locally, even when sent to address of
+ an interface other than the loopback. Accept packet if it arrived via a loopback
+ interface, even when we're not accepting packets that way, as long as the destination
+ address is one we're believing. Interface list must be up-to-date before calling. */
+int loopback_exception(int fd, int family, union all_addr *addr, char *name)
+{
+ struct ifreq ifr;
+ struct irec *iface;
+
+ safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 &&
+ ifr.ifr_flags & IFF_LOOPBACK)
+ {
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->addr.sa.sa_family == family)
+ {
+ if (family == AF_INET)
+ {
+ if (iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+ return 1;
+ }
+ else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr6))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* If we're configured with something like --interface=eth0:0 then we'll listen correctly
+ on the relevant address, but the name of the arrival interface, derived from the
+ index won't match the config. Check that we found an interface address for the arrival
+ interface: daemon->interfaces must be up-to-date. */
+int label_exception(int index, int family, union all_addr *addr)
+{
+ struct irec *iface;
+
+ /* labels only supported on IPv4 addresses. */
+ if (family != AF_INET)
+ return 0;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->index == index && iface->addr.sa.sa_family == AF_INET &&
+ iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+ return 1;
+
+ return 0;
+}
+
+struct iface_param {
+ struct addrlist *spare;
+ int fd;
+};
+
+static int iface_allowed(struct iface_param *param, int if_index, char *label,
+ union mysockaddr *addr, struct in_addr netmask, int prefixlen, int iface_flags)
+{
+ struct irec *iface;
+ int loopback;
+ struct ifreq ifr;
+ int tftp_ok = !!option_bool(OPT_TFTP);
+ int dhcp_ok = 1;
+ int auth_dns = 0;
+ int is_label = 0;
+#if defined(HAVE_DHCP) || defined(HAVE_TFTP)
+ struct iname *tmp;
+#endif
+
+ (void)prefixlen;
+
+ if (!indextoname(param->fd, if_index, ifr.ifr_name) ||
+ ioctl(param->fd, SIOCGIFFLAGS, &ifr) == -1)
+ return 0;
+
+ loopback = ifr.ifr_flags & IFF_LOOPBACK;
+
+ if (loopback)
+ dhcp_ok = 0;
+
+ if (!label)
+ label = ifr.ifr_name;
+ else
+ is_label = strcmp(label, ifr.ifr_name);
+
+ /* maintain a list of all addresses on all interfaces for --local-service option */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *al;
+
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->next = daemon->interface_addrs;
+ daemon->interface_addrs = al;
+ al->prefixlen = prefixlen;
+
+ if (addr->sa.sa_family == AF_INET)
+ {
+ al->addr.addr4 = addr->in.sin_addr;
+ al->flags = 0;
+ }
+ else
+ {
+ al->addr.addr6 = addr->in6.sin6_addr;
+ al->flags = ADDRLIST_IPV6;
+ }
+ }
+ }
+
+ if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
+ {
+ struct interface_name *int_name;
+ struct addrlist *al;
+#ifdef HAVE_AUTH
+ struct auth_zone *zone;
+ struct auth_name_list *name;
+
+ /* Find subnets in auth_zones */
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ for (name = zone->interface_names; name; name = name->next)
+ if (wildcard_match(name->name, label))
+ {
+ if (addr->sa.sa_family == AF_INET && (name->flags & AUTH4))
+ {
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->next = zone->subnet;
+ zone->subnet = al;
+ al->prefixlen = prefixlen;
+ al->addr.addr4 = addr->in.sin_addr;
+ al->flags = 0;
+ }
+ }
+
+ if (addr->sa.sa_family == AF_INET6 && (name->flags & AUTH6))
+ {
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->next = zone->subnet;
+ zone->subnet = al;
+ al->prefixlen = prefixlen;
+ al->addr.addr6 = addr->in6.sin6_addr;
+ al->flags = ADDRLIST_IPV6;
+ }
+ }
+ }
+#endif
+
+ /* Update addresses from interface_names. These are a set independent
+ of the set we're listening on. */
+ for (int_name = daemon->int_names; int_name; int_name = int_name->next)
+ if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0)
+ {
+ struct addrlist *lp;
+
+ al = NULL;
+
+ if (addr->sa.sa_family == AF_INET && (int_name->flags & (IN4 | INP4)))
+ {
+ struct in_addr newaddr = addr->in.sin_addr;
+
+ if (int_name->flags & INP4)
+ {
+ if (netmask.s_addr == 0xffff)
+ continue;
+
+ newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) |
+ (int_name->proto4.s_addr & ~netmask.s_addr);
+ }
+
+ /* check for duplicates. */
+ for (lp = int_name->addr; lp; lp = lp->next)
+ if (lp->flags == 0 && lp->addr.addr4.s_addr == newaddr.s_addr)
+ break;
+
+ if (!lp)
+ {
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->flags = 0;
+ al->addr.addr4 = newaddr;
+ }
+ }
+ }
+
+ if (addr->sa.sa_family == AF_INET6 && (int_name->flags & (IN6 | INP6)))
+ {
+ struct in6_addr newaddr = addr->in6.sin6_addr;
+
+ if (int_name->flags & INP6)
+ {
+ int i;
+
+ /* No sense in doing /128. */
+ if (prefixlen == 128)
+ continue;
+
+ for (i = 0; i < 16; i++)
+ {
+ int bits = ((i+1)*8) - prefixlen;
+
+ if (bits >= 8)
+ newaddr.s6_addr[i] = int_name->proto6.s6_addr[i];
+ else if (bits >= 0)
+ {
+ unsigned char mask = 0xff << bits;
+ newaddr.s6_addr[i] =
+ (addr->in6.sin6_addr.s6_addr[i] & mask) |
+ (int_name->proto6.s6_addr[i] & ~mask);
+ }
+ }
+ }
+
+ /* check for duplicates. */
+ for (lp = int_name->addr; lp; lp = lp->next)
+ if ((lp->flags & ADDRLIST_IPV6) &&
+ IN6_ARE_ADDR_EQUAL(&lp->addr.addr6, &newaddr))
+ break;
+
+ if (!lp)
+ {
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->flags = ADDRLIST_IPV6;
+ al->addr.addr6 = newaddr;
+
+ /* Privacy addresses and addresses still undergoing DAD and deprecated addresses
+ don't appear in forward queries, but will in reverse ones. */
+ if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE)))
+ al->flags |= ADDRLIST_REVONLY;
+ }
+ }
+ }
+
+ if (al)
+ {
+ al->next = int_name->addr;
+ int_name->addr = al;
+ }
+ }
+ }
+
+ /* check whether the interface IP has been added already
+ we call this routine multiple times. */
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (sockaddr_isequal(&iface->addr, addr) && iface->index == if_index)
+ {
+ iface->dad = !!(iface_flags & IFACE_TENTATIVE);
+ iface->found = 1; /* for garbage collection */
+ iface->netmask = netmask;
+ return 1;
+ }
+
+ /* 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)
+ break;
+
+ if (!lo && (lo = whine_malloc(sizeof(struct iname))))
+ {
+ if ((lo->name = whine_malloc(strlen(ifr.ifr_name)+1)))
+ {
+ strcpy(lo->name, ifr.ifr_name);
+ lo->used = 1;
+ lo->next = daemon->if_names;
+ daemon->if_names = lo;
+ }
+ else
+ free(lo);
+ }
+ }
+
+ if (addr->sa.sa_family == AF_INET &&
+ !iface_check(AF_INET, (union all_addr *)&addr->in.sin_addr, label, &auth_dns))
+ return 1;
+
+ if (addr->sa.sa_family == AF_INET6 &&
+ !iface_check(AF_INET6, (union all_addr *)&addr->in6.sin6_addr, label, &auth_dns))
+ return 1;
+
+#ifdef HAVE_DHCP
+ /* No DHCP where we're doing auth DNS. */
+ if (auth_dns)
+ {
+ tftp_ok = 0;
+ dhcp_ok = 0;
+ }
+ else
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ {
+ tftp_ok = 0;
+ dhcp_ok = 0;
+ }
+#endif
+
+
+#ifdef HAVE_TFTP
+ if (daemon->tftp_interfaces)
+ {
+ /* dedicated tftp interface list */
+ tftp_ok = 0;
+ for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ tftp_ok = 1;
+ }
+#endif
+
+ /* add to list */
+ if ((iface = whine_malloc(sizeof(struct irec))))
+ {
+ int mtu = 0;
+
+ if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1)
+ mtu = ifr.ifr_mtu;
+
+ iface->addr = *addr;
+ iface->netmask = netmask;
+ iface->tftp_ok = tftp_ok;
+ iface->dhcp_ok = dhcp_ok;
+ iface->dns_auth = auth_dns;
+ iface->mtu = mtu;
+ iface->dad = !!(iface_flags & IFACE_TENTATIVE);
+ iface->found = 1;
+ iface->done = iface->multicast_done = iface->warned = 0;
+ iface->index = if_index;
+ iface->label = is_label;
+ if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1)))
+ {
+ strcpy(iface->name, ifr.ifr_name);
+ iface->next = daemon->interfaces;
+ daemon->interfaces = iface;
+ return 1;
+ }
+ free(iface);
+
+ }
+
+ errno = ENOMEM;
+ return 0;
+}
+
+static int iface_allowed_v6(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ int preferred, int valid, void *vparam)
+{
+ union mysockaddr addr;
+ struct in_addr netmask; /* dummy */
+ netmask.s_addr = 0;
+
+ (void)scope; /* warning */
+ (void)preferred;
+ (void)valid;
+
+ 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);
+ /* FreeBSD insists this is zero for non-linklocal addresses */
+ if (IN6_IS_ADDR_LINKLOCAL(local))
+ addr.in6.sin6_scope_id = if_index;
+ else
+ addr.in6.sin6_scope_id = 0;
+
+ return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, prefix, flags);
+}
+
+static int iface_allowed_v4(struct in_addr local, int if_index, char *label,
+ struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+ union mysockaddr addr;
+ int prefix, bit;
+
+ (void)broadcast; /* warning */
+
+ memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in.sin_len = sizeof(addr.in);
+#endif
+ addr.in.sin_family = AF_INET;
+ addr.in.sin_addr = local;
+ addr.in.sin_port = htons(daemon->port);
+
+ /* determine prefix length from netmask */
+ for (prefix = 32, bit = 1; (bit & ntohl(netmask.s_addr)) == 0 && prefix != 0; bit = bit << 1, prefix--);
+
+ return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0);
+}
+
+/*
+ * Clean old interfaces no longer found.
+ */
+static void clean_interfaces()
+{
+ struct irec *iface;
+ struct irec **up = &daemon->interfaces;
+
+ for (iface = *up; iface; iface = *up)
+ {
+ if (!iface->found && !iface->done)
+ {
+ *up = iface->next;
+ free(iface->name);
+ free(iface);
+ }
+ else
+ {
+ up = &iface->next;
+ }
+ }
+}
+
+/** Release listener if no other interface needs it.
+ *
+ * @return 1 if released, 0 if still required
+ */
+static int release_listener(struct listener *l)
+{
+ if (l->used > 1)
+ {
+ struct irec *iface;
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->done && sockaddr_isequal(&l->addr, &iface->addr))
+ {
+ if (iface->found)
+ {
+ /* update listener to point to active interface instead */
+ if (!l->iface->found)
+ l->iface = iface;
+ }
+ else
+ {
+ l->used--;
+ iface->done = 0;
+ }
+ }
+
+ /* Someone is still using this listener, skip its deletion */
+ if (l->used > 0)
+ return 0;
+ }
+
+ if (l->iface->done)
+ {
+ int port;
+
+ port = prettyprint_addr(&l->iface->addr, daemon->addrbuff);
+ my_syslog(LOG_DEBUG|MS_DEBUG, _("stopped listening on %s(#%d): %s port %d"),
+ l->iface->name, l->iface->index, daemon->addrbuff, port);
+ /* In case it ever returns */
+ l->iface->done = 0;
+ }
+
+ if (l->fd != -1)
+ close(l->fd);
+ if (l->tcpfd != -1)
+ close(l->tcpfd);
+ if (l->tftpfd != -1)
+ close(l->tftpfd);
+
+ free(l);
+ return 1;
+}
+
+int enumerate_interfaces(int reset)
+{
+ static struct addrlist *spare = NULL;
+ static int done = 0;
+ struct iface_param param;
+ int errsave, ret = 1;
+ struct addrlist *addr, *tmp;
+ struct interface_name *intname;
+ struct irec *iface;
+#ifdef HAVE_AUTH
+ struct auth_zone *zone;
+#endif
+ struct server *serv;
+
+ /* Do this max once per select cycle - also inhibits netlink socket use
+ in TCP child processes. */
+
+ if (reset)
+ {
+ done = 0;
+ return 1;
+ }
+
+ if (done)
+ return 1;
+
+ done = 1;
+
+ if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+ return 0;
+
+ /* iface indexes can change when interfaces are created/destroyed.
+ We use them in the main forwarding control path, when the path
+ to a server is specified by an interface, so cache them.
+ Update the cache here. */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (serv->interface[0] != 0)
+ {
+#ifdef HAVE_LINUX_NETWORK
+ struct ifreq ifr;
+
+ safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE);
+ if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1)
+ serv->ifindex = ifr.ifr_ifindex;
+#else
+ serv->ifindex = if_nametoindex(serv->interface);
+#endif
+ }
+
+again:
+ /* Mark interfaces for garbage collection */
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ iface->found = 0;
+
+ /* remove addresses stored against interface_names */
+ for (intname = daemon->int_names; intname; intname = intname->next)
+ {
+ for (addr = intname->addr; addr; addr = tmp)
+ {
+ tmp = addr->next;
+ addr->next = spare;
+ spare = addr;
+ }
+
+ intname->addr = NULL;
+ }
+
+ /* Remove list of addresses of local interfaces */
+ for (addr = daemon->interface_addrs; addr; addr = tmp)
+ {
+ tmp = addr->next;
+ addr->next = spare;
+ spare = addr;
+ }
+ daemon->interface_addrs = NULL;
+
+#ifdef HAVE_AUTH
+ /* remove addresses stored against auth_zone subnets, but not
+ ones configured as address literals */
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+ if (zone->interface_names)
+ {
+ struct addrlist **up;
+ for (up = &zone->subnet, addr = zone->subnet; addr; addr = tmp)
+ {
+ tmp = addr->next;
+ if (addr->flags & ADDRLIST_LITERAL)
+ up = &addr->next;
+ else
+ {
+ *up = addr->next;
+ addr->next = spare;
+ spare = addr;
+ }
+ }
+ }
+#endif
+
+ param.spare = spare;
+
+ ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6);
+ if (ret < 0)
+ goto again;
+ else if (ret)
+ {
+ ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4);
+ if (ret < 0)
+ goto again;
+ }
+
+ errsave = errno;
+ close(param.fd);
+
+ if (option_bool(OPT_CLEVERBIND))
+ {
+ /* Garbage-collect listeners listening on addresses that no longer exist.
+ Does nothing when not binding interfaces or for listeners on localhost,
+ since the ->iface field is NULL. Note that this needs the protections
+ against reentrancy, hence it's here. It also means there's a possibility,
+ in OPT_CLEVERBIND mode, that at listener will just disappear after
+ a call to enumerate_interfaces, this is checked OK on all calls. */
+ struct listener *l, *tmp, **up;
+ int freed = 0;
+
+ for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp)
+ {
+ tmp = l->next;
+
+ if (!l->iface || l->iface->found)
+ up = &l->next;
+ else if (release_listener(l))
+ {
+ *up = tmp;
+ freed = 1;
+ }
+ }
+
+ if (freed)
+ clean_interfaces();
+ }
+
+ errno = errsave;
+ spare = param.spare;
+
+ return ret;
+}
+
+/* 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 dienow)
+{
+ int family = addr->sa.sa_family;
+ int fd, rc, opt = 1;
+
+ if ((fd = socket(family, type, 0)) == -1)
+ {
+ int port, errsave;
+ char *s;
+
+ /* No error if the kernel just doesn't support this IP flavour */
+ if (errno == EPROTONOSUPPORT ||
+ errno == EAFNOSUPPORT ||
+ errno == EINVAL)
+ return -1;
+
+ err:
+ errsave = errno;
+ port = prettyprint_addr(addr, daemon->addrbuff);
+ if (!option_bool(OPT_NOWILD) && !option_bool(OPT_CLEVERBIND))
+ sprintf(daemon->addrbuff, "port %d", port);
+ s = _("failed to create listening socket for %s: %s");
+
+ if (fd != -1)
+ close (fd);
+
+ errno = errsave;
+
+ if (dienow)
+ {
+ /* failure to bind addresses given by --listen-address at this point
+ is OK if we're doing bind-dynamic */
+ if (!option_bool(OPT_CLEVERBIND))
+ die(s, daemon->addrbuff, EC_BADNET);
+ }
+ else
+ my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno));
+
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd))
+ goto err;
+
+ if (family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1)
+ goto err;
+
+ if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) == -1)
+ goto err;
+
+ if (type == SOCK_STREAM)
+ {
+#ifdef TCP_FASTOPEN
+ int qlen = 5;
+ setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
+#endif
+
+ if (listen(fd, TCP_BACKLOG) == -1)
+ goto err;
+ }
+ else if (family == AF_INET)
+ {
+ if (!option_bool(OPT_NOWILD))
+ {
+#if defined(HAVE_LINUX_NETWORK)
+ if (setsockopt(fd, IPPROTO_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
+ }
+ }
+ else if (!set_ipv6pktinfo(fd))
+ goto err;
+
+ return fd;
+}
+
+int set_ipv6pktinfo(int fd)
+{
+ int opt = 1;
+
+ /* 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
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) != -1)
+ return 1;
+# ifdef IPV6_2292PKTINFO
+ else if (errno == ENOPROTOOPT && setsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1)
+ {
+ daemon->v6pktinfo = IPV6_2292PKTINFO;
+ return 1;
+ }
+# endif
+#else
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &opt, sizeof(opt)) != -1)
+ return 1;
+#endif
+
+ return 0;
+}
+
+
+/* Find the interface on which a TCP connection arrived, if possible, or zero otherwise. */
+int tcp_interface(int fd, int af)
+{
+ (void)fd; /* suppress potential unused warning */
+ (void)af; /* suppress potential unused warning */
+ int if_index = 0;
+
+#ifdef HAVE_LINUX_NETWORK
+ int opt = 1;
+ struct cmsghdr *cmptr;
+ struct msghdr msg;
+ socklen_t len;
+
+ /* use mshdr so that the CMSDG_* macros are available */
+ msg.msg_control = daemon->packet;
+ msg.msg_controllen = len = daemon->packet_buff_sz;
+
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+
+ if (af == AF_INET)
+ {
+ if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 &&
+ getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, &len) != -1)
+ {
+ msg.msg_controllen = len;
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+ {
+ union {
+ unsigned char *c;
+ struct in_pktinfo *p;
+ } p;
+
+ p.c = CMSG_DATA(cmptr);
+ if_index = p.p->ipi_ifindex;
+ }
+ }
+ }
+ else
+ {
+ /* Only the RFC-2292 API has the ability to find the interface for TCP connections,
+ it was removed in RFC-3542 !!!!
+
+ Fortunately, Linux kept the 2292 ABI when it moved to 3542. The following code always
+ uses the old ABI, and should work with pre- and post-3542 kernel headers */
+
+#ifdef IPV6_2292PKTOPTIONS
+# define PKTOPTIONS IPV6_2292PKTOPTIONS
+#else
+# define PKTOPTIONS IPV6_PKTOPTIONS
+#endif
+
+ if (set_ipv6pktinfo(fd) &&
+ getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, &len) != -1)
+ {
+ msg.msg_controllen = len;
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
+ {
+ union {
+ unsigned char *c;
+ struct in6_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+
+ if_index = p.p->ipi6_ifindex;
+ }
+ }
+ }
+#endif /* Linux */
+
+ return if_index;
+}
+
+static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, int dienow)
+{
+ struct listener *l = NULL;
+ int fd = -1, tcpfd = -1, tftpfd = -1;
+
+ (void)do_tftp;
+
+ if (daemon->port != 0)
+ {
+ fd = make_sock(addr, SOCK_DGRAM, dienow);
+ tcpfd = make_sock(addr, SOCK_STREAM, dienow);
+ }
+
+#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, dienow);
+ addr->in.sin_port = save;
+ }
+ else
+ {
+ short save = addr->in6.sin6_port;
+ addr->in6.sin6_port = htons(TFTP_PORT);
+ tftpfd = make_sock(addr, SOCK_DGRAM, dienow);
+ addr->in6.sin6_port = save;
+ }
+ }
+#endif
+
+ if (fd != -1 || tcpfd != -1 || tftpfd != -1)
+ {
+ l = safe_malloc(sizeof(struct listener));
+ l->next = NULL;
+ l->fd = fd;
+ l->tcpfd = tcpfd;
+ l->tftpfd = tftpfd;
+ l->addr = *addr;
+ l->used = 1;
+ l->iface = NULL;
+ }
+
+ return l;
+}
+
+void create_wildcard_listeners(void)
+{
+ union mysockaddr addr;
+ struct listener *l, *l6;
+
+ 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, !!option_bool(OPT_TFTP), 1);
+
+ 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);
+
+ l6 = create_listeners(&addr, !!option_bool(OPT_TFTP), 1);
+ if (l)
+ l->next = l6;
+ else
+ l = l6;
+
+ daemon->listeners = l;
+}
+
+static struct listener *find_listener(union mysockaddr *addr)
+{
+ struct listener *l;
+ for (l = daemon->listeners; l; l = l->next)
+ if (sockaddr_isequal(&l->addr, addr))
+ return l;
+ return NULL;
+}
+
+void create_bound_listeners(int dienow)
+{
+ struct listener *new;
+ struct irec *iface;
+ struct iname *if_tmp;
+ struct listener *existing;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (!iface->done && !iface->dad && iface->found)
+ {
+ existing = find_listener(&iface->addr);
+ if (existing)
+ {
+ iface->done = 1;
+ existing->used++; /* increase usage counter */
+ }
+ else if ((new = create_listeners(&iface->addr, iface->tftp_ok, dienow)))
+ {
+ new->iface = iface;
+ new->next = daemon->listeners;
+ daemon->listeners = new;
+ iface->done = 1;
+
+ /* Don't log the initial set of listen addresses created
+ at startup, since this is happening before the logging
+ system is initialised and the sign-on printed. */
+ if (!dienow)
+ {
+ int port = prettyprint_addr(&iface->addr, daemon->addrbuff);
+ my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s(#%d): %s port %d"),
+ iface->name, iface->index, daemon->addrbuff, port);
+ }
+ }
+ }
+
+ /* Check for --listen-address options that haven't been used because there's
+ no interface with a matching address. These may be valid: eg it's possible
+ to listen on 127.0.1.1 even if the loopback interface is 127.0.0.1
+
+ If the address isn't valid the bind() will fail and we'll die()
+ (except in bind-dynamic mode, when we'll complain but keep trying.)
+
+ The resulting listeners have the ->iface field NULL, and this has to be
+ handled by the DNS and TFTP code. It disables --localise-queries processing
+ (no netmask) and some MTU login the tftp code. */
+
+ for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next)
+ if (!if_tmp->used &&
+ (new = create_listeners(&if_tmp->addr, !!option_bool(OPT_TFTP), dienow)))
+ {
+ new->next = daemon->listeners;
+ daemon->listeners = new;
+
+ if (!dienow)
+ {
+ int port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff);
+ my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port);
+ }
+ }
+}
+
+/* In --bind-interfaces, the only access control is the addresses we're listening on.
+ There's nothing to avoid a query to the address of an internal interface arriving via
+ an external interface where we don't want to accept queries, except that in the usual
+ case the addresses of internal interfaces are RFC1918. When bind-interfaces in use,
+ and we listen on an address that looks like it's probably globally routeable, shout.
+
+ The fix is to use --bind-dynamic, which actually checks the arrival interface too.
+ Tough if your platform doesn't support this.
+
+ Note that checking the arrival interface is supported in the standard IPv6 API and
+ always done, so we don't warn about any IPv6 addresses here.
+*/
+
+void warn_bound_listeners(void)
+{
+ struct irec *iface;
+ int advice = 0;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (!iface->dns_auth)
+ {
+ if (iface->addr.sa.sa_family == AF_INET)
+ {
+ if (!private_net(iface->addr.in.sin_addr, 1))
+ {
+ inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+ iface->warned = advice = 1;
+ my_syslog(LOG_WARNING,
+ _("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
+ daemon->addrbuff, iface->name);
+ }
+ }
+ }
+
+ if (advice)
+ my_syslog(LOG_WARNING, _("LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification attacks via these interface(s)"));
+}
+
+void warn_wild_labels(void)
+{
+ struct irec *iface;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->found && iface->name && iface->label)
+ my_syslog(LOG_WARNING, _("warning: using interface %s instead"), iface->name);
+}
+
+void warn_int_names(void)
+{
+ struct interface_name *intname;
+
+ for (intname = daemon->int_names; intname; intname = intname->next)
+ if (!intname->addr)
+ my_syslog(LOG_WARNING, _("warning: no addresses found for interface %s"), intname->intr);
+}
+
+int is_dad_listeners(void)
+{
+ struct irec *iface;
+
+ if (option_bool(OPT_NOWILD))
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->dad && !iface->done)
+ return 1;
+
+ return 0;
+}
+
+#ifdef HAVE_DHCP6
+void join_multicast(int dienow)
+{
+ struct irec *iface, *tmp;
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (iface->addr.sa.sa_family == AF_INET6 && iface->dhcp_ok && !iface->multicast_done)
+ {
+ /* There's an irec per address but we only want to join for multicast
+ once per interface. Weed out duplicates. */
+ for (tmp = daemon->interfaces; tmp; tmp = tmp->next)
+ if (tmp->multicast_done && tmp->index == iface->index)
+ break;
+
+ iface->multicast_done = 1;
+
+ if (!tmp)
+ {
+ struct ipv6_mreq mreq;
+ int err = 0;
+
+ mreq.ipv6mr_interface = iface->index;
+
+ inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
+
+ if ((daemon->doing_dhcp6 || daemon->relay6) &&
+ setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+ err = errno;
+
+ inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr);
+
+ if (daemon->doing_dhcp6 &&
+ setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+ err = errno;
+
+ inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr);
+
+ if (daemon->doing_ra &&
+ setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+ err = errno;
+
+ if (err)
+ {
+ char *s = _("interface %s failed to join DHCPv6 multicast group: %s");
+ errno = err;
+
+#ifdef HAVE_LINUX_NETWORK
+ if (errno == ENOMEM)
+ my_syslog(LOG_ERR, _("try increasing /proc/sys/net/core/optmem_max"));
+#endif
+
+ if (dienow)
+ die(s, iface->name, EC_BADNET);
+ else
+ my_syslog(LOG_ERR, s, iface->name, strerror(errno));
+ }
+ }
+ }
+}
+#endif
+
+int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
+{
+ union mysockaddr addr_copy = *addr;
+ unsigned short port;
+ int tries = 1;
+ unsigned short ports_avail = 1;
+
+ if (addr_copy.sa.sa_family == AF_INET)
+ port = addr_copy.in.sin_port;
+ else
+ port = addr_copy.in6.sin6_port;
+
+ /* cannot set source _port_ for TCP connections. */
+ if (is_tcp)
+ port = 0;
+ else if (port == 0 && daemon->max_port != 0)
+ {
+ /* Bind a random port within the range given by min-port and max-port if either
+ or both are set. Otherwise use the OS's random ephemeral port allocation by
+ leaving port == 0 and tries == 1 */
+ ports_avail = daemon->max_port - daemon->min_port + 1;
+ tries = ports_avail < 30 ? 3 * ports_avail : 100;
+ port = htons(daemon->min_port + (rand16() % ports_avail));
+ }
+
+ while (1)
+ {
+ /* elide bind() call if it's to port 0, address 0 */
+ if (addr_copy.sa.sa_family == AF_INET)
+ {
+ if (port == 0 && addr_copy.in.sin_addr.s_addr == 0)
+ break;
+ addr_copy.in.sin_port = port;
+ }
+ else
+ {
+ if (port == 0 && IN6_IS_ADDR_UNSPECIFIED(&addr_copy.in6.sin6_addr))
+ break;
+ addr_copy.in6.sin6_port = port;
+ }
+
+ if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
+ break;
+
+ if (errno != EADDRINUSE && errno != EACCES)
+ return 0;
+
+ if (--tries == 0)
+ return 0;
+
+ port = htons(daemon->min_port + (rand16() % ports_avail));
+ }
+
+ if (!is_tcp && ifindex > 0)
+ {
+#if defined(IP_UNICAST_IF)
+ if (addr_copy.sa.sa_family == AF_INET)
+ {
+ uint32_t ifindex_opt = htonl(ifindex);
+ return setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
+ }
+#endif
+#if defined (IPV6_UNICAST_IF)
+ if (addr_copy.sa.sa_family == AF_INET6)
+ {
+ uint32_t ifindex_opt = htonl(ifindex);
+ return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
+ }
+#endif
+ }
+
+ (void)intname; /* suppress potential unused warning */
+#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, unsigned int ifindex)
+{
+ struct serverfd *sfd;
+ int errsave;
+ int opt = 1;
+
+ /* when using random ports, servers which would otherwise use
+ the INADDR_ANY/port0 socket have sfd set to NULL, this is
+ anything without an explictly set source port. */
+ if (!daemon->osport)
+ {
+ errno = 0;
+
+ if (addr->sa.sa_family == AF_INET &&
+ addr->in.sin_port == htons(0))
+ return NULL;
+
+ if (addr->sa.sa_family == AF_INET6 &&
+ addr->in6.sin6_port == htons(0))
+ return NULL;
+ }
+
+ /* may have a suitable one already */
+ for (sfd = daemon->sfds; sfd; sfd = sfd->next )
+ if (ifindex == sfd->ifindex &&
+ 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 ((addr->sa.sa_family == AF_INET6 && setsockopt(sfd->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) ||
+ !local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd))
+ {
+ errsave = errno; /* save error from bind/setsockopt. */
+ close(sfd->fd);
+ free(sfd);
+ errno = errsave;
+ return NULL;
+ }
+
+ safe_strncpy(sfd->interface, intname, sizeof(sfd->interface));
+ sfd->source_addr = *addr;
+ sfd->next = daemon->sfds;
+ sfd->ifindex = ifindex;
+ sfd->preallocated = 0;
+ 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;
+ struct serverfd *sfd;
+
+ 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
+ if ((sfd = allocate_sfd(&addr, "", 0)))
+ sfd->preallocated = 1;
+
+ 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
+ if ((sfd = allocate_sfd(&addr, "", 0)))
+ sfd->preallocated = 1;
+ }
+
+ for (srv = daemon->servers; srv; srv = srv->next)
+ if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
+ errno != 0 &&
+ option_bool(OPT_NOWILD))
+ {
+ (void)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(int no_loop_check)
+{
+ struct irec *iface;
+ struct server *serv;
+ struct serverfd *sfd, *tmp, **up;
+ int port = 0, count;
+ int locals = 0;
+
+#ifdef HAVE_LOOP
+ if (!no_loop_check)
+ loop_send_probes();
+#endif
+
+ /* clear all marks. */
+ mark_servers(0);
+
+ /* interface may be new since startup */
+ if (!option_bool(OPT_NOWILD))
+ enumerate_interfaces(0);
+
+ /* don't garbage collect pre-allocated sfds. */
+ for (sfd = daemon->sfds; sfd; sfd = sfd->next)
+ sfd->used = sfd->preallocated;
+
+ for (count = 0, serv = daemon->servers; serv; serv = serv->next)
+ {
+ /* Init edns_pktsz for newly created server records. */
+ if (serv->edns_pktsz == 0)
+ serv->edns_pktsz = daemon->edns_pktsz;
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ if (!(serv->flags & SERV_FOR_NODOTS))
+ serv->flags |= SERV_DO_DNSSEC;
+
+ /* Disable DNSSEC validation when using server=/domain/.... servers
+ unless there's a configured trust anchor. */
+ if (strlen(serv->domain) != 0)
+ {
+ struct ds_config *ds;
+ char *domain = serv->domain;
+
+ /* .example.com is valid */
+ while (*domain == '.')
+ domain++;
+
+ for (ds = daemon->ds; ds; ds = ds->next)
+ if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
+ break;
+
+ if (!ds)
+ serv->flags &= ~SERV_DO_DNSSEC;
+ }
+ }
+#endif
+
+ port = prettyprint_addr(&serv->addr, daemon->namebuff);
+
+ /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
+ if (serv->addr.sa.sa_family == AF_INET &&
+ serv->addr.in.sin_addr.s_addr == 0)
+ {
+ serv->flags |= SERV_MARK;
+ continue;
+ }
+
+ for (iface = daemon->interfaces; iface; iface = iface->next)
+ if (sockaddr_isequal(&serv->addr, &iface->addr))
+ break;
+ if (iface)
+ {
+ my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
+ serv->flags |= SERV_MARK;
+ continue;
+ }
+
+ /* Do we need a socket set? */
+ if (!serv->sfd &&
+ !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
+ errno != 0)
+ {
+ my_syslog(LOG_WARNING,
+ _("ignoring nameserver %s - cannot make/bind socket: %s"),
+ daemon->namebuff, strerror(errno));
+ serv->flags |= SERV_MARK;
+ continue;
+ }
+
+ if (serv->sfd)
+ serv->sfd->used = 1;
+
+ if (++count > SERVERS_LOGGED)
+ continue;
+
+ if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
+ {
+ char *s1, *s2, *s3 = "", *s4 = "";
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
+ s3 = _("(no DNSSEC)");
+#endif
+ if (serv->flags & SERV_FOR_NODOTS)
+ s1 = _("unqualified"), s2 = _("names");
+ else if (strlen(serv->domain) == 0)
+ s1 = _("default"), s2 = "";
+ else
+ s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : "";
+
+ my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s%s %s"), daemon->namebuff, port, s1, s4, s2, s3);
+ }
+#ifdef HAVE_LOOP
+ else if (serv->flags & SERV_LOOP)
+ my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port);
+#endif
+ else if (serv->interface[0] != 0)
+ my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
+ else
+ my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
+
+ }
+
+ for (count = 0, serv = daemon->local_domains; serv; serv = serv->next)
+ {
+ if (++count > SERVERS_LOGGED)
+ continue;
+
+ if ((serv->flags & SERV_LITERAL_ADDRESS) &&
+ !(serv->flags & (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)))
+ {
+ count--;
+ if (++locals <= LOCALS_LOGGED)
+ my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
+ }
+ else if (serv->flags & SERV_USE_RESOLV)
+ my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
+ }
+
+ if (locals > LOCALS_LOGGED)
+ my_syslog(LOG_INFO, _("using %d more local addresses"), locals - LOCALS_LOGGED);
+ if (count - 1 > SERVERS_LOGGED)
+ my_syslog(LOG_INFO, _("using %d more nameservers"), count - SERVERS_LOGGED - 1);
+
+ /* Remove unused sfds */
+ for (sfd = daemon->sfds, up = &daemon->sfds; sfd; sfd = tmp)
+ {
+ tmp = sfd->next;
+ if (!sfd->used)
+ {
+ *up = sfd->next;
+ close(sfd->fd);
+ free(sfd);
+ }
+ else
+ up = &sfd->next;
+ }
+
+ cleanup_servers(); /* remove servers we just deleted. */
+ build_server_array();
+}
+
+/* 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;
+ 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;
+ }
+
+ mark_servers(SERV_FROM_RESOLV);
+
+ 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 (inet_pton(AF_INET, token, &addr.in.sin_addr) > 0)
+ {
+#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);
+ }
+ else
+ {
+ int scope_index = 0;
+ char *scope_id = strchr(token, '%');
+
+ if (scope_id)
+ {
+ *(scope_id++) = 0;
+ scope_index = if_nametoindex(scope_id);
+ }
+
+ 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;
+ source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0;
+ addr.in6.sin6_port = htons(NAMESERVER_PORT);
+ addr.in6.sin6_scope_id = scope_index;
+ source_addr.in6.sin6_addr = in6addr_any;
+ source_addr.in6.sin6_port = htons(daemon->query_port);
+ source_addr.in6.sin6_scope_id = 0;
+ }
+ else
+ continue;
+ }
+
+ add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL, NULL);
+ gotone = 1;
+ }
+
+ fclose(f);
+ cleanup_servers();
+
+ return gotone;
+}
+
+/* Called when addresses are added or deleted from an interface */
+void newaddress(time_t now)
+{
+ (void)now;
+
+ if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) ||
+ daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+ enumerate_interfaces(0);
+
+ if (option_bool(OPT_CLEVERBIND))
+ create_bound_listeners(0);
+
+#ifdef HAVE_DHCP6
+ if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+ join_multicast(0);
+
+ if (daemon->doing_dhcp6 || daemon->doing_ra)
+ dhcp_construct_contexts(now);
+
+ if (daemon->doing_dhcp6)
+ lease_find_interfaces(now);
+#endif
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/option.c b/ap/app/dnsmasq/dnsmasq-2.86/src/option.c
new file mode 100755
index 0000000..f657a08
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/option.c
@@ -0,0 +1,5584 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 int 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
+#define LOPT_INCR_ADDR 302
+#define LOPT_CONNTRACK 303
+#define LOPT_FQDN 304
+#define LOPT_LUASCRIPT 305
+#define LOPT_RA 306
+#define LOPT_DUID 307
+#define LOPT_HOST_REC 308
+#define LOPT_TFTP_LC 309
+#define LOPT_RR 310
+#define LOPT_CLVERBIND 311
+#define LOPT_MAXCTTL 312
+#define LOPT_AUTHZONE 313
+#define LOPT_AUTHSERV 314
+#define LOPT_AUTHTTL 315
+#define LOPT_AUTHSOA 316
+#define LOPT_AUTHSFS 317
+#define LOPT_AUTHPEER 318
+#define LOPT_IPSET 319
+#define LOPT_SYNTH 320
+#define LOPT_RELAY 323
+#define LOPT_RA_PARAM 324
+#define LOPT_ADD_SBNET 325
+#define LOPT_QUIET_DHCP 326
+#define LOPT_QUIET_DHCP6 327
+#define LOPT_QUIET_RA 328
+#define LOPT_SEC_VALID 329
+#define LOPT_TRUST_ANCHOR 330
+#define LOPT_DNSSEC_DEBUG 331
+#define LOPT_REV_SERV 332
+#define LOPT_SERVERS_FILE 333
+#define LOPT_DNSSEC_CHECK 334
+#define LOPT_LOCAL_SERVICE 335
+#define LOPT_DNSSEC_TIME 336
+#define LOPT_LOOP_DETECT 337
+#define LOPT_IGNORE_ADDR 338
+#define LOPT_MINCTTL 339
+#define LOPT_DHCP_INOTIFY 340
+#define LOPT_DHOPT_INOTIFY 341
+#define LOPT_HOST_INOTIFY 342
+#define LOPT_DNSSEC_STAMP 343
+#define LOPT_TFTP_NO_FAIL 344
+#define LOPT_MAXPORT 345
+#define LOPT_CPE_ID 346
+#define LOPT_SCRIPT_ARP 347
+#define LOPT_DHCPTTL 348
+#define LOPT_TFTP_MTU 349
+#define LOPT_REPLY_DELAY 350
+#define LOPT_RAPID_COMMIT 351
+#define LOPT_DUMPFILE 352
+#define LOPT_DUMPMASK 353
+#define LOPT_UBUS 354
+#define LOPT_NAME_MATCH 355
+#define LOPT_CAA 356
+#define LOPT_SHARED_NET 357
+#define LOPT_IGNORE_CLID 358
+#define LOPT_SINGLE_PORT 359
+#define LOPT_SCRIPT_TIME 360
+#define LOPT_PXE_VENDOR 361
+#define LOPT_DYNHOST 362
+#define LOPT_LOG_DEBUG 363
+#define LOPT_UMBRELLA 364
+#define LOPT_CMARK_ALST_EN 365
+#define LOPT_CMARK_ALST 366
+#define LOPT_QUIET_TFTP 367
+
+#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", 2, 0, 'q' },
+ { "user", 2, 0, 'u' },
+ { "group", 2, 0, 'g' },
+ { "resolv-file", 2, 0, 'r' },
+ { "servers-file", 1, 0, LOPT_SERVERS_FILE },
+ { "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' },
+ { "local-service", 0, 0, LOPT_LOCAL_SERVICE },
+ { "bogus-priv", 0, 0, 'b' },
+ { "bogus-nxdomain", 1, 0, 'B' },
+ { "ignore-address", 1, 0, LOPT_IGNORE_ADDR },
+ { "selfmx", 0, 0, 'e' },
+ { "filterwin2k", 0, 0, 'f' },
+ { "pid-file", 2, 0, 'x' },
+ { "strict-order", 0, 0, 'o' },
+ { "server", 1, 0, 'S' },
+ { "rev-server", 1, 0, LOPT_REV_SERV },
+ { "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' },
+ { "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
+ { "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' },
+ { "caa-record", 1, 0 , LOPT_CAA },
+ { "dns-rr", 1, 0, LOPT_RR },
+ { "enable-dbus", 2, 0, '1' },
+ { "enable-ubus", 2, 0, LOPT_UBUS },
+ { "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' },
+ { "script-on-renewal", 0, 0, LOPT_SCRIPT_TIME},
+ { "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-no-fail", 0, 0, LOPT_TFTP_NO_FAIL },
+ { "tftp-unique-root", 2, 0, LOPT_APREF },
+ { "tftp-root", 1, 0, LOPT_PREFIX },
+ { "tftp-max", 1, 0, LOPT_TFTP_MAX },
+ { "tftp-mtu", 1, 0, LOPT_TFTP_MTU },
+ { "tftp-lowercase", 0, 0, LOPT_TFTP_LC },
+ { "tftp-single-port", 0, 0, LOPT_SINGLE_PORT },
+ { "ptr-record", 1, 0, LOPT_PTR },
+ { "naptr-record", 1, 0, LOPT_NAPTR },
+ { "bridge-interface", 1, 0 , LOPT_BRIDGE },
+ { "shared-network", 1, 0, LOPT_SHARED_NET },
+ { "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 },
+ { "dhcp-pxe-vendor", 1, 0, LOPT_PXE_VENDOR },
+ { "interface-name", 1, 0, LOPT_INTNAME },
+ { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
+ { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
+ { "dhcp-hostsdir", 1, 0, LOPT_DHCP_INOTIFY },
+ { "dhcp-optsdir", 1, 0, LOPT_DHOPT_INOTIFY },
+ { "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-name-match", 1, 0, LOPT_NAME_MATCH },
+ { "dhcp-broadcast", 2, 0, LOPT_BROADCAST },
+ { "neg-ttl", 1, 0, LOPT_NEGTTL },
+ { "max-ttl", 1, 0, LOPT_MAXTTL },
+ { "min-cache-ttl", 1, 0, LOPT_MINCTTL },
+ { "max-cache-ttl", 1, 0, LOPT_MAXCTTL },
+ { "dhcp-alternate-port", 2, 0, LOPT_ALTPORT },
+ { "dhcp-scriptuser", 1, 0, LOPT_SCRIPTUSR },
+ { "min-port", 1, 0, LOPT_MINPORT },
+ { "max-port", 1, 0, LOPT_MAXPORT },
+ { "dhcp-fqdn", 0, 0, LOPT_DHCP_FQDN },
+ { "cname", 1, 0, LOPT_CNAME },
+ { "pxe-prompt", 1, 0, LOPT_PXE_PROMT },
+ { "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", 2, 0, LOPT_ADD_MAC },
+ { "add-subnet", 2, 0, LOPT_ADD_SBNET },
+ { "add-cpe-id", 1, 0 , LOPT_CPE_ID },
+ { "proxy-dnssec", 0, 0, LOPT_DNSSEC },
+ { "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR },
+ { "conntrack", 0, 0, LOPT_CONNTRACK },
+ { "dhcp-client-update", 0, 0, LOPT_FQDN },
+ { "dhcp-luascript", 1, 0, LOPT_LUASCRIPT },
+ { "enable-ra", 0, 0, LOPT_RA },
+ { "dhcp-duid", 1, 0, LOPT_DUID },
+ { "host-record", 1, 0, LOPT_HOST_REC },
+ { "bind-dynamic", 0, 0, LOPT_CLVERBIND },
+ { "auth-zone", 1, 0, LOPT_AUTHZONE },
+ { "auth-server", 1, 0, LOPT_AUTHSERV },
+ { "auth-ttl", 1, 0, LOPT_AUTHTTL },
+ { "auth-soa", 1, 0, LOPT_AUTHSOA },
+ { "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
+ { "auth-peer", 1, 0, LOPT_AUTHPEER },
+ { "ipset", 1, 0, LOPT_IPSET },
+ { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
+ { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
+ { "synth-domain", 1, 0, LOPT_SYNTH },
+ { "dnssec", 0, 0, LOPT_SEC_VALID },
+ { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
+ { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
+ { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
+ { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
+ { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
+ { "dhcp-relay", 1, 0, LOPT_RELAY },
+ { "ra-param", 1, 0, LOPT_RA_PARAM },
+ { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
+ { "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 },
+ { "quiet-ra", 0, 0, LOPT_QUIET_RA },
+ { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT },
+ { "script-arp", 0, 0, LOPT_SCRIPT_ARP },
+ { "dhcp-ttl", 1, 0 , LOPT_DHCPTTL },
+ { "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY },
+ { "dhcp-rapid-commit", 0, 0, LOPT_RAPID_COMMIT },
+ { "dumpfile", 1, 0, LOPT_DUMPFILE },
+ { "dumpmask", 1, 0, LOPT_DUMPMASK },
+ { "dhcp-ignore-clid", 0, 0, LOPT_IGNORE_CLID },
+ { "dynamic-host", 1, 0, LOPT_DYNHOST },
+ { "log-debug", 0, 0, LOPT_LOG_DEBUG },
+ { "umbrella", 2, 0, LOPT_UMBRELLA },
+ { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
+ { 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, "<integer>", 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>,...", 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, "<path>", gettext_noop("Read DHCP host specs from file."), NULL },
+ { LOPT_DHCP_OPTS, ARG_DUP, "<path>", gettext_noop("Read DHCP option specs from file."), NULL },
+ { LOPT_DHCP_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from a directory."), NULL },
+ { LOPT_DHOPT_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP options from a directory."), 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 },
+ { LOPT_HOST_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read hosts files from a directory."), NULL },
+ { 'i', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to listen on."), NULL },
+ { 'I', ARG_DUP, "<interface>", 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 },
+ { LOPT_PXE_VENDOR, ARG_DUP, "<vendor>[,...]", gettext_noop("Specify vendor class to match for PXE requests."), 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, "<integer>", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL },
+ { 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" },
+ { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL },
+ { 'Q', ARG_ONE, "<integer>", 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 },
+ { LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
+ { 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL },
+ { LOPT_REV_SERV, ARG_DUP, "<addr>/<prefix>,<ipaddr>", gettext_noop("Specify address of upstream servers for reverse address queries"), 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, "<integer>", gettext_noop("Specify time-to-live in seconds for replies from /etc/hosts."), NULL },
+ { LOPT_NEGTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for negative caching."), NULL },
+ { LOPT_MAXTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL },
+ { LOPT_MAXCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live ceiling for cache."), NULL },
+ { LOPT_MINCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live floor for cache."), 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, "<ipaddr>,<ipaddr>,<netmask>", gettext_noop("Translate IPv4 addresses from upstream servers."), NULL },
+ { 'W', ARG_DUP, "<name>,<target>,...", gettext_noop("Specify a SRV record."), NULL },
+ { 'w', 0, NULL, gettext_noop("Display this message. Use --help dhcp or --help dhcp6 for known DHCP options."), NULL },
+ { 'x', ARG_ONE, "<path>", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE },
+ { 'X', ARG_ONE, "<integer>", gettext_noop("Specify maximum number of DHCP leases (defaults to %s)."), "&" },
+ { 'y', OPT_LOCALISE, NULL, gettext_noop("Answer DNS queries based on the interface a query was sent to."), NULL },
+ { 'Y', ARG_DUP, "<name>,<txt>[,<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', ARG_ONE, "[=<busname>]", gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL },
+ { LOPT_UBUS, ARG_ONE, "[=<busname>]", gettext_noop("Enable the UBus interface."), 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 },
+ { LOPT_SHARED_NET, ARG_DUP, "<iface>|<addr>,<addr>", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL},
+ { '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL },
+ { '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL },
+ { LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL },
+ { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change scripts as this user."), NULL },
+ { LOPT_SCRIPT_ARP, OPT_SCRIPT_ARP, NULL, gettext_noop("Call dhcp-script with changes to local ARP table."), NULL },
+ { '7', ARG_DUP, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL },
+ { '8', ARG_ONE, "<facility>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL },
+ { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL },
+ { '0', ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" },
+ { LOPT_RELOAD, OPT_RELOAD, NULL, gettext_noop("Clear DNS cache when reloading %s."), RESOLVFILE },
+ { 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, "[=<intr>[,<intr>]]", gettext_noop("Enable integrated read-only TFTP server."), NULL },
+ { LOPT_PREFIX, ARG_DUP, "<dir>[,<iface>]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL },
+ { LOPT_APREF, ARG_DUP, "[=ip|mac]", gettext_noop("Add client IP or hardware address to tftp-root."), NULL },
+ { LOPT_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL },
+ { LOPT_TFTP_NO_FAIL, OPT_TFTP_NO_FAIL, NULL, gettext_noop("Do not terminate the service if TFTP directories are inaccessible."), NULL },
+ { LOPT_TFTP_MAX, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent TFTP transfers (defaults to %s)."), "#" },
+ { LOPT_TFTP_MTU, ARG_ONE, "<integer>", gettext_noop("Maximum MTU to use for TFTP transfers."), NULL },
+ { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL },
+ { LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL },
+ { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL },
+ { LOPT_SINGLE_PORT, OPT_SINGLE_PORT, NULL, gettext_noop("Use only one port for TFTP server."), NULL },
+ { LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL },
+ { LOPT_MAX_LOGS, ARG_ONE, "[=<integer>]", 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_NAME_MATCH, ARG_DUP, "set:<tag>,<string>[*]", gettext_noop("Set tag if client provides given name."), NULL },
+ { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL },
+ { LOPT_NAPTR, ARG_DUP, "<name>,<naptr>", gettext_noop("Specify NAPTR DNS record."), NULL },
+ { LOPT_MINPORT, ARG_ONE, "<port>", gettext_noop("Specify lowest port available for DNS query transmission."), NULL },
+ { LOPT_MAXPORT, ARG_ONE, "<port>", gettext_noop("Specify highest port available for DNS query transmission."), NULL },
+ { LOPT_DHCP_FQDN, OPT_DHCP_FQDN, NULL, gettext_noop("Use only fully qualified domain names for DHCP clients."), NULL },
+ { LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL},
+ { LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL },
+ { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<iface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL},
+ { LOPT_CNAME, ARG_DUP, "<alias>,<target>[,<ttl>]", gettext_noop("Specify alias name for LOCAL DNS name."), NULL },
+ { LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL },
+ { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
+ { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
+ { LOPT_ADD_MAC, ARG_DUP, "[=base64|text]", gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
+ { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add specified IP subnet to forwarded DNS queries."), NULL },
+ { LOPT_CPE_ID, ARG_ONE, "<text>", gettext_noop("Add client identification to forwarded DNS queries."), NULL },
+ { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
+ { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
+ { LOPT_IGNORE_CLID, OPT_IGNORE_CLID, NULL, gettext_noop("Ignore client identifier option sent by DHCP clients."), NULL },
+ { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
+ { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
+ { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL },
+ { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL },
+ { LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL },
+ { LOPT_DYNHOST, ARG_DUP, "<name>,[<IPv4>][,<IPv6>],<interface-name>", gettext_noop("Specify host record in interface subnet"), NULL },
+ { LOPT_CAA, ARG_DUP, "<name>,<flags>,<tag>,<value>", gettext_noop("Specify certification authority authorization record"), NULL },
+ { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL },
+ { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL },
+ { LOPT_AUTHSERV, ARG_ONE, "<NS>,<interface>", gettext_noop("Export local names to global DNS"), NULL },
+ { LOPT_AUTHZONE, ARG_DUP, "<domain>,[<subnet>...]", gettext_noop("Domain to export to global DNS"), NULL },
+ { LOPT_AUTHTTL, ARG_ONE, "<integer>", gettext_noop("Set TTL for authoritative replies"), NULL },
+ { LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritative zone information"), NULL },
+ { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
+ { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
+ { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
+ { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
+ { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
+ { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
+ { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+ { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
+ { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
+ { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
+ { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
+ { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
+ { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
+ { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
+ { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
+ { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
+ { LOPT_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), NULL },
+ { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL },
+ { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL },
+ { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL },
+ { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL },
+ { LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL },
+ { LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL },
+ { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
+ { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
+ { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
+ { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
+ { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
+ { 0, 0, NULL, NULL, NULL }
+};
+
+/* We hide metacharacters in quoted strings by mapping them into the ASCII control
+ character space. Note that the \0, \t \b \r \033 and \n characters are carefully placed in the
+ following sequence so that they map to themselves: it is therefore possible to call
+ unhide_metas repeatedly on string without breaking things.
+ 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(const char *cp)
+{
+ char *ret = NULL;
+ size_t len;
+
+ if (cp && (len = strlen(cp)) != 0)
+ {
+ ret = opt_malloc(len+1);
+ memcpy(ret, cp, len+1);
+
+ /* 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;
+
+ if (strlen(s) == 0)
+ return opt_string_alloc("");
+
+ 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 numeric_check(char *a)
+{
+ char *p;
+
+ if (!a)
+ return 0;
+
+ unhide_metas(a);
+
+ for (p = a; *p; p++)
+ if (*p < '0' || *p > '9')
+ return 0;
+
+ return 1;
+}
+
+static int atoi_check(char *a, int *res)
+{
+ if (!numeric_check(a))
+ return 0;
+ *res = atoi(a);
+ return 1;
+}
+
+static int strtoul_check(char *a, u32 *res)
+{
+ unsigned long x;
+
+ if (!numeric_check(a))
+ return 0;
+ x = strtoul(a, NULL, 10);
+ if (errno || x > UINT32_MAX) {
+ errno = 0;
+ return 0;
+ }
+ *res = (u32)x;
+ return 1;
+}
+
+static int atoi_check16(char *a, int *res)
+{
+ if (!(atoi_check(a, res)) ||
+ *res < 0 ||
+ *res > 0xffff)
+ return 0;
+
+ return 1;
+}
+
+#ifdef HAVE_DNSSEC
+static int atoi_check8(char *a, int *res)
+{
+ if (!(atoi_check(a, res)) ||
+ *res < 0 ||
+ *res > 0xff)
+ return 0;
+
+ return 1;
+}
+#endif
+
+#ifndef NO_ID
+static void add_txt(char *name, char *txt, int stat)
+{
+ struct txt_record *r = opt_malloc(sizeof(struct txt_record));
+
+ if (txt)
+ {
+ size_t len = strlen(txt);
+ r->txt = opt_malloc(len+1);
+ r->len = len+1;
+ *(r->txt) = len;
+ memcpy((r->txt)+1, txt, len);
+ }
+
+ r->stat = stat;
+ r->name = opt_string_alloc(name);
+ r->next = daemon->txt;
+ daemon->txt = r;
+ r->class = C_CHAOS;
+}
+#endif
+
+static void do_usage(void)
+{
+ 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("%-55.55s", 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");
+ }
+}
+
+#define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0)
+#define ret_err_free(x,m) do { strcpy(errstr, (x)); free((m)); return 0; } while (0)
+#define goto_err(x) do { strcpy(errstr, (x)); goto on_error; } while (0)
+
+static char *parse_mysockaddr(char *arg, union mysockaddr *addr)
+{
+ if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+ addr->sa.sa_family = AF_INET;
+ else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+ addr->sa.sa_family = AF_INET6;
+ else
+ return _("bad address");
+
+ return NULL;
+}
+
+char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags)
+{
+ int source_port = 0, serv_port = NAMESERVER_PORT;
+ char *portno, *source;
+ char *interface_opt = NULL;
+ int scope_index = 0;
+ char *scope_id;
+
+ *interface = 0;
+
+ if (strcmp(arg, "#") == 0)
+ {
+ if (flags)
+ *flags |= SERV_USE_RESOLV;
+ return NULL;
+ }
+
+ if ((source = split_chr(arg, '@')) && /* is there a source. */
+ (portno = split_chr(source, '#')) &&
+ !atoi_check16(portno, &source_port))
+ return _("bad port");
+
+ if ((portno = split_chr(arg, '#')) && /* is there a port no. */
+ !atoi_check16(portno, &serv_port))
+ return _("bad port");
+
+ scope_id = split_chr(arg, '%');
+
+ if (source) {
+ interface_opt = split_chr(source, '@');
+
+ if (interface_opt)
+ {
+#if defined(SO_BINDTODEVICE)
+ safe_strncpy(interface, source, IF_NAMESIZE);
+ source = interface_opt;
+#else
+ return _("interface binding not supported");
+#endif
+ }
+ }
+
+ if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+ {
+ addr->in.sin_port = htons(serv_port);
+ addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in);
+#endif
+ source_addr->in.sin_addr.s_addr = INADDR_ANY;
+ source_addr->in.sin_port = htons(daemon->query_port);
+
+ if (source)
+ {
+ if (flags)
+ *flags |= SERV_HAS_SOURCE;
+ source_addr->in.sin_port = htons(source_port);
+ if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0))
+ {
+#if defined(SO_BINDTODEVICE)
+ if (interface_opt)
+ return _("interface can only be specified once");
+
+ source_addr->in.sin_addr.s_addr = INADDR_ANY;
+ safe_strncpy(interface, source, IF_NAMESIZE);
+#else
+ return _("interface binding not supported");
+#endif
+ }
+ }
+ }
+ else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+ {
+ if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
+ return _("bad interface name");
+
+ addr->in6.sin6_port = htons(serv_port);
+ addr->in6.sin6_scope_id = scope_index;
+ source_addr->in6.sin6_addr = in6addr_any;
+ source_addr->in6.sin6_port = htons(daemon->query_port);
+ source_addr->in6.sin6_scope_id = 0;
+ addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6;
+ addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6);
+#endif
+ if (source)
+ {
+ if (flags)
+ *flags |= SERV_HAS_SOURCE;
+ source_addr->in6.sin6_port = htons(source_port);
+ if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0)
+ {
+#if defined(SO_BINDTODEVICE)
+ if (interface_opt)
+ return _("interface can only be specified once");
+
+ source_addr->in6.sin6_addr = in6addr_any;
+ safe_strncpy(interface, source, IF_NAMESIZE);
+#else
+ return _("interface binding not supported");
+#endif
+ }
+ }
+ }
+ else
+ return _("bad address");
+
+ return NULL;
+}
+
+static int domain_rev4(char *domain, struct in_addr addr, int msize)
+{
+ in_addr_t a = ntohl(addr.s_addr);
+
+ *domain = 0;
+
+ switch (msize)
+ {
+ case 32:
+ domain += sprintf(domain, "%u.", a & 0xff);
+ /* fall through */
+ case 24:
+ domain += sprintf(domain, "%d.", (a >> 8) & 0xff);
+ /* fall through */
+ case 16:
+ domain += sprintf(domain, "%d.", (a >> 16) & 0xff);
+ /* fall through */
+ case 8:
+ domain += sprintf(domain, "%d.", (a >> 24) & 0xff);
+ break;
+ default:
+ return 0;
+ }
+
+ domain += sprintf(domain, "in-addr.arpa");
+
+ return 1;
+}
+
+static int domain_rev6(char *domain, struct in6_addr *addr, int msize)
+{
+ int i;
+
+ if (msize > 128 || msize%4)
+ return 0;
+
+ *domain = 0;
+
+ for (i = msize-1; i >= 0; i -= 4)
+ {
+ int dig = ((unsigned char *)addr)[i>>3];
+ domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
+ }
+ domain += sprintf(domain, "ip6.arpa");
+
+ return 1;
+}
+
+#ifdef HAVE_DHCP
+
+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;
+}
+
+static struct dhcp_netid *dhcp_netid_create(const char *net, struct dhcp_netid *next)
+{
+ struct dhcp_netid *tt;
+ tt = opt_malloc(sizeof (struct dhcp_netid));
+ tt->net = opt_string_alloc(net);
+ tt->next = next;
+ return tt;
+}
+
+static void dhcp_netid_free(struct dhcp_netid *nid)
+{
+ while (nid)
+ {
+ struct dhcp_netid *tmp = nid;
+ nid = nid->next;
+ free(tmp->net);
+ free(tmp);
+ }
+}
+
+/* Parse one or more tag:s before parameters.
+ * Moves arg to the end of tags. */
+static struct dhcp_netid * dhcp_tags(char **arg)
+{
+ struct dhcp_netid *id = NULL;
+
+ while (is_tag_prefix(*arg))
+ {
+ char *comma = split(*arg);
+ id = dhcp_netid_create((*arg)+4, id);
+ *arg = comma;
+ };
+ if (!*arg)
+ {
+ dhcp_netid_free(id);
+ id = NULL;
+ }
+ return id;
+}
+
+static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
+{
+ while (netid)
+ {
+ struct dhcp_netid_list *tmplist = netid;
+ netid = netid->next;
+ dhcp_netid_free(tmplist->list);
+ free(tmplist);
+ }
+}
+
+static void dhcp_config_free(struct dhcp_config *config)
+{
+ if (config)
+ {
+ struct hwaddr_config *hwaddr = config->hwaddr;
+
+ while (hwaddr)
+ {
+ struct hwaddr_config *tmp = hwaddr;
+ hwaddr = hwaddr->next;
+ free(tmp);
+ }
+
+ dhcp_netid_list_free(config->netid);
+ dhcp_netid_free(config->filter);
+
+ if (config->flags & CONFIG_CLID)
+ free(config->clid);
+ if (config->flags & CONFIG_NAME)
+ free(config->hostname);
+
+#ifdef HAVE_DHCP6
+ if (config->flags & CONFIG_ADDR6)
+ {
+ struct addrlist *addr, *tmp;
+
+ for (addr = config->addr6; addr; addr = tmp)
+ {
+ tmp = addr->next;
+ free(addr);
+ }
+ }
+#endif
+
+ free(config);
+ }
+}
+
+static void dhcp_context_free(struct dhcp_context *ctx)
+{
+ if (ctx)
+ {
+ dhcp_netid_free(ctx->filter);
+ free(ctx->netid.net);
+#ifdef HAVE_DHCP6
+ free(ctx->template_interface);
+#endif
+ free(ctx);
+ }
+}
+
+static void dhcp_opt_free(struct dhcp_opt *opt)
+{
+ if (opt->flags & DHOPT_VENDOR)
+ free(opt->u.vendor_class);
+ dhcp_netid_free(opt->netid);
+ free(opt->val);
+ free(opt);
+}
+
+
+/* This is too insanely large to keep in-line in the switch */
+static int parse_dhcp_opt(char *errstr, char *arg, int flags)
+{
+ struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt));
+ char lenchar = 0, *cp;
+ int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots;
+ char *comma = NULL;
+ u16 opt_len = 0;
+ int is6 = 0;
+ int option_ok = 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;
+ option_ok = 1;
+ break;
+ }
+
+ if (strstr(arg, "option:") == arg)
+ {
+ if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
+ {
+ opt_len = lookup_dhcp_len(AF_INET, new->opt);
+ /* option:<optname> must follow tag and vendor string. */
+ if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
+ option_ok = 1;
+ }
+ break;
+ }
+#ifdef HAVE_DHCP6
+ else if (strstr(arg, "option6:") == arg)
+ {
+ for (cp = arg+8; *cp; cp++)
+ if (*cp < '0' || *cp > '9')
+ break;
+
+ if (!*cp)
+ {
+ new->opt = atoi(arg+8);
+ opt_len = 0;
+ option_ok = 1;
+ }
+ else
+ {
+ if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
+ {
+ opt_len = lookup_dhcp_len(AF_INET6, new->opt);
+ if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
+ option_ok = 1;
+ }
+ }
+ /* option6:<opt>|<optname> must follow tag and vendor string. */
+ is6 = 1;
+ break;
+ }
+#endif
+ else if (strstr(arg, "vendor:") == arg)
+ {
+ new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
+ new->flags |= DHOPT_VENDOR;
+ if ((new->flags & DHOPT_ENCAPSULATE) || flags == DHOPT_MATCH)
+ goto_err(_("inappropriate vendor:"));
+ }
+ else if (strstr(arg, "encap:") == arg)
+ {
+ new->u.encap = atoi(arg+6);
+ new->flags |= DHOPT_ENCAPSULATE;
+ if ((new->flags & DHOPT_VENDOR) || flags == DHOPT_MATCH)
+ goto_err(_("inappropriate encap:"));
+ }
+ else if (strstr(arg, "vi-encap:") == arg)
+ {
+ new->u.encap = atoi(arg+9);
+ new->flags |= DHOPT_RFC3925;
+ if (flags == DHOPT_MATCH)
+ {
+ option_ok = 1;
+ break;
+ }
+ }
+ else
+ {
+ /* allow optional "net:" or "tag:" for consistency */
+ const char *name = (is_tag_prefix(arg)) ? arg+4 : set_prefix(arg);
+ new->netid = dhcp_netid_create(name, new->netid);
+ }
+
+ arg = comma;
+ }
+
+#ifdef HAVE_DHCP6
+ if (is6)
+ {
+ if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))
+ goto_err(_("unsupported encapsulation for IPv6 option"));
+
+ if (opt_len == 0 &&
+ !(new->flags & DHOPT_RFC3925))
+ opt_len = lookup_dhcp_len(AF_INET6, new->opt);
+ }
+ else
+#endif
+ if (opt_len == 0 &&
+ !(new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925)))
+ opt_len = lookup_dhcp_len(AF_INET, new->opt);
+
+ /* option may be missing with rfc3925 match */
+ if (!option_ok)
+ goto_err(_("bad dhcp-option"));
+
+ if (comma)
+ {
+ /* characterise the value */
+ char c;
+ int found_dig = 0, found_colon = 0;
+ is_addr = is_addr6 = 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;
+ found_colon = 1;
+ }
+ else if (c == '/')
+ {
+ is_addr6 = 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 = is_addr6 = 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;
+ if (c != '[' && c != ']')
+ is_addr6 = 0;
+ }
+ }
+ else
+ found_dig = 1;
+
+ if (!found_dig)
+ is_dec = is_addr = 0;
+
+ if (!found_colon)
+ is_addr6 = 0;
+
+#ifdef HAVE_DHCP6
+ /* NTP server option takes hex, addresses or FQDN */
+ if (is6 && new->opt == OPTION6_NTP_SERVER && !is_hex)
+ opt_len |= is_addr6 ? OT_ADDR_LIST : OT_RFC1035_NAME;
+#endif
+
+ /* We know that some options take addresses */
+ if (opt_len & OT_ADDR_LIST)
+ {
+ is_string = is_dec = is_hex = 0;
+
+ if (!is6 && (!is_addr || dots == 0))
+ goto_err(_("bad IP address"));
+
+ if (is6 && !is_addr6)
+ goto_err(_("bad IPv6 address"));
+ }
+ /* or names */
+ else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING))
+ is_addr6 = is_addr = is_dec = is_hex = 0;
+
+ if (found_dig && (opt_len & OT_TIME) && strlen(comma) > 0)
+ {
+ int val, fac = 1;
+
+ switch (comma[strlen(comma) - 1])
+ {
+ case 'w':
+ case 'W':
+ fac *= 7;
+ /* fall through */
+ 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':
+ comma[strlen(comma) - 1] = 0;
+ }
+
+ new->len = 4;
+ new->val = opt_malloc(4);
+ val = atoi(comma);
+ *((int *)new->val) = htonl(val * fac);
+ }
+ else 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 && !is6)
+ {
+ 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, '/');
+ if (!inet_pton(AF_INET, cp, &in))
+ goto_err(_("bad IPv4 address"));
+ if (!slash)
+ {
+ memcpy(op, &in, INADDRSZ);
+ op += INADDRSZ;
+ }
+ else
+ {
+ unsigned char *p = (unsigned char *)∈
+ int netsize = atoi(slash);
+ *op++ = netsize;
+ if (netsize > 0)
+ *op++ = *p++;
+ if (netsize > 8)
+ *op++ = *p++;
+ if (netsize > 16)
+ *op++ = *p++;
+ if (netsize > 24)
+ *op++ = *p++;
+ new->flags &= ~DHOPT_ADDR; /* cannot re-write descriptor format */
+ }
+ }
+ new->len = op - new->val;
+ }
+ else if (is_addr6 && is6)
+ {
+ unsigned char *op;
+ new->val = op = opt_malloc(16 * addrs);
+ new->flags |= DHOPT_ADDR6;
+ while (addrs--)
+ {
+ cp = comma;
+ comma = split(cp);
+
+ /* check for [1234::7] */
+ if (*cp == '[')
+ cp++;
+ if (strlen(cp) > 1 && cp[strlen(cp)-1] == ']')
+ cp[strlen(cp)-1] = 0;
+
+ if (inet_pton(AF_INET6, cp, op))
+ {
+ op += IN6ADDRSZ;
+ continue;
+ }
+
+ goto_err(_("bad IPv6 address"));
+ }
+ new->len = op - new->val;
+ }
+ else if (is_string)
+ {
+ /* text arg */
+ if ((new->opt == OPTION_DOMAIN_SEARCH || new->opt == OPTION_SIP_SERVER) &&
+ !is6 && !(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)))
+ goto_err(_("bad domain in dhcp-option"));
+
+ 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)
+ m[0] = 0;
+ new->len = (int) len + header_size;
+ new->val = m;
+ }
+#ifdef HAVE_DHCP6
+ else if (comma && (opt_len & OT_CSTRING))
+ {
+ /* length fields are two bytes so need 16 bits for each string */
+ int i, commas = 1;
+ unsigned char *p, *newp;
+
+ for (i = 0; comma[i]; i++)
+ if (comma[i] == ',')
+ commas++;
+
+ newp = opt_malloc(strlen(comma)+(2*commas));
+ p = newp;
+ arg = comma;
+ comma = split(arg);
+
+ while (arg && *arg)
+ {
+ u16 len = strlen(arg);
+ unhide_metas(arg);
+ PUTSHORT(len, p);
+ memcpy(p, arg, len);
+ p += len;
+
+ arg = comma;
+ comma = split(arg);
+ }
+
+ new->val = newp;
+ new->len = p - newp;
+ }
+ else if (comma && (opt_len & OT_RFC1035_NAME))
+ {
+ unsigned char *p = NULL, *q, *newp, *end;
+ int len = 0;
+ int header_size = (is6 && new->opt == OPTION6_NTP_SERVER) ? 4 : 0;
+ arg = comma;
+ comma = split(arg);
+
+ while (arg && *arg)
+ {
+ char *dom = canonicalise_opt(arg);
+ if (!dom)
+ goto_err(_("bad domain in dhcp-option"));
+
+ newp = opt_malloc(len + header_size + strlen(dom) + 2);
+
+ if (p)
+ {
+ memcpy(newp, p, len);
+ free(p);
+ }
+
+ p = newp;
+ q = p + len;
+ end = do_rfc1035_name(q + header_size, dom, NULL);
+ *end++ = 0;
+ if (is6 && new->opt == OPTION6_NTP_SERVER)
+ {
+ PUTSHORT(NTP_SUBOPTION_SRV_FQDN, q);
+ PUTSHORT(end - q - 2, q);
+ }
+ len = end - p;
+ free(dom);
+
+ arg = comma;
+ comma = split(arg);
+ }
+
+ new->val = p;
+ new->len = len;
+ }
+#endif
+ else
+ {
+ new->len = strlen(comma);
+ /* keep terminating zero on string */
+ new->val = (unsigned char *)opt_string_alloc(comma);
+ new->flags |= DHOPT_STRING;
+ }
+ }
+ }
+
+ if (!is6 &&
+ ((new->len > 255) ||
+ (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) ||
+ (new->len > 250 && (new->flags & DHOPT_RFC3925))))
+ goto_err(_("dhcp-option too long"));
+
+ if (flags == DHOPT_MATCH)
+ {
+ if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
+ !new->netid ||
+ new->netid->next)
+ goto_err(_("illegal dhcp-match"));
+
+ if (is6)
+ {
+ new->next = daemon->dhcp_match6;
+ daemon->dhcp_match6 = new;
+ }
+ else
+ {
+ new->next = daemon->dhcp_match;
+ daemon->dhcp_match = new;
+ }
+ }
+ else if (is6)
+ {
+ new->next = daemon->dhcp_opts6;
+ daemon->dhcp_opts6 = new;
+ }
+ else
+ {
+ new->next = daemon->dhcp_opts;
+ daemon->dhcp_opts = new;
+ }
+
+ return 1;
+on_error:
+ dhcp_opt_free(new);
+ return 0;
+}
+
+#endif
+
+void set_option_bool(unsigned int opt)
+{
+ option_var(opt) |= option_val(opt);
+}
+
+void reset_option_bool(unsigned int opt)
+{
+ option_var(opt) &= ~(option_val(opt));
+}
+
+static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
+{
+ int i;
+ char *comma;
+
+ if (option == '?')
+ ret_err(gen_err);
+
+ 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)
+ ret_err(_("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)
+ ret_err(_("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 1;
+ }
+
+ 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 *name;
+ struct list *next;
+ } *ignore_suffix = NULL, *match_suffix = NULL, *files = NULL, *li;
+
+ comma = split(arg);
+ if (!(directory = opt_string_alloc(arg)))
+ break;
+
+ for (arg = comma; arg; arg = comma)
+ {
+ comma = split(arg);
+ if (strlen(arg) != 0)
+ {
+ li = opt_malloc(sizeof(struct list));
+ if (*arg == '*')
+ {
+ /* "*" with no suffix is a no-op */
+ if (arg[1] == 0)
+ free(li);
+ else
+ {
+ li->next = match_suffix;
+ match_suffix = li;
+ /* Have to copy: buffer is overwritten */
+ li->name = opt_string_alloc(arg+1);
+ }
+ }
+ else
+ {
+ li->next = ignore_suffix;
+ ignore_suffix = li;
+ /* Have to copy: buffer is overwritten */
+ li->name = 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;
+
+ if (match_suffix)
+ {
+ for (li = match_suffix; li; li = li->next)
+ {
+ /* check for required suffices */
+ size_t ls = strlen(li->name);
+ if (len > ls &&
+ strcmp(li->name, &ent->d_name[len - ls]) == 0)
+ break;
+ }
+ if (!li)
+ continue;
+ }
+
+ for (li = ignore_suffix; li; li = li->next)
+ {
+ /* check for proscribed suffices */
+ size_t ls = strlen(li->name);
+ if (len > ls &&
+ strcmp(li->name, &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);
+
+ /* files must be readable */
+ if (stat(path, &buf) == -1)
+ die(_("cannot access %s: %s"), path, EC_FILE);
+
+ /* only reg files allowed. */
+ if (S_ISREG(buf.st_mode))
+ {
+ /* sort files into order. */
+ struct list **up, *new = opt_malloc(sizeof(struct list));
+ new->name = path;
+
+ for (up = &files, li = files; li; up = &li->next, li = li->next)
+ if (strcmp(li->name, path) >=0)
+ break;
+
+ new->next = li;
+ *up = new;
+ }
+
+ }
+
+ for (li = files; li; li = li->next)
+ one_file(li->name, 0);
+
+ closedir(dir_stream);
+ free(directory);
+ for(; ignore_suffix; ignore_suffix = li)
+ {
+ li = ignore_suffix->next;
+ free(ignore_suffix->name);
+ free(ignore_suffix);
+ }
+ for(; match_suffix; match_suffix = li)
+ {
+ li = match_suffix->next;
+ free(match_suffix->name);
+ free(match_suffix);
+ }
+ for(; files; files = li)
+ {
+ li = files->next;
+ free(files->name);
+ free(files);
+ }
+ break;
+ }
+
+ case LOPT_ADD_SBNET: /* --add-subnet */
+ set_option_bool(OPT_CLIENT_SUBNET);
+ if (arg)
+ {
+ char *err, *end;
+ comma = split(arg);
+
+ struct mysubnet* new = opt_malloc(sizeof(struct mysubnet));
+ if ((end = split_chr(arg, '/')))
+ {
+ /* has subnet+len */
+ err = parse_mysockaddr(arg, &new->addr);
+ if (err)
+ ret_err_free(err, new);
+ if (!atoi_check(end, &new->mask))
+ ret_err_free(gen_err, new);
+ new->addr_used = 1;
+ }
+ else if (!atoi_check(arg, &new->mask))
+ ret_err_free(gen_err, new);
+
+ daemon->add_subnet4 = new;
+
+ if (comma)
+ {
+ new = opt_malloc(sizeof(struct mysubnet));
+ if ((end = split_chr(comma, '/')))
+ {
+ /* has subnet+len */
+ err = parse_mysockaddr(comma, &new->addr);
+ if (err)
+ ret_err_free(err, new);
+ if (!atoi_check(end, &new->mask))
+ ret_err_free(gen_err, new);
+ new->addr_used = 1;
+ }
+ else
+ {
+ if (!atoi_check(comma, &new->mask))
+ ret_err_free(gen_err, new);
+ }
+
+ daemon->add_subnet6 = new;
+ }
+ }
+ break;
+
+ case '1': /* --enable-dbus */
+ set_option_bool(OPT_DBUS);
+ if (arg)
+ daemon->dbus_name = opt_string_alloc(arg);
+ else
+ daemon->dbus_name = DNSMASQ_SERVICE;
+ break;
+
+ case LOPT_UBUS: /* --enable-ubus */
+ set_option_bool(OPT_UBUS);
+ if (arg)
+ daemon->ubus_name = opt_string_alloc(arg);
+ else
+ daemon->ubus_name = DNSMASQ_UBUS_NAME;
+ 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__
+ ret_err(_("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
+ ret_err(_("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 LOPT_SERVERS_FILE:
+ daemon->servers_file = opt_string_alloc(arg);
+ 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))
+ ret_err(_("bad MX preference"));
+ }
+
+ if (!(name = canonicalise_opt(arg)) ||
+ (comma && !(target = canonicalise_opt(comma))))
+ ret_err(_("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)))
+ ret_err(_("bad MX target"));
+ break;
+
+ case LOPT_DUMPFILE: /* --dumpfile */
+ daemon->dump_file = opt_string_alloc(arg);
+ break;
+
+ case LOPT_DUMPMASK: /* --dumpmask */
+ daemon->dump_mask = strtol(arg, NULL, 0);
+ break;
+
+#ifdef HAVE_DHCP
+ case 'l': /* --dhcp-leasefile */
+ daemon->lease_file = opt_string_alloc(arg);
+ break;
+
+ /* Sorry about the gross pre-processor abuse */
+ case '6': /* --dhcp-script */
+ case LOPT_LUASCRIPT: /* --dhcp-luascript */
+# if !defined(HAVE_SCRIPT)
+ ret_err(_("recompile with HAVE_SCRIPT defined to enable lease-change scripts"));
+# else
+ if (option == LOPT_LUASCRIPT)
+# if !defined(HAVE_LUASCRIPT)
+ ret_err(_("recompile with HAVE_LUASCRIPT defined to enable Lua scripts"));
+# else
+ daemon->luascript = opt_string_alloc(arg);
+# endif
+ else
+ daemon->lease_change_command = opt_string_alloc(arg);
+# endif
+ break;
+#endif /* HAVE_DHCP */
+
+ case LOPT_DHCP_HOST: /* --dhcp-hostsfile */
+ case LOPT_DHCP_OPTS: /* --dhcp-optsfile */
+ case LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */
+ case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */
+ case LOPT_HOST_INOTIFY: /* --hostsdir */
+ case 'H': /* --addn-hosts */
+ {
+ struct hostsfile *new = opt_malloc(sizeof(struct hostsfile));
+ static unsigned int hosts_index = SRC_AH;
+ 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;
+ }
+ else
+ {
+ new->next = daemon->dynamic_dirs;
+ daemon->dynamic_dirs = new;
+ if (option == LOPT_DHCP_INOTIFY)
+ new->flags |= AH_DHCP_HST;
+ else if (option == LOPT_DHOPT_INOTIFY)
+ new->flags |= AH_DHCP_OPT;
+ else if (option == LOPT_HOST_INOTIFY)
+ new->flags |= AH_HOSTS;
+ }
+
+ break;
+ }
+
+ case LOPT_AUTHSERV: /* --auth-server */
+ comma = split(arg);
+
+ daemon->authserver = opt_string_alloc(arg);
+
+ while ((arg = comma))
+ {
+ struct iname *new = opt_malloc(sizeof(struct iname));
+ comma = split(arg);
+ new->name = NULL;
+ unhide_metas(arg);
+ if (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0)
+ new->addr.sa.sa_family = AF_INET;
+ else if (inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0)
+ new->addr.sa.sa_family = AF_INET6;
+ else
+ {
+ char *fam = split_chr(arg, '/');
+ new->name = opt_string_alloc(arg);
+ new->addr.sa.sa_family = 0;
+ if (fam)
+ {
+ if (strcmp(fam, "4") == 0)
+ new->addr.sa.sa_family = AF_INET;
+ else if (strcmp(fam, "6") == 0)
+ new->addr.sa.sa_family = AF_INET6;
+ else
+ {
+ free(new->name);
+ ret_err_free(gen_err, new);
+ }
+ }
+ }
+ new->next = daemon->authinterface;
+ daemon->authinterface = new;
+ };
+
+ break;
+
+ case LOPT_AUTHSFS: /* --auth-sec-servers */
+ {
+ struct name_list *new;
+
+ do {
+ comma = split(arg);
+ new = opt_malloc(sizeof(struct name_list));
+ new->name = opt_string_alloc(arg);
+ new->next = daemon->secondary_forward_server;
+ daemon->secondary_forward_server = new;
+ arg = comma;
+ } while (arg);
+ break;
+ }
+
+ case LOPT_AUTHZONE: /* --auth-zone */
+ {
+ struct auth_zone *new;
+
+ comma = split(arg);
+
+ new = opt_malloc(sizeof(struct auth_zone));
+ new->domain = opt_string_alloc(arg);
+ new->subnet = NULL;
+ new->exclude = NULL;
+ new->interface_names = NULL;
+ new->next = daemon->auth_zones;
+ daemon->auth_zones = new;
+
+ while ((arg = comma))
+ {
+ int prefixlen = 0;
+ int is_exclude = 0;
+ char *prefix;
+ struct addrlist *subnet = NULL;
+ union all_addr addr;
+
+ comma = split(arg);
+ prefix = split_chr(arg, '/');
+
+ if (prefix && !atoi_check(prefix, &prefixlen))
+ ret_err(gen_err);
+
+ if (strstr(arg, "exclude:") == arg)
+ {
+ is_exclude = 1;
+ arg = arg+8;
+ }
+
+ if (inet_pton(AF_INET, arg, &addr.addr4))
+ {
+ subnet = opt_malloc(sizeof(struct addrlist));
+ subnet->prefixlen = (prefixlen == 0) ? 24 : prefixlen;
+ subnet->flags = ADDRLIST_LITERAL;
+ }
+ else if (inet_pton(AF_INET6, arg, &addr.addr6))
+ {
+ subnet = opt_malloc(sizeof(struct addrlist));
+ subnet->prefixlen = (prefixlen == 0) ? 64 : prefixlen;
+ subnet->flags = ADDRLIST_LITERAL | ADDRLIST_IPV6;
+ }
+ else
+ {
+ struct auth_name_list *name = opt_malloc(sizeof(struct auth_name_list));
+ name->name = opt_string_alloc(arg);
+ name->flags = AUTH4 | AUTH6;
+ name->next = new->interface_names;
+ new->interface_names = name;
+ if (prefix)
+ {
+ if (prefixlen == 4)
+ name->flags &= ~AUTH6;
+ else if (prefixlen == 6)
+ name->flags &= ~AUTH4;
+ else
+ ret_err(gen_err);
+ }
+ }
+
+ if (subnet)
+ {
+ subnet->addr = addr;
+
+ if (is_exclude)
+ {
+ subnet->next = new->exclude;
+ new->exclude = subnet;
+ }
+ else
+ {
+ subnet->next = new->subnet;
+ new->subnet = subnet;
+ }
+ }
+ }
+ break;
+ }
+
+ case LOPT_AUTHSOA: /* --auth-soa */
+ comma = split(arg);
+ daemon->soa_sn = (u32)atoi(arg);
+ if (comma)
+ {
+ char *cp;
+ arg = comma;
+ comma = split(arg);
+ daemon->hostmaster = opt_string_alloc(arg);
+ for (cp = daemon->hostmaster; cp && *cp; cp++)
+ if (*cp == '@')
+ *cp = '.';
+
+ if (comma)
+ {
+ arg = comma;
+ comma = split(arg);
+ daemon->soa_refresh = (u32)atoi(arg);
+ if (comma)
+ {
+ arg = comma;
+ comma = split(arg);
+ daemon->soa_retry = (u32)atoi(arg);
+ if (comma)
+ daemon->soa_expiry = (u32)atoi(comma);
+ }
+ }
+ }
+
+ break;
+
+ case 's': /* --domain */
+ case LOPT_SYNTH: /* --synth-domain */
+ if (strcmp (arg, "#") == 0)
+ set_option_bool(OPT_RESOLV_DOMAIN);
+ else
+ {
+ char *d, *d_raw = arg;
+ comma = split(arg);
+ if (!(d = canonicalise_opt(d_raw)))
+ ret_err(gen_err);
+ else
+ {
+ free(d); /* allocate this again below. */
+ if (comma)
+ {
+ struct cond_domain *new = opt_malloc(sizeof(struct cond_domain));
+ char *netpart;
+
+ new->prefix = NULL;
+ new->indexed = 0;
+ new->prefixlen = 0;
+
+ unhide_metas(comma);
+ if ((netpart = split_chr(comma, '/')))
+ {
+ int msize;
+
+ arg = split(netpart);
+ if (!atoi_check(netpart, &msize))
+ ret_err_free(gen_err, new);
+ else if (inet_pton(AF_INET, comma, &new->start))
+ {
+ int mask;
+
+ if (msize > 32)
+ ret_err_free(_("bad prefix length"), new);
+
+ mask = (1 << (32 - msize)) - 1;
+ new->is6 = 0;
+ new->start.s_addr = ntohl(htonl(new->start.s_addr) & ~mask);
+ new->end.s_addr = new->start.s_addr | htonl(mask);
+ if (arg)
+ {
+ if (option != 's')
+ {
+ if (!(new->prefix = canonicalise_opt(arg)) ||
+ strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
+ ret_err_free(_("bad prefix"), new);
+ }
+ else if (strcmp(arg, "local") != 0 ||
+ (msize != 8 && msize != 16 && msize != 24))
+ ret_err_free(gen_err, new);
+ else
+ {
+ char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
+ /* local=/xxx.yyy.zzz.in-addr.arpa/ */
+ /* domain_rev4 can't fail here, msize checked above. */
+ domain_rev4(domain, new->start, msize);
+ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
+
+ /* local=/<domain>/ */
+ /* d_raw can't failed to canonicalise here, checked above. */
+ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
+ }
+ }
+ }
+ else if (inet_pton(AF_INET6, comma, &new->start6))
+ {
+ u64 mask, addrpart = addr6part(&new->start6);
+
+ if (msize > 128)
+ ret_err_free(_("bad prefix length"), new);
+
+ mask = (1LLU << (128 - msize)) - 1LLU;
+
+ new->is6 = 1;
+ new->prefixlen = msize;
+
+ /* prefix==64 overflows the mask calculation above */
+ if (msize <= 64)
+ mask = (u64)-1LL;
+
+ new->end6 = new->start6;
+ setaddr6part(&new->start6, addrpart & ~mask);
+ setaddr6part(&new->end6, addrpart | mask);
+
+ if (arg)
+ {
+ if (option != 's')
+ {
+ if (!(new->prefix = canonicalise_opt(arg)) ||
+ strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN)
+ ret_err_free(_("bad prefix"), new);
+ }
+ else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0))
+ ret_err_free(gen_err, new);
+ else
+ {
+ char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
+ /* generate the equivalent of
+ local=/xxx.yyy.zzz.ip6.arpa/ */
+ domain_rev6(domain, &new->start6, msize);
+ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
+
+ /* local=/<domain>/ */
+ /* d_raw can't failed to canonicalise here, checked above. */
+ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
+ }
+ }
+ }
+ else
+ ret_err_free(gen_err, new);
+ }
+ else
+ {
+ char *prefstr;
+ arg = split(comma);
+ prefstr = split(arg);
+
+ if (inet_pton(AF_INET, comma, &new->start))
+ {
+ new->is6 = 0;
+ if (!arg)
+ new->end.s_addr = new->start.s_addr;
+ else if (!inet_pton(AF_INET, arg, &new->end))
+ ret_err_free(gen_err, new);
+ }
+ else if (inet_pton(AF_INET6, comma, &new->start6))
+ {
+ new->is6 = 1;
+ if (!arg)
+ memcpy(&new->end6, &new->start6, IN6ADDRSZ);
+ else if (!inet_pton(AF_INET6, arg, &new->end6))
+ ret_err_free(gen_err, new);
+ }
+ else
+ ret_err_free(gen_err, new);
+
+ if (option != 's' && prefstr)
+ {
+ if (!(new->prefix = canonicalise_opt(prefstr)) ||
+ strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
+ ret_err_free(_("bad prefix"), new);
+ }
+ }
+
+ new->domain = canonicalise_opt(d_raw);
+ if (option == 's')
+ {
+ new->next = daemon->cond_domain;
+ daemon->cond_domain = new;
+ }
+ else
+ {
+ char *star;
+ if (new->prefix &&
+ (star = strrchr(new->prefix, '*'))
+ && *(star+1) == 0)
+ {
+ *star = 0;
+ new->indexed = 1;
+ if (new->is6 && new->prefixlen < 64)
+ ret_err_free(_("prefix length too small"), new);
+ }
+ new->next = daemon->synth_domains;
+ daemon->synth_domains = new;
+ }
+ }
+ else if (option == 's')
+ daemon->domain_suffix = canonicalise_opt(d_raw);
+ else
+ ret_err(gen_err);
+ }
+ }
+ break;
+
+ case LOPT_CPE_ID: /* --add-dns-client */
+ if (arg)
+ daemon->dns_client_id = opt_string_alloc(arg);
+ break;
+
+ case LOPT_UMBRELLA: /* --umbrella */
+ set_option_bool(OPT_UMBRELLA);
+ while (arg) {
+ comma = split(arg);
+ if (strstr(arg, "deviceid:")) {
+ arg += 9;
+ if (strlen(arg) != 16)
+ ret_err(gen_err);
+ char *p;
+ for (p = arg; *p; p++) {
+ if (!isxdigit((int)*p))
+ ret_err(gen_err);
+ }
+ set_option_bool(OPT_UMBRELLA_DEVID);
+
+ u8 *u = daemon->umbrella_device;
+ char word[3];
+ u8 i;
+ for (i = 0; i < sizeof(daemon->umbrella_device); i++, arg+=2) {
+ memcpy(word, &(arg[0]), 2);
+ *u++ = strtoul(word, NULL, 16);
+ }
+ }
+ else if (strstr(arg, "orgid:")) {
+ if (!strtoul_check(arg+6, &daemon->umbrella_org)) {
+ ret_err(gen_err);
+ }
+ }
+ else if (strstr(arg, "assetid:")) {
+ if (!strtoul_check(arg+8, &daemon->umbrella_asset)) {
+ ret_err(gen_err);
+ }
+ }
+ arg = comma;
+ }
+ break;
+
+ case LOPT_ADD_MAC: /* --add-mac */
+ if (!arg)
+ set_option_bool(OPT_ADD_MAC);
+ else
+ {
+ unhide_metas(arg);
+ if (strcmp(arg, "base64") == 0)
+ set_option_bool(OPT_MAC_B64);
+ else if (strcmp(arg, "text") == 0)
+ set_option_bool(OPT_MAC_HEX);
+ else
+ ret_err(gen_err);
+ }
+ break;
+
+ case 'u': /* --user */
+ daemon->username = opt_string_alloc(arg);
+ break;
+
+ 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->used = 0;
+ arg = comma;
+ } while (arg);
+ break;
+
+ case LOPT_TFTP: /* --enable-tftp */
+ set_option_bool(OPT_TFTP);
+ if (!arg)
+ break;
+ /* fall through */
+
+ 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 if (option == LOPT_TFTP)
+ {
+ new->next = daemon->tftp_interfaces;
+ daemon->tftp_interfaces = new;
+ }
+ else
+ {
+ new->next = daemon->dhcp_except;
+ daemon->dhcp_except = new;
+ }
+ arg = comma;
+ } while (arg);
+ break;
+
+ case 'B': /* --bogus-nxdomain */
+ case LOPT_IGNORE_ADDR: /* --ignore-address */
+ {
+ union all_addr addr;
+ int prefix, is6 = 0;
+ struct bogus_addr *baddr;
+
+ unhide_metas(arg);
+
+ if (!arg ||
+ ((comma = split_chr(arg, '/')) && !atoi_check(comma, &prefix)))
+ ret_err(gen_err);
+
+ if (inet_pton(AF_INET6, arg, &addr.addr6) == 1)
+ is6 = 1;
+ else if (inet_pton(AF_INET, arg, &addr.addr4) != 1)
+ ret_err(gen_err);
+
+ if (!comma)
+ {
+ if (is6)
+ prefix = 128;
+ else
+ prefix = 32;
+ }
+
+ if (prefix > 128 || (!is6 && prefix > 32))
+ ret_err(gen_err);
+
+ baddr = opt_malloc(sizeof(struct bogus_addr));
+ if (option == 'B')
+ {
+ baddr->next = daemon->bogus_addr;
+ daemon->bogus_addr = baddr;
+ }
+ else
+ {
+ baddr->next = daemon->ignore_addr;
+ daemon->ignore_addr = baddr;
+ }
+
+ baddr->prefix = prefix;
+ baddr->is6 = is6;
+ baddr->addr = addr;
+ break;
+ }
+
+ case 'a': /* --listen-address */
+ case LOPT_AUTHPEER: /* --auth-peer */
+ do {
+ struct iname *new = opt_malloc(sizeof(struct iname));
+ comma = split(arg);
+ unhide_metas(arg);
+ if (arg && (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0))
+ {
+ new->addr.sa.sa_family = AF_INET;
+ new->addr.in.sin_port = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ new->addr.in.sin_len = sizeof(new->addr.in);
+#endif
+ }
+ 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;
+ new->addr.in6.sin6_port = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ new->addr.in6.sin6_len = sizeof(new->addr.in6);
+#endif
+ }
+ else
+ ret_err_free(gen_err, new);
+
+ new->used = 0;
+ if (option == 'a')
+ {
+ new->next = daemon->if_addrs;
+ daemon->if_addrs = new;
+ }
+ else
+ {
+ new->next = daemon->auth_peers;
+ daemon->auth_peers = new;
+ }
+ arg = comma;
+ } while (arg);
+ break;
+
+ case LOPT_NO_REBIND: /* --rebind-domain-ok */
+ {
+ struct server *new;
+
+ unhide_metas(arg);
+
+ if (*arg == '/')
+ arg++;
+
+ do {
+ comma = split_chr(arg, '/');
+ new = opt_malloc(sizeof(struct serv_local));
+ new->domain = opt_string_alloc(arg);
+ new->domain_len = strlen(arg);
+ new->next = daemon->no_rebind;
+ daemon->no_rebind = new;
+ arg = comma;
+ } while (arg && *arg);
+
+ break;
+ }
+
+ case 'S': /* --server */
+ case LOPT_LOCAL: /* --local */
+ case 'A': /* --address */
+ {
+ char *lastdomain = NULL, *domain = "";
+ u16 flags = 0;
+ char *err;
+ union all_addr addr;
+ union mysockaddr serv_addr, source_addr;
+ char interface[IF_NAMESIZE+1];
+
+ unhide_metas(arg);
+
+ /* split the domain args, if any and skip to the end of them. */
+ if (arg && *arg == '/')
+ {
+ char *last;
+
+ domain = lastdomain = ++arg;
+
+ while ((last = split_chr(arg, '/')))
+ {
+ lastdomain = arg;
+ arg = last;
+ }
+ }
+
+ if (!arg || !*arg)
+ flags = SERV_LITERAL_ADDRESS;
+ else if (option == 'A')
+ {
+ /* # as literal address means return zero address for 4 and 6 */
+ if (strcmp(arg, "#") == 0)
+ flags = SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS;
+ else if (inet_pton(AF_INET, arg, &addr.addr4) > 0)
+ flags = SERV_4ADDR | SERV_LITERAL_ADDRESS;
+ else if (inet_pton(AF_INET6, arg, &addr.addr6) > 0)
+ flags = SERV_6ADDR | SERV_LITERAL_ADDRESS;
+ else
+ ret_err(_("Bad address in --address"));
+ }
+ else
+ {
+ if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags)))
+ ret_err(err);
+ }
+
+ if (servers_only && option == 'S')
+ flags |= SERV_FROM_FILE;
+
+ while (1)
+ {
+ /* server=//1.2.3.4 is special. */
+ if (strlen(domain) == 0 && lastdomain)
+ flags |= SERV_FOR_NODOTS;
+ else
+ flags &= ~SERV_FOR_NODOTS;
+
+ if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr))
+ ret_err(gen_err);
+
+ if (!lastdomain || domain == lastdomain)
+ break;
+
+ domain += strlen(domain) + 1;
+ }
+
+ break;
+ }
+
+ case LOPT_REV_SERV: /* --rev-server */
+ {
+ char *string;
+ int size;
+ u16 flags = 0;
+ char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ union mysockaddr serv_addr, source_addr;
+ char interface[IF_NAMESIZE+1];
+
+ unhide_metas(arg);
+ if (!arg)
+ ret_err(gen_err);
+
+ comma=split(arg);
+
+ if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size))
+ ret_err(gen_err);
+
+ if (inet_pton(AF_INET, arg, &addr4))
+ {
+ if (!domain_rev4(domain, addr4, size))
+ ret_err(_("bad IPv4 prefix"));
+ }
+ else if (inet_pton(AF_INET6, arg, &addr6))
+ {
+ if (!domain_rev6(domain, &addr6, size))
+ ret_err(_("bad IPv6 prefix"));
+ }
+ else
+ ret_err(gen_err);
+
+ if (!comma)
+ flags |= SERV_LITERAL_ADDRESS;
+ else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags)))
+ ret_err(string);
+
+ if (servers_only)
+ flags |= SERV_FROM_FILE;
+
+ if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+ ret_err(gen_err);
+
+ break;
+ }
+
+ case LOPT_IPSET: /* --ipset */
+#ifndef HAVE_IPSET
+ ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
+ break;
+#else
+ {
+ struct ipsets ipsets_head;
+ struct ipsets *ipsets = &ipsets_head;
+ int size;
+ char *end;
+ char **sets, **sets_pos;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ unhide_metas(arg);
+ if (arg && *arg == '/')
+ {
+ arg++;
+ while ((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 || !*arg)
+ domain = "";
+ else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg)))
+ ret_err(gen_err);
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = domain;
+ arg = end;
+ }
+ }
+ else
+ {
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = "";
+ }
+
+ if (!arg || !*arg)
+ ret_err(gen_err);
+
+ for (size = 2, end = arg; *end; ++end)
+ if (*end == ',')
+ ++size;
+
+ sets = sets_pos = opt_malloc(sizeof(char *) * size);
+
+ do {
+ end = split(arg);
+ *sets_pos++ = opt_string_alloc(arg);
+ arg = end;
+ } while (end);
+ *sets_pos = 0;
+ for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next)
+ ipsets->next->sets = sets;
+ ipsets->next = daemon->ipsets;
+ daemon->ipsets = ipsets_head.next;
+
+ break;
+ }
+#endif
+
+ case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
+#ifndef HAVE_CONNTRACK
+ ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
+ break;
+#else
+ {
+ u32 mask = UINT32_MAX;
+
+ if (arg)
+ if (!strtoul_check(arg, &mask) || mask < 1)
+ ret_err(gen_err);
+
+ set_option_bool(OPT_CMARK_ALST_EN);
+ daemon->allowlist_mask = mask;
+ break;
+ }
+#endif
+
+ case LOPT_CMARK_ALST: /* --connmark-allowlist */
+#ifndef HAVE_CONNTRACK
+ ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
+ break;
+#else
+ {
+ struct allowlist *allowlists;
+ char **patterns, **patterns_pos;
+ u32 mark, mask = UINT32_MAX;
+ size_t num_patterns = 0;
+
+ char *c, *m = NULL;
+ char *separator;
+ unhide_metas(arg);
+ if (!arg)
+ ret_err(gen_err);
+ c = arg;
+ if (*c < '0' || *c > '9')
+ ret_err(gen_err);
+ while (*c && *c != ',')
+ {
+ if (*c == '/')
+ {
+ if (m)
+ ret_err(gen_err);
+ *c = '\0';
+ m = ++c;
+ }
+ if (*c < '0' || *c > '9')
+ ret_err(gen_err);
+ c++;
+ }
+ separator = c;
+ if (!*separator)
+ break;
+ while (c && *c)
+ {
+ char *end = strchr(++c, '/');
+ if (end)
+ *end = '\0';
+ if (strcmp(c, "*") && !is_valid_dns_name_pattern(c))
+ ret_err(gen_err);
+ if (end)
+ *end = '/';
+ if (num_patterns >= UINT16_MAX - 1)
+ ret_err(gen_err);
+ num_patterns++;
+ c = end;
+ }
+
+ *separator = '\0';
+ if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
+ ret_err(gen_err);
+ if (m)
+ if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask))
+ ret_err(gen_err);
+ if (num_patterns)
+ *separator = ',';
+ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+ if (allowlists->mark == mark && allowlists->mask == mask)
+ ret_err(gen_err);
+
+ patterns = opt_malloc((num_patterns + 1) * sizeof(char *));
+ if (!patterns)
+ goto fail_cmark_allowlist;
+ patterns_pos = patterns;
+ c = separator;
+ while (c && *c)
+ {
+ char *end = strchr(++c, '/');
+ if (end)
+ *end = '\0';
+ if (!(*patterns_pos++ = opt_string_alloc(c)))
+ goto fail_cmark_allowlist;
+ if (end)
+ *end = '/';
+ c = end;
+ }
+ *patterns_pos++ = NULL;
+
+ allowlists = opt_malloc(sizeof(struct allowlist));
+ if (!allowlists)
+ goto fail_cmark_allowlist;
+ memset(allowlists, 0, sizeof(struct allowlist));
+ allowlists->mark = mark;
+ allowlists->mask = mask;
+ allowlists->patterns = patterns;
+ allowlists->next = daemon->allowlists;
+ daemon->allowlists = allowlists;
+ break;
+
+ fail_cmark_allowlist:
+ if (patterns)
+ {
+ for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
+ {
+ free(*patterns_pos);
+ *patterns_pos = NULL;
+ }
+ free(patterns);
+ patterns = NULL;
+ }
+ if (allowlists)
+ {
+ free(allowlists);
+ allowlists = NULL;
+ }
+ ret_err(gen_err);
+ }
+#endif
+
+ case 'c': /* --cache-size */
+ {
+ int size;
+
+ if (!atoi_check(arg, &size))
+ ret_err(gen_err);
+ else
+ {
+ /* zero is OK, and means no caching. */
+
+ if (size < 0)
+ size = 0;
+
+ /* Note that for very large cache sizes, the malloc()
+ will overflow. For the size of the cache record
+ at the time this was noted, the value of "very large"
+ was 46684428. Limit to an order of magnitude less than
+ that to be safe from changes to the cache record. */
+ if (size > 5000000)
+ size = 5000000;
+
+ daemon->cachesize = size;
+ }
+ break;
+ }
+
+ case 'p': /* --port */
+ if (!atoi_check16(arg, &daemon->port))
+ ret_err(gen_err);
+ break;
+
+ case LOPT_MINPORT: /* --min-port */
+ if (!atoi_check16(arg, &daemon->min_port))
+ ret_err(gen_err);
+ break;
+
+ case LOPT_MAXPORT: /* --max-port */
+ if (!atoi_check16(arg, &daemon->max_port))
+ ret_err(gen_err);
+ break;
+
+ case '0': /* --dns-forward-max */
+ if (!atoi_check(arg, &daemon->ftabsize))
+ ret_err(gen_err);
+ break;
+
+ case 'q': /* --log-queries */
+ set_option_bool(OPT_LOG);
+ if (arg && strcmp(arg, "extra") == 0)
+ set_option_bool(OPT_EXTRALOG);
+ break;
+
+ case LOPT_MAX_LOGS: /* --log-async */
+ daemon->max_logs = LOG_MAX; /* default */
+ if (arg && !atoi_check(arg, &daemon->max_logs))
+ ret_err(gen_err);
+ else if (daemon->max_logs > 100)
+ daemon->max_logs = 100;
+ break;
+
+ case 'P': /* --edns-packet-max */
+ {
+ int i;
+ if (!atoi_check(arg, &i))
+ ret_err(gen_err);
+ daemon->edns_pktsz = (unsigned short)i;
+ break;
+ }
+
+ case 'Q': /* --query-port */
+ if (!atoi_check16(arg, &daemon->query_port))
+ ret_err(gen_err);
+ /* 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 */
+ case LOPT_MINCTTL: /* --min-cache-ttl */
+ case LOPT_MAXCTTL: /* --max-cache-ttl */
+ case LOPT_AUTHTTL: /* --auth-ttl */
+ case LOPT_DHCPTTL: /* --dhcp-ttl */
+ {
+ int ttl;
+ if (!atoi_check(arg, &ttl))
+ ret_err(gen_err);
+ else if (option == LOPT_NEGTTL)
+ daemon->neg_ttl = (unsigned long)ttl;
+ else if (option == LOPT_MAXTTL)
+ daemon->max_ttl = (unsigned long)ttl;
+ else if (option == LOPT_MINCTTL)
+ {
+ if (ttl > TTL_FLOOR_LIMIT)
+ ttl = TTL_FLOOR_LIMIT;
+ daemon->min_cache_ttl = (unsigned long)ttl;
+ }
+ else if (option == LOPT_MAXCTTL)
+ daemon->max_cache_ttl = (unsigned long)ttl;
+ else if (option == LOPT_AUTHTTL)
+ daemon->auth_ttl = (unsigned long)ttl;
+ else if (option == LOPT_DHCPTTL)
+ {
+ daemon->dhcp_ttl = (unsigned long)ttl;
+ daemon->use_dhcp_ttl = 1;
+ }
+ else
+ daemon->local_ttl = (unsigned long)ttl;
+ break;
+ }
+
+#ifdef HAVE_DHCP
+ case 'X': /* --dhcp-lease-max */
+ if (!atoi_check(arg, &daemon->dhcp_max))
+ ret_err(gen_err);
+ break;
+#endif
+
+#ifdef HAVE_TFTP
+ case LOPT_TFTP_MAX: /* --tftp-max */
+ if (!atoi_check(arg, &daemon->tftp_max))
+ ret_err(gen_err);
+ break;
+
+ case LOPT_TFTP_MTU: /* --tftp-mtu */
+ if (!atoi_check(arg, &daemon->tftp_mtu))
+ ret_err(gen_err);
+ break;
+
+ case LOPT_PREFIX: /* --tftp-prefix */
+ comma = split(arg);
+ if (comma)
+ {
+ 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))
+ ret_err(_("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;
+
+ case LOPT_APREF: /* --tftp-unique-root */
+ if (!arg || strcasecmp(arg, "ip") == 0)
+ set_option_bool(OPT_TFTP_APREF_IP);
+ else if (strcasecmp(arg, "mac") == 0)
+ set_option_bool(OPT_TFTP_APREF_MAC);
+ else
+ ret_err(gen_err);
+ break;
+#endif
+
+ case LOPT_BRIDGE: /* --bridge-interface */
+ {
+ struct dhcp_bridge *new;
+
+ if (!(comma = split(arg)) || strlen(arg) > IF_NAMESIZE - 1 )
+ ret_err(_("bad bridge-interface"));
+
+ for (new = daemon->bridges; new; new = new->next)
+ if (strcmp(new->iface, arg) == 0)
+ break;
+
+ if (!new)
+ {
+ new = opt_malloc(sizeof(struct dhcp_bridge));
+ strcpy(new->iface, arg);
+ new->alias = NULL;
+ new->next = daemon->bridges;
+ daemon->bridges = new;
+ }
+
+ do {
+ arg = comma;
+ comma = split(arg);
+ 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 LOPT_SHARED_NET: /* --shared-network */
+ {
+ struct shared_network *new = opt_malloc(sizeof(struct shared_network));
+
+#ifdef HAVE_DHCP6
+ new->shared_addr.s_addr = 0;
+#endif
+ new->if_index = 0;
+
+ if (!(comma = split(arg)))
+ {
+ snerr:
+ free(new);
+ ret_err(_("bad shared-network"));
+ }
+
+ if (inet_pton(AF_INET, comma, &new->shared_addr))
+ {
+ if (!inet_pton(AF_INET, arg, &new->match_addr) &&
+ !(new->if_index = if_nametoindex(arg)))
+ goto snerr;
+ }
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, comma, &new->shared_addr6))
+ {
+ if (!inet_pton(AF_INET6, arg, &new->match_addr6) &&
+ !(new->if_index = if_nametoindex(arg)))
+ goto snerr;
+ }
+#endif
+ else
+ goto snerr;
+
+ new->next = daemon->shared_networks;
+ daemon->shared_networks = new;
+ break;
+ }
+
+ case 'F': /* --dhcp-range */
+ {
+ int k, leasepos = 2;
+ char *cp, *a[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+ struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context));
+
+ memset (new, 0, sizeof(*new));
+
+ while(1)
+ {
+ for (cp = arg; *cp; cp++)
+ if (!(*cp == ' ' || *cp == '.' || *cp == ':' ||
+ (*cp >= 'a' && *cp <= 'f') || (*cp >= 'A' && *cp <= 'F') ||
+ (*cp >='0' && *cp <= '9')))
+ break;
+
+ if (*cp != ',' && (comma = split(arg)))
+ {
+ if (is_tag_prefix(arg))
+ {
+ /* ignore empty tag */
+ if (arg[4])
+ new->filter = dhcp_netid_create(arg+4, new->filter);
+ }
+ else
+ {
+ if (new->netid.net)
+ {
+ dhcp_context_free(new);
+ ret_err(_("only one tag allowed"));
+ }
+ else
+ new->netid.net = opt_string_alloc(set_prefix(arg));
+ }
+ arg = comma;
+ }
+ else
+ {
+ a[0] = arg;
+ break;
+ }
+ }
+
+ for (k = 1; k < 8; k++)
+ if (!(a[k] = split(a[k-1])))
+ break;
+
+ if (k < 2)
+ {
+ dhcp_context_free(new);
+ ret_err(_("bad dhcp-range"));
+ }
+
+ if (inet_pton(AF_INET, a[0], &new->start))
+ {
+ new->next = daemon->dhcp;
+ new->lease_time = DEFLEASE;
+ daemon->dhcp = new;
+ new->end = new->start;
+ if (strcmp(a[1], "static") == 0)
+ new->flags |= CONTEXT_STATIC;
+ else if (strcmp(a[1], "proxy") == 0)
+ new->flags |= CONTEXT_PROXY;
+ else if (!inet_pton(AF_INET, a[1], &new->end))
+ {
+ dhcp_context_free(new);
+ ret_err(_("bad dhcp-range"));
+ }
+
+ 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 (k >= 3 && strchr(a[2], '.') &&
+ (inet_pton(AF_INET, a[2], &new->netmask) > 0))
+ {
+ new->flags |= CONTEXT_NETMASK;
+ leasepos = 3;
+ if (!is_same_net(new->start, new->end, new->netmask))
+ {
+ dhcp_context_free(new);
+ ret_err(_("inconsistent DHCP range"));
+ }
+
+
+ if (k >= 4 && strchr(a[3], '.') &&
+ (inet_pton(AF_INET, a[3], &new->broadcast) > 0))
+ {
+ new->flags |= CONTEXT_BRDCAST;
+ leasepos = 4;
+ }
+ }
+ }
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, a[0], &new->start6))
+ {
+ const char *err = NULL;
+
+ new->flags |= CONTEXT_V6;
+ new->prefix = 64; /* default */
+ new->end6 = new->start6;
+ new->lease_time = DEFLEASE6;
+ new->next = daemon->dhcp6;
+ daemon->dhcp6 = new;
+
+ for (leasepos = 1; leasepos < k; leasepos++)
+ {
+ if (strcmp(a[leasepos], "static") == 0)
+ new->flags |= CONTEXT_STATIC | CONTEXT_DHCP;
+ else if (strcmp(a[leasepos], "ra-only") == 0 || strcmp(a[leasepos], "slaac") == 0 )
+ new->flags |= CONTEXT_RA;
+ else if (strcmp(a[leasepos], "ra-names") == 0)
+ new->flags |= CONTEXT_RA_NAME | CONTEXT_RA;
+ else if (strcmp(a[leasepos], "ra-advrouter") == 0)
+ new->flags |= CONTEXT_RA_ROUTER | CONTEXT_RA;
+ else if (strcmp(a[leasepos], "ra-stateless") == 0)
+ new->flags |= CONTEXT_RA_STATELESS | CONTEXT_DHCP | CONTEXT_RA;
+ else if (strcmp(a[leasepos], "off-link") == 0)
+ new->flags |= CONTEXT_RA_OFF_LINK;
+ else if (leasepos == 1 && inet_pton(AF_INET6, a[leasepos], &new->end6))
+ new->flags |= CONTEXT_DHCP;
+ else if (strstr(a[leasepos], "constructor:") == a[leasepos])
+ {
+ new->template_interface = opt_string_alloc(a[leasepos] + 12);
+ new->flags |= CONTEXT_TEMPLATE;
+ }
+ else
+ break;
+ }
+
+ /* bare integer < 128 is prefix value */
+ if (leasepos < k)
+ {
+ int pref;
+ for (cp = a[leasepos]; *cp; cp++)
+ if (!(*cp >= '0' && *cp <= '9'))
+ break;
+ if (!*cp && (pref = atoi(a[leasepos])) <= 128)
+ {
+ new->prefix = pref;
+ leasepos++;
+ }
+ }
+
+ if (new->prefix > 64)
+ {
+ if (new->flags & CONTEXT_RA)
+ err=(_("prefix length must be exactly 64 for RA subnets"));
+ else if (new->flags & CONTEXT_TEMPLATE)
+ err=(_("prefix length must be exactly 64 for subnet constructors"));
+ }
+ else if (new->prefix < 64)
+ err=(_("prefix length must be at least 64"));
+
+ if (!err && !is_same_net6(&new->start6, &new->end6, new->prefix))
+ err=(_("inconsistent DHCPv6 range"));
+
+ if (err)
+ {
+ dhcp_context_free(new);
+ ret_err(err);
+ }
+
+ /* dhcp-range=:: enables DHCP stateless on any interface */
+ if (IN6_IS_ADDR_UNSPECIFIED(&new->start6) && !(new->flags & CONTEXT_TEMPLATE))
+ new->prefix = 0;
+
+ if (new->flags & CONTEXT_TEMPLATE)
+ {
+ struct in6_addr zero;
+ memset(&zero, 0, sizeof(zero));
+ if (!is_same_net6(&zero, &new->start6, new->prefix))
+ {
+ dhcp_context_free(new);
+ ret_err(_("prefix must be zero with \"constructor:\" argument"));
+ }
+ }
+
+ if (addr6part(&new->start6) > addr6part(&new->end6))
+ {
+ struct in6_addr tmp = new->start6;
+ new->start6 = new->end6;
+ new->end6 = tmp;
+ }
+ }
+#endif
+ else
+ {
+ dhcp_context_free(new);
+ ret_err(_("bad dhcp-range"));
+ }
+
+ if (leasepos < k)
+ {
+ if (leasepos != k-1)
+ {
+ dhcp_context_free(new);
+ ret_err(_("bad dhcp-range"));
+ }
+
+ if (strcmp(a[leasepos], "infinite") == 0)
+ {
+ new->lease_time = 0xffffffff;
+ new->flags |= CONTEXT_SETLEASE;
+ }
+ else if (strcmp(a[leasepos], "deprecated") == 0)
+ new->flags |= CONTEXT_DEPRECATE;
+ else
+ {
+ int fac = 1;
+ if (strlen(a[leasepos]) > 0)
+ {
+ switch (a[leasepos][strlen(a[leasepos]) - 1])
+ {
+ case 'w':
+ case 'W':
+ fac *= 7;
+ /* fall through */
+ 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':
+ a[leasepos][strlen(a[leasepos]) - 1] = 0;
+ }
+
+ for (cp = a[leasepos]; *cp; cp++)
+ if (!(*cp >= '0' && *cp <= '9'))
+ break;
+
+ if (*cp || (leasepos+1 < k))
+ ret_err_free(_("bad dhcp-range"), new);
+
+ new->lease_time = atoi(a[leasepos]) * fac;
+ new->flags |= CONTEXT_SETLEASE;
+ /* 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 */
+ {
+ 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;
+ new->filter = NULL;
+ new->clid = NULL;
+#ifdef HAVE_DHCP6
+ new->addr6 = NULL;
+#endif
+
+ while (arg)
+ {
+ comma = split(arg);
+ if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */
+ {
+ 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)
+ {
+ dhcp_config_free(new);
+ ret_err(_("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_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
+ newlist->next = new->netid;
+ new->netid = newlist;
+ newlist->list = dhcp_netid_create(arg+4, NULL);
+ }
+ else if (strstr(arg, "tag:") == arg)
+ new->filter = dhcp_netid_create(arg+4, new->filter);
+
+#ifdef HAVE_DHCP6
+ else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
+ {
+ char *pref;
+ struct in6_addr in6;
+ struct addrlist *new_addr;
+
+ arg[strlen(arg)-1] = 0;
+ arg++;
+ pref = split_chr(arg, '/');
+
+ if (!inet_pton(AF_INET6, arg, &in6))
+ {
+ dhcp_config_free(new);
+ ret_err(_("bad IPv6 address"));
+ }
+
+ new_addr = opt_malloc(sizeof(struct addrlist));
+ new_addr->next = new->addr6;
+ new_addr->flags = 0;
+ new_addr->addr.addr6 = in6;
+ new->addr6 = new_addr;
+
+ if (pref)
+ {
+ u64 addrpart = addr6part(&in6);
+
+ if (!atoi_check(pref, &new_addr->prefixlen) ||
+ new_addr->prefixlen > 128 ||
+ ((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0)
+ {
+ dhcp_config_free(new);
+ ret_err(_("bad IPv6 prefix"));
+ }
+
+ new_addr->flags |= ADDRLIST_PREFIX;
+ }
+
+ for (i= 0; i < 8; i++)
+ if (in6.s6_addr[i] != 0)
+ break;
+
+ /* set WILDCARD if network part all zeros */
+ if (i == 8)
+ new_addr->flags |= ADDRLIST_WILDCARD;
+
+ new->flags |= CONFIG_ADDR6;
+ }
+#endif
+ else
+ {
+ struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config));
+ if ((newhw->hwaddr_len = parse_hex(arg, newhw->hwaddr, DHCP_CHADDR_MAX,
+ &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1)
+ {
+ free(newhw);
+ dhcp_config_free(new);
+ ret_err(_("bad hex constant"));
+ }
+ else
+ {
+ newhw->next = new->hwaddr;
+ new->hwaddr = newhw;
+ }
+ }
+ }
+ else if (strchr(arg, '.') && (inet_pton(AF_INET, arg, &in) > 0))
+ {
+ struct dhcp_config *configs;
+
+ new->addr = in;
+ new->flags |= CONFIG_ADDR;
+
+ /* 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. */
+ for (configs = daemon->dhcp_conf; configs; configs = configs->next)
+ if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr)
+ {
+ inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN);
+ sprintf(errstr, _("duplicate dhcp-host IP address %s"),
+ daemon->addrbuff);
+ return 0;
+ }
+ }
+ else
+ {
+ char *cp, *lastp = NULL, last = 0;
+ int fac = 1, isdig = 0;
+
+ if (strlen(arg) > 1)
+ {
+ lastp = arg + strlen(arg) - 1;
+ last = *lastp;
+ switch (last)
+ {
+ case 'w':
+ case 'W':
+ fac *= 7;
+ /* fall through */
+ 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 = arg; *cp; cp++)
+ if (isdigit((unsigned char)*cp))
+ isdig = 1;
+ else if (*cp != ' ')
+ break;
+
+ if (*cp)
+ {
+ if (lastp)
+ *lastp = last;
+ if (strcmp(arg, "infinite") == 0)
+ {
+ new->lease_time = 0xffffffff;
+ new->flags |= CONFIG_TIME;
+ }
+ else if (strcmp(arg, "ignore") == 0)
+ new->flags |= CONFIG_DISABLE;
+ else
+ {
+ if (!(new->hostname = canonicalise_opt(arg)) ||
+ !legal_hostname(new->hostname))
+ {
+ dhcp_config_free(new);
+ ret_err(_("bad DHCP host name"));
+ }
+
+ new->flags |= CONFIG_NAME;
+ new->domain = strip_hostname(new->hostname);
+ }
+ }
+ else if (isdig)
+ {
+ new->lease_time = atoi(arg) * 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;
+ }
+ }
+
+ arg = comma;
+ }
+
+ 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 = dhcp_netid_create(arg+4, NULL);
+
+ 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;
+ dhcp_netid_free(newtag);
+ break;
+ }
+ }
+
+ arg = comma;
+ }
+
+ if (!new->set)
+ {
+ dhcp_netid_free(new->tag);
+ dhcp_netid_list_free(new->set);
+ ret_err_free(_("bad tag-if"), new);
+ }
+
+ break;
+ }
+
+
+ case 'O': /* --dhcp-option */
+ case LOPT_FORCE: /* --dhcp-option-force */
+ case LOPT_OPTS:
+ case LOPT_MATCH: /* --dhcp-match */
+ return parse_dhcp_opt(errstr, arg,
+ option == LOPT_FORCE ? DHOPT_FORCE :
+ (option == LOPT_MATCH ? DHOPT_MATCH :
+ (option == LOPT_OPTS ? DHOPT_BANK : 0)));
+
+ case LOPT_NAME_MATCH: /* --dhcp-name-match */
+ {
+ struct dhcp_match_name *new = opt_malloc(sizeof(struct dhcp_match_name));
+ struct dhcp_netid *id = opt_malloc(sizeof(struct dhcp_netid));
+ ssize_t len;
+
+ if (!(comma = split(arg)) || (len = strlen(comma)) == 0)
+ ret_err(gen_err);
+
+ new->wildcard = 0;
+ new->netid = id;
+ id->net = opt_string_alloc(set_prefix(arg));
+
+ if (comma[len-1] == '*')
+ {
+ comma[len-1] = 0;
+ new->wildcard = 1;
+ }
+ new->name = opt_string_alloc(comma);
+
+ new->next = daemon->dhcp_name_match;
+ daemon->dhcp_name_match = new;
+
+ break;
+ }
+
+ case 'M': /* --dhcp-boot */
+ {
+ struct dhcp_netid *id = dhcp_tags(&arg);
+
+ if (!arg)
+ {
+ ret_err(gen_err);
+ }
+ else
+ {
+ char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL;
+ struct in_addr dhcp_next_server;
+ struct dhcp_boot *new;
+ 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 (!(inet_pton(AF_INET, comma, &dhcp_next_server) > 0))
+ {
+ /*
+ * The user may have specified the tftp hostname here.
+ * save it so that it can be resolved/looked up during
+ * actual dhcp_reply().
+ */
+
+ tftp_sname = opt_string_alloc(comma);
+ dhcp_next_server.s_addr = 0;
+ }
+ }
+ }
+
+ new = opt_malloc(sizeof(struct dhcp_boot));
+ new->file = dhcp_file;
+ new->sname = dhcp_sname;
+ new->tftp_sname = tftp_sname;
+ new->next_server = dhcp_next_server;
+ new->netid = id;
+ new->next = daemon->boot_config;
+ daemon->boot_config = new;
+ }
+
+ break;
+ }
+
+ case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */
+ {
+ struct dhcp_netid *id = dhcp_tags(&arg);
+
+ if (!arg)
+ {
+ ret_err(gen_err);
+ }
+ else
+ {
+ struct delay_config *new;
+ int delay;
+ if (!atoi_check(arg, &delay))
+ ret_err(gen_err);
+
+ new = opt_malloc(sizeof(struct delay_config));
+ new->delay = delay;
+ new->netid = id;
+ new->next = daemon->delay_conf;
+ daemon->delay_conf = new;
+ }
+
+ break;
+ }
+
+ case LOPT_PXE_PROMT: /* --pxe-prompt */
+ {
+ struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt));
+ int timeout;
+
+ new->netid = NULL;
+ new->opt = 10; /* PXE_MENU_PROMPT */
+ new->netid = dhcp_tags(&arg);
+
+ if (!arg)
+ {
+ dhcp_opt_free(new);
+ ret_err(gen_err);
+ }
+ 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 = NULL;
+ new->flags = DHOPT_VENDOR | DHOPT_VENDOR_PXE;
+
+ 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", "x86-64_EFI", "Xscale_EFI", "BC_EFI",
+ "ARM32_EFI", "ARM64_EFI", NULL };
+ static int boottype = 32768;
+
+ new->netid = NULL;
+ new->sname = NULL;
+ new->server.s_addr = 0;
+ new->netid = dhcp_tags(&arg);
+
+ 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)
+ {
+ if (!inet_pton(AF_INET, comma, &new->server))
+ {
+ new->server.s_addr = 0;
+ new->sname = opt_string_alloc(comma);
+ }
+
+ }
+ }
+
+ /* 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;
+
+ }
+ }
+
+ ret_err(gen_err);
+ }
+
+ case '4': /* --dhcp-mac */
+ {
+ if (!(comma = split(arg)))
+ ret_err(gen_err);
+ 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)
+ {
+ free(new->netid.net);
+ ret_err_free(gen_err, new);
+ }
+ 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 */
+ {
+ unsigned char *p;
+ int dig = 0;
+ struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor));
+
+ if (!(comma = split(arg)))
+ ret_err_free(gen_err, new);
+
+ 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. */
+
+ arg = comma;
+ if ((comma = split(arg)))
+ {
+ if (option != 'U' || strstr(arg, "enterprise:") != arg)
+ {
+ free(new->netid.net);
+ ret_err_free(gen_err, new);
+ }
+ else
+ new->enterprise = atoi(arg+11);
+ }
+ else
+ comma = arg;
+
+ 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)))
+ ret_err(_("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) {
+ comma = split(arg);
+ list = dhcp_netid_create(is_tag_prefix(arg) ? arg+4 :arg, list);
+ 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 (!(inet_pton(AF_INET, arg, &new->addr) > 0))
+ ret_err_free(_("bad dhcp-proxy address"), new);
+ new->next = daemon->override_relays;
+ daemon->override_relays = new;
+ arg = comma;
+ }
+ break;
+
+ case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */
+ {
+ while (arg) {
+ struct dhcp_pxe_vendor *new = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+ comma = split(arg);
+ new->data = opt_string_alloc(arg);
+ new->next = daemon->dhcp_pxe_vendors;
+ daemon->dhcp_pxe_vendors = new;
+ arg = comma;
+ }
+ }
+ break;
+
+ case LOPT_RELAY: /* --dhcp-relay */
+ {
+ struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
+ comma = split(arg);
+ new->interface = opt_string_alloc(split(comma));
+ new->iface_index = 0;
+ if (comma && inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server))
+ {
+ new->next = daemon->relay4;
+ daemon->relay4 = new;
+ }
+#ifdef HAVE_DHCP6
+ else if (comma && inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server))
+ {
+ new->next = daemon->relay6;
+ daemon->relay6 = new;
+ }
+#endif
+ else
+ {
+ free(new->interface);
+ ret_err_free(_("Bad dhcp-relay"), new);
+ }
+
+ break;
+ }
+
+#endif
+
+#ifdef HAVE_DHCP6
+ case LOPT_RA_PARAM: /* --ra-param */
+ if ((comma = split(arg)))
+ {
+ struct ra_interface *new = opt_malloc(sizeof(struct ra_interface));
+ new->lifetime = -1;
+ new->prio = 0;
+ new->mtu = 0;
+ new->mtu_name = NULL;
+ new->name = opt_string_alloc(arg);
+ if (strcasestr(comma, "mtu:") == comma)
+ {
+ arg = comma + 4;
+ if (!(comma = split(comma)))
+ goto err;
+ if (!strcasecmp(arg, "off"))
+ new->mtu = -1;
+ else if (!atoi_check(arg, &new->mtu))
+ new->mtu_name = opt_string_alloc(arg);
+ else if (new->mtu < 1280)
+ goto err;
+ }
+ if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma)
+ {
+ if (*comma == 'l' || *comma == 'L')
+ new->prio = 0x18;
+ else
+ new->prio = 0x08;
+ comma = split(comma);
+ }
+ arg = split(comma);
+ if (!atoi_check(comma, &new->interval) ||
+ (arg && !atoi_check(arg, &new->lifetime)))
+ {
+err:
+ free(new->name);
+ ret_err_free(_("bad RA-params"), new);
+ }
+
+ new->next = daemon->ra_interfaces;
+ daemon->ra_interfaces = new;
+ }
+ break;
+
+ case LOPT_DUID: /* --dhcp-duid */
+ if (!(comma = split(arg)) || !atoi_check(arg, (int *)&daemon->duid_enterprise))
+ ret_err(_("bad DUID"));
+ else
+ {
+ daemon->duid_config_len = parse_hex(comma,(unsigned char *)comma, strlen(comma), NULL, NULL);
+ daemon->duid_config = opt_malloc(daemon->duid_config_len);
+ memcpy(daemon->duid_config, comma, daemon->duid_config_len);
+ }
+ 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) ||
+ (!(inet_pton(AF_INET, a[0], &new->in) > 0)) ||
+ (!(inet_pton(AF_INET, a[1], &new->out) > 0)) ||
+ (k == 3 && !inet_pton(AF_INET, a[2], &new->mask)))
+ ret_err(_("missing address in alias"));
+
+ if (dash &&
+ (!(inet_pton(AF_INET, dash, &new->end) > 0) ||
+ !is_same_net(new->in, new->end, new->mask) ||
+ ntohl(new->in.s_addr) > ntohl(new->end.s_addr)))
+ ret_err_free(_("invalid alias range"), new);
+
+ break;
+ }
+
+ case LOPT_INTNAME: /* --interface-name */
+ case LOPT_DYNHOST: /* --dynamic-host */
+ {
+ struct interface_name *new, **up;
+ char *domain = arg;
+
+ arg = split(arg);
+
+ new = opt_malloc(sizeof(struct interface_name));
+ memset(new, 0, sizeof(struct interface_name));
+ new->flags = IN4 | IN6;
+
+ /* 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;
+
+ while ((comma = split(arg)))
+ {
+ if (inet_pton(AF_INET, arg, &new->proto4))
+ new->flags |= INP4;
+ else if (inet_pton(AF_INET6, arg, &new->proto6))
+ new->flags |= INP6;
+ else
+ break;
+
+ arg = comma;
+ }
+
+ if ((comma = split_chr(arg, '/')))
+ {
+ if (strcmp(comma, "4") == 0)
+ new->flags &= ~IN6;
+ else if (strcmp(comma, "6") == 0)
+ new->flags &= ~IN4;
+ else
+ ret_err_free(gen_err, new);
+ }
+
+ new->intr = opt_string_alloc(arg);
+
+ if (option == LOPT_DYNHOST)
+ {
+ if (!(new->flags & (INP4 | INP6)))
+ ret_err(_("missing address in dynamic host"));
+
+ if (!(new->flags & IN4) || !(new->flags & IN6))
+ arg = NULL; /* provoke error below */
+
+ new->flags &= ~(IN4 | IN6);
+ }
+ else
+ {
+ if (new->flags & (INP4 | INP6))
+ arg = NULL; /* provoke error below */
+ }
+
+ if (!domain || !arg || !(new->name = canonicalise_opt(domain)))
+ ret_err(option == LOPT_DYNHOST ?
+ _("bad dynamic host") : _("bad interface name"));
+
+ break;
+ }
+
+ case LOPT_CNAME: /* --cname */
+ {
+ struct cname *new;
+ char *alias, *target, *last, *pen;
+ int ttl = -1;
+
+ for (last = pen = NULL, comma = arg; comma; comma = split(comma))
+ {
+ pen = last;
+ last = comma;
+ }
+
+ if (!pen)
+ ret_err(_("bad CNAME"));
+
+ if (pen != arg && atoi_check(last, &ttl))
+ last = pen;
+
+ target = canonicalise_opt(last);
+
+ while (arg != last)
+ {
+ int arglen = strlen(arg);
+ alias = canonicalise_opt(arg);
+
+ if (!alias || !target)
+ {
+ free(target);
+ free(alias);
+ ret_err(_("bad CNAME"));
+ }
+
+ for (new = daemon->cnames; new; new = new->next)
+ if (hostname_isequal(new->alias, alias))
+ {
+ free(target);
+ free(alias);
+ ret_err(_("duplicate CNAME"));
+ }
+ new = opt_malloc(sizeof(struct cname));
+ new->next = daemon->cnames;
+ daemon->cnames = new;
+ new->alias = alias;
+ new->target = target;
+ new->ttl = ttl;
+
+ for (arg += arglen+1; *arg && isspace(*arg); arg++);
+ }
+
+ break;
+ }
+
+ 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))))
+ {
+ free(dom);
+ free(target);
+ ret_err(_("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=NULL, *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]))))
+ {
+ free(name);
+ free(replace);
+ ret_err(_("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 LOPT_RR: /* dns-rr */
+ {
+ struct txt_record *new;
+ size_t len = 0;
+ char *data;
+ int class;
+
+ comma = split(arg);
+ data = split(comma);
+
+ new = opt_malloc(sizeof(struct txt_record));
+ new->name = NULL;
+
+ if (!atoi_check(comma, &class) ||
+ !(new->name = canonicalise_opt(arg)) ||
+ (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U))
+ {
+ free(new->name);
+ ret_err_free(_("bad RR record"), new);
+ }
+
+ new->len = 0;
+ new->class = class;
+ new->next = daemon->rr;
+ daemon->rr = new;
+
+ if (data)
+ {
+ new->txt = opt_malloc(len);
+ new->len = len;
+ memcpy(new->txt, data, len);
+ }
+
+ break;
+ }
+
+ case LOPT_CAA: /* --caa-record */
+ {
+ struct txt_record *new;
+ char *tag, *value;
+ int flags;
+
+ comma = split(arg);
+ tag = split(comma);
+ value = split(tag);
+
+ new = opt_malloc(sizeof(struct txt_record));
+ new->next = daemon->rr;
+ daemon->rr = new;
+
+ if (!atoi_check(comma, &flags) || !tag || !value || !(new->name = canonicalise_opt(arg)))
+ ret_err(_("bad CAA record"));
+
+ unhide_metas(tag);
+ unhide_metas(value);
+
+ new->len = strlen(tag) + strlen(value) + 2;
+ new->txt = opt_malloc(new->len);
+ new->txt[0] = flags;
+ new->txt[1] = strlen(tag);
+ memcpy(&new->txt[2], tag, strlen(tag));
+ memcpy(&new->txt[2 + strlen(tag)], value, strlen(value));
+ new->class = T_CAA;
+
+ 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->class = C_IN;
+ new->stat = 0;
+
+ if (!(new->name = canonicalise_opt(arg)))
+ ret_err_free(_("bad TXT record"), new);
+
+ new->next = daemon->txt;
+ daemon->txt = new;
+ 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)))
+ ret_err(_("bad SRV record"));
+
+ if (comma)
+ {
+ arg = comma;
+ comma = split(arg);
+ if (!(target = canonicalise_opt(arg)))
+ ret_err_free(_("bad SRV target"), name);
+
+ if (comma)
+ {
+ arg = comma;
+ comma = split(arg);
+ if (!atoi_check16(arg, &port))
+ {
+ free(name);
+ ret_err_free(_("invalid port number"), target);
+ }
+
+ if (comma)
+ {
+ arg = comma;
+ comma = split(arg);
+ if (!atoi_check16(arg, &priority))
+ {
+ free(name);
+ ret_err_free(_("invalid priority"), target);
+ }
+ if (comma && !atoi_check16(comma, &weight))
+ {
+ free(name);
+ ret_err_free(_("invalid weight"), target);
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+
+ case LOPT_HOST_REC: /* --host-record */
+ {
+ struct host_record *new;
+
+ if (!arg || !(comma = split(arg)))
+ ret_err(_("Bad host-record"));
+
+ new = opt_malloc(sizeof(struct host_record));
+ memset(new, 0, sizeof(struct host_record));
+ new->ttl = -1;
+ new->flags = 0;
+
+ while (arg)
+ {
+ union all_addr addr;
+ char *dig;
+
+ for (dig = arg; *dig != 0; dig++)
+ if (*dig < '0' || *dig > '9')
+ break;
+ if (*dig == 0)
+ new->ttl = atoi(arg);
+ else if (inet_pton(AF_INET, arg, &addr.addr4))
+ {
+ new->addr = addr.addr4;
+ new->flags |= HR_4;
+ }
+ else if (inet_pton(AF_INET6, arg, &addr.addr6))
+ {
+ new->addr6 = addr.addr6;
+ new->flags |= HR_6;
+ }
+ else
+ {
+ char *canon = canonicalise_opt(arg);
+ struct name_list *nl;
+ if (!canon)
+ {
+ struct name_list *tmp = new->names, *next;
+ for (tmp = new->names; tmp; tmp = next)
+ {
+ next = tmp->next;
+ free(tmp);
+ }
+ ret_err_free(_("Bad name in host-record"), new);
+ }
+
+ nl = opt_malloc(sizeof(struct name_list));
+ nl->name = canon;
+ /* keep order, so that PTR record goes to first name */
+ nl->next = NULL;
+ if (!new->names)
+ new->names = nl;
+ else
+ {
+ struct name_list *tmp;
+ for (tmp = new->names; tmp->next; tmp = tmp->next);
+ tmp->next = nl;
+ }
+ }
+
+ arg = comma;
+ comma = split(arg);
+ }
+
+ /* Keep list order */
+ if (!daemon->host_records_tail)
+ daemon->host_records = new;
+ else
+ daemon->host_records_tail->next = new;
+ new->next = NULL;
+ daemon->host_records_tail = new;
+ break;
+ }
+
+#ifdef HAVE_DNSSEC
+ case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
+ daemon->timestamp_file = opt_string_alloc(arg);
+ break;
+
+ case LOPT_DNSSEC_CHECK: /* --dnssec-check-unsigned */
+ if (arg)
+ {
+ if (strcmp(arg, "no") == 0)
+ set_option_bool(OPT_DNSSEC_IGN_NS);
+ else
+ ret_err(_("bad value for dnssec-check-unsigned"));
+ }
+ break;
+
+ case LOPT_TRUST_ANCHOR: /* --trust-anchor */
+ {
+ struct ds_config *new = opt_malloc(sizeof(struct ds_config));
+ char *cp, *cp1, *keyhex, *digest, *algo = NULL;
+ int len;
+
+ new->class = C_IN;
+ new->name = NULL;
+
+ if ((comma = split(arg)) && (algo = split(comma)))
+ {
+ int class = 0;
+ if (strcmp(comma, "IN") == 0)
+ class = C_IN;
+ else if (strcmp(comma, "CH") == 0)
+ class = C_CHAOS;
+ else if (strcmp(comma, "HS") == 0)
+ class = C_HESIOD;
+
+ if (class != 0)
+ {
+ new->class = class;
+ comma = algo;
+ algo = split(comma);
+ }
+ }
+
+ if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
+ !atoi_check16(comma, &new->keytag) ||
+ !atoi_check8(algo, &new->algo) ||
+ !atoi_check8(digest, &new->digest_type) ||
+ !(new->name = canonicalise_opt(arg)))
+ ret_err_free(_("bad trust anchor"), new);
+
+ /* Upper bound on length */
+ len = (2*strlen(keyhex))+1;
+ new->digest = opt_malloc(len);
+ unhide_metas(keyhex);
+ /* 4034: "Whitespace is allowed within digits" */
+ for (cp = keyhex; *cp; )
+ if (isspace(*cp))
+ for (cp1 = cp; *cp1; cp1++)
+ *cp1 = *(cp1+1);
+ else
+ cp++;
+ if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
+ {
+ free(new->name);
+ ret_err_free(_("bad HEX in trust anchor"), new);
+ }
+
+ new->next = daemon->ds;
+ daemon->ds = new;
+
+ break;
+ }
+#endif
+
+ default:
+ ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
+
+ }
+
+ return 1;
+}
+
+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;
+ volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
+ char *errmess, *p, *arg, *start;
+ size_t len;
+
+ /* Memory allocation failure longjmps here if mem_recover == 1 */
+ if (option != 0 || hard_opt == LOPT_REV_SERV)
+ {
+ if (setjmp(mem_jmp))
+ continue;
+ mem_recover = 1;
+ }
+
+ arg = NULL;
+ 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 (option != 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 (option == 0)
+ {
+ 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");
+ else if (hard_opt == LOPT_REV_SERV && option != 'S' && option != LOPT_REV_SERV)
+ errmess = _("illegal option");
+ }
+
+ oops:
+ if (errmess)
+ strcpy(daemon->namebuff, errmess);
+
+ if (errmess || !one_opt(option, arg, daemon->namebuff, _("error"), 0, hard_opt == LOPT_REV_SERV))
+ {
+ sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
+ if (hard_opt != 0)
+ my_syslog(LOG_ERR, "%s", daemon->namebuff);
+ else
+ die("%s", daemon->namebuff, EC_BADCONF);
+ }
+ }
+
+ mem_recover = 0;
+ fclose(f);
+}
+
+#if defined(HAVE_DHCP) && defined(HAVE_INOTIFY)
+int option_read_dynfile(char *file, int flags)
+{
+ my_syslog(MS_DHCP | LOG_INFO, _("read %s"), file);
+
+ if (flags & AH_DHCP_HST)
+ return one_file(file, LOPT_BANK);
+ else if (flags & AH_DHCP_OPT)
+ return one_file(file, LOPT_OPTS);
+
+ return 0;
+}
+#endif
+
+static int 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 1;
+ 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 1;
+
+ 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 1; /* No conffile, all done. */
+ else
+ {
+ char *str = _("cannot read %s: %s");
+ if (hard_opt != 0)
+ {
+ my_syslog(LOG_ERR, str, file, strerror(errno));
+ return 0;
+ }
+ else
+ die(str, file, EC_FILE);
+ }
+ }
+ }
+
+ read_file(file, f, hard_opt);
+ return 1;
+}
+
+static int file_filter(const struct dirent *ent)
+{
+ size_t lenfile = strlen(ent->d_name);
+
+ /* 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] == '.')
+ return 0;
+
+ return 1;
+}
+/* expand any name which is a directory */
+struct hostsfile *expand_filelist(struct hostsfile *list)
+{
+ unsigned int i;
+ int entcnt, n;
+ struct hostsfile *ah, *last, *next, **up;
+ struct dirent **namelist;
+
+ /* find largest used index */
+ for (i = SRC_AH, ah = list; ah; ah = ah->next)
+ {
+ last = ah;
+
+ 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))
+ {
+ struct dirent *ent;
+
+ /* don't read this as a file */
+ ah->flags |= AH_INACTIVE;
+
+ entcnt = scandir(ah->fname, &namelist, file_filter, alphasort);
+ if (entcnt < 0)
+ my_syslog(LOG_ERR, _("cannot access directory %s: %s"),
+ ah->fname, strerror(errno));
+ else
+ {
+ for (n = 0; n < entcnt; n++)
+ {
+ ent = namelist[n];
+ size_t lendir = strlen(ah->fname);
+ size_t lenfile = strlen(ent->d_name);
+ struct hostsfile *ah1;
+ char *path;
+
+ /* see if we have an existing record.
+ dir is ah->fname
+ file is ent->d_name
+ path to match is ah1->fname */
+
+ for (up = &list, ah1 = list; ah1; ah1 = next)
+ {
+ next = 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;
+ /* If found, remove from list to re-insert at the end.
+ Unless it's already at the end. */
+ if (last != ah1)
+ *up = next;
+ break;
+ }
+
+ up = &ah1->next;
+ }
+
+ /* 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;
+ }
+
+ /* Edge case, may be the last in the list anyway */
+ if (last != ah1)
+ last->next = ah1;
+ ah1->next = NULL;
+ last = 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;
+
+ }
+ }
+ free(namelist);
+ }
+ }
+
+ return list;
+}
+
+void read_servers_file(void)
+{
+ FILE *f;
+
+ if (!(f = fopen(daemon->servers_file, "r")))
+ {
+ my_syslog(LOG_ERR, _("cannot read %s: %s"), daemon->servers_file, strerror(errno));
+ return;
+ }
+
+ mark_servers(SERV_FROM_FILE);
+ read_file(daemon->servers_file, f, LOPT_REV_SERV);
+ cleanup_servers();
+ check_servers(0);
+}
+
+
+#ifdef HAVE_DHCP
+static void clear_dynamic_conf(void)
+{
+ 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)
+ {
+ *up = cp;
+ dhcp_config_free(configs);
+ }
+ else
+ up = &configs->next;
+ }
+}
+
+static void clear_dynamic_opt(void)
+{
+ struct dhcp_opt *opts, *cp, **up;
+
+ for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp)
+ {
+ cp = opts->next;
+
+ if (opts->flags & DHOPT_BANK)
+ {
+ *up = cp;
+ dhcp_opt_free(opts);
+ }
+ else
+ up = &opts->next;
+ }
+}
+
+void reread_dhcp(void)
+{
+ struct hostsfile *hf;
+
+ /* Do these even if there is no daemon->dhcp_hosts_file or
+ daemon->dhcp_opts_file since entries may have been created by the
+ inotify dynamic file reading system. */
+
+ clear_dynamic_conf();
+ clear_dynamic_opt();
+
+ if (daemon->dhcp_hosts_file)
+ {
+ daemon->dhcp_hosts_file = expand_filelist(daemon->dhcp_hosts_file);
+ for (hf = daemon->dhcp_hosts_file; hf; hf = hf->next)
+ if (!(hf->flags & AH_INACTIVE))
+ {
+ if (one_file(hf->fname, LOPT_BANK))
+ my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname);
+ }
+ }
+
+ if (daemon->dhcp_opts_file)
+ {
+ 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))
+ {
+ if (one_file(hf->fname, LOPT_OPTS))
+ my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname);
+ }
+ }
+
+# ifdef HAVE_INOTIFY
+ /* Setup notify and read pre-existing files. */
+ set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0);
+# endif
+}
+#endif
+
+void read_opts(int argc, char **argv, char *compile_opts)
+{
+ size_t argbuf_size = MAXDNAME;
+ char *argbuf = opt_malloc(argbuf_size);
+ char *buff = opt_malloc(MAXDNAME);
+ int option, testmode = 0;
+ char *arg, *conffile = NULL;
+
+ opterr = 0;
+
+ daemon = opt_malloc(sizeof(struct daemon));
+ memset(daemon, 0, sizeof(struct daemon));
+ daemon->namebuff = buff;
+ daemon->addrbuff = safe_malloc(ADDRSTRLEN);
+
+ /* 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;
+ daemon->auth_ttl = AUTH_TTL;
+ daemon->soa_refresh = SOA_REFRESH;
+ daemon->soa_retry = SOA_RETRY;
+ daemon->soa_expiry = SOA_EXPIRY;
+
+#ifndef NO_ID
+ add_txt("version.bind", "dnsmasq-" VERSION, 0 );
+ add_txt("authors.bind", "Simon Kelley", 0);
+ add_txt("copyright.bind", COPYRIGHT, 0);
+ add_txt("cachesize.bind", NULL, TXT_STAT_CACHESIZE);
+ add_txt("insertions.bind", NULL, TXT_STAT_INSERTS);
+ add_txt("evictions.bind", NULL, TXT_STAT_EVICTIONS);
+ add_txt("misses.bind", NULL, TXT_STAT_MISSES);
+ add_txt("hits.bind", NULL, TXT_STAT_HITS);
+#ifdef HAVE_AUTH
+ add_txt("auth.bind", NULL, TXT_STAT_AUTH);
+#endif
+ add_txt("servers.bind", NULL, TXT_STAT_SERVERS);
+#endif
+
+ 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)
+ {
+ if (strlen(optarg) >= argbuf_size)
+ {
+ free(argbuf);
+ argbuf_size = strlen(optarg) + 1;
+ argbuf = opt_malloc(argbuf_size);
+ }
+ safe_strncpy(argbuf, optarg, argbuf_size);
+ arg = argbuf;
+ }
+ else
+ arg = NULL;
+
+ /* command-line only stuff */
+ if (option == LOPT_TEST)
+ testmode = 1;
+ else if (option == 'w')
+ {
+#ifdef HAVE_DHCP
+ if (argc == 3 && strcmp(argv[2], "dhcp") == 0)
+ display_opts();
+#ifdef HAVE_DHCP6
+ else if (argc == 3 && strcmp(argv[2], "dhcp6") == 0)
+ display_opts6();
+#endif
+ else
+#endif
+ do_usage();
+
+ 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')
+ {
+ if (!conffile)
+ conffile = opt_string_alloc(arg);
+ else
+ {
+ char *extra = opt_string_alloc(arg);
+ one_file(extra, 0);
+ free(extra);
+ }
+ }
+ else
+ {
+#ifdef HAVE_GETOPT_LONG
+ if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1, 0))
+#else
+ if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1, 0))
+#endif
+ die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF);
+ }
+ }
+
+ free(argbuf);
+
+ if (conffile)
+ {
+ one_file(conffile, 0);
+ free(conffile);
+ }
+ else
+ one_file(CONFFILE, '7');
+
+ /* 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);
+ else if (tmp->source_addr.sa.sa_family == AF_INET6)
+ tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
+ }
+ }
+
+ if (daemon->host_records)
+ {
+ struct host_record *hr;
+
+ for (hr = daemon->host_records; hr; hr = hr->next)
+ if (hr->ttl == -1)
+ hr->ttl = daemon->local_ttl;
+ }
+
+ if (daemon->cnames)
+ {
+ struct cname *cn, *cn2, *cn3;
+
+#define NOLOOP 1
+#define TESTLOOP 2
+
+ /* Fill in TTL for CNAMES now we have local_ttl.
+ Also prepare to do loop detection. */
+ for (cn = daemon->cnames; cn; cn = cn->next)
+ {
+ if (cn->ttl == -1)
+ cn->ttl = daemon->local_ttl;
+ cn->flag = 0;
+ cn->targetp = NULL;
+ for (cn2 = daemon->cnames; cn2; cn2 = cn2->next)
+ if (hostname_isequal(cn->target, cn2->alias))
+ {
+ cn->targetp = cn2;
+ break;
+ }
+ }
+
+ /* Find any CNAME loops.*/
+ for (cn = daemon->cnames; cn; cn = cn->next)
+ {
+ for (cn2 = cn->targetp; cn2; cn2 = cn2->targetp)
+ {
+ if (cn2->flag == NOLOOP)
+ break;
+
+ if (cn2->flag == TESTLOOP)
+ die(_("CNAME loop involving %s"), cn->alias, EC_BADCONF);
+
+ cn2->flag = TESTLOOP;
+ }
+
+ for (cn3 = cn->targetp; cn3 != cn2; cn3 = cn3->targetp)
+ cn3->flag = NOLOOP;
+ }
+ }
+
+ if (daemon->if_addrs)
+ {
+ struct iname *tmp;
+ 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);
+ else if (tmp->addr.sa.sa_family == AF_INET6)
+ tmp->addr.in6.sin6_port = htons(daemon->port);
+ }
+
+ /* create default, if not specified */
+ if (daemon->authserver && !daemon->hostmaster)
+ {
+ strcpy(buff, "hostmaster.");
+ strcat(buff, daemon->authserver);
+ daemon->hostmaster = opt_string_alloc(buff);
+ }
+
+ if (!daemon->dhcp_pxe_vendors)
+ {
+ daemon->dhcp_pxe_vendors = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+ daemon->dhcp_pxe_vendors->data = opt_string_alloc(DHCP_PXE_DEF_VENDOR);
+ daemon->dhcp_pxe_vendors->next = NULL;
+ }
+
+ /* 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 there's access-control config, then ignore --local-service, it's intended
+ as a system default to keep otherwise unconfigured installations safe. */
+ if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver)
+ reset_option_bool(OPT_LOCAL_SERVICE);
+
+ if (testmode)
+ {
+ fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK"));
+ exit(0);
+ }
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c b/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c
new file mode 100755
index 0000000..da6f73c
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c
@@ -0,0 +1,118 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP6
+
+static size_t outpacket_counter;
+
+void end_opt6(int container)
+{
+ void *p = daemon->outpacket.iov_base + container + 2;
+ u16 len = outpacket_counter - container - 4 ;
+
+ PUTSHORT(len, p);
+}
+
+void reset_counter(void)
+{
+ /* Clear out buffer when starting from beginning */
+ if (daemon->outpacket.iov_base)
+ memset(daemon->outpacket.iov_base, 0, daemon->outpacket.iov_len);
+
+ save_counter(0);
+}
+
+int save_counter(int newval)
+{
+ int ret = outpacket_counter;
+
+ if (newval != -1)
+ outpacket_counter = newval;
+
+ return ret;
+}
+
+void *expand(size_t headroom)
+{
+ void *ret;
+
+ if (expand_buf(&daemon->outpacket, outpacket_counter + headroom))
+ {
+ ret = daemon->outpacket.iov_base + outpacket_counter;
+ outpacket_counter += headroom;
+ return ret;
+ }
+
+ return NULL;
+}
+
+int new_opt6(int opt)
+{
+ int ret = outpacket_counter;
+ void *p;
+
+ if ((p = expand(4)))
+ {
+ PUTSHORT(opt, p);
+ PUTSHORT(0, p);
+ }
+
+ return ret;
+}
+
+void *put_opt6(void *data, size_t len)
+{
+ void *p;
+
+ if ((p = expand(len)) && data)
+ memcpy(p, data, len);
+
+ return p;
+}
+
+void put_opt6_long(unsigned int val)
+{
+ void *p;
+
+ if ((p = expand(4)))
+ PUTLONG(val, p);
+}
+
+void put_opt6_short(unsigned int val)
+{
+ void *p;
+
+ if ((p = expand(2)))
+ PUTSHORT(val, p);
+}
+
+void put_opt6_char(unsigned int val)
+{
+ unsigned char *p;
+
+ if ((p = expand(1)))
+ *p = val;
+}
+
+void put_opt6_string(char *s)
+{
+ put_opt6(s, strlen(s));
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c b/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c
new file mode 100755
index 0000000..03e23b9
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c
@@ -0,0 +1,386 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_CONNTRACK
+
+#define LOG(...) \
+ do { \
+ my_syslog(LOG_DEBUG, __VA_ARGS__); \
+ } while (0)
+
+#define ASSERT(condition) \
+ do { \
+ if (!(condition)) \
+ my_syslog(LOG_ERR, _("[pattern.c:%d] Assertion failure: %s"), __LINE__, #condition); \
+ } while (0)
+
+/**
+ * Determines whether a given string value matches against a glob pattern
+ * which may contain zero-or-more-character wildcards denoted by '*'.
+ *
+ * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox,
+ * See https://research.swtch.com/glob
+ *
+ * @param value A string value.
+ * @param num_value_bytes The number of bytes of the string value.
+ * @param pattern A glob pattern.
+ * @param num_pattern_bytes The number of bytes of the glob pattern.
+ *
+ * @return 1 If the provided value matches against the glob pattern.
+ * @return 0 Otherwise.
+ */
+static int is_string_matching_glob_pattern(
+ const char *value,
+ size_t num_value_bytes,
+ const char *pattern,
+ size_t num_pattern_bytes)
+{
+ ASSERT(value);
+ ASSERT(pattern);
+
+ size_t value_index = 0;
+ size_t next_value_index = 0;
+ size_t pattern_index = 0;
+ size_t next_pattern_index = 0;
+ while (value_index < num_value_bytes || pattern_index < num_pattern_bytes)
+ {
+ if (pattern_index < num_pattern_bytes)
+ {
+ char pattern_character = pattern[pattern_index];
+ if ('a' <= pattern_character && pattern_character <= 'z')
+ pattern_character -= 'a' - 'A';
+ if (pattern_character == '*')
+ {
+ /* zero-or-more-character wildcard */
+ /* Try to match at value_index, otherwise restart at value_index + 1 next. */
+ next_pattern_index = pattern_index;
+ pattern_index++;
+ if (value_index < num_value_bytes)
+ next_value_index = value_index + 1;
+ else
+ next_value_index = 0;
+ continue;
+ }
+ else
+ {
+ /* ordinary character */
+ if (value_index < num_value_bytes)
+ {
+ char value_character = value[value_index];
+ if ('a' <= value_character && value_character <= 'z')
+ value_character -= 'a' - 'A';
+ if (value_character == pattern_character)
+ {
+ pattern_index++;
+ value_index++;
+ continue;
+ }
+ }
+ }
+ }
+ if (next_value_index)
+ {
+ pattern_index = next_pattern_index;
+ value_index = next_value_index;
+ continue;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * Determines whether a given string value represents a valid DNS name.
+ *
+ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
+ * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
+ * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
+ *
+ * - A valid name must be fully qualified, i.e., consist of at least two labels.
+ * The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
+ *
+ * - Examples:
+ * Valid: "example.com"
+ * Invalid: "ipcamera", "ipcamera.local", "8.8.8.8"
+ *
+ * @param value A string value.
+ *
+ * @return 1 If the provided string value is a valid DNS name.
+ * @return 0 Otherwise.
+ */
+int is_valid_dns_name(const char *value)
+{
+ ASSERT(value);
+
+ size_t num_bytes = 0;
+ size_t num_labels = 0;
+ const char *label = NULL;
+ int is_label_numeric = 1;
+ for (const char *c = value;; c++)
+ {
+ if (*c &&
+ *c != '-' && *c != '.' &&
+ (*c < '0' || *c > '9') &&
+ (*c < 'A' || *c > 'Z') &&
+ (*c < 'a' || *c > 'z'))
+ {
+ LOG(_("Invalid DNS name: Invalid character %c."), *c);
+ return 0;
+ }
+ if (*c)
+ num_bytes++;
+ if (!label)
+ {
+ if (!*c || *c == '.')
+ {
+ LOG(_("Invalid DNS name: Empty label."));
+ return 0;
+ }
+ if (*c == '-')
+ {
+ LOG(_("Invalid DNS name: Label starts with hyphen."));
+ return 0;
+ }
+ label = c;
+ }
+ if (*c && *c != '.')
+ {
+ if (*c < '0' || *c > '9')
+ is_label_numeric = 0;
+ }
+ else
+ {
+ if (c[-1] == '-')
+ {
+ LOG(_("Invalid DNS name: Label ends with hyphen."));
+ return 0;
+ }
+ size_t num_label_bytes = (size_t) (c - label);
+ if (num_label_bytes > 63)
+ {
+ LOG(_("Invalid DNS name: Label is too long (%zu)."), num_label_bytes);
+ return 0;
+ }
+ num_labels++;
+ if (!*c)
+ {
+ if (num_labels < 2)
+ {
+ LOG(_("Invalid DNS name: Not enough labels (%zu)."), num_labels);
+ return 0;
+ }
+ if (is_label_numeric)
+ {
+ LOG(_("Invalid DNS name: Final label is fully numeric."));
+ return 0;
+ }
+ if (num_label_bytes == 5 &&
+ (label[0] == 'l' || label[0] == 'L') &&
+ (label[1] == 'o' || label[1] == 'O') &&
+ (label[2] == 'c' || label[2] == 'C') &&
+ (label[3] == 'a' || label[3] == 'A') &&
+ (label[4] == 'l' || label[4] == 'L'))
+ {
+ LOG(_("Invalid DNS name: \"local\" pseudo-TLD."));
+ return 0;
+ }
+ if (num_bytes < 1 || num_bytes > 253)
+ {
+ LOG(_("DNS name has invalid length (%zu)."), num_bytes);
+ return 0;
+ }
+ return 1;
+ }
+ label = NULL;
+ is_label_numeric = 1;
+ }
+ }
+}
+
+/**
+ * Determines whether a given string value represents a valid DNS name pattern.
+ *
+ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
+ * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
+ * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
+ *
+ * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to
+ * twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot
+ * (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com").
+ *
+ * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels.
+ * The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
+ * A pattern must end with at least two literal (non-wildcard) labels.
+ *
+ * - Examples:
+ * Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com"
+ * Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8"
+ *
+ * @param value A string value.
+ *
+ * @return 1 If the provided string value is a valid DNS name pattern.
+ * @return 0 Otherwise.
+ */
+int is_valid_dns_name_pattern(const char *value)
+{
+ ASSERT(value);
+
+ size_t num_bytes = 0;
+ size_t num_labels = 0;
+ const char *label = NULL;
+ int is_label_numeric = 1;
+ size_t num_wildcards = 0;
+ int previous_label_has_wildcard = 1;
+ for (const char *c = value;; c++)
+ {
+ if (*c &&
+ *c != '*' && /* Wildcard. */
+ *c != '-' && *c != '.' &&
+ (*c < '0' || *c > '9') &&
+ (*c < 'A' || *c > 'Z') &&
+ (*c < 'a' || *c > 'z'))
+ {
+ LOG(_("Invalid DNS name pattern: Invalid character %c."), *c);
+ return 0;
+ }
+ if (*c && *c != '*')
+ num_bytes++;
+ if (!label)
+ {
+ if (!*c || *c == '.')
+ {
+ LOG(_("Invalid DNS name pattern: Empty label."));
+ return 0;
+ }
+ if (*c == '-')
+ {
+ LOG(_("Invalid DNS name pattern: Label starts with hyphen."));
+ return 0;
+ }
+ label = c;
+ }
+ if (*c && *c != '.')
+ {
+ if (*c < '0' || *c > '9')
+ is_label_numeric = 0;
+ if (*c == '*')
+ {
+ if (num_wildcards >= 2)
+ {
+ LOG(_("Invalid DNS name pattern: Wildcard character used more than twice per label."));
+ return 0;
+ }
+ num_wildcards++;
+ }
+ }
+ else
+ {
+ if (c[-1] == '-')
+ {
+ LOG(_("Invalid DNS name pattern: Label ends with hyphen."));
+ return 0;
+ }
+ size_t num_label_bytes = (size_t) (c - label) - num_wildcards;
+ if (num_label_bytes > 63)
+ {
+ LOG(_("Invalid DNS name pattern: Label is too long (%zu)."), num_label_bytes);
+ return 0;
+ }
+ num_labels++;
+ if (!*c)
+ {
+ if (num_labels < 2)
+ {
+ LOG(_("Invalid DNS name pattern: Not enough labels (%zu)."), num_labels);
+ return 0;
+ }
+ if (num_wildcards != 0 || previous_label_has_wildcard)
+ {
+ LOG(_("Invalid DNS name pattern: Wildcard within final two labels."));
+ return 0;
+ }
+ if (is_label_numeric)
+ {
+ LOG(_("Invalid DNS name pattern: Final label is fully numeric."));
+ return 0;
+ }
+ if (num_label_bytes == 5 &&
+ (label[0] == 'l' || label[0] == 'L') &&
+ (label[1] == 'o' || label[1] == 'O') &&
+ (label[2] == 'c' || label[2] == 'C') &&
+ (label[3] == 'a' || label[3] == 'A') &&
+ (label[4] == 'l' || label[4] == 'L'))
+ {
+ LOG(_("Invalid DNS name pattern: \"local\" pseudo-TLD."));
+ return 0;
+ }
+ if (num_bytes < 1 || num_bytes > 253)
+ {
+ LOG(_("DNS name pattern has invalid length after removing wildcards (%zu)."), num_bytes);
+ return 0;
+ }
+ return 1;
+ }
+ label = NULL;
+ is_label_numeric = 1;
+ previous_label_has_wildcard = num_wildcards != 0;
+ num_wildcards = 0;
+ }
+ }
+}
+
+/**
+ * Determines whether a given DNS name matches against a DNS name pattern.
+ *
+ * @param name A valid DNS name.
+ * @param pattern A valid DNS name pattern.
+ *
+ * @return 1 If the provided DNS name matches against the DNS name pattern.
+ * @return 0 Otherwise.
+ */
+int is_dns_name_matching_pattern(const char *name, const char *pattern)
+{
+ ASSERT(name);
+ ASSERT(is_valid_dns_name(name));
+ ASSERT(pattern);
+ ASSERT(is_valid_dns_name_pattern(pattern));
+
+ const char *n = name;
+ const char *p = pattern;
+
+ do {
+ const char *name_label = n;
+ while (*n && *n != '.')
+ n++;
+ const char *pattern_label = p;
+ while (*p && *p != '.')
+ p++;
+ if (!is_string_matching_glob_pattern(
+ name_label, (size_t) (n - name_label),
+ pattern_label, (size_t) (p - pattern_label)))
+ break;
+ if (*n)
+ n++;
+ if (*p)
+ p++;
+ } while (*n && *p);
+
+ return !*n && !*p;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c b/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c
new file mode 100755
index 0000000..f414690
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c
@@ -0,0 +1,125 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+/* Wrapper for poll(). Allocates and extends array of struct pollfds,
+ keeps them in fd order so that we can set and test conditions on
+ fd using a simple but efficient binary chop. */
+
+/* poll_reset()
+ poll_listen(fd, event)
+ .
+ .
+ poll_listen(fd, event);
+
+ hits = do_poll(timeout);
+
+ if (poll_check(fd, event)
+ .
+ .
+
+ if (poll_check(fd, event)
+ .
+ .
+
+ event is OR of POLLIN, POLLOUT, POLLERR, etc
+*/
+
+static struct pollfd *pollfds = NULL;
+static nfds_t nfds, arrsize = 0;
+
+/* Binary search. Returns either the pollfd with fd, or
+ if the fd doesn't match, or return equals nfds, the entry
+ to the left of which a new record should be inserted. */
+static nfds_t fd_search(int fd)
+{
+ nfds_t left, right, mid;
+
+ if ((right = nfds) == 0)
+ return 0;
+
+ left = 0;
+
+ while (1)
+ {
+ if (right == left + 1)
+ return (pollfds[left].fd >= fd) ? left : right;
+
+ mid = (left + right)/2;
+
+ if (pollfds[mid].fd > fd)
+ right = mid;
+ else
+ left = mid;
+ }
+}
+
+void poll_reset(void)
+{
+ nfds = 0;
+}
+
+int do_poll(int timeout)
+{
+ return poll(pollfds, nfds, timeout);
+}
+
+int poll_check(int fd, short event)
+{
+ nfds_t i = fd_search(fd);
+
+ if (i < nfds && pollfds[i].fd == fd)
+ return pollfds[i].revents & event;
+
+ return 0;
+}
+
+void poll_listen(int fd, short event)
+{
+ nfds_t i = fd_search(fd);
+
+ if (i < nfds && pollfds[i].fd == fd)
+ pollfds[i].events |= event;
+ else
+ {
+ if (arrsize != nfds)
+ memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+ else
+ {
+ /* Array too small, extend. */
+ struct pollfd *new;
+
+ arrsize = (arrsize == 0) ? 64 : arrsize * 2;
+
+ if (!(new = whine_malloc(arrsize * sizeof(struct pollfd))))
+ return;
+
+ if (pollfds)
+ {
+ memcpy(new, pollfds, i * sizeof(struct pollfd));
+ memcpy(&new[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+ free(pollfds);
+ }
+
+ pollfds = new;
+ }
+
+ pollfds[i].fd = fd;
+ pollfds[i].events = event;
+ nfds++;
+ }
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h
new file mode 100755
index 0000000..8314e8a
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h
@@ -0,0 +1,55 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 ALL_NODES "FF02::1"
+#define ALL_ROUTERS "FF02::2"
+
+struct ping_packet {
+ u8 type, code;
+ u16 checksum;
+ u16 identifier;
+ u16 sequence_no;
+};
+
+struct ra_packet {
+ u8 type, code;
+ u16 checksum;
+ u8 hop_limit, flags;
+ u16 lifetime;
+ u32 reachable_time;
+ u32 retrans_time;
+};
+
+struct neigh_packet {
+ u8 type, code;
+ u16 checksum;
+ u16 reserved;
+ struct in6_addr target;
+};
+
+struct prefix_opt {
+ u8 type, len, prefix_len, flags;
+ u32 valid_lifetime, preferred_lifetime, reserved;
+ struct in6_addr prefix;
+};
+
+#define ICMP6_OPT_SOURCE_MAC 1
+#define ICMP6_OPT_PREFIX 3
+#define ICMP6_OPT_MTU 5
+#define ICMP6_OPT_ADV_INTERVAL 7
+#define ICMP6_OPT_RT_INFO 24
+#define ICMP6_OPT_RDNSS 25
+#define ICMP6_OPT_DNSSL 31
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c b/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c
new file mode 100755
index 0000000..3255904
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c
@@ -0,0 +1,1008 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+
+/* NB. This code may be called during a DHCPv4 or transaction which is in ping-wait
+ It therefore cannot use any DHCP buffer resources except outpacket, which is
+ not used by DHCPv4 code. This code may also be called when DHCP 4 or 6 isn't
+ active, so we ensure that outpacket is allocated here too */
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DHCP6
+
+#include <netinet/icmp6.h>
+
+struct ra_param {
+ time_t now;
+ int ind, managed, other, first, adv_router;
+ char *if_name;
+ struct dhcp_netid *tags;
+ struct in6_addr link_local, link_global, ula;
+ unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval, prio;
+ struct dhcp_context *found_context;
+};
+
+struct search_param {
+ time_t now; int iface;
+ char name[IF_NAMESIZE+1];
+};
+
+struct alias_param {
+ int iface;
+ struct dhcp_bridge *bridge;
+ int num_alias_ifs;
+ int max_alias_ifs;
+ int *alias_ifs;
+};
+
+static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest);
+static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest,
+ int send_iface);
+static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+static int add_prefixes(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ unsigned int preferred, unsigned int valid, void *vparam);
+static int iface_search(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ int prefered, int valid, void *vparam);
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
+static unsigned int calc_lifetime(struct ra_interface *ra);
+static unsigned int calc_interval(struct ra_interface *ra);
+static unsigned int calc_prio(struct ra_interface *ra);
+static struct ra_interface *find_iface_param(char *iface);
+
+static int hop_limit;
+
+void ra_init(time_t now)
+{
+ struct icmp6_filter filter;
+ int fd;
+#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
+ int class = IPTOS_CLASS_CS6;
+#endif
+ int val = 255; /* radvd uses this value */
+ socklen_t len = sizeof(int);
+ struct dhcp_context *context;
+
+ /* ensure this is around even if we're not doing DHCPv6 */
+ expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet));
+
+ /* See if we're guessing SLAAC addresses, if so we need to receive ping replies */
+ for (context = daemon->dhcp6; context; context = context->next)
+ if ((context->flags & CONTEXT_RA_NAME))
+ break;
+
+ /* Need ICMP6 socket for transmission for DHCPv6 even when not doing RA. */
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ if (daemon->doing_ra)
+ {
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+ if (context)
+ ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
+ }
+
+ if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 ||
+ getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, &len) ||
+#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
+ setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
+#endif
+ !fix_fd(fd) ||
+ !set_ipv6pktinfo(fd) ||
+ setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
+ setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
+ setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
+ die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
+
+ daemon->icmp6fd = fd;
+
+ if (daemon->doing_ra)
+ ra_start_unsolicited(now, NULL);
+}
+
+void ra_start_unsolicited(time_t now, struct dhcp_context *context)
+{
+ /* init timers so that we do ra's for some/all soon. some ra_times will end up zeroed
+ if it's not appropriate to advertise those contexts.
+ This gets re-called on a netlink route-change to re-do the advertisement
+ and pick up new interfaces */
+
+ if (context)
+ context->ra_short_period_start = context->ra_time = now;
+ else
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (!(context->flags & CONTEXT_TEMPLATE))
+ {
+ context->ra_time = now + (rand16()/13000); /* range 0 - 5 */
+ /* re-do frequently for a minute or so, in case the first gets lost. */
+ context->ra_short_period_start = now;
+ }
+}
+
+void icmp6_packet(time_t now)
+{
+ char interface[IF_NAMESIZE+1];
+ ssize_t sz;
+ int if_index = 0;
+ struct cmsghdr *cmptr;
+ struct msghdr msg;
+ union {
+ struct cmsghdr align; /* this ensures alignment */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ } control_u;
+ struct sockaddr_in6 from;
+ unsigned char *packet;
+ struct iname *tmp;
+
+ /* Note: use outpacket for input buffer */
+ msg.msg_control = control_u.control6;
+ msg.msg_controllen = sizeof(control_u);
+ msg.msg_flags = 0;
+ msg.msg_name = &from;
+ msg.msg_namelen = sizeof(from);
+ msg.msg_iov = &daemon->outpacket;
+ msg.msg_iovlen = 1;
+
+ if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8)
+ return;
+
+ packet = (unsigned char *)daemon->outpacket.iov_base;
+
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
+ {
+ union {
+ unsigned char *c;
+ struct in6_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+
+ if_index = p.p->ipi6_ifindex;
+ }
+
+ if (!indextoname(daemon->icmp6fd, if_index, interface))
+ return;
+
+ if (!iface_check(AF_LOCAL, NULL, interface, NULL))
+ return;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, interface))
+ return;
+
+ if (packet[1] != 0)
+ return;
+
+ if (packet[0] == ICMP6_ECHO_REPLY)
+ lease_ping_reply(&from.sin6_addr, packet, interface);
+ else if (packet[0] == ND_ROUTER_SOLICIT)
+ {
+ char *mac = "";
+ struct dhcp_bridge *bridge, *alias;
+
+ /* look for link-layer address option for logging */
+ if (sz >= 16 && packet[8] == ICMP6_OPT_SOURCE_MAC && (packet[9] * 8) + 8 <= sz)
+ {
+ if ((packet[9] * 8 - 2) * 3 - 1 >= MAXDNAME) {
+ return;
+ }
+ print_mac(daemon->namebuff, &packet[10], (packet[9] * 8) - 2);
+ mac = daemon->namebuff;
+ }
+
+ if (!option_bool(OPT_QUIET_RA))
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-SOLICIT(%s) %s", interface, mac);
+
+ /* If the incoming interface is an alias of some other one (as
+ specified by the --bridge-interface option), send an RA using
+ the context of the aliased interface. */
+ for (bridge = daemon->bridges; bridge; bridge = bridge->next)
+ {
+ int bridge_index = if_nametoindex(bridge->iface);
+ if (bridge_index)
+ {
+ for (alias = bridge->alias; alias; alias = alias->next)
+ if (wildcard_matchn(alias->iface, interface, IF_NAMESIZE))
+ {
+ /* Send an RA on if_index with information from
+ bridge_index. */
+ send_ra_alias(now, bridge_index, bridge->iface, NULL, if_index);
+ break;
+ }
+ if (alias)
+ break;
+ }
+ }
+
+ /* If the incoming interface wasn't an alias, send an RA using
+ the context of the incoming interface. */
+ if (!bridge)
+ /* source address may not be valid in solicit request. */
+ send_ra(now, if_index, interface, !IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr) ? &from.sin6_addr : NULL);
+ }
+}
+
+static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, int send_iface)
+{
+ struct ra_packet *ra;
+ struct ra_param parm;
+ struct sockaddr_in6 addr;
+ struct dhcp_context *context, *tmp, **up;
+ struct dhcp_netid iface_id;
+ struct dhcp_opt *opt_cfg;
+ struct ra_interface *ra_param = find_iface_param(iface_name);
+ int done_dns = 0, old_prefix = 0, mtu = 0;
+ unsigned int min_pref_time;
+#ifdef HAVE_LINUX_NETWORK
+ FILE *f;
+#endif
+
+ parm.ind = iface;
+ parm.managed = 0;
+ parm.other = 0;
+ parm.found_context = NULL;
+ parm.adv_router = 0;
+ parm.if_name = iface_name;
+ parm.first = 1;
+ parm.now = now;
+ parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0;
+ parm.adv_interval = calc_interval(ra_param);
+ parm.prio = calc_prio(ra_param);
+
+ reset_counter();
+
+ if (!(ra = expand(sizeof(struct ra_packet))))
+ return;
+
+ ra->type = ND_ROUTER_ADVERT;
+ ra->code = 0;
+ ra->hop_limit = hop_limit;
+ ra->flags = parm.prio;
+ ra->lifetime = htons(calc_lifetime(ra_param));
+ ra->reachable_time = 0;
+ ra->retrans_time = 0;
+
+ /* set tag with name == interface */
+ iface_id.net = iface_name;
+ iface_id.next = NULL;
+ parm.tags = &iface_id;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ {
+ context->flags &= ~CONTEXT_RA_DONE;
+ context->netid.next = &context->netid;
+ }
+
+ /* If no link-local address then we can't advertise since source address of
+ advertisement must be link local address: RFC 4861 para 6.1.2. */
+ if (!iface_enumerate(AF_INET6, &parm, add_prefixes) ||
+ parm.link_pref_time == 0)
+ return;
+
+ /* Find smallest preferred time within address classes,
+ to use as lifetime for options. This is a rather arbitrary choice. */
+ min_pref_time = 0xffffffff;
+ if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time)
+ min_pref_time = parm.glob_pref_time;
+
+ if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time)
+ min_pref_time = parm.ula_pref_time;
+
+ if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time)
+ min_pref_time = parm.link_pref_time;
+
+ /* Look for constructed contexts associated with addresses which have gone,
+ and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */
+ for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
+ {
+ tmp = context->next;
+
+ if (context->if_index == iface && (context->flags & CONTEXT_OLD))
+ {
+ unsigned int old = difftime(now, context->address_lost_time);
+
+ if (old > context->saved_valid)
+ {
+ /* We've advertised this enough, time to go */
+
+ /* If this context held the timeout, and there's another context in use
+ transfer the timeout there. */
+ if (context->ra_time != 0 && parm.found_context && parm.found_context->ra_time == 0)
+ new_timeout(parm.found_context, iface_name, now);
+
+ *up = context->next;
+ free(context);
+ }
+ else
+ {
+ struct prefix_opt *opt;
+ struct in6_addr local = context->start6;
+ int do_slaac = 0;
+
+ old_prefix = 1;
+
+ /* zero net part of address */
+ setaddr6part(&local, addr6part(&local) & ~((context->prefix == 64) ? (u64)-1LL : (1LLU << (128 - context->prefix)) - 1LLU));
+
+
+ if (context->flags & CONTEXT_RA)
+ {
+ do_slaac = 1;
+ if (context->flags & CONTEXT_DHCP)
+ {
+ parm.other = 1;
+ if (!(context->flags & CONTEXT_RA_STATELESS))
+ parm.managed = 1;
+ }
+ }
+ else
+ {
+ /* don't do RA for non-ra-only unless --enable-ra is set */
+ if (option_bool(OPT_RA))
+ {
+ parm.managed = 1;
+ parm.other = 1;
+ }
+ }
+
+ if ((opt = expand(sizeof(struct prefix_opt))))
+ {
+ opt->type = ICMP6_OPT_PREFIX;
+ opt->len = 4;
+ opt->prefix_len = context->prefix;
+ /* autonomous only if we're not doing dhcp, set
+ "on-link" unless "off-link" was specified */
+ opt->flags = (do_slaac ? 0x40 : 0) |
+ ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80);
+ opt->valid_lifetime = htonl(context->saved_valid - old);
+ opt->preferred_lifetime = htonl(0);
+ opt->reserved = 0;
+ opt->prefix = local;
+
+ inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN);
+ if (!option_bool(OPT_QUIET_RA))
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s old prefix", iface_name, daemon->addrbuff);
+ }
+
+ up = &context->next;
+ }
+ }
+ else
+ up = &context->next;
+ }
+
+ /* If we're advertising only old prefixes, set router lifetime to zero. */
+ if (old_prefix && !parm.found_context)
+ ra->lifetime = htons(0);
+
+ /* No prefixes to advertise. */
+ if (!old_prefix && !parm.found_context)
+ return;
+
+ /* If we're sending router address instead of prefix in at least on prefix,
+ include the advertisement interval option. */
+ if (parm.adv_router)
+ {
+ put_opt6_char(ICMP6_OPT_ADV_INTERVAL);
+ put_opt6_char(1);
+ put_opt6_short(0);
+ /* interval value is in milliseconds */
+ put_opt6_long(1000 * calc_interval(find_iface_param(iface_name)));
+ }
+
+ /* Set the MTU from ra_param if any, an MTU of 0 mean automatic for linux, */
+ /* an MTU of -1 prevents the option from being sent. */
+ if (ra_param)
+ mtu = ra_param->mtu;
+#ifdef HAVE_LINUX_NETWORK
+ /* Note that IPv6 MTU is not necessarily the same as the IPv4 MTU
+ available from SIOCGIFMTU */
+ if (mtu == 0)
+ {
+ char *mtu_name = ra_param ? ra_param->mtu_name : NULL;
+ sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name);
+ if ((f = fopen(daemon->namebuff, "r")))
+ {
+ if (fgets(daemon->namebuff, MAXDNAME, f))
+ mtu = atoi(daemon->namebuff);
+ fclose(f);
+ }
+ }
+#endif
+ if (mtu > 0)
+ {
+ put_opt6_char(ICMP6_OPT_MTU);
+ put_opt6_char(1);
+ put_opt6_short(0);
+ put_opt6_long(mtu);
+ }
+
+ iface_enumerate(AF_LOCAL, &send_iface, add_lla);
+
+ /* RDNSS, RFC 6106, use relevant DHCP6 options */
+ (void)option_filter(parm.tags, NULL, daemon->dhcp_opts6);
+
+ for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+ {
+ int i;
+
+ /* netids match and not encapsulated? */
+ if (!(opt_cfg->flags & DHOPT_TAGOK))
+ continue;
+
+ if (opt_cfg->opt == OPTION6_DNS_SERVER)
+ {
+ struct in6_addr *a;
+ int len;
+
+ done_dns = 1;
+
+ if (opt_cfg->len == 0)
+ continue;
+
+ /* reduce len for any addresses we can't substitute */
+ for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0;
+ i < opt_cfg->len; i += IN6ADDRSZ, a++)
+ if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) ||
+ (IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) ||
+ (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0))
+ len -= IN6ADDRSZ;
+
+ if (len != 0)
+ {
+ put_opt6_char(ICMP6_OPT_RDNSS);
+ put_opt6_char((len/8) + 1);
+ put_opt6_short(0);
+ put_opt6_long(min_pref_time);
+
+ for (a = (struct in6_addr *)opt_cfg->val, i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++)
+ if (IN6_IS_ADDR_UNSPECIFIED(a))
+ {
+ if (parm.glob_pref_time != 0)
+ put_opt6(&parm.link_global, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_ULA_ZERO(a))
+ {
+ if (parm.ula_pref_time != 0)
+ put_opt6(&parm.ula, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+ {
+ if (parm.link_pref_time != 0)
+ put_opt6(&parm.link_local, IN6ADDRSZ);
+ }
+ else
+ put_opt6(a, IN6ADDRSZ);
+ }
+ }
+
+ if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0)
+ {
+ int len = ((opt_cfg->len+7)/8);
+
+ put_opt6_char(ICMP6_OPT_DNSSL);
+ put_opt6_char(len + 1);
+ put_opt6_short(0);
+ put_opt6_long(min_pref_time);
+ put_opt6(opt_cfg->val, opt_cfg->len);
+
+ /* pad */
+ for (i = opt_cfg->len; i < len * 8; i++)
+ put_opt6_char(0);
+ }
+ }
+
+ if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0)
+ {
+ /* default == us, as long as we are supplying DNS service. */
+ put_opt6_char(ICMP6_OPT_RDNSS);
+ put_opt6_char(3);
+ put_opt6_short(0);
+ put_opt6_long(min_pref_time);
+ put_opt6(&parm.link_local, IN6ADDRSZ);
+ }
+
+ /* set managed bits unless we're providing only RA on this link */
+ if (parm.managed)
+ ra->flags |= 0x80; /* M flag, managed, */
+ if (parm.other)
+ ra->flags |= 0x40; /* O flag, other */
+
+ /* decide where we're sending */
+ memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(IPPROTO_ICMPV6);
+ if (dest)
+ {
+ addr.sin6_addr = *dest;
+ if (IN6_IS_ADDR_LINKLOCAL(dest) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(dest))
+ addr.sin6_scope_id = iface;
+ }
+ else
+ {
+ inet_pton(AF_INET6, ALL_NODES, &addr.sin6_addr);
+ setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &send_iface, sizeof(send_iface));
+ }
+
+ while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base,
+ save_counter(-1), 0, (struct sockaddr *)&addr,
+ sizeof(addr))));
+
+}
+
+static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest)
+{
+ /* Send an RA on the same interface that the RA content is based
+ on. */
+ send_ra_alias(now, iface, iface_name, dest, iface);
+}
+
+static int add_prefixes(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ unsigned int preferred, unsigned int valid, void *vparam)
+{
+ struct ra_param *param = vparam;
+
+ (void)scope; /* warning */
+
+ if (if_index == param->ind)
+ {
+ if (IN6_IS_ADDR_LINKLOCAL(local))
+ {
+ /* Can there be more than one LL address?
+ Select the one with the longest preferred time
+ if there is. */
+ if (preferred > param->link_pref_time)
+ {
+ param->link_pref_time = preferred;
+ param->link_local = *local;
+ }
+ }
+ else if (!IN6_IS_ADDR_LOOPBACK(local) &&
+ !IN6_IS_ADDR_MULTICAST(local))
+ {
+ int real_prefix = 0;
+ int do_slaac = 0;
+ int deprecate = 0;
+ int constructed = 0;
+ int adv_router = 0;
+ int off_link = 0;
+ unsigned int time = 0xffffffff;
+ struct dhcp_context *context;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ prefix <= context->prefix &&
+ is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix))
+ {
+ context->saved_valid = valid;
+
+ if (context->flags & CONTEXT_RA)
+ {
+ do_slaac = 1;
+ if (context->flags & CONTEXT_DHCP)
+ {
+ param->other = 1;
+ if (!(context->flags & CONTEXT_RA_STATELESS))
+ param->managed = 1;
+ }
+ }
+ else
+ {
+ /* don't do RA for non-ra-only unless --enable-ra is set */
+ if (!option_bool(OPT_RA))
+ continue;
+ param->managed = 1;
+ param->other = 1;
+ }
+
+ /* Configured to advertise router address, not prefix. See RFC 3775 7.2
+ In this case we do all addresses associated with a context,
+ hence the real_prefix setting here. */
+ if (context->flags & CONTEXT_RA_ROUTER)
+ {
+ adv_router = 1;
+ param->adv_router = 1;
+ real_prefix = context->prefix;
+ }
+
+ /* find floor time, don't reduce below 3 * RA interval.
+ If the lease time has been left as default, don't
+ use that as a floor. */
+ if ((context->flags & CONTEXT_SETLEASE) &&
+ time > context->lease_time)
+ {
+ time = context->lease_time;
+ if (time < ((unsigned int)(3 * param->adv_interval)))
+ time = 3 * param->adv_interval;
+ }
+
+ if (context->flags & CONTEXT_DEPRECATE)
+ deprecate = 1;
+
+ if (context->flags & CONTEXT_CONSTRUCTED)
+ constructed = 1;
+
+
+ /* collect dhcp-range tags */
+ if (context->netid.next == &context->netid && context->netid.net)
+ {
+ context->netid.next = param->tags;
+ param->tags = &context->netid;
+ }
+
+ /* subsequent prefixes on the same interface
+ and subsequent instances of this prefix don't need timers.
+ Be careful not to find the same prefix twice with different
+ addresses unless we're advertising the actual addresses. */
+ if (!(context->flags & CONTEXT_RA_DONE))
+ {
+ if (!param->first)
+ context->ra_time = 0;
+ context->flags |= CONTEXT_RA_DONE;
+ real_prefix = context->prefix;
+ off_link = (context->flags & CONTEXT_RA_OFF_LINK);
+ }
+
+ param->first = 0;
+ /* found_context is the _last_ one we found, so if there's
+ more than one, it's not the first. */
+ param->found_context = context;
+ }
+
+ /* configured time is ceiling */
+ if (!constructed || valid > time)
+ valid = time;
+
+ if (flags & IFACE_DEPRECATED)
+ preferred = 0;
+
+ if (deprecate)
+ time = 0;
+
+ /* configured time is ceiling */
+ if (!constructed || preferred > time)
+ preferred = time;
+
+ if (IN6_IS_ADDR_ULA(local))
+ {
+ if (preferred > param->ula_pref_time)
+ {
+ param->ula_pref_time = preferred;
+ param->ula = *local;
+ }
+ }
+ else
+ {
+ if (preferred > param->glob_pref_time)
+ {
+ param->glob_pref_time = preferred;
+ param->link_global = *local;
+ }
+ }
+
+ if (real_prefix != 0)
+ {
+ struct prefix_opt *opt;
+
+ if ((opt = expand(sizeof(struct prefix_opt))))
+ {
+ /* zero net part of address */
+ if (!adv_router)
+ setaddr6part(local, addr6part(local) & ~((real_prefix == 64) ? (u64)-1LL : (1LLU << (128 - real_prefix)) - 1LLU));
+
+ opt->type = ICMP6_OPT_PREFIX;
+ opt->len = 4;
+ opt->prefix_len = real_prefix;
+ /* autonomous only if we're not doing dhcp, set
+ "on-link" unless "off-link" was specified */
+ opt->flags = (off_link ? 0 : 0x80);
+ if (do_slaac)
+ opt->flags |= 0x40;
+ if (adv_router)
+ opt->flags |= 0x20;
+ opt->valid_lifetime = htonl(valid);
+ opt->preferred_lifetime = htonl(preferred);
+ opt->reserved = 0;
+ opt->prefix = *local;
+
+ inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN);
+ if (!option_bool(OPT_QUIET_RA))
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff);
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+ (void)type;
+
+ if (index == *((int *)parm))
+ {
+ /* size is in units of 8 octets and includes type and length (2 bytes)
+ add 7 to round up */
+ int len = (maclen + 9) >> 3;
+ unsigned char *p = expand(len << 3);
+ memset(p, 0, len << 3);
+ *p++ = ICMP6_OPT_SOURCE_MAC;
+ *p++ = len;
+ memcpy(p, mac, maclen);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+time_t periodic_ra(time_t now)
+{
+ struct search_param param;
+ struct dhcp_context *context;
+ time_t next_event;
+ struct alias_param aparam;
+
+ param.now = now;
+ param.iface = 0;
+
+ while (1)
+ {
+ /* find overdue events, and time of first future event */
+ for (next_event = 0, context = daemon->dhcp6; context; context = context->next)
+ if (context->ra_time != 0)
+ {
+ if (difftime(context->ra_time, now) <= 0.0)
+ break; /* overdue */
+
+ if (next_event == 0 || difftime(next_event, context->ra_time) > 0.0)
+ next_event = context->ra_time;
+ }
+
+ /* none overdue */
+ if (!context)
+ break;
+
+ if ((context->flags & CONTEXT_OLD) &&
+ context->if_index != 0 &&
+ indextoname(daemon->icmp6fd, context->if_index, param.name))
+ {
+ /* A context for an old address. We'll not find the interface by
+ looking for addresses, but we know it anyway, since the context is
+ constructed */
+ param.iface = context->if_index;
+ new_timeout(context, param.name, now);
+ }
+ else if (iface_enumerate(AF_INET6, ¶m, iface_search))
+ /* There's a context overdue, but we can't find an interface
+ associated with it, because it's for a subnet we dont
+ have an interface on. Probably we're doing DHCP on
+ a remote subnet via a relay. Zero the timer, since we won't
+ ever be able to send ra's and satisfy it. */
+ context->ra_time = 0;
+
+ if (param.iface != 0 &&
+ iface_check(AF_LOCAL, NULL, param.name, NULL))
+ {
+ struct iname *tmp;
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, param.name))
+ break;
+ if (!tmp)
+ {
+ send_ra(now, param.iface, param.name, NULL);
+
+ /* Also send on all interfaces that are aliases of this
+ one. */
+ for (aparam.bridge = daemon->bridges;
+ aparam.bridge;
+ aparam.bridge = aparam.bridge->next)
+ if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
+ {
+ /* Count the number of alias interfaces for this
+ 'bridge', by calling iface_enumerate with
+ send_ra_to_aliases and NULL alias_ifs. */
+ aparam.iface = param.iface;
+ aparam.alias_ifs = NULL;
+ aparam.num_alias_ifs = 0;
+ iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases);
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => %d alias(es)",
+ param.name, daemon->addrbuff, aparam.num_alias_ifs);
+
+ /* Allocate memory to store the alias interface
+ indices. */
+ aparam.alias_ifs = (int *)whine_malloc(aparam.num_alias_ifs *
+ sizeof(int));
+ if (aparam.alias_ifs)
+ {
+ /* Use iface_enumerate again to get the alias
+ interface indices, then send on each of
+ those. */
+ aparam.max_alias_ifs = aparam.num_alias_ifs;
+ aparam.num_alias_ifs = 0;
+ iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases);
+ for (; aparam.num_alias_ifs; aparam.num_alias_ifs--)
+ {
+ my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => i/f %d",
+ param.name, daemon->addrbuff,
+ aparam.alias_ifs[aparam.num_alias_ifs - 1]);
+ send_ra_alias(now,
+ param.iface,
+ param.name,
+ NULL,
+ aparam.alias_ifs[aparam.num_alias_ifs - 1]);
+ }
+ free(aparam.alias_ifs);
+ }
+
+ /* The source interface can only appear in at most
+ one --bridge-interface. */
+ break;
+ }
+ }
+ }
+ }
+ return next_event;
+}
+
+static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+ struct alias_param *aparam = (struct alias_param *)parm;
+ char ifrn_name[IFNAMSIZ];
+ struct dhcp_bridge *alias;
+
+ (void)type;
+ (void)mac;
+ (void)maclen;
+
+ if (if_indextoname(index, ifrn_name))
+ for (alias = aparam->bridge->alias; alias; alias = alias->next)
+ if (wildcard_matchn(alias->iface, ifrn_name, IFNAMSIZ))
+ {
+ if (aparam->alias_ifs && (aparam->num_alias_ifs < aparam->max_alias_ifs))
+ aparam->alias_ifs[aparam->num_alias_ifs] = index;
+ aparam->num_alias_ifs++;
+ }
+
+ return 1;
+}
+
+static int iface_search(struct in6_addr *local, int prefix,
+ int scope, int if_index, int flags,
+ int preferred, int valid, void *vparam)
+{
+ struct search_param *param = vparam;
+ struct dhcp_context *context;
+ struct iname *tmp;
+
+ (void)scope;
+ (void)preferred;
+ (void)valid;
+
+ /* ignore interfaces we're not doing DHCP on. */
+ if (!indextoname(daemon->icmp6fd, if_index, param->name) ||
+ !iface_check(AF_LOCAL, NULL, param->name, NULL))
+ return 1;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, param->name))
+ return 1;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ prefix <= context->prefix &&
+ is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix) &&
+ context->ra_time != 0 &&
+ difftime(context->ra_time, param->now) <= 0.0)
+ {
+ /* found an interface that's overdue for RA determine new
+ timeout value and arrange for RA to be sent unless interface is
+ still doing DAD.*/
+ if (!(flags & IFACE_TENTATIVE))
+ param->iface = if_index;
+
+ new_timeout(context, param->name, param->now);
+
+ /* zero timers for other contexts on the same subnet, so they don't timeout
+ independently */
+ for (context = context->next; context; context = context->next)
+ if (prefix <= context->prefix &&
+ is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix))
+ context->ra_time = 0;
+
+ return 0; /* found, abort */
+ }
+
+ return 1; /* keep searching */
+}
+
+static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now)
+{
+ if (difftime(now, context->ra_short_period_start) < 60.0)
+ /* range 5 - 20 */
+ context->ra_time = now + 5 + (rand16()/4400);
+ else
+ {
+ /* range 3/4 - 1 times MaxRtrAdvInterval */
+ unsigned int adv_interval = calc_interval(find_iface_param(iface_name));
+ context->ra_time = now + (3 * adv_interval)/4 + ((adv_interval * (unsigned int)rand16()) >> 18);
+ }
+}
+
+static struct ra_interface *find_iface_param(char *iface)
+{
+ struct ra_interface *ra;
+
+ for (ra = daemon->ra_interfaces; ra; ra = ra->next)
+ if (wildcard_match(ra->name, iface))
+ return ra;
+
+ return NULL;
+}
+
+static unsigned int calc_interval(struct ra_interface *ra)
+{
+ int interval = 600;
+
+ if (ra && ra->interval != 0)
+ {
+ interval = ra->interval;
+ if (interval > 1800)
+ interval = 1800;
+ else if (interval < 4)
+ interval = 4;
+ }
+
+ return (unsigned int)interval;
+}
+
+static unsigned int calc_lifetime(struct ra_interface *ra)
+{
+ int lifetime, interval = (int)calc_interval(ra);
+
+ if (!ra || ra->lifetime == -1) /* not specified */
+ lifetime = 3 * interval;
+ else
+ {
+ lifetime = ra->lifetime;
+ if (lifetime < interval && lifetime != 0)
+ lifetime = interval;
+ else if (lifetime > 9000)
+ lifetime = 9000;
+ }
+
+ return (unsigned int)lifetime;
+}
+
+static unsigned int calc_prio(struct ra_interface *ra)
+{
+ if (ra)
+ return ra->prio;
+
+ return 0;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c
new file mode 100755
index 0000000..6fc4f26
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c
@@ -0,0 +1,2061 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+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, namelen = 0, 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, p1 ? p1 : 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 == 0x00)
+ { /* label_type = 0 -> label. */
+ namelen += l + 1; /* include period */
+ if (namelen >= 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;
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ if (c == 0 || c == '.' || c == NAME_ESCAPE)
+ {
+ *cp++ = NAME_ESCAPE;
+ *cp++ = c+1;
+ }
+ else
+ *cp++ = c;
+ }
+ else
+#endif
+ if (c != 0 && c != '.')
+ *cp++ = c;
+ 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';
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE)
+ c1 = (*cp++)-1;
+#endif
+
+ if (c2 >= 'A' && c2 <= 'Z')
+ c2 += 'a' - 'A';
+
+ if (c1 != c2)
+ retvalue = 2;
+ }
+ }
+
+ if (isExtract)
+ *cp++ = '.';
+ else if (*cp != 0 && *cp++ != '.')
+ retvalue = 2;
+ }
+ else
+ return 0; /* label types 0x40 and 0x80 not supported */
+ }
+}
+
+/* Max size of input string (for IPv6) is 75 chars.) */
+#define MAXARPANAME 75
+int in_arpa_name_2_addr(char *namein, union 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(union 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 arrives as a name of the form
+ www.xxx.yyy.zzz.in-addr.arpa
+ some of the low order address octets might be missing
+ and should be set to zero. */
+ 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;
+ }
+ 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 representations are obsolete and
+ left-over from the many DNS-for-IPv6 wars. We support all the formats
+ that we can since there is no reason not to.
+ */
+
+ 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 in6_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;
+ }
+ }
+
+ return 0;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+/* is addr in the non-globally-routed IP space? */
+int private_net(struct in_addr addr, int ban_localhost)
+{
+ in_addr_t ip_addr = ntohl(addr.s_addr);
+
+ return
+ (((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost) /* 127.0.0.0/8 (loopback) */ ||
+ (((ip_addr & 0xFF000000) == 0x00000000) && ban_localhost) /* RFC 5735 section 3. "here" network */ ||
+ ((ip_addr & 0xFF000000) == 0x0A000000) /* 10.0.0.0/8 (private) */ ||
+ ((ip_addr & 0xFFF00000) == 0xAC100000) /* 172.16.0.0/12 (private) */ ||
+ ((ip_addr & 0xFFFF0000) == 0xC0A80000) /* 192.168.0.0/16 (private) */ ||
+ ((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xC0000200) /* 192.0.2.0/24 (test-net) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xC6336400) /* 198.51.100.0/24(test-net) */ ||
+ ((ip_addr & 0xFFFFFF00) == 0xCB007100) /* 203.0.113.0/24 (test-net) */ ||
+ ((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF) /* 255.255.255.255/32 (broadcast)*/ ;
+}
+
+static int private_net6(struct in6_addr *a, int ban_localhost)
+{
+ /* Block IPv4-mapped IPv6 addresses in private IPv4 address space */
+ if (IN6_IS_ADDR_V4MAPPED(a))
+ {
+ struct in_addr v4;
+ v4.s_addr = ((const uint32_t *) (a))[3];
+ return private_net(v4, ban_localhost);
+ }
+
+ return
+ (IN6_IS_ADDR_UNSPECIFIED(a) && ban_localhost) || /* RFC 6303 4.3 */
+ (IN6_IS_ADDR_LOOPBACK(a) && ban_localhost) || /* RFC 6303 4.3 */
+ IN6_IS_ADDR_LINKLOCAL(a) || /* RFC 6303 4.5 */
+ IN6_IS_ADDR_SITELOCAL(a) ||
+ ((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */
+ ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
+}
+
+static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored)
+{
+ int i, qtype, qclass, rdlen;
+
+ for (i = count; i != 0; i--)
+ {
+ if (!(p = skip_name(p, header, qlen, 10)))
+ return 0; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ p += 4; /* ttl */
+ 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;
+ *doctored = 1;
+ memcpy(p, &addr, INADDRSZ);
+ break;
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, qlen, rdlen))
+ return 0; /* bad packet */
+ }
+
+ return p;
+}
+
+static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
+{
+ 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, doctored)))
+ 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 additional section too */
+ if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored))
+ return 0;
+
+ if (!found_soa)
+ minttl = daemon->neg_ttl;
+
+ return minttl;
+}
+
+/* Print TXT reply to log */
+static int print_txt(struct dns_header *header, const size_t qlen, char *name,
+ unsigned char *p, const int ardlen, int secflag)
+{
+ unsigned char *p1 = p;
+ if (!CHECK_LEN(header, p1, qlen, ardlen))
+ return 0;
+ /* Loop over TXT payload */
+ while ((p1 - p) < ardlen)
+ {
+ unsigned int i, len = *p1;
+ unsigned char *p3 = p1;
+ if ((p1 + len - p) >= ardlen)
+ return 0; /* bad packet */
+
+ /* make counted string zero-term and sanitise */
+ for (i = 0; i < len; i++)
+ {
+ if (!isprint((int)*(p3+1)))
+ break;
+ *p3 = *(p3+1);
+ p3++;
+ }
+
+ *p3 = 0;
+ log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1);
+ /* restore */
+ memmove(p1 + 1, p1, i);
+ *p1 = len;
+ p1 += len+1;
+ }
+ return 1;
+}
+
+/* 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,
+ char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
+ int secure, int *doctored)
+{
+ unsigned char *p, *p1, *endrr, *namep;
+ int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
+ unsigned long ttl = 0;
+ union all_addr addr;
+#ifdef HAVE_IPSET
+ char **ipsets_cur;
+#else
+ (void)ipsets; /* unused */
+#endif
+ int found = 0, cname_count = CNAME_CHAIN;
+ struct crec *cpp = NULL;
+ int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
+#ifdef HAVE_DNSSEC
+ int cname_short = 0;
+#endif
+ unsigned long cttl = ULONG_MAX, attl;
+
+ cache_start_insert();
+
+ /* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */
+ if (daemon->doctors || option_bool(OPT_DNSSEC_VALID))
+ {
+ searched_soa = 1;
+ ttl = find_soa(header, qlen, doctored);
+
+ if (*doctored)
+ {
+ if (secure)
+ return 0;
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ for (j = 0; j < ntohs(header->ancount); j++)
+ if (daemon->rr_status[j] != 0)
+ return 0;
+#endif
+ }
+ }
+
+ namep = p = (unsigned char *)(header+1);
+
+ if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
+ return 0; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ if (qclass != C_IN)
+ return 0;
+
+ /* PTRs: we chase CNAMEs here, since we have no way to
+ represent them in the cache. */
+ if (qtype == T_PTR)
+ {
+ int insert = 1, name_encoding = in_arpa_name_2_addr(name, &addr);
+
+ if (!(flags & F_NXDOMAIN))
+ {
+ cname_loop:
+ if (!(p1 = skip_questions(header, qlen)))
+ return 0;
+
+ for (j = 0; j < ntohs(header->ancount); j++)
+ {
+ int secflag = 0;
+ if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
+ return 0; /* bad packet */
+
+ 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))
+ {
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
+ {
+ /* validated RR anywhere in CNAME chain, don't cache. */
+ if (cname_short || aqtype == T_CNAME)
+ insert = 0;
+
+ secflag = F_DNSSECOK;
+ /* limit TTL based on signature. */
+ if (daemon->rr_status[j] < cttl)
+ cttl = daemon->rr_status[j];
+ }
+#endif
+
+ if (aqtype == T_CNAME)
+ log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+
+ if (!extract_name(header, qlen, &p1, name, 1, 0))
+ return 0;
+
+ if (aqtype == T_CNAME)
+ {
+ if (!cname_count--)
+ return 0; /* looped CNAMES, we can't cache. */
+#ifdef HAVE_DNSSEC
+ cname_short = 1;
+#endif
+ goto cname_loop;
+ }
+
+ found = 1;
+
+ if (!name_encoding)
+ log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+ else
+ {
+ log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL);
+ if (insert)
+ cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE);
+ }
+ }
+
+ 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, doctored);
+ }
+
+ flags |= F_NEG | (secure ? F_DNSSECOK : 0);
+ if (name_encoding && ttl)
+ {
+ flags |= F_REVERSE | name_encoding;
+ cache_insert(NULL, &addr, C_IN, now, ttl, flags);
+ }
+
+ log_query(flags | F_UPSTREAM, name, &addr, NULL);
+ }
+ }
+ else
+ {
+ /* everything other than PTR */
+ struct crec *newc;
+ int addrlen = 0, insert = 1;
+
+ if (qtype == T_A)
+ {
+ addrlen = INADDRSZ;
+ flags |= F_IPV4;
+ }
+ else if (qtype == T_AAAA)
+ {
+ addrlen = IN6ADDRSZ;
+ flags |= F_IPV6;
+ }
+ else if (qtype == T_SRV)
+ flags |= F_SRV;
+ else
+ insert = 0; /* NOTE: do not cache data from CNAME queries. */
+
+ cname_loop1:
+ if (!(p1 = skip_questions(header, qlen)))
+ return 0;
+
+ for (j = 0; j < ntohs(header->ancount); j++)
+ {
+ int secflag = 0;
+
+ if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
+ return 0; /* bad packet */
+
+ 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;
+
+ /* Not what we're looking for? */
+ if (aqclass != C_IN || res == 2)
+ {
+ p1 = endrr;
+ if (!CHECK_LEN(header, p1, qlen, 0))
+ return 0; /* bad packet */
+ continue;
+ }
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
+ {
+ secflag = F_DNSSECOK;
+
+ /* limit TTl based on sig. */
+ if (daemon->rr_status[j] < attl)
+ attl = daemon->rr_status[j];
+ }
+#endif
+
+ if (aqtype == T_CNAME)
+ {
+ if (!cname_count--)
+ return 0; /* looped CNAMES */
+
+ log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+
+ if (insert)
+ {
+ if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag)))
+ {
+ newc->addr.cname.target.cache = NULL;
+ newc->addr.cname.is_name_ptr = 0;
+ if (cpp)
+ {
+ next_uid(newc);
+ cpp->addr.cname.target.cache = newc;
+ cpp->addr.cname.uid = newc->uid;
+ }
+ }
+
+ cpp = newc;
+ if (attl < cttl)
+ cttl = attl;
+ }
+
+ namep = p1;
+ if (!extract_name(header, qlen, &p1, name, 1, 0))
+ return 0;
+
+ goto cname_loop1;
+ }
+ else if (aqtype != qtype)
+ {
+#ifdef HAVE_DNSSEC
+ if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
+#endif
+ log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+ }
+ else if (!(flags & F_NXDOMAIN))
+ {
+ found = 1;
+
+ if (flags & F_SRV)
+ {
+ unsigned char *tmp = namep;
+
+ if (!CHECK_LEN(header, p1, qlen, 6))
+ return 0; /* bad packet */
+ GETSHORT(addr.srv.priority, p1);
+ GETSHORT(addr.srv.weight, p1);
+ GETSHORT(addr.srv.srvport, p1);
+ if (!extract_name(header, qlen, &p1, name, 1, 0))
+ return 0;
+ addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
+ if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
+ return 0;
+
+ /* we overwrote the original name, so get it back here. */
+ if (!extract_name(header, qlen, &tmp, name, 1, 0))
+ return 0;
+ }
+ else if (flags & (F_IPV4 | F_IPV6))
+ {
+ /* 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)
+ {
+ if ((flags & F_IPV4) &&
+ private_net(addr.addr4, !option_bool(OPT_LOCAL_REBIND)))
+ return 1;
+
+ if ((flags & F_IPV6) &&
+ private_net6(&addr.addr6, !option_bool(OPT_LOCAL_REBIND)))
+ return 1;
+ }
+
+#ifdef HAVE_IPSET
+ if (ipsets && (flags & (F_IPV4 | F_IPV6)))
+ {
+ ipsets_cur = ipsets;
+ while (*ipsets_cur)
+ {
+ log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur);
+ add_to_ipset(*ipsets_cur++, &addr, flags, 0);
+ }
+ }
+#endif
+ }
+
+ if (insert)
+ {
+ newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag);
+ if (newc && cpp)
+ {
+ next_uid(newc);
+ cpp->addr.cname.target.cache = newc;
+ cpp->addr.cname.uid = newc->uid;
+ }
+ cpp = NULL;
+ }
+
+ if (aqtype == T_TXT)
+ {
+ if (!print_txt(header, qlen, name, p1, ardlen, secflag))
+ return 0;
+ }
+ else
+ log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, querystr(NULL, aqtype));
+ }
+
+ 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, doctored);
+ }
+
+ /* If there's no SOA to get the TTL from, but there is a CNAME
+ pointing at this, inherit its TTL */
+ if (ttl || cpp)
+ {
+ if (ttl == 0)
+ ttl = cttl;
+
+ log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL);
+
+ if (insert)
+ {
+ newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
+ if (newc && cpp)
+ {
+ next_uid(newc);
+ cpp->addr.cname.target.cache = newc;
+ cpp->addr.cname.uid = newc->uid;
+ }
+ }
+ }
+ }
+ }
+
+ /* Don't put stuff from a truncated packet into the cache.
+ Don't cache replies from non-recursive nameservers, since we may get a
+ reply containing a CNAME but not its target, even though the target
+ does exist. */
+ if (!(header->hb3 & HB3_TC) &&
+ !(header->hb4 & HB4_CD) &&
+ (header->hb4 & HB4_RA) &&
+ !no_cache_dnssec)
+ cache_end_insert();
+
+ return 0;
+}
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+/* Don't pass control chars and weird escapes to UBus. */
+static int safe_name(char *name)
+{
+ unsigned char *r;
+
+ for (r = (unsigned char *)name; *r; r++)
+ if (!isprint((int)*r))
+ return 0;
+
+ return 1;
+}
+
+void report_addresses(struct dns_header *header, size_t len, u32 mark)
+{
+ unsigned char *p, *endrr;
+ int i;
+ unsigned long attl;
+ struct allowlist *allowlists;
+ char **pattern_pos;
+
+ if (RCODE(header) != NOERROR)
+ return;
+
+ for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+ if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
+ for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++)
+ if (!strcmp(*pattern_pos, "*"))
+ return;
+
+ if (!(p = skip_questions(header, len)))
+ return;
+ for (i = ntohs(header->ancount); i != 0; i--)
+ {
+ int aqtype, aqclass, ardlen;
+
+ if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
+ return;
+
+ if (!CHECK_LEN(header, p, len, 10))
+ return;
+ GETSHORT(aqtype, p);
+ GETSHORT(aqclass, p);
+ GETLONG(attl, p);
+ GETSHORT(ardlen, p);
+
+ if (!CHECK_LEN(header, p, len, ardlen))
+ return;
+ endrr = p+ardlen;
+
+ if (aqclass == C_IN)
+ {
+ if (aqtype == T_CNAME)
+ {
+ if (!extract_name(header, len, &p, daemon->workspacename, 1, 0))
+ return;
+ if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename))
+ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, attl);
+ }
+ if (aqtype == T_A)
+ {
+ struct in_addr addr;
+ char ip[INET_ADDRSTRLEN];
+ if (ardlen != INADDRSZ)
+ return;
+ memcpy(&addr, p, ardlen);
+ if (inet_ntop(AF_INET, &addr, ip, sizeof ip) && safe_name(daemon->namebuff))
+ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
+ }
+ else if (aqtype == T_AAAA)
+ {
+ struct in6_addr addr;
+ char ip[INET6_ADDRSTRLEN];
+ if (ardlen != IN6ADDRSZ)
+ return;
+ memcpy(&addr, p, ardlen);
+ if (inet_ntop(AF_INET6, &addr, ip, sizeof ip) && safe_name(daemon->namebuff))
+ ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
+ }
+ }
+
+ p = endrr;
+ }
+}
+#endif
+
+/* 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;
+
+ *name = 0; /* return empty name if no query found. */
+
+ if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY)
+ return 0; /* must be exactly one query. */
+
+ if (!(header->hb3 & HB3_QR) && (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0))
+ return 0; /* non-standard 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;
+ }
+
+#ifdef HAVE_DNSSEC
+ /* F_DNSSECOK as agument to search_servers() inhibits forwarding
+ to servers for domains without a trust anchor. This make the
+ behaviour for DS and DNSKEY queries we forward the same
+ as for DS and DNSKEY queries we originate. */
+ if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
+ return F_DNSSECOK;
+#endif
+
+ return F_QUERY;
+}
+
+void setup_reply(struct dns_header *header, unsigned int flags, int ede)
+{
+ /* clear authoritative and truncated flags, set QR flag */
+ header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR;
+ /* clear AD flag, set RA flag */
+ header->hb4 = (header->hb4 & ~HB4_AD) | HB4_RA;
+
+ header->nscount = htons(0);
+ header->arcount = htons(0);
+ header->ancount = htons(0); /* no answers unless changed below */
+ if (flags == F_NOERR)
+ SET_RCODE(header, NOERROR); /* empty domain */
+ else if (flags == F_NXDOMAIN)
+ SET_RCODE(header, NXDOMAIN);
+ else if (flags & ( F_IPV4 | F_IPV6))
+ {
+ SET_RCODE(header, NOERROR);
+ header->hb3 |= HB3_AA;
+ }
+ else /* nowhere to forward to */
+ {
+ union all_addr a;
+ a.log.rcode = REFUSED;
+ a.log.ede = ede;
+ log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
+ SET_RCODE(header, REFUSED);
+ }
+}
+
+/* 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 mx_srv_record *mx;
+ struct txt_record *txt;
+ struct interface_name *intr;
+ struct ptr_record *ptr;
+ struct naptr *naptr;
+
+ for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+ if (hostname_issubdomain(name, naptr->name))
+ return 1;
+
+ for (mx = daemon->mxnames; mx; mx = mx->next)
+ if (hostname_issubdomain(name, mx->name))
+ return 1;
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ if (hostname_issubdomain(name, txt->name))
+ return 1;
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (hostname_issubdomain(name, intr->name))
+ return 1;
+
+ for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+ if (hostname_issubdomain(name, ptr->name))
+ return 1;
+
+ if (cache_find_non_terminal(name, now))
+ return 1;
+
+ return 0;
+}
+
+static int check_bad_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr, char *name, unsigned long *ttlp)
+{
+ 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 (name && !extract_name(header, qlen, &p, name, 1, 10))
+ return 0; /* bad packet */
+
+ if (!name && !(p = skip_name(p, header, qlen, 10)))
+ return 0;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ if (ttlp)
+ *ttlp = ttl;
+
+ if (qclass == C_IN)
+ {
+ if (qtype == T_A)
+ {
+ struct in_addr addr;
+
+ if (!CHECK_LEN(header, p, qlen, INADDRSZ))
+ return 0;
+
+ memcpy(&addr, p, INADDRSZ);
+
+ for (baddrp = baddr; baddrp; baddrp = baddrp->next)
+ if (!baddrp->is6 && is_same_net_prefix(addr, baddrp->addr.addr4, baddrp->prefix))
+ return 1;
+ }
+ else if (qtype == T_AAAA)
+ {
+ struct in6_addr addr;
+
+ if (!CHECK_LEN(header, p, qlen, IN6ADDRSZ))
+ return 0;
+
+ memcpy(&addr, p, IN6ADDRSZ);
+
+ for (baddrp = baddr; baddrp; baddrp = baddrp->next)
+ if (baddrp->is6 && is_same_net6(&addr, &baddrp->addr.addr6, baddrp->prefix))
+ return 1;
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, qlen, rdlen))
+ return 0;
+ }
+
+ 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, time_t now)
+{
+ unsigned long ttl;
+
+ if (check_bad_address(header, qlen, daemon->bogus_addr, name, &ttl))
+ {
+ /* 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, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN);
+ cache_end_insert();
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int check_for_ignored_address(struct dns_header *header, size_t qlen)
+{
+ return check_bad_address(header, qlen, daemon->ignore_addr, NULL, NULL);
+}
+
+int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
+ unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
+{
+ va_list ap;
+ unsigned char *sav, *p = *pp;
+ int j;
+ unsigned short usval;
+ long lval;
+ char *sval;
+
+#define CHECK_LIMIT(size) \
+ if (limit && p + (size) > (unsigned char*)limit) goto truncated;
+
+ va_start(ap, format); /* make ap point to 1st unamed argument */
+
+ if (truncp && *truncp)
+ goto truncated;
+
+ if (nameoffset > 0)
+ {
+ CHECK_LIMIT(2);
+ PUTSHORT(nameoffset | 0xc000, p);
+ }
+ else
+ {
+ char *name = va_arg(ap, char *);
+ if (name && !(p = do_rfc1035_name(p, name, limit)))
+ goto truncated;
+
+ if (nameoffset < 0)
+ {
+ CHECK_LIMIT(2);
+ PUTSHORT(-nameoffset | 0xc000, p);
+ }
+ else
+ {
+ CHECK_LIMIT(1);
+ *p++ = 0;
+ }
+ }
+
+ /* type (2) + class (2) + ttl (4) + rdlen (2) */
+ CHECK_LIMIT(10);
+
+ PUTSHORT(type, p);
+ PUTSHORT(class, p);
+ PUTLONG(ttl, p); /* TTL */
+
+ sav = p; /* Save pointer to RDLength field */
+ PUTSHORT(0, p); /* Placeholder RDLength */
+
+ for (; *format; format++)
+ switch (*format)
+ {
+ case '6':
+ CHECK_LIMIT(IN6ADDRSZ);
+ sval = va_arg(ap, char *);
+ memcpy(p, sval, IN6ADDRSZ);
+ p += IN6ADDRSZ;
+ break;
+
+ case '4':
+ CHECK_LIMIT(INADDRSZ);
+ sval = va_arg(ap, char *);
+ memcpy(p, sval, INADDRSZ);
+ p += INADDRSZ;
+ break;
+
+ case 'b':
+ CHECK_LIMIT(1);
+ usval = va_arg(ap, int);
+ *p++ = usval;
+ break;
+
+ case 's':
+ CHECK_LIMIT(2);
+ usval = va_arg(ap, int);
+ PUTSHORT(usval, p);
+ break;
+
+ case 'l':
+ CHECK_LIMIT(4);
+ lval = va_arg(ap, long);
+ PUTLONG(lval, p);
+ break;
+
+ case 'd':
+ /* get domain-name answer arg and store it in RDATA field */
+ if (offset)
+ *offset = p - (unsigned char *)header;
+ if (!(p = do_rfc1035_name(p, va_arg(ap, char *), limit)))
+ goto truncated;
+ CHECK_LIMIT(1);
+ *p++ = 0;
+ break;
+
+ case 't':
+ usval = va_arg(ap, int);
+ CHECK_LIMIT(usval);
+ sval = va_arg(ap, char *);
+ if (usval != 0)
+ memcpy(p, sval, usval);
+ p += usval;
+ break;
+
+ case 'z':
+ sval = va_arg(ap, char *);
+ usval = sval ? strlen(sval) : 0;
+ if (usval > 255)
+ usval = 255;
+ CHECK_LIMIT(usval + 1);
+ *p++ = (unsigned char)usval;
+ memcpy(p, sval, usval);
+ p += usval;
+ break;
+ }
+
+ va_end(ap); /* clean up variable argument pointer */
+
+ /* Now, store real RDLength. sav already checked against limit. */
+ j = p - sav - 2;
+ PUTSHORT(j, sav);
+
+ *pp = p;
+ return 1;
+
+ truncated:
+ va_end(ap);
+ if (truncp)
+ *truncp = 1;
+ return 0;
+
+#undef CHECK_LIMIT
+}
+
+static unsigned long crec_ttl(struct crec *crecp, time_t now)
+{
+ /* Return 0 ttl for DHCP entries, which might change
+ before the lease expires, unless configured otherwise. */
+
+ if (crecp->flags & F_DHCP)
+ {
+ int conf_ttl = daemon->use_dhcp_ttl ? daemon->dhcp_ttl : daemon->local_ttl;
+
+ /* Apply ceiling of actual lease length to configured TTL. */
+ if (!(crecp->flags & F_IMMORTAL) && (crecp->ttd - now) < conf_ttl)
+ return crecp->ttd - now;
+
+ return conf_ttl;
+ }
+
+ /* Immortal entries other than DHCP are local, and hold TTL in TTD field. */
+ if (crecp->flags & F_IMMORTAL)
+ return crecp->ttd;
+
+ /* Return the Max TTL value if it is lower than the actual TTL */
+ if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl))
+ return crecp->ttd - now;
+ else
+ return daemon->max_ttl;
+}
+
+static int cache_validated(const struct crec *crecp)
+{
+ return (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK));
+}
+
+/* return zero if we can't answer from cache, or packet size if we can */
+size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
+ struct in_addr local_addr, struct in_addr local_netmask,
+ time_t now, int ad_reqd, int do_bit, int have_pseudoheader)
+{
+ char *name = daemon->namebuff;
+ unsigned char *p, *ansp;
+ unsigned int qtype, qclass;
+ union all_addr addr;
+ int nameoffset;
+ unsigned short flag;
+ int q, ans, anscount = 0, addncount = 0;
+ int dryrun = 0;
+ struct crec *crecp;
+ int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1;
+ struct mx_srv_record *rec;
+ size_t len;
+ int rd_bit = (header->hb3 & HB3_RD);
+
+ /* never answer queries with RD unset, to avoid cache snooping. */
+ if (ntohs(header->ancount) != 0 ||
+ ntohs(header->nscount) != 0 ||
+ ntohs(header->qdcount) == 0 ||
+ OPCODE(header) != QUERY )
+ return 0;
+
+ /* Don't return AD set if checking disabled. */
+ if (header->hb4 & HB4_CD)
+ sec_data = 0;
+
+ /* If there is an additional data section then it will be overwritten by
+ partial replies, so we have to do a dry run to see if we can answer
+ the query. */
+ if (ntohs(header->arcount) != 0)
+ dryrun = 1;
+
+ 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--)
+ {
+ int count = 255; /* catch loops */
+
+ /* 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 */
+
+ while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME)))
+ {
+ char *cname_target = cache_get_cname_target(crecp);
+
+ /* If the client asked for DNSSEC don't use cached data. */
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+ (rd_bit && (!do_bit || cache_validated(crecp))))
+ {
+ if (crecp->flags & F_CONFIG || qtype == T_CNAME)
+ ans = 1;
+
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
+ 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", cname_target))
+ anscount++;
+ }
+
+ }
+ else
+ return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
+
+ strcpy(name, cname_target);
+ }
+
+ 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, sec_data = 0;
+ if (!dryrun)
+ {
+ unsigned long ttl = daemon->local_ttl;
+ int ok = 1;
+#ifndef NO_ID
+ /* Dynamically generate stat record */
+ if (t->stat != 0)
+ {
+ ttl = 0;
+ if (!cache_make_stat(t))
+ ok = 0;
+ }
+#endif
+ if (ok)
+ {
+ log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ ttl, NULL,
+ T_TXT, t->class, "t", t->len, t->txt))
+ anscount++;
+ }
+ }
+ }
+ }
+ }
+
+ if (qclass == C_CHAOS)
+ {
+ /* don't forward *.bind and *.server chaos queries - always reply with NOTIMP */
+ if (hostname_issubdomain("bind", name) || hostname_issubdomain("server", name))
+ {
+ if (!ans)
+ {
+ notimp = 1, auth = 0;
+ if (!dryrun)
+ {
+ addr.log.rcode = NOTIMP;
+ log_query(F_CONFIG | F_RCODE, name, &addr, NULL);
+ }
+ ans = 1, sec_data = 0;
+ }
+ }
+ }
+
+ if (qclass == C_IN)
+ {
+ struct txt_record *t;
+
+ for (t = daemon->rr; t; t = t->next)
+ if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name))
+ {
+ ans = 1;
+ sec_data = 0;
+ if (!dryrun)
+ {
+ log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, t->class));
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->local_ttl, NULL,
+ t->class, C_IN, "t", t->len, t->txt))
+ anscount++;
+ }
+ }
+
+ 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)
+ {
+ struct addrlist *addrlist;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr)
+ break;
+
+ if (addrlist)
+ break;
+ else
+ while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+ intr = intr->next;
+ }
+ else if (is_arpa == F_IPV6)
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ struct addrlist *addrlist;
+
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6))
+ break;
+
+ if (addrlist)
+ break;
+ else
+ while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+ intr = intr->next;
+ }
+
+ if (intr)
+ {
+ sec_data = 0;
+ ans = 1;
+ if (!dryrun)
+ {
+ log_query(is_arpa | 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;
+ sec_data = 0;
+ 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)))
+ {
+ /* Don't use cache when DNSSEC data required, unless we know that
+ the zone is unsigned, which implies that we're doing
+ validation. */
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+ (rd_bit && (!do_bit || cache_validated(crecp)) ))
+ {
+ 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_DNSSECOK))
+ sec_data = 0;
+
+ ans = 1;
+
+ if (crecp->flags & F_NEG)
+ {
+ 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)))
+ 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_rev_synth(is_arpa, &addr, name))
+ {
+ ans = 1;
+ sec_data = 0;
+ if (!dryrun)
+ {
+ log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL);
+
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->local_ttl, NULL,
+ T_PTR, C_IN, "d", name))
+ anscount++;
+ }
+ }
+ else if (option_bool(OPT_BOGUSPRIV) &&
+ ((is_arpa == F_IPV6 && private_net6(&addr.addr6, 1)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) &&
+ !lookup_domain(name, F_DOMAINSRV, NULL, NULL))
+ {
+ /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */
+ ans = 1;
+ sec_data = 0;
+ nxdomain = 1;
+ if (!dryrun)
+ log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
+ name, &addr, NULL);
+ }
+ }
+
+ for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0)
+ {
+ unsigned short type = (flag == F_IPV6) ? T_AAAA : T_A;
+ struct interface_name *intr;
+
+ if (qtype != type && qtype != T_ANY)
+ continue;
+
+ /* interface name stuff */
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (hostname_isequal(name, intr->name))
+ break;
+
+ if (intr)
+ {
+ struct addrlist *addrlist;
+ int gotit = 0, localise = 0;
+
+ enumerate_interfaces(0);
+
+ /* See if a putative address is on the network from which we received
+ the query, is so we'll filter other answers. */
+ if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && type == T_A)
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (hostname_isequal(name, intr->name))
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (!(addrlist->flags & ADDRLIST_IPV6) &&
+ is_same_net(addrlist->addr.addr4, local_addr, local_netmask))
+ {
+ localise = 1;
+ break;
+ }
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ if (hostname_isequal(name, intr->name))
+ {
+ for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+ if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type)
+ {
+ if (localise &&
+ !is_same_net(addrlist->addr.addr4, local_addr, local_netmask))
+ continue;
+
+ if (addrlist->flags & ADDRLIST_REVONLY)
+ continue;
+
+ ans = 1;
+ sec_data = 0;
+ if (!dryrun)
+ {
+ gotit = 1;
+ log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL);
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->local_ttl, NULL, type, C_IN,
+ type == T_A ? "4" : "6", &addrlist->addr))
+ anscount++;
+ }
+ }
+ }
+
+ if (!dryrun && !gotit)
+ log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL);
+
+ continue;
+ }
+
+ if ((crecp = cache_find_by_name(NULL, name, now, flag | (dryrun ? F_NO_RR : 0))))
+ {
+ int localise = 0;
+
+ /* See if a putative address is on the network from which we received
+ the query, is so we'll filter other answers. */
+ if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
+ {
+ struct crec *save = crecp;
+ do {
+ if ((crecp->flags & F_HOSTS) &&
+ is_same_net(crecp->addr.addr4, local_addr, local_netmask))
+ {
+ localise = 1;
+ break;
+ }
+ } while ((crecp = cache_find_by_name(crecp, name, now, flag)));
+ crecp = save;
+ }
+
+ /* If the client asked for DNSSEC don't use cached data. */
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+ (rd_bit && (!do_bit || cache_validated(crecp)) ))
+ 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 | F_CONFIG)))
+ break;
+
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
+ 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 we are returning local answers depending on network,
+ filter here. */
+ if (localise &&
+ (crecp->flags & F_HOSTS) &&
+ !is_same_net(crecp->addr.addr4, 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,
+ 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)));
+ }
+ else if (is_name_synthetic(flag, name, &addr))
+ {
+ ans = 1, sec_data = 0;
+ if (!dryrun)
+ {
+ log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL);
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr))
+ anscount++;
+ }
+ }
+ }
+
+ 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;
+ sec_data = 0;
+ if (!dryrun)
+ {
+ 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 | F_NO_RR))
+ {
+ ans = 1;
+ sec_data = 0;
+ 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;
+ sec_data = 0;
+ if (!dryrun)
+ {
+ 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)
+ {
+ if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | (dryrun ? F_NO_RR : 0))) &&
+ rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))))
+ {
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
+ auth = 0;
+ found = ans = 1;
+
+ do {
+ if (crecp->flags & F_NEG)
+ {
+ if (crecp->flags & F_NXDOMAIN)
+ nxdomain = 1;
+ if (!dryrun)
+ log_query(crecp->flags, name, NULL, NULL);
+ }
+ else if (!dryrun)
+ {
+ char *target = blockdata_retrieve(crecp->addr.srv.target, crecp->addr.srv.targetlen, NULL);
+ log_query(crecp->flags, name, NULL, 0);
+
+ if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
+ crec_ttl(crecp, now), NULL, T_SRV, C_IN, "sssd",
+ crecp->addr.srv.priority, crecp->addr.srv.weight, crecp->addr.srv.srvport,
+ target))
+ anscount++;
+ }
+ } while ((crecp = cache_find_by_name(crecp, name, now, F_SRV)));
+ }
+ }
+
+ if (!found && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_'))))
+ {
+ ans = 1;
+ sec_data = 0;
+ 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;
+ sec_data = 0;
+ 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, sec_data = 0;
+
+ if (qtype == T_SOA && option_bool(OPT_FILTER))
+ {
+ ans = 1;
+ sec_data = 0;
+ 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)))
+ {
+ int type = crecp->flags & F_IPV4 ? T_A : T_AAAA;
+
+ 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;
+
+ /* authoritative - only hosts and DHCP derived names. */
+ if (auth)
+ header->hb3 |= HB3_AA;
+
+ /* truncation */
+ if (trunc)
+ header->hb3 |= HB3_TC;
+
+ if (nxdomain)
+ SET_RCODE(header, NXDOMAIN);
+ else if (notimp)
+ SET_RCODE(header, NOTIMP);
+ else
+ SET_RCODE(header, NOERROR); /* no error */
+ header->ancount = htons(anscount);
+ header->nscount = htons(0);
+ header->arcount = htons(addncount);
+
+ len = ansp - (unsigned char *)header;
+
+ /* Advertise our packet size limit in our reply */
+ if (have_pseudoheader)
+ len = add_pseudoheader(header, len, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+
+ if (ad_reqd && sec_data)
+ header->hb4 |= HB4_AD;
+ else
+ header->hb4 &= ~HB4_AD;
+
+ return len;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c
new file mode 100755
index 0000000..c902eb7
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c
@@ -0,0 +1,2810 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 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_opt(struct dhcp_lease *lease, unsigned char *opt);
+#endif
+
+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, const char *string, int null_term);
+static struct in_addr option_addr(unsigned char *opt);
+static unsigned int option_uint(unsigned char *opt, int offset, int size);
+static void log_packet(char *type, void *addr, unsigned char *ext_mac,
+ int mac_len, char *interface, char *string, char *err, u32 xid);
+static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize);
+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, unsigned char *agent_id, unsigned char *real_end);
+static void clear_packet(struct dhcp_packet *mess, unsigned char *end);
+static int in_list(unsigned char *list, int opt);
+static void do_options(struct dhcp_context *context,
+ struct dhcp_packet *mess,
+ unsigned char *end,
+ unsigned char *req_options,
+ char *hostname,
+ char *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,
+ time_t now,
+ unsigned int lease_time,
+ unsigned short fuzz,
+ const char *pxevendor);
+
+
+static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
+static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor);
+static int prune_vendor_opts(struct dhcp_netid *netid);
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now);
+struct dhcp_boot *find_boot(struct dhcp_netid *netid);
+static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
+static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
+
+size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
+ size_t sz, time_t now, int unicast_dest, int loopback,
+ int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
+{
+ unsigned char *opt, *clid = NULL;
+ struct dhcp_lease *ltmp, *lease = NULL;
+ struct dhcp_vendor *vendor;
+ struct dhcp_mac *mac;
+ struct dhcp_netid_list *id_list;
+ int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1;
+ const char *pxevendor = NULL;
+ 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, 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;
+#ifdef HAVE_SCRIPT
+ unsigned char *class = NULL;
+#endif
+
+ 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 && offset + elen + 5 <= len)
+ {
+ 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);
+#ifdef HAVE_SCRIPT
+ class = option_find1(x, y, 3, 1);
+#endif
+ /* 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_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)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 (!option_bool(OPT_IGNORE_CLID) && (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 shared_network *share = NULL;
+ struct in_addr addr;
+ int force = 0, via_relay = 0;
+
+ if (subnet_addr.s_addr)
+ {
+ addr = subnet_addr;
+ force = 1;
+ }
+ else if (mess->giaddr.s_addr)
+ {
+ addr = mess->giaddr;
+ force = 1;
+ via_relay = 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)
+ {
+ struct in_addr netmask = context_tmp->netmask;
+
+ /* guess the netmask for relayed networks */
+ if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0)
+ {
+ if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr)))
+ netmask.s_addr = htonl(0xff000000);
+ else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr)))
+ netmask.s_addr = htonl(0xffff0000);
+ else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr)))
+ netmask.s_addr = htonl(0xffffff00);
+ }
+
+ /* check to see is a context is OK because of a shared address on
+ the relayed subnet. */
+ if (via_relay)
+ for (share = daemon->shared_networks; share; share = share->next)
+ {
+#ifdef HAVE_DHCP6
+ if (share->shared_addr.s_addr == 0)
+ continue;
+#endif
+ if (share->if_index != 0 ||
+ share->match_addr.s_addr != mess->giaddr.s_addr)
+ continue;
+
+ if (netmask.s_addr != 0 &&
+ is_same_net(share->shared_addr, context_tmp->start, netmask) &&
+ is_same_net(share->shared_addr, context_tmp->end, netmask))
+ break;
+ }
+
+ /* This section fills in context mainly when a client which is on a remote (relayed)
+ network renews a lease without using the relay, after dnsmasq has restarted. */
+ if (share ||
+ (netmask.s_addr != 0 &&
+ is_same_net(addr, context_tmp->start, netmask) &&
+ is_same_net(addr, context_tmp->end, netmask)))
+ {
+ context_tmp->netmask = netmask;
+ if (context_tmp->local.s_addr == 0)
+ context_tmp->local = fallback;
+ if (context_tmp->router.s_addr == 0 && !share)
+ context_tmp->router = mess->giaddr;
+
+ /* fill in missing broadcast addresses for relayed ranges */
+ if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
+ context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr;
+
+ context_tmp->current = context_new;
+ context_new = context_tmp;
+ }
+
+ }
+ }
+
+ if (context_new || force)
+ context = context_new;
+ }
+
+ if (!context)
+ {
+ const char *via;
+ if (subnet_addr.s_addr)
+ {
+ via = _("with subnet selector");
+ inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
+ }
+ else
+ {
+ via = _("via");
+ if (mess->giaddr.s_addr)
+ inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
+ }
+ my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
+ via, daemon->addrbuff);
+ return 0;
+ }
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ struct dhcp_context *context_tmp;
+ for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
+ {
+ inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
+ if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+ {
+ inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
+ ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ }
+ else
+ {
+ inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
+ ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ }
+ }
+ }
+
+ /* 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, existence 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 <= (unsigned)(option_len(opt))) &&
+ (option_uint(opt, offset, 4) == (unsigned int)o->u.encap))
+ for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1)
+ {
+ elen = option_uint(opt, o2, 1);
+ if ((o2 + elen + 1 <= (unsigned)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);
+ }
+
+ mess->op = BOOTREPLY;
+
+ config = find_config(daemon->dhcp_conf, context, clid, clid_len,
+ mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid));
+
+ /* set "known" tag for known hosts */
+ if (config)
+ {
+ known_id.net = "known";
+ known_id.next = netid;
+ netid = &known_id;
+ }
+ else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len,
+ mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid)))
+ {
+ known_id.net = "known-othernet";
+ 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, loopback))
+ 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;
+ tagif_netid = run_tag_if(&context->netid);
+ }
+
+ log_tags(tagif_netid, ntohl(mess->xid));
+
+ 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 = lease4_allocate(mess->yiaddr))))
+ message = _("no leases left");
+
+ if (!message)
+ {
+ logaddr = &mess->yiaddr;
+
+ lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 1);
+ if (hostname)
+ lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain);
+ /* 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, now);
+
+ clear_packet(mess, end);
+ do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr),
+ netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
+ }
+ }
+
+ daemon->metrics[METRIC_BOOTP]++;
+ log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid);
+
+ return message ? 0 : dhcp_packet_size(mess, agent_id, real_end);
+ }
+
+ if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 3)))
+ {
+ /* 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;
+
+ /* NB, the following always sets at least one bit */
+ if (option_bool(OPT_FQDN_UPDATE))
+ {
+ if (fqdn_flags & 0x01)
+ {
+ fqdn_flags |= 0x02; /* set O */
+ fqdn_flags &= ~0x01; /* clear S */
+ }
+ fqdn_flags |= 0x08; /* set N */
+ }
+ else
+ {
+ if (!(fqdn_flags & 0x01))
+ fqdn_flags |= 0x03; /* set S and O */
+ fqdn_flags &= ~0x08; /* clear N */
+ }
+
+ if (fqdn_flags & 0x04)
+ while (*op != 0 && ((op + (*op)) - 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)
+ {
+ struct dhcp_match_name *m;
+ size_t nl = strlen(client_hostname);
+
+ if (option_bool(OPT_LOG_OPTS))
+ my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname);
+ for (m = daemon->dhcp_name_match; m; m = m->next)
+ {
+ size_t ml = strlen(m->name);
+ char save = 0;
+
+ if (nl < ml)
+ continue;
+ if (nl > ml)
+ {
+ save = client_hostname[ml];
+ client_hostname[ml] = 0;
+ }
+
+ if (hostname_isequal(client_hostname, m->name) &&
+ (save == 0 || m->wildcard))
+ {
+ m->netid->next = netid;
+ netid = m->netid;
+ }
+
+ if (save != 0)
+ client_hostname[ml] = save;
+ }
+ }
+
+ 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, run_tag_if(netid));
+ 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;
+ }
+ }
+
+ 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 &&
+ is_pxe_client(mess, sz, &pxevendor))
+ {
+ 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;
+
+ for (; context; context = context->current)
+ if (match_netid(context->filter, tagif_netid, 1) &&
+ is_same_net(mess->ciaddr, context->start, context->netmask))
+ break;
+
+ if (!service || !service->basename || !context)
+ return 0;
+
+ clear_packet(mess, end);
+
+ mess->yiaddr = mess->ciaddr;
+ mess->ciaddr.s_addr = 0;
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = context->local;
+
+ if (strchr(service->basename, '.'))
+ snprintf((char *)mess->file, sizeof(mess->file),
+ "%s", service->basename);
+ else
+ snprintf((char *)mess->file, sizeof(mess->file),
+ "%s.%d", service->basename, layer);
+
+ option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
+ option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
+ pxe_misc(mess, end, uuid, pxevendor);
+
+ 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, NULL, mess->xid);
+ log_tags(tagif_netid, ntohl(mess->xid));
+ return dhcp_packet_size(mess, 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;
+ int workaround = 0;
+
+ 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;
+ int redirect4011 = 0;
+
+ if (tmp->netid.net)
+ {
+ tmp->netid.next = netid;
+ tagif_netid = run_tag_if(&tmp->netid);
+ }
+
+ 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);
+
+ /* Redirect EFI clients to port 4011 */
+ if (pxearch >= 6)
+ {
+ redirect4011 = 1;
+ mess->siaddr = tmp->local;
+ }
+
+ /* Returns true if only one matching service is available. On port 4011,
+ it also inserts the boot file and server name. */
+ workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe);
+
+ if (!workaround && boot)
+ {
+ /* Provide the bootfile here, for iPXE, and in case we have no menu items
+ and set discovery_control = 8 */
+ if (boot->next_server.s_addr)
+ mess->siaddr = boot->next_server;
+ else if (boot->tftp_sname)
+ mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+
+ if (boot->file)
+ safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+ }
+
+ option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
+ mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
+ option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
+ pxe_misc(mess, end, uuid, pxevendor);
+ prune_vendor_opts(tagif_netid);
+ if ((pxe && !workaround) || !redirect4011)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+
+ daemon->metrics[METRIC_PXE]++;
+ log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
+ log_tags(tagif_netid, ntohl(mess->xid));
+ if (!ignore)
+ apply_delay(mess->xid, recvtime, tagif_netid);
+ return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end);
+ }
+ }
+ }
+ }
+
+ /* 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;
+
+ daemon->metrics[METRIC_DHCPDECLINE]++;
+ log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, 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);
+ inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"),
+ daemon->addrbuff, 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");
+
+ daemon->metrics[METRIC_DHCPRELEASE]++;
+ log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
+
+ return 0;
+
+ case DHCPDISCOVER:
+ if (ignore || have_config(config, CONFIG_DISABLE))
+ {
+ if (option_bool(OPT_QUIET_DHCP))
+ return 0;
+ 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))
+ {
+ inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN);
+
+ 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"),
+ daemon->addrbuff, 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"), daemon->addrbuff);
+ 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"), daemon->addrbuff);
+ 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) && do_icmp_ping(now, addr, 0, loopback))
+ mess->yiaddr = addr;
+ else if (emac_len == 0)
+ message = _("no unique-id");
+ else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now, loopback))
+ message = _("no address available");
+ }
+
+ daemon->metrics[METRIC_DHCPDISCOVER]++;
+ log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid);
+
+ if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid)))
+ return 0;
+
+ if (context->netid.net)
+ {
+ context->netid.next = netid;
+ tagif_netid = run_tag_if(&context->netid);
+ }
+
+ log_tags(tagif_netid, ntohl(mess->xid));
+ apply_delay(mess->xid, recvtime, tagif_netid);
+
+ if (option_bool(OPT_RAPID_COMMIT) && option_find(mess, sz, OPTION_RAPID_COMMIT, 0))
+ {
+ rapid_commit = 1;
+ goto rapid_commit;
+ }
+
+ daemon->metrics[METRIC_DHCPOFFER]++;
+ log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
+
+ time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
+ 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. */
+ do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr),
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+
+ return dhcp_packet_size(mess, 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)
+ {
+ /* Handle very strange configs where clients have more than one route to the server.
+ If a clients idea of its server-id matches any of our DHCP interfaces, we let it pass.
+ Have to set override to make sure we echo back the correct server-id */
+ struct irec *intr;
+
+ enumerate_interfaces(0);
+
+ for (intr = daemon->interfaces; intr; intr = intr->next)
+ if (intr->addr.sa.sa_family == AF_INET &&
+ intr->addr.in.sin_addr.s_addr == option_addr(opt).s_addr &&
+ intr->tftp_ok)
+ break;
+
+ if (intr)
+ override = intr->addr.in.sin_addr;
+ else
+ {
+ /* 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;
+ }
+
+ daemon->metrics[METRIC_DHCPREQUEST]++;
+ log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
+
+ rapid_commit:
+ 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))
+ {
+ inet_ntop(AF_INET, <mp->addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"),
+ print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len),
+ daemon->addrbuff);
+ lease = ltmp;
+ }
+ else
+ message = _("address in use");
+ }
+
+ if (!message)
+ {
+ if (emac_len == 0)
+ message = _("no unique-id");
+
+ else if (!lease)
+ {
+ if ((lease = lease4_allocate(mess->yiaddr)))
+ do_classes = 1;
+ else
+ message = _("no leases left");
+ }
+ }
+ }
+
+ if (message)
+ {
+ daemon->metrics[rapid_commit ? METRIC_NOANSWER : METRIC_DHCPNAK]++;
+ log_packet(rapid_commit ? "NOANSWER" : "DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
+
+ /* rapid commit case: lease allocate failed but don't send DHCPNAK */
+ if (rapid_commit)
+ return 0;
+
+ 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;
+ tagif_netid = run_tag_if( &context->netid);
+ }
+
+ log_tags(tagif_netid, ntohl(mess->xid));
+
+ if (do_classes)
+ {
+ /* pick up INIT-REBOOT events. */
+ lease->flags |= LEASE_CHANGED;
+
+#ifdef HAVE_SCRIPT
+ if (daemon->lease_change_command)
+ {
+ struct dhcp_netid *n;
+
+ if (mess->giaddr.s_addr)
+ lease->giaddr = mess->giaddr;
+
+ 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);
+
+ if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
+ {
+ add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_CIRCUIT_ID, 1));
+ add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBSCR_ID, 1));
+ add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_REMOTE_ID, 1));
+ }
+ else
+ {
+ add_extradata_opt(lease, NULL);
+ add_extradata_opt(lease, NULL);
+ add_extradata_opt(lease, NULL);
+ }
+
+ /* DNSMASQ_REQUESTED_OPTIONS */
+ if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 1)))
+ {
+ int len = option_len(opt);
+ unsigned char *rop = option_ptr(opt, 0);
+ char *q = daemon->namebuff;
+ int i;
+ for (i = 0; i < len; i++)
+ {
+ q += snprintf(q, MAXDNAME - (q - daemon->namebuff), "%d%s", rop[i], i + 1 == len ? "" : ",");
+ }
+ lease_add_extradata(lease, (unsigned char *)daemon->namebuff, (q - daemon->namebuff), 0);
+ }
+ else
+ {
+ add_extradata_opt(lease, NULL);
+ }
+
+ /* space-concat tag set */
+ if (!tagif_netid)
+ add_extradata_opt(lease, NULL);
+ else
+ for (n = tagif_netid; n; n = n->next)
+ {
+ struct dhcp_netid *n1;
+ /* kill dupes */
+ for (n1 = n->next; n1; n1 = n1->next)
+ if (strcmp(n->net, n1->net) == 0)
+ break;
+ if (!n1)
+ lease_add_extradata(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--;
+ lease_add_extradata(lease, ucp, len, -1);
+ }
+ }
+#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, now, do_classes);
+
+ /* 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, get_domain(lease->addr), domain);
+
+ lease_set_expires(lease, time, now);
+ lease_set_interface(lease, int_index, now);
+
+ if (override.s_addr != 0)
+ lease->override = override;
+ else
+ override = lease->override;
+
+ daemon->metrics[METRIC_DHCPACK]++;
+ log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);
+
+ 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 (rapid_commit)
+ option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
+ do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr),
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+ }
+
+ return dhcp_packet_size(mess, agent_id, real_end);
+
+ case DHCPINFORM:
+ if (ignore || have_config(config, CONFIG_DISABLE))
+ message = _("ignored");
+
+ daemon->metrics[METRIC_DHCPINFORM]++;
+ log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, 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);
+
+ if (context && context->netid.net)
+ {
+ context->netid.next = netid;
+ tagif_netid = run_tag_if(&context->netid);
+ }
+
+ log_tags(tagif_netid, ntohl(mess->xid));
+
+ daemon->metrics[METRIC_DHCPACK]++;
+ log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);
+
+ if (lease)
+ {
+ lease_set_interface(lease, int_index, now);
+ 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));
+
+ /* RFC 2131 says that DHCPINFORM shouldn't include lease-time parameters, but
+ we supply a utility which makes DHCPINFORM requests to get this information.
+ Only include lease time if OPTION_LEASE_TIME is in the parameter request list,
+ which won't be true for ordinary clients, but will be true for the
+ dhcp_lease_time utility. */
+ if (lease && in_list(req_options, OPTION_LEASE_TIME))
+ {
+ if (lease->expires == 0)
+ time = 0xffffffff;
+ else
+ time = (unsigned int)difftime(lease->expires, now);
+ option_put(mess, end, OPTION_LEASE_TIME, 4, time);
+ }
+
+ do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
+
+ *is_inform = 1; /* handle reply differently */
+ return dhcp_packet_size(mess, agent_id, real_end);
+ }
+
+ 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 && context->local.s_addr != 0)
+ 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_opt(struct dhcp_lease *lease, unsigned char *opt)
+{
+ if (!opt)
+ lease_add_extradata(lease, NULL, 0, 0);
+ else
+ lease_add_extradata(lease, option_ptr(opt, 0), option_len(opt), 0);
+}
+#endif
+
+static void log_packet(char *type, void *addr, unsigned char *ext_mac,
+ int mac_len, char *interface, char *string, char *err, u32 xid)
+{
+ if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
+ return;
+
+ daemon->addrbuff[0] = 0;
+ if (addr)
+ inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
+
+ 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%s",
+ ntohl(xid),
+ type,
+ interface,
+ daemon->addrbuff,
+ addr ? " " : "",
+ daemon->namebuff,
+ string ? string : "",
+ err ? err : "");
+ else
+ my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s",
+ type,
+ interface,
+ daemon->addrbuff,
+ addr ? " " : "",
+ daemon->namebuff,
+ string ? string : "",
+ err ? err : "");
+
+#ifdef HAVE_UBUS
+ if (!strcmp(type, "DHCPACK"))
+ ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
+ else if (!strcmp(type, "DHCPRELEASE"))
+ ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
+#endif
+}
+
+static void log_options(unsigned char *start, u32 xid)
+{
+ while (*start != OPTION_END)
+ {
+ char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME);
+
+ my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s %s",
+ ntohl(xid), option_len(start), start[0], optname, daemon->namebuff);
+ 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(unsigned char *opt)
+{
+ /* this worries about unaligned data in the option. */
+ /* struct in_addr is network byte order */
+ struct in_addr ret;
+
+ memcpy(&ret, option_ptr(opt, 0), INADDRSZ);
+
+ return ret;
+}
+
+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, 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;
+
+ /* 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 */
+ }
+
+ /* 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;
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ if (mess->siaddr.s_addr != 0)
+ {
+ inet_ntop(AF_INET, &mess->siaddr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), daemon->addrbuff);
+ }
+
+ 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->sname))
+ 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,
+ const 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
+ /* empty string may be extended to "\0" by null_term */
+ memcpy(p, opt->val ? opt->val : (unsigned char *)"", 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(int opt)
+{
+ struct dhcp_opt *opts;
+
+ for (opts = daemon->dhcp_opts; opts; opts = opts->next)
+ if (opts->opt == opt && (opts->flags & DHOPT_TAGOK))
+ return opts;
+
+ 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))
+ {
+ const struct dhcp_pxe_vendor *pv;
+ struct dhcp_pxe_vendor dummy_vendor = {
+ .data = (char *)dopt->u.vendor_class,
+ .next = NULL,
+ };
+ if (dopt->flags & DHOPT_VENDOR_PXE)
+ pv = daemon->dhcp_pxe_vendors;
+ else
+ pv = &dummy_vendor;
+ for (; pv; pv = pv->next)
+ {
+ int i, len = 0, matched = 0;
+ if (pv->data)
+ len = strlen(pv->data);
+ for (i = 0; i <= (option_len(opt) - len); i++)
+ if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0)
+ {
+ matched = 1;
+ break;
+ }
+ if (matched)
+ {
+ 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, const char *pxevendor)
+{
+ unsigned char *p;
+
+ if (!pxevendor)
+ pxevendor="PXEClient";
+ option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 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;
+}
+
+
+/* Many UEFI PXE implementations have badly broken menu code.
+ If there's exactly one relevant menu item, we abandon the menu system,
+ and jamb the data direct into the DHCP file, siaddr and sname fields.
+ Note that in this case, we have to assume that layer zero would be requested
+ by the client PXE stack. */
+static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe)
+{
+ struct pxe_service *service, *found;
+
+ /* Only workaround UEFI archs. */
+ if (pxe_arch < 6)
+ return 0;
+
+ for (found = NULL, service = daemon->pxe_services; service; service = service->next)
+ if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1))
+ {
+ if (found)
+ return 0; /* More than one relevant menu item */
+
+ found = service;
+ }
+
+ if (!found)
+ return 0; /* No relevant menu items. */
+
+ if (!pxe)
+ return 1;
+
+ if (found->sname)
+ {
+ mess->siaddr = a_record_from_hosts(found->sname, now);
+ snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname);
+ }
+ else
+ {
+ if (found->server.s_addr != 0)
+ mess->siaddr = found->server;
+ else
+ mess->siaddr = local;
+
+ inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN);
+ }
+
+ snprintf((char *)mess->file, sizeof(mess->file),
+ strchr(found->basename, '.') ? "%s" : "%s.0", found->basename);
+
+ return 1;
+}
+
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now)
+{
+#define NUM_OPTS 4
+
+ 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->sname ? a_record_from_hosts(service->sname, now) : 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 int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor)
+{
+ const unsigned char *opt = NULL;
+ ssize_t conf_len = 0;
+ const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors;
+ opt = option_find(mess, sz, OPTION_VENDOR_ID, 0);
+ if (!opt)
+ return 0;
+ for (; conf; conf = conf->next)
+ {
+ conf_len = strlen(conf->data);
+ if (option_len(opt) < conf_len)
+ continue;
+ if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0)
+ {
+ if (pxe_vendor)
+ *pxe_vendor = conf->data;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void do_options(struct dhcp_context *context,
+ struct dhcp_packet *mess,
+ unsigned char *end,
+ unsigned char *req_options,
+ char *hostname,
+ char *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,
+ time_t now,
+ unsigned int lease_time,
+ unsigned short fuzz,
+ const char *pxevendor)
+{
+ 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;
+ struct dhcp_netid *tagif;
+ struct dhcp_netid_list *id_list;
+
+ /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
+ if (context)
+ context->netid.next = NULL;
+ tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts);
+
+ /* 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(AF_INET, req_options[i], NULL, 0, NULL, 0);
+ q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
+ "%d%s%s%s",
+ req_options[i],
+ strlen(s) != 0 ? ":" : "",
+ 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);
+ }
+ }
+ }
+
+ 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 (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 a manual option to disable it.
+ Some PXE ROMs have bugs (surprise!) and need zero-terminated
+ names, so we always send those. */
+ if ((boot = find_boot(tagif)))
+ {
+ 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
+ safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
+ }
+
+ 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
+ safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+ }
+
+ if (boot->next_server.s_addr)
+ mess->siaddr = boot->next_server;
+ else if (boot->tftp_sname)
+ mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+ }
+ 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(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
+ {
+ safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
+ done_file = 1;
+ }
+
+ if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
+ (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
+ {
+ safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
+ done_server = 1;
+ }
+
+ if ((opt = option_find2(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));
+
+ if (lease_time != 0xffffffff)
+ {
+ unsigned int t1val = lease_time/2;
+ unsigned int t2val = (lease_time*7)/8;
+ unsigned int hval;
+
+ /* If set by user, sanity check, so not longer than lease. */
+ if ((opt = option_find2(OPTION_T1)))
+ {
+ hval = ntohl(*((unsigned int *)opt->val));
+ if (hval < lease_time && hval > 2)
+ t1val = hval;
+ }
+
+ if ((opt = option_find2(OPTION_T2)))
+ {
+ hval = ntohl(*((unsigned int *)opt->val));
+ if (hval < lease_time && hval > 2)
+ t2val = hval;
+ }
+
+ /* ensure T1 is still < T2 */
+ if (t2val <= t1val)
+ t1val = t2val - 1;
+
+ while (fuzz > (t1val/8))
+ fuzz = fuzz/2;
+
+ t1val -= fuzz;
+ t2val -= fuzz;
+
+ option_put(mess, end, OPTION_T1, 4, t1val);
+ option_put(mess, end, OPTION_T2, 4, t2val);
+ }
+
+ /* replies to DHCPINFORM may not have a valid context */
+ if (context)
+ {
+ if (!option_find2(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(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(OPTION_ROUTER))
+ option_put(mess, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
+
+ if (daemon->port == NAMESERVER_PORT &&
+ in_list(req_options, OPTION_DNSSERVER) &&
+ !option_find2(OPTION_DNSSERVER))
+ option_put(mess, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr));
+ }
+
+ if (domain && in_list(req_options, OPTION_DOMAINNAME) &&
+ !option_find2(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(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;
+ else if (fqdn_flags & 0x04)
+ len--;
+
+ if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len)))
+ {
+ *(p++) = fqdn_flags & 0x0f; /* MBZ bits to zero */
+ *(p++) = 255;
+ *(p++) = 255;
+
+ if (fqdn_flags & 0x04)
+ {
+ p = do_rfc1035_name(p, hostname, NULL);
+ if (domain)
+ {
+ p = do_rfc1035_name(p, domain, NULL);
+ *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;
+
+ /* netids match and not encapsulated? */
+ if (!(opt->flags & DHOPT_TAGOK))
+ continue;
+
+ /* 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. T1 and T2 already handled. */
+ if (optno == OPTION_CLIENT_FQDN ||
+ optno == OPTION_MAXMESSAGE ||
+ optno == OPTION_OVERLOAD ||
+ optno == OPTION_PAD ||
+ optno == OPTION_END ||
+ optno == OPTION_T1 ||
+ optno == OPTION_T2)
+ continue;
+
+ if (optno == OPTION_SNAME && done_server)
+ continue;
+
+ if (optno == OPTION_FILENAME && done_file)
+ 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 handle vendor-identifying vendor-encapsulated options,
+ dhcp-option = vi-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, tagif, 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(tagif);
+
+ if (context && pxe_arch != -1)
+ {
+ pxe_misc(mess, end, uuid, pxevendor);
+ if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
+ config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
+ }
+
+ if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&
+ 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;
+ }
+}
+
+static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid)
+{
+ struct delay_config *delay_conf;
+
+ /* Decide which delay_config option we're using */
+ for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next)
+ if (match_netid(delay_conf->netid, netid, 0))
+ break;
+
+ if (!delay_conf)
+ /* No match, look for one without a netid */
+ for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next)
+ if (match_netid(delay_conf->netid, netid, 1))
+ break;
+
+ if (delay_conf)
+ {
+ if (!option_bool(OPT_QUIET_DHCP))
+ my_syslog(MS_DHCP | LOG_INFO, _("%u reply delay: %d"), ntohl(xid), delay_conf->delay);
+ delay_dhcp(recvtime, delay_conf->delay, -1, 0, 0);
+ }
+}
+
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c
new file mode 100755
index 0000000..5c2ff97
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c
@@ -0,0 +1,2230 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP6
+
+struct state {
+ unsigned char *clid;
+ int clid_len, ia_type, interface, hostname_auth, lease_allocate;
+ char *client_hostname, *hostname, *domain, *send_domain;
+ struct dhcp_context *context;
+ struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
+ unsigned int xid, fqdn_flags, iaid;
+ char *iface_name;
+ void *packet_options, *end;
+ struct dhcp_netid *tags, *context_tags;
+ unsigned char mac[DHCP_CHADDR_MAX];
+ unsigned int mac_len, mac_type;
+};
+
+static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
+ struct in6_addr *client_addr, int is_unicast, time_t now);
+static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now);
+static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts);
+static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string);
+static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
+static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize);
+static void *opt6_next(void *opts, void *end);
+static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
+static void get_context_tag(struct state *state, struct dhcp_context *context);
+static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
+static int build_ia(struct state *state, int *t1cntr);
+static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz);
+static void mark_context_used(struct state *state, struct in6_addr *addr);
+static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr);
+static int check_address(struct state *state, struct in6_addr *addr);
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now);
+static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
+static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option,
+ unsigned int *min_time, struct in6_addr *addr, time_t now);
+static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now);
+static int add_local_addrs(struct dhcp_context *context);
+static struct dhcp_netid *add_options(struct state *state, int do_refresh);
+static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep,
+ unsigned int *preferred_timep, unsigned int lease_time);
+
+#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
+#define opt6_type(opt) (opt6_uint(opt, -4, 2))
+#define opt6_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[4+(i)]))
+
+#define opt6_user_vendor_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2+(i)]))
+#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
+#define opt6_user_vendor_next(opt, end) (opt6_next(((void *) opt) - 2, end))
+
+
+unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
+ struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+ size_t sz, struct in6_addr *client_addr, time_t now)
+{
+ struct dhcp_vendor *vendor;
+ int msg_type;
+ struct state state;
+
+ if (sz <= 4)
+ return 0;
+
+ msg_type = *((unsigned char *)daemon->dhcp_packet.iov_base);
+
+ /* Mark these so we only match each at most once, to avoid tangled linked lists */
+ for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+ vendor->netid.next = &vendor->netid;
+
+ reset_counter();
+ state.context = context;
+ state.interface = interface;
+ state.iface_name = iface_name;
+ state.fallback = fallback;
+ state.ll_addr = ll_addr;
+ state.ula_addr = ula_addr;
+ state.mac_len = 0;
+ state.tags = NULL;
+ state.link_address = NULL;
+
+ if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr,
+ IN6_IS_ADDR_MULTICAST(client_addr), now))
+ return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
+
+ return 0;
+}
+
+/* This cost me blood to write, it will probably cost you blood to understand - srk. */
+static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
+ struct in6_addr *client_addr, int is_unicast, time_t now)
+{
+ void *end = inbuff + sz;
+ void *opts = inbuff + 34;
+ int msg_type = *((unsigned char *)inbuff);
+ unsigned char *outmsgtypep;
+ void *opt;
+ struct dhcp_vendor *vendor;
+
+ /* if not an encapsulated relayed message, just do the stuff */
+ if (msg_type != DHCP6RELAYFORW)
+ {
+ /* if link_address != NULL if points to the link address field of the
+ innermost nested RELAYFORW message, which is where we find the
+ address of the network on which we can allocate an address.
+ Recalculate the available contexts using that information.
+
+ link_address == NULL means there's no relay in use, so we try and find the client's
+ MAC address from the local ND cache. */
+
+ if (!state->link_address)
+ get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now);
+ else
+ {
+ struct dhcp_context *c;
+ struct shared_network *share = NULL;
+ state->context = NULL;
+
+ if (!IN6_IS_ADDR_LOOPBACK(state->link_address) &&
+ !IN6_IS_ADDR_LINKLOCAL(state->link_address) &&
+ !IN6_IS_ADDR_MULTICAST(state->link_address))
+ for (c = daemon->dhcp6; c; c = c->next)
+ {
+ for (share = daemon->shared_networks; share; share = share->next)
+ {
+ if (share->shared_addr.s_addr != 0)
+ continue;
+
+ if (share->if_index != 0 ||
+ !IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6))
+ continue;
+
+ if ((c->flags & CONTEXT_DHCP) &&
+ !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ is_same_net6(&share->shared_addr6, &c->start6, c->prefix) &&
+ is_same_net6(&share->shared_addr6, &c->end6, c->prefix))
+ break;
+ }
+
+ if (share ||
+ ((c->flags & CONTEXT_DHCP) &&
+ !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ is_same_net6(state->link_address, &c->start6, c->prefix) &&
+ is_same_net6(state->link_address, &c->end6, c->prefix)))
+ {
+ c->preferred = c->valid = 0xffffffff;
+ c->current = state->context;
+ state->context = c;
+ }
+ }
+
+ if (!state->context)
+ {
+ inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("no address range available for DHCPv6 request from relay at %s"),
+ daemon->addrbuff);
+ return 0;
+ }
+ }
+
+ if (!state->context)
+ {
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("no address range available for DHCPv6 request via %s"), state->iface_name);
+ return 0;
+ }
+
+ return dhcp6_no_relay(state, msg_type, inbuff, sz, is_unicast, now);
+ }
+
+ /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
+ which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */
+ if (sz < 38)
+ return 0;
+
+ /* copy header stuff into reply message and set type to reply */
+ if (!(outmsgtypep = put_opt6(inbuff, 34)))
+ return 0;
+ *outmsgtypep = DHCP6RELAYREPL;
+
+ /* look for relay options and set tags if found. */
+ for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+ {
+ int mopt;
+
+ if (vendor->match_type == MATCH_SUBSCRIBER)
+ mopt = OPTION6_SUBSCRIBER_ID;
+ else if (vendor->match_type == MATCH_REMOTE)
+ mopt = OPTION6_REMOTE_ID;
+ else
+ continue;
+
+ if ((opt = opt6_find(opts, end, mopt, 1)) &&
+ vendor->len == opt6_len(opt) &&
+ memcmp(vendor->data, opt6_ptr(opt, 0), vendor->len) == 0 &&
+ vendor->netid.next != &vendor->netid)
+ {
+ vendor->netid.next = state->tags;
+ state->tags = &vendor->netid;
+ break;
+ }
+ }
+
+ /* RFC-6939 */
+ if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))
+ {
+ if (opt6_len(opt) - 2 > DHCP_CHADDR_MAX) {
+ return 0;
+ }
+ state->mac_type = opt6_uint(opt, 0, 2);
+ state->mac_len = opt6_len(opt) - 2;
+ memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
+ }
+
+ for (opt = opts; opt; opt = opt6_next(opt, end))
+ {
+ if (opt6_ptr(opt, 0) + opt6_len(opt) > end)
+ return 0;
+
+ /* Don't copy MAC address into reply. */
+ if (opt6_type(opt) != OPTION6_CLIENT_MAC)
+ {
+ int o = new_opt6(opt6_type(opt));
+ if (opt6_type(opt) == OPTION6_RELAY_MSG)
+ {
+ struct in6_addr align;
+ /* the packet data is unaligned, copy to aligned storage */
+ memcpy(&align, inbuff + 2, IN6ADDRSZ);
+ state->link_address = &align;
+ /* zero is_unicast since that is now known to refer to the
+ relayed packet, not the original sent by the client */
+ if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now))
+ return 0;
+ }
+ else
+ put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
+ end_opt6(o);
+ }
+ }
+
+ return 1;
+}
+
+static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now)
+{
+ void *opt;
+ int i, o, o1, start_opts;
+ struct dhcp_opt *opt_cfg;
+ struct dhcp_netid *tagif;
+ struct dhcp_config *config = NULL;
+ struct dhcp_netid known_id, iface_id, v6_id;
+ unsigned char *outmsgtypep;
+ struct dhcp_vendor *vendor;
+ struct dhcp_context *context_tmp;
+ struct dhcp_mac *mac_opt;
+ unsigned int ignore = 0;
+
+ state->packet_options = inbuff + 4;
+ state->end = inbuff + sz;
+ state->clid = NULL;
+ state->clid_len = 0;
+ state->lease_allocate = 0;
+ state->context_tags = NULL;
+ state->domain = NULL;
+ state->send_domain = NULL;
+ state->hostname_auth = 0;
+ state->hostname = NULL;
+ state->client_hostname = NULL;
+ state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */
+
+ /* set tag with name == interface */
+ iface_id.net = state->iface_name;
+ iface_id.next = state->tags;
+ state->tags = &iface_id;
+
+ /* set tag "dhcpv6" */
+ v6_id.net = "dhcpv6";
+ v6_id.next = state->tags;
+ state->tags = &v6_id;
+
+ /* copy over transaction-id, and save pointer to message type */
+ if (!(outmsgtypep = put_opt6(inbuff, 4)))
+ return 0;
+ start_opts = save_counter(-1);
+ state->xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16;
+
+ /* We're going to be linking tags from all context we use.
+ mark them as unused so we don't link one twice and break the list */
+ for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current)
+ {
+ context_tmp->netid.next = &context_tmp->netid;
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ inet_ntop(AF_INET6, &context_tmp->start6, daemon->dhcp_buff, ADDRSTRLEN);
+ inet_ntop(AF_INET6, &context_tmp->end6, daemon->dhcp_buff2, ADDRSTRLEN);
+ if (context_tmp->flags & (CONTEXT_STATIC))
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCPv6 subnet: %s/%d"),
+ state->xid, daemon->dhcp_buff, context_tmp->prefix);
+ else
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
+ state->xid, daemon->dhcp_buff, daemon->dhcp_buff2);
+ }
+ }
+
+ if ((opt = opt6_find(state->packet_options, state->end, OPTION6_CLIENT_ID, 1)))
+ {
+ state->clid = opt6_ptr(opt, 0);
+ state->clid_len = opt6_len(opt);
+ o = new_opt6(OPTION6_CLIENT_ID);
+ put_opt6(state->clid, state->clid_len);
+ end_opt6(o);
+ }
+ else if (msg_type != DHCP6IREQ)
+ return 0;
+
+ /* server-id must match except for SOLICIT, CONFIRM and REBIND messages */
+ if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ && msg_type != DHCP6REBIND &&
+ (!(opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1)) ||
+ opt6_len(opt) != daemon->duid_len ||
+ memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
+ return 0;
+
+ o = new_opt6(OPTION6_SERVER_ID);
+ put_opt6(daemon->duid, daemon->duid_len);
+ end_opt6(o);
+
+ if (is_unicast &&
+ (msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE))
+
+ {
+ *outmsgtypep = DHCP6REPLY;
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6USEMULTI);
+ put_opt6_string("Use multicast");
+ end_opt6(o1);
+ return 1;
+ }
+
+ /* match vendor and user class options */
+ for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+ {
+ int mopt;
+
+ if (vendor->match_type == MATCH_VENDOR)
+ mopt = OPTION6_VENDOR_CLASS;
+ else if (vendor->match_type == MATCH_USER)
+ mopt = OPTION6_USER_CLASS;
+ else
+ continue;
+
+ if ((opt = opt6_find(state->packet_options, state->end, mopt, 2)))
+ {
+ void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
+ int offset = 0;
+
+ if (mopt == OPTION6_VENDOR_CLASS)
+ {
+ if (opt6_len(opt) < 4)
+ continue;
+
+ if (vendor->enterprise != opt6_uint(opt, 0, 4))
+ continue;
+
+ offset = 4;
+ }
+
+ /* Note that format if user/vendor classes is different to DHCP options - no option types. */
+ for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
+ for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
+ if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
+ {
+ vendor->netid.next = state->tags;
+ state->tags = &vendor->netid;
+ break;
+ }
+ }
+ }
+
+ if (option_bool(OPT_LOG_OPTS) && (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4)))
+ my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %u"), state->xid, opt6_uint(opt, 0, 4));
+
+ /* 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, existence of the option is enough. This code handles
+ V-I opts too. */
+ for (opt_cfg = daemon->dhcp_match6; opt_cfg; opt_cfg = opt_cfg->next)
+ {
+ int match = 0;
+
+ if (opt_cfg->flags & DHOPT_RFC3925)
+ {
+ for (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_OPTS, 4);
+ opt;
+ opt = opt6_find(opt6_next(opt, state->end), state->end, OPTION6_VENDOR_OPTS, 4))
+ {
+ void *vopt;
+ void *vend = opt6_ptr(opt, opt6_len(opt));
+
+ for (vopt = opt6_find(opt6_ptr(opt, 4), vend, opt_cfg->opt, 0);
+ vopt;
+ vopt = opt6_find(opt6_next(vopt, vend), vend, opt_cfg->opt, 0))
+ if ((match = match_bytes(opt_cfg, opt6_ptr(vopt, 0), opt6_len(vopt))))
+ break;
+ }
+ if (match)
+ break;
+ }
+ else
+ {
+ if (!(opt = opt6_find(state->packet_options, state->end, opt_cfg->opt, 1)))
+ continue;
+
+ match = match_bytes(opt_cfg, opt6_ptr(opt, 0), opt6_len(opt));
+ }
+
+ if (match)
+ {
+ opt_cfg->netid->next = state->tags;
+ state->tags = opt_cfg->netid;
+ }
+ }
+
+ if (state->mac_len != 0)
+ {
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ print_mac(daemon->dhcp_buff, state->mac, state->mac_len);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid, daemon->dhcp_buff);
+ }
+
+ for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next)
+ if ((unsigned)mac_opt->hwaddr_len == state->mac_len &&
+ ((unsigned)mac_opt->hwaddr_type == state->mac_type || mac_opt->hwaddr_type == 0) &&
+ memcmp_masked(mac_opt->hwaddr, state->mac, state->mac_len, mac_opt->mask))
+ {
+ mac_opt->netid.next = state->tags;
+ state->tags = &mac_opt->netid;
+ }
+ }
+
+ if ((opt = opt6_find(state->packet_options, state->end, OPTION6_FQDN, 1)))
+ {
+ /* RFC4704 refers */
+ int len = opt6_len(opt) - 1;
+
+ state->fqdn_flags = opt6_uint(opt, 0, 1);
+
+ /* Always force update, since the client has no way to do it itself. */
+ if (!option_bool(OPT_FQDN_UPDATE) && !(state->fqdn_flags & 0x01))
+ state->fqdn_flags |= 0x03;
+
+ state->fqdn_flags &= ~0x04;
+
+ if (len != 0 && len < 255)
+ {
+ unsigned char *pp, *op = opt6_ptr(opt, 1);
+ char *pq = daemon->dhcp_buff;
+
+ pp = op;
+ while (*op != 0 && ((op + (*op)) - pp) < len)
+ {
+ memcpy(pq, op+1, *op);
+ pq += *op;
+ op += (*op)+1;
+ *(pq++) = '.';
+ }
+
+ if (pq != daemon->dhcp_buff)
+ pq--;
+ *pq = 0;
+
+ if (legal_hostname(daemon->dhcp_buff))
+ {
+ struct dhcp_match_name *m;
+ size_t nl = strlen(daemon->dhcp_buff);
+
+ state->client_hostname = daemon->dhcp_buff;
+
+ if (option_bool(OPT_LOG_OPTS))
+ my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), state->xid, state->client_hostname);
+
+ for (m = daemon->dhcp_name_match; m; m = m->next)
+ {
+ size_t ml = strlen(m->name);
+ char save = 0;
+
+ if (nl < ml)
+ continue;
+ if (nl > ml)
+ {
+ save = state->client_hostname[ml];
+ state->client_hostname[ml] = 0;
+ }
+
+ if (hostname_isequal(state->client_hostname, m->name) &&
+ (save == 0 || m->wildcard))
+ {
+ m->netid->next = state->tags;
+ state->tags = m->netid;
+ }
+
+ if (save != 0)
+ state->client_hostname[ml] = save;
+ }
+ }
+ }
+ }
+
+ if (state->clid &&
+ (config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len,
+ state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags))) &&
+ have_config(config, CONFIG_NAME))
+ {
+ state->hostname = config->hostname;
+ state->domain = config->domain;
+ state->hostname_auth = 1;
+ }
+ else if (state->client_hostname)
+ {
+ state->domain = strip_hostname(state->client_hostname);
+
+ if (strlen(state->client_hostname) != 0)
+ {
+ state->hostname = state->client_hostname;
+
+ if (!config)
+ {
+ /* Search again now we have a hostname.
+ Only accept configs without CLID here, (it won't match)
+ to avoid impersonation by name. */
+ struct dhcp_config *new = find_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname, run_tag_if(state->tags));
+ if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr)
+ config = new;
+ }
+ }
+ }
+
+ if (config)
+ {
+ struct dhcp_netid_list *list;
+
+ for (list = config->netid; list; list = list->next)
+ {
+ list->list->next = state->tags;
+ state->tags = list->list;
+ }
+
+ /* set "known" tag for known hosts */
+ known_id.net = "known";
+ known_id.next = state->tags;
+ state->tags = &known_id;
+
+ if (have_config(config, CONFIG_DISABLE))
+ ignore = 1;
+ }
+ else if (state->clid &&
+ find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len,
+ state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags)))
+ {
+ known_id.net = "known-othernet";
+ known_id.next = state->tags;
+ state->tags = &known_id;
+ }
+
+ tagif = run_tag_if(state->tags);
+
+ /* if all the netids in the ignore list are present, ignore this client */
+ if (daemon->dhcp_ignore)
+ {
+ struct dhcp_netid_list *id_list;
+
+ for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
+ if (match_netid(id_list->list, tagif, 0))
+ ignore = 1;
+ }
+
+ /* if all the netids in the ignore_name list are present, ignore client-supplied name */
+ if (!state->hostname_auth)
+ {
+ struct dhcp_netid_list *id_list;
+
+ for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+ if ((!id_list->list) || match_netid(id_list->list, tagif, 0))
+ break;
+ if (id_list)
+ state->hostname = NULL;
+ }
+
+
+ switch (msg_type)
+ {
+ default:
+ return 0;
+
+
+ case DHCP6SOLICIT:
+ {
+ int address_assigned = 0;
+ /* tags without all prefix-class tags */
+ struct dhcp_netid *solicit_tags;
+ struct dhcp_context *c;
+
+ *outmsgtypep = DHCP6ADVERTISE;
+
+ if (opt6_find(state->packet_options, state->end, OPTION6_RAPID_COMMIT, 0))
+ {
+ *outmsgtypep = DHCP6REPLY;
+ state->lease_allocate = 1;
+ o = new_opt6(OPTION6_RAPID_COMMIT);
+ end_opt6(o);
+ }
+
+ log6_quiet(state, "DHCPSOLICIT", NULL, ignore ? _("ignored") : NULL);
+
+ request_no_address:
+ solicit_tags = tagif;
+
+ if (ignore)
+ return 0;
+
+ /* reset USED bits in leases */
+ lease6_reset();
+
+ /* Can use configured address max once per prefix */
+ for (c = state->context; c; c = c->current)
+ c->flags &= ~CONTEXT_CONF_USED;
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+ unsigned int min_time = 0xffffffff;
+ int t1cntr;
+ int ia_counter;
+ /* set unless we're sending a particular prefix-class, when we
+ want only dhcp-ranges with the correct tags set and not those without any tags. */
+ int plain_range = 1;
+ u32 lease_time;
+ struct dhcp_lease *ltmp;
+ struct in6_addr req_addr, addr;
+
+ if (!check_ia(state, opt, &ia_end, &ia_option))
+ continue;
+
+ /* reset USED bits in contexts - one address per prefix per IAID */
+ for (c = state->context; c; c = c->current)
+ c->flags &= ~CONTEXT_USED;
+
+ o = build_ia(state, &t1cntr);
+ if (address_assigned)
+ address_assigned = 2;
+
+ for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ /* worry about alignment here. */
+ memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+ if ((c = address6_valid(state->context, &req_addr, solicit_tags, plain_range)))
+ {
+ lease_time = c->lease_time;
+ /* If the client asks for an address on the same network as a configured address,
+ offer the configured address instead, to make moving to newly-configured
+ addresses automatic. */
+ if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state, now))
+ {
+ req_addr = addr;
+ mark_config_used(c, &addr);
+ if (have_config(config, CONFIG_TIME))
+ lease_time = config->lease_time;
+ }
+ else if (!(c = address6_available(state->context, &req_addr, solicit_tags, plain_range)))
+ continue; /* not an address we're allowed */
+ else if (!check_address(state, &req_addr))
+ continue; /* address leased elsewhere */
+
+ /* add address to output packet */
+ add_address(state, c, lease_time, ia_option, &min_time, &req_addr, now);
+ mark_context_used(state, &req_addr);
+ get_context_tag(state, c);
+ address_assigned = 1;
+ }
+ }
+
+ /* Suggest configured address(es) */
+ for (c = state->context; c; c = c->current)
+ if (!(c->flags & CONTEXT_CONF_USED) &&
+ match_netid(c->filter, solicit_tags, plain_range) &&
+ config_valid(config, c, &addr, state, now))
+ {
+ mark_config_used(state->context, &addr);
+ if (have_config(config, CONFIG_TIME))
+ lease_time = config->lease_time;
+ else
+ lease_time = c->lease_time;
+
+ /* add address to output packet */
+ add_address(state, c, lease_time, NULL, &min_time, &addr, now);
+ mark_context_used(state, &addr);
+ get_context_tag(state, c);
+ address_assigned = 1;
+ }
+
+ /* return addresses for existing leases */
+ ltmp = NULL;
+ while ((ltmp = lease6_find_by_client(ltmp, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state->clid, state->clid_len, state->iaid)))
+ {
+ req_addr = ltmp->addr6;
+ if ((c = address6_available(state->context, &req_addr, solicit_tags, plain_range)))
+ {
+ add_address(state, c, c->lease_time, NULL, &min_time, &req_addr, now);
+ mark_context_used(state, &req_addr);
+ get_context_tag(state, c);
+ address_assigned = 1;
+ }
+ }
+
+ /* Return addresses for all valid contexts which don't yet have one */
+ while ((c = address6_allocate(state->context, state->clid, state->clid_len, state->ia_type == OPTION6_IA_TA,
+ state->iaid, ia_counter, solicit_tags, plain_range, &addr)))
+ {
+ add_address(state, c, c->lease_time, NULL, &min_time, &addr, now);
+ mark_context_used(state, &addr);
+ get_context_tag(state, c);
+ address_assigned = 1;
+ }
+
+ if (address_assigned != 1)
+ {
+ /* If the server will not assign any addresses to any IAs in a
+ subsequent Request from the client, the server MUST send an Advertise
+ message to the client that doesn't include any IA options. */
+ if (!state->lease_allocate)
+ {
+ save_counter(o);
+ continue;
+ }
+
+ /* If the server cannot assign any addresses to an IA in the message
+ from the client, the server MUST include the IA in the Reply message
+ with no addresses in the IA and a Status Code option in the IA
+ containing status code NoAddrsAvail. */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOADDRS);
+ put_opt6_string(_("address unavailable"));
+ end_opt6(o1);
+ }
+
+ end_ia(t1cntr, min_time, 0);
+ end_opt6(o);
+ }
+
+ if (address_assigned)
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("success"));
+ end_opt6(o1);
+
+ /* If --dhcp-authoritative is set, we can tell client not to wait for
+ other possible servers */
+ o = new_opt6(OPTION6_PREFERENCE);
+ put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0);
+ end_opt6(o);
+ tagif = add_options(state, 0);
+ }
+ else
+ {
+ /* no address, return error */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOADDRS);
+ put_opt6_string(_("no addresses available"));
+ end_opt6(o1);
+
+ /* Some clients will ask repeatedly when we're not giving
+ out addresses because we're in stateless mode. Avoid spamming
+ the log in that case. */
+ for (c = state->context; c; c = c->current)
+ if (!(c->flags & CONTEXT_RA_STATELESS))
+ {
+ log6_packet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", NULL, _("no addresses available"));
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case DHCP6REQUEST:
+ {
+ int address_assigned = 0;
+ int start = save_counter(-1);
+
+ /* set reply message type */
+ *outmsgtypep = DHCP6REPLY;
+ state->lease_allocate = 1;
+
+ log6_quiet(state, "DHCPREQUEST", NULL, ignore ? _("ignored") : NULL);
+
+ if (ignore)
+ return 0;
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+ unsigned int min_time = 0xffffffff;
+ int t1cntr;
+
+ if (!check_ia(state, opt, &ia_end, &ia_option))
+ continue;
+
+ if (!ia_option)
+ {
+ /* If we get a request with an IA_*A without addresses, treat it exactly like
+ a SOLICT with rapid commit set. */
+ save_counter(start);
+ goto request_no_address;
+ }
+
+ o = build_ia(state, &t1cntr);
+
+ for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ struct in6_addr req_addr;
+ struct dhcp_context *dynamic, *c;
+ unsigned int lease_time;
+ int config_ok = 0;
+
+ /* align. */
+ memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+ if ((c = address6_valid(state->context, &req_addr, tagif, 1)))
+ config_ok = (config_implies(config, c, &req_addr) != NULL);
+
+ if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c)
+ {
+ if (!dynamic && !config_ok)
+ {
+ /* Static range, not configured. */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOADDRS);
+ put_opt6_string(_("address unavailable"));
+ end_opt6(o1);
+ }
+ else if (!check_address(state, &req_addr))
+ {
+ /* Address leased to another DUID/IAID */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6UNSPEC);
+ put_opt6_string(_("address in use"));
+ end_opt6(o1);
+ }
+ else
+ {
+ if (!dynamic)
+ dynamic = c;
+
+ lease_time = dynamic->lease_time;
+
+ if (config_ok && have_config(config, CONFIG_TIME))
+ lease_time = config->lease_time;
+
+ add_address(state, dynamic, lease_time, ia_option, &min_time, &req_addr, now);
+ get_context_tag(state, dynamic);
+ address_assigned = 1;
+ }
+ }
+ else
+ {
+ /* requested address not on the correct link */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOTONLINK);
+ put_opt6_string(_("not on link"));
+ end_opt6(o1);
+ }
+ }
+
+ end_ia(t1cntr, min_time, 0);
+ end_opt6(o);
+ }
+
+ if (address_assigned)
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("success"));
+ end_opt6(o1);
+ }
+ else
+ {
+ /* no address, return error */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOADDRS);
+ put_opt6_string(_("no addresses available"));
+ end_opt6(o1);
+ log6_packet(state, "DHCPREPLY", NULL, _("no addresses available"));
+ }
+
+ tagif = add_options(state, 0);
+ break;
+ }
+
+
+ case DHCP6RENEW:
+ case DHCP6REBIND:
+ {
+ int address_assigned = 0;
+
+ /* set reply message type */
+ *outmsgtypep = DHCP6REPLY;
+
+ log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+ unsigned int min_time = 0xffffffff;
+ int t1cntr, iacntr;
+
+ if (!check_ia(state, opt, &ia_end, &ia_option))
+ continue;
+
+ o = build_ia(state, &t1cntr);
+ iacntr = save_counter(-1);
+
+ for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ struct dhcp_lease *lease = NULL;
+ struct in6_addr req_addr;
+ unsigned int preferred_time = opt6_uint(ia_option, 16, 4);
+ unsigned int valid_time = opt6_uint(ia_option, 20, 4);
+ char *message = NULL;
+ struct dhcp_context *this_context;
+
+ memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+ if (!(lease = lease6_find(state->clid, state->clid_len,
+ state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA,
+ state->iaid, &req_addr)))
+ {
+ if (msg_type == DHCP6REBIND)
+ {
+ /* When rebinding, we can create a lease if it doesn't exist. */
+ lease = lease6_allocate(&req_addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
+ if (lease)
+ lease_set_iaid(lease, state->iaid);
+ else
+ break;
+ }
+ else
+ {
+ /* If the server cannot find a client entry for the IA the server
+ returns the IA containing no addresses with a Status Code option set
+ to NoBinding in the Reply message. */
+ save_counter(iacntr);
+ t1cntr = 0;
+
+ log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found"));
+
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOBINDING);
+ put_opt6_string(_("no binding found"));
+ end_opt6(o1);
+
+ preferred_time = valid_time = 0;
+ break;
+ }
+ }
+
+ if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) ||
+ (this_context = address6_valid(state->context, &req_addr, tagif, 1)))
+ {
+ unsigned int lease_time;
+
+ get_context_tag(state, this_context);
+
+ if (config_implies(config, this_context, &req_addr) && have_config(config, CONFIG_TIME))
+ lease_time = config->lease_time;
+ else
+ lease_time = this_context->lease_time;
+
+ calculate_times(this_context, &min_time, &valid_time, &preferred_time, lease_time);
+
+ lease_set_expires(lease, valid_time, now);
+ /* Update MAC record in case it's new information. */
+ if (state->mac_len != 0)
+ lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0);
+ if (state->ia_type == OPTION6_IA_NA && state->hostname)
+ {
+ char *addr_domain = get_domain6(&req_addr);
+ if (!state->send_domain)
+ state->send_domain = addr_domain;
+ lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain);
+ message = state->hostname;
+ }
+
+
+ if (preferred_time == 0)
+ message = _("deprecated");
+
+ address_assigned = 1;
+ }
+ else
+ {
+ preferred_time = valid_time = 0;
+ message = _("address invalid");
+ }
+
+ if (message && (message != state->hostname))
+ log6_packet(state, "DHCPREPLY", &req_addr, message);
+ else
+ log6_quiet(state, "DHCPREPLY", &req_addr, message);
+
+ o1 = new_opt6(OPTION6_IAADDR);
+ put_opt6(&req_addr, sizeof(req_addr));
+ put_opt6_long(preferred_time);
+ put_opt6_long(valid_time);
+ end_opt6(o1);
+ }
+
+ end_ia(t1cntr, min_time, 1);
+ end_opt6(o);
+ }
+
+ if (!address_assigned && msg_type == DHCP6REBIND)
+ {
+ /* can't create lease for any address, return error */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOADDRS);
+ put_opt6_string(_("no addresses available"));
+ end_opt6(o1);
+ }
+
+ tagif = add_options(state, 0);
+ break;
+ }
+
+ case DHCP6CONFIRM:
+ {
+ int good_addr = 0;
+
+ /* set reply message type */
+ *outmsgtypep = DHCP6REPLY;
+
+ log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+
+ for (check_ia(state, opt, &ia_end, &ia_option);
+ ia_option;
+ ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ struct in6_addr req_addr;
+
+ /* alignment */
+ memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+ if (!address6_valid(state->context, &req_addr, tagif, 1))
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOTONLINK);
+ put_opt6_string(_("confirm failed"));
+ end_opt6(o1);
+ log6_quiet(state, "DHCPREPLY", &req_addr, _("confirm failed"));
+ return 1;
+ }
+
+ good_addr = 1;
+ log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname);
+ }
+ }
+
+ /* No addresses, no reply: RFC 3315 18.2.2 */
+ if (!good_addr)
+ return 0;
+
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS );
+ put_opt6_string(_("all addresses still on link"));
+ end_opt6(o1);
+ break;
+ }
+
+ case DHCP6IREQ:
+ {
+ /* We can't discriminate contexts based on address, as we don't know it.
+ If there is only one possible context, we can use its tags */
+ if (state->context && state->context->netid.net && !state->context->current)
+ {
+ state->context->netid.next = NULL;
+ state->context_tags = &state->context->netid;
+ }
+
+ /* Similarly, we can't determine domain from address, but if the FQDN is
+ given in --dhcp-host, we can use that, and failing that we can use the
+ unqualified configured domain, if any. */
+ if (state->hostname_auth)
+ state->send_domain = state->domain;
+ else
+ state->send_domain = get_domain6(NULL);
+
+ log6_quiet(state, "DHCPINFORMATION-REQUEST", NULL, ignore ? _("ignored") : state->hostname);
+ if (ignore)
+ return 0;
+ *outmsgtypep = DHCP6REPLY;
+ tagif = add_options(state, 1);
+ break;
+ }
+
+
+ case DHCP6RELEASE:
+ {
+ /* set reply message type */
+ *outmsgtypep = DHCP6REPLY;
+
+ log6_quiet(state, "DHCPRELEASE", NULL, NULL);
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+ int made_ia = 0;
+
+ for (check_ia(state, opt, &ia_end, &ia_option);
+ ia_option;
+ ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ struct dhcp_lease *lease;
+ struct in6_addr addr;
+
+ /* align */
+ memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+ if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA,
+ state->iaid, &addr)))
+ lease_prune(lease, now);
+ else
+ {
+ if (!made_ia)
+ {
+ o = new_opt6(state->ia_type);
+ put_opt6_long(state->iaid);
+ if (state->ia_type == OPTION6_IA_NA)
+ {
+ put_opt6_long(0);
+ put_opt6_long(0);
+ }
+ made_ia = 1;
+ }
+
+ o1 = new_opt6(OPTION6_IAADDR);
+ put_opt6(&addr, IN6ADDRSZ);
+ put_opt6_long(0);
+ put_opt6_long(0);
+ end_opt6(o1);
+ }
+ }
+
+ if (made_ia)
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOBINDING);
+ put_opt6_string(_("no binding found"));
+ end_opt6(o1);
+
+ end_opt6(o);
+ }
+ }
+
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("release received"));
+ end_opt6(o1);
+
+ break;
+ }
+
+ case DHCP6DECLINE:
+ {
+ /* set reply message type */
+ *outmsgtypep = DHCP6REPLY;
+
+ log6_quiet(state, "DHCPDECLINE", NULL, NULL);
+
+ for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+ {
+ void *ia_option, *ia_end;
+ int made_ia = 0;
+
+ for (check_ia(state, opt, &ia_end, &ia_option);
+ ia_option;
+ ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+ {
+ struct dhcp_lease *lease;
+ struct in6_addr addr;
+ struct addrlist *addr_list;
+
+ /* align */
+ memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+ if ((addr_list = config_implies(config, state->context, &addr)))
+ {
+ prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF);
+ inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"),
+ daemon->addrbuff, daemon->dhcp_buff3);
+ addr_list->flags |= ADDRLIST_DECLINED;
+ addr_list->decline_time = now;
+ }
+ else
+ /* make sure this host gets a different address next time. */
+ for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current)
+ context_tmp->addr_epoch++;
+
+ if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA,
+ state->iaid, &addr)))
+ lease_prune(lease, now);
+ else
+ {
+ if (!made_ia)
+ {
+ o = new_opt6(state->ia_type);
+ put_opt6_long(state->iaid);
+ if (state->ia_type == OPTION6_IA_NA)
+ {
+ put_opt6_long(0);
+ put_opt6_long(0);
+ }
+ made_ia = 1;
+ }
+
+ o1 = new_opt6(OPTION6_IAADDR);
+ put_opt6(&addr, IN6ADDRSZ);
+ put_opt6_long(0);
+ put_opt6_long(0);
+ end_opt6(o1);
+ }
+ }
+
+ if (made_ia)
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOBINDING);
+ put_opt6_string(_("no binding found"));
+ end_opt6(o1);
+
+ end_opt6(o);
+ }
+
+ }
+
+ /* We must answer with 'success' in global section anyway */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("success"));
+ end_opt6(o1);
+ break;
+ }
+
+ }
+
+ log_tags(tagif, state->xid);
+ log6_opts(0, state->xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1));
+
+ return 1;
+
+}
+
+static struct dhcp_netid *add_options(struct state *state, int do_refresh)
+{
+ void *oro;
+ /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
+ struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6);
+ struct dhcp_opt *opt_cfg;
+ int done_dns = 0, done_refresh = !do_refresh, do_encap = 0;
+ int i, o, o1;
+
+ oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0);
+
+ for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+ {
+ /* netids match and not encapsulated? */
+ if (!(opt_cfg->flags & DHOPT_TAGOK))
+ continue;
+
+ if (!(opt_cfg->flags & DHOPT_FORCE) && oro)
+ {
+ for (i = 0; i < opt6_len(oro) - 1; i += 2)
+ if (opt6_uint(oro, i, 2) == (unsigned)opt_cfg->opt)
+ break;
+
+ /* option not requested */
+ if (i >= opt6_len(oro) - 1)
+ continue;
+ }
+
+ if (opt_cfg->opt == OPTION6_REFRESH_TIME)
+ done_refresh = 1;
+
+ if (opt_cfg->opt == OPTION6_DNS_SERVER)
+ done_dns = 1;
+
+ if (opt_cfg->flags & DHOPT_ADDR6)
+ {
+ int len, j;
+ struct in6_addr *a;
+
+ for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0;
+ j < opt_cfg->len; j += IN6ADDRSZ, a++)
+ if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) ||
+ (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)))
+ len -= IN6ADDRSZ;
+
+ if (len != 0)
+ {
+
+ o = new_opt6(opt_cfg->opt);
+
+ for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
+ {
+ struct in6_addr *p = NULL;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(a))
+ {
+ if (!add_local_addrs(state->context))
+ p = state->fallback;
+ }
+ else if (IN6_IS_ADDR_ULA_ZERO(a))
+ {
+ if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr))
+ p = state->ula_addr;
+ }
+ else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+ {
+ if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))
+ p = state->ll_addr;
+ }
+ else
+ p = a;
+
+ if (!p)
+ continue;
+ else if (opt_cfg->opt == OPTION6_NTP_SERVER)
+ {
+ if (IN6_IS_ADDR_MULTICAST(p))
+ o1 = new_opt6(NTP_SUBOPTION_MC_ADDR);
+ else
+ o1 = new_opt6(NTP_SUBOPTION_SRV_ADDR);
+ put_opt6(p, IN6ADDRSZ);
+ end_opt6(o1);
+ }
+ else
+ put_opt6(p, IN6ADDRSZ);
+ }
+
+ end_opt6(o);
+ }
+ }
+ else
+ {
+ o = new_opt6(opt_cfg->opt);
+ if (opt_cfg->val)
+ put_opt6(opt_cfg->val, opt_cfg->len);
+ end_opt6(o);
+ }
+ }
+
+ if (daemon->port == NAMESERVER_PORT && !done_dns)
+ {
+ o = new_opt6(OPTION6_DNS_SERVER);
+ if (!add_local_addrs(state->context))
+ put_opt6(state->fallback, IN6ADDRSZ);
+ end_opt6(o);
+ }
+
+ if (state->context && !done_refresh)
+ {
+ struct dhcp_context *c;
+ unsigned int lease_time = 0xffffffff;
+
+ /* Find the smallest lease tie of all contexts,
+ subject to the RFC-4242 stipulation that this must not
+ be less than 600. */
+ for (c = state->context; c; c = c->next)
+ if (c->lease_time < lease_time)
+ {
+ if (c->lease_time < 600)
+ lease_time = 600;
+ else
+ lease_time = c->lease_time;
+ }
+
+ o = new_opt6(OPTION6_REFRESH_TIME);
+ put_opt6_long(lease_time);
+ end_opt6(o);
+ }
+
+ /* handle vendor-identifying vendor-encapsulated options,
+ dhcp-option = vi-encap:13,17,....... */
+ for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+ opt_cfg->flags &= ~DHOPT_ENCAP_DONE;
+
+ if (oro)
+ for (i = 0; i < opt6_len(oro) - 1; i += 2)
+ if (opt6_uint(oro, i, 2) == OPTION6_VENDOR_OPTS)
+ do_encap = 1;
+
+ for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+ {
+ if (opt_cfg->flags & DHOPT_RFC3925)
+ {
+ int found = 0;
+ struct dhcp_opt *oc;
+
+ if (opt_cfg->flags & DHOPT_ENCAP_DONE)
+ continue;
+
+ for (oc = daemon->dhcp_opts6; oc; oc = oc->next)
+ {
+ oc->flags &= ~DHOPT_ENCAP_MATCH;
+
+ if (!(oc->flags & DHOPT_RFC3925) || opt_cfg->u.encap != oc->u.encap)
+ continue;
+
+ oc->flags |= DHOPT_ENCAP_DONE;
+ if (match_netid(oc->netid, tagif, 1))
+ {
+ /* option requested/forced? */
+ if (!oro || do_encap || (oc->flags & DHOPT_FORCE))
+ {
+ oc->flags |= DHOPT_ENCAP_MATCH;
+ found = 1;
+ }
+ }
+ }
+
+ if (found)
+ {
+ o = new_opt6(OPTION6_VENDOR_OPTS);
+ put_opt6_long(opt_cfg->u.encap);
+
+ for (oc = daemon->dhcp_opts6; oc; oc = oc->next)
+ if (oc->flags & DHOPT_ENCAP_MATCH)
+ {
+ o1 = new_opt6(oc->opt);
+ put_opt6(oc->val, oc->len);
+ end_opt6(o1);
+ }
+ end_opt6(o);
+ }
+ }
+ }
+
+
+ if (state->hostname)
+ {
+ unsigned char *p;
+ size_t len = strlen(state->hostname);
+
+ if (state->send_domain)
+ len += strlen(state->send_domain) + 2;
+
+ o = new_opt6(OPTION6_FQDN);
+ if ((p = expand(len + 2)))
+ {
+ *(p++) = state->fqdn_flags;
+ p = do_rfc1035_name(p, state->hostname, NULL);
+ if (state->send_domain)
+ {
+ p = do_rfc1035_name(p, state->send_domain, NULL);
+ *p = 0;
+ }
+ }
+ end_opt6(o);
+ }
+
+
+ /* logging */
+ if (option_bool(OPT_LOG_OPTS) && oro)
+ {
+ char *q = daemon->namebuff;
+ for (i = 0; i < opt6_len(oro) - 1; i += 2)
+ {
+ char *s = option_string(AF_INET6, opt6_uint(oro, i, 2), NULL, 0, NULL, 0);
+ q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
+ "%d%s%s%s",
+ opt6_uint(oro, i, 2),
+ strlen(s) != 0 ? ":" : "",
+ s,
+ (i > opt6_len(oro) - 3) ? "" : ", ");
+ if ( i > opt6_len(oro) - 3 || (q - daemon->namebuff) > 40)
+ {
+ q = daemon->namebuff;
+ my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), state->xid, daemon->namebuff);
+ }
+ }
+ }
+
+ return tagif;
+}
+
+static int add_local_addrs(struct dhcp_context *context)
+{
+ int done = 0;
+
+ for (; context; context = context->current)
+ if ((context->flags & CONTEXT_USED) && !IN6_IS_ADDR_UNSPECIFIED(&context->local6))
+ {
+ /* squash duplicates */
+ struct dhcp_context *c;
+ for (c = context->current; c; c = c->current)
+ if ((c->flags & CONTEXT_USED) &&
+ IN6_ARE_ADDR_EQUAL(&context->local6, &c->local6))
+ break;
+
+ if (!c)
+ {
+ done = 1;
+ put_opt6(&context->local6, IN6ADDRSZ);
+ }
+ }
+
+ return done;
+}
+
+
+static void get_context_tag(struct state *state, struct dhcp_context *context)
+{
+ /* get tags from context if we've not used it before */
+ if (context->netid.next == &context->netid && context->netid.net)
+ {
+ context->netid.next = state->context_tags;
+ state->context_tags = &context->netid;
+ if (!state->hostname_auth)
+ {
+ struct dhcp_netid_list *id_list;
+
+ for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+ if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0))
+ break;
+ if (id_list)
+ state->hostname = NULL;
+ }
+ }
+}
+
+static int check_ia(struct state *state, void *opt, void **endp, void **ia_option)
+{
+ state->ia_type = opt6_type(opt);
+ *ia_option = NULL;
+
+ if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
+ return 0;
+
+ if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) < 12)
+ return 0;
+
+ if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4)
+ return 0;
+
+ *endp = opt6_ptr(opt, opt6_len(opt));
+ state->iaid = opt6_uint(opt, 0, 4);
+ *ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24);
+
+ return 1;
+}
+
+
+static int build_ia(struct state *state, int *t1cntr)
+{
+ int o = new_opt6(state->ia_type);
+
+ put_opt6_long(state->iaid);
+ *t1cntr = 0;
+
+ if (state->ia_type == OPTION6_IA_NA)
+ {
+ /* save pointer */
+ *t1cntr = save_counter(-1);
+ /* so we can fill these in later */
+ put_opt6_long(0);
+ put_opt6_long(0);
+ }
+
+ return o;
+}
+
+static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz)
+{
+ if (t1cntr != 0)
+ {
+ /* go back and fill in fields in IA_NA option */
+ int sav = save_counter(t1cntr);
+ unsigned int t1, t2, fuzz = 0;
+
+ if (do_fuzz)
+ {
+ fuzz = rand16();
+
+ while (fuzz > (min_time/16))
+ fuzz = fuzz/2;
+ }
+
+ t1 = (min_time == 0xffffffff) ? 0xffffffff : min_time/2 - fuzz;
+ t2 = (min_time == 0xffffffff) ? 0xffffffff : ((min_time/8)*7) - fuzz;
+ put_opt6_long(t1);
+ put_opt6_long(t2);
+ save_counter(sav);
+ }
+}
+
+static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option,
+ unsigned int *min_time, struct in6_addr *addr, time_t now)
+{
+ unsigned int valid_time = 0, preferred_time = 0;
+ int o = new_opt6(OPTION6_IAADDR);
+ struct dhcp_lease *lease;
+
+ /* get client requested times */
+ if (ia_option)
+ {
+ preferred_time = opt6_uint(ia_option, 16, 4);
+ valid_time = opt6_uint(ia_option, 20, 4);
+ }
+
+ calculate_times(context, min_time, &valid_time, &preferred_time, lease_time);
+
+ put_opt6(addr, sizeof(*addr));
+ put_opt6_long(preferred_time);
+ put_opt6_long(valid_time);
+ end_opt6(o);
+
+ if (state->lease_allocate)
+ update_leases(state, context, addr, valid_time, now);
+
+ if ((lease = lease6_find_by_addr(addr, 128, 0)))
+ lease->flags |= LEASE_USED;
+
+ /* get tags from context if we've not used it before */
+ if (context->netid.next == &context->netid && context->netid.net)
+ {
+ context->netid.next = state->context_tags;
+ state->context_tags = &context->netid;
+
+ if (!state->hostname_auth)
+ {
+ struct dhcp_netid_list *id_list;
+
+ for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+ if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0))
+ break;
+ if (id_list)
+ state->hostname = NULL;
+ }
+ }
+
+ log6_quiet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", addr, state->hostname);
+
+}
+
+static void mark_context_used(struct state *state, struct in6_addr *addr)
+{
+ struct dhcp_context *context;
+
+ /* Mark that we have an address for this prefix. */
+ for (context = state->context; context; context = context->current)
+ if (is_same_net6(addr, &context->start6, context->prefix))
+ context->flags |= CONTEXT_USED;
+}
+
+static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr)
+{
+ for (; context; context = context->current)
+ if (is_same_net6(addr, &context->start6, context->prefix))
+ context->flags |= CONTEXT_CONF_USED;
+}
+
+/* make sure address not leased to another CLID/IAID */
+static int check_address(struct state *state, struct in6_addr *addr)
+{
+ struct dhcp_lease *lease;
+
+ if (!(lease = lease6_find_by_addr(addr, 128, 0)))
+ return 1;
+
+ if (lease->clid_len != state->clid_len ||
+ memcmp(lease->clid, state->clid, state->clid_len) != 0 ||
+ lease->iaid != state->iaid)
+ return 0;
+
+ return 1;
+}
+
+
+/* return true of *addr could have been generated from config. */
+static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
+{
+ int prefix;
+ struct in6_addr wild_addr;
+ struct addrlist *addr_list;
+
+ if (!config || !(config->flags & CONFIG_ADDR6))
+ return NULL;
+
+ for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+ {
+ prefix = (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128;
+ wild_addr = addr_list->addr.addr6;
+
+ if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+ {
+ wild_addr = context->start6;
+ setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr6));
+ }
+ else if (!is_same_net6(&context->start6, addr, context->prefix))
+ continue;
+
+ if (is_same_net6(&wild_addr, addr, prefix))
+ return addr_list;
+ }
+
+ return NULL;
+}
+
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now)
+{
+ u64 addrpart, i, addresses;
+ struct addrlist *addr_list;
+
+ if (!config || !(config->flags & CONFIG_ADDR6))
+ return 0;
+
+ for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+ if (!(addr_list->flags & ADDRLIST_DECLINED) ||
+ difftime(now, addr_list->decline_time) >= (float)DECLINE_BACKOFF)
+ {
+ addrpart = addr6part(&addr_list->addr.addr6);
+ addresses = 1;
+
+ if (addr_list->flags & ADDRLIST_PREFIX)
+ addresses = (u64)1<<(128-addr_list->prefixlen);
+
+ if ((addr_list->flags & ADDRLIST_WILDCARD))
+ {
+ if (context->prefix != 64)
+ continue;
+
+ *addr = context->start6;
+ }
+ else if (is_same_net6(&context->start6, &addr_list->addr.addr6, context->prefix))
+ *addr = addr_list->addr.addr6;
+ else
+ continue;
+
+ for (i = 0 ; i < addresses; i++)
+ {
+ setaddr6part(addr, addrpart+i);
+
+ if (check_address(state, addr))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Calculate valid and preferred times to send in leases/renewals.
+
+ Inputs are:
+
+ *valid_timep, *preferred_timep - requested times from IAADDR options.
+ context->valid, context->preferred - times associated with subnet address on local interface.
+ context->flags | CONTEXT_DEPRECATE - "deprecated" flag in dhcp-range.
+ lease_time - configured time for context for individual client.
+ *min_time - smallest valid time sent so far.
+
+ Outputs are :
+
+ *valid_timep, *preferred_timep - times to be send in IAADDR option.
+ *min_time - smallest valid time sent so far, to calculate T1 and T2.
+
+ */
+static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep,
+ unsigned int *preferred_timep, unsigned int lease_time)
+{
+ unsigned int req_preferred = *preferred_timep, req_valid = *valid_timep;
+ unsigned int valid_time = lease_time, preferred_time = lease_time;
+
+ /* RFC 3315: "A server ignores the lifetimes set
+ by the client if the preferred lifetime is greater than the valid
+ lifetime. */
+ if (req_preferred <= req_valid)
+ {
+ if (req_preferred != 0)
+ {
+ /* 0 == "no preference from client" */
+ if (req_preferred < 120u)
+ req_preferred = 120u; /* sanity */
+
+ if (req_preferred < preferred_time)
+ preferred_time = req_preferred;
+ }
+
+ if (req_valid != 0)
+ /* 0 == "no preference from client" */
+ {
+ if (req_valid < 120u)
+ req_valid = 120u; /* sanity */
+
+ if (req_valid < valid_time)
+ valid_time = req_valid;
+ }
+ }
+
+ /* deprecate (preferred == 0) which configured, or when local address
+ is deprecated */
+ if ((context->flags & CONTEXT_DEPRECATE) || context->preferred == 0)
+ preferred_time = 0;
+
+ if (preferred_time != 0 && preferred_time < *min_time)
+ *min_time = preferred_time;
+
+ if (valid_time != 0 && valid_time < *min_time)
+ *min_time = valid_time;
+
+ *valid_timep = valid_time;
+ *preferred_timep = preferred_time;
+}
+
+static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now)
+{
+ struct dhcp_lease *lease = lease6_find_by_addr(addr, 128, 0);
+#ifdef HAVE_SCRIPT
+ struct dhcp_netid *tagif = run_tag_if(state->tags);
+#endif
+
+ (void)context;
+
+ if (!lease)
+ lease = lease6_allocate(addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
+
+ if (lease)
+ {
+ lease_set_expires(lease, lease_time, now);
+ lease_set_iaid(lease, state->iaid);
+ lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0);
+ lease_set_interface(lease, state->interface, now);
+ if (state->hostname && state->ia_type == OPTION6_IA_NA)
+ {
+ char *addr_domain = get_domain6(addr);
+ if (!state->send_domain)
+ state->send_domain = addr_domain;
+ lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain);
+ }
+
+#ifdef HAVE_SCRIPT
+ if (daemon->lease_change_command)
+ {
+ void *class_opt;
+ lease->flags |= LEASE_CHANGED;
+ free(lease->extradata);
+ lease->extradata = NULL;
+ lease->extradata_size = lease->extradata_len = 0;
+ lease->vendorclass_count = 0;
+
+ if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4)))
+ {
+ void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt));
+ lease->vendorclass_count++;
+ /* send enterprise number first */
+ sprintf(daemon->dhcp_buff2, "%u", opt6_uint(class_opt, 0, 4));
+ lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
+
+ if (opt6_len(class_opt) >= 6)
+ for (enc_opt = opt6_ptr(class_opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
+ {
+ lease->vendorclass_count++;
+ lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
+ }
+ }
+
+ lease_add_extradata(lease, (unsigned char *)state->client_hostname,
+ state->client_hostname ? strlen(state->client_hostname) : 0, 0);
+
+ /* space-concat tag set */
+ if (!tagif && !context->netid.net)
+ lease_add_extradata(lease, NULL, 0, 0);
+ else
+ {
+ if (context->netid.net)
+ lease_add_extradata(lease, (unsigned char *)context->netid.net, strlen(context->netid.net), tagif ? ' ' : 0);
+
+ if (tagif)
+ {
+ struct dhcp_netid *n;
+ for (n = tagif; n; n = n->next)
+ {
+ struct dhcp_netid *n1;
+ /* kill dupes */
+ for (n1 = n->next; n1; n1 = n1->next)
+ if (strcmp(n->net, n1->net) == 0)
+ break;
+ if (!n1)
+ lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0);
+ }
+ }
+ }
+
+ if (state->link_address)
+ inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
+
+ lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0);
+
+ if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
+ {
+ void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt));
+ for (enc_opt = opt6_ptr(class_opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
+ lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
+ }
+ }
+#endif
+
+ }
+}
+
+
+
+static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts)
+{
+ void *opt;
+ char *desc = nest ? "nest" : "sent";
+
+ if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts)
+ return;
+
+ for (opt = start_opts; opt; opt = opt6_next(opt, end_opts))
+ {
+ int type = opt6_type(opt);
+ void *ia_options = NULL;
+ char *optname;
+
+ if (type == OPTION6_IA_NA)
+ {
+ sprintf(daemon->namebuff, "IAID=%u T1=%u T2=%u",
+ opt6_uint(opt, 0, 4), opt6_uint(opt, 4, 4), opt6_uint(opt, 8, 4));
+ optname = "ia-na";
+ ia_options = opt6_ptr(opt, 12);
+ }
+ else if (type == OPTION6_IA_TA)
+ {
+ sprintf(daemon->namebuff, "IAID=%u", opt6_uint(opt, 0, 4));
+ optname = "ia-ta";
+ ia_options = opt6_ptr(opt, 4);
+ }
+ else if (type == OPTION6_IAADDR)
+ {
+ struct in6_addr addr;
+
+ /* align */
+ memcpy(&addr, opt6_ptr(opt, 0), IN6ADDRSZ);
+ inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN);
+ sprintf(daemon->namebuff, "%s PL=%u VL=%u",
+ daemon->addrbuff, opt6_uint(opt, 16, 4), opt6_uint(opt, 20, 4));
+ optname = "iaaddr";
+ ia_options = opt6_ptr(opt, 24);
+ }
+ else if (type == OPTION6_STATUS_CODE)
+ {
+ int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
+ memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
+ daemon->namebuff[len + opt6_len(opt) - 2] = 0;
+ optname = "status";
+ }
+ else
+ {
+ /* account for flag byte on FQDN */
+ int offset = type == OPTION6_FQDN ? 1 : 0;
+ optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAME);
+ }
+
+ my_syslog(MS_DHCP | LOG_INFO, "%u %s size:%3d option:%3d %s %s",
+ xid, desc, opt6_len(opt), type, optname, daemon->namebuff);
+
+ if (ia_options)
+ log6_opts(1, xid, ia_options, opt6_ptr(opt, opt6_len(opt)));
+ }
+}
+
+static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string)
+{
+ if (option_bool(OPT_LOG_OPTS) || !option_bool(OPT_QUIET_DHCP6))
+ log6_packet(state, type, addr, string);
+}
+
+static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string)
+{
+ int clid_len = state->clid_len;
+
+ /* avoid buffer overflow */
+ if (clid_len > 100)
+ clid_len = 100;
+
+ print_mac(daemon->namebuff, state->clid, clid_len);
+
+ if (addr)
+ {
+ inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, DHCP_BUFF_SZ - 1);
+ strcat(daemon->dhcp_buff2, " ");
+ }
+ else
+ daemon->dhcp_buff2[0] = 0;
+
+ if(option_bool(OPT_LOG_OPTS))
+ my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s %s",
+ state->xid,
+ type,
+ state->iface_name,
+ daemon->dhcp_buff2,
+ daemon->namebuff,
+ string ? string : "");
+ else
+ my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s",
+ type,
+ state->iface_name,
+ daemon->dhcp_buff2,
+ daemon->namebuff,
+ string ? string : "");
+}
+
+static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize)
+{
+ u16 opt, opt_len;
+ void *start;
+
+ if (!opts)
+ return NULL;
+
+ while (1)
+ {
+ if (end - opts < 4)
+ return NULL;
+
+ start = opts;
+ GETSHORT(opt, opts);
+ GETSHORT(opt_len, opts);
+
+ if (opt_len > (end - opts))
+ return NULL;
+
+ if (opt == search && (opt_len >= minsize))
+ return start;
+
+ opts += opt_len;
+ }
+}
+
+static void *opt6_next(void *opts, void *end)
+{
+ u16 opt_len;
+
+ if (end - opts < 4)
+ return NULL;
+
+ opts += 2;
+ GETSHORT(opt_len, opts);
+
+ if (opt_len >= (end - opts))
+ return NULL;
+
+ return opts + opt_len;
+}
+
+static unsigned int opt6_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 = opt6_ptr(opt, offset);
+
+ for (i = 0; i < size; i++)
+ ret = (ret << 8) | *p++;
+
+ return ret;
+}
+
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
+ struct in6_addr *peer_address, u32 scope_id, time_t now)
+{
+ /* ->local is same value for all relays on ->current chain */
+
+ union all_addr from;
+ unsigned char *header;
+ unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+ int msg_type = *inbuff;
+ int hopcount;
+ struct in6_addr multicast;
+ unsigned int maclen, mactype;
+ unsigned char mac[DHCP_CHADDR_MAX];
+
+ inet_pton(AF_INET6, ALL_SERVERS, &multicast);
+ get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now);
+
+ /* source address == relay address */
+ from.addr6 = relay->local.addr6;
+
+ /* Get hop count from nested relayed message */
+ if (msg_type == DHCP6RELAYFORW)
+ hopcount = *((unsigned char *)inbuff+1) + 1;
+ else
+ hopcount = 0;
+
+ /* RFC 3315 HOP_COUNT_LIMIT */
+ if (hopcount > 32)
+ return;
+
+ reset_counter();
+
+ if ((header = put_opt6(NULL, 34)))
+ {
+ int o;
+
+ header[0] = DHCP6RELAYFORW;
+ header[1] = hopcount;
+ memcpy(&header[2], &relay->local.addr6, IN6ADDRSZ);
+ memcpy(&header[18], peer_address, IN6ADDRSZ);
+
+ /* RFC-6939 */
+ if (maclen != 0)
+ {
+ o = new_opt6(OPTION6_CLIENT_MAC);
+ put_opt6_short(mactype);
+ put_opt6(mac, maclen);
+ end_opt6(o);
+ }
+
+ o = new_opt6(OPTION6_RELAY_MSG);
+ put_opt6(inbuff, sz);
+ end_opt6(o);
+
+ for (; relay; relay = relay->current)
+ {
+ union mysockaddr to;
+
+ to.sa.sa_family = AF_INET6;
+ to.in6.sin6_addr = relay->server.addr6;
+ to.in6.sin6_port = htons(DHCPV6_SERVER_PORT);
+ to.in6.sin6_flowinfo = 0;
+ to.in6.sin6_scope_id = 0;
+
+ if (IN6_ARE_ADDR_EQUAL(&relay->server.addr6, &multicast))
+ {
+ int multicast_iface;
+ if (!relay->interface || strchr(relay->interface, '*') ||
+ (multicast_iface = if_nametoindex(relay->interface)) == 0 ||
+ setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_iface, sizeof(multicast_iface)) == -1)
+ my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast to DHCPv6 server without correct interface"));
+ }
+
+ send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(-1), &to, &from, 0);
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->namebuff);
+ }
+
+ /* Save this for replies */
+ relay->iface_index = scope_id;
+ }
+ }
+}
+
+unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
+{
+ struct dhcp_relay *relay;
+ struct in6_addr link;
+ unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+
+ /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
+ which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */
+
+ if (sz < 38 || *inbuff != DHCP6RELAYREPL)
+ return 0;
+
+ memcpy(&link, &inbuff[2], IN6ADDRSZ);
+
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr6) &&
+ (!relay->interface || wildcard_match(relay->interface, arrival_interface)))
+ break;
+
+ reset_counter();
+
+ if (relay)
+ {
+ void *opt, *opts = inbuff + 34;
+ void *end = inbuff + sz;
+ for (opt = opts; opt; opt = opt6_next(opt, end))
+ if (opt6_type(opt) == OPTION6_RELAY_MSG && opt6_len(opt) > 0)
+ {
+ int encap_type = *((unsigned char *)opt6_ptr(opt, 0));
+ put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
+ memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ);
+ peer->sin6_scope_id = relay->iface_index;
+ return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
+ }
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c
new file mode 100755
index 0000000..58c6d8f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c
@@ -0,0 +1,339 @@
+/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Code to safely remove RRs from a DNS answer */
+
+#include "dnsmasq.h"
+
+/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+ we've chopped out of the packet, or check they don't point into an elided part. */
+static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+ unsigned char *ansp = *namep;
+
+ while(1)
+ {
+ unsigned int label_type;
+
+ if (!CHECK_LEN(header, ansp, plen, 1))
+ return 0;
+
+ label_type = (*ansp) & 0xc0;
+
+ if (label_type == 0xc0)
+ {
+ /* pointer for compression. */
+ unsigned int offset;
+ int i;
+ unsigned char *p;
+
+ if (!CHECK_LEN(header, ansp, plen, 2))
+ return 0;
+
+ offset = ((*ansp++) & 0x3f) << 8;
+ offset |= *ansp++;
+
+ p = offset + (unsigned char *)header;
+
+ for (i = 0; i < rr_count; i++)
+ if (p < rrs[i])
+ break;
+ else
+ if (i & 1)
+ offset -= rrs[i] - rrs[i-1];
+
+ /* does the pointer end up in an elided RR? */
+ if (i & 1)
+ return 0;
+
+ /* No, scale the pointer */
+ if (fixup)
+ {
+ ansp -= 2;
+ *ansp++ = (offset >> 8) | 0xc0;
+ *ansp++ = offset & 0xff;
+ }
+ break;
+ }
+ else if (label_type == 0x80)
+ return 0; /* reserved */
+ else if (label_type == 0x40)
+ {
+ /* Extended label type */
+ unsigned int count;
+
+ if (!CHECK_LEN(header, ansp, plen, 2))
+ return 0;
+
+ if (((*ansp++) & 0x3f) != 1)
+ return 0; /* we only understand bitstrings */
+
+ count = *(ansp++); /* Bits in bitstring */
+
+ if (count == 0) /* count == 0 means 256 bits */
+ ansp += 32;
+ else
+ ansp += ((count-1)>>3)+1;
+ }
+ else
+ { /* label type == 0 Bottom six bits is length */
+ unsigned int len = (*ansp++) & 0x3f;
+
+ if (!ADD_RDLEN(header, ansp, plen, len))
+ return 0;
+
+ if (len == 0)
+ break; /* zero length label marks the end. */
+ }
+ }
+
+ *namep = ansp;
+
+ return 1;
+}
+
+/* Go through RRs and check or fixup the domain names contained within */
+static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+ int i, j, type, class, rdlen;
+ unsigned char *pp;
+
+ for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
+ {
+ pp = p;
+
+ if (!(p = skip_name(p, header, plen, 10)))
+ return 0;
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ /* If this RR is to be elided, don't fix up its contents */
+ for (j = 0; j < rr_count; j += 2)
+ if (rrs[j] == pp)
+ break;
+
+ if (j >= rr_count)
+ {
+ /* fixup name of RR */
+ if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+ return 0;
+
+ if (class == C_IN)
+ {
+ u16 *d;
+
+ for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
+ {
+ if (*d != 0)
+ pp += *d;
+ else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+ return 0;
+ }
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode)
+{
+ static unsigned char **rrs;
+ static int rr_sz = 0;
+
+ unsigned char *p = (unsigned char *)(header+1);
+ int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
+
+ if (ntohs(header->qdcount) != 1 ||
+ !(p = skip_name(p, header, plen, 4)))
+ return plen;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ /* First pass, find pointers to start and end of all the records we wish to elide:
+ records added for DNSSEC, unless explicitly queried for */
+ for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0;
+ i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
+ i++)
+ {
+ unsigned char *pstart = p;
+ int type, class;
+
+ if (!(p = skip_name(p, header, plen, 10)))
+ return plen;
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return plen;
+
+ /* Don't remove the answer. */
+ if (i < ntohs(header->ancount) && type == qtype && class == qclass)
+ continue;
+
+ if (mode == 0) /* EDNS */
+ {
+ /* EDNS mode, remove T_OPT from additional section only */
+ if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
+ continue;
+ }
+ else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+ /* DNSSEC mode, remove SIGs and NSECs from all three sections. */
+ continue;
+
+
+ if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+ return plen;
+
+ rrs[rr_found++] = pstart;
+ rrs[rr_found++] = p;
+
+ if (i < ntohs(header->ancount))
+ chop_an++;
+ else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
+ chop_ns++;
+ else
+ chop_ar++;
+ }
+
+ /* Nothing to do. */
+ if (rr_found == 0)
+ return plen;
+
+ /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+ point to records we're going to elide. This is theoretically possible, but unlikely. If
+ it happens, we give up and leave the answer unchanged. */
+ p = (unsigned char *)(header+1);
+
+ /* question first */
+ if (!check_name(&p, header, plen, 0, rrs, rr_found))
+ return plen;
+ p += 4; /* qclass, qtype */
+
+ /* Now answers and NS */
+ if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+ return plen;
+
+ /* Third pass, actually fix up pointers in the records */
+ p = (unsigned char *)(header+1);
+
+ check_name(&p, header, plen, 1, rrs, rr_found);
+ p += 4; /* qclass, qtype */
+
+ check_rrs(p, header, plen, 1, rrs, rr_found);
+
+ /* Fourth pass, elide records */
+ for (p = rrs[0], i = 1; i < rr_found; i += 2)
+ {
+ unsigned char *start = rrs[i];
+ unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen;
+
+ memmove(p, start, end-start);
+ p += end-start;
+ }
+
+ plen = p - (unsigned char *)header;
+ header->ancount = htons(ntohs(header->ancount) - chop_an);
+ header->nscount = htons(ntohs(header->nscount) - chop_ns);
+ header->arcount = htons(ntohs(header->arcount) - chop_ar);
+
+ return plen;
+}
+
+/* This is used in the DNSSEC code too, hence it's exported */
+u16 *rrfilter_desc(int type)
+{
+ /* List of RRtypes which include domains in the data.
+ 0 -> domain
+ integer -> no. of plain bytes
+ -1 -> end
+
+ zero is not a valid RRtype, so the final entry is returned for
+ anything which needs no mangling.
+ */
+
+ static u16 rr_desc[] =
+ {
+ T_NS, 0, -1,
+ T_MD, 0, -1,
+ T_MF, 0, -1,
+ T_CNAME, 0, -1,
+ T_SOA, 0, 0, -1,
+ T_MB, 0, -1,
+ T_MG, 0, -1,
+ T_MR, 0, -1,
+ T_PTR, 0, -1,
+ T_MINFO, 0, 0, -1,
+ T_MX, 2, 0, -1,
+ T_RP, 0, 0, -1,
+ T_AFSDB, 2, 0, -1,
+ T_RT, 2, 0, -1,
+ T_SIG, 18, 0, -1,
+ T_PX, 2, 0, 0, -1,
+ T_NXT, 0, -1,
+ T_KX, 2, 0, -1,
+ T_SRV, 6, 0, -1,
+ T_DNAME, 0, -1,
+ 0, -1 /* wildcard/catchall */
+ };
+
+ u16 *p = rr_desc;
+
+ while (*p != type && *p != 0)
+ while (*p++ != (u16)-1);
+
+ return p+1;
+}
+
+int expand_workspace(unsigned char ***wkspc, int *szp, int new)
+{
+ unsigned char **p;
+ int old = *szp;
+
+ if (old >= new+1)
+ return 1;
+
+ if (new >= 100)
+ return 0;
+
+ new += 5;
+
+ if (!(p = whine_malloc(new * sizeof(unsigned char *))))
+ return 0;
+
+ if (old != 0 && *wkspc)
+ {
+ memcpy(p, *wkspc, old * sizeof(unsigned char *));
+ free(*wkspc);
+ }
+
+ *wkspc = p;
+ *szp = new;
+
+ return 1;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c b/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c
new file mode 100755
index 0000000..9b10063
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c
@@ -0,0 +1,213 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP6
+
+#include <netinet/icmp6.h>
+
+static int ping_id = 0;
+
+void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
+{
+ struct slaac_address *slaac, *old, **up;
+ struct dhcp_context *context;
+ int dns_dirty = 0;
+
+ if (!(lease->flags & LEASE_HAVE_HWADDR) ||
+ (lease->flags & (LEASE_TA | LEASE_NA)) ||
+ lease->last_interface == 0 ||
+ !lease->hostname)
+ return ;
+
+ old = lease->slaac_address;
+ lease->slaac_address = NULL;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if ((context->flags & CONTEXT_RA_NAME) &&
+ !(context->flags & CONTEXT_OLD) &&
+ lease->last_interface == context->if_index)
+ {
+ struct in6_addr addr = context->start6;
+ if (lease->hwaddr_len == 6 &&
+ (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
+ {
+ /* convert MAC address to EUI-64 */
+ memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
+ memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
+ addr.s6_addr[11] = 0xff;
+ addr.s6_addr[12] = 0xfe;
+ }
+#if defined(ARPHRD_EUI64)
+ else if (lease->hwaddr_len == 8 &&
+ lease->hwaddr_type == ARPHRD_EUI64)
+ memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
+#endif
+#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
+ else if (lease->clid_len == 9 &&
+ lease->clid[0] == ARPHRD_EUI64 &&
+ lease->hwaddr_type == ARPHRD_IEEE1394)
+ /* firewire has EUI-64 identifier as clid */
+ memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
+#endif
+ else
+ continue;
+
+ addr.s6_addr[8] ^= 0x02;
+
+ /* check if we already have this one */
+ for (up = &old, slaac = old; slaac; slaac = slaac->next)
+ {
+ if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
+ {
+ *up = slaac->next;
+ /* recheck when DHCPv4 goes through init-reboot */
+ if (force)
+ {
+ slaac->ping_time = now;
+ slaac->backoff = 1;
+ dns_dirty = 1;
+ }
+ break;
+ }
+ up = &slaac->next;
+ }
+
+ /* No, make new one */
+ if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
+ {
+ slaac->ping_time = now;
+ slaac->backoff = 1;
+ slaac->addr = addr;
+ /* Do RA's to prod it */
+ ra_start_unsolicited(now, context);
+ }
+
+ if (slaac)
+ {
+ slaac->next = lease->slaac_address;
+ lease->slaac_address = slaac;
+ }
+ }
+
+ if (old || dns_dirty)
+ lease_update_dns(1);
+
+ /* Free any no reused */
+ for (; old; old = slaac)
+ {
+ slaac = old->next;
+ free(old);
+ }
+}
+
+
+time_t periodic_slaac(time_t now, struct dhcp_lease *leases)
+{
+ struct dhcp_context *context;
+ struct dhcp_lease *lease;
+ struct slaac_address *slaac;
+ time_t next_event = 0;
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD))
+ break;
+
+ /* nothing configured */
+ if (!context)
+ return 0;
+
+ while (ping_id == 0)
+ ping_id = rand16();
+
+ for (lease = leases; lease; lease = lease->next)
+ for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+ {
+ /* confirmed or given up? */
+ if (slaac->backoff == 0 || slaac->ping_time == 0)
+ continue;
+
+ if (difftime(slaac->ping_time, now) <= 0.0)
+ {
+ struct ping_packet *ping;
+ struct sockaddr_in6 addr;
+
+ reset_counter();
+
+ if (!(ping = expand(sizeof(struct ping_packet))))
+ continue;
+
+ ping->type = ICMP6_ECHO_REQUEST;
+ ping->code = 0;
+ ping->identifier = ping_id;
+ ping->sequence_no = slaac->backoff;
+
+ memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(IPPROTO_ICMPV6);
+ addr.sin6_addr = slaac->addr;
+
+ if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(-1), 0,
+ (struct sockaddr *)&addr, sizeof(addr)) == -1 &&
+ errno == EHOSTUNREACH &&
+ slaac->backoff == 12)
+ slaac->ping_time = 0; /* Give up */
+ else
+ {
+ slaac->ping_time += (1 << (slaac->backoff - 1)) + (rand16()/21785); /* 0 - 3 */
+ if (slaac->backoff > 4)
+ slaac->ping_time += rand16()/4000; /* 0 - 15 */
+ if (slaac->backoff < 12)
+ slaac->backoff++;
+ }
+ }
+
+ if (slaac->ping_time != 0 &&
+ (next_event == 0 || difftime(next_event, slaac->ping_time) >= 0.0))
+ next_event = slaac->ping_time;
+ }
+
+ return next_event;
+}
+
+
+void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases)
+{
+ struct dhcp_lease *lease;
+ struct slaac_address *slaac;
+ struct ping_packet *ping = (struct ping_packet *)packet;
+ int gotone = 0;
+
+ if (ping->identifier == ping_id)
+ for (lease = leases; lease; lease = lease->next)
+ for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+ if (slaac->backoff != 0 && IN6_ARE_ADDR_EQUAL(sender, &slaac->addr))
+ {
+ slaac->backoff = 0;
+ gotone = 1;
+ inet_ntop(AF_INET6, sender, daemon->addrbuff, ADDRSTRLEN);
+ if (!option_bool(OPT_QUIET_DHCP6))
+ my_syslog(MS_DHCP | LOG_INFO, "SLAAC-CONFIRM(%s) %s %s", interface, daemon->addrbuff, lease->hostname);
+ }
+
+ lease_update_dns(gotone);
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c b/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c
new file mode 100755
index 0000000..ccfe9ed
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c
@@ -0,0 +1,144 @@
+/* tables.c is Copyright (c) 2014 Sven Falempin All Rights Reserved.
+
+ Author's email: sfalempin@citypassenger.com
+
+ 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_IPSET) && defined(HAVE_BSD_NETWORK)
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#define UNUSED(x) (void)(x)
+
+static char *pf_device = "/dev/pf";
+static int dev = -1;
+
+static char *pfr_strerror(int errnum)
+{
+ switch (errnum)
+ {
+ case ESRCH:
+ return "Table does not exist";
+ case ENOENT:
+ return "Anchor or Ruleset does not exist";
+ default:
+ return strerror(errnum);
+ }
+}
+
+
+void ipset_init(void)
+{
+ dev = open( pf_device, O_RDWR);
+ if (dev == -1)
+ {
+ err(1, "%s", pf_device);
+ die (_("failed to access pf devices: %s"), NULL, EC_MISC);
+ }
+}
+
+int add_to_ipset(const char *setname, const union all_addr *ipaddr,
+ int flags, int remove)
+{
+ struct pfr_addr addr;
+ struct pfioc_table io;
+ struct pfr_table table;
+
+ if (dev == -1)
+ {
+ my_syslog(LOG_ERR, _("warning: no opened pf devices %s"), pf_device);
+ return -1;
+ }
+
+ bzero(&table, sizeof(struct pfr_table));
+ table.pfrt_flags |= PFR_TFLAG_PERSIST;
+ if (strlen(setname) >= PF_TABLE_NAME_SIZE)
+ {
+ my_syslog(LOG_ERR, _("error: cannot use table name %s"), setname);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ if (strlcpy(table.pfrt_name, setname,
+ sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name))
+ {
+ my_syslog(LOG_ERR, _("error: cannot strlcpy table name %s"), setname);
+ return -1;
+ }
+
+ bzero(&io, sizeof io);
+ io.pfrio_flags = 0;
+ io.pfrio_buffer = &table;
+ io.pfrio_esize = sizeof(table);
+ io.pfrio_size = 1;
+ if (ioctl(dev, DIOCRADDTABLES, &io))
+ {
+ my_syslog(LOG_WARNING, _("IPset: error: %s"), pfr_strerror(errno));
+
+ return -1;
+ }
+
+ table.pfrt_flags &= ~PFR_TFLAG_PERSIST;
+ if (io.pfrio_nadd)
+ my_syslog(LOG_INFO, _("info: table created"));
+
+ bzero(&addr, sizeof(addr));
+
+ if (flags & F_IPV6)
+ {
+ addr.pfra_af = AF_INET6;
+ addr.pfra_net = 0x80;
+ memcpy(&(addr.pfra_ip6addr), ipaddr, sizeof(struct in6_addr));
+ }
+ else
+ {
+ addr.pfra_af = AF_INET;
+ addr.pfra_net = 0x20;
+ addr.pfra_ip4addr.s_addr = ipaddr->addr4.s_addr;
+ }
+
+ bzero(&io, sizeof(io));
+ io.pfrio_flags = 0;
+ io.pfrio_table = table;
+ io.pfrio_buffer = &addr;
+ io.pfrio_esize = sizeof(addr);
+ io.pfrio_size = 1;
+ if (ioctl(dev, ( remove ? DIOCRDELADDRS : DIOCRADDADDRS ), &io))
+ {
+ my_syslog(LOG_WARNING, _("warning: DIOCR%sADDRS: %s"), ( remove ? "DEL" : "ADD" ), pfr_strerror(errno));
+ return -1;
+ }
+
+ my_syslog(LOG_INFO, _("%d addresses %s"),
+ io.pfrio_nadd, ( remove ? "removed" : "added" ));
+
+ return io.pfrio_nadd;
+}
+
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c
new file mode 100755
index 0000000..37bdff2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c
@@ -0,0 +1,879 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
+static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix);
+static void free_transfer(struct tftp_transfer *transfer);
+static ssize_t tftp_err(int err, char *packet, char *message, char *file);
+static ssize_t tftp_err_oops(char *packet, const char *file);
+static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
+static char *next(char **p, char *end);
+static void sanitise(char *buf);
+
+#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
+#define ERR_TID 5
+
+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;
+ struct iname *tmp;
+ struct tftp_transfer *transfer = NULL, **up;
+ 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 *name = NULL;
+ char *prefix = daemon->tftp_prefix;
+ struct tftp_prefix *pref;
+ union all_addr addra;
+ int family = listen->addr.sa.sa_family;
+ /* Can always get recvd interface for IPv6 */
+ int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6;
+ union {
+ struct cmsghdr align; /* this ensures alignment */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#if defined(HAVE_LINUX_NETWORK)
+ char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#elif defined(HAVE_SOLARIS_NETWORK)
+ char control[CMSG_SPACE(sizeof(struct in_addr)) +
+ CMSG_SPACE(sizeof(unsigned int))];
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+ char control[CMSG_SPACE(sizeof(struct in_addr)) +
+ 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;
+
+ /* Can always get recvd interface for IPv6 */
+ if (!check_dest)
+ {
+ if (listen->iface)
+ {
+ addr = listen->iface->addr;
+ name = listen->iface->name;
+ mtu = listen->iface->mtu;
+ if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
+ mtu = daemon->tftp_mtu;
+ }
+ else
+ {
+ /* we're listening on an address that doesn't appear on an interface,
+ ask the kernel what the socket is bound to */
+ socklen_t tcp_len = sizeof(union mysockaddr);
+ if (getsockname(listen->tftpfd, (struct sockaddr *)&addr, &tcp_len) == -1)
+ return;
+ }
+ }
+ else
+ {
+ struct cmsghdr *cmptr;
+
+ if (msg.msg_controllen < sizeof(struct cmsghdr))
+ return;
+
+ addr.sa.sa_family = family;
+
+#if defined(HAVE_LINUX_NETWORK)
+ if (family == AF_INET)
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+ {
+ union {
+ unsigned char *c;
+ struct in_pktinfo *p;
+ } p;
+ p.c = CMSG_DATA(cmptr);
+ addr.in.sin_addr = p.p->ipi_spec_dst;
+ if_index = p.p->ipi_ifindex;
+ }
+
+#elif defined(HAVE_SOLARIS_NETWORK)
+ if (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 (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
+
+ if (family == AF_INET6)
+ {
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && 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;
+ }
+ }
+
+ if (!indextoname(listen->tftpfd, if_index, namebuff))
+ return;
+
+ name = namebuff;
+
+ addra.addr4 = addr.in.sin_addr;
+
+ if (family == AF_INET6)
+ addra.addr6 = addr.in6.sin6_addr;
+
+ if (daemon->tftp_interfaces)
+ {
+ /* dedicated tftp interface list */
+ for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, name))
+ break;
+
+ if (!tmp)
+ return;
+ }
+ else
+ {
+ /* Do the same as DHCP */
+ if (!iface_check(family, &addra, name, NULL))
+ {
+ if (!option_bool(OPT_CLEVERBIND))
+ enumerate_interfaces(0);
+ if (!loopback_exception(listen->tftpfd, family, &addra, name) &&
+ !label_exception(if_index, family, &addra))
+ return;
+ }
+
+#ifdef HAVE_DHCP
+ /* allowed interfaces are the same as for DHCP */
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, name))
+ return;
+#endif
+ }
+
+ safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1)
+ {
+ mtu = ifr.ifr_mtu;
+ if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
+ mtu = daemon->tftp_mtu;
+ }
+ }
+
+ /* Failed to get interface mtu - can use configured value. */
+ if (mtu == 0)
+ mtu = daemon->tftp_mtu;
+
+ /* data transfer via server listening socket */
+ if (option_bool(OPT_SINGLE_PORT))
+ {
+ int tftp_cnt;
+
+ for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
+ {
+ tftp_cnt++;
+
+ if (sockaddr_isequal(&peer, &transfer->peer))
+ {
+ if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
+ {
+ /* Handle repeated RRQ or abandoned transfer from same host and port
+ by unlinking and reusing the struct transfer. */
+ *up = transfer->next;
+ break;
+ }
+ else
+ {
+ handle_tftp(now, transfer, len);
+ return;
+ }
+ }
+ }
+
+ /* Enforce simultaneous transfer limit. In non-single-port mode
+ this is doene by not listening on the server socket when
+ too many transfers are in progress. */
+ if (!transfer && tftp_cnt >= daemon->tftp_max)
+ return;
+ }
+
+ if (name)
+ {
+ /* check for per-interface prefix */
+ for (pref = daemon->if_prefix; pref; pref = pref->next)
+ if (strcmp(pref->interface, name) == 0)
+ prefix = pref->prefix;
+ }
+
+ if (family == AF_INET)
+ {
+ addr.in.sin_port = htons(port);
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in.sin_len = sizeof(addr.in);
+#endif
+ }
+ else
+ {
+ addr.in6.sin6_port = htons(port);
+ addr.in6.sin6_flowinfo = 0;
+ addr.in6.sin6_scope_id = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr.in6.sin6_len = sizeof(addr.in6);
+#endif
+ }
+
+ /* May reuse struct transfer from abandoned transfer in single port mode. */
+ if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
+ return;
+
+ if (option_bool(OPT_SINGLE_PORT))
+ transfer->sockfd = listen->tftpfd;
+ else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
+ {
+ free(transfer);
+ return;
+ }
+
+ transfer->peer = peer;
+ transfer->source = addra;
+ transfer->if_index = if_index;
+ 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;
+
+ (void)prettyprint_addr(&peer, daemon->addrbuff);
+
+ /* if we have a nailed-down range, iterate until we find a free one. */
+ while (!option_bool(OPT_SINGLE_PORT))
+ {
+ if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 ||
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+ setsockopt(transfer->sockfd, IPPROTO_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 (family == AF_INET)
+ addr.in.sin_port = htons(port);
+ else
+ addr.in6.sin6_port = htons(port);
+
+ 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"), daemon->addrbuff);
+ is_err = 1;
+ }
+ else
+ {
+ if (strcasecmp(mode, "netascii") == 0)
+ transfer->netascii = 1;
+
+ while ((opt = next(&p, end)))
+ {
+ if (strcasecmp(opt, "blksize") == 0)
+ {
+ if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK))
+ {
+ /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */
+ int overhead = (family == AF_INET) ? 32 : 52;
+ transfer->blocksize = atoi(opt);
+ if (transfer->blocksize < 1)
+ transfer->blocksize = 1;
+ if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
+ transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
+ if (mtu != 0 && transfer->blocksize > (unsigned)mtu - overhead)
+ transfer->blocksize = (unsigned)mtu - overhead;
+ 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. */
+ for (p = filename; *p; p++)
+ if (*p == '\\')
+ *p = '/';
+ else if (option_bool(OPT_TFTP_LC))
+ *p = tolower(*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 (option_bool(OPT_TFTP_APREF_IP))
+ {
+ size_t oldlen = strlen(daemon->namebuff);
+ struct stat statbuf;
+
+ strncat(daemon->namebuff, daemon->addrbuff, (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;
+ }
+
+ if (option_bool(OPT_TFTP_APREF_MAC))
+ {
+ unsigned char *macaddr = NULL;
+ unsigned char macbuf[DHCP_CHADDR_MAX];
+
+#ifdef HAVE_DHCP
+ if (daemon->dhcp && peer.sa.sa_family == AF_INET)
+ {
+ /* Check if the client IP is in our lease database */
+ struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr);
+ if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN)
+ macaddr = lease->hwaddr;
+ }
+#endif
+
+ /* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */
+ if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0)
+ macaddr = macbuf;
+
+ if (macaddr)
+ {
+ size_t oldlen = strlen(daemon->namebuff);
+ struct stat statbuf;
+
+ snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
+ macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
+
+ /* remove unique-directory if it doesn't exist */
+ if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
+ daemon->namebuff[oldlen] = 0;
+ }
+ }
+
+ /* Absolute pathnames OK if they match prefix */
+ if (filename[0] == '/')
+ {
+ 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)))
+ {
+ if ((len = get_block(packet, transfer)) == -1)
+ len = tftp_err_oops(packet, daemon->namebuff);
+ else
+ is_err = 0;
+ }
+ }
+
+ send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
+
+ 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)
+{
+ 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 (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid)
+ goto perm;
+
+ /* If we're doing many transfers from the same file, only
+ open it once this saves lots of file descriptors
+ when mass-booting a big cluster, for instance.
+ Be conservative and only share when inode and name match
+ 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(time_t now)
+{
+ struct tftp_transfer *transfer, *tmp, **up;
+
+ /* In single port mode, all packets come via port 69 and tftp_request() */
+ if (!option_bool(OPT_SINGLE_PORT))
+ for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
+ if (poll_check(transfer->sockfd, POLLIN))
+ {
+ union mysockaddr peer;
+ socklen_t addr_len = sizeof(union mysockaddr);
+ ssize_t len;
+
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+
+ if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0)
+ {
+ if (sockaddr_isequal(&peer, &transfer->peer))
+ handle_tftp(now, transfer, len);
+ else
+ {
+ /* Wrong source address. See rfc1350 para 4. */
+ prettyprint_addr(&peer, daemon->addrbuff);
+ len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff);
+ sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer));
+ }
+ }
+ }
+
+ for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
+ {
+ tmp = transfer->next;
+
+ if (difftime(now, transfer->timeout) >= 0.0)
+ {
+ int endcon = 0;
+ ssize_t len;
+
+ /* timeout, retransmit */
+ transfer->timeout += 1 + (1<<(transfer->backoff/2));
+
+ /* 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 > 7)
+ {
+ /* don't complain about timeout when we're awaiting the last
+ ACK, some clients never send it */
+ if ((unsigned)len == transfer->blocksize + 4)
+ endcon = 1;
+ len = 0;
+ }
+
+ if (len != 0)
+ send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
+ &transfer->peer, &transfer->source, transfer->if_index);
+
+ if (endcon || len == 0)
+ {
+ strcpy(daemon->namebuff, transfer->file->filename);
+ sanitise(daemon->namebuff);
+ (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
+ my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
+ /* unlink */
+ *up = tmp;
+ if (endcon)
+ free_transfer(transfer);
+ else
+ {
+ /* put on queue to be sent to script and deleted */
+ transfer->next = daemon->tftp_done_trans;
+ daemon->tftp_done_trans = transfer;
+ }
+ continue;
+ }
+ }
+
+ up = &transfer->next;
+ }
+}
+
+/* packet in daemon->packet as this is called. */
+static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
+{
+ struct ack {
+ unsigned short op, block;
+ } *mess = (struct ack *)daemon->packet;
+
+ if (len >= (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);
+
+ (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
+
+ /* Sanitise error message */
+ if (!err)
+ err = "";
+ else
+ sanitise(err);
+
+ my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
+ (int)ntohs(mess->block), err,
+ daemon->addrbuff);
+
+ /* Got err, ensure we take abort */
+ transfer->timeout = now;
+ transfer->backoff = 100;
+ }
+ }
+}
+
+static void free_transfer(struct tftp_transfer *transfer)
+{
+ if (!option_bool(OPT_SINGLE_PORT))
+ 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 void sanitise(char *buf)
+{
+ unsigned char *q, *r;
+ for (q = r = (unsigned char *)buf; *r; r++)
+ if (isprint((int)*r))
+ *(q++) = *r;
+ *q = 0;
+
+}
+
+#define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */
+static ssize_t tftp_err(int err, char *packet, char *message, char *file)
+{
+ struct errmess {
+ unsigned short op, err;
+ char message[];
+ } *mess = (struct errmess *)packet;
+ ssize_t len, ret = 4;
+ char *errstr = strerror(errno);
+
+ memset(packet, 0, daemon->packet_buff_sz);
+ if (file)
+ sanitise(file);
+
+ mess->op = htons(OP_ERR);
+ mess->err = htons(err);
+ len = snprintf(mess->message, MAXMESSAGE, message, file, errstr);
+ ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */
+
+ if (err != ERR_FNF || !option_bool(OPT_QUIET_TFTP))
+ my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message);
+
+ return ret;
+}
+
+static ssize_t tftp_err_oops(char *packet, const char *file)
+{
+ /* May have >1 refs to file, so potentially mangle a copy of the name */
+ if (file != daemon->namebuff)
+ strcpy(daemon->namebuff, file);
+ return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff);
+}
+
+/* return -1 for error, zero for done. */
+static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
+{
+ memset(packet, 0, daemon->packet_buff_sz);
+
+ if (transfer->block == 0)
+ {
+ /* send OACK */
+ 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, "%u", 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))
+ {
+ transfer->expansion++;
+
+ if (size != transfer->blocksize)
+ size++; /* room in this block */
+ else if (i == size - 1)
+ newcarrylf = 1; /* don't expand LF again if it moves to the next 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;
+ }
+}
+
+
+int do_tftp_script_run(void)
+{
+ struct tftp_transfer *transfer;
+
+ if ((transfer = daemon->tftp_done_trans))
+ {
+ daemon->tftp_done_trans = transfer->next;
+#ifdef HAVE_SCRIPT
+ queue_tftp(transfer->file->size, transfer->file->filename, &transfer->peer);
+#endif
+ free_transfer(transfer);
+ return 1;
+ }
+
+ return 0;
+}
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c b/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c
new file mode 100755
index 0000000..0c502ad
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c
@@ -0,0 +1,386 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_UBUS
+
+#include <libubus.h>
+
+static struct blob_buf b;
+static int error_logged = 0;
+
+static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg);
+
+#ifdef HAVE_CONNTRACK
+enum {
+ SET_CONNMARK_ALLOWLIST_MARK,
+ SET_CONNMARK_ALLOWLIST_MASK,
+ SET_CONNMARK_ALLOWLIST_PATTERNS
+};
+static const struct blobmsg_policy set_connmark_allowlist_policy[] = {
+ [SET_CONNMARK_ALLOWLIST_MARK] = {
+ .name = "mark",
+ .type = BLOBMSG_TYPE_INT32
+ },
+ [SET_CONNMARK_ALLOWLIST_MASK] = {
+ .name = "mask",
+ .type = BLOBMSG_TYPE_INT32
+ },
+ [SET_CONNMARK_ALLOWLIST_PATTERNS] = {
+ .name = "patterns",
+ .type = BLOBMSG_TYPE_ARRAY
+ }
+};
+static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg);
+#endif
+
+static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj);
+
+static const struct ubus_method ubus_object_methods[] = {
+ UBUS_METHOD_NOARG("metrics", ubus_handle_metrics),
+#ifdef HAVE_CONNTRACK
+ UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy),
+#endif
+};
+
+static struct ubus_object_type ubus_object_type =
+ UBUS_OBJECT_TYPE("dnsmasq", ubus_object_methods);
+
+static struct ubus_object ubus_object = {
+ .name = NULL,
+ .type = &ubus_object_type,
+ .methods = ubus_object_methods,
+ .n_methods = ARRAY_SIZE(ubus_object_methods),
+ .subscribe_cb = ubus_subscribe_cb,
+};
+
+static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
+{
+ (void)ctx;
+
+ my_syslog(LOG_DEBUG, _("UBus subscription callback: %s subscriber(s)"), obj->has_subscribers ? "1" : "0");
+}
+
+static void ubus_destroy(struct ubus_context *ubus)
+{
+ ubus_free(ubus);
+ daemon->ubus = NULL;
+
+ /* Forces re-initialization when we're reusing the same definitions later on. */
+ ubus_object.id = 0;
+ ubus_object_type.id = 0;
+}
+
+static void ubus_disconnect_cb(struct ubus_context *ubus)
+{
+ int ret;
+
+ ret = ubus_reconnect(ubus, NULL);
+ if (ret)
+ {
+ my_syslog(LOG_ERR, _("Cannot reconnect to UBus: %s"), ubus_strerror(ret));
+
+ ubus_destroy(ubus);
+ }
+}
+
+char *ubus_init()
+{
+ struct ubus_context *ubus = NULL;
+ int ret = 0;
+
+ if (!(ubus = ubus_connect(NULL)))
+ return NULL;
+
+ ubus_object.name = daemon->ubus_name;
+ ret = ubus_add_object(ubus, &ubus_object);
+ if (ret)
+ {
+ ubus_destroy(ubus);
+ return (char *)ubus_strerror(ret);
+ }
+
+ ubus->connection_lost = ubus_disconnect_cb;
+ daemon->ubus = ubus;
+ error_logged = 0;
+
+ return NULL;
+}
+
+void set_ubus_listeners()
+{
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+ if (!ubus)
+ {
+ if (!error_logged)
+ {
+ my_syslog(LOG_ERR, _("Cannot set UBus listeners: no connection"));
+ error_logged = 1;
+ }
+ return;
+ }
+
+ error_logged = 0;
+
+ poll_listen(ubus->sock.fd, POLLIN);
+ poll_listen(ubus->sock.fd, POLLERR);
+ poll_listen(ubus->sock.fd, POLLHUP);
+}
+
+void check_ubus_listeners()
+{
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+ if (!ubus)
+ {
+ if (!error_logged)
+ {
+ my_syslog(LOG_ERR, _("Cannot poll UBus listeners: no connection"));
+ error_logged = 1;
+ }
+ return;
+ }
+
+ error_logged = 0;
+
+ if (poll_check(ubus->sock.fd, POLLIN))
+ ubus_handle_event(ubus);
+
+ if (poll_check(ubus->sock.fd, POLLHUP | POLLERR))
+ {
+ my_syslog(LOG_INFO, _("Disconnecting from UBus"));
+
+ ubus_destroy(ubus);
+ }
+}
+
+#define CHECK(stmt) \
+ do { \
+ int e = (stmt); \
+ if (e) \
+ { \
+ my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \
+ return (UBUS_STATUS_UNKNOWN_ERROR); \
+ } \
+ } while (0)
+
+static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ int i;
+
+ (void)obj;
+ (void)method;
+ (void)msg;
+
+ CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE));
+
+ for (i=0; i < __METRIC_MAX; i++)
+ CHECK(blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i]));
+
+ CHECK(ubus_send_reply(ctx, req, b.head));
+ return UBUS_STATUS_OK;
+}
+
+#ifdef HAVE_CONNTRACK
+static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ const struct blobmsg_policy *policy = set_connmark_allowlist_policy;
+ size_t policy_len = countof(set_connmark_allowlist_policy);
+ struct allowlist *allowlists = NULL, **allowlists_pos;
+ char **patterns = NULL, **patterns_pos;
+ u32 mark, mask = UINT32_MAX;
+ size_t num_patterns = 0;
+ struct blob_attr *tb[policy_len];
+ struct blob_attr *attr;
+
+ if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)))
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ if (!tb[SET_CONNMARK_ALLOWLIST_MARK])
+ return UBUS_STATUS_INVALID_ARGUMENT;
+ mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]);
+ if (!mark)
+ return UBUS_STATUS_INVALID_ARGUMENT;
+
+ if (tb[SET_CONNMARK_ALLOWLIST_MASK])
+ {
+ mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]);
+ if (!mask || (mark & ~mask))
+ return UBUS_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
+ {
+ struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+ size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+ __blob_for_each_attr(attr, head, len)
+ {
+ char *pattern;
+ if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+ return UBUS_STATUS_INVALID_ARGUMENT;
+ if (!(pattern = blobmsg_get_string(attr)))
+ return UBUS_STATUS_INVALID_ARGUMENT;
+ if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern))
+ return UBUS_STATUS_INVALID_ARGUMENT;
+ num_patterns++;
+ }
+ }
+
+ for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next)
+ if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask)
+ {
+ struct allowlist *allowlists_next = (*allowlists_pos)->next;
+ for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++)
+ {
+ free(*patterns_pos);
+ *patterns_pos = NULL;
+ }
+ free((*allowlists_pos)->patterns);
+ (*allowlists_pos)->patterns = NULL;
+ free(*allowlists_pos);
+ *allowlists_pos = allowlists_next;
+ break;
+ }
+
+ if (!num_patterns)
+ return UBUS_STATUS_OK;
+
+ patterns = whine_malloc((num_patterns + 1) * sizeof(char *));
+ if (!patterns)
+ goto fail;
+ patterns_pos = patterns;
+ if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
+ {
+ struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+ size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+ __blob_for_each_attr(attr, head, len)
+ {
+ char *pattern;
+ if (!(pattern = blobmsg_get_string(attr)))
+ goto fail;
+ if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1)))
+ goto fail;
+ strcpy(*patterns_pos++, pattern);
+ }
+ }
+
+ allowlists = whine_malloc(sizeof(struct allowlist));
+ if (!allowlists)
+ goto fail;
+ memset(allowlists, 0, sizeof(struct allowlist));
+ allowlists->mark = mark;
+ allowlists->mask = mask;
+ allowlists->patterns = patterns;
+ allowlists->next = daemon->allowlists;
+ daemon->allowlists = allowlists;
+ return UBUS_STATUS_OK;
+
+fail:
+ if (patterns)
+ {
+ for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
+ {
+ free(*patterns_pos);
+ *patterns_pos = NULL;
+ }
+ free(patterns);
+ patterns = NULL;
+ }
+ if (allowlists)
+ {
+ free(allowlists);
+ allowlists = NULL;
+ }
+ return UBUS_STATUS_UNKNOWN_ERROR;
+}
+#endif
+
+#undef CHECK
+
+#define CHECK(stmt) \
+ do { \
+ int e = (stmt); \
+ if (e) \
+ { \
+ my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \
+ return; \
+ } \
+ } while (0)
+
+void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface)
+{
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+ if (!ubus || !ubus_object.has_subscribers)
+ return;
+
+ CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE));
+ if (mac)
+ CHECK(blobmsg_add_string(&b, "mac", mac));
+ if (ip)
+ CHECK(blobmsg_add_string(&b, "ip", ip));
+ if (name)
+ CHECK(blobmsg_add_string(&b, "name", name));
+ if (interface)
+ CHECK(blobmsg_add_string(&b, "interface", interface));
+
+ CHECK(ubus_notify(ubus, &ubus_object, type, b.head, -1));
+}
+
+#ifdef HAVE_CONNTRACK
+void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name)
+{
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+ if (!ubus || !ubus_object.has_subscribers)
+ return;
+
+ CHECK(blob_buf_init(&b, 0));
+ CHECK(blobmsg_add_u32(&b, "mark", mark));
+ CHECK(blobmsg_add_string(&b, "name", name));
+
+ CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1));
+}
+
+void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *name, const char *value, u32 ttl)
+{
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+ if (!ubus || !ubus_object.has_subscribers)
+ return;
+
+ CHECK(blob_buf_init(&b, 0));
+ CHECK(blobmsg_add_u32(&b, "mark", mark));
+ CHECK(blobmsg_add_string(&b, "name", name));
+ CHECK(blobmsg_add_string(&b, "value", value));
+ CHECK(blobmsg_add_u32(&b, "ttl", ttl));
+
+ /* Set timeout to allow UBus subscriber to configure firewall rules before returning. */
+ CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000));
+}
+#endif
+
+#undef CHECK
+
+#endif /* HAVE_UBUS */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/util.c b/ap/app/dnsmasq/dnsmasq-2.86/src/util.c
new file mode 100755
index 0000000..1425764
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/util.c
@@ -0,0 +1,817 @@
+/* dnsmasq is Copyright (c) 2000-2021 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(HAVE_LIBIDN2)
+#include <idn2.h>
+#elif defined(HAVE_IDN)
+#include <idna.h>
+#endif
+
+#ifdef HAVE_LINUX_NETWORK
+#include <sys/utsname.h>
+#endif
+
+/* SURF random number generator */
+
+static u32 seed[32];
+static u32 in[12];
+static u32 out[8];
+static int outleft = 0;
+
+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)
+{
+ if (!outleft)
+ {
+ if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+ surf();
+ outleft = 8;
+ }
+
+ return (unsigned short) out[--outleft];
+}
+
+u32 rand32(void)
+{
+ if (!outleft)
+ {
+ if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+ surf();
+ outleft = 8;
+ }
+
+ return out[--outleft];
+}
+
+u64 rand64(void)
+{
+ static int outleft = 0;
+
+ if (outleft < 2)
+ {
+ if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+ surf();
+ outleft = 8;
+ }
+
+ outleft -= 2;
+
+ return (u64)out[outleft+1] + (((u64)out[outleft]) << 32);
+}
+
+/* returns 2 if names is OK but contains one or more underscores */
+static int check_name(char *in)
+{
+ /* remove trailing .
+ also fail empty string and label > 63 chars */
+ size_t dotgap = 0, l = strlen(in);
+ char c;
+ int nowhite = 0;
+ int hasuscore = 0;
+
+ if (l == 0 || l > MAXDNAME) return 0;
+
+ if (in[l-1] == '.')
+ {
+ in[l-1] = 0;
+ nowhite = 1;
+ }
+
+ 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(HAVE_IDN) && !defined(HAVE_LIBIDN2)
+ else if (!isascii((unsigned char)c))
+ return 0;
+#endif
+ else if (c != ' ')
+ {
+ nowhite = 1;
+ if (c == '_')
+ hasuscore = 1;
+ }
+ }
+
+ if (!nowhite)
+ return 0;
+
+ return hasuscore ? 2 : 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;
+ int first;
+
+ if (!check_name(name))
+ return 0;
+
+ for (first = 1; (c = *name); name++, first = 0)
+ /* check for legal char a-z A-Z 0-9 - _ . */
+ {
+ if ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9'))
+ continue;
+
+ if (!first && (c == '-' || c == '_'))
+ continue;
+
+ /* end of hostname part */
+ if (c == '.')
+ return 1;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+char *canonicalise(char *in, int *nomem)
+{
+ char *ret = NULL;
+ int rc;
+
+ if (nomem)
+ *nomem = 0;
+
+ if (!(rc = check_name(in)))
+ return NULL;
+
+#if defined(HAVE_LIBIDN2) && (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003)
+ /* older libidn2 strips underscores, so don't do IDN processing
+ if the name has an underscore (check_name() returned 2) */
+ if (rc != 2)
+#endif
+#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2)
+ {
+# ifdef HAVE_LIBIDN2
+ rc = idn2_to_ascii_lz(in, &ret, IDN2_NONTRANSITIONAL);
+ if (rc == IDN2_DISALLOWED)
+ rc = idn2_to_ascii_lz(in, &ret, IDN2_TRANSITIONAL);
+# else
+ rc = idna_to_ascii_lz(in, &ret, 0);
+# endif
+ if (rc != IDNA_SUCCESS)
+ {
+ if (ret)
+ free(ret);
+
+ if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR))
+ {
+ my_syslog(LOG_ERR, _("failed to allocate memory"));
+ *nomem = 1;
+ }
+
+ return NULL;
+ }
+
+ return ret;
+ }
+#endif
+
+ if ((ret = whine_malloc(strlen(in)+1)))
+ strcpy(ret, in);
+ else if (nomem)
+ *nomem = 1;
+
+ return ret;
+}
+
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
+{
+ int j;
+
+ while (sval && *sval)
+ {
+ unsigned char *cp = p++;
+
+ if (limit && p > (unsigned char*)limit)
+ return NULL;
+
+ for (j = 0; *sval && (*sval != '.'); sval++, j++)
+ {
+ if (limit && p + 1 > (unsigned char*)limit)
+ return NULL;
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
+ *p++ = (*(++sval))-1;
+ else
+#endif
+ *p++ = *sval;
+ }
+
+ *cp = j;
+ if (*sval)
+ sval++;
+ }
+
+ return p;
+}
+
+/* for use during startup */
+void *safe_malloc(size_t size)
+{
+ void *ret = calloc(1, size);
+
+ if (!ret)
+ die(_("could not get memory"), NULL, EC_NOMEM);
+
+ return ret;
+}
+
+/* Ensure limited size string is always terminated.
+ * Can be replaced by (void)strlcpy() on some platforms */
+void safe_strncpy(char *dest, const char *src, size_t size)
+{
+ if (size != 0)
+ {
+ dest[size-1] = '\0';
+ strncpy(dest, src, size-1);
+ }
+}
+
+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 = calloc(1, size);
+
+ if (!ret)
+ my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
+
+ return ret;
+}
+
+int sockaddr_isequal(const union mysockaddr *s1, const 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;
+
+ if (s1->sa.sa_family == AF_INET6 &&
+ s1->in6.sin6_port == s2->in6.sin6_port &&
+ s1->in6.sin6_scope_id == s2->in6.sin6_scope_id &&
+ IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr))
+ return 1;
+ }
+ return 0;
+}
+
+int sa_len(union mysockaddr *addr)
+{
+#ifdef HAVE_SOCKADDR_SA_LEN
+ return addr->sa.sa_len;
+#else
+ if (addr->sa.sa_family == AF_INET6)
+ return sizeof(addr->in6);
+ else
+ return sizeof(addr->in);
+#endif
+}
+
+/* don't use strcasecmp and friends here - they may be messed up by LOCALE */
+int hostname_isequal(const char *a, const 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;
+}
+
+/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */
+int hostname_issubdomain(char *a, char *b)
+{
+ char *ap, *bp;
+ unsigned int c1, c2;
+
+ /* move to the end */
+ for (ap = a; *ap; ap++);
+ for (bp = b; *bp; bp++);
+
+ /* a shorter than b or a empty. */
+ if ((bp - b) < (ap - a) || ap == a)
+ return 0;
+
+ do
+ {
+ c1 = (unsigned char) *(--ap);
+ c2 = (unsigned char) *(--bp);
+
+ if (c1 >= 'A' && c1 <= 'Z')
+ c1 += 'a' - 'A';
+ if (c2 >= 'A' && c2 <= 'Z')
+ c2 += 'a' - 'A';
+
+ if (c1 != c2)
+ return 0;
+ } while (ap != a);
+
+ if (bp == b)
+ return 2;
+
+ if (*(--bp) == '.')
+ return 1;
+
+ return 0;
+}
+
+
+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 netmask_length(struct in_addr mask)
+{
+ int zero_count = 0;
+
+ while (0x0 == (mask.s_addr & 0x1) && zero_count < 32)
+ {
+ mask.s_addr >>= 1;
+ zero_count++;
+ }
+
+ return 32 - zero_count;
+}
+
+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);
+}
+
+int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix)
+{
+ struct in_addr mask;
+
+ mask.s_addr = htonl(~((1 << (32 - prefix)) - 1));
+
+ return is_same_net(a, b, mask);
+}
+
+
+int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen)
+{
+ int pfbytes = prefixlen >> 3;
+ int pfbits = prefixlen & 7;
+
+ if (memcmp(&a->s6_addr, &b->s6_addr, pfbytes) != 0)
+ return 0;
+
+ if (pfbits == 0 ||
+ (a->s6_addr[pfbytes] >> (8 - pfbits) == b->s6_addr[pfbytes] >> (8 - pfbits)))
+ return 1;
+
+ return 0;
+}
+
+/* return least significant 64 bits if IPv6 address */
+u64 addr6part(struct in6_addr *addr)
+{
+ int i;
+ u64 ret = 0;
+
+ for (i = 8; i < 16; i++)
+ ret = (ret << 8) + addr->s6_addr[i];
+
+ return ret;
+}
+
+void setaddr6part(struct in6_addr *addr, u64 host)
+{
+ int i;
+
+ for (i = 15; i >= 8; i--)
+ {
+ addr->s6_addr[i] = host;
+ host = host >> 8;
+ }
+}
+
+
+/* returns port number from address */
+int prettyprint_addr(union mysockaddr *addr, char *buf)
+{
+ int port = 0;
+
+ 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)
+ {
+ char name[IF_NAMESIZE];
+ inet_ntop(AF_INET6, &addr->in6.sin6_addr, buf, ADDRSTRLEN);
+ if (addr->in6.sin6_scope_id != 0 &&
+ if_indextoname(addr->in6.sin6_scope_id, name) &&
+ strlen(buf) + strlen(name) + 2 <= ADDRSTRLEN)
+ {
+ strcat(buf, "%");
+ strcat(buf, name);
+ }
+ port = ntohs(addr->in6.sin6_port);
+ }
+
+ 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], "%ud", x);
+ if ((x = (t/3600)%24))
+ p += sprintf(&buf[p], "%uh", x);
+ if ((x = (t/60)%60))
+ p += sprintf(&buf[p], "%um", x);
+ if ((x = t%60))
+ p += sprintf(&buf[p], "%us", 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 done = 0, mask = 0, i = 0;
+ char *r;
+
+ if (mac_type)
+ *mac_type = 0;
+
+ while (!done && (maxlen == -1 || i < maxlen))
+ {
+ for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++)
+ if (*r != '*' && !isxdigit((unsigned char)*r))
+ return -1;
+
+ if (*r == 0)
+ done = 1;
+
+ if (r != in )
+ {
+ if (*r == '-' && i == 0 && mac_type)
+ {
+ *r = 0;
+ *mac_type = strtol(in, NULL, 16);
+ mac_type = NULL;
+ }
+ else
+ {
+ *r = 0;
+ if (strcmp(in, "*") == 0)
+ {
+ mask = (mask << 1) | 1;
+ i++;
+ }
+ else
+ {
+ int j, bytes = (1 + (r - in))/2;
+ for (j = 0; j < bytes; j++)
+ {
+ char sav = sav;
+ if (j < bytes - 1)
+ {
+ sav = in[(j+1)*2];
+ in[(j+1)*2] = 0;
+ }
+ /* checks above allow mix of hexdigit and *, which
+ is illegal. */
+ if (strchr(&in[j*2], '*'))
+ return -1;
+ out[i] = strtol(&in[j*2], NULL, 16);
+ mask = mask << 1;
+ if (++i == maxlen)
+ break;
+ if (j < bytes - 1)
+ in[(j+1)*2] = sav;
+ }
+ }
+ }
+ }
+ 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;
+}
+
+/* rc is return from sendto and friends.
+ Return 1 if we should retry.
+ Set errno to zero if we succeeded. */
+int retry_send(ssize_t rc)
+{
+ static int retries = 0;
+ struct timespec waiter;
+
+ if (rc != -1)
+ {
+ retries = 0;
+ errno = 0;
+ return 0;
+ }
+
+ /* Linux kernels can return EAGAIN in perpetuity when calling
+ sendmsg() and the relevant interface has gone. Here we loop
+ retrying in EAGAIN for 1 second max, to avoid this hanging
+ dnsmasq. */
+
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ waiter.tv_sec = 0;
+ waiter.tv_nsec = 10000;
+ nanosleep(&waiter, NULL);
+ if (retries++ < 1000)
+ return 1;
+ }
+
+ retries = 0;
+
+ 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)
+ {
+ do {
+ 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;
+
+ } while (retry_send(n) || errno == ENOMEM || errno == ENOBUFS);
+
+ if (errno != 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */
+void close_fds(long max_fd, int spare1, int spare2, int spare3)
+{
+ /* On Linux, use the /proc/ filesystem to find which files
+ are actually open, rather than iterate over the whole space,
+ for efficiency reasons. If this fails we drop back to the dumb code. */
+#ifdef HAVE_LINUX_NETWORK
+ DIR *d;
+
+ if ((d = opendir("/proc/self/fd")))
+ {
+ struct dirent *de;
+
+ while ((de = readdir(d)))
+ {
+ long fd;
+ char *e = NULL;
+
+ errno = 0;
+ fd = strtol(de->d_name, &e, 10);
+
+ if (errno != 0 || !e || *e || fd == dirfd(d) ||
+ fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO ||
+ fd == spare1 || fd == spare2 || fd == spare3)
+ continue;
+
+ close(fd);
+ }
+
+ closedir(d);
+ return;
+ }
+#endif
+
+ /* fallback, dumb code. */
+ for (max_fd--; max_fd >= 0; max_fd--)
+ if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO &&
+ max_fd != spare1 && max_fd != spare2 && max_fd != spare3)
+ close(max_fd);
+}
+
+/* Basically match a string value against a wildcard pattern. */
+int wildcard_match(const char* wildcard, const char* match)
+{
+ while (*wildcard && *match)
+ {
+ if (*wildcard == '*')
+ return 1;
+
+ if (*wildcard != *match)
+ return 0;
+
+ ++wildcard;
+ ++match;
+ }
+
+ return *wildcard == *match;
+}
+
+/* The same but comparing a maximum of NUM characters, like strncmp. */
+int wildcard_matchn(const char* wildcard, const char* match, int num)
+{
+ while (*wildcard && *match && num)
+ {
+ if (*wildcard == '*')
+ return 1;
+
+ if (*wildcard != *match)
+ return 0;
+
+ ++wildcard;
+ ++match;
+ --num;
+ }
+
+ return (!num) || (*wildcard == *match);
+}
+
+#ifdef HAVE_LINUX_NETWORK
+int kernel_version(void)
+{
+ struct utsname utsname;
+ int version;
+ char *split;
+
+ if (uname(&utsname) < 0)
+ die(_("failed to find kernel version: %s"), NULL, EC_MISC);
+
+ split = strtok(utsname.release, ".");
+ version = (split ? atoi(split) : 0);
+ split = strtok(NULL, ".");
+ version = version * 256 + (split ? atoi(split) : 0);
+ split = strtok(NULL, ".");
+ return version * 256 + (split ? atoi(split) : 0);
+}
+#endif