diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13d4ca7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/dnsmasq-2.79.tar.xz diff --git a/EMPTY b/EMPTY deleted file mode 100644 index 0519ecb..0000000 --- a/EMPTY +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dnsmasq-2.76-rh1728698-1.patch b/dnsmasq-2.76-rh1728698-1.patch new file mode 100644 index 0000000..2546301 --- /dev/null +++ b/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/dnsmasq-2.76-rh1728698-3.patch b/dnsmasq-2.76-rh1728698-3.patch new file mode 100644 index 0000000..cdb690d --- /dev/null +++ b/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/dnsmasq-2.76-rh1752569.patch b/dnsmasq-2.76-rh1752569.patch new file mode 100644 index 0000000..b46fcd3 --- /dev/null +++ b/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/dnsmasq-2.77-underflow.patch b/dnsmasq-2.77-underflow.patch new file mode 100644 index 0000000..2e900bf --- /dev/null +++ b/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/dnsmasq-2.78-fips.patch b/dnsmasq-2.78-fips.patch new file mode 100644 index 0000000..011433c --- /dev/null +++ b/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/dnsmasq-2.79-CVE-2020-25681.patch b/dnsmasq-2.79-CVE-2020-25681.patch new file mode 100644 index 0000000..41328d1 --- /dev/null +++ b/dnsmasq-2.79-CVE-2020-25681.patch @@ -0,0 +1,364 @@ +From 6689e336042d2ca0b075f8db1fe30ed6b47c7d4c Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 11 Nov 2020 23:25:04 +0000 +Subject: [PATCH 1/4] Fix remote buffer overflow CERT VU#434904 + +The problem is in the sort_rrset() function and allows a remote +attacker to overwrite memory. Any dnsmasq instance with DNSSEC +enabled is vulnerable. +--- + src/dnssec.c | 273 ++++++++++++++++++++++++++++----------------------- + 1 file changed, 152 insertions(+), 121 deletions(-) + +diff --git a/src/dnssec.c b/src/dnssec.c +index 8143185..0c703ac 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -222,138 +222,147 @@ static int check_date_range(u32 date_start, u32 date_end) + && serial_compare_32(curtime, date_end) == SERIAL_LT; + } + +-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining +- data, pointed to by *p, should be used raw. */ +-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, +- unsigned char **p, u16 **desc) ++/* Return bytes of canonicalised rrdata one by one. ++ Init state->ip with the RR, and state->end with the end of same. ++ Init state->op to NULL. ++ Init state->desc to RR descriptor. ++ Init state->buff with a MAXDNAME * 2 buffer. ++ ++ After each call which returns 1, state->op points to the next byte of data. ++ On returning 0, the end has been reached. ++*/ ++struct rdata_state { ++ u16 *desc; ++ size_t c; ++ unsigned char *end, *ip, *op; ++ char *buff; ++}; ++ ++static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) + { +- int d = **desc; ++ int d; + +- /* No more data needs mangling */ +- if (d == (u16)-1) ++ if (state->op && state->c != 1) + { +- /* If there's more data than we have space for, just return what fits, +- we'll get called again for more chunks */ +- if (end - *p > bufflen) +- { +- memcpy(buff, *p, bufflen); +- *p += bufflen; +- return bufflen; +- } +- +- return 0; ++ state->op++; ++ state->c--; ++ return 1; + } +- +- (*desc)++; +- +- if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) +- /* domain-name, canonicalise */ +- return to_wire(buff); +- else +- { +- /* plain data preceding a domain-name, don't run off the end of the data */ +- if ((end - *p) < d) +- d = end - *p; ++ ++ while (1) ++ { ++ d = *(state->desc); + +- if (d != 0) ++ if (d == (u16)-1) + { +- memcpy(buff, *p, d); +- *p += d; ++ /* all the bytes to the end. */ ++ if ((state->c = state->end - state->ip) != 0) ++ { ++ state->op = state->ip; ++ state->ip = state->end;; ++ } ++ else ++ return 0; ++ } ++ else ++ { ++ state->desc++; ++ ++ if (d == (u16)0) ++ { ++ /* domain-name, canonicalise */ ++ int len; ++ ++ if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || ++ (len = to_wire(state->buff)) == 0) ++ continue; ++ ++ state->c = len; ++ state->op = (unsigned char *)state->buff; ++ } ++ else ++ { ++ /* plain data preceding a domain-name, don't run off the end of the data */ ++ if ((state->end - state->ip) < d) ++ d = state->end - state->ip; ++ ++ if (d == 0) ++ continue; ++ ++ state->op = state->ip; ++ state->c = d; ++ state->ip += d; ++ } + } + +- return d; ++ return 1; + } + } + +-/* Bubble sort the RRset into the canonical order. +- Note that the byte-streams from two RRs may get unsynced: consider +- RRs which have two domain-names at the start and then other data. +- The domain-names may have different lengths in each RR, but sort equal +- +- ------------ +- |abcde|fghi| +- ------------ +- |abcd|efghi| +- ------------ +- +- leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +-*/ ++/* Bubble sort the RRset into the canonical order. */ + + static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) + { +- int swap, quit, i, j; ++ int swap, i, j; + + do + { + for (swap = 0, i = 0; i < rrsetidx-1; i++) + { +- int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; +- u16 *dp1, *dp2; +- unsigned char *end1, *end2; ++ int rdlen1, rdlen2; ++ struct rdata_state state1, state2; ++ + /* Note that these have been determined to be OK previously, + so we don't need to check for NULL return here. */ +- unsigned char *p1 = skip_name(rrset[i], header, plen, 10); +- unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); +- +- p1 += 8; /* skip class, type, ttl */ +- GETSHORT(rdlen1, p1); +- end1 = p1 + rdlen1; +- +- p2 += 8; /* skip class, type, ttl */ +- GETSHORT(rdlen2, p2); +- end2 = p2 + rdlen2; ++ state1.ip = skip_name(rrset[i], header, plen, 10); ++ state2.ip = skip_name(rrset[i+1], header, plen, 10); ++ state1.op = state2.op = NULL; ++ state1.buff = buff1; ++ state2.buff = buff2; ++ state1.desc = state2.desc = rr_desc; + +- dp1 = dp2 = rr_desc; ++ state1.ip += 8; /* skip class, type, ttl */ ++ GETSHORT(rdlen1, state1.ip); ++ if (!CHECK_LEN(header, state1.ip, plen, rdlen1)) ++ return rrsetidx; /* short packet */ ++ state1.end = state1.ip + rdlen1; + +- for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) ++ state2.ip += 8; /* skip class, type, ttl */ ++ GETSHORT(rdlen2, state2.ip); ++ if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) ++ return rrsetidx; /* short packet */ ++ state2.end = state2.ip + rdlen2; ++ ++ while (1) + { +- if (left1 != 0) +- memmove(buff1, buff1 + len1 - left1, left1); ++ int ok1, ok2; + +- if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) +- { +- quit = 1; +- len1 = end1 - p1; +- memcpy(buff1 + left1, p1, len1); +- } +- len1 += left1; +- +- if (left2 != 0) +- memmove(buff2, buff2 + len2 - left2, left2); +- +- if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) +- { +- quit = 1; +- len2 = end2 - p2; +- memcpy(buff2 + left2, p2, len2); +- } +- len2 += left2; +- +- if (len1 > len2) +- left1 = len1 - len2, left2 = 0, len = len2; +- else +- left2 = len2 - len1, left1 = 0, len = len1; +- +- rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); +- +- if (rc > 0 || (rc == 0 && quit && len1 > len2)) +- { +- unsigned char *tmp = rrset[i+1]; +- rrset[i+1] = rrset[i]; +- rrset[i] = tmp; +- swap = quit = 1; +- } +- else if (rc == 0 && quit && len1 == len2) ++ ok1 = get_rdata(header, plen, &state1); ++ ok2 = get_rdata(header, plen, &state2); ++ ++ if (!ok1 && !ok2) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; ++ break; ++ } ++ else if (ok1 && (!ok2 || *state1.op > *state2.op)) ++ { ++ unsigned char *tmp = rrset[i+1]; ++ rrset[i+1] = rrset[i]; ++ rrset[i] = tmp; ++ swap = 1; ++ break; + } +- else if (rc < 0) +- quit = 1; ++ else if (ok2 && (!ok1 || *state2.op > *state1.op)) ++ break; ++ ++ /* arrive here when bytes are equal, go round the loop again ++ and compare the next ones. */ + } + } + } while (swap); +@@ -549,15 +558,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + wire_len = to_wire(keyname); + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); + from_wire(keyname); ++ ++#define RRBUFLEN 300 /* Most RRs are smaller than this. */ + + for (i = 0; i < rrsetidx; ++i) + { +- int seg; +- unsigned char *end, *cp; +- u16 len, *dp; ++ int j; ++ struct rdata_state state; ++ u16 len; ++ unsigned char rrbuf[RRBUFLEN]; + + p = rrset[i]; +- ++ + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_BOGUS; + +@@ -566,12 +578,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ + if (labels < name_labels) + { +- int k; +- for (k = name_labels - labels; k != 0; k--) ++ for (j = name_labels - labels; j != 0; j--) + { + while (*name_start != '.' && *name_start != 0) + name_start++; +- if (k != 1 && *name_start == '.') ++ if (j != 1 && *name_start == '.') + name_start++; + } + +@@ -592,24 +603,44 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; + +- end = p + rdlen; ++ /* canonicalise rdata and calculate length of same, use ++ name buffer as workspace for get_rdata. */ ++ state.ip = p; ++ state.op = NULL; ++ state.desc = rr_desc; ++ state.buff = name; ++ state.end = p + rdlen; + +- /* canonicalise rdata and calculate length of same, use name buffer as workspace. +- Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ +- cp = p; +- dp = rr_desc; +- for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); +- len += end - cp; +- len = htons(len); ++ for (j = 0; get_rdata(header, plen, &state); j++) ++ if (j < RRBUFLEN) ++ rrbuf[j] = *state.op; ++ ++ len = htons((u16)j); + hash->update(ctx, 2, (unsigned char *)&len); ++ ++ /* If the RR is shorter than RRBUFLEN (most of them, in practice) ++ then we can just digest it now. If it exceeds RRBUFLEN we have to ++ go back to the start and do it in chunks. */ ++ if (j >= RRBUFLEN) ++ { ++ state.ip = p; ++ state.op = NULL; ++ state.desc = rr_desc; ++ ++ for (j = 0; get_rdata(header, plen, &state); j++) ++ { ++ rrbuf[j] = *state.op; ++ ++ if (j == RRBUFLEN - 1) ++ { ++ hash->update(ctx, RRBUFLEN, rrbuf); ++ j = -1; ++ } ++ } ++ } + +- /* Now canonicalise again and digest. */ +- cp = p; +- dp = rr_desc; +- while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) +- hash->update(ctx, seg, (unsigned char *)name); +- if (cp != end) +- hash->update(ctx, end - cp, cp); ++ if (j != 0) ++ hash->update(ctx, j, rrbuf); + } + + hash->digest(ctx, hash->digest_size, digest); +-- +2.26.2 + diff --git a/dnsmasq-2.79-CVE-2020-25684.patch b/dnsmasq-2.79-CVE-2020-25684.patch new file mode 100644 index 0000000..faee079 --- /dev/null +++ b/dnsmasq-2.79-CVE-2020-25684.patch @@ -0,0 +1,94 @@ +From 04ef96f428beebd07b457b51b5d2f26d3099f1a5 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Thu, 12 Nov 2020 18:49:23 +0000 +Subject: [PATCH 2/4] Check destination of DNS UDP query replies. + +At any time, dnsmasq will have a set of sockets open, bound to +random ports, on which it sends queries to upstream nameservers. +This patch fixes the existing problem that a reply for ANY in-flight +query would be accepted via ANY open port, which increases the +chances of an attacker flooding answers "in the blind" in an +attempt to poison the DNS cache. CERT VU#434904 refers. +--- + src/forward.c | 37 ++++++++++++++++++++++++++++--------- + 1 file changed, 28 insertions(+), 9 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index cdd11d3..85eab27 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -16,7 +16,7 @@ + + #include "dnsmasq.h" + +-static struct frec *lookup_frec(unsigned short id, void *hash); ++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); +@@ -780,7 +780,7 @@ void reply_query(int fd, int family, time_t now) + crc = questions_crc(header, n, daemon->namebuff); + #endif + +- if (!(forward = lookup_frec(ntohs(header->id), hash))) ++ if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) + return; + + /* log_query gets called indirectly all over the place, so +@@ -2195,14 +2195,25 @@ struct frec *get_new_frec(time_t now, int *wait, int force) + } + + /* crc is all-ones if not known. */ +-static struct frec *lookup_frec(unsigned short id, void *hash) ++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) + { + struct frec *f; + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) +- return f; ++ { ++ /* sent from random port */ ++ if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) ++ return f; ++ ++ if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) ++ return f; ++ ++ /* sent to upstream from bound socket. */ ++ if (f->sentto->sfd && f->sentto->sfd->fd == fd) ++ return f; ++ } + + return NULL; + } +@@ -2263,12 +2274,20 @@ void server_gone(struct server *server) + static unsigned short get_id(void) + { + unsigned short ret = 0; ++ struct frec *f; + +- do +- ret = rand16(); +- while (lookup_frec(ret, NULL)); +- +- return ret; ++ while (1) ++ { ++ ret = rand16(); ++ ++ /* ensure id is unique. */ ++ for (f = daemon->frec_list; f; f = f->next) ++ if (f->sentto && f->new_id == ret) ++ break; ++ ++ if (!f) ++ return ret; ++ } + } + + +-- +2.26.2 + diff --git a/dnsmasq-2.79-CVE-2020-25685.patch b/dnsmasq-2.79-CVE-2020-25685.patch new file mode 100644 index 0000000..8cfb4be --- /dev/null +++ b/dnsmasq-2.79-CVE-2020-25685.patch @@ -0,0 +1,586 @@ +From 6cbbae54c9232f182cc76f05962a07244d748b75 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Thu, 12 Nov 2020 22:06:07 +0000 +Subject: [PATCH 3/4] Use SHA-256 to provide security against DNS cache + poisoning. + +Use the SHA-256 hash function to verify that DNS answers +received are for the questions originally asked. This replaces +the slightly insecure SHA-1 (when compiled with DNSSEC) or +the very insecure CRC32 (otherwise). Refer: CERT VU#434904. +--- + Makefile | 3 +- + bld/Android.mk | 3 +- + src/dnsmasq.h | 11 +- + src/dnssec.c | 31 ----- + src/forward.c | 43 ++----- + src/hash_questions.c | 281 +++++++++++++++++++++++++++++++++++++++++++ + src/rfc1035.c | 49 -------- + 7 files changed, 297 insertions(+), 124 deletions(-) + create mode 100644 src/hash_questions.c + +diff --git a/Makefile b/Makefile +index 98ec760..cbbe5d7 100644 +--- a/Makefile ++++ b/Makefile +@@ -76,7 +76,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ + helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ + dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ + domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ +- poll.o rrfilter.o edns0.o arp.o crypto.o ++ poll.o rrfilter.o edns0.o arp.o crypto.o \ ++ hash_questions.o + + hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ + dns-protocol.h radv-protocol.h ip6addr.h +diff --git a/bld/Android.mk b/bld/Android.mk +index 80ec842..2db29c1 100644 +--- a/bld/Android.mk ++++ b/bld/Android.mk +@@ -10,7 +10,8 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ + dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ + radv.c slaac.c auth.c ipset.c domain.c \ + dnssec.c dnssec-openssl.c blockdata.c tables.c \ +- loop.c inotify.c poll.c rrfilter.c edns0.c arp.c crypto.c ++ loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \ ++ crypto.c hash_questions.c + + LOCAL_MODULE := dnsmasq + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 6773b69..f31503d 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -615,11 +615,7 @@ struct hostsfile { + #define FREC_TEST_PKTSZ 256 + #define FREC_HAS_EXTRADATA 512 + +-#ifdef HAVE_DNSSEC +-#define HASH_SIZE 20 /* SHA-1 digest size */ +-#else +-#define HASH_SIZE sizeof(int) +-#endif ++#define HASH_SIZE 32 /* SHA-256 digest size */ + + struct frec { + union mysockaddr source; +@@ -1156,7 +1152,6 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, + struct bogus_addr *baddr, time_t now); + int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); + int check_for_local_domain(char *name, time_t now); +-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name); + size_t resize_packet(struct dns_header *header, size_t plen, + unsigned char *pheader, size_t hlen); + int add_resource_record(struct dns_header *header, char *limit, int *truncp, +@@ -1184,9 +1179,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + int check_unsigned, int *neganswer, int *nons); + int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); + size_t filter_rrsigs(struct dns_header *header, size_t plen); +-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); + int setup_timestamp(void); + ++/* hash_questions.c */ ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name); ++ + /* crypto.c */ + const struct nettle_hash *hash_find(char *name); + int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp); +diff --git a/src/dnssec.c b/src/dnssec.c +index 0c703ac..b2dda1b 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -2095,35 +2095,4 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char + return ret; + } + +-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) +-{ +- int q; +- unsigned int len; +- unsigned char *p = (unsigned char *)(header+1); +- const struct nettle_hash *hash; +- void *ctx; +- unsigned char *digest; +- +- if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) +- return NULL; +- +- for (q = ntohs(header->qdcount); q != 0; q--) +- { +- if (!extract_name(header, plen, &p, name, 1, 4)) +- break; /* bad packet */ +- +- len = to_wire(name); +- hash->update(ctx, len, (unsigned char *)name); +- /* CRC the class and type as well */ +- hash->update(ctx, 4, p); +- +- p += 4; +- if (!CHECK_LEN(header, p, plen, 0)) +- break; /* bad packet */ +- } +- +- hash->digest(ctx, hash->digest_size, digest); +- return digest; +-} +- + #endif /* HAVE_DNSSEC */ +diff --git a/src/forward.c b/src/forward.c +index 85eab27..7ffcaf7 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -239,19 +239,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + struct all_addr *addrp = NULL; + unsigned int flags = 0; + struct server *start = NULL; +-#ifdef HAVE_DNSSEC + void *hash = hash_questions(header, plen, daemon->namebuff); ++#ifdef HAVE_DNSSEC + int do_dnssec = 0; +-#else +- unsigned int crc = questions_crc(header, plen, daemon->namebuff); +- void *hash = &crc; + #endif + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + + (void)do_bit; + + /* may be no servers available. */ +- if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) ++ if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) + { + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. +@@ -741,9 +738,6 @@ void reply_query(int fd, int family, time_t now) + size_t nn; + struct server *server; + void *hash; +-#ifndef HAVE_DNSSEC +- unsigned int crc; +-#endif + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +@@ -773,12 +767,7 @@ void reply_query(int fd, int family, time_t now) + if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) + server->edns_pktsz = daemon->edns_pktsz; + +-#ifdef HAVE_DNSSEC + hash = hash_questions(header, n, daemon->namebuff); +-#else +- hash = &crc; +- crc = questions_crc(header, n, daemon->namebuff); +-#endif + + if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) + return; +@@ -1006,8 +995,7 @@ void reply_query(int fd, int family, time_t now) + nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } +- if ((hash = hash_questions(header, nn, daemon->namebuff))) +- memcpy(new->hash, hash, HASH_SIZE); ++ memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ +@@ -1840,15 +1828,9 @@ unsigned char *tcp_request(int confd, time_t now, + if (!flags && last_server) + { + struct server *firstsendto = NULL; +-#ifdef HAVE_DNSSEC +- unsigned char *newhash, hash[HASH_SIZE]; +- if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) +- memcpy(hash, newhash, HASH_SIZE); +- else +- memset(hash, 0, HASH_SIZE); +-#else +- unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); +-#endif ++ unsigned char hash[HASH_SIZE]; ++ memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE); ++ + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtly ensures that consecutive queries on this connection + which can go to the same server, do so. */ +@@ -1973,20 +1955,11 @@ unsigned char *tcp_request(int confd, time_t now, + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ +-#ifdef HAVE_DNSSEC +- newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); +- if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) ++ if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0) + { + m = 0; + break; + } +-#else +- if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) +- { +- m = 0; +- break; +- } +-#endif + + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, +@@ -2201,7 +2174,7 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && +- (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) ++ (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ + if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) +diff --git a/src/hash_questions.c b/src/hash_questions.c +new file mode 100644 +index 0000000..ae112ac +--- /dev/null ++++ b/src/hash_questions.c +@@ -0,0 +1,281 @@ ++/* Copyright (c) 2012-2020 Simon Kelley ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; version 2 dated June, 1991, or ++ (at your option) version 3 dated 29 June, 2007. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++*/ ++ ++ ++/* Hash the question section. This is used to safely detect query ++ retransmission and to detect answers to questions we didn't ask, which ++ might be poisoning attacks. Note that we decode the name rather ++ than CRC the raw bytes, since replies might be compressed differently. ++ We ignore case in the names for the same reason. ++ ++ The hash used is SHA-256. If we're building with DNSSEC support, ++ we use the Nettle cypto library. If not, we prefer not to ++ add a dependency on Nettle, and use a stand-alone implementaion. ++*/ ++ ++#include "dnsmasq.h" ++ ++#ifdef HAVE_DNSSEC ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) ++{ ++ int q; ++ unsigned char *p = (unsigned char *)(header+1); ++ const struct nettle_hash *hash; ++ void *ctx; ++ unsigned char *digest; ++ ++ if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest)) ++ { ++ /* don't think this can ever happen. */ ++ static unsigned char dummy[HASH_SIZE]; ++ static int warned = 0; ++ ++ if (warned) ++ my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object")); ++ warned = 1; ++ ++ return dummy; ++ } ++ ++ for (q = ntohs(header->qdcount); q != 0; q--) ++ { ++ char *cp, c; ++ ++ if (!extract_name(header, plen, &p, name, 1, 4)) ++ break; /* bad packet */ ++ ++ for (cp = name; (c = *cp); cp++) ++ if (c >= 'A' && c <= 'Z') ++ *cp += 'a' - 'A'; ++ ++ hash->update(ctx, cp - name, (unsigned char *)name); ++ /* CRC the class and type as well */ ++ hash->update(ctx, 4, p); ++ ++ p += 4; ++ if (!CHECK_LEN(header, p, plen, 0)) ++ break; /* bad packet */ ++ } ++ ++ hash->digest(ctx, hash->digest_size, digest); ++ return digest; ++} ++ ++#else /* HAVE_DNSSEC */ ++ ++#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest ++typedef unsigned char BYTE; // 8-bit byte ++typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines ++ ++typedef struct { ++ BYTE data[64]; ++ WORD datalen; ++ unsigned long long bitlen; ++ WORD state[8]; ++} SHA256_CTX; ++ ++static void sha256_init(SHA256_CTX *ctx); ++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); ++static void sha256_final(SHA256_CTX *ctx, BYTE hash[]); ++ ++ ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) ++{ ++ int q; ++ unsigned char *p = (unsigned char *)(header+1); ++ SHA256_CTX ctx; ++ static BYTE digest[SHA256_BLOCK_SIZE]; ++ ++ sha256_init(&ctx); ++ ++ for (q = ntohs(header->qdcount); q != 0; q--) ++ { ++ char *cp, c; ++ ++ if (!extract_name(header, plen, &p, name, 1, 4)) ++ break; /* bad packet */ ++ ++ for (cp = name; (c = *cp); cp++) ++ if (c >= 'A' && c <= 'Z') ++ *cp += 'a' - 'A'; ++ ++ sha256_update(&ctx, (BYTE *)name, cp - name); ++ /* CRC the class and type as well */ ++ sha256_update(&ctx, (BYTE *)p, 4); ++ ++ p += 4; ++ if (!CHECK_LEN(header, p, plen, 0)) ++ break; /* bad packet */ ++ } ++ ++ sha256_final(&ctx, digest); ++ return (unsigned char *)digest; ++} ++ ++/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms ++ and was written by Brad Conte (brad@bradconte.com), to whom all credit is given. ++ ++ This code is in the public domain, and the copyright notice at the head of this ++ file does not apply to it. ++*/ ++ ++ ++/****************************** MACROS ******************************/ ++#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) ++#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) ++ ++#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) ++#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) ++#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) ++#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) ++#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) ++#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) ++ ++/**************************** VARIABLES *****************************/ ++static const WORD k[64] = { ++ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, ++ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, ++ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, ++ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, ++ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, ++ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, ++ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, ++ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 ++}; ++ ++/*********************** FUNCTION DEFINITIONS ***********************/ ++static void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) ++{ ++ WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; ++ ++ for (i = 0, j = 0; i < 16; ++i, j += 4) ++ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); ++ for ( ; i < 64; ++i) ++ m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; ++ ++ a = ctx->state[0]; ++ b = ctx->state[1]; ++ c = ctx->state[2]; ++ d = ctx->state[3]; ++ e = ctx->state[4]; ++ f = ctx->state[5]; ++ g = ctx->state[6]; ++ h = ctx->state[7]; ++ ++ for (i = 0; i < 64; ++i) ++ { ++ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; ++ t2 = EP0(a) + MAJ(a,b,c); ++ h = g; ++ g = f; ++ f = e; ++ e = d + t1; ++ d = c; ++ c = b; ++ b = a; ++ a = t1 + t2; ++ } ++ ++ ctx->state[0] += a; ++ ctx->state[1] += b; ++ ctx->state[2] += c; ++ ctx->state[3] += d; ++ ctx->state[4] += e; ++ ctx->state[5] += f; ++ ctx->state[6] += g; ++ ctx->state[7] += h; ++} ++ ++static void sha256_init(SHA256_CTX *ctx) ++{ ++ ctx->datalen = 0; ++ ctx->bitlen = 0; ++ ctx->state[0] = 0x6a09e667; ++ ctx->state[1] = 0xbb67ae85; ++ ctx->state[2] = 0x3c6ef372; ++ ctx->state[3] = 0xa54ff53a; ++ ctx->state[4] = 0x510e527f; ++ ctx->state[5] = 0x9b05688c; ++ ctx->state[6] = 0x1f83d9ab; ++ ctx->state[7] = 0x5be0cd19; ++} ++ ++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) ++{ ++ WORD i; ++ ++ for (i = 0; i < len; ++i) ++ { ++ ctx->data[ctx->datalen] = data[i]; ++ ctx->datalen++; ++ if (ctx->datalen == 64) { ++ sha256_transform(ctx, ctx->data); ++ ctx->bitlen += 512; ++ ctx->datalen = 0; ++ } ++ } ++} ++ ++static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) ++{ ++ WORD i; ++ ++ i = ctx->datalen; ++ ++ // Pad whatever data is left in the buffer. ++ if (ctx->datalen < 56) ++ { ++ ctx->data[i++] = 0x80; ++ while (i < 56) ++ ctx->data[i++] = 0x00; ++ } ++ else ++ { ++ ctx->data[i++] = 0x80; ++ while (i < 64) ++ ctx->data[i++] = 0x00; ++ sha256_transform(ctx, ctx->data); ++ memset(ctx->data, 0, 56); ++ } ++ ++ // Append to the padding the total message's length in bits and transform. ++ ctx->bitlen += ctx->datalen * 8; ++ ctx->data[63] = ctx->bitlen; ++ ctx->data[62] = ctx->bitlen >> 8; ++ ctx->data[61] = ctx->bitlen >> 16; ++ ctx->data[60] = ctx->bitlen >> 24; ++ ctx->data[59] = ctx->bitlen >> 32; ++ ctx->data[58] = ctx->bitlen >> 40; ++ ctx->data[57] = ctx->bitlen >> 48; ++ ctx->data[56] = ctx->bitlen >> 56; ++ sha256_transform(ctx, ctx->data); ++ ++ // Since this implementation uses little endian byte ordering and SHA uses big endian, ++ // reverse all the bytes when copying the final state to the output hash. ++ for (i = 0; i < 4; ++i) ++ { ++ hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; ++ } ++} ++ ++#endif +diff --git a/src/rfc1035.c b/src/rfc1035.c +index b078b59..d413f58 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -335,55 +335,6 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h + return ansp; + } + +-/* CRC the question section. This is used to safely detect query +- retransmission and to detect answers to questions we didn't ask, which +- might be poisoning attacks. Note that we decode the name rather +- than CRC the raw bytes, since replies might be compressed differently. +- We ignore case in the names for the same reason. Return all-ones +- if there is not question section. */ +-#ifndef HAVE_DNSSEC +-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) +-{ +- int q; +- unsigned int crc = 0xffffffff; +- unsigned char *p1, *p = (unsigned char *)(header+1); +- +- for (q = ntohs(header->qdcount); q != 0; q--) +- { +- if (!extract_name(header, plen, &p, name, 1, 4)) +- return crc; /* bad packet */ +- +- for (p1 = (unsigned char *)name; *p1; p1++) +- { +- int i = 8; +- char c = *p1; +- +- if (c >= 'A' && c <= 'Z') +- c += 'a' - 'A'; +- +- crc ^= c << 24; +- while (i--) +- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; +- } +- +- /* CRC the class and type as well */ +- for (p1 = p; p1 < p+4; p1++) +- { +- int i = 8; +- crc ^= *p1 << 24; +- while (i--) +- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; +- } +- +- p += 4; +- if (!CHECK_LEN(header, p, plen, 0)) +- return crc; /* bad packet */ +- } +- +- return crc; +-} +-#endif +- + size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) + { + unsigned char *ansp = skip_questions(header, plen); +-- +2.26.2 + diff --git a/dnsmasq-2.79-CVE-2020-25686-2.patch b/dnsmasq-2.79-CVE-2020-25686-2.patch new file mode 100644 index 0000000..6b3a9a8 --- /dev/null +++ b/dnsmasq-2.79-CVE-2020-25686-2.patch @@ -0,0 +1,61 @@ +From e9db3fdf55cdf3175d96db90313c33f848985960 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 4 Dec 2020 18:35:11 +0000 +Subject: [PATCH] Small cleanups in frec_src datastucture handling. + +--- + src/forward.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index 25ad8b1..c496f86 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -364,7 +364,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (!daemon->free_frec_src && + daemon->frec_src_count < daemon->ftabsize && + (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) +- daemon->frec_src_count++; ++ { ++ daemon->frec_src_count++; ++ daemon->free_frec_src->next = NULL; ++ } + + /* If we've been spammed with many duplicates, just drop the query. */ + if (daemon->free_frec_src) +@@ -401,6 +404,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + forward->frec_src.orig_id = ntohs(header->id); + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; ++ forward->frec_src.next = NULL; + forward->new_id = get_id(); + forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); +@@ -2262,16 +2266,16 @@ void free_rfd(struct randfd *rfd) + + static void free_frec(struct frec *f) + { +- struct frec_src *src, *tmp; +- +- /* add back to freelist of not the record builtin to every frec. */ +- for (src = f->frec_src.next; src; src = tmp) ++ struct frec_src *last; ++ ++ /* add back to freelist if not the record builtin to every frec. */ ++ for (last = f->frec_src.next; last && last->next; last = last->next) ; ++ if (last) + { +- tmp = src->next; +- src->next = daemon->free_frec_src; +- daemon->free_frec_src = src; ++ last->next = daemon->free_frec_src; ++ daemon->free_frec_src = f->frec_src.next; + } +- ++ + f->frec_src.next = NULL; + free_rfd(f->rfd4); + f->rfd4 = NULL; +-- +2.26.2 + diff --git a/dnsmasq-2.79-CVE-2020-25686.patch b/dnsmasq-2.79-CVE-2020-25686.patch new file mode 100644 index 0000000..fd9ad9b --- /dev/null +++ b/dnsmasq-2.79-CVE-2020-25686.patch @@ -0,0 +1,327 @@ +From 7d98347aeeb575a022ffae630ee02da215b6f37b Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 18 Nov 2020 18:34:55 +0000 +Subject: [PATCH 4/4] Handle multiple identical near simultaneous DNS queries + better. + +Previously, such queries would all be forwarded +independently. This is, in theory, inefficent but in practise +not a problem, _except_ that is means that an answer for any +of the forwarded queries will be accepted and cached. +An attacker can send a query multiple times, and for each repeat, +another {port, ID} becomes capable of accepting the answer he is +sending in the blind, to random IDs and ports. The chance of a +succesful attack is therefore multiplied by the number of repeats +of the query. The new behaviour detects repeated queries and +merely stores the clients sending repeats so that when the +first query completes, the answer can be sent to all the +clients who asked. Refer: CERT VU#434904. +--- + src/dnsmasq.h | 19 ++++--- + src/forward.c | 141 ++++++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 132 insertions(+), 28 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index f31503d..6744e2b 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -613,21 +613,26 @@ struct hostsfile { + #define FREC_DO_QUESTION 64 + #define FREC_ADDED_PHEADER 128 + #define FREC_TEST_PKTSZ 256 +-#define FREC_HAS_EXTRADATA 512 ++#define FREC_HAS_EXTRADATA 512 ++#define FREC_HAS_PHEADER 1024 + + #define HASH_SIZE 32 /* SHA-256 digest size */ + + struct frec { +- union mysockaddr source; +- struct all_addr dest; ++ struct frec_src { ++ union mysockaddr source; ++ struct all_addr dest; ++ unsigned int iface, log_id; ++ unsigned short orig_id; ++ struct frec_src *next; ++ } frec_src; + struct server *sentto; /* NULL means free */ + struct randfd *rfd4; + #ifdef HAVE_IPV6 + struct randfd *rfd6; + #endif +- unsigned int iface; +- unsigned short orig_id, new_id; +- int log_id, fd, forwardall, flags; ++ unsigned short new_id; ++ int fd, forwardall, flags; + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +@@ -1033,6 +1038,8 @@ extern struct daemon { + #endif + unsigned int local_answer, queries_forwarded, auth_answer; + struct frec *frec_list; ++ struct frec_src *free_frec_src; ++ int frec_src_count; + struct serverfd *sfds; + struct irec *interfaces; + struct listener *listeners; +diff --git a/src/forward.c b/src/forward.c +index 7ffcaf7..db378a5 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); ++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); ++ + static unsigned short get_id(void); + static void free_frec(struct frec *f); + +@@ -238,15 +240,28 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + int type = SERV_DO_DNSSEC, norebind = 0; + struct all_addr *addrp = NULL; + unsigned int flags = 0; ++ unsigned int fwd_flags = 0; + struct server *start = NULL; + void *hash = hash_questions(header, plen, daemon->namebuff); + #ifdef HAVE_DNSSEC + int do_dnssec = 0; + #endif +- unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); ++ unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); ++ unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); + + (void)do_bit; +- ++ ++ if (header->hb4 & HB4_CD) ++ fwd_flags |= FREC_CHECKING_DISABLED; ++ if (ad_reqd) ++ fwd_flags |= FREC_AD_QUESTION; ++ if (oph) ++ fwd_flags |= FREC_HAS_PHEADER; ++#ifdef HAVE_DNSSEC ++ if (do_bit) ++ fwd_flags |= FREC_DO_QUESTION; ++#endif ++ + /* may be no servers available. */ + if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) + { +@@ -322,6 +337,39 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + } + else + { ++ /* Query from new source, but the same query may be in progress ++ from another source. If so, just add this client to the ++ list that will get the reply. ++ ++ Note that is the EDNS client subnet option is in use, we can't do this, ++ as the clients (and therefore query EDNS options) will be different ++ for each query. The EDNS subnet code has checks to avoid ++ attacks in this case. */ ++ if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags))) ++ { ++ /* Note whine_malloc() zeros memory. */ ++ if (!daemon->free_frec_src && ++ daemon->frec_src_count < daemon->ftabsize && ++ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) ++ daemon->frec_src_count++; ++ ++ /* If we've been spammed with many duplicates, just drop the query. */ ++ if (daemon->free_frec_src) ++ { ++ struct frec_src *new = daemon->free_frec_src; ++ daemon->free_frec_src = new->next; ++ new->next = forward->frec_src.next; ++ forward->frec_src.next = new; ++ new->orig_id = ntohs(header->id); ++ new->source = *udpaddr; ++ new->dest = *dst_addr; ++ new->log_id = daemon->log_id; ++ new->iface = dst_iface; ++ } ++ ++ return 1; ++ } ++ + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + +@@ -329,22 +377,22 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + do_dnssec = type & SERV_DO_DNSSEC; + #endif + type &= ~SERV_DO_DNSSEC; +- ++ + if (daemon->servers && !flags) + forward = get_new_frec(now, NULL, 0); + /* table full - flags == 0, return REFUSED */ + + if (forward) + { +- forward->source = *udpaddr; +- forward->dest = *dst_addr; +- forward->iface = dst_iface; +- forward->orig_id = ntohs(header->id); ++ forward->frec_src.source = *udpaddr; ++ forward->frec_src.orig_id = ntohs(header->id); ++ forward->frec_src.dest = *dst_addr; ++ forward->frec_src.iface = dst_iface; + forward->new_id = get_id(); + forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; +- forward->flags = 0; ++ forward->flags = fwd_flags; + if (norebind) + forward->flags |= FREC_NOREBIND; + if (header->hb4 & HB4_CD) +@@ -400,9 +448,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + unsigned char *pheader; + + /* If a query is retried, use the log_id for the retry when logging the answer. */ +- forward->log_id = daemon->log_id; ++ forward->frec_src.log_id = daemon->log_id; + +- plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet); ++ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet); + + if (subnet) + forward->flags |= FREC_HAS_SUBNET; +@@ -539,7 +587,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + return 1; + + /* could not send on, prepare to return */ +- header->id = htons(forward->orig_id); ++ header->id = htons(forward->frec_src.orig_id); + free_frec(forward); /* cancel */ + } + +@@ -774,8 +822,8 @@ void reply_query(int fd, int family, time_t now) + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ +- daemon->log_display_id = forward->log_id; +- daemon->log_source_addr = &forward->source; ++ daemon->log_display_id = forward->frec_src.log_id; ++ daemon->log_source_addr = &forward->frec_src.source; + + if (daemon->ignore_addr && RCODE(header) == NOERROR && + check_for_ignored_address(header, n, daemon->ignore_addr)) +@@ -978,6 +1026,7 @@ void reply_query(int fd, int family, time_t now) + #ifdef HAVE_IPV6 + new->rfd6 = NULL; + #endif ++ new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); + + new->dependent = forward; /* to find query awaiting new one. */ +@@ -1099,9 +1148,11 @@ void reply_query(int fd, int family, time_t now) + + if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, +- forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) ++ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) + { +- header->id = htons(forward->orig_id); ++ struct frec_src *src; ++ ++ header->id = htons(forward->frec_src.orig_id); + header->hb4 |= HB4_RA; /* recursion if available */ + #ifdef HAVE_DNSSEC + /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size +@@ -1116,9 +1167,22 @@ void reply_query(int fd, int family, time_t now) + nn = resize_packet(header, nn, NULL, 0); + } + #endif +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, +- &forward->source, &forward->dest, forward->iface); ++ for (src = &forward->frec_src; src; src = src->next) ++ { ++ header->id = htons(src->orig_id); ++ ++ send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, ++ &src->source, &src->dest, src->iface); ++ ++ if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) ++ { ++ daemon->log_display_id = src->log_id; ++ daemon->log_source_addr = &src->source; ++ log_query(F_UPSTREAM, "query", NULL, "duplicate"); ++ } ++ } + } ++ + free_frec(forward); /* cancel */ + } + } +@@ -2053,6 +2117,17 @@ void free_rfd(struct randfd *rfd) + + static void free_frec(struct frec *f) + { ++ struct frec_src *src, *tmp; ++ ++ /* add back to freelist of not the record builtin to every frec. */ ++ for (src = f->frec_src.next; src; src = tmp) ++ { ++ tmp = src->next; ++ src->next = daemon->free_frec_src; ++ daemon->free_frec_src = src; ++ } ++ ++ f->frec_src.next = NULL; + free_rfd(f->rfd4); + f->rfd4 = NULL; + f->sentto = NULL; +@@ -2196,17 +2271,39 @@ static struct frec *lookup_frec_by_sender(unsigned short id, + void *hash) + { + struct frec *f; ++ struct frec_src *src; ++ ++ for (f = daemon->frec_list; f; f = f->next) ++ if (f->sentto && ++ !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && ++ memcmp(hash, f->hash, HASH_SIZE) == 0) ++ for (src = &f->frec_src; src; src = src->next) ++ if (src->orig_id == id && ++ sockaddr_isequal(&src->source, addr)) ++ return f; ++ ++ return NULL; ++} ++ ++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) ++{ ++ struct frec *f; ++ ++ /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below ++ ensures that no frec created for internal DNSSEC query can be returned here. */ ++ ++#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ ++ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY) + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && +- f->orig_id == id && +- memcmp(hash, f->hash, HASH_SIZE) == 0 && +- sockaddr_isequal(&f->source, addr)) ++ (f->flags & FLAGMASK) == flags && ++ memcmp(hash, f->hash, HASH_SIZE) == 0) + return f; +- ++ + return NULL; + } +- ++ + /* Send query packet again, if we can. */ + void resend_query() + { +-- +2.26.2 + diff --git a/dnsmasq-2.79-alternative-lease.patch b/dnsmasq-2.79-alternative-lease.patch new file mode 100644 index 0000000..e51d2b3 --- /dev/null +++ b/dnsmasq-2.79-alternative-lease.patch @@ -0,0 +1,107 @@ +From 268080fc19990711a1d1e1acd68a50aa2f6cb5fb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Fri, 17 Sep 2021 20:12:21 +0200 +Subject: [PATCH] Offer alternative DHCPv6 address if requested is taken + +In some cases multiple requests might arrive from single DUID. It may +happen just one address is offered to different IAID requests. When +the first request confirms lease, another would be offered alternative +address instead of address in use error. + +Includes check on such Rapid commit equivalents and returns NotOnLink +error, required by RFC 8145, if requested address were not on any +supported prefix. +--- + src/rfc3315.c | 39 ++++++++++++++++++++++++++++----------- + 1 file changed, 28 insertions(+), 11 deletions(-) + +diff --git a/src/rfc3315.c b/src/rfc3315.c +index 5c2ff97..d1534ad 100644 +--- a/src/rfc3315.c ++++ b/src/rfc3315.c +@@ -614,7 +614,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + + case DHCP6SOLICIT: + { +- int address_assigned = 0; ++ int address_assigned = 0, ia_invalid = 0; + /* tags without all prefix-class tags */ + struct dhcp_netid *solicit_tags; + struct dhcp_context *c; +@@ -697,6 +697,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + get_context_tag(state, c); + address_assigned = 1; + } ++ else ++ ia_invalid++; + } + + /* Suggest configured address(es) */ +@@ -782,11 +784,26 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + tagif = add_options(state, 0); + } + else +- { ++ { ++ char *errmsg; + /* no address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); +- put_opt6_short(DHCP6NOADDRS); +- put_opt6_string(_("no addresses available")); ++ if (state->lease_allocate && ia_invalid) ++ { ++ /* RFC 8415, Section 18.3.2: ++ If any of the prefixes of the included addresses are not ++ appropriate for the link to which the client is connected, ++ the server MUST return the IA to the client with a Status ++ Code option with the value NotOnLink. */ ++ put_opt6_short(DHCP6NOTONLINK); ++ errmsg = _("not on link"); ++ } ++ else ++ { ++ put_opt6_short(DHCP6NOADDRS); ++ errmsg = _("no addresses available"); ++ } ++ put_opt6_string(errmsg); + end_opt6(o1); + + /* Some clients will ask repeatedly when we're not giving +@@ -795,7 +812,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_RA_STATELESS)) + { +- log6_packet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", NULL, _("no addresses available")); ++ log6_packet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", NULL, errmsg); + break; + } + } +@@ -831,7 +848,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + /* If we get a request with an IA_*A without addresses, treat it exactly like + a SOLICT with rapid commit set. */ + save_counter(start); +- goto request_no_address; ++ goto request_no_address; + } + + o = build_ia(state, &t1cntr); +@@ -861,11 +878,11 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ + } + else if (!check_address(state, &req_addr)) + { +- /* Address leased to another DUID/IAID */ +- o1 = new_opt6(OPTION6_STATUS_CODE); +- put_opt6_short(DHCP6UNSPEC); +- put_opt6_string(_("address in use")); +- end_opt6(o1); ++ /* Address leased to another DUID/IAID. ++ Find another address for the client, treat it exactly like ++ a SOLICT with rapid commit set. */ ++ save_counter(start); ++ goto request_no_address; + } + else + { +-- +2.31.1 + diff --git a/dnsmasq-2.79-mixed-family-failed.patch b/dnsmasq-2.79-mixed-family-failed.patch new file mode 100644 index 0000000..c241d15 --- /dev/null +++ b/dnsmasq-2.79-mixed-family-failed.patch @@ -0,0 +1,79 @@ +From 4348c43be45d20aba87ee5564ecdde10aff7e5e7 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 22 Jan 2021 16:49:12 +0000 +Subject: [PATCH] Move fd into frec_src, fixes + 15b60ddf935a531269bb8c68198de012a4967156 + +If identical queries from IPv4 and IPv6 sources are combined by the +new code added in 15b60ddf935a531269bb8c68198de012a4967156 then replies +can end up being sent via the wrong family of socket. The ->fd +should be per query, not per-question. + +In bind-interfaces mode, this could also result in replies being sent +via the wrong socket even when IPv4/IPV6 issues are not in play. + +(cherry picked from commit 04490bf622ac84891aad6f2dd2edf83725decdee) + +Fix for 12af2b171de0d678d98583e2190789e544440e02 + +(cherry picked from commit 3f535da79e7a42104543ef5c7b5fa2bed819a78b) +--- + src/dnsmasq.h | 3 ++- + src/forward.c | 5 +++-- + 2 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index f3bbb4e..e7e1693 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -632,6 +632,7 @@ struct frec { + union mysockaddr source; + struct all_addr dest; + unsigned int iface, log_id; ++ int fd; + unsigned short orig_id; + struct frec_src *next; + } frec_src; +@@ -641,7 +642,7 @@ struct frec { + struct randfd *rfd6; + #endif + unsigned short new_id; +- int fd, forwardall, flags; ++ int forwardall, flags; + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +diff --git a/src/forward.c b/src/forward.c +index 9d249c0..82dd850 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -368,6 +368,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + new->dest = *dst_addr; + new->log_id = daemon->log_id; + new->iface = dst_iface; ++ new->fd = udpfd; + } + + return 1; +@@ -392,8 +393,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; + forward->frec_src.next = NULL; ++ forward->frec_src.fd = udpfd; + forward->new_id = get_id(); +- forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; + forward->flags = fwd_flags; +@@ -1175,7 +1176,7 @@ void reply_query(int fd, int family, time_t now) + { + header->id = htons(src->orig_id); + +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, ++ send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) +-- +2.26.2 + diff --git a/dnsmasq-2.79-rh1602477-2.patch b/dnsmasq-2.79-rh1602477-2.patch new file mode 100644 index 0000000..8421b76 --- /dev/null +++ b/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/dnsmasq-2.79-rh1602477.patch b/dnsmasq-2.79-rh1602477.patch new file mode 100644 index 0000000..c896db1 --- /dev/null +++ b/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/dnsmasq-2.79-rh1700916.patch b/dnsmasq-2.79-rh1700916.patch new file mode 100644 index 0000000..0511e24 --- /dev/null +++ b/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/dnsmasq-2.79-rh1728698-2.patch b/dnsmasq-2.79-rh1728698-2.patch new file mode 100644 index 0000000..9c335e5 --- /dev/null +++ b/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/dnsmasq-2.79-rh1728698-4.patch b/dnsmasq-2.79-rh1728698-4.patch new file mode 100644 index 0000000..86d7e02 --- /dev/null +++ b/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/dnsmasq-2.79-rh1746411.patch b/dnsmasq-2.79-rh1746411.patch new file mode 100644 index 0000000..74922c5 --- /dev/null +++ b/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/dnsmasq-2.79-rh1749092-fail.patch b/dnsmasq-2.79-rh1749092-fail.patch new file mode 100644 index 0000000..3550d61 --- /dev/null +++ b/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/dnsmasq-2.79-server-domain-rh1919894.patch b/dnsmasq-2.79-server-domain-rh1919894.patch new file mode 100644 index 0000000..3f101d0 --- /dev/null +++ b/dnsmasq-2.79-server-domain-rh1919894.patch @@ -0,0 +1,473 @@ +From b15c92e5d793c9767591dbf8910bf3466aba92ee Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Mon, 19 Apr 2021 13:56:23 +0200 +Subject: [PATCH] Use load-balancing also for --server=/domains/ + +Do not (yet) move servers to server_domain structure. Instead use +separate server_domains to store just last_server and requests count and +time. + +Introduces domain information duplicity, but minimizes required changes +to daemon->servers usage. + +Optimize server domain record + +Set pointer to domain record when struct server is created. When +searching for domain pointer, use this pointer to make it quick. +--- + src/dnsmasq.h | 18 +++++++-- + src/forward.c | 54 ++++++++++++++++----------- + src/network.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++---- + src/option.c | 5 +++ + 4 files changed, 147 insertions(+), 31 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 4beef35..27ff86a 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -531,6 +531,17 @@ struct randfd_list { + struct randfd_list *next; + }; + ++/* contains domain specific set of servers. ++ * If domain is NULL, just normal servers. */ ++struct server_domain { ++ char *domain; ++ struct server *last_server; ++ time_t forwardtime; ++ int forwardcount; ++ unsigned int flags; /* server.flags alternative */ ++ struct server_domain *next; ++}; ++ + struct server { + union mysockaddr addr, source_addr; + char interface[IF_NAMESIZE+1]; +@@ -543,6 +554,7 @@ struct server { + #ifdef HAVE_LOOP + u32 uid; + #endif ++ struct server_domain *serv_domain; + struct server *next; + }; + +@@ -995,6 +1007,7 @@ extern struct daemon { + struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces; + struct bogus_addr *bogus_addr, *ignore_addr; + struct server *servers; ++ struct server_domain *server_domains; + struct ipsets *ipsets; + int log_fac; /* log facility */ + char *log_file; /* optional log file */ +@@ -1061,9 +1074,6 @@ extern struct daemon { + struct serverfd *sfds; + struct irec *interfaces; + struct listener *listeners; +- struct server *last_server; +- time_t forwardtime; +- int forwardcount; + struct server *srv_save; /* Used for resend on DoD */ + size_t packet_len; /* " " */ + int fd_save; /* " " */ +@@ -1319,6 +1329,8 @@ int loopback_exception(int fd, int family, struct all_addr *addr, char *name); + int label_exception(int index, int family, struct all_addr *addr); + int fix_fd(int fd); + int tcp_interface(int fd, int af); ++struct server_domain *server_domain_find_domain(const char *domain); ++struct server_domain *server_domain_new(struct server *serv); + #ifdef HAVE_IPV6 + int set_ipv6pktinfo(int fd); + #endif +diff --git a/src/forward.c b/src/forward.c +index 11e0310..d8e845a 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -109,7 +109,8 @@ int send_from(int fd, int nowild, char *packet, size_t len, + } + + static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigned int qtype, +- char *qdomain, int *type, char **domain, int *norebind) ++ char *qdomain, int *type, char **domain, int *norebind, ++ struct server_domain **serv_domain) + + { + /* If the query ends in the domain in one of our servers, set +@@ -121,6 +122,9 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne + struct server *serv; + unsigned int flags = 0; + ++ if (serv_domain) ++ *serv_domain = NULL; ++ + for (serv = daemon->servers; serv; serv=serv->next) + if (qtype == F_DNSSECOK && !(serv->flags & SERV_DO_DNSSEC)) + continue; +@@ -181,6 +185,8 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne + { + *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_DO_DNSSEC); + *domain = serv->domain; ++ if (serv_domain) ++ *serv_domain = serv->serv_domain; + matchlen = domainlen; + if (serv->flags & SERV_NO_ADDR) + flags = F_NXDOMAIN; +@@ -228,6 +234,8 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, unsigne + *type = 0; /* use normal servers for this domain */ + *domain = NULL; + } ++ if (serv_domain && !*serv_domain) ++ *serv_domain = server_domain_find_domain(*domain); + return flags; + } + +@@ -242,6 +250,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + unsigned int flags = 0; + unsigned int fwd_flags = 0; + struct server *start = NULL; ++ struct server_domain *sd = NULL; + void *hash = hash_questions(header, plen, daemon->namebuff); + #ifdef HAVE_DNSSEC + int do_dnssec = 0; +@@ -313,8 +322,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + forward->sentto->failed_queries++; + if (!option_bool(OPT_ORDER)) + { ++ sd = forward->sentto->serv_domain; + forward->forwardall = 1; +- daemon->last_server = NULL; ++ if (sd) ++ sd->last_server = NULL; + } + type = forward->sentto->flags & SERV_TYPE; + #ifdef HAVE_DNSSEC +@@ -363,10 +374,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + + return 1; + } +- ++ + if (gotname) +- flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); +- ++ flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &sd); ++ + #ifdef HAVE_DNSSEC + do_dnssec = type & SERV_DO_DNSSEC; + #endif +@@ -407,18 +418,18 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + always try all the available servers, + otherwise, use the one last known to work. */ + +- if (type == 0) ++ if (sd) + { + if (option_bool(OPT_ORDER)) + start = daemon->servers; +- else if (!(start = daemon->last_server) || +- daemon->forwardcount++ > FORWARD_TEST || +- difftime(now, daemon->forwardtime) > FORWARD_TIME) ++ else if (!(start = sd->last_server) || ++ sd->forwardcount++ > FORWARD_TEST || ++ difftime(now, sd->forwardtime) > FORWARD_TIME) + { + start = daemon->servers; + forward->forwardall = 1; +- daemon->forwardcount = 0; +- daemon->forwardtime = now; ++ sd->forwardcount = 0; ++ sd->forwardtime = now; + } + } + else +@@ -758,6 +769,7 @@ void reply_query(int fd, time_t now) + size_t nn; + struct server *server; + void *hash; ++ struct server_domain *sd; + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +@@ -845,7 +857,8 @@ void reply_query(int fd, time_t now) + } + + server = forward->sentto; +- if ((forward->sentto->flags & SERV_TYPE) == 0) ++ sd = server->serv_domain; ++ if (sd) + { + if (RCODE(header) == REFUSED) + server = NULL; +@@ -863,7 +876,7 @@ void reply_query(int fd, time_t now) + } + } + if (!option_bool(OPT_ALL_SERVERS)) +- daemon->last_server = server; ++ sd->last_server = server; + } + + /* We tried resending to this server with a smaller maximum size and got an answer. +@@ -964,7 +977,7 @@ void reply_query(int fd, time_t now) + /* Find server to forward to. This will normally be the + same as for the original query, but may be another if + servers for domains are involved. */ +- if (search_servers(now, NULL, F_DNSSECOK, daemon->keyname, &type, &domain, NULL) == 0) ++ if (search_servers(now, NULL, F_DNSSECOK, daemon->keyname, &type, &domain, NULL, &sd) == 0) + { + struct server *start = server, *new_server = NULL; + +@@ -1541,7 +1554,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si + /* Find server to forward to. This will normally be the + same as for the original query, but may be another if + servers for domains are involved. */ +- if (search_servers(now, NULL, F_DNSSECOK, keyname, &type, &domain, NULL) != 0) ++ if (search_servers(now, NULL, F_DNSSECOK, keyname, &type, &domain, NULL, NULL) != 0) + { + new_status = STAT_ABANDONED; + break; +@@ -1814,11 +1827,12 @@ unsigned char *tcp_request(int confd, time_t now, + int type = SERV_DO_DNSSEC; + char *domain = NULL; + unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); ++ struct server_domain *sd = NULL; + + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); + + if (gotname) +- flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); ++ flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &sd); + + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (type & SERV_DO_DNSSEC)) +@@ -1839,10 +1853,10 @@ unsigned char *tcp_request(int confd, time_t now, + + type &= ~SERV_DO_DNSSEC; + +- if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) ++ if (!sd || option_bool(OPT_ORDER) || !sd->last_server) + last_server = daemon->servers; + else +- last_server = daemon->last_server; ++ last_server = sd->last_server; + + if (!flags && last_server) + { +@@ -2439,9 +2453,7 @@ void server_gone(struct server *server) + if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) + daemon->randomsocks[i].serv = NULL; + +- if (daemon->last_server == server) +- daemon->last_server = NULL; +- ++ /* last_server cleared by server_domain_cleanup */ + if (daemon->srv_save == server) + daemon->srv_save = NULL; + } +diff --git a/src/network.c b/src/network.c +index 4eda1fd..4d140bb 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -1428,6 +1428,29 @@ void cleanup_servers(void) + #endif + } + ++void server_domains_cleanup(void) ++{ ++ struct server_domain *sd, *tmp, **up; ++ ++ /* unlink and free anything still marked. */ ++ for (up = &daemon->server_domains, sd=*up; sd; sd = tmp) ++ { ++ tmp = sd->next; ++ if (sd->flags & SERV_MARK) ++ { ++ *up = sd->next; ++ if (sd->domain) ++ free(sd->domain); ++ free(sd); ++ } ++ else { ++ up = &sd->next; ++ if (sd->last_server && (sd->last_server->flags & SERV_MARK)) ++ sd->last_server = NULL; ++ } ++ } ++} ++ + void add_update_server(int flags, + union mysockaddr *addr, + union mysockaddr *source_addr, +@@ -1507,10 +1530,72 @@ void add_update_server(int flags, + } + } + ++static const char *server_get_domain(const struct server *serv) ++{ ++ const char *domain = serv->domain; ++ ++ if (serv->flags & SERV_HAS_DOMAIN) ++ /* .example.com is valid */ ++ while (*domain == '.') ++ domain++; ++ ++ return domain; ++} ++ ++struct server_domain *server_domain_find_domain(const char *domain) ++{ ++ struct server_domain *sd; ++ for (sd = daemon->server_domains; sd; sd = sd->next) ++ if ((!domain && sd->domain == domain) || (domain && sd->domain && hostname_isequal(domain, sd->domain))) ++ return sd; ++ return NULL; ++} ++ ++/**< Test structure has already set domain pointer. ++ * ++ * If not, create a new record. */ ++struct server_domain *server_domain_new(struct server *serv) ++{ ++ struct server_domain *sd; ++ ++ if ((sd = whine_malloc(sizeof(struct server_domain)))) ++ { ++ const char *domain = server_get_domain(serv); ++ ++ /* Ensure all serv->domain values have own record in server_domain. ++ * Add a new record. */ ++ if (domain) ++ { ++ size_t len = strlen(domain)+1; ++ sd->domain = whine_malloc(len); ++ if (sd->domain) ++ memcpy(sd->domain, domain, len); ++ } ++ sd->next = daemon->server_domains; ++ serv->serv_domain = sd; ++ daemon->server_domains = sd; ++ } ++ return sd; ++} ++ ++/**< Test structure has already set domain pointer. ++ * ++ * If not, create a new record. */ ++static void server_domain_check(struct server *serv) ++{ ++ struct server_domain *sd = serv->serv_domain; ++ ++ if (sd) ++ sd->flags &= (~SERV_MARK); /* found domain, mark active */ ++ else ++ server_domain_new(serv); ++} ++ + void check_servers(void) + { + struct irec *iface; + struct server *serv; ++ struct server_domain *sd; + struct serverfd *sfd, *tmp, **up; + int port = 0, count; + int locals = 0; +@@ -1522,10 +1607,14 @@ void check_servers(void) + for (sfd = daemon->sfds; sfd; sfd = sfd->next) + sfd->used = 0; + ++ for (sd = daemon->server_domains; sd; sd = sd->next) ++ sd->flags |= SERV_MARK; ++ + for (count = 0, serv = daemon->servers; serv; serv = serv->next) + { + if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) + { ++ + /* Init edns_pktsz for newly created server records. */ + if (serv->edns_pktsz == 0) + serv->edns_pktsz = daemon->edns_pktsz; +@@ -1541,12 +1630,8 @@ void check_servers(void) + if (serv->flags & SERV_HAS_DOMAIN) + { + struct ds_config *ds; +- char *domain = serv->domain; +- +- /* .example.com is valid */ +- while (*domain == '.') +- domain++; +- ++ const char *domain = server_get_domain(serv); ++ + for (ds = daemon->ds; ds; ds = ds->next) + if (ds->name[0] != 0 && hostname_isequal(domain, ds->name)) + break; +@@ -1556,7 +1641,6 @@ void check_servers(void) + } + } + #endif +- + port = prettyprint_addr(&serv->addr, daemon->namebuff); + + /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ +@@ -1591,6 +1675,8 @@ void check_servers(void) + + if (serv->sfd) + serv->sfd->used = 1; ++ ++ server_domain_check(serv); + } + + if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS)) +@@ -1653,6 +1739,7 @@ void check_servers(void) + up = &sfd->next; + } + ++ server_domains_cleanup(); + cleanup_servers(); + } + +diff --git a/src/option.c b/src/option.c +index abc5a48..6fa7bbd 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -906,6 +906,7 @@ static struct server *add_rev4(struct in_addr addr, int msize) + p += sprintf(p, "in-addr.arpa"); + + serv->flags = SERV_HAS_DOMAIN; ++ server_domain_new(serv); + serv->next = daemon->servers; + daemon->servers = serv; + +@@ -930,6 +931,7 @@ static struct server *add_rev6(struct in6_addr *addr, int msize) + p += sprintf(p, "ip6.arpa"); + + serv->flags = SERV_HAS_DOMAIN; ++ server_domain_new(serv); + serv->next = daemon->servers; + daemon->servers = serv; + +@@ -2231,6 +2233,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + memset(serv, 0, sizeof(struct server)); + serv->domain = d; + serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; ++ server_domain_new(serv); + serv->next = daemon->servers; + daemon->servers = serv; + } +@@ -2275,6 +2278,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + memset(serv, 0, sizeof(struct server)); + serv->domain = d; + serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; ++ server_domain_new(serv); + serv->next = daemon->servers; + daemon->servers = serv; + } +@@ -2525,6 +2529,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma + newlist = serv; + serv->domain = domain; + serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS; ++ server_domain_new(serv); + arg = end; + if (rebind) + break; +-- +2.34.1 + diff --git a/dnsmasq-2.80-dnssec.patch b/dnsmasq-2.80-dnssec.patch new file mode 100644 index 0000000..a34f46e --- /dev/null +++ b/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/dnsmasq-2.80-man-nameing.patch b/dnsmasq-2.80-man-nameing.patch new file mode 100644 index 0000000..3318a4a --- /dev/null +++ b/dnsmasq-2.80-man-nameing.patch @@ -0,0 +1,25 @@ +From 03212e533b1e07aba30d2f4112009dc3af867ea5 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Tue, 4 Sep 2018 17:52:28 +0100 +Subject: [PATCH] Manpage typo. + +--- + man/dnsmasq.8 | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index ebfadba..a62860e 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -27,7 +27,7 @@ TFTP server to allow net/PXE boot of DHCP hosts and also supports BOOTP. The PXE + .PP + The dnsmasq DHCPv6 server provides the same set of features as the + DHCPv4 server, and in addition, it includes router advertisements and +-a neat feature which allows nameing for clients which use DHCPv4 and ++a neat feature which allows naming for clients which use DHCPv4 and + stateless autoconfiguration only for IPv6 configuration. There is support for doing address allocation (both DHCPv6 and RA) from subnets which are dynamically delegated via DHCPv6 prefix delegation. + .PP + Dnsmasq is coded with small embedded systems in mind. It aims for the smallest possible memory footprint compatible with the supported functions, and allows unneeded functions to be omitted from the compiled binary. +-- +2.31.1 + diff --git a/dnsmasq-2.80-rh1795370.patch b/dnsmasq-2.80-rh1795370.patch new file mode 100644 index 0000000..220a6af --- /dev/null +++ b/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/dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch b/dnsmasq-2.80-unaligned-addresses-in-DHCPv6-packet.patch new file mode 100644 index 0000000..bc55656 --- /dev/null +++ b/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/dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch b/dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch new file mode 100644 index 0000000..fad386f --- /dev/null +++ b/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/dnsmasq-2.81-netlink-table.patch b/dnsmasq-2.81-netlink-table.patch new file mode 100644 index 0000000..05d22cf --- /dev/null +++ b/dnsmasq-2.81-netlink-table.patch @@ -0,0 +1,45 @@ +From 595b2e2e87f152c4ade7e2d66cb78915096f60c2 Mon Sep 17 00:00:00 2001 +From: Donald Sharp +Date: Mon, 2 Mar 2020 11:23:36 -0500 +Subject: [PATCH] Ignore routes in non-main tables + +Route lookup in Linux is bounded by `ip rules` as well +as the contents of specific routing tables. With the +advent of vrf's(l3mdev's) non-default tables are regularly being +used for routing purposes. + +dnsmasq listens to all route changes on the box and responds +to each one with an event. This is *expensive* when a full +BGP routing table is placed into the linux kernel, moreso +when dnsmasq is responding to events in tables that it will +never actually need to respond to, since dnsmasq at this +point in time has no concept of vrf's and would need +to be programmed to understand them. Help alleviate this load +by reducing the set of data that dnsmasq pays attention to +when we know there are events that are not useful at this +point in time. + +Signed-off-by: Donald Sharp +(cherry picked from commit b2ed691eb3ca6488a8878f5f3dd950a07b14a9db) +--- + src/netlink.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 8cd51af..0a3da3e 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -363,7 +363,9 @@ static void nl_async(struct nlmsghdr *h) + failing. */ + struct rtmsg *rtm = NLMSG_DATA(h); + +- if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK) ++ if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && ++ (rtm->rtm_table == RT_TABLE_MAIN || ++ rtm->rtm_table == RT_TABLE_LOCAL)) + queue_event(EVENT_NEWROUTE); + } + else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) +-- +2.26.2 + diff --git a/dnsmasq-2.81-optimize-fds-close.patch b/dnsmasq-2.81-optimize-fds-close.patch new file mode 100644 index 0000000..58689da --- /dev/null +++ b/dnsmasq-2.81-optimize-fds-close.patch @@ -0,0 +1,132 @@ +commit 98c6998116e33f9f34b798682e0695f4166bd86d +Author: Simon Kelley +Date: Mon Mar 2 17:10:25 2020 +0000 + + Optimise closing file descriptors. + + Dnsmasq needs to close all the file descriptors it inherits, for security + reasons. This is traditionally done by calling close() on every possible + file descriptor (most of which won't be open.) On big servers where + "every possible file descriptor" is a rather large set, this gets + rather slow, so we use the /proc//fd directory to get a list + of the fds which are acually open. + + This only works on Linux. On other platforms, and on Linux systems + without a /proc filesystem, we fall back to the old way. + +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index 573aac0..10f19ea 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -138,20 +138,18 @@ int main (int argc, char **argv) + } + #endif + +- /* Close any file descriptors we inherited apart from std{in|out|err} +- +- Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist, ++ /* Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist, + otherwise file descriptors we create can end up being 0, 1, or 2 + and then get accidentally closed later when we make 0, 1, and 2 + open to /dev/null. Normally we'll be started with 0, 1 and 2 open, + but it's not guaranteed. By opening /dev/null three times, we + ensure that we're not using those fds for real stuff. */ +- for (i = 0; i < max_fd; i++) +- if (i != STDOUT_FILENO && i != STDERR_FILENO && i != STDIN_FILENO) +- close(i); +- else +- open("/dev/null", O_RDWR); +- ++ for (i = 0; i < 3; i++) ++ open("/dev/null", O_RDWR); ++ ++ /* Close any file descriptors we inherited apart from std{in|out|err} */ ++ close_fds(max_fd, -1, -1, -1); ++ + #ifndef HAVE_LINUX_NETWORK + # if !(defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR)) + if (!option_bool(OPT_NOWILD)) +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 6103eb5..c46bfeb 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -1283,7 +1283,7 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len, + int expand_buf(struct iovec *iov, size_t size); + char *print_mac(char *buff, unsigned char *mac, int len); + int read_write(int fd, unsigned char *packet, int size, int rw); +- ++void close_fds(long max_fd, int spare1, int spare2, int spare3); + int wildcard_match(const char* wildcard, const char* match); + int wildcard_matchn(const char* wildcard, const char* match, int num); + +diff --git a/src/helper.c b/src/helper.c +index 1b260a1..7072cf4 100644 +--- a/src/helper.c ++++ b/src/helper.c +@@ -131,12 +131,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) + Don't close err_fd, in case the lua-init fails. + Note that we have to do this before lua init + so we don't close any lua fds. */ +- for (max_fd--; max_fd >= 0; max_fd--) +- if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && +- max_fd != STDIN_FILENO && max_fd != pipefd[0] && +- max_fd != event_fd && max_fd != err_fd) +- close(max_fd); +- ++ close_fds(max_fd, pipefd[0], event_fd, err_fd); ++ + #ifdef HAVE_LUASCRIPT + if (daemon->luascript) + { +diff --git a/src/util.c b/src/util.c +index 73bf62a..f058c92 100644 +--- a/src/util.c ++++ b/src/util.c +@@ -705,6 +705,47 @@ int read_write(int fd, unsigned char *packet, int size, int rw) + return 1; + } + ++/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */ ++void close_fds(long max_fd, int spare1, int spare2, int spare3) ++{ ++ /* On Linux, use the /proc/ filesystem to find which files ++ are actually open, rather than iterate over the whole space, ++ for efficiency reasons. If this fails we drop back to the dumb code. */ ++#ifdef HAVE_LINUX_NETWORK ++ DIR *d; ++ ++ if ((d = opendir("/proc/self/fd"))) ++ { ++ struct dirent *de; ++ ++ while ((de = readdir(d))) ++ { ++ long fd; ++ char *e = NULL; ++ ++ errno = 0; ++ fd = strtol(de->d_name, &e, 10); ++ ++ if (errno != 0 || !e || *e || fd == dirfd(d) || ++ fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO || ++ fd == spare1 || fd == spare2 || fd == spare3) ++ continue; ++ ++ close(fd); ++ } ++ ++ closedir(d); ++ return; ++ } ++#endif ++ ++ /* fallback, dumb code. */ ++ for (max_fd--; max_fd >= 0; max_fd--) ++ if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO && ++ max_fd != spare1 && max_fd != spare2 && max_fd != spare3) ++ close(max_fd); ++} ++ + /* Basically match a string value against a wildcard pattern. */ + int wildcard_match(const char* wildcard, const char* match) + { diff --git a/dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch b/dnsmasq-2.81-prefix-ranges-or-list-of-ipv6-addresses.patch new file mode 100644 index 0000000..cf4833f --- /dev/null +++ b/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/dnsmasq-2.81-rh1829448.patch b/dnsmasq-2.81-rh1829448.patch new file mode 100644 index 0000000..f31b230 --- /dev/null +++ b/dnsmasq-2.81-rh1829448.patch @@ -0,0 +1,62 @@ +From 3d113137fd64cd0723cbecab6a36a75d3ecfb0a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Harald=20Jens=C3=A5s?= +Date: Thu, 7 May 2020 00:33:54 +0200 +Subject: [PATCH 1/1] Fix regression in s_config_in_context() method + +Prior to commit 137286e9baecf6a3ba97722ef1b49c851b531810 +a config would not be considered in context if: +a) it has no address family flags set +b) it has the address family flag of current context set + +Since above commit config is considered in context if the +address family is the opposite of current context. + +The result is that a config with two dhcp-host records, +one for IPv6 and another for IPv4 no longer works, for +example with the below config the config with the IPv6 +address would be considered in context for a DHCP(v4) +request. + dhcp-host=52:54:00:bc:c3:fd,172.20.0.11,host2 + dhcp-host=52:54:00:bc:c3:fd,[fd12:3456:789a:1::aadd],host2 + +This commit restores the previous behavior. +--- + src/dhcp-common.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/src/dhcp-common.c b/src/dhcp-common.c +index eae9886..ffc78ca 100644 +--- a/src/dhcp-common.c ++++ b/src/dhcp-common.c +@@ -280,14 +280,18 @@ 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; +- ++ ++ /* No address present in config == in context */ ++ if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) ++ return 1; ++ + #ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_V6) + { + struct addrlist *addr_list; + + if (!(config->flags & CONFIG_ADDR6)) +- return 1; ++ return 0; + + for (; context; context = context->current) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) +@@ -303,7 +307,7 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config + #endif + { + if (!(config->flags & CONFIG_ADDR)) +- return 1; ++ return 0; + + for (; context; context = context->current) + if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) +-- +2.25.4 diff --git a/dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch b/dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch new file mode 100644 index 0000000..65592a0 --- /dev/null +++ b/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/dnsmasq-2.84-bind-dynamic-netlink.patch b/dnsmasq-2.84-bind-dynamic-netlink.patch new file mode 100644 index 0000000..f325e6d --- /dev/null +++ b/dnsmasq-2.84-bind-dynamic-netlink.patch @@ -0,0 +1,237 @@ +From 5010c42c47b7b5a3d68d83369d6c17ed0bc11cff Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Wed, 17 Feb 2021 11:47:28 +0100 +Subject: [PATCH] Correct occasional --bind-dynamic synchronization break + +Request only one re-read of addresses and/or routes + +Previous implementation re-reads systemd addresses exactly the same +number of time equal number of notifications received. +This is not necessary, we need just notification of change, then re-read +the current state and adapt listeners. Repeated re-reading slows netlink +processing and highers CPU usage on mass interface changes. + +Continue reading multicast events from netlink, even when ENOBUFS +arrive. Broadcasts are not trusted anyway and refresh would be done in +iface_enumerate. Save queued events sent again. + +Remove sleeping on netlink ENOBUFS + +With reduced number of written events netlink should receive ENOBUFS +rarely. It does not make sense to wait if it is received. It is just a +signal some packets got missing. Fast reading all pending packets is required, +seq checking ensures it already. Finishes changes by +commit 1d07667ac77c55b9de56b1b2c385167e0e0ec27a. + +Move restart from iface_enumerate to enumerate_interfaces + +When ENOBUFS is received, restart of reading addresses is done. But +previously found addresses might not have been found this time. In order +to catch this, restart both IPv4 and IPv6 enumeration with clearing +found interfaces first. It should deliver up-to-date state also after +ENOBUFS. + +Read all netlink messages before netlink restart + +Before writing again into netlink socket, try fetching all pending +messages. They would be ignored, only might trigger new address +synchronization. Should ensure new try has better chance to succeed. + +Request sending ENOBUFS again + +ENOBUFS error handling was improved. Netlink is correctly drained before +sending a new request again. It seems ENOBUFS supression is no longer +necessary or wanted. Let kernel tell us when it failed and handle it a +good way. +--- + src/netlink.c | 67 ++++++++++++++++++++++++++++++++++++--------------- + src/network.c | 11 +++++++-- + 2 files changed, 57 insertions(+), 21 deletions(-) + +diff --git a/src/netlink.c b/src/netlink.c +index ac1a1c5..f95f3e8 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -32,13 +32,21 @@ + + #ifndef NDA_RTA + # define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +-#endif ++#endif ++ ++/* Used to request refresh of addresses or routes just once, ++ * when multiple changes might be announced. */ ++enum async_states { ++ STATE_NEWADDR = (1 << 0), ++ STATE_NEWROUTE = (1 << 1), ++}; + + + static struct iovec iov; + static u32 netlink_pid; + +-static void nl_async(struct nlmsghdr *h); ++static unsigned nl_async(struct nlmsghdr *h, unsigned state); ++static void nl_multicast_state(unsigned state); + + void netlink_init(void) + { +@@ -135,7 +143,9 @@ static ssize_t netlink_recv(void) + + + /* family = AF_UNSPEC finds ARP table entries. +- family = AF_LOCAL finds MAC addresses. */ ++ family = AF_LOCAL finds MAC addresses. ++ returns 0 on failure, 1 on success, -1 when restart is required ++*/ + int iface_enumerate(int family, void *parm, int (*callback)()) + { + struct sockaddr_nl addr; +@@ -143,6 +153,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + ssize_t len; + static unsigned int seq = 0; + int callback_ok = 1; ++ unsigned state = 0; + + struct { + struct nlmsghdr nlh; +@@ -154,7 +165,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + addr.nl_groups = 0; + addr.nl_pid = 0; /* address to kernel */ + +- again: + if (family == AF_UNSPEC) + req.nlh.nlmsg_type = RTM_GETNEIGH; + else if (family == AF_LOCAL) +@@ -181,8 +191,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + { + if (errno == ENOBUFS) + { +- sleep(1); +- goto again; ++ nl_multicast_state(state); ++ return -1; + } + return 0; + } +@@ -191,7 +201,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) + { + /* May be multicast arriving async */ +- nl_async(h); ++ state = nl_async(h, state); + } + else if (h->nlmsg_seq != seq) + { +@@ -327,26 +337,36 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + } + } + +-void netlink_multicast(void) ++static void nl_multicast_state(unsigned state) + { + ssize_t len; + struct nlmsghdr *h; + int flags; +- +- /* don't risk blocking reading netlink messages here. */ ++ + if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || + fcntl(daemon->netlinkfd, F_SETFL, flags | O_NONBLOCK) == -1) + return; ++ ++ do { ++ /* don't risk blocking reading netlink messages here. */ ++ while ((len = netlink_recv()) != -1) + +- if ((len = netlink_recv()) != -1) +- for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) +- nl_async(h); +- ++ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) ++ state = nl_async(h, state); ++ } while (errno == ENOBUFS); ++ + /* restore non-blocking status */ + fcntl(daemon->netlinkfd, F_SETFL, flags); + } + +-static void nl_async(struct nlmsghdr *h) ++void netlink_multicast(void) ++{ ++ unsigned state = 0; ++ nl_multicast_state(state); ++} ++ ++ ++static unsigned nl_async(struct nlmsghdr *h, unsigned state) + { + if (h->nlmsg_type == NLMSG_ERROR) + { +@@ -354,7 +374,8 @@ static void nl_async(struct nlmsghdr *h) + if (err->error != 0) + my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); + } +- else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) ++ else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE && ++ (state & STATE_NEWROUTE)==0) + { + /* We arrange to receive netlink multicast messages whenever the network route is added. + If this happens and we still have a DNS packet in the buffer, we re-send it. +@@ -366,10 +387,18 @@ static void nl_async(struct nlmsghdr *h) + if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && + (rtm->rtm_table == RT_TABLE_MAIN || + rtm->rtm_table == RT_TABLE_LOCAL)) +- queue_event(EVENT_NEWROUTE); ++ { ++ queue_event(EVENT_NEWROUTE); ++ state |= STATE_NEWROUTE; ++ } ++ } ++ else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) && ++ (state & STATE_NEWADDR)==0) ++ { ++ queue_event(EVENT_NEWADDR); ++ state |= STATE_NEWADDR; + } +- else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) +- queue_event(EVENT_NEWADDR); ++ return state; + } + #endif + +diff --git a/src/network.c b/src/network.c +index c6e7d89..47caf38 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -656,7 +656,8 @@ int enumerate_interfaces(int reset) + + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; +- ++ ++again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) + iface->found = 0; +@@ -709,10 +710,16 @@ int enumerate_interfaces(int reset) + + #ifdef HAVE_IPV6 + ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); ++ if (ret < 0) ++ goto again; + #endif + + if (ret) +- ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ { ++ ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ if (ret < 0) ++ goto again; ++ } + + errsave = errno; + close(param.fd); +-- +2.26.2 + diff --git a/dnsmasq-2.85-CVE-2021-3448.patch b/dnsmasq-2.85-CVE-2021-3448.patch new file mode 100644 index 0000000..a95c6df --- /dev/null +++ b/dnsmasq-2.85-CVE-2021-3448.patch @@ -0,0 +1,1056 @@ +From d88dc5e696f1b8b95e416890ac831eb0c26250ff Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Mon, 15 Mar 2021 21:59:51 +0000 +Subject: [PATCH] Use random source ports where possible if source + addresses/interfaces in use. + +CVE-2021-3448 applies. + +It's possible to specify the source address or interface to be +used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4 +or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of +these have, until now, used a single socket, bound to a fixed +port. This was originally done to allow an error (non-existent +interface, or non-local address) to be detected at start-up. This +means that any upstream servers specified in such a way don't use +random source ports, and are more susceptible to cache-poisoning +attacks. + +We now use random ports where possible, even when the +source is specified, so server=8.8.8.8@1.2.3.4 or +server=8.8.8.8@eth0 will use random source +ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will +use the explicitly configured port, and should only be done with +understanding of the security implications. +Note that this change changes non-existing interface, or non-local +source address errors from fatal to run-time. The error will be +logged and communiction with the server not possible. +--- + man/dnsmasq.8 | 4 +- + src/dnsmasq.c | 31 +++-- + src/dnsmasq.h | 28 ++-- + src/forward.c | 373 +++++++++++++++++++++++++++++++------------------- + src/loop.c | 20 +-- + src/network.c | 100 ++++---------- + src/option.c | 3 +- + src/tftp.c | 6 +- + src/util.c | 2 +- + 9 files changed, 310 insertions(+), 257 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index 45d2273..7f4c62e 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -419,7 +419,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots + or domain parts, to upstream nameservers. If the name is not known + from /etc/hosts or DHCP then a "not found" answer is returned. + .TP +-.B \-S, --local, --server=[/[]/[domain/]][[#][@|[#]] ++.B \-S, --local, --server=[/[]/[domain/]][[#]][@][@[#]] + Specify IP address of upstream servers directly. Setting this flag does + not suppress reading of /etc/resolv.conf, use -R to do that. If one or + more +@@ -481,7 +481,7 @@ source address specified but the port may be specified directly as + part of the source address. Forcing queries to an interface is not + implemented on all platforms supported by dnsmasq. + .TP +-.B --rev-server=/,[#][@|[#]] ++.B --rev-server=/,[#][@|@[#]] + This is functionally the same as + .B --server, + but provides some syntactic sugar to make specifying address-to-name queries easier. For example +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index b7f0a29..3a1f65e 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -1538,6 +1538,7 @@ static int set_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int wait = 0, i; + + #ifdef HAVE_TFTP +@@ -1557,11 +1558,14 @@ static int set_dns_listeners(time_t now) + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + poll_listen(serverfdp->fd, POLLIN); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0) +- poll_listen(daemon->randomsocks[i].fd, POLLIN); +- ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0) ++ poll_listen(daemon->randomsocks[i].fd, POLLIN); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ poll_listen(rfl->rfd->fd, POLLIN); ++ + for (listener = daemon->listeners; listener; listener = listener->next) + { + /* only listen for queries if we have resources */ +@@ -1592,17 +1596,22 @@ static void check_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int i; + + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + if (poll_check(serverfdp->fd, POLLIN)) +- reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); ++ reply_query(serverfdp->fd, now); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0 && +- poll_check(daemon->randomsocks[i].fd, POLLIN)) +- reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && ++ poll_check(daemon->randomsocks[i].fd, POLLIN)) ++ reply_query(daemon->randomsocks[i].fd, now); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ if (poll_check(rfl->rfd->fd, POLLIN)) ++ reply_query(rfl->rfd->fd, now); + + for (listener = daemon->listeners; listener; listener = listener->next) + { +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 221f788..4beef35 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -521,13 +521,20 @@ struct serverfd { + }; + + struct randfd { ++ struct server *serv; + int fd; +- unsigned short refcount, family; ++ unsigned short refcount; /* refcount == 0xffff means overflow record. */ + }; +- ++ ++struct randfd_list { ++ struct randfd *rfd; ++ struct randfd_list *next; ++}; ++ + struct server { + union mysockaddr addr, source_addr; + char interface[IF_NAMESIZE+1]; ++ unsigned int ifindex; /* corresponding to interface, above */ + struct serverfd *sfd; + char *domain; /* set if this server only handles a domain. */ + int flags, tcpfd, edns_pktsz; +@@ -640,10 +647,7 @@ struct frec { + struct frec_src *next; + } frec_src; + struct server *sentto; /* NULL means free */ +- struct randfd *rfd4; +-#ifdef HAVE_IPV6 +- struct randfd *rfd6; +-#endif ++ struct randfd_list *rfds; + unsigned short new_id; + int forwardall, flags; + time_t time; +@@ -1062,9 +1066,10 @@ extern struct daemon { + int forwardcount; + struct server *srv_save; /* Used for resend on DoD */ + size_t packet_len; /* " " */ +- struct randfd *rfd_save; /* " " */ ++ int fd_save; /* " " */ + pid_t tcp_pids[MAX_PROCS]; + struct randfd randomsocks[RANDOM_SOCKS]; ++ struct randfd_list *rfl_spare, *rfl_poll; + int v6pktinfo; + struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ + int log_id, log_display_id; /* ids of transactions for logging */ +@@ -1227,7 +1232,7 @@ void safe_strncpy(char *dest, const char *src, size_t size); + void safe_pipe(int *fd, int read_noblock); + void *whine_malloc(size_t size); + int sa_len(union mysockaddr *addr); +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); + int hostname_isequal(const char *a, const char *b); + time_t dnsmasq_time(void); + int netmask_length(struct in_addr mask); +@@ -1276,7 +1281,7 @@ char *parse_server(char *arg, union mysockaddr *addr, + int option_read_dynfile(char *file, int flags); + + /* forward.c */ +-void reply_query(int fd, int family, time_t now); ++void reply_query(int fd, time_t now); + void receive_query(struct listener *listen, time_t now); + unsigned char *tcp_request(int confd, time_t now, + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); +@@ -1286,13 +1291,12 @@ int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface); + void resend_query(void); +-struct randfd *allocate_rfd(int family); +-void free_rfd(struct randfd *rfd); ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv); ++void free_rfds(struct randfd_list **fdlp); + + /* network.c */ + int indextoname(int fd, int index, char *name); + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); +-int random_sock(int family); + void pre_allocate_sfds(void); + int reload_servers(char *fname); + void mark_servers(int flag); +diff --git a/src/forward.c b/src/forward.c +index 82dd850..11e0310 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -16,7 +16,7 @@ + + #include "dnsmasq.h" + +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash); + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); +@@ -291,29 +291,19 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(SAFE_PKTSZ, pheader); + +- if (forward->sentto->addr.sa.sa_family == AF_INET) +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); +-#ifdef HAVE_IPV6 +- else +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); +-#endif +- +- if (forward->sentto->sfd) +- fd = forward->sentto->sfd->fd; +- else ++ if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) + { ++ if (forward->sentto->addr.sa.sa_family == AF_INET) ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); + #ifdef HAVE_IPV6 +- if (forward->sentto->addr.sa.sa_family == AF_INET6) +- fd = forward->rfd6->fd; + else ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); + #endif +- fd = forward->rfd4->fd; ++ while (retry_send(sendto(fd, (char *)header, plen, 0, ++ &forward->sentto->addr.sa, ++ sa_len(&forward->sentto->addr)))); + } +- +- while (retry_send( sendto(fd, (char *)header, plen, 0, +- &forward->sentto->addr.sa, +- sa_len(&forward->sentto->addr)))); +- ++ + return 1; + } + #endif +@@ -490,50 +480,26 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + + while (1) + { ++ int fd; ++ + /* only send to servers dealing with our domain. + domain may be NULL, in which case server->domain + must be NULL also. */ + + if (type == (start->flags & SERV_TYPE) && + (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && +- !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) ++ !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ ((fd = allocate_rfd(&forward->rfds, start)) != -1)) + { +- int fd; +- +- /* find server socket to use, may need to get random one. */ +- if (start->sfd) +- fd = start->sfd->fd; +- else +- { +-#ifdef HAVE_IPV6 +- if (start->addr.sa.sa_family == AF_INET6) +- { +- if (!forward->rfd6 && +- !(forward->rfd6 = allocate_rfd(AF_INET6))) +- break; +- daemon->rfd_save = forward->rfd6; +- fd = forward->rfd6->fd; +- } +- else +-#endif +- { +- if (!forward->rfd4 && +- !(forward->rfd4 = allocate_rfd(AF_INET))) +- break; +- daemon->rfd_save = forward->rfd4; +- fd = forward->rfd4->fd; +- } +- + #ifdef HAVE_CONNTRACK +- /* Copy connection mark of incoming query to outgoing connection. */ +- if (option_bool(OPT_CONNTRACK)) +- { +- unsigned int mark; +- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) +- setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +- } +-#endif ++ /* Copy connection mark of incoming query to outgoing connection. */ ++ if (option_bool(OPT_CONNTRACK)) ++ { ++ unsigned int mark; ++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) ++ setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } ++#endif + + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) +@@ -561,6 +527,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + /* Keep info in case we want to re-send this packet */ + daemon->srv_save = start; + daemon->packet_len = plen; ++ daemon->fd_save = fd; + + if (!gotname) + strcpy(daemon->namebuff, "query"); +@@ -579,7 +546,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + break; + forward->forwardall++; + } +- } ++ } + + if (!(start = start->next)) + start = daemon->servers; +@@ -779,7 +746,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server + } + + /* sets new last_server */ +-void reply_query(int fd, int family, time_t now) ++void reply_query(int fd, time_t now) + { + /* packet from peer server, extract data for cache, and send to + original requester */ +@@ -794,9 +761,8 @@ void reply_query(int fd, int family, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + /* Determine the address of the server replying so that we can mark that as good */ +- serveraddr.sa.sa_family = family; + #ifdef HAVE_IPV6 + if (serveraddr.sa.sa_family == AF_INET6) + serveraddr.in6.sin6_flowinfo = 0; +@@ -822,7 +788,7 @@ void reply_query(int fd, int family, time_t now) + + hash = hash_questions(header, n, daemon->namebuff); + +- if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) ++ if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) + return; + + /* log_query gets called indirectly all over the place, so +@@ -1027,9 +993,8 @@ void reply_query(int fd, int family, time_t now) + } + + new->sentto = server; +- new->rfd4 = NULL; ++ new->rfds = NULL; + #ifdef HAVE_IPV6 +- new->rfd6 = NULL; + #endif + new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); +@@ -1059,26 +1024,7 @@ void reply_query(int fd, int family, time_t now) + /* Don't resend this. */ + daemon->srv_save = NULL; + +- if (server->sfd) +- fd = server->sfd->fd; +- else +- { +- fd = -1; +-#ifdef HAVE_IPV6 +- if (server->addr.sa.sa_family == AF_INET6) +- { +- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) +- fd = new->rfd6->fd; +- } +- else +-#endif +- { +- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) +- fd = new->rfd4->fd; +- } +- } +- +- if (fd != -1) ++ if ((fd = allocate_rfd(&new->rfds, server)) != -1) + { + #ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ +@@ -1234,7 +1180,7 @@ void receive_query(struct listener *listen, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0; + netmask.s_addr = 0; + +@@ -2066,10 +2012,9 @@ static struct frec *allocate_frec(time_t now) + f->next = daemon->frec_list; + f->time = now; + f->sentto = NULL; +- f->rfd4 = NULL; ++ f->rfds = NULL; + f->flags = 0; + #ifdef HAVE_IPV6 +- f->rfd6 = NULL; + #endif + #ifdef HAVE_DNSSEC + f->dependent = NULL; +@@ -2082,46 +2027,192 @@ static struct frec *allocate_frec(time_t now) + return f; + } + +-struct randfd *allocate_rfd(int family) ++/* return a UDP socket bound to a random port, have to cope with straying into ++ occupied port nos and reserved ones. */ ++static int random_sock(struct server *s) + { +- static int finger = 0; +- int i; ++ int fd; ++ ++ if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) ++ { ++ if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) ++ return fd; + ++ if (s->interface[0] == 0) ++ (void)prettyprint_addr(&s->source_addr, daemon->namebuff); ++ else ++ strcpy(daemon->namebuff, s->interface); ++ ++ my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), ++ daemon->namebuff, strerror(errno)); ++ close(fd); ++ } ++ ++ return -1; ++} ++ ++/* compare source addresses and interface, serv2 can be null. */ ++static int server_isequal(const struct server *serv1, ++ const struct server *serv2) ++{ ++ return (serv2 && ++ serv2->ifindex == serv1->ifindex && ++ sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) && ++ strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0); ++} ++ ++/* fdlp points to chain of randomfds already in use by transaction. ++ If there's already a suitable one, return it, else allocate a ++ new one and add it to the list. ++ ++ Not leaking any resources in the face of allocation failures ++ is rather convoluted here. ++ ++ Note that rfd->serv may be NULL, when a server goes away. ++*/ ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv) ++{ ++ static int finger = 0; ++ int i, j = 0; ++ struct randfd_list *rfl; ++ struct randfd *rfd = NULL; ++ int fd = 0; ++ ++ /* If server has a pre-allocated fd, use that. */ ++ if (serv->sfd) ++ return serv->sfd->fd; ++ ++ /* existing suitable random port socket linked to this transaction? */ ++ for (rfl = *fdlp; rfl; rfl = rfl->next) ++ if (server_isequal(serv, rfl->rfd->serv)) ++ return rfl->rfd->fd; ++ ++ /* No. need new link. */ ++ if ((rfl = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl->next; ++ else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) ++ return -1; ++ + /* limit the number of sockets we have open to avoid starvation of + (eg) TFTP. Once we have a reasonable number, randomness should be OK */ +- + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount == 0) + { +- if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) +- break; +- +- daemon->randomsocks[i].refcount = 1; +- daemon->randomsocks[i].family = family; +- return &daemon->randomsocks[i]; ++ if ((fd = random_sock(serv)) != -1) ++ { ++ rfd = &daemon->randomsocks[i]; ++ rfd->serv = serv; ++ rfd->fd = fd; ++ rfd->refcount = 1; ++ } ++ break; + } +- ++ + /* No free ones or cannot get new socket, grab an existing one */ +- for (i = 0; i < RANDOM_SOCKS; i++) ++ if (!rfd) ++ for (j = 0; j < RANDOM_SOCKS; j++) ++ { ++ i = (j + finger) % RANDOM_SOCKS; ++ if (daemon->randomsocks[i].refcount != 0 && ++ server_isequal(serv, daemon->randomsocks[i].serv) && ++ daemon->randomsocks[i].refcount != 0xfffe) ++ { ++ finger = i + 1; ++ rfd = &daemon->randomsocks[i]; ++ rfd->refcount++; ++ break; ++ } ++ } ++ ++ if (j == RANDOM_SOCKS) + { +- int j = (i+finger) % RANDOM_SOCKS; +- if (daemon->randomsocks[j].refcount != 0 && +- daemon->randomsocks[j].family == family && +- daemon->randomsocks[j].refcount != 0xffff) ++ struct randfd_list *rfl_poll; ++ ++ /* there are no free slots, and non with the same parameters we can piggy-back on. ++ We're going to have to allocate a new temporary record, distinguished by ++ refcount == 0xffff. This will exist in the frec randfd list, never be shared, ++ and be freed when no longer in use. It will also be held on ++ the daemon->rfl_poll list so the poll system can find it. */ ++ ++ if ((rfl_poll = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl_poll->next; ++ else ++ rfl_poll = whine_malloc(sizeof(struct randfd_list)); ++ ++ if (!rfl_poll || ++ !(rfd = whine_malloc(sizeof(struct randfd))) || ++ (fd = random_sock(serv)) == -1) + { +- finger = j; +- daemon->randomsocks[j].refcount++; +- return &daemon->randomsocks[j]; ++ ++ /* Don't leak anything we may already have */ ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ ++ if (rfl_poll) ++ { ++ rfl_poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl_poll; ++ } ++ ++ if (rfd) ++ free(rfd); ++ ++ return -1; /* doom */ + } +- } + +- return NULL; /* doom */ ++ /* Note rfd->serv not set here, since it's not reused */ ++ rfd->fd = fd; ++ rfd->refcount = 0xffff; /* marker for temp record */ ++ ++ rfl_poll->rfd = rfd; ++ rfl_poll->next = daemon->rfl_poll; ++ daemon->rfl_poll = rfl_poll; ++ } ++ ++ rfl->rfd = rfd; ++ rfl->next = *fdlp; ++ *fdlp = rfl; ++ ++ return rfl->rfd->fd; + } + +-void free_rfd(struct randfd *rfd) ++void free_rfds(struct randfd_list **fdlp) + { +- if (rfd && --(rfd->refcount) == 0) +- close(rfd->fd); ++ struct randfd_list *tmp, *rfl, *poll, *next, **up; ++ ++ for (rfl = *fdlp; rfl; rfl = tmp) ++ { ++ if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0) ++ close(rfl->rfd->fd); ++ ++ /* temporary overflow record */ ++ if (rfl->rfd->refcount == 0xffff) ++ { ++ free(rfl->rfd); ++ ++ /* go through the link of all these by steam to delete. ++ This list is expected to be almost always empty. */ ++ for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next) ++ { ++ next = poll->next; ++ ++ if (poll->rfd == rfl->rfd) ++ { ++ *up = poll->next; ++ poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = poll; ++ } ++ else ++ up = &poll->next; ++ } ++ } ++ ++ tmp = rfl->next; ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ } ++ ++ *fdlp = NULL; + } + + static void free_frec(struct frec *f) +@@ -2137,14 +2228,11 @@ static void free_frec(struct frec *f) + } + + f->frec_src.next = NULL; +- free_rfd(f->rfd4); +- f->rfd4 = NULL; ++ free_rfds(&f->rfds); + f->sentto = NULL; + f->flags = 0; + + #ifdef HAVE_IPV6 +- free_rfd(f->rfd6); +- f->rfd6 = NULL; + #endif + + #ifdef HAVE_DNSSEC +@@ -2252,26 +2340,39 @@ struct frec *get_new_frec(time_t now, int *wait, int force) + } + + /* crc is all-ones if not known. */ +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash) + { + struct frec *f; +- ++ struct server *s; ++ int type; ++ struct randfd_list *fdl; ++ + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ +- if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) +- return f; +- +- if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) +- return f; +- +- /* sent to upstream from bound socket. */ +- if (f->sentto->sfd && f->sentto->sfd->fd == fd) ++ for (fdl = f->rfds; fdl; fdl = fdl->next) ++ if (fdl->rfd->fd == fd) + return f; ++ ++ /* Sent to upstream from socket associated with a server. ++ Note we have to iterate over all the possible servers, since they may ++ have different bound sockets. */ ++ type = f->sentto->flags & SERV_TYPE; ++ s = f->sentto; ++ do { ++ if ((type == (s->flags & SERV_TYPE)) && ++ (type != SERV_HAS_DOMAIN || ++ (s->domain && hostname_isequal(f->sentto->domain, s->domain))) && ++ !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ s->sfd && s->sfd->fd == fd) ++ return f; ++ ++ s = s->next ? s->next : daemon->servers; ++ } while (s != f->sentto); + } +- ++ + return NULL; + } + +@@ -2317,31 +2418,27 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) + void resend_query() + { + if (daemon->srv_save) +- { +- int fd; +- +- if (daemon->srv_save->sfd) +- fd = daemon->srv_save->sfd->fd; +- else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) +- fd = daemon->rfd_save->fd; +- else +- return; +- +- while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, +- &daemon->srv_save->addr.sa, +- sa_len(&daemon->srv_save->addr)))); +- } ++ while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0, ++ &daemon->srv_save->addr.sa, ++ sa_len(&daemon->srv_save->addr)))); + } + + /* A server record is going away, remove references to it */ + void server_gone(struct server *server) + { + struct frec *f; ++ int i; + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->sentto == server) + free_frec(f); +- ++ ++ /* If any random socket refers to this server, NULL the reference. ++ No more references to the socket will be created in the future. */ ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) ++ daemon->randomsocks[i].serv = NULL; ++ + if (daemon->last_server == server) + daemon->last_server = NULL; + +diff --git a/src/loop.c b/src/loop.c +index 0b47a2f..98d0b9e 100644 +--- a/src/loop.c ++++ b/src/loop.c +@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid); + void loop_send_probes() + { + struct server *serv; ++ struct randfd_list *rfds = NULL; + + if (!option_bool(OPT_LOOP_DETECT)) + return; +@@ -34,29 +35,22 @@ void loop_send_probes() + { + ssize_t len = loop_make_probe(serv->uid); + int fd; +- struct randfd *rfd = NULL; + +- if (serv->sfd) +- fd = serv->sfd->fd; +- else +- { +- if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) +- continue; +- fd = rfd->fd; +- } +- ++ if ((fd = allocate_rfd(&rfds, serv)) == -1) ++ continue; ++ + while (retry_send(sendto(fd, daemon->packet, len, 0, + &serv->addr.sa, sa_len(&serv->addr)))); +- +- free_rfd(rfd); + } ++ ++ free_rfds(&rfds); + } + + static ssize_t loop_make_probe(u32 uid) + { + struct dns_header *header = (struct dns_header *)daemon->packet; + unsigned char *p = (unsigned char *)(header+1); +- ++ + /* packet buffer overwritten */ + daemon->srv_save = NULL; + +diff --git a/src/network.c b/src/network.c +index 47caf38..4eda1fd 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -639,7 +639,8 @@ int enumerate_interfaces(int reset) + #ifdef HAVE_AUTH + struct auth_zone *zone; + #endif +- ++ struct server *serv; ++ + /* Do this max once per select cycle - also inhibits netlink socket use + in TCP child processes. */ + +@@ -657,6 +658,13 @@ int enumerate_interfaces(int reset) + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + ++ /* iface indexes can change when interfaces are created/destroyed. ++ We use them in the main forwarding control path, when the path ++ to a server is specified by an interface, so cache them. ++ Update the cache here. */ ++ for (serv = daemon->servers; serv; serv = serv->next) ++ serv->ifindex = if_nametoindex(serv->interface); ++ + again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) +@@ -754,7 +762,7 @@ again: + + errno = errsave; + spare = param.spare; +- ++ + return ret; + } + +@@ -893,10 +901,10 @@ int tcp_interface(int fd, int af) + /* use mshdr so that the CMSDG_* macros are available */ + msg.msg_control = daemon->packet; + msg.msg_controllen = len = daemon->packet_buff_sz; +- ++ + /* we overwrote the buffer... */ +- daemon->srv_save = NULL; +- ++ daemon->srv_save = NULL; ++ + if (af == AF_INET) + { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && +@@ -1228,61 +1236,6 @@ void join_multicast(int dienow) + } + #endif + +-/* return a UDP socket bound to a random port, have to cope with straying into +- occupied port nos and reserved ones. */ +-int random_sock(int family) +-{ +- int fd; +- +- if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) +- { +- union mysockaddr addr; +- unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; +- int tries = ports_avail < 30 ? 3 * ports_avail : 100; +- +- memset(&addr, 0, sizeof(addr)); +- addr.sa.sa_family = family; +- +- /* don't loop forever if all ports in use. */ +- +- if (fix_fd(fd)) +- while(tries--) +- { +- unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); +- +- if (family == AF_INET) +- { +- addr.in.sin_addr.s_addr = INADDR_ANY; +- addr.in.sin_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in.sin_len = sizeof(struct sockaddr_in); +-#endif +- } +-#ifdef HAVE_IPV6 +- else +- { +- addr.in6.sin6_addr = in6addr_any; +- addr.in6.sin6_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in6.sin6_len = sizeof(struct sockaddr_in6); +-#endif +- } +-#endif +- +- if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) +- return fd; +- +- if (errno != EADDRINUSE && errno != EACCES) +- break; +- } +- +- close(fd); +- } +- +- return -1; +-} +- +- + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) + { + union mysockaddr addr_copy = *addr; +@@ -1328,39 +1281,34 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind + return 1; + } + +-static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) ++static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex) + { + struct serverfd *sfd; +- unsigned int ifindex = 0; + int errsave; + + /* when using random ports, servers which would otherwise use +- the INADDR_ANY/port0 socket have sfd set to NULL */ +- if (!daemon->osport && intname[0] == 0) ++ the INADDR_ANY/port0 socket have sfd set to NULL, this is ++ anything without an explictly set source port. */ ++ if (!daemon->osport) + { + errno = 0; + + if (addr->sa.sa_family == AF_INET && +- addr->in.sin_addr.s_addr == INADDR_ANY && + addr->in.sin_port == htons(0)) + return NULL; + + #ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && +- memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && + addr->in6.sin6_port == htons(0)) + return NULL; + #endif + } + +- if (intname && strlen(intname) != 0) +- ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ +- + /* may have a suitable one already */ + for (sfd = daemon->sfds; sfd; sfd = sfd->next ) +- if (sockaddr_isequal(&sfd->source_addr, addr) && +- strcmp(intname, sfd->interface) == 0 && +- ifindex == sfd->ifindex) ++ if (ifindex == sfd->ifindex && ++ sockaddr_isequal(&sfd->source_addr, addr) && ++ strcmp(intname, sfd->interface) == 0) + return sfd; + + /* need to make a new one. */ +@@ -1408,7 +1356,7 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); + #endif +- allocate_sfd(&addr, ""); ++ allocate_sfd(&addr, "", 0); + #ifdef HAVE_IPV6 + memset(&addr, 0, sizeof(addr)); + addr.in6.sin6_family = AF_INET6; +@@ -1417,13 +1365,13 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); + #endif +- allocate_sfd(&addr, ""); ++ allocate_sfd(&addr, "", 0); + #endif + } + + for (srv = daemon->servers; srv; srv = srv->next) + if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && +- !allocate_sfd(&srv->source_addr, srv->interface) && ++ !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && + errno != 0 && + option_bool(OPT_NOWILD)) + { +@@ -1631,7 +1579,7 @@ void check_servers(void) + + /* Do we need a socket set? */ + if (!serv->sfd && +- !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && ++ !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && + errno != 0) + { + my_syslog(LOG_WARNING, +diff --git a/src/option.c b/src/option.c +index 79122df..abc5a48 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -795,7 +795,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + if (interface_opt) + { + #if defined(SO_BINDTODEVICE) +- safe_strncpy(interface, interface_opt, IF_NAMESIZE); ++ safe_strncpy(interface, source, IF_NAMESIZE); ++ source = interface_opt; + #else + return _("interface binding not supported"); + #endif +diff --git a/src/tftp.c b/src/tftp.c +index f2eccbc..ba9833e 100644 +--- a/src/tftp.c ++++ b/src/tftp.c +@@ -96,7 +96,7 @@ void tftp_request(struct listener *listen, time_t now) + + if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) + return; +- ++ + /* Can always get recvd interface for IPv6 */ + if (!check_dest) + { +@@ -566,7 +566,7 @@ void check_tftp_listeners(time_t now) + { + /* we overwrote the buffer... */ + daemon->srv_save = NULL; +- ++ + if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) + { + if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) +@@ -609,7 +609,7 @@ void check_tftp_listeners(time_t now) + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; +- ++ + if ((len = get_block(daemon->packet, transfer)) == -1) + { + len = tftp_err_oops(daemon->packet, transfer->file->filename); +diff --git a/src/util.c b/src/util.c +index 6287529..d016db6 100644 +--- a/src/util.c ++++ b/src/util.c +@@ -311,7 +311,7 @@ void *whine_malloc(size_t size) + return ret; + } + +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) + { + if (s1->sa.sa_family == s2->sa.sa_family) + { +-- +2.26.2 + diff --git a/dnsmasq-2.86-dhcpv6-client-arch.patch b/dnsmasq-2.86-dhcpv6-client-arch.patch new file mode 100644 index 0000000..b03d864 --- /dev/null +++ b/dnsmasq-2.86-dhcpv6-client-arch.patch @@ -0,0 +1,28 @@ +From 9e2b6474f2074511c3911b2f777e8e8704782670 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Wed, 22 Sep 2021 14:54:01 +0200 +Subject: [PATCH] Add support for option6 names of RFC 5970 + +Client Network Interface Identifier and Client System Architecture Type +options were not understood by dnsmasq. Add it to supported option +types. +--- + src/dhcp-common.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/dhcp-common.c b/src/dhcp-common.c +index 224c4d6..368d686 100644 +--- a/src/dhcp-common.c ++++ b/src/dhcp-common.c +@@ -645,6 +645,8 @@ static const struct opttab_t opttab6[] = { + { "ntp-server", 56, 0 }, + { "bootfile-url", 59, OT_NAME }, + { "bootfile-param", 60, OT_CSTRING }, ++ { "client-arch", 61, 2 | OT_DEC }, /* RFC 5970 */ ++ { "client-interface-id", 62, 1 | OT_DEC }, /* RFC 5970 */ + { NULL, 0, 0 } + }; + #endif +-- +2.31.1 + diff --git a/dnsmasq-systemd-sysusers.conf b/dnsmasq-systemd-sysusers.conf new file mode 100644 index 0000000..2106ae5 --- /dev/null +++ b/dnsmasq-systemd-sysusers.conf @@ -0,0 +1 @@ +u dnsmasq - "Dnsmasq DHCP and DNS server" /var/lib/dnsmasq diff --git a/dnsmasq.service b/dnsmasq.service new file mode 100644 index 0000000..07fa92e --- /dev/null +++ b/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/dnsmasq.spec b/dnsmasq.spec new file mode 100644 index 0000000..394d8a6 --- /dev/null +++ b/dnsmasq.spec @@ -0,0 +1,718 @@ +%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: 21%{?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 +Patch20: dnsmasq-2.81-optimize-fds-close.patch +Patch21: dnsmasq-2.81-rh1829448.patch +Patch22: dnsmasq-2.79-CVE-2020-25681.patch +Patch23: dnsmasq-2.79-CVE-2020-25684.patch +Patch24: dnsmasq-2.79-CVE-2020-25685.patch +Patch25: dnsmasq-2.79-CVE-2020-25686.patch +Patch26: dnsmasq-2.79-CVE-2020-25686-2.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=3f535da79e7a42104543ef5c7b5fa2bed819a78b +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=04490bf622ac84891aad6f2dd2edf83725decdee +Patch27: dnsmasq-2.79-mixed-family-failed.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=b2ed691eb3ca6488a8878f5f3dd950a07b14a9db +Patch28: dnsmasq-2.81-netlink-table.patch +Patch29: dnsmasq-2.84-bind-dynamic-netlink.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 +Patch30: dnsmasq-2.85-CVE-2021-3448.patch +# http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=03212e533b1e07aba30d2f4112009dc3af867ea5 +Patch31: dnsmasq-2.80-man-nameing.patch +Patch32: dnsmasq-2.79-alternative-lease.patch +Patch33: dnsmasq-2.86-dhcpv6-client-arch.patch +# Downstream only patch; https://bugzilla.redhat.com/show_bug.cgi?id=1919894 +# Similar functionality is implemented since 2.86 in upstream, but introduced +# several regressions. This implements just limited change in different way. +Patch34: dnsmasq-2.79-server-domain-rh1919894.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 +%patch20 -p1 -b .rh1816613 +%patch21 -p1 -b .rh1829448 +%patch22 -p1 -b .CVE-2020-25681 +%patch23 -p1 -b .CVE-2020-25684 +%patch24 -p1 -b .CVE-2020-25685 +%patch25 -p1 -b .CVE-2020-25686 +%patch26 -p1 -b .CVE-2020-25686-2 +%patch27 -p1 -b .rh1921153 +%patch28 -p1 -b .rh1887649-table +%patch29 -p1 -b .rh1887649 +%patch30 -p1 -b .CVE-2021-3448 +%patch31 -p1 -b .rh1947039 +%patch32 -p1 -b .rh1998448 +%patch33 -p1 -b .dhcpv6-client-arch +%patch34 -p1 -b .rh1919894 + +# 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_package %{name} %{SOURCE2} + +%post +#https://fedoraproject.org/wiki/Changes/SystemdSysusers +%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 +* Thu Jan 27 2022 Petr Menšík - 2.79-21 +- Send queries only to best domain-specific server (#1919894) + +* Mon Sep 20 2021 Petr Menšík - 2.79-20 +- Offer alternate DHCPv6 address if requested is already leased (#1998448) + +* Tue Jun 29 2021 Petr Menšík - 2.79-19 +- Correct typo in man page (#1947039) + +* Thu Mar 18 2021 Petr Menšík - 2.79-18 +- Properly randomize outgoing ports also with bound interface (CVE-2021-3448) + +* Fri Feb 12 2021 Petr Menšík - 2.79-17 +- Fix sporadic bind-dynamic failures (#1887649) + +* Wed Jan 27 2021 Petr Menšík - 2.79-16 +- Fix network errors on queries both from ipv4 and ipv6 (#1921153) + +* Wed Nov 25 2020 Petr Menšík - 2.79-15 +- Fix various issues in dnssec validation (CVE-2020-25681) +- Accept responses only on correct sockets (CVE-2020-25684) +- Use strong verification on queries (CVE-2020-25685) + +* Wed Aug 26 2020 Tomas Korbar - 2.79-14 +- Honor sysusers.d during installation (#1819684) + +* Tue May 05 2020 Petr Menšík - 2.79-13 +- Fix mixed address family reservations on DHCP (#1829448) + +* Mon Mar 30 2020 Tomas Korbar - 2.79-12 +- Minimize count of close syscalls on startup (#1816613) + +* 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 diff --git a/sources b/sources new file mode 100644 index 0000000..c0990c5 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (dnsmasq-2.79.tar.xz) = 2c06212696ab55e1584f6133872f5b196013509e4b1822d0457787b456e14341afdde887749e370a2e512124cb4138f012f4601b08690707be4acc7cf2f2876f