diff --git a/.dnsmasq.metadata b/.dnsmasq.metadata new file mode 100644 index 0000000..e37e407 --- /dev/null +++ b/.dnsmasq.metadata @@ -0,0 +1 @@ +d4a1af08b02b27736954ce8b2db2da7799d75812 SOURCES/dnsmasq-2.79.tar.xz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dec13ea --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/dnsmasq-2.79.tar.xz diff --git a/SOURCES/dnsmasq-2.76-rh1728698-1.patch b/SOURCES/dnsmasq-2.76-rh1728698-1.patch new file mode 100644 index 0000000..2546301 --- /dev/null +++ b/SOURCES/dnsmasq-2.76-rh1728698-1.patch @@ -0,0 +1,52 @@ +From cae343c1f3bea9d1ca2e71d3709d3f02b799f94d Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Thu, 4 Jul 2019 20:28:08 +0200 +Subject: [PATCH 1/5] Log listening on new interfaces + +Log in debug mode listening on interfaces. They can be dynamically +found, include interface number, since it is checked on TCP connections. +Print also addresses found on them. +--- + src/network.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/src/network.c b/src/network.c +index d75f560..fd90288 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -662,6 +662,13 @@ int enumerate_interfaces(int reset) + else + { + *up = l->next; ++ if (l->iface->done) ++ { ++ iface = l->iface; ++ (void)prettyprint_addr(&iface->addr, daemon->addrbuff); ++ my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s"), ++ iface->name, iface->index, daemon->addrbuff); ++ } + + /* In case it ever returns */ + l->iface->done = 0; +@@ -978,6 +985,9 @@ void create_bound_listeners(int dienow) + new->next = daemon->listeners; + daemon->listeners = new; + iface->done = 1; ++ (void)prettyprint_addr(&iface->addr, daemon->addrbuff); ++ my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s"), ++ iface->name, iface->index, daemon->addrbuff); + } + + /* Check for --listen-address options that haven't been used because there's +@@ -997,6 +1007,8 @@ void create_bound_listeners(int dienow) + { + new->next = daemon->listeners; + daemon->listeners = new; ++ (void)prettyprint_addr(&if_tmp->addr, daemon->addrbuff); ++ my_syslog(LOG_DEBUG, _("listening on %s"), daemon->addrbuff); + } + } + +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.76-rh1728698-3.patch b/SOURCES/dnsmasq-2.76-rh1728698-3.patch new file mode 100644 index 0000000..cdb690d --- /dev/null +++ b/SOURCES/dnsmasq-2.76-rh1728698-3.patch @@ -0,0 +1,74 @@ +From 527029312cbe37c0285240943ad02352d64d403d Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Tue, 9 Jul 2019 14:05:59 +0200 +Subject: [PATCH 3/5] Cleanup interfaces no longer available + +Clean addresses and interfaces not found after enumerate. Free unused +records to speed up checking active interfaces and reduce used memory. +--- + src/network.c | 32 ++++++++++++++++++++++++++++++-- + 1 file changed, 30 insertions(+), 2 deletions(-) + +diff --git a/src/network.c b/src/network.c +index f247811..d6d4b01 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -553,7 +553,30 @@ static int iface_allowed_v4(struct in_addr local, int if_index, char *label, + + 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; ++ } ++ } ++} ++ + int enumerate_interfaces(int reset) + { + static struct addrlist *spare = NULL; +@@ -653,6 +676,7 @@ int enumerate_interfaces(int reset) + 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) + { +@@ -682,10 +706,14 @@ int enumerate_interfaces(int reset) + close(l->tftpfd); + + free(l); ++ freed = 1; + } + } ++ ++ if (freed) ++ clean_interfaces(); + } +- ++ + errno = errsave; + spare = param.spare; + +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.76-rh1752569.patch b/SOURCES/dnsmasq-2.76-rh1752569.patch new file mode 100644 index 0000000..b46fcd3 --- /dev/null +++ b/SOURCES/dnsmasq-2.76-rh1752569.patch @@ -0,0 +1,75 @@ +From 3d27384fc5f2a437b7bce128c8ba62e8d6e12df7 Mon Sep 17 00:00:00 2001 +From: Brian Haley +Date: Wed, 28 Aug 2019 16:13:23 -0400 +Subject: [PATCH] Change dhcp_release to use default address when no IP subnet + matches + +Currently, dhcp_release will only send a 'fake' release +when the address given is in the same subnet as an IP +on the interface that was given. + +This doesn't work in an environment where dnsmasq is +managing leases for remote subnets via a DHCP relay, as +running dhcp_release locally will just cause it to +silently exit without doing anything, leaving the lease +in the database. + +Change it to use the default IP on the interface, as the +dnsmasq source code at src/dhcp.c does, if no matching subnet +IP is found, as a fall-back. This fixes an issue we are +seeing in certain Openstack deployments where we are using +dnsmasq to provision baremetal systems in a datacenter. + +While using Dbus might have seemed like an obvious solution, +because of our extensive use of network namespaces (which +Dbus doesn't support), this seemed like a better solution +than creating system.d policy files for each dnsmasq we +might spawn and using --enable-dbus=$id in order to isolate +messages to specific dnsmasq instances. + +Signed-off-by: Brian Haley +(cherry picked from commit d9f882bea2806799bf3d1f73937f5e72d0bfc650) +--- + contrib/lease-tools/dhcp_release.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/contrib/lease-tools/dhcp_release.c b/contrib/lease-tools/dhcp_release.c +index a51f04b..1dd8d32 100644 +--- a/contrib/lease-tools/dhcp_release.c ++++ b/contrib/lease-tools/dhcp_release.c +@@ -178,7 +178,7 @@ static 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); + } + +-static struct in_addr find_interface(struct in_addr client, int fd, unsigned int index) ++static struct in_addr find_interface(struct in_addr client, int fd, unsigned int index, int ifrfd, struct ifreq *ifr) + { + struct sockaddr_nl addr; + struct nlmsghdr *h; +@@ -218,7 +218,13 @@ static struct in_addr find_interface(struct in_addr client, int fd, unsigned int + + for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) + if (h->nlmsg_type == NLMSG_DONE) +- exit(0); ++ { ++ /* No match found, return first address as src/dhcp.c code does */ ++ ifr->ifr_addr.sa_family = AF_INET; ++ if (ioctl(ifrfd, SIOCGIFADDR, ifr) != -1) ++ return ((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr; ++ exit(0); ++ } + else if (h->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg *ifa = NLMSG_DATA(h); +@@ -284,7 +290,7 @@ int main(int argc, char **argv) + } + + lease.s_addr = inet_addr(argv[2]); +- server = find_interface(lease, nl, if_nametoindex(argv[1])); ++ server = find_interface(lease, nl, if_nametoindex(argv[1]), fd, &ifr); + + memset(&packet, 0, sizeof(packet)); + +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.77-underflow.patch b/SOURCES/dnsmasq-2.77-underflow.patch new file mode 100644 index 0000000..2e900bf --- /dev/null +++ b/SOURCES/dnsmasq-2.77-underflow.patch @@ -0,0 +1,63 @@ +From c82a594d95431e8615126621397ea595eb037a6b Mon Sep 17 00:00:00 2001 +From: Doran Moppert +Date: Tue, 26 Sep 2017 14:48:20 +0930 +Subject: [PATCH] google patch hand-applied + +--- + src/edns0.c | 10 +++++----- + src/forward.c | 4 ++++ + src/rfc1035.c | 2 ++ + 3 files changed, 11 insertions(+), 5 deletions(-) + +diff --git a/src/edns0.c b/src/edns0.c +index af33877..ba6ff0c 100644 +--- a/src/edns0.c ++++ b/src/edns0.c +@@ -212,11 +212,11 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l + /* Copy back any options */ + if (buff) + { +- if (p + rdlen > limit) +- { +- free(buff); +- return plen; /* Too big */ +- } ++ if (p + rdlen > limit) ++ { ++ free(buff); ++ return plen; /* Too big */ ++ } + memcpy(p, buff, rdlen); + free(buff); + p += rdlen; +diff --git a/src/forward.c b/src/forward.c +index cdd11d3..3078f64 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -1438,6 +1438,10 @@ void receive_query(struct listener *listen, time_t now) + udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ + } + ++ // Make sure the udp size is not smaller than the incoming message so that we ++ // do not underflow ++ if (udp_size < n) udp_size = n; ++ + #ifdef HAVE_AUTH + if (auth_dns) + { +diff --git a/src/rfc1035.c b/src/rfc1035.c +index b078b59..777911b 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1281,6 +1281,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; + struct mx_srv_record *rec; + size_t len; ++ // Make sure we do not underflow here too. ++ if (qlen > (limit - ((char *)header))) return 0; + + if (ntohs(header->ancount) != 0 || + ntohs(header->nscount) != 0 || +-- +2.14.3 + diff --git a/SOURCES/dnsmasq-2.78-fips.patch b/SOURCES/dnsmasq-2.78-fips.patch new file mode 100644 index 0000000..011433c --- /dev/null +++ b/SOURCES/dnsmasq-2.78-fips.patch @@ -0,0 +1,37 @@ +From 89f57e39b69f92beacb6bad9c68d61f9c4fb0e77 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Fri, 2 Mar 2018 13:17:04 +0100 +Subject: [PATCH] Print warning on FIPS machine with dnssec enabled. Dnsmasq + has no proper FIPS 140-2 compliant implementation. + +--- + src/dnsmasq.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index ce44809..9f6c020 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -187,6 +187,7 @@ int main (int argc, char **argv) + + 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 +@@ -769,7 +770,10 @@ int main (int argc, char **argv) + } + + my_syslog(LOG_INFO, _("DNSSEC validation enabled")); +- ++ ++ if (access("/etc/system-fips", F_OK) == 0) ++ my_syslog(LOG_WARNING, _("DNSSEC support is not FIPS 140-2 compliant")); ++ + 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")); +-- +2.14.4 + diff --git a/SOURCES/dnsmasq-2.79-rh1602477-2.patch b/SOURCES/dnsmasq-2.79-rh1602477-2.patch new file mode 100644 index 0000000..8421b76 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1602477-2.patch @@ -0,0 +1,49 @@ +From dcb4fa04548ab2364f662b735be86e275bd50745 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Fri, 19 Jul 2019 14:00:08 +0200 +Subject: [PATCH] Remove warnings in coverity + +Change in dnsmasq should never occur, because ent_pw would not change. +But keep Coverity happy and prevent logic error. Second change avoids +warning from compiler. +--- + src/dnsmasq.c | 9 ++++++++- + src/option.c | 2 +- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index ce44809..2984f55 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -608,7 +608,14 @@ int main (int argc, char **argv) + + if (ent_pw && ent_pw->pw_uid != 0) + { +-#if defined(HAVE_LINUX_NETWORK) ++#if defined(HAVE_LINUX_NETWORK) ++ if (!hdr || !data) ++ { ++ /* Just failsafe for logic errors */ ++ send_event(err_pipe[1], EVENT_CAP_ERR, ENOMEM, NULL); ++ _exit(0); ++ } ++ + /* On linux, 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, +diff --git a/src/option.c b/src/option.c +index 9768efb..b12183b 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -4255,7 +4255,7 @@ err: + struct name_list *nl; + if (!canon) + { +- struct name_list *tmp = new->names, *next; ++ struct name_list *tmp, *next; + for (tmp = new->names; tmp; tmp = next) + { + next = tmp->next; +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.79-rh1602477.patch b/SOURCES/dnsmasq-2.79-rh1602477.patch new file mode 100644 index 0000000..c896db1 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1602477.patch @@ -0,0 +1,1686 @@ +From 8e11d702921e51a5eb00b9ee12925cae69039c22 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Thu, 18 Jul 2019 18:49:30 +0200 +Subject: [PATCH] Fix issues detected by Coverity +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Squashed commit of the following: + +commit 6a5fa6c9207961a662f8debbe9172500c752e3ac +Author: Kevin Darbyshire-Bryant +Date: Mon Dec 10 10:34:35 2018 +0000 + + build failure on master with NO_DHCPv6 and fix.... + + Hi Simon, + + master has a build error when building without HAVE_DHCPv6 + + option.c: In function 'dhcp_context_free': + option.c:1042:15: error: 'struct dhcp_context' has no member named 'template_interface' + free(ctx->template_interface); + + Sadly, need to put in a little conditional compilation ifdef'erey + + Simplest patch in the world attached + + Cheers, + + Kevin D-B + + 012C ACB2 28C6 C53E 9775 9123 B3A2 389B 9DE2 334A + + From 061eb8599636bb360e0b7fa5986935b86db39497 Mon Sep 17 00:00:00 2001 + From: Kevin Darbyshire-Bryant + Date: Mon, 10 Dec 2018 10:07:33 +0000 + Subject: [PATCH] option: fix non DHCPv6 build error + + option.c: In function 'dhcp_context_free': + option.c:1042:15: error: 'struct dhcp_context' has no member named 'template_interface' + free(ctx->template_interface); + ^~ + + Signed-off-by: Kevin Darbyshire-Bryant + (cherry picked from commit b683cf37f9f3dd3dc5d95d621ee75850d559b2e4) + +commit a4be120618a5d8517d23e687003cab53e7db11c9 +Author: Petr Menšík +Date: Sun Dec 16 21:25:29 2018 +0000 + + Fix option parsing errors introduced in 59e470381f84f2fdf0640c7bc67827f3f0c64784 + + Thanks to Kevin Darbyshire-Bryant for spotting this. + + (cherry picked from commit 137e9f878fafb38369eab7d9dfe84e4228ff5f89) + +commit 3e0752faa67ffd25893ebbcbe6a5788699a2e1c9 +Author: Petr Menšík +Date: Fri Nov 2 22:39:39 2018 +0000 + + Free config file values on parsing errors. + + This time I have a little bit more controversal patches. But I think + still useful. They fixes memory leaks that might occur in some cases. + Most dnsmasq errors is fatal, so it does not matter. But some are not. + Some parts are reloaded on SIGHUP signal, so it might leak more than once. + + Some example when it changes the failures. Use dhcp-options file with + this content: + + tag:error,vendor:redhat + option:ntp-server,1.2.3.4.5 + option6:ntp-server,[:::] + + Is not fatal and dnsmasq will start. On each reload command, it would + leak some memory. I validated it using valgrind --leak-check=full + dnsmasq -d. This patch fixes it. It introduces something that might be + considered constructor and destructor of selected structures. + + (cherry picked from commit 59e470381f84f2fdf0640c7bc67827f3f0c64784) + +commit c2a44c21dddffff95346c931feda696704ea73ca +Author: Petr Menšík +Date: Wed Oct 24 22:30:18 2018 +0100 + + Do not rely on dead code elimination, use array instead. + Make options bits derived from size and count. Use size of option bits + and last supported bit in computation. No new change would be required + when new options are added. Just change OPT_LAST constant. + + (cherry picked from commit 24b87607c1353e94689e8a2190571ab3f3b36f31) + +commit 5b9f199a1b16b7aa41cf544e9312c93e893206b3 +Author: Petr Menšík +Date: Fri Aug 17 10:20:05 2018 +0200 + + Minor improvements in lease-tools + + Limit max interface name to fit into buffer. + Make sure pointer have to be always positive. + Close socket after received reply. + + (cherry picked from commit 2b38e3823b12ab13f86c3a44648de436daadb1f6) + +commit d30a8f4c46a1b446b7d9932d022a09f1ee6b4554 +Author: Petr Menšík +Date: Thu Aug 16 15:48:15 2018 +0200 + + Close socket after received reply + +commit 6e5dedbb8aa3d27c9477558e66f9d260414340a3 +Author: Petr Menšík +Date: Wed Aug 15 19:41:07 2018 +0200 + + Mark die function as never returning + + Improves static analysis output and reduces false positives. + +commit 3d7e9ba115d3c229d678814103dbf3401738dcf5 +Author: Petr Menšík +Date: Wed Aug 15 18:17:00 2018 +0200 + + Fix lengths of interface names + + Use helper function similar to copy correctly limited names into + buffers. +--- + contrib/lease-tools/dhcp_lease_time.c | 2 +- + contrib/lease-tools/dhcp_release.c | 3 +- + contrib/lease-tools/dhcp_release6.c | 5 +- + src/bpf.c | 2 +- + src/dhcp.c | 9 +- + src/dnsmasq.h | 20 +- + src/log.c | 2 +- + src/network.c | 12 +- + src/option.c | 548 +++++++++++++++++--------- + src/rfc2131.c | 10 +- + src/tftp.c | 2 +- + src/util.c | 12 +- + 12 files changed, 408 insertions(+), 219 deletions(-) + +diff --git a/contrib/lease-tools/dhcp_lease_time.c b/contrib/lease-tools/dhcp_lease_time.c +index f9d7a85..697d627 100644 +--- a/contrib/lease-tools/dhcp_lease_time.c ++++ b/contrib/lease-tools/dhcp_lease_time.c +@@ -83,7 +83,7 @@ static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt + if (p >= end - 2) + return NULL; /* malformed packet */ + opt_len = option_len(p); +- if (p >= end - (2 + opt_len)) ++ if (end - p >= (2 + opt_len)) + return NULL; /* malformed packet */ + if (*p == opt && opt_len >= minsize) + return p; +diff --git a/contrib/lease-tools/dhcp_release.c b/contrib/lease-tools/dhcp_release.c +index 201fcd3..59883d4 100644 +--- a/contrib/lease-tools/dhcp_release.c ++++ b/contrib/lease-tools/dhcp_release.c +@@ -270,7 +270,8 @@ int main(int argc, char **argv) + + /* This voodoo fakes up a packet coming from the correct interface, which really matters for + a DHCP server */ +- strcpy(ifr.ifr_name, argv[1]); ++ strncpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name)-1); ++ ifr.ifr_name[sizeof(ifr.ifr_name)-1] = '\0'; + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + { + perror("cannot setup interface"); +diff --git a/contrib/lease-tools/dhcp_release6.c b/contrib/lease-tools/dhcp_release6.c +index 7f79fa7..d680222 100644 +--- a/contrib/lease-tools/dhcp_release6.c ++++ b/contrib/lease-tools/dhcp_release6.c +@@ -376,9 +376,12 @@ int send_release_packet(const char* iface, struct dhcp6_packet* packet) + sleep(1); + continue; + } ++ ++ close(sock); + return result; + } +- ++ ++ close(sock); + fprintf(stderr, "Response timed out\n"); + return -1; + } +diff --git a/src/bpf.c b/src/bpf.c +index 49a11bf..ff66d6d 100644 +--- a/src/bpf.c ++++ b/src/bpf.c +@@ -169,7 +169,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + struct in6_ifreq ifr6; + + memset(&ifr6, 0, sizeof(ifr6)); +- strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name)); ++ 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) +diff --git a/src/dhcp.c b/src/dhcp.c +index 5a8daec..26f3287 100644 +--- a/src/dhcp.c ++++ b/src/dhcp.c +@@ -232,7 +232,7 @@ void dhcp_packet(time_t now, int pxe_fd) + + #ifdef HAVE_LINUX_NETWORK + /* ARP fiddling uses original interface even if we pretend to use a different one. */ +- strncpy(arp_req.arp_dev, ifr.ifr_name, 16); ++ 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 +@@ -255,7 +255,7 @@ void dhcp_packet(time_t now, int pxe_fd) + } + else + { +- strncpy(ifr.ifr_name, bridge->iface, IF_NAMESIZE); ++ safe_strncpy(ifr.ifr_name, bridge->iface, sizeof(ifr.ifr_name)); + break; + } + } +@@ -279,7 +279,7 @@ void dhcp_packet(time_t now, int pxe_fd) + is_relay_reply = 1; + iov.iov_len = sz; + #ifdef HAVE_LINUX_NETWORK +- strncpy(arp_req.arp_dev, ifr.ifr_name, 16); ++ safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev)); + #endif + } + else +@@ -988,8 +988,7 @@ char *host_from_dns(struct in_addr addr) + if (!legal_hostname(hostname)) + return NULL; + +- strncpy(daemon->dhcp_buff, hostname, 256); +- daemon->dhcp_buff[255] = 0; ++ safe_strncpy(daemon->dhcp_buff, hostname, 256); + strip_hostname(daemon->dhcp_buff); + + return daemon->dhcp_buff; +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 6773b69..6b18bb7 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -42,6 +42,12 @@ + # 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 + #include +@@ -190,9 +196,6 @@ struct event_desc { + #define EC_MISC 5 + #define EC_INIT_OFFSET 10 + +-/* Trust the compiler dead-code eliminator.... */ +-#define option_bool(x) (((x) < 32) ? daemon->options & (1u << (x)) : daemon->options2 & (1u << ((x) - 32))) +- + #define OPT_BOGUSPRIV 0 + #define OPT_FILTER 1 + #define OPT_LOG 2 +@@ -252,6 +255,12 @@ struct event_desc { + #define OPT_TFTP_APREF_MAC 56 + #define OPT_LAST 57 + ++#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 a couple of facilities since they are known + not to occupy the same bits as priorities, no matter how syslog.h is set up. */ + #define MS_TFTP LOG_USER +@@ -947,7 +956,7 @@ extern struct daemon { + config file arguments. All set (including defaults) + in option.c */ + +- unsigned int options, options2; ++ unsigned int options[OPTION_SIZE]; + struct resolvc default_resolv, *resolv_files; + time_t last_resolv; + char *servers_file; +@@ -1205,6 +1214,7 @@ 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); +@@ -1233,7 +1243,7 @@ int wildcard_match(const char* wildcard, const char* match); + int wildcard_matchn(const char* wildcard, const char* match, int num); + + /* log.c */ +-void die(char *message, char *arg1, int exit_code); ++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); + +diff --git a/src/log.c b/src/log.c +index dae8a75..d0d4780 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -232,7 +232,7 @@ static void log_write(void) + logaddr.sun_len = sizeof(logaddr) - sizeof(logaddr.sun_path) + strlen(_PATH_LOG) + 1; + #endif + logaddr.sun_family = AF_UNIX; +- strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); ++ 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) +diff --git a/src/network.c b/src/network.c +index 0381513..efb7b03 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -29,7 +29,7 @@ int indextoname(int fd, int index, char *name) + if (ioctl(fd, SIOCGIFNAME, &ifr) == -1) + return 0; + +- strncpy(name, ifr.ifr_name, IF_NAMESIZE); ++ safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE); + + return 1; + } +@@ -82,12 +82,12 @@ int indextoname(int fd, int index, char *name) + for (i = lifc.lifc_len / sizeof(struct lifreq); i; i--, lifrp++) + { + struct lifreq lifr; +- strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE); ++ safe_strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE); + if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0) + return 0; + + if (lifr.lifr_index == index) { +- strncpy(name, lifr.lifr_name, IF_NAMESIZE); ++ safe_strncpy(name, lifr.lifr_name, IF_NAMESIZE); + return 1; + } + } +@@ -188,7 +188,7 @@ int loopback_exception(int fd, int family, struct all_addr *addr, char *name) + struct ifreq ifr; + struct irec *iface; + +- strncpy(ifr.ifr_name, name, IF_NAMESIZE); ++ safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); + if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 && + ifr.ifr_flags & IFF_LOOPBACK) + { +@@ -1284,7 +1284,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) + return NULL; + } + +- strcpy(sfd->interface, intname); ++ safe_strncpy(sfd->interface, intname, sizeof(sfd->interface)); + sfd->source_addr = *addr; + sfd->next = daemon->sfds; + sfd->ifindex = ifindex; +@@ -1452,7 +1452,7 @@ void add_update_server(int flags, + serv->flags |= SERV_HAS_DOMAIN; + + if (interface) +- strcpy(serv->interface, interface); ++ safe_strncpy(serv->interface, interface, sizeof(serv->interface)); + if (addr) + serv->addr = *addr; + if (source_addr) +diff --git a/src/option.c b/src/option.c +index d358d99..9768efb 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -559,14 +559,15 @@ static void *opt_malloc(size_t size) + return ret; + } + +-static char *opt_string_alloc(char *cp) ++static char *opt_string_alloc(const char *cp) + { + char *ret = NULL; ++ size_t len; + +- if (cp && strlen(cp) != 0) ++ if (cp && (len = strlen(cp)) != 0) + { +- ret = opt_malloc(strlen(cp)+1); +- strcpy(ret, cp); ++ ret = opt_malloc(len+1); ++ memcpy(ret, cp, len+1); + + /* restore hidden metachars */ + unhide_metas(ret); +@@ -741,6 +742,8 @@ static void do_usage(void) + } + + #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) + { +@@ -792,7 +795,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + if (interface_opt) + { + #if defined(SO_BINDTODEVICE) +- strncpy(interface, interface_opt, IF_NAMESIZE - 1); ++ safe_strncpy(interface, interface_opt, IF_NAMESIZE); + #else + return _("interface binding not supported"); + #endif +@@ -821,7 +824,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + return _("interface can only be specified once"); + + source_addr->in.sin_addr.s_addr = INADDR_ANY; +- strncpy(interface, source, IF_NAMESIZE - 1); ++ safe_strncpy(interface, source, IF_NAMESIZE); + #else + return _("interface binding not supported"); + #endif +@@ -856,7 +859,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + return _("interface can only be specified once"); + + source_addr->in6.sin6_addr = in6addr_any; +- strncpy(interface, source, IF_NAMESIZE - 1); ++ safe_strncpy(interface, source, IF_NAMESIZE); + #else + return _("interface binding not supported"); + #endif +@@ -894,6 +897,8 @@ static struct server *add_rev4(struct in_addr addr, int msize) + p += sprintf(p, "%d.", (a >> 24) & 0xff); + break; + default: ++ free(serv->domain); ++ free(serv); + return NULL; + } + +@@ -948,6 +953,99 @@ static char *set_prefix(char *arg) + 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); ++ if (config->flags & CONFIG_CLID) ++ free(config->clid); ++ 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) + { +@@ -955,7 +1053,6 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + char lenchar = 0, *cp; + int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots; + char *comma = NULL; +- struct dhcp_netid *np = NULL; + u16 opt_len = 0; + int is6 = 0; + int option_ok = 0; +@@ -1042,14 +1139,9 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + } + else + { +- new->netid = opt_malloc(sizeof (struct dhcp_netid)); + /* allow optional "net:" or "tag:" for consistency */ +- if (is_tag_prefix(arg)) +- new->netid->net = opt_string_alloc(arg+4); +- else +- new->netid->net = opt_string_alloc(set_prefix(arg)); +- new->netid->next = np; +- np = new->netid; ++ const char *name = (is_tag_prefix(arg)) ? arg+4 : set_prefix(arg); ++ new->netid = dhcp_netid_create(name, new->netid); + } + + arg = comma; +@@ -1059,7 +1151,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + if (is6) + { + if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE)) +- ret_err(_("unsupported encapsulation for IPv6 option")); ++ goto_err(_("unsupported encapsulation for IPv6 option")); + + if (opt_len == 0 && + !(new->flags & DHOPT_RFC3925)) +@@ -1073,7 +1165,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + + /* option may be missing with rfc3925 match */ + if (!option_ok) +- ret_err(_("bad dhcp-option")); ++ goto_err(_("bad dhcp-option")); + + if (comma) + { +@@ -1141,10 +1233,10 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + is_string = is_dec = is_hex = 0; + + if (!is6 && (!is_addr || dots == 0)) +- ret_err(_("bad IP address")); ++ goto_err(_("bad IP address")); + + if (is6 && !is_addr6) +- ret_err(_("bad IPv6 address")); ++ goto_err(_("bad IPv6 address")); + } + /* or names */ + else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING)) +@@ -1237,7 +1329,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + comma = split(cp); + slash = split_chr(cp, '/'); + if (!inet_pton(AF_INET, cp, &in)) +- ret_err(_("bad IPv4 address")); ++ goto_err(_("bad IPv4 address")); + if (!slash) + { + memcpy(op, &in, INADDRSZ); +@@ -1282,8 +1374,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + op += IN6ADDRSZ; + continue; + } +- +- ret_err(_("bad IPv6 address")); ++ ++ goto_err(_("bad IPv6 address")); + } + new->len = op - new->val; + } +@@ -1310,7 +1402,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + if (strcmp (arg, ".") != 0) + { + if (!(dom = canonicalise_opt(arg))) +- ret_err(_("bad domain in dhcp-option")); ++ goto_err(_("bad domain in dhcp-option")); + + domlen = strlen(dom) + 2; + } +@@ -1404,7 +1496,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + { + char *dom = canonicalise_opt(arg); + if (!dom) +- ret_err(_("bad domain in dhcp-option")); ++ goto_err(_("bad domain in dhcp-option")); + + newp = opt_malloc(len + strlen(dom) + 2); + +@@ -1442,14 +1534,14 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + ((new->len > 255) || + (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) || + (new->len > 250 && (new->flags & DHOPT_RFC3925)))) +- ret_err(_("dhcp-option too long")); ++ goto_err(_("dhcp-option too long")); + + if (flags == DHOPT_MATCH) + { + if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || + !new->netid || + new->netid->next) +- ret_err(_("illegal dhcp-match")); ++ goto_err(_("illegal dhcp-match")); + + if (is6) + { +@@ -1474,24 +1566,31 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) + } + + return 1; ++on_error: ++ dhcp_opt_free(new); ++ return 0; + } + + #endif + + void set_option_bool(unsigned int opt) + { +- if (opt < 32) +- daemon->options |= 1u << opt; +- else +- daemon->options2 |= 1u << (opt - 32); ++ option_var(opt) |= option_val(opt); + } + + void reset_option_bool(unsigned int opt) + { +- if (opt < 32) +- daemon->options &= ~(1u << opt); +- else +- daemon->options2 &= ~(1u << (opt - 32)); ++ option_var(opt) &= ~(option_val(opt)); ++} ++ ++static void server_list_free(struct server *list) ++{ ++ while (list) ++ { ++ struct server *tmp = list; ++ list = list->next; ++ free(tmp); ++ } + } + + static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only) +@@ -1675,13 +1774,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + /* has subnet+len */ + err = parse_mysockaddr(arg, &new->addr); + if (err) +- ret_err(err); ++ ret_err_free(err, new); + if (!atoi_check(end, &new->mask)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + new->addr_used = 1; + } + else if (!atoi_check(arg, &new->mask)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + + daemon->add_subnet4 = new; + +@@ -1693,15 +1792,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + /* has subnet+len */ + err = parse_mysockaddr(comma, &new->addr); + if (err) +- ret_err(err); ++ ret_err_free(err, new); + if (!atoi_check(end, &new->mask)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + new->addr_used = 1; + } + else + { + if (!atoi_check(comma, &new->mask)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + } + + daemon->add_subnet6 = new; +@@ -1908,7 +2007,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->addr.sa.sa_family = AF_INET6; + #endif + else +- ret_err(gen_err); ++ { ++ free(new->name); ++ ret_err_free(gen_err, new); ++ } + } + } + new->next = daemon->authinterface; +@@ -2080,7 +2182,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + arg = split(netpart); + if (!atoi_check(netpart, &msize)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + else if (inet_pton(AF_INET, comma, &new->start)) + { + int mask = (1 << (32 - msize)) - 1; +@@ -2093,18 +2195,18 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + { + if (!(new->prefix = canonicalise_opt(arg)) || + strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) +- ret_err(_("bad prefix")); ++ ret_err_free(_("bad prefix"), new); + } + else if (strcmp(arg, "local") != 0 || + (msize != 8 && msize != 16 && msize != 24)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + else + { + /* generate the equivalent of + local=/xxx.yyy.zzz.in-addr.arpa/ */ + struct server *serv = add_rev4(new->start, msize); + if (!serv) +- ret_err(_("bad prefix")); ++ ret_err_free(_("bad prefix"), new); + + serv->flags |= SERV_NO_ADDR; + +@@ -2134,17 +2236,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + setaddr6part(&new->end6, addrpart | mask); + + if (msize < 64) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + else if (arg) + { + if (option != 's') + { + if (!(new->prefix = canonicalise_opt(arg)) || + strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN) +- ret_err(_("bad prefix")); ++ ret_err_free(_("bad prefix"), new); + } + else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + else + { + /* generate the equivalent of +@@ -2164,7 +2266,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + #endif + else +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + } + else + { +@@ -2178,7 +2280,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + if (!arg) + new->end.s_addr = new->start.s_addr; + else if (!inet_pton(AF_INET, arg, &new->end)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + } + #ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, comma, &new->start6)) +@@ -2187,17 +2289,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + if (!arg) + memcpy(&new->end6, &new->start6, IN6ADDRSZ); + else if (!inet_pton(AF_INET6, arg, &new->end6)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + } + #endif + else +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + + if (option != 's' && prefstr) + { + if (!(new->prefix = canonicalise_opt(prefstr)) || + strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) +- ret_err(_("bad prefix")); ++ ret_err_free(_("bad prefix"), new); + } + } + +@@ -2359,7 +2461,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + #endif + else +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + + new->used = 0; + if (option == 'a') +@@ -2430,7 +2532,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + { + newlist->flags |= SERV_LITERAL_ADDRESS; + if (!(newlist->flags & SERV_TYPE)) +- ret_err(gen_err); ++ { ++ server_list_free(newlist); ++ ret_err(gen_err); ++ } + } + else if (option == LOPT_NO_REBIND) + newlist->flags |= SERV_NO_REBIND; +@@ -2451,7 +2556,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + { + char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); + if (err) +- ret_err(err); ++ { ++ server_list_free(newlist); ++ ret_err(err); ++ } + } + + serv = newlist; +@@ -2801,21 +2909,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + { + if (is_tag_prefix(arg)) + { +- struct dhcp_netid *tt = opt_malloc(sizeof (struct dhcp_netid)); +- tt->net = opt_string_alloc(arg+4); +- tt->next = new->filter; + /* ignore empty tag */ +- if (tt->net) +- new->filter = tt; ++ if (arg[4]) ++ new->filter = dhcp_netid_create(arg+4, new->filter); + } + else + { + if (new->netid.net) +- ret_err(_("only one tag allowed")); +- else if (strstr(arg, "set:") == arg) +- new->netid.net = opt_string_alloc(arg+4); ++ { ++ dhcp_context_free(new); ++ ret_err(_("only one tag allowed")); ++ } + else +- new->netid.net = opt_string_alloc(arg); ++ new->netid.net = opt_string_alloc(set_prefix(arg)); + } + arg = comma; + } +@@ -2831,7 +2937,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + break; + + if (k < 2) +- ret_err(_("bad dhcp-range")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("bad dhcp-range")); ++ } + + if (inet_pton(AF_INET, a[0], &new->start)) + { +@@ -2843,7 +2952,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + else if (strcmp(a[1], "proxy") == 0) + new->flags |= CONTEXT_PROXY; + else if (!inet_pton(AF_INET, a[1], &new->end)) +- ret_err(_("bad dhcp-range")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("bad dhcp-range")); ++ } + + if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr)) + { +@@ -2858,7 +2970,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->flags |= CONTEXT_NETMASK; + leasepos = 3; + if (!is_same_net(new->start, new->end, new->netmask)) +- ret_err(_("inconsistent DHCP range")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("inconsistent DHCP range")); ++ } + + + if (k >= 4 && strchr(a[3], '.') && +@@ -2872,6 +2987,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + #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; +@@ -2917,19 +3034,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + } + +- if (new->prefix != 64) ++ if (new->prefix > 64) + { + if (new->flags & CONTEXT_RA) +- ret_err(_("prefix length must be exactly 64 for RA subnets")); ++ err=(_("prefix length must be exactly 64 for RA subnets")); + else if (new->flags & CONTEXT_TEMPLATE) +- ret_err(_("prefix length must be exactly 64 for subnet constructors")); ++ err=(_("prefix length must be exactly 64 for subnet constructors")); + } +- +- if (new->prefix < 64) +- ret_err(_("prefix length must be at least 64")); ++ else if (new->prefix < 64) ++ err=(_("prefix length must be at least 64")); + +- if (!is_same_net6(&new->start6, &new->end6, new->prefix)) +- ret_err(_("inconsistent DHCPv6 range")); ++ 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)) +@@ -2940,7 +3062,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + struct in6_addr zero; + memset(&zero, 0, sizeof(zero)); + if (!is_same_net6(&zero, &new->start6, new->prefix)) +- ret_err(_("prefix must be zero with \"constructor:\" argument")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("prefix must be zero with \"constructor:\" argument")); ++ } + } + + if (addr6part(&new->start6) > addr6part(&new->end6)) +@@ -2952,12 +3077,18 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + #endif + else +- ret_err(_("bad dhcp-range")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("bad dhcp-range")); ++ } + + if (leasepos < k) + { + if (leasepos != k-1) +- ret_err(_("bad dhcp-range")); ++ { ++ dhcp_context_free(new); ++ ret_err(_("bad dhcp-range")); ++ } + + if (strcmp(a[leasepos], "infinite") == 0) + new->lease_time = 0xffffffff; +@@ -2996,7 +3127,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + break; + + if (*cp || (leasepos+1 < k)) +- ret_err(_("bad dhcp-range")); ++ ret_err_free(_("bad dhcp-range"), new); + + new->lease_time = atoi(a[leasepos]) * fac; + /* Leases of a minute or less confuse +@@ -3023,6 +3154,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0; + new->hwaddr = NULL; + new->netid = NULL; ++ new->clid = NULL; + + if ((a[0] = arg)) + for (k = 1; k < 7; k++) +@@ -3053,7 +3185,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + + if (len == -1) +- ret_err(_("bad hex constant")); ++ { ++ dhcp_config_free(new); ++ ret_err(_("bad hex constant")); ++ } + else if ((new->clid = opt_malloc(len))) + { + new->flags |= CONFIG_CLID; +@@ -3065,17 +3200,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + /* dhcp-host has strange backwards-compat needs. */ + else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) + { +- struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); + struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); +- newtag->net = opt_malloc(strlen(arg + 4) + 1); + newlist->next = new->netid; + new->netid = newlist; +- newlist->list = newtag; +- strcpy(newtag->net, arg+4); +- unhide_metas(newtag->net); ++ newlist->list = dhcp_netid_create(arg+4, NULL); + } + else if (strstr(arg, "tag:") == arg) +- ret_err(_("cannot match tags in --dhcp-host")); ++ { ++ ++ dhcp_config_free(new); ++ ret_err(_("cannot match tags in --dhcp-host")); ++ } + #ifdef HAVE_DHCP6 + else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') + { +@@ -3083,7 +3218,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + arg++; + + if (!inet_pton(AF_INET6, arg, &new->addr6)) +- ret_err(_("bad IPv6 address")); ++ { ++ dhcp_config_free(new); ++ ret_err(_("bad IPv6 address")); ++ } + + for (i= 0; i < 8; i++) + if (new->addr6.s6_addr[i] != 0) +@@ -3101,10 +3239,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); + if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, + &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) +- ret_err(_("bad hex constant")); ++ { ++ free(newhw); ++ dhcp_config_free(new); ++ ret_err(_("bad hex constant")); ++ } + else + { +- + newhw->next = new->hwaddr; + new->hwaddr = newhw; + } +@@ -3181,7 +3322,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + { + if (!(new->hostname = canonicalise_opt(a[j])) || + !legal_hostname(new->hostname)) +- ret_err(_("bad DHCP host name")); ++ { ++ dhcp_config_free(new); ++ ret_err(_("bad DHCP host name")); ++ } + + new->flags |= CONFIG_NAME; + new->domain = strip_hostname(new->hostname); +@@ -3234,10 +3378,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + else + { +- struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); +- newtag->net = opt_malloc(len - 3); +- strcpy(newtag->net, arg+4); +- unhide_metas(newtag->net); ++ struct dhcp_netid *newtag = dhcp_netid_create(arg+4, NULL); + + if (strstr(arg, "set:") == arg) + { +@@ -3254,7 +3395,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + else + { + new->set = NULL; +- free(newtag); ++ dhcp_netid_free(newtag); + break; + } + } +@@ -3263,7 +3404,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + + if (!new->set) +- ret_err(_("bad tag-if")); ++ { ++ dhcp_netid_free(new->tag); ++ dhcp_netid_list_free(new->set); ++ ret_err_free(_("bad tag-if"), new); ++ } + + break; + } +@@ -3280,19 +3425,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + case 'M': /* --dhcp-boot */ + { +- struct dhcp_netid *id = NULL; +- while (is_tag_prefix(arg)) +- { +- struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid)); +- newid->next = id; +- id = newid; +- comma = split(arg); +- newid->net = opt_string_alloc(arg+4); +- arg = comma; +- }; ++ struct dhcp_netid *id = dhcp_tags(&arg); + + if (!arg) +- ret_err(gen_err); ++ { ++ ret_err(gen_err); ++ } + else + { + char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL; +@@ -3338,19 +3476,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */ + { +- struct dhcp_netid *id = NULL; +- while (is_tag_prefix(arg)) +- { +- struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid)); +- newid->next = id; +- id = newid; +- comma = split(arg); +- newid->net = opt_string_alloc(arg+4); +- arg = comma; +- }; ++ struct dhcp_netid *id = dhcp_tags(&arg); + + if (!arg) +- ret_err(gen_err); ++ { ++ ret_err(gen_err); ++ } + else + { + struct delay_config *new; +@@ -3375,19 +3506,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + new->netid = NULL; + new->opt = 10; /* PXE_MENU_PROMPT */ +- +- while (is_tag_prefix(arg)) +- { +- struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); +- comma = split(arg); +- nn->next = new->netid; +- new->netid = nn; +- nn->net = opt_string_alloc(arg+4); +- arg = comma; +- } ++ new->netid = dhcp_tags(&arg); + + if (!arg) +- ret_err(gen_err); ++ { ++ dhcp_opt_free(new); ++ ret_err(gen_err); ++ } + else + { + comma = split(arg); +@@ -3423,17 +3548,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->netid = NULL; + new->sname = NULL; + new->server.s_addr = 0; ++ new->netid = dhcp_tags(&arg); + +- while (is_tag_prefix(arg)) +- { +- struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid)); +- comma = split(arg); +- nn->next = new->netid; +- new->netid = nn; +- nn->net = opt_string_alloc(arg+4); +- arg = comma; +- } +- + if (arg && (comma = split(arg))) + { + for (i = 0; CSA[i]; i++) +@@ -3510,7 +3626,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + unhide_metas(comma); + new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type); + if (new->hwaddr_len == -1) +- ret_err(gen_err); ++ { ++ free(new->netid.net); ++ ret_err_free(gen_err, new); ++ } + else + { + new->next = daemon->dhcp_macs; +@@ -3527,7 +3646,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + if (!(comma = split(arg)) || + !atoi_check16(comma, &new->class)) +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + + new->tag.net = opt_string_alloc(set_prefix(arg)); + new->next = daemon->prefix_classes; +@@ -3549,7 +3668,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); + + if (!(comma = split(arg))) +- ret_err(gen_err); ++ 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, +@@ -3559,7 +3678,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + if ((comma = split(arg))) + { + if (option != 'U' || strstr(arg, "enterprise:") != arg) +- ret_err(gen_err); ++ { ++ free(new->netid.net); ++ ret_err_free(gen_err, new); ++ } + else + new->enterprise = atoi(arg+11); + } +@@ -3661,14 +3783,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + + while (arg) { +- struct dhcp_netid *member = opt_malloc(sizeof(struct dhcp_netid)); + comma = split(arg); +- member->next = list; +- list = member; +- if (is_tag_prefix(arg)) +- member->net = opt_string_alloc(arg+4); +- else +- member->net = opt_string_alloc(arg); ++ list = dhcp_netid_create(is_tag_prefix(arg) ? arg+4 :arg, list); + arg = comma; + } + +@@ -3682,7 +3798,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + struct addr_list *new = opt_malloc(sizeof(struct addr_list)); + comma = split(arg); + if (!(inet_pton(AF_INET, arg, &new->addr) > 0)) +- ret_err(_("bad dhcp-proxy address")); ++ ret_err_free(_("bad dhcp-proxy address"), new); + new->next = daemon->override_relays; + daemon->override_relays = new; + arg = comma; +@@ -3708,7 +3824,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + } + #endif + else +- ret_err(_("Bad dhcp-relay")); ++ { ++ free(new->interface); ++ ret_err_free(_("Bad dhcp-relay"), new); ++ } + + break; + } +@@ -3748,8 +3867,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + arg = split(comma); + if (!atoi_check(comma, &new->interval) || + (arg && !atoi_check(arg, &new->lifetime))) ++ { + err: +- ret_err(_("bad RA-params")); ++ free(new->name); ++ ret_err_free(_("bad RA-params"), new); ++ } + + new->next = daemon->ra_interfaces; + daemon->ra_interfaces = new; +@@ -3800,7 +3922,7 @@ err: + (!(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(_("invalid alias range")); ++ ret_err_free(_("invalid alias range"), new); + + break; + } +@@ -3835,7 +3957,7 @@ err: + new->family = AF_INET6; + #endif + else +- ret_err(gen_err); ++ ret_err_free(gen_err, new); + } + new->intr = opt_string_alloc(comma); + break; +@@ -3867,11 +3989,19 @@ err: + alias = canonicalise_opt(arg); + + if (!alias || !target) +- ret_err(_("bad CNAME")); ++ { ++ free(target); ++ free(alias); ++ ret_err(_("bad CNAME")); ++ } + + for (new = daemon->cnames; new; new = new->next) + if (hostname_isequal(new->alias, alias)) +- ret_err(_("duplicate CNAME")); ++ { ++ free(target); ++ free(alias); ++ ret_err(_("duplicate CNAME")); ++ } + new = opt_malloc(sizeof(struct cname)); + new->next = daemon->cnames; + daemon->cnames = new; +@@ -3894,7 +4024,11 @@ err: + + if (!(dom = canonicalise_opt(arg)) || + (comma && !(target = canonicalise_opt(comma)))) +- ret_err(_("bad PTR record")); ++ { ++ free(dom); ++ free(target); ++ ret_err(_("bad PTR record")); ++ } + else + { + new = opt_malloc(sizeof(struct ptr_record)); +@@ -3912,7 +4046,7 @@ err: + int k = 0; + struct naptr *new; + int order, pref; +- char *name, *replace = NULL; ++ char *name=NULL, *replace = NULL; + + if ((a[0] = arg)) + for (k = 1; k < 7; k++) +@@ -3925,7 +4059,11 @@ err: + !atoi_check16(a[1], &order) || + !atoi_check16(a[2], &pref) || + (k == 7 && !(replace = canonicalise_opt(a[6])))) +- ret_err(_("bad NAPTR record")); ++ { ++ free(name); ++ free(replace); ++ ret_err(_("bad NAPTR record")); ++ } + else + { + new = opt_malloc(sizeof(struct naptr)); +@@ -3947,22 +4085,26 @@ err: + struct txt_record *new; + size_t len = 0; + char *data; +- int val; ++ int class; + + comma = split(arg); + data = split(comma); + + new = opt_malloc(sizeof(struct txt_record)); +- new->next = daemon->rr; +- daemon->rr = new; ++ new->name = NULL; + +- if (!atoi_check(comma, &val) || ++ if (!atoi_check(comma, &class) || + !(new->name = canonicalise_opt(arg)) || + (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U)) +- ret_err(_("bad RR record")); +- +- new->class = val; ++ { ++ 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) + { +@@ -3983,14 +4125,14 @@ err: + comma = split(arg); + + new = opt_malloc(sizeof(struct txt_record)); +- new->next = daemon->txt; +- daemon->txt = new; + new->class = C_IN; + new->stat = 0; + + if (!(new->name = canonicalise_opt(arg))) +- ret_err(_("bad TXT record")); ++ 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); +@@ -4037,24 +4179,32 @@ err: + arg = comma; + comma = split(arg); + if (!(target = canonicalise_opt(arg))) +- ret_err(_("bad SRV target")); ++ ret_err_free(_("bad SRV target"), name); + + if (comma) + { + arg = comma; + comma = split(arg); + if (!atoi_check16(arg, &port)) +- ret_err(_("invalid port number")); ++ { ++ free(name); ++ ret_err_free(_("invalid port number"), target); ++ } + + if (comma) + { + arg = comma; + comma = split(arg); + if (!atoi_check16(arg, &priority)) +- ret_err(_("invalid priority")); +- ++ { ++ free(name); ++ ret_err_free(_("invalid priority"), target); ++ } + if (comma && !atoi_check16(comma, &weight)) +- ret_err(_("invalid weight")); ++ { ++ free(name); ++ ret_err_free(_("invalid weight"), target); ++ } + } + } + } +@@ -4073,13 +4223,15 @@ err: + + case LOPT_HOST_REC: /* --host-record */ + { +- struct host_record *new = opt_malloc(sizeof(struct host_record)); +- memset(new, 0, sizeof(struct host_record)); +- new->ttl = -1; ++ 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; ++ + while (arg) + { + struct all_addr addr; +@@ -4100,10 +4252,19 @@ err: + { + int nomem; + char *canon = canonicalise(arg, &nomem); +- struct name_list *nl = opt_malloc(sizeof(struct name_list)); ++ struct name_list *nl; + if (!canon) +- ret_err(_("Bad name in host-record")); ++ { ++ 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; +@@ -4143,6 +4304,7 @@ err: + int len; + + new->class = C_IN; ++ new->name = NULL; + + if ((comma = split(arg)) && (algo = split(comma))) + { +@@ -4167,7 +4329,7 @@ err: + !atoi_check8(algo, &new->algo) || + !atoi_check8(digest, &new->digest_type) || + !(new->name = canonicalise_opt(arg))) +- ret_err(_("bad trust anchor")); ++ ret_err_free(_("bad trust anchor"), new); + + /* Upper bound on length */ + len = (2*strlen(keyhex))+1; +@@ -4181,7 +4343,10 @@ err: + else + cp++; + if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1) +- ret_err(_("bad HEX in trust anchor")); ++ { ++ free(new->name); ++ ret_err_free(_("bad HEX in trust anchor"), new); ++ } + + new->next = daemon->ds; + daemon->ds = new; +@@ -4650,8 +4815,8 @@ void read_opts(int argc, char **argv, char *compile_opts) + size_t argbuf_size = MAXDNAME; + char *argbuf = opt_malloc(argbuf_size); + char *buff = opt_malloc(MAXDNAME); +- int option, conffile_opt = '7', testmode = 0; +- char *arg, *conffile = CONFFILE; ++ int option, testmode = 0; ++ char *arg, *conffile = NULL; + + opterr = 0; + +@@ -4725,8 +4890,7 @@ void read_opts(int argc, char **argv, char *compile_opts) + argbuf_size = strlen(optarg) + 1; + argbuf = opt_malloc(argbuf_size); + } +- strncpy(argbuf, optarg, argbuf_size); +- argbuf[argbuf_size-1] = 0; ++ safe_strncpy(argbuf, optarg, argbuf_size); + arg = argbuf; + } + else +@@ -4761,7 +4925,8 @@ void read_opts(int argc, char **argv, char *compile_opts) + } + else if (option == 'C') + { +- conffile_opt = 0; /* file must exist */ ++ if (conffile) ++ free(conffile); + conffile = opt_string_alloc(arg); + } + else +@@ -4779,10 +4944,11 @@ void read_opts(int argc, char **argv, char *compile_opts) + + if (conffile) + { +- one_file(conffile, conffile_opt); +- if (conffile_opt == 0) +- free(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) +diff --git a/src/rfc2131.c b/src/rfc2131.c +index c08a8ab..997575a 100644 +--- a/src/rfc2131.c ++++ b/src/rfc2131.c +@@ -917,7 +917,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, + mess->siaddr = a_record_from_hosts(boot->tftp_sname, now); + + if (boot->file) +- strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); ++ safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); + } + + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, +@@ -2296,7 +2296,7 @@ static void do_options(struct dhcp_context *context, + in_list(req_options, OPTION_SNAME)) + option_put_string(mess, end, OPTION_SNAME, boot->sname, 1); + else +- strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)-1); ++ safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)); + } + + if (boot->file) +@@ -2306,7 +2306,7 @@ static void do_options(struct dhcp_context *context, + in_list(req_options, OPTION_FILENAME)) + option_put_string(mess, end, OPTION_FILENAME, boot->file, 1); + else +- strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); ++ safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); + } + + if (boot->next_server.s_addr) +@@ -2323,14 +2323,14 @@ static void do_options(struct dhcp_context *context, + if ((!req_options || !in_list(req_options, OPTION_FILENAME)) && + (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) + { +- strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1); ++ 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)) + { +- strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1); ++ safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)); + done_server = 1; + } + +diff --git a/src/tftp.c b/src/tftp.c +index bccca69..f2eccbc 100644 +--- a/src/tftp.c ++++ b/src/tftp.c +@@ -234,7 +234,7 @@ void tftp_request(struct listener *listen, time_t now) + #endif + } + +- strncpy(ifr.ifr_name, name, IF_NAMESIZE); ++ safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); + if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) + { + mtu = ifr.ifr_mtu; +diff --git a/src/util.c b/src/util.c +index 532bc16..e003e3c 100644 +--- a/src/util.c ++++ b/src/util.c +@@ -281,7 +281,17 @@ void *safe_malloc(size_t size) + die(_("could not get memory"), NULL, EC_NOMEM); + + return ret; +-} ++} ++ ++/* can be replaced by (void)strlcpy() on some platforms */ ++void safe_strncpy(char *dest, const char *src, size_t size) ++{ ++ if (size) ++ { ++ dest[size-1] = '\0'; ++ strncpy(dest, src, size-1); ++ } ++} + + void safe_pipe(int *fd, int read_noblock) + { +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.79-rh1700916.patch b/SOURCES/dnsmasq-2.79-rh1700916.patch new file mode 100644 index 0000000..0511e24 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1700916.patch @@ -0,0 +1,95 @@ +From 10642f9fb350e118d88e995b8dfa2badc7be1c30 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Wed, 11 Dec 2019 13:41:57 +0100 +Subject: [PATCH] Restore ability to answer non-recursive requests + +Instead, check only local configured entries are answered without +rdbit set. All cached replies are still denied, but locally configured +names are available with both recursion and without it. +--- + src/rfc1035.c | 27 ++++++++++++++------------- + 1 file changed, 14 insertions(+), 13 deletions(-) + +diff --git a/src/rfc1035.c b/src/rfc1035.c +index 6b3bb27..6a7c154 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1262,7 +1262,11 @@ static unsigned long crec_ttl(struct crec *crecp, time_t 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, +@@ -1281,6 +1285,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; + struct mx_srv_record *rec; + size_t len; ++ int rd_bit; + // Make sure we do not underflow here too. + if (qlen > (limit - ((char *)header))) return 0; + +@@ -1290,10 +1295,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + OPCODE(header) != QUERY ) + return 0; + +- /* always servfail queries with RD unset, to avoid cache snooping. */ +- if (!(header->hb3 & HB3_RD)) +- return setup_reply(header, qlen, NULL, F_SERVFAIL, 0); +- ++ rd_bit = (header->hb3 & HB3_RD); ++ + /* Don't return AD set if checking disabled. */ + if (header->hb4 & HB4_CD) + sec_data = 0; +@@ -1458,9 +1461,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + /* Don't use cache when DNSSEC data required, unless we know that + the zone is unsigned, which implies that we're doing + validation. */ +- if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || +- !do_bit || +- (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) ++ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || ++ (rd_bit && (!do_bit || cache_validated(crecp)) )) + { + do + { +@@ -1657,8 +1659,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + + /* If the client asked for DNSSEC don't use cached data. */ + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || +- !do_bit || +- (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) ++ (rd_bit && (!do_bit || cache_validated(crecp)) )) + do + { + /* don't answer wildcard queries with data not from /etc/hosts +@@ -1741,8 +1742,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + if (qtype == T_CNAME || qtype == T_ANY) + { + if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) && +- (qtype == T_CNAME || (crecp->flags & F_CONFIG)) && +- ((crecp->flags & F_CONFIG) || !do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))) ++ ((qtype == T_CNAME && rd_bit) || (crecp->flags & F_CONFIG)) && ++ ((crecp->flags & F_CONFIG) || (!do_bit || cache_validated(crecp)))) + { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; +@@ -1780,7 +1781,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + } + } + +- if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && ++ 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; +-- +2.21.0 + diff --git a/SOURCES/dnsmasq-2.79-rh1728698-2.patch b/SOURCES/dnsmasq-2.79-rh1728698-2.patch new file mode 100644 index 0000000..9c335e5 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1728698-2.patch @@ -0,0 +1,48 @@ +From 7e3250d52921b5f75bdbe0b794514bb78a209969 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Wed, 3 Jul 2019 17:02:16 +0200 +Subject: [PATCH 2/5] Compare address and interface index for allowed interface + +If interface is recreated with the same address but different index, it +would not change any other parameter. + +Test also address family on incoming TCP queries. +--- + src/dnsmasq.c | 3 ++- + src/network.c | 3 ++- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index f3d2671..7812be8 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -1667,7 +1667,8 @@ static void check_dns_listeners(time_t now) + #endif + + for (iface = daemon->interfaces; iface; iface = iface->next) +- if (iface->index == if_index) ++ 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)) +diff --git a/src/network.c b/src/network.c +index fd90288..f247811 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -404,10 +404,11 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, + /* 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)) ++ 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; + } + +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.79-rh1728698-4.patch b/SOURCES/dnsmasq-2.79-rh1728698-4.patch new file mode 100644 index 0000000..86d7e02 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1728698-4.patch @@ -0,0 +1,188 @@ +From 11ab42e63f9089c4c14a391f30175d4c2d071e99 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Mon, 15 Jul 2019 17:13:12 +0200 +Subject: [PATCH 4/5] Handle listening on duplicate addresses + +Save listening address into listener. Use it to find existing listeners +before creating new one. If it exist, increase just used counter. +Release only listeners not already used. + +Duplicates family in listener. +--- + src/dnsmasq.h | 3 +- + src/network.c | 115 ++++++++++++++++++++++++++++++++++++-------------- + 2 files changed, 85 insertions(+), 33 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 89d138a..3b3f6ef 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -552,7 +552,8 @@ struct irec { + }; + + struct listener { +- int fd, tcpfd, tftpfd, family; ++ int fd, tcpfd, tftpfd, family, used; ++ union mysockaddr addr; + struct irec *iface; /* only sometimes valid for non-wildcard */ + struct listener *next; + }; +diff --git a/src/network.c b/src/network.c +index d6d4b01..4bbd810 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -577,6 +577,56 @@ static void clean_interfaces() + } + } + ++/** 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) ++ { ++ (void)prettyprint_addr(&l->iface->addr, daemon->addrbuff); ++ my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s"), ++ l->iface->name, l->iface->index, daemon->addrbuff); ++ /* 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; +@@ -684,29 +734,10 @@ int enumerate_interfaces(int reset) + + if (!l->iface || l->iface->found) + up = &l->next; +- else ++ else if (release_listener(l)) + { +- *up = l->next; +- if (l->iface->done) +- { +- iface = l->iface; +- (void)prettyprint_addr(&iface->addr, daemon->addrbuff); +- my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s"), +- iface->name, iface->index, daemon->addrbuff); +- } +- +- /* 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); +- freed = 1; ++ *up = tmp; ++ freed = 1; + } + } + +@@ -959,7 +990,9 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, in + l->family = addr->sa.sa_family; + l->fd = fd; + l->tcpfd = tcpfd; +- l->tftpfd = tftpfd; ++ l->tftpfd = tftpfd; ++ l->addr = *addr; ++ l->used = 1; + l->iface = NULL; + } + +@@ -1000,23 +1033,41 @@ void create_wildcard_listeners(void) + 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 && +- (new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) ++ if (!iface->done && !iface->dad && iface->found) + { +- new->iface = iface; +- new->next = daemon->listeners; +- daemon->listeners = new; +- iface->done = 1; +- (void)prettyprint_addr(&iface->addr, daemon->addrbuff); +- my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s"), +- iface->name, iface->index, daemon->addrbuff); ++ 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; ++ (void)prettyprint_addr(&iface->addr, daemon->addrbuff); ++ my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s"), ++ iface->name, iface->index, daemon->addrbuff); ++ } + } + + /* Check for --listen-address options that haven't been used because there's +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.79-rh1746411.patch b/SOURCES/dnsmasq-2.79-rh1746411.patch new file mode 100644 index 0000000..74922c5 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1746411.patch @@ -0,0 +1,22 @@ +From: Simon Kelley +Date: Wed, 14 Aug 2019 20:52:50 +0000 (+0100) +Subject: Fix breakage of dhcp_lease_time utility. +X-Git-Url: http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commitdiff_plain;h=225accd235a09413ca253e710d7d691a3475c523 + +Fix breakage of dhcp_lease_time utility. +--- + +diff --git a/contrib/lease-tools/dhcp_lease_time.c b/contrib/lease-tools/dhcp_lease_time.c +index 697d627..91edbfa 100644 +--- a/contrib/lease-tools/dhcp_lease_time.c ++++ b/contrib/lease-tools/dhcp_lease_time.c +@@ -83,7 +83,7 @@ static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt + if (p >= end - 2) + return NULL; /* malformed packet */ + opt_len = option_len(p); +- if (end - p >= (2 + opt_len)) ++ if (end - p < (2 + opt_len)) + return NULL; /* malformed packet */ + if (*p == opt && opt_len >= minsize) + return p; + diff --git a/SOURCES/dnsmasq-2.79-rh1749092-fail.patch b/SOURCES/dnsmasq-2.79-rh1749092-fail.patch new file mode 100644 index 0000000..3550d61 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-rh1749092-fail.patch @@ -0,0 +1,34 @@ +From 8fda4b4620ca2b23152ca805d14c7cde1083fe31 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Tue, 1 Oct 2019 16:08:28 +0200 +Subject: [PATCH] Report error on dhcp_release + +If no IPv4 address is present on given interface, the tool would not +send any request. It would not report any error at the same time. Report +error if request send failed. + +Signed-off-by: Petr Mensik +--- + contrib/lease-tools/dhcp_release.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/contrib/lease-tools/dhcp_release.c b/contrib/lease-tools/dhcp_release.c +index c866cd9..30e77c6 100644 +--- a/contrib/lease-tools/dhcp_release.c ++++ b/contrib/lease-tools/dhcp_release.c +@@ -223,7 +223,11 @@ static struct in_addr find_interface(struct in_addr client, int fd, unsigned int + ifr->ifr_addr.sa_family = AF_INET; + if (ioctl(ifrfd, SIOCGIFADDR, ifr) != -1) + return ((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr; +- exit(0); ++ else ++ { ++ fprintf(stderr, "error: local IPv4 address not found\n"); ++ exit(1); ++ } + } + else if (h->nlmsg_type == RTM_NEWADDR) + { +-- +2.20.1 + diff --git a/SOURCES/dnsmasq-2.80-dnssec.patch b/SOURCES/dnsmasq-2.80-dnssec.patch new file mode 100644 index 0000000..a34f46e --- /dev/null +++ b/SOURCES/dnsmasq-2.80-dnssec.patch @@ -0,0 +1,73 @@ +From a997ca0da044719a0ce8a232d14da8b30022592b Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 29 Jun 2018 14:39:41 +0100 +Subject: [PATCH] Fix sometimes missing DNSSEC RRs when DNSSEC validation not + enabled. + +Dnsmasq does pass on the do-bit, and return DNSSEC RRs, irrespective +of of having DNSSEC validation compiled in or enabled. + +The thing to understand here is that the cache does not store all the +DNSSEC RRs, and dnsmasq doesn't have the (very complex) logic required +to determine the set of DNSSEC RRs required in an answer. Therefore if +the client wants the DNSSEC RRs, the query can not be answered from +the cache. When DNSSEC validation is enabled, any query with the +do-bit set is never answered from the cache, unless the domain is +known not to be signed: the query is always forwarded. This ensures +that the DNSEC RRs are included. + +The same thing should be true when DNSSEC validation is not enabled, +but there's a bug in the logic. + +line 1666 of src/rfc1035.c looks like this + + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !do_bit || !(crecp->flags & F_DNSSECOK)) + +{ ...answer from cache ... } + +So local stuff (hosts, DHCP, ) get answered. If the do_bit is not set +then the query is answered, and if the domain is known not to be +signed, the query is answered. + +Unfortunately, if DNSSEC validation is not turned on then the +F_DNSSECOK bit is not valid, and it's always zero, so the question +always gets answered from the cache, even when the do-bit is set. + +This code should look like that at line 1468, dealing with PTR queries + + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + !do_bit || + (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) + +where the F_DNSSECOK bit is only used when validation is enabled. +--- + src/rfc1035.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/rfc1035.c b/src/rfc1035.c +index ebb1f36..580f5ef 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1663,7 +1663,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + } + + /* If the client asked for DNSSEC don't use cached data. */ +- if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !do_bit || !(crecp->flags & F_DNSSECOK)) ++ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || ++ !do_bit || ++ (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))) + do + { + /* don't answer wildcard queries with data not from /etc/hosts +@@ -1747,7 +1749,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + { + if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) && + (qtype == T_CNAME || (crecp->flags & F_CONFIG)) && +- ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK))) ++ ((crecp->flags & F_CONFIG) || !do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))) + { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; +-- +2.14.4 + diff --git a/SOURCES/dnsmasq-2.80-rh1795370.patch b/SOURCES/dnsmasq-2.80-rh1795370.patch new file mode 100644 index 0000000..220a6af --- /dev/null +++ b/SOURCES/dnsmasq-2.80-rh1795370.patch @@ -0,0 +1,47 @@ +From 69bc94779c2f035a9fffdb5327a54c3aeca73ed5 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 14 Aug 2019 20:44:50 +0100 +Subject: [PATCH] Fix memory leak in helper.c + +Thanks to Xu Mingjie for spotting this. +--- + src/helper.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/helper.c b/src/helper.c +index 33ba120..c392eec 100644 +--- a/src/helper.c ++++ b/src/helper.c +@@ -80,7 +80,8 @@ 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) +@@ -186,11 +187,16 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) + struct script_data data; + char *p, *action_str, *hostname = NULL, *domain = NULL; + unsigned char *buf = (unsigned char *)daemon->namebuff; +- unsigned char *end, *extradata, *alloc_buff = NULL; ++ unsigned char *end, *extradata; + int is6, err = 0; + int pipeout[2]; + +- free(alloc_buff); ++ /* 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)) +-- +1.7.10.4 + + diff --git a/SOURCES/dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch b/SOURCES/dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch new file mode 100644 index 0000000..bc55656 --- /dev/null +++ b/SOURCES/dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch @@ -0,0 +1,317 @@ +From 653481c6ebf46dcadb5a017085325d956dd04a28 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Tue, 21 Aug 2018 22:06:36 +0100 +Subject: [PATCH] Properly deal with unaligned addresses in DHCPv6 packets. + +Thanks to Vladislav Grishenko for spotting this. + +(cherry picked from commit 97f876b64c22b2b18412e2e3d8506ee33e42db7c) + +Conflicts: + src/rfc3315.c +--- + src/rfc1035.c | 2 +- + src/rfc3315.c | 101 ++++++++++++++++++++++++++++++++++------------------------ + 2 files changed, 61 insertions(+), 42 deletions(-) + +diff --git a/src/rfc1035.c b/src/rfc1035.c +index 6b3bb27..ee5f7a0 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1376,7 +1376,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + t->class, C_IN, "t", t->len, t->txt)) +- anscount ++; ++ anscount++; + } + } + +diff --git a/src/rfc3315.c b/src/rfc3315.c +index 21fcd9b..ee1cf17 100644 +--- a/src/rfc3315.c ++++ b/src/rfc3315.c +@@ -639,9 +639,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + int plain_range = 1; + u32 lease_time; + struct dhcp_lease *ltmp; +- struct in6_addr *req_addr; +- struct in6_addr addr; +- ++ struct in6_addr req_addr, addr; ++ + if (!check_ia(state, opt, &ia_end, &ia_option)) + continue; + +@@ -709,9 +708,10 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + + for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { +- req_addr = opt6_ptr(ia_option, 0); ++ /* 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))) ++ 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, +@@ -719,14 +719,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + addresses automatic. */ + if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr) && check_address(state, &addr)) + { +- req_addr = &addr; ++ 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))) ++ 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)) ++ else if (!check_address(state, &req_addr)) + continue; /* address leased elsewhere */ + + /* add address to output packet */ +@@ -734,8 +734,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); + #endif +- add_address(state, c, lease_time, ia_option, &min_time, req_addr, now); +- mark_context_used(state, req_addr); ++ 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; + } +@@ -768,15 +768,15 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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 = <mp->addr6; +- if ((c = address6_available(state->context, req_addr, solicit_tags, plain_range))) ++ req_addr = ltmp->addr6; ++ if ((c = address6_available(state->context, &req_addr, solicit_tags, plain_range))) + { + #ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); + #endif +- add_address(state, c, c->lease_time, NULL, &min_time, req_addr, now); +- mark_context_used(state, req_addr); ++ 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; + } +@@ -892,16 +892,19 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { +- struct in6_addr *req_addr = opt6_ptr(ia_option, 0); ++ struct in6_addr req_addr; + struct dhcp_context *dynamic, *c; + unsigned int lease_time; + struct in6_addr addr; + 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_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr); ++ if ((c = address6_valid(state->context, &req_addr, tagif, 1))) ++ config_ok = config_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr); + +- if ((dynamic = address6_available(state->context, req_addr, tagif, 1)) || c) ++ if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c) + { + if (!dynamic && !config_ok) + { +@@ -911,7 +914,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + put_opt6_string(_("address unavailable")); + end_opt6(o1); + } +- else if (!check_address(state, req_addr)) ++ else if (!check_address(state, &req_addr)) + { + /* Address leased to another DUID/IAID */ + o1 = new_opt6(OPTION6_STATUS_CODE); +@@ -933,7 +936,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); + #endif +- add_address(state, dynamic, lease_time, ia_option, &min_time, req_addr, now); ++ add_address(state, dynamic, lease_time, ia_option, &min_time, &req_addr, now); + get_context_tag(state, dynamic); + address_assigned = 1; + } +@@ -996,15 +999,17 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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 = opt6_ptr(ia_option, 0); ++ 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))) ++ state->iaid, &req_addr))) + { + /* 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 +@@ -1012,7 +1017,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + save_counter(iacntr); + t1cntr = 0; + +- log6_packet(state, "DHCPREPLY", req_addr, _("lease not found")); ++ log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found")); + + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOBINDING); +@@ -1024,15 +1029,15 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + } + + +- if ((this_context = address6_available(state->context, req_addr, tagif, 1)) || +- (this_context = address6_valid(state->context, req_addr, tagif, 1))) ++ if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) || ++ (this_context = address6_valid(state->context, &req_addr, tagif, 1))) + { + struct in6_addr addr; + unsigned int lease_time; + + get_context_tag(state, this_context); + +- if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr) && have_config(config, CONFIG_TIME)) ++ if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr) && have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + else + lease_time = this_context->lease_time; +@@ -1045,7 +1050,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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); ++ 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); +@@ -1063,12 +1068,12 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + } + + if (message && (message != state->hostname)) +- log6_packet(state, "DHCPREPLY", req_addr, message); ++ log6_packet(state, "DHCPREPLY", &req_addr, message); + else +- log6_quiet(state, "DHCPREPLY", req_addr, message); ++ log6_quiet(state, "DHCPREPLY", &req_addr, message); + + o1 = new_opt6(OPTION6_IAADDR); +- put_opt6(req_addr, sizeof(*req_addr)); ++ put_opt6(&req_addr, sizeof(req_addr)); + put_opt6_long(preferred_time); + put_opt6_long(valid_time); + end_opt6(o1); +@@ -1100,19 +1105,23 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + ia_option; + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { +- struct in6_addr *req_addr = opt6_ptr(ia_option, 0); ++ struct in6_addr req_addr; ++ ++ /* alignment */ ++ memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); + +- if (!address6_valid(state->context, req_addr, tagif, 1)) ++ 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); ++ log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname); + } + } + +@@ -1171,9 +1180,12 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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, opt6_ptr(ia_option, 0)))) ++ state->iaid, &addr))) + lease_prune(lease, now); + else + { +@@ -1233,12 +1245,15 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct dhcp_lease *lease; +- struct in6_addr *addrp = opt6_ptr(ia_option, 0); ++ struct in6_addr addr; + +- if (have_config(config, CONFIG_ADDR6) && IN6_ARE_ADDR_EQUAL(&config->addr6, addrp)) ++ /* align */ ++ memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); ++ ++ if (have_config(config, CONFIG_ADDR6) && IN6_ARE_ADDR_EQUAL(&config->addr6, &addr)) + { + prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF); +- inet_ntop(AF_INET6, addrp, daemon->addrbuff, ADDRSTRLEN); ++ 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); + config->flags |= CONFIG_DECLINED; +@@ -1250,7 +1265,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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, opt6_ptr(ia_option, 0)))) ++ state->iaid, &addr))) + lease_prune(lease, now); + else + { +@@ -1267,7 +1282,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + } + + o1 = new_opt6(OPTION6_IAADDR); +- put_opt6(opt6_ptr(ia_option, 0), IN6ADDRSZ); ++ put_opt6(&addr, IN6ADDRSZ); + put_opt6_long(0); + put_opt6_long(0); + end_opt6(o1); +@@ -1935,7 +1950,11 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op + } + else if (type == OPTION6_IAADDR) + { +- inet_ntop(AF_INET6, opt6_ptr(opt, 0), daemon->addrbuff, ADDRSTRLEN); ++ 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"; +-- +1.8.3.1 + diff --git a/SOURCES/dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch b/SOURCES/dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch new file mode 100644 index 0000000..fad386f --- /dev/null +++ b/SOURCES/dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch @@ -0,0 +1,44 @@ +From 6307208c806f9b968eca178931b3d77c4ed83c54 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Fri, 6 Mar 2020 15:37:23 +0100 +Subject: [PATCH] Correct range check of dhcp-host prefix + +It incorrectly works with 32 bit integer only when counting number of +addresses in range. It works correctly only between prefixlen 96 and +128. Use 64bit shift to work with well with numbers higher than 64. + +Fixes commit 79aba0f10ad0157fb4f48afbbcb03f094caff97a error. +--- + src/option.c | 2 +- + src/rfc3315.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/option.c b/src/option.c +index 88cd2ab..79122df 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -3247,7 +3247,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + + if (!atoi_check(pref, &new_addr->prefixlen) || + new_addr->prefixlen > 128 || +- (((1<<(128-new_addr->prefixlen))-1) & addrpart) != 0) ++ ((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0) + { + dhcp_config_free(new); + ret_err(_("bad IPv6 prefix")); +diff --git a/src/rfc3315.c b/src/rfc3315.c +index a0067e9..f59aedc 100644 +--- a/src/rfc3315.c ++++ b/src/rfc3315.c +@@ -1798,7 +1798,7 @@ static int config_valid(struct dhcp_config *config, struct dhcp_context *context + addresses = 1; + + if (addr_list->flags & ADDRLIST_PREFIX) +- addresses = 1<<(128-addr_list->prefixlen); ++ addresses = (u64)1<<(128-addr_list->prefixlen); + + if ((addr_list->flags & ADDRLIST_WILDCARD)) + { +-- +2.21.1 + diff --git a/SOURCES/dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch b/SOURCES/dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch new file mode 100644 index 0000000..cf4833f --- /dev/null +++ b/SOURCES/dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch @@ -0,0 +1,895 @@ +From 9b200103342c0909def9f8d9b97cfd889be6bfd8 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Mon, 3 Feb 2020 23:58:45 +0000 +Subject: [PATCH] Support prefixed ranges of ipv6 addresses in dhcp-host. + +When a request matching the clid or mac address is +recieved the server will iterate over all candidate +addresses until it find's one that is not already +leased to a different clid/iaid and advertise +this address. + +Using multiple reservations for a single host makes it +possible to maintain a static leases only configuration +which support network booting systems with UEFI firmware +that request a new address (a new SOLICIT with a new IA_NA +option using a new IAID) for different boot modes, for +instance 'PXE over IPv6', and 'HTTP-Boot over IPv6'. Open +Virtual Machine Firmware (OVMF) and most UEFI firmware +build on the EDK2 code base exhibit this behaviour. + +(cherry picked from commit 79aba0f10ad0157fb4f48afbbcb03f094caff97a) + +Conflicts: + CHANGELOG + src/dhcp-common.c + src/dnsmasq.h + src/dhcp6.c + +Extend 79aba0f10ad0157fb4f48afbbcb03f094caff97a for multiple IPv6 addresses. + +(cherry picked from commit 137286e9baecf6a3ba97722ef1b49c851b531810) + +Conflicts: + man/dnsmasq.8 + src/dhcp-common.c + src/dhcp6.c + src/rfc3315.c + src/option.c + +Fix bug with prefixed wildcard addresses in 137286e9baecf6a3ba97722ef1b49c851b531810 + +(cherry picked from commit f064188032a829efdcf3988b24ac795ff52785ec) + +Conflicts: + src/rfc3315.c +--- + man/dnsmasq.8 | 13 +- + src/dhcp-common.c | 56 +++++--- + src/dhcp6.c | 51 +++---- + src/dnsmasq.h | 17 +-- + src/option.c | 402 ++++++++++++++++++++++++++++++------------------------ + src/rfc3315.c | 83 ++++++++++- + 6 files changed, 370 insertions(+), 252 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index f52762f..2c9d9f6 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -985,13 +985,20 @@ allowed to specify the client ID as text, like this: + .B --dhcp-host=id:clientidastext,..... + + A single +-.B dhcp-host +-may contain an IPv4 address or an IPv6 address, or both. IPv6 addresses must be bracketed by square brackets thus: ++.B --dhcp-host ++may contain an IPv4 address or one or more IPv6 addresses, or both. IPv6 addresses must be bracketed by square brackets thus: + .B --dhcp-host=laptop,[1234::56] + IPv6 addresses may contain only the host-identifier part: + .B --dhcp-host=laptop,[::56] + in which case they act as wildcards in constructed dhcp ranges, with +-the appropriate network part inserted. ++the appropriate network part inserted. For IPv6, an address may include a prefix length: ++.B --dhcp-host=laptop,[1234:50/126] ++which (in this case) specifies four addresses, 1234::50 to 1234::53. This (an the ability ++to specify multiple addresses) is useful ++when a host presents either a consistent name or hardware-ID, but varying DUIDs, since it allows ++dnsmasq to honour the static address allocation but assign a different adddress for each DUID. This ++typically occurs when chain netbooting, as each stage of the chain gets in turn allocates an address. ++ + Note that in IPv6 DHCP, the hardware address may not be + available, though it normally is for direct-connected clients, or + clients using DHCP relays which support RFC 6939. +diff --git a/src/dhcp-common.c b/src/dhcp-common.c +index d9719d1..5d437dd 100644 +--- a/src/dhcp-common.c ++++ b/src/dhcp-common.c +@@ -271,26 +271,35 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_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) && (config->flags & CONFIG_WILDCARD)) +- return 1; +-#endif ++ if (context->flags & CONTEXT_V6) ++ { ++ struct addrlist *addr_list; + +- for (; context; context = context->current) +-#ifdef HAVE_DHCP6 +- if (context->flags & CONTEXT_V6) +- { +- if ((config->flags & CONFIG_ADDR6) && is_same_net6(&config->addr6, &context->start6, context->prefix)) +- return 1; +- } +- else ++ if (!(config->flags & CONFIG_ADDR6)) ++ return 1; ++ ++ 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.addr.addr6, &context->start6, context->prefix)) ++ return 1; ++ } ++ } ++ else + #endif +- if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) ++ { ++ if (!(config->flags & CONFIG_ADDR)) + return 1; ++ ++ for (; context; context = context->current) ++ if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) ++ return 1; ++ } + + return 0; + } +@@ -418,10 +427,21 @@ void dhcp_update_configs(struct dhcp_config *configs) + + #ifdef HAVE_DHCP6 + if (prot == AF_INET6 && +- (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config)) ++ (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr.addr.addr6)) || conf_tmp == config)) + { +- memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); +- config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS; ++ /* 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.addr.addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); ++ config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS; ++ } ++ + continue; + } + #endif +diff --git a/src/dhcp6.c b/src/dhcp6.c +index 0853664..6f1f54e 100644 +--- a/src/dhcp6.c ++++ b/src/dhcp6.c +@@ -384,21 +384,26 @@ static int complete_context6(struct in6_addr *local, int prefix, + return 1; + } + +-struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr) ++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) && +- is_same_net6(&config->addr6, net, prefix) && +- (prefix == 128 || addr6part(&config->addr6) == addr)) +- return config; ++ 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.addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) && ++ is_same_net6(&addr_list->addr.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, +- int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) ++ 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. +@@ -453,16 +458,15 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned c + 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, addr)) +- { +- *ans = c->start6; +- setaddr6part (ans, addr); +- return c; +- } +- ++ !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans)) ++ return c; ++ + addr++; + + if (addr == addr6part(&c->end6) + 1) +@@ -516,27 +520,6 @@ struct dhcp_context *address6_valid(struct dhcp_context *context, + return NULL; + } + +-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr) +-{ +- if (!config || !(config->flags & CONFIG_ADDR6)) +- return 0; +- +- if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64) +- { +- *addr = context->start6; +- setaddr6part(addr, addr6part(&config->addr6)); +- return 1; +- } +- +- if (is_same_net6(&context->start6, &config->addr6, context->prefix)) +- { +- *addr = config->addr6; +- return 1; +- } +- +- return 0; +-} +- + void make_duid(time_t now) + { + (void)now; +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 6b18bb7..9437226 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -343,9 +343,11 @@ struct ds_config { + struct ds_config *next; + }; + +-#define ADDRLIST_LITERAL 1 +-#define ADDRLIST_IPV6 2 +-#define ADDRLIST_REVONLY 4 ++#define ADDRLIST_LITERAL 1 ++#define ADDRLIST_IPV6 2 ++#define ADDRLIST_REVONLY 4 ++#define ADDRLIST_PREFIX 8 ++#define ADDRLIST_WILDCARD 16 + + struct addrlist { + struct all_addr addr; +@@ -748,7 +750,7 @@ struct dhcp_config { + char *hostname, *domain; + struct dhcp_netid_list *netid; + #ifdef HAVE_DHCP6 +- struct in6_addr addr6; ++ struct addrlist *addr6; + #endif + struct in_addr addr; + time_t decline_time; +@@ -770,7 +772,7 @@ struct dhcp_config { + #define CONFIG_DECLINED 1024 /* address declined by client */ + #define CONFIG_BANK 2048 /* from dhcp hosts file */ + #define CONFIG_ADDR6 4096 +-#define CONFIG_WILDCARD 8192 ++#define CONFIG_ADDR6_HOSTS 16384 /* address added by from /etc/hosts */ + + struct dhcp_opt { + int opt, len, flags; +@@ -1463,8 +1465,7 @@ int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, + 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, +- int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans); +-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *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, +@@ -1474,7 +1475,7 @@ struct dhcp_context *address6_valid(struct dhcp_context *context, + struct dhcp_netid *netids, + int plain_range); + struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, +- int prefix, u64 addr); ++ 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, +diff --git a/src/option.c b/src/option.c +index b12183b..ea70ee3 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -1010,15 +1010,30 @@ 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); ++ + if (config->flags & CONFIG_CLID) + free(config->clid); ++ ++ if (config->flags & CONFIG_ADDR6) ++ { ++ struct addrlist *addr, *tmp; ++ ++ for (addr = config->addr6; addr; addr = tmp) ++ { ++ tmp = addr->next; ++ free(addr); ++ } ++ } ++ + free(config); + } + } +@@ -3143,8 +3158,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + case LOPT_BANK: + case 'G': /* --dhcp-host */ + { +- int j, k = 0; +- char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + struct dhcp_config *new; + struct in_addr in; + +@@ -3155,197 +3168,222 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->hwaddr = NULL; + new->netid = NULL; + new->clid = NULL; ++ new->addr6 = NULL; + +- if ((a[0] = arg)) +- for (k = 1; k < 7; k++) +- if (!(a[k] = split(a[k-1]))) +- break; +- +- for (j = 0; j < k; j++) +- if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */ +- { +- char *arg = a[j]; +- +- if ((arg[0] == 'i' || arg[0] == 'I') && +- (arg[1] == 'd' || arg[1] == 'D') && +- arg[2] == ':') +- { +- if (arg[3] == '*') +- new->flags |= CONFIG_NOCLID; +- else +- { +- int len; +- arg += 3; /* dump id: */ +- if (strchr(arg, ':')) +- len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); +- else +- { +- unhide_metas(arg); +- len = (int) strlen(arg); +- } +- +- if (len == -1) +- { +- 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) +- { +- +- dhcp_config_free(new); +- ret_err(_("cannot match tags in --dhcp-host")); +- } ++ 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) ++ { ++ ++ dhcp_config_free(new); ++ ret_err(_("cannot match tags in --dhcp-host")); ++ } + #ifdef HAVE_DHCP6 +- else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') +- { +- arg[strlen(arg)-1] = 0; +- arg++; +- +- if (!inet_pton(AF_INET6, arg, &new->addr6)) +- { +- dhcp_config_free(new); +- ret_err(_("bad IPv6 address")); +- } +- +- for (i= 0; i < 8; i++) +- if (new->addr6.s6_addr[i] != 0) +- break; ++ 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")); ++ } + +- /* set WILDCARD if network part all zeros */ +- if (i == 8) +- new->flags |= CONFIG_WILDCARD; ++ new_addr = opt_malloc(sizeof(struct addrlist)); ++ new_addr->next = new->addr6; ++ new_addr->flags = 0; ++ new_addr->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 || ++ (((1<<(128-new_addr->prefixlen))-1) & addrpart) != 0) ++ { ++ dhcp_config_free(new); ++ ret_err(_("bad IPv6 prefix")); ++ } ++ ++ new_addr->flags |= ADDRLIST_PREFIX; ++ } + +- new->flags |= CONFIG_ADDR6; +- } ++ 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(a[j], 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(a[j], '.') && (inet_pton(AF_INET, a[j], &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) ++ else + { +- sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); +- return 0; +- } +- } +- else +- { +- char *cp, *lastp = NULL, last = 0; +- int fac = 1, isdig = 0; +- +- if (strlen(a[j]) > 1) +- { +- lastp = a[j] + strlen(a[j]) - 1; +- last = *lastp; +- switch (last) ++ 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) + { +- 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 = a[j]; *cp; cp++) +- if (isdigit((unsigned char)*cp)) +- isdig = 1; +- else if (*cp != ' ') +- break; ++ sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); ++ 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; ++ } + +- if (*cp) +- { +- if (lastp) +- *lastp = last; +- if (strcmp(a[j], "infinite") == 0) +- { +- new->lease_time = 0xffffffff; +- new->flags |= CONFIG_TIME; +- } +- else if (strcmp(a[j], "ignore") == 0) +- new->flags |= CONFIG_DISABLE; +- else +- { +- if (!(new->hostname = canonicalise_opt(a[j])) || +- !legal_hostname(new->hostname)) +- { +- 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(a[j]) * fac; +- /* Leases of a minute or less confuse +- some clients, notably Apple's */ +- if (new->lease_time < 120) +- new->lease_time = 120; +- new->flags |= CONFIG_TIME; +- } +- } +- + daemon->dhcp_conf = new; + break; + } +- ++ + case LOPT_TAG_IF: /* --tag-if */ + { + struct tag_if *new = opt_malloc(sizeof(struct tag_if)); +diff --git a/src/rfc3315.c b/src/rfc3315.c +index ee1cf17..ee58b57 100644 +--- a/src/rfc3315.c ++++ b/src/rfc3315.c +@@ -55,6 +55,8 @@ static struct prefix_class *prefix_class_from_context(struct dhcp_context *conte + 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); ++static int 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); +@@ -717,7 +719,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + /* 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) && check_address(state, &addr)) ++ if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state)) + { + req_addr = addr; + mark_config_used(c, &addr); +@@ -745,8 +747,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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) && +- check_address(state, &addr)) ++ config_valid(config, c, &addr, state)) + { + mark_config_used(state->context, &addr); + if (have_config(config, CONFIG_TIME)) +@@ -895,14 +896,13 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + struct in6_addr req_addr; + struct dhcp_context *dynamic, *c; + unsigned int lease_time; +- struct in6_addr addr; + 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_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr); ++ config_ok = config_implies(config, c, &req_addr); + + if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c) + { +@@ -1032,12 +1032,11 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) || + (this_context = address6_valid(state->context, &req_addr, tagif, 1))) + { +- struct in6_addr addr; + unsigned int lease_time; + + get_context_tag(state, this_context); + +- if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr) && have_config(config, CONFIG_TIME)) ++ 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; +@@ -1760,6 +1759,76 @@ static int check_address(struct state *state, struct in6_addr *addr) + } + + ++/* return true of *addr could have been generated from config. */ ++static int 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 0; ++ ++ 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.addr.addr6; ++ ++ if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) ++ { ++ wild_addr = context->start6; ++ setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr.addr6)); ++ } ++ else if (!is_same_net6(&context->start6, addr, context->prefix)) ++ continue; ++ ++ if (is_same_net6(&wild_addr, addr, prefix)) ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state) ++{ ++ 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) ++ { ++ addrpart = addr6part(&addr_list->addr.addr.addr6); ++ addresses = 1; ++ ++ if (addr_list->flags & ADDRLIST_PREFIX) ++ addresses = 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.addr.addr6, context->prefix)) ++ *addr = addr_list->addr.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: +-- +1.8.3.1 + diff --git a/SOURCES/dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch b/SOURCES/dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch new file mode 100644 index 0000000..65592a0 --- /dev/null +++ b/SOURCES/dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch @@ -0,0 +1,322 @@ +From dd04a0d90d2fca66b5f91952ae7286c5de1714f1 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 7 Feb 2020 21:05:54 +0000 +Subject: [PATCH] Add tag filtering of dhcp-host directives. + +(cherry picked from commit 52ec7836139e7a11374971905e5ac0d2d02e32c0) + +Conflicts: + CHANGELOG + src/rfc3315.c +--- + man/dnsmasq.8 | 5 ++++- + src/dhcp-common.c | 42 ++++++++++++++++++++++++++++++++---------- + src/dnsmasq.h | 4 +++- + src/lease.c | 2 +- + src/option.c | 14 ++++++-------- + src/rfc2131.c | 6 +++--- + src/rfc3315.c | 49 ++++++++++++++++++++++--------------------------- + 7 files changed, 71 insertions(+), 51 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index 2c9d9f6..a59b06f 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -953,7 +953,7 @@ is also included, as described in RFC-3775 section 7.3. + tells dnsmasq to advertise the prefix without the on-link (aka L) bit set. + + .TP +-.B \-G, --dhcp-host=[][,id:|*][,set:][,][,][,][,ignore] ++.B \-G, --dhcp-host=[][,id:|*][,set:][tag:][,][,][,][,ignore] + Specify per host parameters for the DHCP server. This allows a machine + with a particular hardware address to be always allocated the same + hostname, IP address and lease time. A hostname specified like this +@@ -1038,6 +1038,9 @@ ignore requests from unknown machines using + .B --dhcp-ignore=tag:!known + If the host matches only a dhcp-host directive which cannot + be used because it specifies an address on different subnet, the tag "known-othernet" is set. ++ ++The tag: construct filters which dhcp-host directives are used. Tagged directives are used in preference to untagged ones. ++ + Ethernet addresses (but not client-ids) may have + wildcard bytes, so for example + .B --dhcp-host=00:20:e0:3b:13:*,ignore +diff --git a/src/dhcp-common.c b/src/dhcp-common.c +index 5d437dd..71e9e5b 100644 +--- a/src/dhcp-common.c ++++ b/src/dhcp-common.c +@@ -304,11 +304,12 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config + return 0; + } + +-struct dhcp_config *find_config(struct dhcp_config *configs, +- struct dhcp_context *context, +- unsigned char *clid, int clid_len, +- unsigned char *hwaddr, int hw_len, +- int hw_type, char *hostname) ++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; +@@ -320,7 +321,9 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + { + if (config->clid_len == clid_len && + memcmp(config->clid, clid, clid_len) == 0 && +- is_config_in_context(context, config)) ++ 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 +@@ -328,7 +331,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + 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)) ++ is_config_in_context(context, config) && ++ match_netid(config->filter, tags, tag_not_needed)) + return config; + } + +@@ -336,14 +340,16 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + 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)) ++ 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)) ++ is_config_in_context(context, config) && ++ match_netid(config->filter, tags, tag_not_needed)) + return config; + + +@@ -352,7 +358,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + + /* use match with fewest wildcard octets */ + for (candidate = NULL, count = 0, config = configs; config; config = config->next) +- if (is_config_in_context(context, config)) ++ 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 && +@@ -366,6 +373,21 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + 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. +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 9437226..055a0d1 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -749,6 +749,7 @@ struct dhcp_config { + unsigned char *clid; /* clientid */ + char *hostname, *domain; + struct dhcp_netid_list *netid; ++ struct dhcp_netid *filter; + #ifdef HAVE_DHCP6 + struct addrlist *addr6; + #endif +@@ -1514,7 +1515,8 @@ struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, +- int hw_type, char *hostname); ++ int 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); +diff --git a/src/lease.c b/src/lease.c +index 5c33df7..00c82f6 100644 +--- a/src/lease.c ++++ b/src/lease.c +@@ -222,7 +222,7 @@ void lease_update_from_configs(void) + 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)) && ++ 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); +diff --git a/src/option.c b/src/option.c +index ea70ee3..88cd2ab 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -953,8 +953,7 @@ static char *set_prefix(char *arg) + return arg; + } + +-static struct dhcp_netid * +-dhcp_netid_create(const char *net, struct dhcp_netid *next) ++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)); +@@ -1019,7 +1018,8 @@ static void dhcp_config_free(struct dhcp_config *config) + } + + dhcp_netid_list_free(config->netid); +- ++ dhcp_netid_free(config->filter); ++ + if (config->flags & CONFIG_CLID) + free(config->clid); + +@@ -3167,6 +3167,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0; + new->hwaddr = NULL; + new->netid = NULL; ++ new->filter = NULL; + new->clid = NULL; + new->addr6 = NULL; + +@@ -3215,11 +3216,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + newlist->list = dhcp_netid_create(arg+4, NULL); + } + else if (strstr(arg, "tag:") == arg) +- { +- +- dhcp_config_free(new); +- ret_err(_("cannot match tags in --dhcp-host")); +- } ++ new->filter = dhcp_netid_create(arg+4, new->filter); ++ + #ifdef HAVE_DHCP6 + else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') + { +diff --git a/src/rfc2131.c b/src/rfc2131.c +index 997575a..a741f9f 100644 +--- a/src/rfc2131.c ++++ b/src/rfc2131.c +@@ -479,7 +479,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, + mess->op = BOOTREPLY; + + config = find_config(daemon->dhcp_conf, context, clid, clid_len, +- mess->chaddr, mess->hlen, mess->htype, NULL); ++ mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid)); + + /* set "known" tag for known hosts */ + if (config) +@@ -489,7 +489,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, + netid = &known_id; + } + else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, +- mess->chaddr, mess->hlen, mess->htype, NULL)) ++ mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid))) + { + known_id.net = "known-othernet"; + known_id.next = netid; +@@ -725,7 +725,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, + to avoid impersonation by name. */ + struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, + mess->chaddr, mess->hlen, +- mess->htype, hostname); ++ mess->htype, hostname, run_tag_if(netid)); + if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) + { + config = new; +diff --git a/src/rfc3315.c b/src/rfc3315.c +index ee58b57..a0067e9 100644 +--- a/src/rfc3315.c ++++ b/src/rfc3315.c +@@ -486,35 +486,29 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + } + } + +- if (state->clid) ++ 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)) + { +- config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL); +- +- if (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); ++ 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); +- if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) +- config = new; +- } +- } ++ if (strlen(state->client_hostname) != 0) ++ { ++ /* 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) + { +@@ -535,7 +529,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + 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)) ++ 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; +-- +1.8.3.1 + diff --git a/SOURCES/dnsmasq-systemd-sysusers.conf b/SOURCES/dnsmasq-systemd-sysusers.conf new file mode 100644 index 0000000..2106ae5 --- /dev/null +++ b/SOURCES/dnsmasq-systemd-sysusers.conf @@ -0,0 +1 @@ +u dnsmasq - "Dnsmasq DHCP and DNS server" /var/lib/dnsmasq diff --git a/SOURCES/dnsmasq.service b/SOURCES/dnsmasq.service new file mode 100644 index 0000000..07fa92e --- /dev/null +++ b/SOURCES/dnsmasq.service @@ -0,0 +1,9 @@ +[Unit] +Description=DNS caching server. +After=network.target + +[Service] +ExecStart=/usr/sbin/dnsmasq -k + +[Install] +WantedBy=multi-user.target diff --git a/SPECS/dnsmasq.spec b/SPECS/dnsmasq.spec new file mode 100644 index 0000000..a2dc4b1 --- /dev/null +++ b/SPECS/dnsmasq.spec @@ -0,0 +1,649 @@ +%define testrelease 0 +%define releasecandidate 0 +%if 0%{testrelease} + %define extrapath test-releases/ + %define extraversion test%{testrelease} +%endif +%if 0%{releasecandidate} + %define extrapath release-candidates/ + %define extraversion rc%{releasecandidate} +%endif + +%define _hardened_build 1 + +Name: dnsmasq +Version: 2.79 +Release: 11%{?extraversion:.%{extraversion}}%{?dist} +Summary: A lightweight DHCP/caching DNS server + +License: GPLv2 or GPLv3 +URL: http://www.thekelleys.org.uk/dnsmasq/ +Source0: http://www.thekelleys.org.uk/dnsmasq/%{?extrapath}%{name}-%{version}%{?extraversion}.tar.xz +Source1: %{name}.service +Source2: dnsmasq-systemd-sysusers.conf + +# https://bugzilla.redhat.com/show_bug.cgi?id=1495409 +Patch1: dnsmasq-2.77-underflow.patch +Patch3: dnsmasq-2.78-fips.patch +Patch4: dnsmasq-2.80-dnssec.patch +Patch5: dnsmasq-2.79-rh1602477.patch +# Few changes not yet in upstream +Patch6: dnsmasq-2.79-rh1602477-2.patch +# commit 60ac10d8d86e6f95ab0f06abe6c42596adcedcb8 +Patch7: dnsmasq-2.76-rh1752569.patch +# Report failure when no release would be sent +Patch8: dnsmasq-2.79-rh1749092-fail.patch +Patch9: dnsmasq-2.76-rh1728698-1.patch +Patch10: dnsmasq-2.79-rh1728698-2.patch +Patch11: dnsmasq-2.76-rh1728698-3.patch +Patch12: dnsmasq-2.79-rh1728698-4.patch +Patch13: dnsmasq-2.79-rh1746411.patch +Patch14: dnsmasq-2.79-rh1700916.patch +Patch15: dnsmasq-2.80-rh1795370.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=1779187 +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=97f876b64c22b2b18412e2e3d8506ee33e42db7c +Patch16: dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=f064188032a829efdcf3988b24ac795ff52785ec +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=137286e9baecf6a3ba97722ef1b49c851b531810 +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=79aba0f10ad0157fb4f48afbbcb03f094caff97a +Patch17: dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=52ec7836139e7a11374971905e5ac0d2d02e32c0 +Patch18: dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch +Patch19: dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch + +# This is workaround to nettle bug #1549190 +# https://bugzilla.redhat.com/show_bug.cgi?id=1549190 +Requires: nettle >= 3.4 + +BuildRequires: dbus-devel +BuildRequires: pkgconfig +BuildRequires: libidn2-devel +BuildRequires: nettle-devel +Buildrequires: gcc + +BuildRequires: systemd +%{?systemd_requires} + +%description +Dnsmasq is lightweight, easy to configure DNS forwarder and DHCP server. +It is designed to provide DNS and, optionally, DHCP, to a small network. +It can serve the names of local machines which are not in the global +DNS. The DHCP server integrates with the DNS server and allows machines +with DHCP-allocated addresses to appear in the DNS with names configured +either in each host or in a central configuration file. Dnsmasq supports +static and dynamic DHCP leases and BOOTP for network booting of diskless +machines. + +%package utils +Summary: Utilities for manipulating DHCP server leases + +%description utils +Utilities that use the standard DHCP protocol to query/remove a DHCP +server's leases. + + +%prep +%setup -q -n %{name}-%{version}%{?extraversion} +%patch1 -p1 -b .underflow +%patch3 -p1 -b .fips +%patch4 -p1 -b .dnssec +%patch5 -p1 -b .rh1602477 +%patch6 -p1 -b .rh1602477-2 +%patch7 -p1 -b .rh1752569 +%patch8 -p1 -b .rh1752569 +%patch9 -p1 -b .rh1728698-1 +%patch10 -p1 -b .rh1728698-2 +%patch11 -p1 -b .rh1728698-3 +%patch12 -p1 -b .rh1728698-4 +%patch13 -p1 -b .rh1746411 +%patch14 -p1 -b .rh1700916 +%patch15 -p1 -b .rh1795370 +%patch16 -p1 -b .rh1779187-1 +%patch17 -p1 -b .rh1779187-2 +%patch18 -p1 -b .rh1779187-3 +%patch19 -p1 -b .rh1779187-4 + +# use /var/lib/dnsmasq instead of /var/lib/misc +for file in dnsmasq.conf.example man/dnsmasq.8 man/es/dnsmasq.8 src/config.h; do + sed -i 's|/var/lib/misc/dnsmasq.leases|/var/lib/dnsmasq/dnsmasq.leases|g' "$file" +done + +# fix the path to the trust anchor +sed -i 's|%%%%PREFIX%%%%|%{_prefix}|' dnsmasq.conf.example + +#set dnsmasq user / group +sed -i 's|#user=|user=dnsmasq|' dnsmasq.conf.example +sed -i 's|#group=|group=dnsmasq|' dnsmasq.conf.example +#set default user /group in src/config.h +sed -i 's|#define CHUSER "nobody"|#define CHUSER "dnsmasq"|' src/config.h +sed -i 's|#define CHGRP "dip"|#define CHGRP "dnsmasq"|' src/config.h + +# optional parts +sed -i 's|^COPTS[[:space:]]*=|\0 -DHAVE_DBUS -DHAVE_LIBIDN2 -DHAVE_DNSSEC|' Makefile + +#enable /etc/dnsmasq.d fix bz 526703, ignore RPM backup files +cat << EOF >> dnsmasq.conf.example + +# Include all files in /etc/dnsmasq.d except RPM backup files +conf-dir=/etc/dnsmasq.d,.rpmnew,.rpmsave,.rpmorig +EOF + + +%build +%make_build CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="$RPM_LD_FLAGS" +%make_build -C contrib/lease-tools CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="$RPM_LD_FLAGS" + + +%install +# normally i'd do 'make install'...it's a bit messy, though +mkdir -p $RPM_BUILD_ROOT%{_sbindir} \ + $RPM_BUILD_ROOT%{_mandir}/man8 \ + $RPM_BUILD_ROOT%{_var}/lib/dnsmasq \ + $RPM_BUILD_ROOT%{_sysconfdir}/dnsmasq.d \ + $RPM_BUILD_ROOT%{_sysconfdir}/dbus-1/system.d +install src/dnsmasq $RPM_BUILD_ROOT%{_sbindir}/dnsmasq +install dnsmasq.conf.example $RPM_BUILD_ROOT%{_sysconfdir}/dnsmasq.conf +install dbus/dnsmasq.conf $RPM_BUILD_ROOT%{_sysconfdir}/dbus-1/system.d/ +install -m 644 man/dnsmasq.8 $RPM_BUILD_ROOT%{_mandir}/man8/ +install -D trust-anchors.conf $RPM_BUILD_ROOT%{_datadir}/%{name}/trust-anchors.conf + +# utils sub package +mkdir -p $RPM_BUILD_ROOT%{_bindir} \ + $RPM_BUILD_ROOT%{_mandir}/man1 +install -m 755 contrib/lease-tools/dhcp_release $RPM_BUILD_ROOT%{_bindir}/dhcp_release +install -m 644 contrib/lease-tools/dhcp_release.1 $RPM_BUILD_ROOT%{_mandir}/man1/dhcp_release.1 +install -m 755 contrib/lease-tools/dhcp_release6 $RPM_BUILD_ROOT%{_bindir}/dhcp_release6 +install -m 644 contrib/lease-tools/dhcp_release6.1 $RPM_BUILD_ROOT%{_mandir}/man1/dhcp_release6.1 +install -m 755 contrib/lease-tools/dhcp_lease_time $RPM_BUILD_ROOT%{_bindir}/dhcp_lease_time +install -m 644 contrib/lease-tools/dhcp_lease_time.1 $RPM_BUILD_ROOT%{_mandir}/man1/dhcp_lease_time.1 + +# Systemd +mkdir -p %{buildroot}%{_unitdir} +install -m644 %{SOURCE1} %{buildroot}%{_unitdir} +rm -rf %{buildroot}%{_initrddir} + +#install systemd sysuser file +install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf + +%pre +#precreate users so that rpm can install files owned by that user +%sysusers_create_inline %(cat %{SOURCE2}) + +%post +#https://fedoraproject.org/wiki/Changes/SystemdSysusers +%sysusers_create +%systemd_post dnsmasq.service + +%preun +%systemd_preun dnsmasq.service + +%postun +%systemd_postun_with_restart dnsmasq.service + +%files +%doc CHANGELOG FAQ doc.html setup.html dbus/DBus-interface +%license COPYING COPYING-v3 +%defattr(0644,root,dnsmasq,0755) +%config(noreplace) %{_sysconfdir}/dnsmasq.conf +%dir %{_sysconfdir}/dnsmasq.d +%dir %{_var}/lib/dnsmasq +%defattr(-,root,root,-) +%config(noreplace) %{_sysconfdir}/dbus-1/system.d/dnsmasq.conf +%{_unitdir}/%{name}.service +%{_sbindir}/dnsmasq +%{_mandir}/man8/dnsmasq* +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/trust-anchors.conf +%{_sysusersdir}/dnsmasq.conf + +%files utils +%license COPYING COPYING-v3 +%{_bindir}/dhcp_* +%{_mandir}/man1/dhcp_* + +%changelog +* Mon Mar 02 2020 Petr Menšík - 2.79-11 +- Support multiple static leases for single mac on IPv6 (#1779187) + +* Mon Feb 17 2020 Tomas Korbar - 2.79-10 +- Fix memory leak in helper.c (#1795370) + +* Tue Dec 10 2019 Tomas Korbar - 2.79-9 +- Fix replies to non-recursive queries (#1700916) + +* Mon Dec 09 2019 Tomas Korbar - 2.79-8 +- Fix dhcp_lease_time (#1746411) + +* Mon Dec 09 2019 Tomas Korbar - 2.79-7 +- Fix TCP queries after interface recreation (#1728698) + +* Mon Sep 30 2019 Petr Menšík - 2.79-6 +- Send dhcp_release even for addresses not on local network (#1749092) + +* Thu Jul 18 2019 Petr Menšík - 2.79-5 +- Fix Coverity detected issues (#1602477) + +* Thu Jul 26 2018 Zbigniew Jędrzejewski-Szmek - 2.79-4 +- Fix %%pre scriptlet (#1548050) + +* Mon Jul 02 2018 Petr Menšík - 2.79-3 +- Make dnsmasq leases writeable by root again (#1554390) + +* Mon Jul 02 2018 Petr Menšík - 2.79-2 +- Fix passing of dnssec enabled queries (#1597309) + +* Thu Mar 15 2018 Petr Menšík - 2.79-1 +- Rebase to 2.79 +- Stop using nettle_hashes directly, use access function (#1548060) +- Do not break on cname with spaces (#1498667) +- Require nettle 3.4+ +- Do not own sysusers.d directory, already depends on systemd providing it + +* Fri Mar 02 2018 Petr Menšík - 2.78-7 +- Emit warning with dnssec enabled on FIPS system (#1549507) + +* Sun Feb 25 2018 Zbigniew Jędrzejewski-Szmek - 2.78-6 +- Create user before installing files (#1548050) + +* Fri Feb 23 2018 Petr Menšík - 2.78-5 +- Create user first and then restart service + +* Thu Feb 22 2018 Itamar Reis Peixoto - 2.78-4 +- add gcc into buildrequires +- deliver an extra sysusers.d file to create dnsmasq user/group +- set CHUSER and CHGRP to dnsmasq in src/config.h + +* Wed Feb 07 2018 Fedora Release Engineering - 2.78-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Jan 22 2018 Petr Menšík - 2.78-2 +- DNSSEC fix for wildcard NSEC records (CVE-2017-15107) + +* Tue Oct 03 2017 Petr Menšík - 2.78-1 +- Rebase to 2.78 + +* Tue Oct 03 2017 Petr Menšík - 2.77-9 +- More patches related to CVE-2017-14491 + +* Mon Oct 02 2017 Petr Menšík - 2.77-8 +- Security fix, CVE-2017-14491, DNS heap buffer overflow +- Security fix, CVE-2017-14492, DHCPv6 RA heap overflow +- Security fix, CVE-2017-14493, DHCPv6 - Stack buffer overflow +- Security fix, CVE-2017-14494, Infoleak handling DHCPv6 +- Security fix, CVE-2017-14496, Integer underflow in DNS response creation +- Security fix, CVE-2017-14495, OOM in DNS response creation +- Misc code cleanups arising from Google analysis +- Do not include stdio.h before dnsmasq.h + +* Thu Sep 14 2017 Petr Menšík - 2.77-7 +- Fix CVE-2017-13704 + +* Mon Aug 14 2017 Petr Menšík - 2.77-6 +- Own the /usr/share/dnsmasq dir (#1480856) + +* Wed Aug 02 2017 Fedora Release Engineering - 2.77-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 2.77-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Wed Jun 07 2017 Petr Menšík - 2.77-3 +- Update to 2.77 + +* Fri May 12 2017 Petr Menšík - 2.77-2.rc2 +- Fix dhcp + +* Thu May 11 2017 Petr Menšík - 2.77-1 +- Update to 2.77rc2 + +* Thu May 11 2017 Petr Menšík +- Include dhcp_release6 tool and license in utils +- Support for IDN 2008 (#1449150) + +* Fri Feb 10 2017 Fedora Release Engineering - 2.76-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Wed Oct 19 2016 Pavel Šimerda - 2.76-2 +- Resolves: #1373485 - dns not updated after sleep and resume laptop + +* Fri Jul 15 2016 Pavel Šimerda - 2.76-1 +- New version 2.76 + +* Wed Feb 03 2016 Fedora Release Engineering - 2.75-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Mon Jan 25 2016 Tomas Hozza - 2.75-3 +- Fixed minor bug in dnsmasq.conf (#1295143) + +* Fri Oct 02 2015 Pavel Šimerda - 2.75-2 +- Resolves: #1239256 - install trust-anchors.conf + +* Wed Aug 05 2015 Pavel Šimerda - 2.75-1 +- new version 2.75 + +* Wed Jun 17 2015 Fedora Release Engineering - 2.72-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Mon Oct 06 2014 Nils Philippsen - 2.72-3 +- don't include /etc/dnsmasq.d in triplicate, ignore RPM backup files instead +- package is dual-licensed GPL v2 or v3 +- drop %%triggerun, we're not supposed to automatically migrate from SysV to + systemd anyway + +* Mon Oct 06 2014 Tomas Hozza - 2.72-2 +- Fix typo in default configuration (#1149459) + +* Thu Sep 25 2014 Tomas Hozza - 2.72-1 +- Update to 2.72 stable + +* Sat Aug 16 2014 Fedora Release Engineering - 2.71-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sat Jun 07 2014 Fedora Release Engineering - 2.71-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Tue May 20 2014 Tomas Hozza - 2.71-1 +- Update to 2.71 stable + +* Fri Apr 25 2014 Tomas Hozza - 2.70-1 +- Update to 2.70 stable + +* Fri Apr 11 2014 Tomas Hozza - 2.69-1 +- Update to 2.69 stable + +* Mon Mar 24 2014 Tomas Hozza - 2.69-0.1.rc1 +- Update to 2.69rc1 +- enable DNSSEC implementation + +* Mon Dec 09 2013 Tomas Hozza - 2.68-1 +- Update to 2.68 stable + +* Tue Nov 26 2013 Tomas Hozza - 2.68-0.1.rc3 +- Update to 2.68rc3 + +* Fri Nov 01 2013 Tomas Hozza - 2.67-1 +- Update to 2.67 stable +- Include one post release upstream fix for CNAME + +* Fri Oct 18 2013 Tomas Hozza - 2.67-0.9.rc4 +- update to 2.67rc4 + +* Wed Oct 02 2013 Tomas Hozza - 2.67-0.8.rc2 +- update to 2.67rc2 + +* Thu Sep 12 2013 Tomas Hozza - 2.67-0.7.test13 +- update to 2.67test13 +- use .tar.xz upstream archives + +* Thu Aug 15 2013 Tomas Hozza - 2.67-0.6.test7 +- Use SO_REUSEPORT and SO_REUSEADDR if possible for DHCPv4/6 (#981973) + +* Mon Aug 12 2013 Tomas Hozza - 2.67-0.5.test7 +- Don't use SO_REUSEPORT on DHCPv4 socket to prevent conflicts with ISC DHCP (#981973) + +* Sat Aug 03 2013 Fedora Release Engineering - 2.67-0.4.test7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Tue Jun 11 2013 Tomas Hozza - 2.67-0.3.test7 +- update to 2.67test7 +- drop merged patch +- use _hardened_build macro instead of hardcoded flags + +* Fri May 17 2013 Tomas Hozza - 2.67-0.2.test4 +- Fix failure to start with ENOTSOCK (#962874) + +* Wed May 15 2013 Tomas Hozza - 2.67-0.1.test4 +- update to the latest testing release 2.67test4 (#962246) +- drop mergerd patches + +* Tue Apr 30 2013 Tomas Hozza - 2.66-5 +- dnsmasq unit file cleanup + - drop forking Type and PIDfile and rather start dnsmasq with "-k" option + - drop After syslog.target as this is by default + +* Thu Apr 25 2013 Tomas Hozza - 2.66-4 +- include several fixes from upstream repo: + - Send TCP DNS messages in one packet + - Fix crash on SERVFAIL when using --conntrack option + - Fix regression in dhcp_lease_time utility + - Man page typos fixes + - Note that dhcp_lease_time and dhcp_release work only for IPv4 + - Fix for --dhcp-match option to work also with BOOTP protocol + +* Sat Apr 20 2013 Tomas Hozza - 2.66-3 +- Use Full RELRO when linking the daemon +- compile the daemon with PIE +- include two fixes from upstream git repo + +* Thu Apr 18 2013 Tomas Hozza - 2.66-2 +- New stable version dnsmasq-2.66 +- Drop of merged patch + +* Fri Apr 12 2013 Tomas Hozza - 2.66-1.rc5 +- Update to latest dnsmasq-2.66rc5 +- Include fix for segfault when lease limit is reached + +* Fri Mar 22 2013 Tomas Hozza - 2.66-1.rc1 +- Update to latest dnsmasq-2.66rc1 +- Dropping unneeded patches +- Enable IDN support + +* Fri Mar 15 2013 Tomas Hozza - 2.65-5 +- Allocate dhcp_buff-ers also if daemon->ra_contexts to prevent SIGSEGV (#920300) + +* Thu Jan 31 2013 Tomas Hozza - 2.65-4 +- Handle locally-routed DNS Queries (#904940) + +* Thu Jan 24 2013 Tomas Hozza - 2.65-3 +- build dnsmasq with $RPM_OPT_FLAGS, $RPM_LD_FLAGS explicitly (#903362) + +* Tue Jan 22 2013 Tomas Hozza - 2.65-2 +- Fix for CVE-2013-0198 (checking of TCP connection interfaces) (#901555) + +* Sat Dec 15 2012 Tomas Hozza - 2.65-1 +- new version 2.65 + +* Wed Dec 05 2012 Tomas Hozza - 2.64-1 +- New version 2.64 +- Merged patches dropped + +* Tue Nov 20 2012 Tomas Hozza - 2.63-4 +- Remove EnvironmentFile from service file (#878343) + +* Mon Nov 19 2012 Tomas Hozza - 2.63-3 +- dhcp6 support fixes (#867054) +- removed "-s $HOSTNAME" from .service file (#753656, #822797) + +* Tue Oct 23 2012 Tomas Hozza - 2.63-2 +- Introduce new systemd-rpm macros in dnsmasq spec file (#850096) + +* Thu Aug 23 2012 Douglas Schilling Landgraf - 2.63-1 +- Use .tar.gz compression, in upstream site there is no .lzma anymore +- New version 2.63 + +* Sat Feb 11 2012 Pádraig Brady - 2.59-5 +- Compile DHCP lease management utils with RPM_OPT_FLAGS + +* Thu Feb 9 2012 Pádraig Brady - 2.59-4 +- Include DHCP lease management utils in a subpackage + +* Fri Jan 13 2012 Fedora Release Engineering - 2.59-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Fri Aug 26 2011 Douglas Schilling Landgraf - 2.59-2 +- do not enable service by default + +* Fri Aug 26 2011 Douglas Schilling Landgraf - 2.59-1 +- New version 2.59 +- Fix regression in 2.58 (IPv6 issue) - bz 744814 + +* Fri Aug 26 2011 Douglas Schilling Landgraf - 2.58-1 +- Fixed License +- New version 2.58 + +* Mon Aug 08 2011 Patrick "Jima" Laughton - 2.52-5 +- Include systemd unit file + +* Mon Aug 08 2011 Patrick "Jima" Laughton - 2.52-3 +- Applied Jóhann's patch, minor cleanup + +* Tue Jul 26 2011 Jóhann B. Guðmundsson - 2.52-3 +- Introduce systemd unit file, drop SysV support + +* Tue Feb 08 2011 Fedora Release Engineering - 2.52-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Tue Jan 26 2010 Itamar Reis Peixoto - 2.52-1 +- New Version 2.52 +- fix condrestart() in initscript bz 547605 +- fix sed to enable DBUS(the '*' need some escaping) bz 553161 + +* Sun Nov 22 2009 Itamar Reis Peixoto - 2.51-2 +- fix bz 512664 + +* Sat Oct 17 2009 Itamar Reis Peixoto - 2.51-1 +- move initscript from patch to a plain text file +- drop (dnsmasq-configuration.patch) and use sed instead +- enable /etc/dnsmasq.d fix bz 526703 +- change requires to package name instead of file +- new version 2.51 + +* Mon Oct 5 2009 Mark McLoughlin - 2.48-4 +- Fix multiple TFTP server vulnerabilities (CVE-2009-2957, CVE-2009-2958) + +* Wed Aug 12 2009 Ville Skyttä - 2.48-3 +- Use lzma compressed upstream tarball. + +* Fri Jul 24 2009 Fedora Release Engineering - 2.48-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Wed Jun 10 2009 Patrick "Jima" Laughton 2.48-1 +- Bugfix/feature enhancement update +- Fixing BZ#494094 + +* Fri May 29 2009 Patrick "Jima" Laughton 2.47-1 +- Bugfix/feature enhancement update + +* Tue Feb 24 2009 Fedora Release Engineering - 2.46-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + +* Mon Dec 29 2008 Matěj Cepl - 2.45-2 +- rebuilt + +* Mon Jul 21 2008 Patrick "Jima" Laughton 2.45-1 +- Upstream release (bugfixes) + +* Wed Jul 16 2008 Patrick "Jima" Laughton 2.43-2 +- New upstream release, contains fixes for CVE-2008-1447/CERT VU#800113 +- Dropped patch for newer glibc (merged upstream) + +* Wed Feb 13 2008 Patrick "Jima" Laughton 2.41-0.8 +- Added upstream-authored patch for newer glibc (thanks Simon!) + +* Wed Feb 13 2008 Patrick "Jima" Laughton 2.41-0.7 +- New upstream release + +* Wed Jan 30 2008 Patrick "Jima" Laughton 2.41-0.6.rc1 +- Release candidate +- Happy Birthday Isaac! + +* Wed Jan 23 2008 Patrick "Jima" Laughton 2.41-0.5.test30 +- Bugfix update + +* Mon Dec 31 2007 Patrick "Jima" Laughton 2.41-0.4.test26 +- Bugfix/feature enhancement update + +* Thu Dec 13 2007 Patrick "Jima" Laughton 2.41-0.3.test24 +- Upstream fix for fairly serious regression + +* Tue Dec 04 2007 Patrick "Jima" Laughton 2.41-0.2.test20 +- New upstream test release +- Moving dnsmasq.leases to /var/lib/dnsmasq/ as per BZ#407901 +- Ignoring dangerous-command-in-%%post rpmlint warning (as per above fix) +- Patch consolidation/cleanup +- Removed conditionals for Fedora <= 3 and Aurora 2.0 + +* Tue Sep 18 2007 Patrick "Jima" Laughton 2.40-1 +- Finalized upstream release +- Removing URLs from patch lines (CVS is the authoritative source) +- Added more magic to make spinning rc/test packages more seamless + +* Sun Aug 26 2007 Patrick "Jima" Laughton 2.40-0.1.rc2 +- New upstream release candidate (feature-frozen), thanks Simon! +- License clarification + +* Tue May 29 2007 Patrick "Jima" Laughton 2.39-1 +- New upstream version (bugfixes, enhancements) + +* Mon Feb 12 2007 Patrick "Jima" Laughton 2.38-1 +- New upstream version with bugfix for potential hang + +* Tue Feb 06 2007 Patrick "Jima" Laughton 2.37-1 +- New upstream version + +* Wed Jan 24 2007 Patrick "Jima" Laughton 2.36-1 +- New upstream version + +* Mon Nov 06 2006 Patrick "Jima" Laughton 2.35-2 +- Stop creating /etc/sysconfig on %%install +- Create /etc/dnsmasq.d on %%install + +* Mon Nov 06 2006 Patrick "Jima" Laughton 2.35-1 +- Update to 2.35 +- Removed UPGRADING_to_2.0 from %%doc as per upstream change +- Enabled conf-dir in default config as per RFE BZ#214220 (thanks Chris!) +- Added %%dir /etc/dnsmasq.d to %%files as per above RFE + +* Tue Oct 24 2006 Patrick "Jima" Laughton 2.34-2 +- Fixed BZ#212005 +- Moved %%postun scriptlet to %%post, where it made more sense +- Render scriptlets safer +- Minor cleanup for consistency + +* Thu Oct 19 2006 Patrick "Jima" Laughton 2.34-1 +- Hardcoded version in patches, as I'm getting tired of updating them +- Update to 2.34 + +* Mon Aug 28 2006 Patrick "Jima" Laughton 2.33-2 +- Rebuild for FC6 + +* Tue Aug 15 2006 Patrick "Jima" Laughton 2.33-1 +- Update + +* Sat Jul 22 2006 Patrick "Jima" Laughton 2.32-3 +- Added pkgconfig BuildReq due to reduced buildroot + +* Thu Jul 20 2006 Patrick "Jima" Laughton 2.32-2 +- Forced update due to dbus version bump + +* Mon Jun 12 2006 Patrick "Jima" Laughton 2.32-1 +- Update from upstream +- Patch from Dennis Gilmore fixed the conditionals to detect Aurora Linux + +* Mon May 8 2006 Patrick "Jima" Laughton 2.31-1 +- Removed dbus config patch (now provided upstream) +- Patched in init script (no longer provided upstream) +- Added DBus-interface to docs + +* Tue May 2 2006 Patrick "Jima" Laughton 2.30-4.2 +- More upstream-recommended cleanups :) +- Killed sysconfig file (provides unneeded functionality) +- Tweaked init script a little more + +* Tue May 2 2006 Patrick "Jima" Laughton 2.30-4 +- Moved options out of init script and into /etc/sysconfig/dnsmasq +- Disabled DHCP_LEASE in sysconfig file, fixing bug #190379 +- Simon Kelley provided dbus/dnsmasq.conf, soon to be part of the tarball + +* Thu Apr 27 2006 Patrick "Jima" Laughton 2.30-3 +- Un-enabled HAVE_ISC_READER, a hack to enable a deprecated feature (request) +- Split initscript & enable-dbus patches, conditionalized dbus for FC3 +- Tweaked name field in changelog entries (trying to be consistent) + +* Mon Apr 24 2006 Patrick "Jima" Laughton 2.30-2 +- Disabled stripping of binary while installing (oops) +- Enabled HAVE_ISC_READER/HAVE_DBUS via patch +- Added BuildReq for dbus-devel + +* Mon Apr 24 2006 Patrick "Jima" Laughton 2.30-1 +- Initial Fedora Extras RPM