mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-04 13:48:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10fbc39cf5 | ||
|
|
2463798e03 | ||
|
|
a570a139d5 | ||
|
|
738c049988 | ||
|
|
145abca7f6 | ||
|
|
a627a7847f | ||
|
|
cdbfb97227 | ||
|
|
11493fe218 | ||
|
|
7719737f36 | ||
|
|
3a3e7a049f | ||
|
|
bc80b93cd1 | ||
|
|
03fd0c19ef | ||
|
|
baeb7430e2 | ||
|
|
f90df6c532 | ||
|
|
a9880c595f | ||
|
|
ad9208358e | ||
|
|
baf85a8f38 | ||
|
|
a9cb3971c6 | ||
|
|
f031f5b4b3 | ||
|
|
cf9c9c74e0 | ||
|
|
b518bc425d | ||
|
|
0c177ad607 | ||
|
|
4388c38468 | ||
|
|
0a31ade4db | ||
|
|
a4ebe438df | ||
|
|
8d8ad76d89 | ||
|
|
b50b55e227 | ||
|
|
7a663fd46b | ||
|
|
1f122521bb | ||
|
|
8af20e56ed | ||
|
|
8aa607d322 | ||
|
|
52982cfe8f | ||
|
|
81d78d9c75 | ||
|
|
7ebe7ebb93 | ||
|
|
1da5cc2951 | ||
|
|
5990261a57 | ||
|
|
b6b7e5a6fd | ||
|
|
f8c162b8b0 | ||
|
|
c93e9ce592 | ||
|
|
bf80262874 | ||
|
|
ea4645d52c | ||
|
|
ea9bd30cd4 | ||
|
|
1af6ee6468 | ||
|
|
c310853907 | ||
|
|
3ff66da573 | ||
|
|
d823d8e9cd | ||
|
|
2c78b6db6c | ||
|
|
4bf46ceb37 | ||
|
|
7d42376a3a | ||
|
|
08cc209e7a | ||
|
|
ff4964d01d | ||
|
|
45771dc92c | ||
|
|
af73e48067 | ||
|
|
77d4749a5e | ||
|
|
0375e40824 | ||
|
|
18a139f48f | ||
|
|
d5d2bfb74a | ||
|
|
7f5802c144 | ||
|
|
7280e46aa5 | ||
|
|
754177b368 | ||
|
|
228d7ff964 | ||
|
|
14094e88be | ||
|
|
c705607122 | ||
|
|
9d2c93b608 | ||
|
|
073082ddc0 | ||
|
|
9f651f3398 | ||
|
|
e3a26d092e | ||
|
|
10e6b5b83e | ||
|
|
788b4e0f6c | ||
|
|
7b151eb606 | ||
|
|
014e909f78 | ||
|
|
29f8712f02 | ||
|
|
0450f9e28a | ||
|
|
0637bcf010 | ||
|
|
4bcc9650fa | ||
|
|
fc2286805f | ||
|
|
d158aeca75 | ||
|
|
36d081e374 | ||
|
|
9eb0757da9 | ||
|
|
2d0e0c7a54 | ||
|
|
cfc2244c2a | ||
|
|
9ad74926d4 | ||
|
|
a6069da373 | ||
|
|
14f53171ca | ||
|
|
8fd06d2cc0 | ||
|
|
d152e81e42 | ||
|
|
e0bec3342b | ||
|
|
dd707937d5 | ||
|
|
c5f46412a3 | ||
|
|
cd04c0ce20 | ||
|
|
0630a45ae1 | ||
|
|
3ea905b98b | ||
|
|
ca509cb611 | ||
|
|
8ceb8ee9b5 | ||
|
|
1c4a97edb5 | ||
|
|
9e3e0465cd | ||
|
|
b12a9249f6 | ||
|
|
9dec84ca42 | ||
|
|
68c149e9dd | ||
|
|
5ea054b264 | ||
|
|
b95d5a2577 | ||
|
|
5be5dc1f16 | ||
|
|
48348a2d77 | ||
|
|
b4a0663654 | ||
|
|
53313014b5 | ||
|
|
cf08eeee12 | ||
|
|
0597828b74 | ||
|
|
c8168e888e | ||
|
|
3a052d55fb | ||
|
|
1eab169173 | ||
|
|
4070a74862 | ||
|
|
65adc53130 | ||
|
|
66d9658f5f | ||
|
|
ca30c82647 | ||
|
|
32a54fc8a5 | ||
|
|
3f2bfca3df | ||
|
|
e4d0bb4579 | ||
|
|
84415a87be | ||
|
|
1537645e8f | ||
|
|
ceab392c60 | ||
|
|
5f0f6aa9b1 | ||
|
|
578ea08391 | ||
|
|
fbfcf87f8b | ||
|
|
19f3e06e7a | ||
|
|
40d1152059 | ||
|
|
3c830c4f1c | ||
|
|
e9f400dd37 | ||
|
|
870f14108f | ||
|
|
714d36229a | ||
|
|
60e90e2224 | ||
|
|
ed2ba5b07a | ||
|
|
729c16a8ac | ||
|
|
80acb76f46 | ||
|
|
f1fa05d754 | ||
|
|
b148ba4752 | ||
|
|
81f619612e | ||
|
|
cb321709e9 | ||
|
|
8eb36844a0 | ||
|
|
8fa68a6e1e | ||
|
|
1269f074f8 | ||
|
|
e1faf705c9 | ||
|
|
f603a4f920 | ||
|
|
c090f1d17b | ||
|
|
fa48bdb939 | ||
|
|
d8f66f4fda | ||
|
|
629107bd6d | ||
|
|
052aa0fcf3 |
No files matched your search
@@ -1,3 +1,66 @@
|
||||
version 2.94
|
||||
Record that CVE-2026-12969 has been allocated to the
|
||||
buffer OOB read in find_soa() which is fixed in git commit
|
||||
14094e88beca519c53151184cc4553656672b54f, released at 2.93.
|
||||
Thanks to do litli for spotting this.
|
||||
|
||||
Fix erroneous behaviour of --no-dhcpv4-interface with
|
||||
constructed DHCPv6/RA - DHCPv6 and RA are inhibited by
|
||||
--no-dhcpv4-interface when using constructed address ranges.
|
||||
Thanks to David Nichols for discovery, diagnosis and patch.
|
||||
|
||||
Fix heap OOB read in zone_status. Thanks to Yaohui Zhu (朱耀辉)
|
||||
for spotting and reporting this. A crafted reply can crash dnsmasq.
|
||||
CNVD-C-2026-590208 applies.
|
||||
|
||||
|
||||
version 2.93
|
||||
Fix a corner-case in DNSSEC validation with wildcards. If we have
|
||||
a wildcard record *.example.com and receive a query for
|
||||
a.example.com then that's OK, but we have to check that there isn't
|
||||
an actual a.example.com record. The corner case is when we get a
|
||||
query for *.example.com in that case the non-existence check
|
||||
is not required, was being done. Thanks to Jan Breig for
|
||||
spotting this.
|
||||
|
||||
Enable support for inotify on FreeBSD 15.0-RELEASE, which added
|
||||
Linux-compatible inotify support.
|
||||
|
||||
Fix DNSSEC failure with spurious RRSIGs. The presence of wrong
|
||||
RRSIG RRs in replies caused DNSSEC validation to fail even
|
||||
when the RRs do not require validation because the zone is
|
||||
unsigned. Note that, at the time of this commit, Google
|
||||
DNS appears to have the same bug, so if you're using 8.8.8.8
|
||||
or friends as upstream, resolving the broken zones
|
||||
(eg rivcoed.org) will still fail. Thanks to Petr Menšík
|
||||
for the bug report.
|
||||
|
||||
Fix DNSSEC fail with CNAME replies to DS queries. A CNAME reply
|
||||
to a DNSSEC query was confusing the validation logic. It now
|
||||
accepts a signed CNAME reply to a DS query as proof that no DS
|
||||
exists at the domain. This fixes the DS/zone break detection logic.
|
||||
|
||||
Fix regression in 2.92 release which broke DHCPv6 when a DHCP
|
||||
relay is in use. Many thanks to Jørgen Søvik for help
|
||||
finding this bug.
|
||||
|
||||
Modify the inotify implementation so that inotify watches are
|
||||
only created after dnsmasq has changed permissions and userid.
|
||||
This means that the permissions used when creating the watches
|
||||
are the same as used for accessing watched files, which makes
|
||||
more sense and avoids odd and confusing error conditions.
|
||||
|
||||
Rework storage allocation for domain names. This fixes a security
|
||||
bug that can cause heap-overwrite with long domain names.
|
||||
CVE-2026-2291 covers this and a simple patch for existing
|
||||
releases was released with the CVE. This patch reworks the whole
|
||||
code base to make it cleaner and less liable to future
|
||||
confusions. Either upgrading to 2.93 or applying the patch
|
||||
to earlier versions is sufficient to close the security hole.
|
||||
Thanks to Andrew S. Fasano for spotting this problem in the
|
||||
first place.
|
||||
|
||||
|
||||
version 2.92
|
||||
Redesign the interaction between DNSSEC validation and per-domain
|
||||
servers, specified as --server=/<domain>/<ip-address>. This should
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
# dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
@@ -233,31 +233,55 @@ struct dhcp6_packet create_release_packet(const char* iaid, const char* ip, cons
|
||||
return result;
|
||||
}
|
||||
|
||||
static void copy_option_string(char *dst, size_t dst_len, const char *src, size_t src_len)
|
||||
{
|
||||
if (dst_len == 0)
|
||||
return;
|
||||
|
||||
if (src_len >= dst_len)
|
||||
src_len = dst_len - 1;
|
||||
|
||||
memcpy(dst, src, src_len);
|
||||
dst[src_len] = '\0';
|
||||
}
|
||||
|
||||
uint16_t parse_iana_suboption(char* buf, size_t len)
|
||||
{
|
||||
size_t current_pos = 0;
|
||||
char option_value[1024];
|
||||
while (current_pos < len)
|
||||
{
|
||||
if (len - current_pos < 2 * sizeof(uint16_t))
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
uint16_t option_type, option_len;
|
||||
memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
|
||||
memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
|
||||
option_type = ntohs(option_type);
|
||||
option_len = ntohs(option_len);
|
||||
current_pos += 2 * sizeof(uint16_t);
|
||||
if (option_len > len - current_pos)
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
if (option_type == STATUS_CODE)
|
||||
{
|
||||
uint16_t status;
|
||||
if (option_len < sizeof(uint16_t))
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
memcpy(&status, buf + current_pos, sizeof(uint16_t));
|
||||
status = ntohs(status);
|
||||
if (status != SUCCESS)
|
||||
{
|
||||
memcpy(option_value, buf + current_pos + sizeof(uint16_t) , option_len - sizeof(uint16_t));
|
||||
option_value[option_len-sizeof(uint16_t)] ='\0';
|
||||
copy_option_string(option_value, sizeof(option_value),
|
||||
buf + current_pos + sizeof(uint16_t),
|
||||
option_len - sizeof(uint16_t));
|
||||
fprintf(stderr, "Error: %s\n", option_value);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
current_pos += option_len;
|
||||
}
|
||||
|
||||
return -2;
|
||||
@@ -266,6 +290,9 @@ uint16_t parse_iana_suboption(char* buf, size_t len)
|
||||
int16_t parse_packet(char* buf, size_t len)
|
||||
{
|
||||
int16_t ret = -1;
|
||||
if (len < 4)
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
uint8_t type = buf[0];
|
||||
/*skipping tx id. you need it, uncomment following line
|
||||
uint16_t tx_id = ntohs((buf[1] <<16) + (buf[2] <<8) + buf[3]);
|
||||
@@ -277,20 +304,31 @@ int16_t parse_packet(char* buf, size_t len)
|
||||
char option_value[1024];
|
||||
while (current_pos < len)
|
||||
{
|
||||
if (len - current_pos < 2 * sizeof(uint16_t))
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
uint16_t option_type, option_len;
|
||||
memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
|
||||
memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
|
||||
option_type = ntohs(option_type);
|
||||
option_len = ntohs(option_len);
|
||||
current_pos += 2 * sizeof(uint16_t);
|
||||
if (option_len > len - current_pos)
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
if (option_type == STATUS_CODE)
|
||||
{
|
||||
uint16_t status;
|
||||
if (option_len < sizeof(uint16_t))
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
memcpy(&status, buf + current_pos, sizeof(uint16_t));
|
||||
status = ntohs(status);
|
||||
if (status != SUCCESS)
|
||||
{
|
||||
memcpy(option_value, buf + current_pos +sizeof(uint16_t) , option_len -sizeof(uint16_t));
|
||||
copy_option_string(option_value, sizeof(option_value),
|
||||
buf + current_pos + sizeof(uint16_t),
|
||||
option_len - sizeof(uint16_t));
|
||||
fprintf(stderr, "Error: %d %s\n", status, option_value);
|
||||
return status;
|
||||
}
|
||||
@@ -301,6 +339,9 @@ int16_t parse_packet(char* buf, size_t len)
|
||||
|
||||
if (option_type == IA_NA )
|
||||
{
|
||||
if (option_len < 24)
|
||||
return UNSPEC_FAIL;
|
||||
|
||||
uint16_t result = parse_iana_suboption(buf + current_pos +24, option_len -24);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
@@ -331,9 +331,15 @@ static char *print_mac(char *buff, unsigned char *mac, int len)
|
||||
if (len == 0)
|
||||
sprintf(p, "<null>");
|
||||
else
|
||||
for (i = 0; i < len; i++)
|
||||
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
|
||||
|
||||
{
|
||||
/* Server-controlled lengths (e.g. BOOTREPLY hlen) must not be trusted
|
||||
to fit the destination buffer; cap at the actual chaddr field size. */
|
||||
if (len > DHCP_CHADDR_MAX)
|
||||
len = DHCP_CHADDR_MAX;
|
||||
for (i = 0; i < len; i++)
|
||||
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
+55
-14
@@ -136,6 +136,8 @@ Log the results of DNS queries handled by dnsmasq. Enable a full cache dump on r
|
||||
then the log has extra information at the start of each line.
|
||||
This consists of a serial number which ties together the log lines associated with an individual query, and the IP address of the requestor. If the argument "proto" is supplied, this shows everything that "extra" does and also the network protocol used to communicate the queries. Logging of only queries to the authoritative server can be configured with
|
||||
.B --log-queries=auth
|
||||
and logging only failed replies (Error, NXDOMAIN or NODATA) can be configured with
|
||||
.B --log-queries=only_failed
|
||||
.TP
|
||||
.B \-8, --log-facility=<facility>
|
||||
Set the facility to which dnsmasq will send syslog entries, this
|
||||
@@ -152,6 +154,9 @@ allows the log file to be rotated without stopping dnsmasq.
|
||||
.B --log-debug
|
||||
Enable extra logging intended for debugging rather than information.
|
||||
.TP
|
||||
.B --log-malloc
|
||||
Enable logging of memory allocation and release, for debugging.
|
||||
.TP
|
||||
.B --log-async[=<lines>]
|
||||
Enable asynchronous logging and optionally set the limit on the
|
||||
number of lines
|
||||
@@ -169,12 +174,18 @@ Specify an alternate path for dnsmasq to record its process-id in. Normally /var
|
||||
.B \-u, --user=<username>
|
||||
Specify the userid to which dnsmasq will change after startup. Dnsmasq must normally be started as root, but it will drop root
|
||||
privileges after startup by changing id to another user. Normally this user is "nobody" but that
|
||||
can be over-ridden with this switch.
|
||||
can be over-ridden with this switch.
|
||||
.TP
|
||||
.B \-g, --group=<groupname>
|
||||
Specify the group which dnsmasq will run
|
||||
as. The default is "dip", if available, to facilitate access to
|
||||
/etc/ppp/resolv.conf which is not normally world readable.
|
||||
/etc/ppp/resolv.conf which is not normally world readable. If "dip" is not available, the default becomes
|
||||
the main group associated with the uid specified by
|
||||
.B --user
|
||||
, or "nobody".
|
||||
Specifying
|
||||
.B --group
|
||||
without an argument inhibits any change to the gid of the process, even to the defaults.
|
||||
.TP
|
||||
.B \-v, --version
|
||||
Print the version number.
|
||||
@@ -863,7 +874,7 @@ will be logged. If this coincides with DNS not functioning, it
|
||||
is necessary to disable the feature. As at version 2.91, 0x20 encoding
|
||||
is disabled by default, and must be enabled with --do-0x20-encode. The default
|
||||
may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode
|
||||
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default
|
||||
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-0x20-encode or a future default
|
||||
0x20-encode enable.
|
||||
.TP
|
||||
.B --use-stale-cache[=<max TTL excess in s>]
|
||||
@@ -1036,7 +1047,10 @@ options. If the lease time is given, then leases
|
||||
will be given for that length of time. The lease time is in seconds,
|
||||
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
|
||||
the default lease time is one hour for IPv4 and one day for IPv6. The
|
||||
minimum lease time is two minutes. For IPv6 ranges, the lease time
|
||||
minimum lease time is two minutes. An explicitly configured lease time also
|
||||
affects IPv6 router advertisements: it acts as a ceiling on the advertised
|
||||
prefix lifetimes. For constructed ranges it also acts as a floor, unless the
|
||||
prefix is being deprecated. For IPv6 ranges, the lease time
|
||||
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
|
||||
lease or router advertisement to zero, which causes clients to use
|
||||
other addresses, if available, for new connections as a prelude to renumbering.
|
||||
@@ -1324,7 +1338,7 @@ have exactly the same effect as
|
||||
options containing the same information. /etc/ethers is re-read when
|
||||
dnsmasq receives SIGHUP. IPv6 addresses are NOT read from /etc/ethers.
|
||||
.TP
|
||||
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt-name>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
|
||||
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt>|option:<opt-name>|option4:<opt>|option4:<optname>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
|
||||
Send various extra options to DHCP clients. By default,
|
||||
dnsmasq sends some standard options to DHCP clients, the netmask and
|
||||
broadcast address are set to the same as the host running dnsmasq, and
|
||||
@@ -1343,6 +1357,13 @@ For example, to set the default route option to 192.168.4.4, use
|
||||
and to set the time-server address to 192.168.0.4, use
|
||||
\fB--dhcp-option=42,192.168.0.4\fP or
|
||||
\fB--dhcp-option=option:ntp-server,192.168.0.4\fP.
|
||||
|
||||
The above examples are backwards compatible, for consistency with IPV6, the time-server example
|
||||
may also be given as
|
||||
\fB--dhcp-option=option:42,192.168.0.4\fP or
|
||||
\fB--dhcp-option=option4:42,192.168.0.4\fP or
|
||||
\fB--dhcp-option=option4:ntp-server,192.168.0.4\fP
|
||||
|
||||
The special address 0.0.0.0 means "the address of the system running dnsmasq".
|
||||
|
||||
An option without data is valid, and includes just the option without data.
|
||||
@@ -1367,11 +1388,14 @@ described in RFC 3442.
|
||||
IPv6 options are specified using the \fBoption6:\fP
|
||||
keyword, followed by the option number or option name. The IPv6 option
|
||||
name space is disjoint from the IPv4 option name space. IPv6 addresses
|
||||
in options must be bracketed with square brackets, eg.
|
||||
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
|
||||
in options must be bracketed with square brackets, eg. \fB --dhcp-option=option6:sntp-server,[1234::56]\fP or
|
||||
--dhcp-option=option6:31,[1234::56].
|
||||
For IPv6, [::] means "the global address of
|
||||
the machine running dnsmasq", whilst [fd00::] is replaced with the
|
||||
ULA, if it exists, and [fe80::] with the link-local address.
|
||||
ULA, if it exists, and [fe80::] with the link-local address. The vendorclass option
|
||||
is special for DHCPv6, since it requires an enterprise number which must appear as a decimal number
|
||||
before the class(es). eg. \fB --dhcp-option=option6:vendor-class,343,HTTPClient\fP
|
||||
|
||||
|
||||
Be careful: data-type suitability for the option number sent is not checked.
|
||||
It is quite possible to persuade dnsmasq to generate illegal DHCP packets with
|
||||
@@ -1513,7 +1537,7 @@ The normal default DNS server (the same address as the DHCP server)
|
||||
will not be appropriate when there is no route bewteen the
|
||||
two, so this will have to be explicitly configured.
|
||||
.TP
|
||||
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,]<vendor-class>
|
||||
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,][<vendor-class>]
|
||||
Map from a vendor-class string to a tag. Most DHCP clients provide a
|
||||
"vendor class" which represents, in some sense, the type of host. This option
|
||||
maps vendor classes to tags, so that DHCP options may be selectively delivered
|
||||
@@ -1527,9 +1551,11 @@ allow fuzzy matching. The set: prefix is optional but allowed for
|
||||
consistency.
|
||||
|
||||
Note that in IPv6 only, vendorclasses are namespaced with an
|
||||
IANA-allocated enterprise number. This is given with enterprise:
|
||||
IANA-allocated enterprise number. This may be given with an enterprise:
|
||||
keyword and specifies that only vendorclasses matching the specified
|
||||
number should be searched.
|
||||
number should be searched. Similarly, an enterprise number may be given
|
||||
with no vendor-class string. This always matches vendorclasses with
|
||||
the given enterprise number.
|
||||
.TP
|
||||
.B \-j, --dhcp-userclass=set:<tag>,<user-class>
|
||||
Map from a user-class string to a tag (with substring
|
||||
@@ -1895,7 +1921,12 @@ DNSMASQ_CLIENT_ID if the host provided a client-id.
|
||||
|
||||
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID if a
|
||||
DHCP relay-agent added any of these options.
|
||||
|
||||
|
||||
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS from
|
||||
an RFC3925 Vendor-Identifying Vendor-Specific Information Option
|
||||
containing the Broadband Forum enterprise number and options
|
||||
defined in TR-069.
|
||||
|
||||
If the client provides vendor-class, DNSMASQ_VENDOR_CLASS.
|
||||
|
||||
For IPv6 only:
|
||||
@@ -1918,7 +1949,17 @@ only supplied for
|
||||
since these data are not held in dnsmasq's lease
|
||||
database.
|
||||
|
||||
|
||||
Please note that the environment options DNSMASQ_USER_CLASS0..DNSMASQ_USER_CLASSn,
|
||||
DNSMASQ_VENDOR_CLASS0..DNSMASQ_VENDOR_CLASSn,
|
||||
DNSMASQ_VENDOR_CLASS,
|
||||
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID,
|
||||
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS,
|
||||
DNSMASQ_MUD_URL all contain values which are derived directly from untrusted data recieved
|
||||
from clients. Be very careful with quoting when using these values, lest an attacker sends
|
||||
a vendor class of "rm -rf /". Note also that the RFCs defining these options tend to simply
|
||||
define them an opaque data: the implication but not the letter of the definition is
|
||||
printable strings. In practise, dnsmasq passes all octets except zero, which is treated
|
||||
as a terminator.
|
||||
|
||||
All file descriptors are
|
||||
closed except stdin, which is open to /dev/null, and stdout and stderr which capture output for logging by dnsmasq.
|
||||
@@ -2697,7 +2738,7 @@ IPv4 and IPv6 addresses from /etc/hosts (and
|
||||
and
|
||||
.B --interface-name
|
||||
and
|
||||
.B ---dynamic-host
|
||||
.B --dynamic-host
|
||||
provided the address falls into one of the subnets specified in the
|
||||
.B --auth-zone.
|
||||
.PP
|
||||
|
||||
+2
-2
@@ -869,7 +869,7 @@ Om detta sammanfaller med att DNS inte fungerar, är det
|
||||
nödvändigt att inaktivera funktionen. I version 2.91 är 0x20-kodning
|
||||
inaktiverad som standard och måste aktiveras med --do-0x20-encode. Standardinställningen
|
||||
kan komma att ändras i framtiden, så för att vara säker på dess status efter en uppgradering, ställ in --do-0x20-encode
|
||||
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-x20-encode eller en framtida standardinställning
|
||||
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-0x20-encode eller en framtida standardinställning
|
||||
0x20-encode enable.
|
||||
.TP
|
||||
.B --use-stale-cache[=<max TTL excess in s>]
|
||||
@@ -2732,7 +2732,7 @@ IPv4- och IPv6-adresser från /etc/hosts (och
|
||||
och
|
||||
.B --interface-name
|
||||
och
|
||||
.B ---dynamic-host
|
||||
.B --dynamic-host
|
||||
förutsatt att adressen faller inom ett av de subnät som anges i
|
||||
.B --auth-zone.
|
||||
.PP
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# This revised version is (C) Copyright by
|
||||
# Matthias Andree <matthias.andree@gmx.de>, 2010 - 2021.
|
||||
# Conrad Kostecki <conrad@kostecki.com>, 2014 - 2024.
|
||||
# Conrad Kostecki <conrad@kostecki.com>, 2014 - 2025.
|
||||
# It is subject to the GNU General Public License v2,
|
||||
# or at your option, any later version.
|
||||
#
|
||||
@@ -10,10 +10,10 @@
|
||||
# Simon Kelley <simon@thekelleys.org.uk>, 2005.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: dnsmasq 2.91\n"
|
||||
"Project-Id-Version: dnsmasq 2.92\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-03-20 00:00+0000\n"
|
||||
"PO-Revision-Date: 2024-12-23 22:36+0100\n"
|
||||
"PO-Revision-Date: 2025-12-06 16:24+0100\n"
|
||||
"Last-Translator: Conrad Kostecki <conrad@kostecki.com>\n"
|
||||
"Language-Team: German <de@li.org>\n"
|
||||
"Language: de\n"
|
||||
@@ -21,7 +21,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.5\n"
|
||||
"X-Generator: Poedit 3.8\n"
|
||||
|
||||
#: cache.c:696
|
||||
msgid "Internal error in cache."
|
||||
@@ -94,22 +94,22 @@ msgstr "Anfragen nach autoritativen Zonen %u"
|
||||
#: cache.c:2078
|
||||
#, c-format
|
||||
msgid "DNSSEC per-query subqueries HWM %u"
|
||||
msgstr ""
|
||||
msgstr "DNSSEC-Unterabfragen pro Anfrage HWM %u"
|
||||
|
||||
#: cache.c:2079
|
||||
#, c-format
|
||||
msgid "DNSSEC per-query crypto work HWM %u"
|
||||
msgstr ""
|
||||
msgstr "DNSSEC-Kryptografie pro Anfrage HWM %u"
|
||||
|
||||
#: cache.c:2080
|
||||
#, c-format
|
||||
msgid "DNSSEC per-RRSet signature fails HWM %u"
|
||||
msgstr ""
|
||||
msgstr "DNSSEC-Signatur pro RRSet fehlgeschlagen HWM %u"
|
||||
|
||||
#: cache.c:2084
|
||||
#, c-format
|
||||
msgid "child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."
|
||||
msgstr ""
|
||||
msgstr "Kindprozesse für TCP-Anfragen: aktuell in Gebrauch %zu, höchster Wert seit letztem SIGUSR1 %zu, maximal erlaubt %zu."
|
||||
|
||||
#: cache.c:2113
|
||||
#, c-format
|
||||
@@ -215,9 +215,8 @@ msgid "Don't include IPv6 addresses in DNS answers."
|
||||
msgstr "Keine IPv6-Adressen in DNS-Antworten inkludieren."
|
||||
|
||||
#: option.c:430
|
||||
#, fuzzy
|
||||
msgid "Don't include resource records of the given type in DNS answers."
|
||||
msgstr "Keine IPv4-Adressen in DNS-Antworten inkludieren."
|
||||
msgstr "Resource Records des angegebenen Typs nicht in DNS-Antworten einfügen."
|
||||
|
||||
#: option.c:431
|
||||
msgid "Enable DHCP in the range given with lease duration."
|
||||
@@ -354,9 +353,8 @@ msgid "DHCP option sent even if the client does not request it."
|
||||
msgstr "DHCP-Option, die selbst ohne Klientenanfrage gesendet wird."
|
||||
|
||||
#: option.c:463
|
||||
#, fuzzy
|
||||
msgid "DHCP option sent only to PXE clients."
|
||||
msgstr "Aufforderung, die an PXE-Klienten geschickt wird."
|
||||
msgstr "DHCP-Option wird nur an PXE-Clients gesendet."
|
||||
|
||||
#: option.c:464
|
||||
msgid "Specify port to listen for DNS requests on (defaults to 53)."
|
||||
@@ -509,14 +507,12 @@ msgid "Do not provide DHCP on this interface, only provide DNS."
|
||||
msgstr "Auf dieser Schnittstelle kein DHCP anbieten, sondern nur DNS."
|
||||
|
||||
#: option.c:500
|
||||
#, fuzzy
|
||||
msgid "Do not provide DHCPv6 on this interface."
|
||||
msgstr "Auf dieser Schnittstelle kein DHCP anbieten, sondern nur DNS."
|
||||
msgstr "DHCPv6 auf dieser Schnittstelle nicht bereitstellen."
|
||||
|
||||
#: option.c:501
|
||||
#, fuzzy
|
||||
msgid "Do not provide DHCPv4 on this interface."
|
||||
msgstr "Auf dieser Schnittstelle kein DHCP anbieten, sondern nur DNS."
|
||||
msgstr "DHCPv4 auf dieser Schnittstelle nicht bereitstellen."
|
||||
|
||||
#: option.c:502
|
||||
msgid "Enable dynamic address allocation for bootp."
|
||||
@@ -536,7 +532,7 @@ msgstr "Geben Sie zusätzliche Netzwerke an, die eine Broadcast-Domäne für DHC
|
||||
|
||||
#: option.c:506
|
||||
msgid "Enable RFC 4388 leasequery functions for DHCPv4"
|
||||
msgstr ""
|
||||
msgstr "RFC-4388-Leasequery-Funktionen für DHCPv4 aktivieren"
|
||||
|
||||
#: option.c:507
|
||||
msgid "Disable ICMP echo address checking in the DHCP server."
|
||||
@@ -858,9 +854,8 @@ msgid "Timestamp file to verify system clock for DNSSEC"
|
||||
msgstr "Zeitstempel-Datei für die Verifizierung der Systemuhrzeit für DNSSEC"
|
||||
|
||||
#: option.c:586
|
||||
#, fuzzy
|
||||
msgid "Set resource limits for DNSSEC validation"
|
||||
msgstr "Aktiviere DNSSEC-Validierung"
|
||||
msgstr "Ressourcenlimits für die DNSSEC-Validierung festlegen"
|
||||
|
||||
#: option.c:587
|
||||
msgid "Set MTU, priority, resend-interval and router-lifetime"
|
||||
@@ -907,14 +902,12 @@ msgid "Enables DHCPv4 Rapid Commit option."
|
||||
msgstr "Aktiviert die DHCPv4-\"Rapid Commit\"-Option."
|
||||
|
||||
#: option.c:598
|
||||
#, fuzzy
|
||||
msgid "Path to debug packet dump file."
|
||||
msgstr "Pfad zur Paketablagedatei zur Fehlersuche"
|
||||
msgstr "Pfad zur Dump-Datei für das Debug-Paket."
|
||||
|
||||
#: option.c:599
|
||||
#, fuzzy
|
||||
msgid "Mask which packets to dump."
|
||||
msgstr "Maskiere Pakete, welche abgelegt werden sollen"
|
||||
msgstr "Maskiere, welche Pakete protokolliert werden sollen."
|
||||
|
||||
#: option.c:600
|
||||
msgid "Call dhcp-script when lease expiry changes."
|
||||
@@ -934,26 +927,23 @@ msgstr "Unterdrückt die Round-Robin-Sortierung von DNS-Einträgen."
|
||||
|
||||
#: option.c:604
|
||||
msgid "Suppress DNS bit 0x20 encoding."
|
||||
msgstr ""
|
||||
msgstr "DNS-Bit-0x20-Codierung unterdrücken."
|
||||
|
||||
#: option.c:605
|
||||
msgid "Enable DNS bit 0x20 encoding."
|
||||
msgstr ""
|
||||
msgstr "DNS-Bit-0x20-Codierung aktivieren."
|
||||
|
||||
#: option.c:606
|
||||
#, fuzzy
|
||||
msgid "Do not add CHAOS TXT records."
|
||||
msgstr "fehlerhafter TXT-Eintrag"
|
||||
msgstr "Keine CHAOS-TXT-Einträge hinzufügen."
|
||||
|
||||
#: option.c:607
|
||||
#, fuzzy
|
||||
msgid "Cache this DNS resource record type."
|
||||
msgstr "Spezifiziere einen beliebiegen DNS Eintrag"
|
||||
msgstr "Diesen DNS-Resource-Record-Typ cachen."
|
||||
|
||||
#: option.c:608
|
||||
#, fuzzy
|
||||
msgid "Maximum number of concurrent tcp connections."
|
||||
msgstr "Maximale Anzahl gleichzeitiger TFTP-Übertragungen (%s voreingestellt)."
|
||||
msgstr "Maximale Anzahl gleichzeitiger TCP-Verbindungen."
|
||||
|
||||
#: option.c:838
|
||||
#, c-format
|
||||
@@ -1056,7 +1046,7 @@ msgstr "DHCP-Option zu lang"
|
||||
|
||||
#: option.c:1961
|
||||
msgid "No vendor-encap options allowed in dhcp-option-pxe"
|
||||
msgstr ""
|
||||
msgstr "Keine vendor-encap-Optionen in dhcp-option-pxe erlaubt"
|
||||
|
||||
#: option.c:1968
|
||||
msgid "illegal dhcp-match"
|
||||
@@ -1141,9 +1131,8 @@ msgid "recompile with HAVE_CONNTRACK defined to enable connmark-allowlist direct
|
||||
msgstr "Neukompilierung mit HAVE_CONNTRACK notwendig, um connmark-allowlist-Direktiven zu aktivieren"
|
||||
|
||||
#: option.c:3585
|
||||
#, fuzzy
|
||||
msgid "bad RR type"
|
||||
msgstr "fehlerhafter RR-Eintrag"
|
||||
msgstr "Ungültiger RR-Typ"
|
||||
|
||||
#: option.c:3644
|
||||
msgid "bad port range"
|
||||
@@ -1203,9 +1192,8 @@ msgid "duplicate dhcp-host IP address %s"
|
||||
msgstr "doppelte dhcp-host IP-Adresse %s"
|
||||
|
||||
#: option.c:4201
|
||||
#, fuzzy
|
||||
msgid "DHCP host has multiple names"
|
||||
msgstr "fehlerhafter DHCP-Hostname"
|
||||
msgstr "DHCP-Host hat mehrere Namen"
|
||||
|
||||
#: option.c:4209
|
||||
msgid "bad DHCP host name"
|
||||
@@ -1452,7 +1440,7 @@ msgstr "keine \"search\"-Anweisung in %s gefunden"
|
||||
#: option.c:6284
|
||||
#, c-format
|
||||
msgid "srv-host name %s too long after domain appended"
|
||||
msgstr ""
|
||||
msgstr "srv-Hostname %s nach Anfügen der Domain zu lang"
|
||||
|
||||
#: option.c:6293
|
||||
msgid "there must be a default domain when --dhcp-fqdn is set"
|
||||
@@ -1483,12 +1471,12 @@ msgstr "möglichen DNS-Rebind-Angriff entdeckt: %s"
|
||||
|
||||
#: forward.c:1013 forward.c:2280
|
||||
msgid "limit exceeded: per-query subqueries"
|
||||
msgstr ""
|
||||
msgstr "Limit überschritten: Unterabfragen pro Anfrage"
|
||||
|
||||
#: forward.c:1103 forward.c:2288
|
||||
#, c-format
|
||||
msgid "validation of %s failed: resource limit exceeded."
|
||||
msgstr ""
|
||||
msgstr "Validierung von %s fehlgeschlagen: Ressourcenlimit überschritten."
|
||||
|
||||
#: forward.c:1697
|
||||
#, c-format
|
||||
@@ -1496,9 +1484,9 @@ msgid "ignoring query from non-local network %s (logged only once)"
|
||||
msgstr "Ignoriere Abfrage von nicht-lokalen Netzwerk %s (Nur einmal protokolliert)"
|
||||
|
||||
#: forward.c:2120
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "TCP connection failed to %s#%d"
|
||||
msgstr "Einspeisen in ARP-Zwischenspeicher fehlgeschlagen: %s"
|
||||
msgstr "TCP-Verbindung zu %s#%d fehlgeschlagen"
|
||||
|
||||
#: forward.c:2426
|
||||
#, c-format
|
||||
@@ -1522,7 +1510,7 @@ msgstr "Maximale Anzahl gleichzeitiger DNS-Abfragen, die erreicht %s (max. %d)"
|
||||
|
||||
#: forward.c:3243
|
||||
msgid "Case mismatch in DNS reply - check bit 0x20 encoding."
|
||||
msgstr ""
|
||||
msgstr "Groß-/Kleinschreibung stimmt in der DNS-Antwort nicht überein – Bit-0x20-Codierung überprüfen."
|
||||
|
||||
#: network.c:705
|
||||
#, c-format
|
||||
@@ -1797,9 +1785,8 @@ msgid "DNS service limited to local subnets"
|
||||
msgstr "DNS-Dienst auf Unternetze eingeschränkt"
|
||||
|
||||
#: dnsmasq.c:882
|
||||
#, fuzzy
|
||||
msgid "DNS service limited to localhost"
|
||||
msgstr "DNS-Dienst auf Unternetze eingeschränkt"
|
||||
msgstr "DNS-Dienst auf localhost beschränkt"
|
||||
|
||||
#: dnsmasq.c:885
|
||||
#, c-format
|
||||
@@ -1839,9 +1826,9 @@ msgid "DNSSEC signature timestamps not checked until system time valid"
|
||||
msgstr "DNSSEC Signatur-Zeitstempel werden erst überprüft, sobald die Systemuhrzeit gültig ist"
|
||||
|
||||
#: dnsmasq.c:946
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "configured with negative trust anchor for %s"
|
||||
msgstr "konfiguriert mit Vertrauensanker für %s Schlüsselanhänger %u"
|
||||
msgstr "Mit negativem Vertrauensanker für %s konfiguriert"
|
||||
|
||||
#: dnsmasq.c:946
|
||||
#, c-format
|
||||
@@ -1867,9 +1854,8 @@ msgid "warning: ignoring resolv-file flag because no-resolv is set"
|
||||
msgstr "Warnung: Ignoriere \"resolv-file\", weil \"no-resolv\" aktiv ist"
|
||||
|
||||
#: dnsmasq.c:981
|
||||
#, fuzzy
|
||||
msgid "no upstream servers configured - please set them from DBus"
|
||||
msgstr "Warnung: keine vorgeschalteten Server konfiguriert"
|
||||
msgstr "Keine Upstream-Server konfiguriert – bitte über DBus festlegen"
|
||||
|
||||
#: dnsmasq.c:984
|
||||
msgid "warning: no upstream servers configured"
|
||||
@@ -1987,7 +1973,7 @@ msgstr "Kann keine Zeitstempel-Datei %s erzeugen: %s"
|
||||
#: dnsmasq.c:1576
|
||||
#, c-format
|
||||
msgid "TCP helper process %u died unexpectedly"
|
||||
msgstr ""
|
||||
msgstr "TCP-Hilfsprozess %u ist unerwartet abgestürzt"
|
||||
|
||||
#: dnsmasq.c:1592
|
||||
#, c-format
|
||||
@@ -2085,7 +2071,7 @@ msgstr "DHCP-Bereich %s - %s passt nicht zur Netzmaske %s"
|
||||
#: dhcp.c:680 dhcp6.c:467
|
||||
#, c-format
|
||||
msgid "DHCP relay address %s appears on more than one interface"
|
||||
msgstr ""
|
||||
msgstr "DHCP-Relay-Adresse %s erscheint auf mehr als einer Schnittstelle"
|
||||
|
||||
#: dhcp.c:978
|
||||
#, c-format
|
||||
@@ -2117,9 +2103,8 @@ msgid "too many stored leases"
|
||||
msgstr "zu viele Leases gespeichert"
|
||||
|
||||
#: lease.c:183
|
||||
#, fuzzy
|
||||
msgid "lease-change script name is too long"
|
||||
msgstr "Lease-Änderungs-Skript mit den Rechten dieses Nutzers ausführen."
|
||||
msgstr "Der Name des Lease-Change-Skripts ist zu lang"
|
||||
|
||||
#: lease.c:203
|
||||
#, c-format
|
||||
@@ -2232,7 +2217,7 @@ msgstr "PXE BIS nicht unterstützt"
|
||||
#: rfc2131.c:1088
|
||||
#, c-format
|
||||
msgid "leasequery from %s not permitted"
|
||||
msgstr ""
|
||||
msgstr "Leasequery von %s nicht erlaubt"
|
||||
|
||||
#: rfc2131.c:1259 rfc3315.c:1248
|
||||
#, c-format
|
||||
@@ -2338,14 +2323,14 @@ msgid "%u reply delay: %d"
|
||||
msgstr "%u Antwortverzögerung: %d"
|
||||
|
||||
#: rfc2131.c:3118
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "Cannot send to server via interface %s: %s"
|
||||
msgstr "DHCP-Relay kann nicht über die Schnittstelle %s gesendet werden"
|
||||
msgstr "Kann nicht über Schnittstelle %s an Server senden: %s"
|
||||
|
||||
#: rfc2131.c:3189
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "Cannot broadcast DHCP relay via interface %s: %s"
|
||||
msgstr "DHCP-Relay kann nicht über die Schnittstelle %s gesendet werden"
|
||||
msgstr "Kann DHCP-Relay nicht über Schnittstelle %s senden: %s"
|
||||
|
||||
#: rfc2131.c:3213
|
||||
#, c-format
|
||||
@@ -2413,14 +2398,14 @@ msgid "unable to get free port for TFTP"
|
||||
msgstr "konnte keinen freien Port für TFTP bekommen"
|
||||
|
||||
#: tftp.c:367
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "unsupported write request from %s"
|
||||
msgstr "nicht unterstützte Anfrage von %s"
|
||||
msgstr "Nicht unterstützte Schreibanfrage von %s"
|
||||
|
||||
#: tftp.c:371
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "empty filename in request from %s"
|
||||
msgstr "nicht unterstützte Anfrage von %s"
|
||||
msgstr "Leerer Dateiname in Anfrage von %s"
|
||||
|
||||
#: tftp.c:373
|
||||
#, c-format
|
||||
@@ -2438,9 +2423,9 @@ msgid "ignoring packet from %s (TID mismatch)"
|
||||
msgstr "Paket von %s wird ignoriert (TID-Nichtübereinstimmung)"
|
||||
|
||||
#: tftp.c:730
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "timeout sending %s to %s"
|
||||
msgstr "konnte %s nicht an %s senden"
|
||||
msgstr "Zeitüberschreitung beim Senden von %s an %s"
|
||||
|
||||
#: tftp.c:732
|
||||
#, c-format
|
||||
@@ -2494,7 +2479,7 @@ msgstr "Kann nicht an DHCPv6-Server-Socket binden: %s"
|
||||
#: dhcp6.c:152
|
||||
#, c-format
|
||||
msgid "Working around kernel bug: faulty source address scope for VRF slave %s"
|
||||
msgstr ""
|
||||
msgstr "Umgehung eines Kernel-Fehlers: fehlerhafter Quelladressbereich für VRF-Slave %s"
|
||||
|
||||
#: rfc3315.c:174
|
||||
#, c-format
|
||||
@@ -2522,9 +2507,9 @@ msgid "%u client MAC address: %s"
|
||||
msgstr "%u Klient MAC-Adresse: %s"
|
||||
|
||||
#: rfc3315.c:487
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "%u cannot determine client MAC address"
|
||||
msgstr "%u Klient MAC-Adresse: %s"
|
||||
msgstr "%u kann die MAC-Adresse des Clients nicht bestimmen"
|
||||
|
||||
#: rfc3315.c:783 rfc3315.c:880
|
||||
msgid "address unavailable"
|
||||
@@ -2651,9 +2636,9 @@ msgid "DHCP relay from %s via %s"
|
||||
msgstr "DHCP-Relay von %s über %s"
|
||||
|
||||
#: dhcp-common.c:1071
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "DHCP split-relay from %s to %s via %s"
|
||||
msgstr "DHCP Weiterleitung von %s nach %s über %s"
|
||||
msgstr "DHCP-Split-Relay von %s nach %s über %s"
|
||||
|
||||
#: dhcp-common.c:1073
|
||||
#, c-format
|
||||
@@ -2784,21 +2769,21 @@ msgstr "Systemzeit als gültig betrachtet, prüfe jetzt DNSSEC Signatur-Zeitstem
|
||||
|
||||
#: dnssec.c:431
|
||||
msgid "per-query crypto work"
|
||||
msgstr ""
|
||||
msgstr "Kryptografische Arbeit pro Anfrage"
|
||||
|
||||
#: dnssec.c:697
|
||||
msgid "per-RRSet signature fails"
|
||||
msgstr ""
|
||||
msgstr "Signatur pro RRSet fehlgeschlagen"
|
||||
|
||||
#: dnssec.c:1027
|
||||
#, c-format
|
||||
msgid "Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."
|
||||
msgstr ""
|
||||
msgstr "Unsichere Antwort für DS %s empfangen, Annahme, dass dies für eine RFC-1918-Adresse in Ordnung ist."
|
||||
|
||||
#: dnssec.c:1034
|
||||
#, c-format
|
||||
msgid "Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."
|
||||
msgstr ""
|
||||
msgstr "Unsichere Antwort für DS %s empfangen, Annahme eines nicht-DNSSEC-domänenspezifischen Servers."
|
||||
|
||||
#: dnssec.c:1041
|
||||
#, c-format
|
||||
@@ -2808,7 +2793,7 @@ msgstr "Unsichere DS-Antwort für %s, bitte Domainkonfiguration und Upstream DNS
|
||||
#: dnssec.c:1152
|
||||
#, c-format
|
||||
msgid "Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."
|
||||
msgstr ""
|
||||
msgstr "Negative DS-Antwort ohne NS-Eintrag für %s empfangen, Annahme eines nicht-DNSSEC-domänenspezifischen Servers."
|
||||
|
||||
#: blockdata.c:55
|
||||
#, c-format
|
||||
@@ -2909,9 +2894,9 @@ msgid "cannot create %s: %s"
|
||||
msgstr "kann %s nicht erstellen: %s"
|
||||
|
||||
#: dump.c:76
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "cannot open pipe %s: %s"
|
||||
msgstr "Kann Logdatei %s nicht öffnen: %s"
|
||||
msgstr "Kann Pipe %s nicht öffnen: %s"
|
||||
|
||||
#: dump.c:82
|
||||
#, c-format
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -591,7 +591,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
|
||||
if (auth && zone)
|
||||
{
|
||||
char *authname;
|
||||
int newoffset, offset = 0;
|
||||
int newoffset = ansp - (unsigned char *)header, offset = 0;
|
||||
|
||||
if (!subnet)
|
||||
authname = zone->domain;
|
||||
@@ -631,8 +631,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
|
||||
}
|
||||
|
||||
/* handle NS and SOA in auth section or for explicit queries */
|
||||
newoffset = ansp - (unsigned char *)header;
|
||||
if (((anscount == 0 && !ns) || soa) &&
|
||||
if (((anscount == 0 && !ns) || soa) &&
|
||||
add_resource_record(header, limit, &trunc, 0, &ansp,
|
||||
daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
|
||||
authname, daemon->authserver, daemon->hostmaster,
|
||||
@@ -650,11 +649,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
|
||||
if (anscount != 0 || ns)
|
||||
{
|
||||
struct name_list *secondary;
|
||||
|
||||
|
||||
/* Only include the machine running dnsmasq if it's acting as an auth server */
|
||||
if (daemon->authinterface)
|
||||
{
|
||||
newoffset = ansp - (unsigned char *)header;
|
||||
if (add_resource_record(header, limit, &trunc, -offset, &ansp,
|
||||
daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver))
|
||||
{
|
||||
@@ -669,9 +667,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
|
||||
|
||||
if (!subnet)
|
||||
for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next)
|
||||
if (add_resource_record(header, limit, &trunc, offset, &ansp,
|
||||
daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name))
|
||||
if (add_resource_record(header, limit, &trunc, -offset, &ansp,
|
||||
daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, secondary->name))
|
||||
{
|
||||
if (offset == 0)
|
||||
offset = newoffset;
|
||||
if (ns)
|
||||
anscount++;
|
||||
else
|
||||
|
||||
+70
-39
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include "dnsmasq.h"
|
||||
|
||||
static struct blockdata *keyblock_free;
|
||||
static struct blockdata *new_block(void);
|
||||
|
||||
static struct blockdata *keyblock_free, *fallback;
|
||||
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
|
||||
|
||||
static void add_blocks(int n)
|
||||
@@ -48,7 +50,11 @@ void blockdata_init(void)
|
||||
/* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
add_blocks(daemon->cachesize);
|
||||
}
|
||||
|
||||
/* make sure we have one block to fall back on. */
|
||||
if (!(fallback = new_block()))
|
||||
die(_("Blockdata init: no memory"), NULL, EC_MISC);
|
||||
}
|
||||
|
||||
void blockdata_report(void)
|
||||
{
|
||||
@@ -84,36 +90,52 @@ static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
|
||||
struct blockdata *block, *ret = NULL;
|
||||
struct blockdata **prev = &ret;
|
||||
size_t blen;
|
||||
int fail;
|
||||
|
||||
do
|
||||
{
|
||||
if (!(block = new_block()))
|
||||
{
|
||||
/* failed to alloc, free partial chain */
|
||||
blockdata_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
memcpy(block->key, data, blen);
|
||||
data += blen;
|
||||
}
|
||||
else if (!read_write(fd, block->key, blen, RW_READ))
|
||||
{
|
||||
/* failed read free partial chain */
|
||||
blockdata_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
len -= blen;
|
||||
*prev = block;
|
||||
prev = &block->next;
|
||||
} while (len != 0);
|
||||
/* Zero length still gives one empty block, blockdata_alloc(NULL, 0)
|
||||
is how callers start a chain to add to with blockdata_expand(). */
|
||||
if (len == 0)
|
||||
return new_block();
|
||||
|
||||
for (fail = 0; len != 0; len -= blen)
|
||||
{
|
||||
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
|
||||
|
||||
if (!fail)
|
||||
{
|
||||
if ((block = new_block()))
|
||||
{
|
||||
*prev = block;
|
||||
prev = &block->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Failed to alloc, use the pre-allocated fall-back.
|
||||
This allows us to always read the specified amount of data from the fd. */
|
||||
fail = 1;
|
||||
block = fallback;
|
||||
}
|
||||
}
|
||||
|
||||
if (data)
|
||||
{
|
||||
memcpy(block->key, data, blen);
|
||||
data += blen;
|
||||
}
|
||||
else if (!read_write(fd, block->key, blen, RW_READ))
|
||||
{
|
||||
fail = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail)
|
||||
{
|
||||
/* failed, free partial chain */
|
||||
blockdata_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -132,7 +154,7 @@ int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t
|
||||
/* find size of current final block */
|
||||
for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN);
|
||||
|
||||
/* chain to short for length, something is broken */
|
||||
/* chain too short for length, something is broken */
|
||||
if (oldlen > KEYBLOCK_LEN)
|
||||
{
|
||||
blockdata_free(block);
|
||||
@@ -193,20 +215,22 @@ void *blockdata_retrieve(struct blockdata *block, size_t len, void *data)
|
||||
{
|
||||
size_t blen;
|
||||
struct blockdata *b;
|
||||
uint8_t *new, *d;
|
||||
uint8_t *d;
|
||||
|
||||
static unsigned int buff_len = 0;
|
||||
static unsigned char *buff = NULL;
|
||||
|
||||
if (!data)
|
||||
{
|
||||
static unsigned int buff_len = 0;
|
||||
static unsigned char *buff = NULL;
|
||||
uint8_t *new;
|
||||
|
||||
if (len > buff_len)
|
||||
{
|
||||
if (!(new = whine_malloc(len)))
|
||||
blen = len + 1024;
|
||||
if (!(new = whine_realloc(buff, blen)))
|
||||
return NULL;
|
||||
if (buff)
|
||||
free(buff);
|
||||
|
||||
buff = new;
|
||||
buff_len = blen;
|
||||
}
|
||||
data = buff;
|
||||
}
|
||||
@@ -233,6 +257,13 @@ void blockdata_write(struct blockdata *block, size_t len, int fd)
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: this can fail (NULL return) for two possible reasons.
|
||||
1) end of stream.
|
||||
2) malloc failure when allocating blocks.
|
||||
|
||||
Unless EOF is encountered, len octets will always be read from the stream,
|
||||
even in memory allocation fails.
|
||||
*/
|
||||
struct blockdata *blockdata_read(int fd, size_t len)
|
||||
{
|
||||
return blockdata_alloc_real(fd, NULL, len);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -47,24 +47,20 @@ static union all_addr del_addr;
|
||||
|
||||
#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
|
||||
|
||||
int arp_enumerate(void *parm, callback_t callback)
|
||||
static int arp_enumerate_family(int family, void *parm, callback_t callback)
|
||||
{
|
||||
int mib[6];
|
||||
size_t needed;
|
||||
char *next;
|
||||
struct rt_msghdr *rtm;
|
||||
struct sockaddr_inarp *sin2;
|
||||
struct sockaddr_dl *sdl;
|
||||
struct iovec buff;
|
||||
static struct iovec buff = { NULL, 0 };
|
||||
int rc;
|
||||
|
||||
buff.iov_base = NULL;
|
||||
buff.iov_len = 0;
|
||||
|
||||
mib[0] = CTL_NET;
|
||||
mib[1] = PF_ROUTE;
|
||||
mib[2] = 0;
|
||||
mib[3] = AF_INET;
|
||||
mib[3] = family;
|
||||
mib[4] = NET_RT_FLAGS;
|
||||
#ifdef RTF_LLINFO
|
||||
mib[5] = RTF_LLINFO;
|
||||
@@ -72,8 +68,8 @@ int arp_enumerate(void *parm, callback_t callback)
|
||||
mib[5] = 0;
|
||||
#endif
|
||||
if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1 || needed == 0)
|
||||
return 0;
|
||||
|
||||
return 1; /* not a failure: unsupported or empty table */
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (!expand_buf(&buff, needed))
|
||||
@@ -83,20 +79,50 @@ int arp_enumerate(void *parm, callback_t callback)
|
||||
break;
|
||||
needed += needed / 8;
|
||||
}
|
||||
|
||||
if (rc == -1)
|
||||
return 0;
|
||||
|
||||
for (next = buff.iov_base ; next < (char *)buff.iov_base + needed; next += rtm->rtm_msglen)
|
||||
{
|
||||
rtm = (struct rt_msghdr *)next;
|
||||
sin2 = (struct sockaddr_inarp *)(rtm + 1);
|
||||
sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
|
||||
if (!callback.af_unspec(AF_INET, &sin2->sin_addr, LLADDR(sdl), sdl->sdl_alen, parm))
|
||||
return 0;
|
||||
if (family == AF_INET)
|
||||
{
|
||||
struct sockaddr_inarp *sin2 = (struct sockaddr_inarp *)(rtm + 1);
|
||||
sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
|
||||
if (!callback.af_unspec(AF_INET, &sin2->sin_addr,
|
||||
LLADDR(sdl), sdl->sdl_alen, parm))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(rtm + 1);
|
||||
sdl = (struct sockaddr_dl *)((char *)sin6 + SA_SIZE(sin6));
|
||||
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
|
||||
{
|
||||
/* PF_ROUTE sysctl returns raw kernel structures with the interface
|
||||
index embedded in bytes 2-3 of link-local addresses. Extract it
|
||||
into sin6_scope_id per the KAME API contract before clearing. */
|
||||
sin6->sin6_scope_id =
|
||||
((uint32_t)(sin6->sin6_addr.s6_addr[2]) << 8) | sin6->sin6_addr.s6_addr[3];
|
||||
sin6->sin6_addr.s6_addr[2] = 0;
|
||||
sin6->sin6_addr.s6_addr[3] = 0;
|
||||
}
|
||||
if (!callback.af_unspec(AF_INET6, &sin6->sin6_addr,
|
||||
LLADDR(sdl), sdl->sdl_alen, parm))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int arp_enumerate(void *parm, callback_t callback)
|
||||
{
|
||||
if (!arp_enumerate_family(AF_INET, parm, callback))
|
||||
return 0;
|
||||
return arp_enumerate_family(AF_INET6, parm, callback);
|
||||
}
|
||||
#endif /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */
|
||||
|
||||
|
||||
|
||||
+249
-138
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -17,14 +17,18 @@
|
||||
#include "dnsmasq.h"
|
||||
|
||||
static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
|
||||
#ifdef HAVE_DHCP
|
||||
static struct crec *dhcp_spare = NULL;
|
||||
#endif
|
||||
static struct crec *config_spare = NULL;
|
||||
static struct crec *new_chain = NULL;
|
||||
static int insert_error;
|
||||
static union bigname *big_free = NULL;
|
||||
static int bignames_left, hash_size;
|
||||
|
||||
struct nameblock {
|
||||
struct nameblock *next;
|
||||
unsigned int last, index;
|
||||
char data[NAMEBLOCK_CHARS];
|
||||
} *hostblocks = NULL;
|
||||
|
||||
static void make_non_terminals(struct crec *source);
|
||||
static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
|
||||
time_t now, unsigned long ttl, unsigned int flags);
|
||||
@@ -192,6 +196,86 @@ void cache_init(void)
|
||||
rehash(daemon->cachesize);
|
||||
}
|
||||
|
||||
static struct crec *get_config_crec(void)
|
||||
{
|
||||
struct crec *ret;
|
||||
|
||||
if (config_spare)
|
||||
{
|
||||
ret = config_spare;
|
||||
config_spare = config_spare->next;
|
||||
}
|
||||
else
|
||||
ret = whine_malloc(SIZEOF_POINTER_CREC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_config_crec(struct crec *p)
|
||||
{
|
||||
p->next = config_spare;
|
||||
config_spare = p;
|
||||
}
|
||||
|
||||
static struct nameblock *nameblock_spare = NULL;
|
||||
|
||||
static char *store_name(unsigned int namelen, unsigned int index)
|
||||
{
|
||||
struct nameblock *block;
|
||||
char *ret = NULL;
|
||||
int i;
|
||||
|
||||
if (namelen > NAMEBLOCK_CHARS)
|
||||
return NULL;
|
||||
|
||||
/* Check the first few blocks for possible space. This allows efficient memory usage
|
||||
without blowing up O(n^2) CPU usage which the linked list gets long. */
|
||||
for (i = 10, block = hostblocks; i != 0 && block; block = block->next, i--)
|
||||
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
|
||||
{
|
||||
ret = &block->data[block->last];
|
||||
block->last += namelen;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (nameblock_spare)
|
||||
{
|
||||
block = nameblock_spare;
|
||||
nameblock_spare = nameblock_spare->next;
|
||||
}
|
||||
else
|
||||
block = whine_malloc(sizeof(struct nameblock));
|
||||
|
||||
if (block)
|
||||
{
|
||||
block->next = hostblocks;
|
||||
block->index = index;
|
||||
block->last = namelen;
|
||||
hostblocks = block;
|
||||
ret = &block->data[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_names(unsigned int index)
|
||||
{
|
||||
struct nameblock *block, *tmp, **up;
|
||||
|
||||
for (up = &hostblocks, block = hostblocks; block; block = tmp)
|
||||
{
|
||||
tmp = block->next;
|
||||
if (index == UID_NONE || block->index == index)
|
||||
{
|
||||
*up = tmp;
|
||||
block->next = nameblock_spare;
|
||||
nameblock_spare = block;
|
||||
}
|
||||
else
|
||||
up = &block->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
|
||||
but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
|
||||
will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
|
||||
@@ -441,12 +525,14 @@ unsigned int cache_remove_uid(const unsigned int uid)
|
||||
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid)
|
||||
{
|
||||
*up = tmp;
|
||||
free(crecp);
|
||||
free_config_crec(crecp);
|
||||
removed++;
|
||||
}
|
||||
else
|
||||
up = &crecp->hash_next;
|
||||
}
|
||||
|
||||
free_names(uid);
|
||||
|
||||
return removed;
|
||||
}
|
||||
@@ -855,7 +941,7 @@ void cache_end_insert(void)
|
||||
#ifdef HAVE_DNSSEC
|
||||
void cache_update_hwm(void)
|
||||
{
|
||||
/* Sneak out possibly updated crypto HWM values. */
|
||||
/* Sneak out possibly updated crypto HWM values. */
|
||||
unsigned char op = PIPE_OP_STATS;
|
||||
|
||||
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
|
||||
@@ -953,9 +1039,18 @@ int cache_recv_insert(time_t now, int fd)
|
||||
unsigned short class = C_IN;
|
||||
struct blockdata *block = NULL;
|
||||
|
||||
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
|
||||
&& !(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
|
||||
continue;
|
||||
/* Note that blockdata_read() can return NULL either
|
||||
because the stream ended, OR memory allocation failed.
|
||||
We continue here rather than returning 0 to finish draining
|
||||
the pipe in the second case. If the stream ended, the read
|
||||
of the next length at the top of the loop will fail
|
||||
and we'll return 0 there. */
|
||||
if (flags & F_RR)
|
||||
{
|
||||
if (!(flags & F_NEG) && (flags & F_KEYTAG) &&
|
||||
!(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
|
||||
continue;
|
||||
}
|
||||
#ifdef HAVE_DNSSEC
|
||||
else if (flags & F_DNSKEY)
|
||||
{
|
||||
@@ -1000,6 +1095,7 @@ int cache_recv_insert(time_t now, int fd)
|
||||
}
|
||||
|
||||
case PIPE_OP_RESULT:
|
||||
case PIPE_OP_KILLED:
|
||||
{
|
||||
/* UDP validation moved to TCP to avoid truncation.
|
||||
Restart UDP validation process with the returned result. */
|
||||
@@ -1013,21 +1109,32 @@ int cache_recv_insert(time_t now, int fd)
|
||||
!read_write(fd, (unsigned char *)&ret_len, sizeof(ret_len), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)daemon->packet, ret_len, RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&forward, sizeof(forward), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ))
|
||||
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ))
|
||||
return 0;
|
||||
|
||||
if (op == PIPE_OP_RESULT &&
|
||||
(!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
|
||||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ)))
|
||||
return 0;
|
||||
|
||||
if (op == PIPE_OP_KILLED)
|
||||
my_syslog(LOG_INFO, _("TCP process for DNSSEC validation timed out"));
|
||||
|
||||
/* There's a tiny chance that the frec may have been freed
|
||||
and reused before the TCP process returns. Detect that with
|
||||
the uid field which is unique modulo 2^32 for each use. */
|
||||
if (uid == forward->uid)
|
||||
/* There's a chance that the frec may have timed-out and been
|
||||
freed before the TCP process returns, so check that the
|
||||
frec is still marked in use. Even if it is in use, there's a tiny chance
|
||||
that the frec may have been freed and _reused_ before the TCP process returns.
|
||||
Detect that with the uid field which is unique modulo 2^32 for each use. */
|
||||
if (forward->sentto && uid == forward->uid)
|
||||
{
|
||||
/* repatriate the work counters from the child process. */
|
||||
*keycountp = keycount;
|
||||
*validatecountp = validatecount;
|
||||
if (op == PIPE_OP_RESULT)
|
||||
{
|
||||
/* repatriate the work counters from the child process. */
|
||||
*keycountp = keycount;
|
||||
*validatecountp = validatecount;
|
||||
}
|
||||
|
||||
if (!forward->dependent)
|
||||
return_reply(now, forward, (struct dns_header *)daemon->packet, ret_len, status);
|
||||
@@ -1254,7 +1361,7 @@ static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrle
|
||||
while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6))))
|
||||
if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
|
||||
{
|
||||
free(cache);
|
||||
free_config_crec(cache);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1333,9 +1440,10 @@ static int eatspace(FILE *f)
|
||||
}
|
||||
}
|
||||
|
||||
static int gettok(FILE *f, char *token)
|
||||
static int gettok(FILE *f, char *token, size_t buffsz)
|
||||
{
|
||||
int c, count = 0;
|
||||
int c;
|
||||
unsigned int count = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
@@ -1348,7 +1456,7 @@ static int gettok(FILE *f, char *token)
|
||||
return eatspace(f);
|
||||
}
|
||||
|
||||
if (count < (MAXDNAME - 1))
|
||||
if (count < (buffsz - 1))
|
||||
{
|
||||
token[count++] = c;
|
||||
token[count] = 0;
|
||||
@@ -1373,17 +1481,17 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
|
||||
|
||||
lineno += eatspace(f);
|
||||
|
||||
while ((atnl = gettok(f, token)) != -1)
|
||||
while ((atnl = gettok(f, token, MAXDNAMESTR)) != -1)
|
||||
{
|
||||
if (inet_pton(AF_INET, token, &addr) > 0)
|
||||
{
|
||||
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
|
||||
flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
|
||||
addrlen = INADDRSZ;
|
||||
domain_suffix = get_domain(addr.addr4);
|
||||
}
|
||||
else if (inet_pton(AF_INET6, token, &addr) > 0)
|
||||
{
|
||||
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
|
||||
flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
|
||||
addrlen = IN6ADDRSZ;
|
||||
domain_suffix = get_domain6(&addr.addr6);
|
||||
}
|
||||
@@ -1391,7 +1499,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
|
||||
{
|
||||
my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
|
||||
while (atnl == 0)
|
||||
atnl = gettok(f, token);
|
||||
atnl = gettok(f, token, MAXDNAMESTR);
|
||||
lineno += atnl;
|
||||
continue;
|
||||
}
|
||||
@@ -1409,7 +1517,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
|
||||
int fqdn, nomem;
|
||||
char *canon;
|
||||
|
||||
if ((atnl = gettok(f, token)) == -1)
|
||||
if ((atnl = gettok(f, token, MAXDNAMESTR)) == -1)
|
||||
break;
|
||||
|
||||
fqdn = !!strchr(token, '.');
|
||||
@@ -1417,27 +1525,37 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
|
||||
if ((canon = canonicalise(token, &nomem)))
|
||||
{
|
||||
/* If set, add a version of the name with a default domain appended */
|
||||
if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn &&
|
||||
(cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
|
||||
if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && (cache = get_config_crec()))
|
||||
{
|
||||
strcpy(cache->name.sname, canon);
|
||||
strcat(cache->name.sname, ".");
|
||||
strcat(cache->name.sname, domain_suffix);
|
||||
cache->flags = flags;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
|
||||
name_count++;
|
||||
names_done++;
|
||||
if (!(cache->name.namep = store_name(strlen(canon) + 2 + strlen(domain_suffix), index)))
|
||||
free_config_crec(cache);
|
||||
else
|
||||
{
|
||||
strcpy(cache->name.namep, canon);
|
||||
strcat(cache->name.namep, ".");
|
||||
strcat(cache->name.namep, domain_suffix);
|
||||
cache->flags = flags;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
|
||||
name_count++;
|
||||
names_done++;
|
||||
}
|
||||
}
|
||||
if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
|
||||
if ((cache = get_config_crec()))
|
||||
{
|
||||
strcpy(cache->name.sname, canon);
|
||||
cache->flags = flags;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
|
||||
name_count++;
|
||||
names_done++;
|
||||
if (!(cache->name.namep = store_name(strlen(canon) + 1, index)))
|
||||
free_config_crec(cache);
|
||||
else
|
||||
{
|
||||
strcpy(cache->name.namep, canon);
|
||||
cache->flags = flags;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
|
||||
name_count++;
|
||||
names_done++;
|
||||
}
|
||||
}
|
||||
|
||||
free(canon);
|
||||
|
||||
}
|
||||
@@ -1488,7 +1606,7 @@ void cache_reload(void)
|
||||
if (cache->flags & (F_HOSTS | F_CONFIG))
|
||||
{
|
||||
*up = cache->hash_next;
|
||||
free(cache);
|
||||
free_config_crec(cache);
|
||||
}
|
||||
else if (!(cache->flags & F_DHCP))
|
||||
{
|
||||
@@ -1503,11 +1621,13 @@ void cache_reload(void)
|
||||
else
|
||||
up = &cache->hash_next;
|
||||
}
|
||||
|
||||
free_names(UID_NONE); /* free everything */
|
||||
|
||||
/* Add locally-configured CNAMEs to the cache */
|
||||
for (a = daemon->cnames; a; a = a->next)
|
||||
if (a->alias[1] != '*' &&
|
||||
((cache = whine_malloc(SIZEOF_POINTER_CREC))))
|
||||
((cache = get_config_crec())))
|
||||
{
|
||||
cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
|
||||
cache->ttd = a->ttl;
|
||||
@@ -1521,25 +1641,30 @@ void cache_reload(void)
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
|
||||
(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
|
||||
if (ds->name && (cache = get_config_crec()))
|
||||
{
|
||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
cache->name.namep = ds->name;
|
||||
cache->uid = ds->class;
|
||||
if (ds->digestlen != 0)
|
||||
{
|
||||
cache->addr.ds.keylen = ds->digestlen;
|
||||
cache->addr.ds.algo = ds->algo;
|
||||
cache->addr.ds.keytag = ds->keytag;
|
||||
cache->addr.ds.digest = ds->digest_type;
|
||||
}
|
||||
else
|
||||
cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR;
|
||||
|
||||
cache_hash(cache);
|
||||
make_non_terminals(cache);
|
||||
if (!(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
|
||||
free_config_crec(cache);
|
||||
else
|
||||
{
|
||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
cache->name.namep = ds->name;
|
||||
cache->uid = ds->class;
|
||||
if (ds->digestlen != 0)
|
||||
{
|
||||
cache->addr.ds.keylen = ds->digestlen;
|
||||
cache->addr.ds.algo = ds->algo;
|
||||
cache->addr.ds.keytag = ds->keytag;
|
||||
cache->addr.ds.digest = ds->digest_type;
|
||||
}
|
||||
else
|
||||
cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR;
|
||||
|
||||
cache_hash(cache);
|
||||
make_non_terminals(cache);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1554,7 +1679,7 @@ void cache_reload(void)
|
||||
for (nl = hr->names; nl; nl = nl->next)
|
||||
{
|
||||
if ((hr->flags & HR_4) &&
|
||||
(cache = whine_malloc(SIZEOF_POINTER_CREC)))
|
||||
(cache = get_config_crec()))
|
||||
{
|
||||
cache->name.namep = nl->name;
|
||||
cache->ttd = hr->ttl;
|
||||
@@ -1563,7 +1688,7 @@ void cache_reload(void)
|
||||
}
|
||||
|
||||
if ((hr->flags & HR_6) &&
|
||||
(cache = whine_malloc(SIZEOF_POINTER_CREC)))
|
||||
(cache = get_config_crec()))
|
||||
{
|
||||
cache->name.namep = nl->name;
|
||||
cache->ttd = hr->ttl;
|
||||
@@ -1655,8 +1780,7 @@ void cache_unhash_dhcp(void)
|
||||
if (cache->flags & F_DHCP)
|
||||
{
|
||||
*up = cache->hash_next;
|
||||
cache->next = dhcp_spare;
|
||||
dhcp_spare = cache;
|
||||
free_config_crec(cache);
|
||||
}
|
||||
else
|
||||
up = &cache->hash_next;
|
||||
@@ -1707,7 +1831,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
|
||||
/* Name in hosts, address doesn't match */
|
||||
if (fail_crec)
|
||||
{
|
||||
inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
|
||||
inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAMESTR);
|
||||
my_syslog(MS_DHCP | LOG_WARNING,
|
||||
_("not giving name %s to the DHCP lease of %s because "
|
||||
"the name exists in %s with address %s"),
|
||||
@@ -1727,12 +1851,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
|
||||
else
|
||||
flags |= F_REVERSE;
|
||||
|
||||
if ((crec = dhcp_spare))
|
||||
dhcp_spare = dhcp_spare->next;
|
||||
else /* need new one */
|
||||
crec = whine_malloc(SIZEOF_POINTER_CREC);
|
||||
|
||||
if (crec) /* malloc may fail */
|
||||
if ((crec = get_config_crec()))
|
||||
{
|
||||
crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
|
||||
if (ttd == 0)
|
||||
@@ -1779,15 +1898,7 @@ static void make_non_terminals(struct crec *source)
|
||||
hostname_isequal(name, cache_get_name(crecp)))
|
||||
{
|
||||
*up = crecp->hash_next;
|
||||
#ifdef HAVE_DHCP
|
||||
if (type & F_DHCP)
|
||||
{
|
||||
crecp->next = dhcp_spare;
|
||||
dhcp_spare = crecp;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
free(crecp);
|
||||
free_config_crec(crecp);
|
||||
break;
|
||||
}
|
||||
else
|
||||
@@ -1820,17 +1931,7 @@ static void make_non_terminals(struct crec *source)
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DHCP
|
||||
if ((source->flags & F_DHCP) && dhcp_spare)
|
||||
{
|
||||
crecp = dhcp_spare;
|
||||
dhcp_spare = dhcp_spare->next;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
crecp = whine_malloc(SIZEOF_POINTER_CREC);
|
||||
|
||||
if (crecp)
|
||||
if ((crecp = get_config_crec()))
|
||||
{
|
||||
crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_RR | F_DNSKEY | F_DS | F_REVERSE);
|
||||
if (!(crecp->flags & F_IMMORTAL))
|
||||
@@ -2159,9 +2260,9 @@ static char *querystr(char *desc, unsigned short type)
|
||||
unsigned int i;
|
||||
int len = 10; /* strlen("type=xxxxx") */
|
||||
const char *types = NULL;
|
||||
static char *buff = NULL;
|
||||
static int bufflen = 0;
|
||||
|
||||
static struct iovec buff = { NULL, 0 };
|
||||
char *buffp;
|
||||
|
||||
for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
|
||||
if (typestr[i].type == type)
|
||||
{
|
||||
@@ -2176,37 +2277,28 @@ static char *querystr(char *desc, unsigned short type)
|
||||
len += strlen(desc);
|
||||
}
|
||||
len++; /* terminator */
|
||||
|
||||
if (!buff || bufflen < len)
|
||||
{
|
||||
if (buff)
|
||||
free(buff);
|
||||
else if (len < 20)
|
||||
len = 20;
|
||||
|
||||
buff = whine_malloc(len);
|
||||
bufflen = len;
|
||||
}
|
||||
|
||||
if (buff)
|
||||
if (!expand_buf(&buff, len))
|
||||
return "";
|
||||
|
||||
buffp = buff.iov_base;
|
||||
|
||||
if (desc)
|
||||
{
|
||||
if (desc)
|
||||
{
|
||||
if (types)
|
||||
sprintf(buff, "%s[%s]", desc, types);
|
||||
else
|
||||
sprintf(buff, "%s[type=%d]", desc, type);
|
||||
}
|
||||
if (types)
|
||||
sprintf(buffp, "%s[%s]", desc, types);
|
||||
else
|
||||
{
|
||||
if (types)
|
||||
sprintf(buff, "<%s>", types);
|
||||
else
|
||||
sprintf(buff, "<type=%d>", type);
|
||||
}
|
||||
sprintf(buffp, "%s[type=%d]", desc, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (types)
|
||||
sprintf(buffp, "<%s>", types);
|
||||
else
|
||||
sprintf(buffp, "<type=%d>", type);
|
||||
}
|
||||
|
||||
return buff ? buff : "";
|
||||
return buffp;
|
||||
}
|
||||
|
||||
static char *edestr(int ede)
|
||||
@@ -2243,10 +2335,20 @@ static char *edestr(int ede)
|
||||
case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value";
|
||||
case EDE_UNABLE_POLICY: return "uanble to conform to policy";
|
||||
case EDE_SYNTHESIZED: return "synthesized";
|
||||
case EDE_US_SERVFAIL: return "upstream returned SERVFAIL";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static int error_occurred(unsigned int flags) {
|
||||
if (flags & F_RCODE)
|
||||
return 1;
|
||||
else if (flags & F_NEG)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type)
|
||||
{
|
||||
char *source, *dest;
|
||||
@@ -2259,12 +2361,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
|
||||
if (!option_bool(OPT_LOG))
|
||||
return;
|
||||
|
||||
/* F_NOERR is reused here to indicate logs arrising from auth queries */
|
||||
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occurred(flags))
|
||||
return;
|
||||
|
||||
/* F_NOERR is reused here to indicate logs arising from auth queries */
|
||||
if (!(flags & F_NOERR) && option_bool(OPT_AUTH_LOG))
|
||||
return;
|
||||
|
||||
/* build query type string if requested */
|
||||
if (!(flags & (F_SERVER | F_IPSET | F_QUERY)) && type > 0)
|
||||
if (!(flags & (F_SERVER | F_IPSET | F_QUERY | F_KEYTAG | F_RR)) && type > 0)
|
||||
arg = querystr(arg, type);
|
||||
|
||||
dest = arg;
|
||||
@@ -2280,19 +2385,25 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
|
||||
{
|
||||
dest = daemon->addrbuff;
|
||||
|
||||
if (flags & F_RR)
|
||||
{
|
||||
if (flags & F_KEYTAG)
|
||||
dest = querystr(NULL, addr->rrblock.rrtype);
|
||||
else
|
||||
dest = querystr(NULL, addr->rrdata.rrtype);
|
||||
}
|
||||
else if (flags & F_KEYTAG)
|
||||
sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
|
||||
if (flags & F_RR)
|
||||
{
|
||||
if (flags & F_KEYTAG)
|
||||
dest = querystr(NULL, addr->rrblock.rrtype);
|
||||
else
|
||||
dest = querystr(NULL, addr->rrdata.rrtype);
|
||||
}
|
||||
#ifdef HAVE_DNSSEC
|
||||
else if (flags & F_KEYTAG)
|
||||
{
|
||||
snprintf(daemon->addrbuff, ADDRSTRLEN, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
|
||||
if (type)
|
||||
extra = " (not supported)";
|
||||
}
|
||||
#endif
|
||||
else if (flags & F_RCODE)
|
||||
{
|
||||
unsigned int rcode = addr->log.rcode;
|
||||
|
||||
|
||||
if (rcode == SERVFAIL)
|
||||
dest = "SERVFAIL";
|
||||
else if (rcode == REFUSED)
|
||||
|
||||
+15
-6
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
#define FTABSIZ 150 /* max number of outstanding requests (default) */
|
||||
#define MAX_PROCS 20 /* default max no children for TCP requests */
|
||||
#define MAX_PROCS 30 /* default max no children for TCP requests */
|
||||
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
|
||||
#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
|
||||
#define TCP_TIMEOUT 5 /* timeout waiting to connect to an upstream server - double this for answer */
|
||||
#define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */
|
||||
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
|
||||
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
|
||||
#define NAMEBLOCK_CHARS 1500 /* quantum of memory allocation for names from /etc/hosts */
|
||||
#define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
|
||||
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
|
||||
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
|
||||
@@ -42,7 +43,7 @@
|
||||
#define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */
|
||||
#define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */
|
||||
#define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */
|
||||
#define SMALLDNAME 50 /* most domain names are smaller than this */
|
||||
#define SMALLDNAME 75 /* most domain names are smaller than this */
|
||||
#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */
|
||||
#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */
|
||||
#define HOSTSFILE "/etc/hosts"
|
||||
@@ -142,7 +143,8 @@ HAVE_LOOP
|
||||
include functionality to probe for and remove DNS forwarding loops.
|
||||
|
||||
HAVE_INOTIFY
|
||||
use the Linux inotify facility to efficiently re-read configuration files.
|
||||
use the Linux and FreeBSD >= 15 inotify facility
|
||||
to efficiently re-read configuration files.
|
||||
|
||||
NO_ID
|
||||
Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
|
||||
@@ -375,8 +377,15 @@ HAVE_SOCKADDR_SA_LEN
|
||||
#undef HAVE_DUMPFILE
|
||||
#endif
|
||||
|
||||
#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY)
|
||||
#define HAVE_INOTIFY
|
||||
#if !defined(NO_INOTIFY)
|
||||
# if defined (HAVE_LINUX_NETWORK)
|
||||
# define HAVE_INOTIFY
|
||||
# elif defined (__FreeBSD__) && __FreeBSD__ + 0 >= 15
|
||||
# include <osreldate.h>
|
||||
# if __FreeBSD_version >= 1500068 /* 15.0.0 */
|
||||
# define HAVE_INOTIFY
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* This never compiles code, it's only used by the makefile to fingerprint builds. */
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+27
-8
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -93,7 +93,15 @@ static void null_hash_update(void *ctxv, size_t length, const uint8_t *src)
|
||||
memcpy(null_hash_buff + ctx->len, src, length);
|
||||
ctx->len += length;
|
||||
}
|
||||
|
||||
|
||||
/* The prototype changes in nettle 4.0 to omit the length argument */
|
||||
#if MIN_VERSION(4, 0)
|
||||
static void null_hash_digest(void *ctx, uint8_t *dst)
|
||||
{
|
||||
((struct null_hash_digest *)dst)->buff = null_hash_buff;
|
||||
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
|
||||
}
|
||||
#else
|
||||
static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
|
||||
{
|
||||
(void)length;
|
||||
@@ -101,6 +109,7 @@ static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
|
||||
((struct null_hash_digest *)dst)->buff = null_hash_buff;
|
||||
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct nettle_hash null_hash = {
|
||||
"null_hash",
|
||||
@@ -126,20 +135,18 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges
|
||||
|
||||
if (ctx_sz < hash->context_size)
|
||||
{
|
||||
if (!(new = whine_malloc(hash->context_size)))
|
||||
if (!(new = whine_realloc(ctx, hash->context_size)))
|
||||
return 0;
|
||||
if (ctx)
|
||||
free(ctx);
|
||||
|
||||
ctx = new;
|
||||
ctx_sz = hash->context_size;
|
||||
}
|
||||
|
||||
if (digest_sz < hash->digest_size)
|
||||
{
|
||||
if (!(new = whine_malloc(hash->digest_size)))
|
||||
if (!(new = whine_realloc(digest, hash->digest_size)))
|
||||
return 0;
|
||||
if (digest)
|
||||
free(digest);
|
||||
|
||||
digest = new;
|
||||
digest_sz = hash->digest_size;
|
||||
}
|
||||
@@ -503,4 +510,16 @@ const struct nettle_hash *hash_find(char *name)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The prototype changes in nettle 4.0 to omit the length argument */
|
||||
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst)
|
||||
{
|
||||
#if MIN_VERSION(4, 0)
|
||||
(void)length;
|
||||
hash->digest(ctx, dst);
|
||||
#else
|
||||
hash->digest(ctx, length, dst);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#endif /* defined(HAVE_DNSSEC) */
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -736,7 +736,7 @@ static void add_dict_entry(DBusMessageIter *container, const char *key, const ch
|
||||
|
||||
static void add_dict_int(DBusMessageIter *container, const char *key, const unsigned int val)
|
||||
{
|
||||
snprintf(daemon->namebuff, MAXDNAME, "%u", val);
|
||||
snprintf(daemon->namebuff, MAXDNAMESTR, "%u", val);
|
||||
|
||||
add_dict_entry(container, key, daemon->namebuff);
|
||||
}
|
||||
@@ -1055,7 +1055,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
|
||||
DBusConnection *connection = (DBusConnection *)daemon->dbus;
|
||||
DBusMessage* message = NULL;
|
||||
DBusMessageIter args;
|
||||
char *action_str, *mac = daemon->namebuff;
|
||||
char *action_str, *mac;
|
||||
unsigned char *p;
|
||||
int i;
|
||||
|
||||
@@ -1068,7 +1068,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
|
||||
#ifdef HAVE_DHCP6
|
||||
if (lease->flags & (LEASE_TA | LEASE_NA))
|
||||
{
|
||||
print_mac(mac, lease->clid, lease->clid_len);
|
||||
mac = print_mac(lease->clid, lease->clid_len);
|
||||
inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
|
||||
}
|
||||
else
|
||||
@@ -1076,7 +1076,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
|
||||
{
|
||||
p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len,
|
||||
lease->hwaddr, lease->clid_len, lease->clid, &i);
|
||||
print_mac(mac, p, i);
|
||||
mac = print_mac(p, i);
|
||||
inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
|
||||
}
|
||||
|
||||
|
||||
+45
-15
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -278,9 +278,9 @@ void log_tags(struct dhcp_netid *netid, u32 xid)
|
||||
|
||||
if (!n)
|
||||
{
|
||||
strncat (s, netid->net, (MAXDNAME-1) - strlen(s));
|
||||
strncat (s, netid->net, MAXDNAMESTR - strlen(s));
|
||||
if (netid->next)
|
||||
strncat (s, ", ", (MAXDNAME-1) - strlen(s));
|
||||
strncat (s, ", ", MAXDNAMESTR - strlen(s));
|
||||
}
|
||||
}
|
||||
my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), xid, s);
|
||||
@@ -734,7 +734,7 @@ static const struct opttab_t opttab6[] = {
|
||||
{ "status", 13, OT_INTERNAL },
|
||||
{ "rapid-commit", 14, OT_INTERNAL },
|
||||
{ "user-class", 15, OT_INTERNAL | OT_CSTRING },
|
||||
{ "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
|
||||
{ "vendor-class", 16, OT_DHCP6_VENDOR },
|
||||
{ "vendor-opts", 17, OT_INTERNAL },
|
||||
{ "sip-server-domain", 21, OT_RFC1035_NAME },
|
||||
{ "sip-server", 22, OT_ADDR_LIST },
|
||||
@@ -882,7 +882,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
|
||||
buf[j++] = c;
|
||||
}
|
||||
i = l;
|
||||
if (val[i] != 0 && j < buf_len)
|
||||
if (i < opt_len && val[i] != 0 && j < buf_len)
|
||||
buf[j++] = '.';
|
||||
}
|
||||
}
|
||||
@@ -892,24 +892,54 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
|
||||
unsigned char *p;
|
||||
|
||||
i = 0, j = 0;
|
||||
while (1)
|
||||
while (i + 2 <= opt_len)
|
||||
{
|
||||
p = &val[i];
|
||||
GETSHORT(len, p);
|
||||
if (i + 2 + len > opt_len)
|
||||
break; /* malformed: body extends beyond option */
|
||||
for (k = 0; k < len && j < buf_len; k++)
|
||||
{
|
||||
char c = *p++;
|
||||
if (isprint((unsigned char)c))
|
||||
buf[j++] = c;
|
||||
}
|
||||
i += len +2;
|
||||
if (i >= opt_len)
|
||||
break;
|
||||
|
||||
if (j < buf_len)
|
||||
i += len + 2;
|
||||
if (i < opt_len && j < buf_len)
|
||||
buf[j++] = ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((ot[o].size & OT_DHCP6_VENDOR))
|
||||
{
|
||||
unsigned int enterprise;
|
||||
unsigned char *p = &val[0];
|
||||
|
||||
if (opt_len >= 4)
|
||||
{
|
||||
GETLONG(enterprise, p);
|
||||
snprintf(buf, buf_len, "%u", enterprise);
|
||||
j = strlen(buf);
|
||||
i = 4;
|
||||
while (i + 2 <= opt_len)
|
||||
{
|
||||
int k, len;
|
||||
p = &val[i];
|
||||
GETSHORT(len, p);
|
||||
if (i + 2 + len > opt_len)
|
||||
break;
|
||||
if (j < buf_len - 1)
|
||||
buf[j++] = ',';
|
||||
for (k = 0; k < len && j < buf_len - 1; k++)
|
||||
{
|
||||
char c = *p++;
|
||||
if (isprint((unsigned char)c))
|
||||
buf[j++] = c;
|
||||
}
|
||||
buf[j] = 0;
|
||||
i += len + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
|
||||
{
|
||||
@@ -937,11 +967,11 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
|
||||
trunc = 1;
|
||||
opt_len = 14;
|
||||
}
|
||||
print_mac(buf, val, opt_len);
|
||||
|
||||
*buf = 0;
|
||||
strncat(buf, print_mac(val, opt_len), buf_len);
|
||||
if (trunc)
|
||||
strncat(buf, "...", buf_len - strlen(buf));
|
||||
|
||||
|
||||
}
|
||||
|
||||
return ot[o].name ? ot[o].name : "";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+10
-9
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -399,7 +399,7 @@ void dhcp_packet(time_t now, int pxe_fd)
|
||||
if (mess->ciaddr.s_addr != 0)
|
||||
dest.sin_addr = mess->ciaddr;
|
||||
}
|
||||
if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
|
||||
else if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
|
||||
{
|
||||
/* Send to BOOTP relay. */
|
||||
if (is_relay_use_source)
|
||||
@@ -925,7 +925,8 @@ void dhcp_read_ethers(void)
|
||||
{
|
||||
FILE *f = fopen(ETHERSFILE, "r");
|
||||
unsigned int flags;
|
||||
char *buff = daemon->namebuff;
|
||||
size_t linesz = 0;
|
||||
char *line = NULL;
|
||||
char *ip, *cp;
|
||||
struct in_addr addr;
|
||||
unsigned char hwaddr[ETHER_ADDR_LEN];
|
||||
@@ -958,22 +959,22 @@ void dhcp_read_ethers(void)
|
||||
up = &config->next;
|
||||
}
|
||||
|
||||
while (fgets(buff, MAXDNAME, f))
|
||||
while (get_line_alloc(f, &line, &linesz))
|
||||
{
|
||||
char *host = NULL;
|
||||
|
||||
lineno++;
|
||||
|
||||
while (strlen(buff) > 0 && isspace((unsigned char)buff[strlen(buff)-1]))
|
||||
buff[strlen(buff)-1] = 0;
|
||||
while (strlen(line) > 0 && isspace((unsigned char)line[strlen(line)-1]))
|
||||
line[strlen(line)-1] = 0;
|
||||
|
||||
if ((*buff == '#') || (*buff == '+') || (*buff == 0))
|
||||
if ((*line == '#') || (*line == '+') || (*line == 0))
|
||||
continue;
|
||||
|
||||
for (ip = buff; *ip && !isspace((unsigned char)*ip); ip++);
|
||||
for (ip = line; *ip && !isspace((unsigned char)*ip); ip++);
|
||||
for(; *ip && isspace((unsigned char)*ip); ip++)
|
||||
*ip = 0;
|
||||
if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
|
||||
if (!*ip || parse_hex(line, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
|
||||
{
|
||||
my_syslog(MS_DHCP | LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno);
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -720,7 +720,7 @@ static int construct_worker(struct in6_addr *local, int prefix,
|
||||
return 1;
|
||||
|
||||
for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
|
||||
if (tmp->name && wildcard_match(tmp->name, ifrn_name))
|
||||
if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, ifrn_name))
|
||||
return 1;
|
||||
|
||||
for (template = daemon->dhcp6; template; template = template->next)
|
||||
|
||||
+32
-3
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -22,8 +22,34 @@
|
||||
#define IN6ADDRSZ 16
|
||||
#define INADDRSZ 4
|
||||
|
||||
/* Dnsmasq handles domains names internally as NULL-terminated C strings.
|
||||
The '.' characters in these strings are significant as label-seperators.
|
||||
'.' and /0 characters _within_ labels are escaped and take up two
|
||||
characters to allow labels to contain arbitrary characters.
|
||||
|
||||
The maximum length of a domain name in wire format is 255 bytes.
|
||||
and the maximum length of this when coverted to a <label>.<label> string
|
||||
is 253 characters. Reasoning: the wire format representation of a
|
||||
label is one byte length followed by up to length characters so each of
|
||||
these labels except the last takes the same space since the length is
|
||||
replaced by the '.' seperator.
|
||||
The last label doesn't have the "." so it takes one less character
|
||||
and the terminating zero-length label takes no character either.
|
||||
|
||||
When we introduce the dnsmasq escaped format, the maximum length
|
||||
of this representation is when there are the minimum number of labels,
|
||||
since that gives us the maximum number of characters that may need
|
||||
escaping. The minimum number of labels is four, so the overhead
|
||||
is five bytes (four non-zero lengths plus the zero length terminator)
|
||||
leaving a total of 250 characters in labels. If every one of these
|
||||
is escaped and becomes two characters, that gives 500 characters,
|
||||
plus the three '.' seperators between the four labels, for a total of
|
||||
503 characters.
|
||||
*/
|
||||
#define MAXDNAME 255 /* Maximum size of a domain name in wire format */
|
||||
#define MAXDNAMESTR 503 /* Maximum size of dnsmasq c-string representaion of a domain name. */
|
||||
|
||||
#define PACKETSZ 512 /* maximum packet size */
|
||||
#define MAXDNAME 1025 /* maximum presentation domain name */
|
||||
#define RRFIXEDSZ 10 /* #/bytes of fixed data in r record */
|
||||
#define MAXLABEL 63 /* maximum length of domain label */
|
||||
|
||||
@@ -86,6 +112,7 @@
|
||||
#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */
|
||||
|
||||
/* RFC-8914 extended errors, negative values are our definitions */
|
||||
#define EDE_US_SERVFAIL -2 /* SERVFAIL from usptream */
|
||||
#define EDE_UNSET -1 /* No extended DNS error available */
|
||||
#define EDE_OTHER 0 /* Other */
|
||||
#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */
|
||||
@@ -184,10 +211,12 @@ struct dns_header {
|
||||
#define ADD_RDLEN(header, pp, plen, len) \
|
||||
(!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
|
||||
|
||||
/* Escape character in our presentation format for names.
|
||||
/* Escape character in our internal c-string format for names.
|
||||
Cannot be '.' or /000 and must be !isprint().
|
||||
Note that escaped chars are stored as
|
||||
<NAME_ESCAPE> <orig-char+1>
|
||||
to ensure that the escaped form of /000 doesn't include /000
|
||||
*/
|
||||
#define NAME_ESCAPE 1
|
||||
#define IS_NAME_ESCAPE(x) ((x) == 0 || (x) == '.' || (x) == NAME_ESCAPE)
|
||||
|
||||
+265
-137
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -135,16 +135,14 @@ int main (int argc, char **argv)
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
/* Note that both /000 and '.' are allowed within labels. These get
|
||||
represented in presentation format using NAME_ESCAPE as an escape
|
||||
character. In theory, if all the characters in a name were /000 or
|
||||
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
||||
presentation format would be twice as long as the spec. */
|
||||
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
|
||||
daemon->cname = safe_malloc((MAXDNAME * 2) + 1);
|
||||
daemon->keyname = safe_malloc(MAXDNAMESTR + 1);
|
||||
daemon->cname = safe_malloc(MAXDNAMESTR + 1);
|
||||
/* one char flag per possible RR in answer section (may get extended). */
|
||||
daemon->rr_status_sz = 64;
|
||||
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
|
||||
/* Increase TCP process slots by half, and reserve the extra for validation-to-tcp use only. */
|
||||
daemon->dnssec_tcp_slot_reserved = daemon->max_procs/2;
|
||||
daemon->max_procs += daemon->dnssec_tcp_slot_reserved;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -199,7 +197,7 @@ int main (int argc, char **argv)
|
||||
/* Must have at least a root trust anchor, or the DNSSEC code
|
||||
can loop forever. */
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
if (ds->name[0] == 0)
|
||||
if (ds->name && ds->name[0] == 0)
|
||||
break;
|
||||
|
||||
if (!ds)
|
||||
@@ -432,19 +430,12 @@ int main (int argc, char **argv)
|
||||
|
||||
daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t));
|
||||
daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int));
|
||||
|
||||
daemon->tcp_type = safe_malloc(daemon->max_procs);
|
||||
|
||||
for (i = 0; i < daemon->max_procs; i++)
|
||||
daemon->tcp_pipes[i] = -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_INOTIFY
|
||||
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
|
||||
daemon->dynamic_dirs)
|
||||
inotify_dnsmasq_init();
|
||||
else
|
||||
daemon->inotifyfd = -1;
|
||||
#endif
|
||||
|
||||
if (daemon->dump_file)
|
||||
#ifdef HAVE_DUMPFILE
|
||||
dump_init();
|
||||
@@ -864,6 +855,17 @@ int main (int argc, char **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_INOTIFY
|
||||
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
|
||||
daemon->dynamic_dirs)
|
||||
inotify_dnsmasq_init(err_pipe[1]);
|
||||
else
|
||||
daemon->inotifyfd = -1;
|
||||
#endif
|
||||
|
||||
/* Don't start logging malloc before logging is set up. */
|
||||
daemon->log_malloc = option_bool(OPT_LOG_MALLOC);
|
||||
|
||||
if (daemon->port == 0)
|
||||
my_syslog(LOG_INFO, _("started, version %s DNS disabled"), VERSION);
|
||||
else
|
||||
@@ -943,9 +945,27 @@ int main (int argc, char **argv)
|
||||
my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid"));
|
||||
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
my_syslog(LOG_INFO,
|
||||
ds->digestlen == 0 ? _("configured with negative trust anchor for %s") : _("configured with trust anchor for %s keytag %u"),
|
||||
ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
|
||||
{
|
||||
struct ds_config *ds1;
|
||||
|
||||
for (ds1 = ds->next; ds1; ds1 = ds1->next)
|
||||
if (strcmp(ds->name, ds1->name) == 0 &&
|
||||
ds->digestlen == ds1->digestlen &&
|
||||
(ds->digestlen == 0 ||
|
||||
(ds->algo == ds1->algo &&
|
||||
ds->keytag == ds1->keytag &&
|
||||
ds->digest_type == ds1->digest_type &&
|
||||
memcmp(ds->digest, ds1->digest, ds->digestlen) == 0)))
|
||||
{
|
||||
ds->name = NULL; /* Mark as duplicate */
|
||||
break;
|
||||
}
|
||||
|
||||
if (ds->name)
|
||||
my_syslog(LOG_INFO,
|
||||
ds->digestlen == 0 ? _("configured with negative trust anchor for %s") : _("configured with trust anchor for %s keytag %u"),
|
||||
ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1335,11 +1355,40 @@ static void sig_handler(int sig)
|
||||
if (sig == SIGTERM || sig == SIGINT)
|
||||
exit(EC_MISC);
|
||||
}
|
||||
else if (pid != getpid())
|
||||
else if (daemon->pipe_to_parent != -1)
|
||||
{
|
||||
/* alarm is used to kill TCP children after a fixed time. */
|
||||
if (sig == SIGALRM)
|
||||
_exit(0);
|
||||
{
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (!daemon->forward_to_tcp)
|
||||
#endif
|
||||
_exit(0); /* Normal TCP child */
|
||||
#ifdef HAVE_DNSSEC
|
||||
else
|
||||
{
|
||||
/* udp_to_tcp transfer.
|
||||
If daemon->header_to_tcp is NULL the waiting is over and
|
||||
we can let things take their course, otherwise, send a failure
|
||||
return down the pipe to unblock the UDP transaction and kill
|
||||
the process. */
|
||||
if (daemon->header_to_tcp)
|
||||
{
|
||||
unsigned char op = PIPE_OP_KILLED;
|
||||
int status = STAT_ABANDONED;
|
||||
|
||||
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
|
||||
read_write(daemon->pipe_to_parent, (unsigned char *)&status, sizeof(status), RW_WRITE);
|
||||
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->plen_to_tcp), sizeof(daemon->plen_to_tcp), RW_WRITE);
|
||||
read_write(daemon->pipe_to_parent, (unsigned char *)(daemon->header_to_tcp), daemon->plen_to_tcp, RW_WRITE);
|
||||
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->forward_to_tcp), sizeof(daemon->forward_to_tcp), RW_WRITE);
|
||||
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->forward_to_tcp->uid), sizeof(daemon->forward_to_tcp->uid), RW_WRITE);
|
||||
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1416,8 +1465,6 @@ void send_event(int fd, int event, int data, char *msg)
|
||||
while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR);
|
||||
}
|
||||
|
||||
/* NOTE: the memory used to return msg is leaked: use msgs in events only
|
||||
to describe fatal errors. */
|
||||
static int read_event(int fd, struct event_desc *evp, char **msg)
|
||||
{
|
||||
char *buf;
|
||||
@@ -1427,12 +1474,22 @@ static int read_event(int fd, struct event_desc *evp, char **msg)
|
||||
|
||||
*msg = NULL;
|
||||
|
||||
if (evp->msg_sz != 0 &&
|
||||
(buf = malloc(evp->msg_sz + 1)) &&
|
||||
read_write(fd, (unsigned char *)buf, evp->msg_sz, RW_READ))
|
||||
if (evp->msg_sz != 0)
|
||||
{
|
||||
buf[evp->msg_sz] = 0;
|
||||
*msg = buf;
|
||||
if (!(buf = whine_malloc(evp->msg_sz + 1)))
|
||||
{
|
||||
int i;
|
||||
unsigned char a;
|
||||
|
||||
/* Keep the stream synchronised if malloc fails. */
|
||||
for (i = 0; i < evp->msg_sz; i++)
|
||||
read_write(fd, &a, 1, RW_READ);
|
||||
}
|
||||
else if (read_write(fd, (unsigned char *)buf, evp->msg_sz, RW_READ))
|
||||
{
|
||||
buf[evp->msg_sz] = 0;
|
||||
*msg = buf;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -1485,6 +1542,26 @@ static void fatal_event(struct event_desc *ev, char *msg)
|
||||
/* fall through */
|
||||
case EVENT_TIME_ERR:
|
||||
die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF);
|
||||
|
||||
/* fall through */
|
||||
case EVENT_LINK_ERR:
|
||||
die(_("cannot access path %s: %s" ), msg, EC_MISC);
|
||||
|
||||
/* fall through */
|
||||
case EVENT_INOTFY_ERR:
|
||||
die(_("failed to create inotify: %s" ), NULL, EC_MISC);
|
||||
|
||||
/* fall through */
|
||||
case EVENT_TMSL_ERR:
|
||||
die(_("too many symlinks following %s"), msg, EC_MISC);
|
||||
|
||||
/* fall through */
|
||||
case EVENT_RESOLV_ERR:
|
||||
die(_("directory %s for resolv-file is missing, cannot poll"), msg, EC_MISC);
|
||||
|
||||
/* fall through */
|
||||
case EVENT_IFILE_ERR:
|
||||
die(_("failed to create inotify for %s: %s"), msg, EC_MISC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,9 +1572,6 @@ static void async_event(int pipe, time_t now)
|
||||
int wstatus, i, check = 0;
|
||||
char *msg;
|
||||
|
||||
/* NOTE: the memory used to return msg is leaked: use msgs in events only
|
||||
to describe fatal errors. */
|
||||
|
||||
if (read_event(pipe, &ev, &msg))
|
||||
switch (ev.event)
|
||||
{
|
||||
@@ -1582,7 +1656,11 @@ static void async_event(int pipe, time_t now)
|
||||
|
||||
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
|
||||
if (daemon->tcp_pipes[i] == -1)
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
|
||||
{
|
||||
if (daemon->tcp_type[i])
|
||||
daemon->dnssec_tcp_slot_reserved++;
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1603,7 +1681,6 @@ static void async_event(int pipe, time_t now)
|
||||
case EVENT_SCRIPT_LOG:
|
||||
my_syslog(MS_SCRIPT | LOG_DEBUG, "%s", msg ? msg : "");
|
||||
free(msg);
|
||||
msg = NULL;
|
||||
break;
|
||||
|
||||
/* necessary for fatal errors in helper */
|
||||
@@ -1819,7 +1896,7 @@ static void set_dns_listeners(void)
|
||||
struct serverfd *serverfdp;
|
||||
struct listener *listener;
|
||||
struct randfd_list *rfl;
|
||||
int i;
|
||||
int i, j;
|
||||
|
||||
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
|
||||
poll_listen(serverfdp->fd, POLLIN);
|
||||
@@ -1833,8 +1910,8 @@ static void set_dns_listeners(void)
|
||||
poll_listen(rfl->rfd->fd, POLLIN);
|
||||
|
||||
/* check to see if we have free tcp process slots. */
|
||||
for (i = daemon->max_procs - 1; i >= 0; i--)
|
||||
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
|
||||
for (j = 0, i = daemon->max_procs - 1; i >= 0; i--)
|
||||
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
|
||||
break;
|
||||
|
||||
for (listener = daemon->listeners; listener; listener = listener->next)
|
||||
@@ -1861,12 +1938,12 @@ static void check_dns_listeners(time_t now)
|
||||
struct serverfd *serverfdp;
|
||||
struct listener *listener;
|
||||
struct randfd_list *rfl;
|
||||
int i;
|
||||
int i, j;
|
||||
|
||||
/* Note that handling events here can create or destroy fds and
|
||||
render the result of the last poll() call invalid. Once
|
||||
we find an fd that needs service, do it, then return to go around the
|
||||
poll() loop again. This avoid really, really, wierd bugs. */
|
||||
poll() loop again. This avoid really, really, weird bugs. */
|
||||
|
||||
if (!option_bool(OPT_DEBUG))
|
||||
for (i = 0; i < daemon->max_procs; i++)
|
||||
@@ -1886,7 +1963,11 @@ static void check_dns_listeners(time_t now)
|
||||
daemon->tcp_pipes[i] = -1;
|
||||
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
|
||||
if (daemon->tcp_pids[i] == 0)
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
|
||||
{
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
|
||||
if (daemon->tcp_type[i])
|
||||
daemon->dnssec_tcp_slot_reserved++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1926,8 +2007,8 @@ static void check_dns_listeners(time_t now)
|
||||
at least one a poll() time, that we still do.
|
||||
There may be more waiting connections after
|
||||
poll() returns then free process slots. */
|
||||
for (i = daemon->max_procs - 1; i >= 0; i--)
|
||||
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
|
||||
for (j = 0,i = daemon->max_procs - 1; i >= 0; i--)
|
||||
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
|
||||
break;
|
||||
|
||||
if (i >= 0)
|
||||
@@ -1941,24 +2022,37 @@ static void check_dns_listeners(time_t now)
|
||||
|
||||
static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
||||
{
|
||||
int confd, client_ok = 1;
|
||||
int confd;
|
||||
struct irec *iface = NULL;
|
||||
pid_t p;
|
||||
union mysockaddr tcp_addr;
|
||||
socklen_t tcp_len = sizeof(union mysockaddr);
|
||||
unsigned char *buff;
|
||||
struct server *s;
|
||||
int flags, auth_dns;
|
||||
struct server *s;
|
||||
struct listener *l;
|
||||
int flags, auth_dns = 0;
|
||||
struct in_addr netmask;
|
||||
int pipefd[2];
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
struct iovec tcpbuff;
|
||||
unsigned char a = 0;
|
||||
#endif
|
||||
|
||||
netmask.s_addr = 0;
|
||||
|
||||
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
|
||||
|
||||
if (confd == -1)
|
||||
return;
|
||||
{
|
||||
/* Transient errors: just return and retry on next poll cycle. */
|
||||
if (errno == EAGAIN || errno == ECONNABORTED ||
|
||||
errno == EMFILE || errno == ENFILE ||
|
||||
errno == ENOMEM || errno == ENOBUFS)
|
||||
return;
|
||||
|
||||
/* Fatal error (EINVAL, EBADF, etc): socket is permanently broken.
|
||||
Close it so poll() no longer selects it. In --bind-dynamic mode
|
||||
the listener will be rebuilt on the next address change event. */
|
||||
close(listener->tcpfd);
|
||||
listener->tcpfd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
|
||||
{
|
||||
@@ -1981,58 +2075,63 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
||||
|
||||
enumerate_interfaces(0);
|
||||
|
||||
if (option_bool(OPT_NOWILD))
|
||||
iface = listener->iface; /* May be NULL */
|
||||
if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
|
||||
{
|
||||
if ((iface = listener->iface))
|
||||
{
|
||||
netmask = iface->netmask;
|
||||
auth_dns = iface->dns_auth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int if_index;
|
||||
int if_index, got_index = 0;
|
||||
char intr_name[IF_NAMESIZE];
|
||||
|
||||
/* if we can find the arrival interface, check it's one that's allowed */
|
||||
/* if we can find the arrival interface, check it's one that's allowed
|
||||
tcp_interface() is not implemented on non-Linux platforms */
|
||||
if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
|
||||
indextoname(listener->tcpfd, if_index, intr_name))
|
||||
{
|
||||
union all_addr addr;
|
||||
|
||||
got_index = 1;
|
||||
|
||||
if (tcp_addr.sa.sa_family == AF_INET6)
|
||||
addr.addr6 = tcp_addr.in6.sin6_addr;
|
||||
else
|
||||
addr.addr4 = tcp_addr.in.sin_addr;
|
||||
|
||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||
if (iface->index == if_index &&
|
||||
iface->addr.sa.sa_family == tcp_addr.sa.sa_family)
|
||||
break;
|
||||
|
||||
if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
|
||||
client_ok = 0;
|
||||
if (!iface_check(tcp_addr.sa.sa_family, &addr, intr_name, &auth_dns) &&
|
||||
!loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
|
||||
goto closeconandreturn;
|
||||
}
|
||||
|
||||
if (option_bool(OPT_CLEVERBIND))
|
||||
iface = listener->iface; /* May be NULL */
|
||||
else
|
||||
/* When binding the wildcard address, try and get the
|
||||
netmask of the interface for localisation. */
|
||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||
if (sockaddr_isequal(&iface->addr, &tcp_addr))
|
||||
{
|
||||
netmask = iface->netmask;
|
||||
break;
|
||||
}
|
||||
|
||||
/* On platforms where tcp_interface() doesn't work, we rely
|
||||
of the presence of the local address of the connection in the
|
||||
interface list to check if we're accepting this connection and
|
||||
for its auth status. */
|
||||
if (!got_index)
|
||||
{
|
||||
/* Check for allowed interfaces when binding the wildcard address:
|
||||
we do this by looking for an interface with the same address as
|
||||
the local address of the TCP connection, then looking to see if that's
|
||||
an allowed interface. As a side effect, we get the netmask of the
|
||||
interface too, for localisation. */
|
||||
|
||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||
if (sockaddr_isequal(&iface->addr, &tcp_addr))
|
||||
break;
|
||||
|
||||
if (!iface)
|
||||
client_ok = 0;
|
||||
goto closeconandreturn;
|
||||
|
||||
auth_dns = iface->dns_auth;
|
||||
}
|
||||
}
|
||||
|
||||
if (!client_ok)
|
||||
goto closeconandreturn;
|
||||
|
||||
if (!option_bool(OPT_DEBUG))
|
||||
{
|
||||
/* The code in edns0.c qthat decorates queries with the source MAC address depends
|
||||
/* The code in edns0.c that decorates queries with the source MAC address depends
|
||||
on the code in arp.c, which populates a cache with the contents of the ARP table
|
||||
using netlink. Since the child process can't use netlink, we pre-populate
|
||||
the cache with the ARP table entry for our source here, including a negative entry
|
||||
@@ -2063,27 +2162,22 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
||||
/* fork() done: parent side */
|
||||
close(pipefd[1]); /* parent needs read pipe end. */
|
||||
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
/* The child process inherits the netlink socket,
|
||||
which it never uses, but when the parent (us)
|
||||
uses it in the future, the answer may go to the
|
||||
child, resulting in the parent blocking
|
||||
forever awaiting the result. To avoid this
|
||||
the child closes the netlink socket, but there's
|
||||
a nasty race, since the parent may use netlink
|
||||
before the child has done the close.
|
||||
/* The child process inherits various sockets,
|
||||
whose existence can mess up the function of
|
||||
the parent. See below for details of which
|
||||
sockets, and why they cause problems. The
|
||||
child closes the sockets during startup,
|
||||
but there's a nasty race, since the parent
|
||||
may use them before the child has done the close.
|
||||
|
||||
To avoid this, the parent blocks here until a
|
||||
To avoid the race, the parent blocks here until a
|
||||
single byte comes back up the pipe, which
|
||||
is sent by the child after it has closed the
|
||||
netlink socket. */
|
||||
|
||||
is sent when the child has finished the close. */
|
||||
read_write(pipefd[0], &a, 1, RW_READ);
|
||||
#endif
|
||||
|
||||
|
||||
daemon->tcp_pids[slot] = p;
|
||||
daemon->tcp_pipes[slot] = pipefd[0];
|
||||
daemon->tcp_type[slot] = 0;
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
|
||||
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
|
||||
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
|
||||
@@ -2100,43 +2194,52 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (iface)
|
||||
{
|
||||
netmask = iface->netmask;
|
||||
auth_dns = iface->dns_auth;
|
||||
}
|
||||
else
|
||||
{
|
||||
netmask.s_addr = 0;
|
||||
auth_dns = 0;
|
||||
}
|
||||
|
||||
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
|
||||
terminate the process. */
|
||||
if (!option_bool(OPT_DEBUG))
|
||||
{
|
||||
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
/* See comment above re: netlink socket. */
|
||||
/* The child process inherits the netlink socket,
|
||||
which it never uses, but when the parent
|
||||
uses it in the future, the answer may go to the
|
||||
child, resulting in the parent blocking
|
||||
forever awaiting the result. To avoid this
|
||||
close the netlink socket in the child. */
|
||||
close(daemon->netlinkfd);
|
||||
#elif defined(HAVE_BSD_NETWORK)
|
||||
close(daemon->routefd);
|
||||
#endif
|
||||
|
||||
/* Close inherited listening sockets in the child process.
|
||||
These are not needed here and holding them prevents the
|
||||
parent from re-binding if an interface is removed and
|
||||
re-added (the child's copy causes EADDRINUSE). */
|
||||
for (l = daemon->listeners; l; l = l->next)
|
||||
{
|
||||
if (l->fd != -1)
|
||||
close(l->fd);
|
||||
if (l->tcpfd != -1)
|
||||
close(l->tcpfd);
|
||||
if (l->tftpfd != -1)
|
||||
close(l->tftpfd);
|
||||
}
|
||||
|
||||
/* unblock parent now we're not holding sockets we shouldn't. */
|
||||
read_write(pipefd[1], &a, 1, RW_WRITE);
|
||||
#endif
|
||||
|
||||
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
|
||||
terminate the process. */
|
||||
alarm(CHILD_LIFETIME);
|
||||
close(pipefd[0]); /* close read end in child. */
|
||||
daemon->pipe_to_parent = pipefd[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* The connected socket inherits non-blocking
|
||||
attribute from the listening socket.
|
||||
Reset that here. */
|
||||
if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
|
||||
while(retry_send(fcntl(confd, F_SETFL, flags & ~O_NONBLOCK)));
|
||||
|
||||
buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
|
||||
|
||||
if (buff)
|
||||
free(buff);
|
||||
tcp_request(confd, now, &tcpbuff, &tcp_addr, netmask, auth_dns);
|
||||
free(tcpbuff.iov_base);
|
||||
|
||||
for (s = daemon->servers; s; s = s->next)
|
||||
if (s->tcpfd != -1)
|
||||
@@ -2173,14 +2276,13 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
|
||||
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
|
||||
{
|
||||
struct server *s;
|
||||
struct listener *l;
|
||||
|
||||
if (!option_bool(OPT_DEBUG))
|
||||
{
|
||||
pid_t p;
|
||||
int i, pipefd[2];
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
unsigned char a = 0;
|
||||
#endif
|
||||
|
||||
/* check to see if we have a free tcp process slot. */
|
||||
for (i = daemon->max_procs - 1; i >= 0; i--)
|
||||
@@ -2201,26 +2303,24 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
|
||||
return STAT_ABANDONED;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
/* The child process inherits the netlink socket,
|
||||
which it never uses, but when the parent (us)
|
||||
uses it in the future, the answer may go to the
|
||||
child, resulting in the parent blocking
|
||||
forever awaiting the result. To avoid this
|
||||
the child closes the netlink socket, but there's
|
||||
a nasty race, since the parent may use netlink
|
||||
before the child has done the close.
|
||||
/* The child process inherits various sockets,
|
||||
whose existence can mess up the function of
|
||||
the parent. See below for details of which
|
||||
sockets, and why they cause problems. The
|
||||
child closes the sockets during startup,
|
||||
but there's a nasty race, since the parent
|
||||
may use them before the child has done the close.
|
||||
|
||||
To avoid this, the parent blocks here until a
|
||||
To avoid the race, the parent blocks here until a
|
||||
single byte comes back up the pipe, which
|
||||
is sent by the child after it has closed the
|
||||
netlink socket. */
|
||||
is sent when the child has finished the close. */
|
||||
read_write(pipefd[0], &a, 1, RW_READ);
|
||||
#endif
|
||||
|
||||
/* i holds index of free slot */
|
||||
daemon->tcp_pids[i] = p;
|
||||
daemon->tcp_pipes[i] = pipefd[0];
|
||||
daemon->tcp_type[i] = 1;
|
||||
daemon->dnssec_tcp_slot_reserved--;
|
||||
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
|
||||
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
|
||||
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
|
||||
@@ -2235,12 +2335,40 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
|
||||
{
|
||||
/* child starts here. */
|
||||
#ifdef HAVE_LINUX_NETWORK
|
||||
/* See comment above re: netlink socket. */
|
||||
/* The child process inherits the netlink socket,
|
||||
which it never uses, but when the parent
|
||||
uses it in the future, the answer may go to the
|
||||
child, resulting in the parent blocking
|
||||
forever awaiting the result. To avoid this
|
||||
close the netlink socket in the child. */
|
||||
close(daemon->netlinkfd);
|
||||
#elif defined(HAVE_BSD_NETWORK)
|
||||
close(daemon->routefd);
|
||||
#endif
|
||||
|
||||
/* Close inherited listening sockets in the child process.
|
||||
These are not needed here and holding them prevents the
|
||||
parent from re-binding if an interface is removed and
|
||||
re-added (the child's copy causes EADDRINUSE). */
|
||||
for (l = daemon->listeners; l; l = l->next)
|
||||
{
|
||||
if (l->fd != -1)
|
||||
close(l->fd);
|
||||
if (l->tcpfd != -1)
|
||||
close(l->tcpfd);
|
||||
if (l->tftpfd != -1)
|
||||
close(l->tftpfd);
|
||||
}
|
||||
|
||||
/* unblock parent now we're not holding sockets we shouldn't. */
|
||||
read_write(pipefd[1], &a, 1, RW_WRITE);
|
||||
#endif
|
||||
|
||||
alarm(CHILD_LIFETIME);
|
||||
close(pipefd[0]); /* close read end in child. */
|
||||
daemon->pipe_to_parent = pipefd[1];
|
||||
daemon->pipe_to_parent = pipefd[1];
|
||||
daemon->forward_to_tcp = forward;
|
||||
daemon->header_to_tcp = header;
|
||||
daemon->plen_to_tcp = *plen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-28
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -14,7 +14,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define COPYRIGHT "Copyright (c) 2000-2025 Simon Kelley"
|
||||
#define COPYRIGHT "Copyright (c) 2000-2026 Simon Kelley"
|
||||
|
||||
/* We do defines that influence behavior of stdio.h, so complain
|
||||
if included too early. */
|
||||
@@ -201,6 +201,11 @@ struct event_desc {
|
||||
#define EVENT_TIME_ERR 24
|
||||
#define EVENT_SCRIPT_LOG 25
|
||||
#define EVENT_TIME 26
|
||||
#define EVENT_LINK_ERR 27
|
||||
#define EVENT_INOTFY_ERR 28
|
||||
#define EVENT_TMSL_ERR 29
|
||||
#define EVENT_RESOLV_ERR 30
|
||||
#define EVENT_IFILE_ERR 31
|
||||
|
||||
/* Exit codes. */
|
||||
#define EC_GOOD 0
|
||||
@@ -289,7 +294,9 @@ struct event_desc {
|
||||
#define OPT_DO_0x20 75
|
||||
#define OPT_AUTH_LOG 76
|
||||
#define OPT_LEASEQUERY 77
|
||||
#define OPT_LAST 78
|
||||
#define OPT_LOG_ONLY_FAILED 78
|
||||
#define OPT_LOG_MALLOC 79
|
||||
#define OPT_LAST 80
|
||||
|
||||
#define OPTION_BITS (sizeof(unsigned int)*8)
|
||||
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
|
||||
@@ -479,7 +486,7 @@ struct interface_name {
|
||||
};
|
||||
|
||||
union bigname {
|
||||
char name[MAXDNAME];
|
||||
char name[MAXDNAMESTR+1];
|
||||
union bigname *next; /* freelist */
|
||||
};
|
||||
|
||||
@@ -549,6 +556,7 @@ struct crec {
|
||||
#define PIPE_OP_STATS 3 /* Update parent's stats */
|
||||
#define PIPE_OP_IPSET 4 /* Update IPset */
|
||||
#define PIPE_OP_NFTSET 5 /* Update NFTset */
|
||||
#define PIPE_OP_KILLED 6 /* child killed by SIGALARM */
|
||||
|
||||
/* struct sockaddr is not large enough to hold any address,
|
||||
and specifically not big enough to hold an IPv6 address.
|
||||
@@ -715,7 +723,7 @@ struct resolvc {
|
||||
#endif
|
||||
};
|
||||
|
||||
/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
|
||||
/* addn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
|
||||
#define AH_DIR 1
|
||||
#define AH_INACTIVE 2
|
||||
#define AH_WD_DONE 4
|
||||
@@ -760,10 +768,9 @@ struct dyndir {
|
||||
#define STAT_NEED_DS 0x40000
|
||||
#define STAT_NEED_KEY 0x50000
|
||||
#define STAT_TRUNCATED 0x60000
|
||||
#define STAT_SECURE_WILDCARD 0x70000
|
||||
#define STAT_OK 0x80000
|
||||
#define STAT_ABANDONED 0x90000
|
||||
#define STAT_ASYNC 0xa0000
|
||||
#define STAT_OK 0x70000
|
||||
#define STAT_ABANDONED 0x80000
|
||||
#define STAT_ASYNC 0x90000
|
||||
|
||||
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
|
||||
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
|
||||
@@ -777,6 +784,7 @@ struct dyndir {
|
||||
#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
|
||||
#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */
|
||||
#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */
|
||||
#define DNSSEC_FAIL_UPSTREAM 0x1000 /* SERVFAIL from UPSTREAM */
|
||||
|
||||
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
|
||||
|
||||
@@ -826,6 +834,7 @@ struct frec {
|
||||
#define OT_CSTRING 0x0800
|
||||
#define OT_DEC 0x0400
|
||||
#define OT_TIME 0x0200
|
||||
#define OT_DHCP6_VENDOR 0x0100
|
||||
|
||||
/* actions in the daemon->helper RPC */
|
||||
#define ACTION_DEL 1
|
||||
@@ -1162,7 +1171,7 @@ struct dhcp_relay {
|
||||
};
|
||||
|
||||
extern struct daemon {
|
||||
/* datastuctures representing the command-line and
|
||||
/* datastructures representing the command-line and
|
||||
config file arguments. All set (including defaults)
|
||||
in option.c */
|
||||
|
||||
@@ -1205,6 +1214,7 @@ extern struct daemon {
|
||||
int log_fac; /* log facility */
|
||||
char *log_file; /* optional log file */
|
||||
int max_logs; /* queue limit */
|
||||
int log_malloc; /* log malloc/realloc/free */
|
||||
int randport_limit; /* Maximum number of source ports for query. */
|
||||
int cachesize, ftabsize;
|
||||
int port, query_port, min_port, max_port;
|
||||
@@ -1262,15 +1272,18 @@ extern struct daemon {
|
||||
/* globally used stuff for DNS */
|
||||
char *packet; /* packet buffer */
|
||||
int packet_buff_sz; /* size of above */
|
||||
char *namebuff; /* MAXDNAME size buffer */
|
||||
char *namebuff; /* MAXDNAMESTR+1 size buffer */
|
||||
char *workspacename;
|
||||
#ifdef HAVE_DNSSEC
|
||||
char *keyname, *cname; /* MAXDNAME size buffer */
|
||||
char *keyname, *cname; /* MAXDNAMESTR+1 size buffer */
|
||||
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
|
||||
int rr_status_sz;
|
||||
int dnssec_no_time_check;
|
||||
int back_to_the_future;
|
||||
int limit[LIMIT_MAX];
|
||||
struct frec *forward_to_tcp;
|
||||
struct dns_header *header_to_tcp;
|
||||
ssize_t plen_to_tcp;
|
||||
#endif
|
||||
struct frec *frec_list;
|
||||
struct frec_src *free_frec_src;
|
||||
@@ -1283,6 +1296,7 @@ extern struct daemon {
|
||||
int fd_save; /* " " */
|
||||
pid_t *tcp_pids;
|
||||
int *tcp_pipes;
|
||||
char *tcp_type;
|
||||
int pipe_to_parent;
|
||||
int numrrand;
|
||||
struct randfd *randomsocks;
|
||||
@@ -1342,7 +1356,7 @@ extern struct daemon {
|
||||
/* file for packet dumps. */
|
||||
int dumpfd;
|
||||
#endif
|
||||
int max_procs;
|
||||
int max_procs, dnssec_tcp_slot_reserved;
|
||||
uint max_procs_used;
|
||||
} *daemon;
|
||||
|
||||
@@ -1456,13 +1470,14 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
|
||||
|
||||
/* dnssec.c */
|
||||
#ifdef HAVE_DNSSEC
|
||||
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int id, int type);
|
||||
size_t dnssec_generate_query(struct dns_header *header, size_t outlen, char *name, int class, int id, int type);
|
||||
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
|
||||
char *keyname, int class, int *validate_count);
|
||||
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
|
||||
char *keyname, int class, int *validate_count);
|
||||
int cache_neg_ds(char *name, int flags, int class, time_t now, int neg_ttl);
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
|
||||
int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
|
||||
int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_count);
|
||||
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
|
||||
size_t filter_rrsigs(struct dns_header *header, size_t plen);
|
||||
int setup_timestamp(void);
|
||||
@@ -1477,6 +1492,7 @@ int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig,
|
||||
char *ds_digest_name(int digest);
|
||||
char *algo_digest_name(int algo);
|
||||
char *nsec3_digest_name(int digest);
|
||||
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst);
|
||||
|
||||
/* util.c */
|
||||
void rand_init(void);
|
||||
@@ -1486,12 +1502,23 @@ u64 rand64(void);
|
||||
int rr_on_list(struct rrlist *list, unsigned short rr);
|
||||
int legal_hostname(char *name);
|
||||
char *canonicalise(char *in, int *nomem);
|
||||
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
|
||||
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, unsigned 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);
|
||||
void *whine_realloc(void *ptr, size_t size);
|
||||
#define malloc_log(x, y) malloc_log_real(__func__, __LINE__, (x), (y))
|
||||
#define whine_malloc(x) whine_malloc_real(__func__, __LINE__, (x))
|
||||
#define whine_realloc(x, y) whine_realloc_real(NULL, __func__, __LINE__, (x), (y))
|
||||
#define expand_buf(x, y) expand_buf_real(__func__, __LINE__, (x), (y))
|
||||
#define expand_workspace(x, y, z) expand_workspace_real(__func__, __LINE__, (x), (y), (z))
|
||||
#define free(x) free_real(__func__, __LINE__, (x))
|
||||
void malloc_log_real(const char *func, unsigned int line, void *mem, size_t size);
|
||||
void free_real(const char *func, unsigned int line, void *ptr);
|
||||
void *whine_malloc_real(const char *func, unsigned int line, size_t size);
|
||||
void *whine_realloc_real(const char *wrapper, const char *func, unsigned int line, void *ptr, size_t size);
|
||||
int expand_buf_real(const char *func, unsigned int line, struct iovec *iov, size_t size);
|
||||
int expand_workspace_real(const char *func, unsigned int line, unsigned char ***wkspc, int *szp, int new);
|
||||
int get_line_alloc(FILE *f, char **buffp, size_t *sizep);
|
||||
int sa_len(union mysockaddr *addr);
|
||||
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
|
||||
int sockaddr_isnull(const union mysockaddr *s);
|
||||
@@ -1513,9 +1540,9 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
|
||||
unsigned int *wildcard_mask, int *mac_type);
|
||||
int memcmp_masked(unsigned char *a, unsigned char *b, int len,
|
||||
unsigned int mask);
|
||||
int expand_buf(struct iovec *iov, size_t size);
|
||||
char *print_mac(char *buff, unsigned char *mac, int len);
|
||||
char *print_mac(unsigned char *mac, int len);
|
||||
int read_write(int fd, unsigned char *packet, int size, int rw);
|
||||
int read_writev(int fd, struct iovec *iov, int iovcnt, 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);
|
||||
@@ -1558,8 +1585,8 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
|
||||
int class, char *name, struct server *server,
|
||||
int *keycount, int *validatecount);
|
||||
#endif
|
||||
unsigned char *tcp_request(int confd, time_t now,
|
||||
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
|
||||
void tcp_request(int confd, time_t now, struct iovec *bigbuff,
|
||||
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
|
||||
void server_gone(struct server *server);
|
||||
int send_from(int fd, int nowild, char *packet, size_t len,
|
||||
union mysockaddr *to, union all_addr *source,
|
||||
@@ -1890,7 +1917,7 @@ int detect_loop(char *query, int type);
|
||||
|
||||
/* inotify.c */
|
||||
#ifdef HAVE_INOTIFY
|
||||
void inotify_dnsmasq_init(void);
|
||||
void inotify_dnsmasq_init(int errfd);
|
||||
int inotify_check(time_t now);
|
||||
void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz);
|
||||
#endif
|
||||
@@ -1904,7 +1931,6 @@ int do_poll(int timeout);
|
||||
/* rrfilter.c */
|
||||
size_t rrfilter(struct dns_header *header, size_t *plen, int mode);
|
||||
short *rrfilter_desc(int type);
|
||||
int expand_workspace(unsigned char ***wkspc, int *szp, int new);
|
||||
int to_wire(char *name);
|
||||
void from_wire(char *name);
|
||||
/* modes. */
|
||||
@@ -1915,11 +1941,11 @@ void from_wire(char *name);
|
||||
/* edns0.c */
|
||||
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
|
||||
size_t *len, unsigned char **p, int *is_sign, int *is_last);
|
||||
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
size_t add_pseudoheader(struct dns_header *header, size_t plen, size_t out_size,
|
||||
int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
|
||||
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
|
||||
size_t add_do_bit(struct dns_header *header, size_t plen, size_t outlen);
|
||||
void edns0_needs_mac(union mysockaddr *addr, time_t now);
|
||||
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
size_t add_edns0_config(struct dns_header *header, size_t plen, size_t outlen,
|
||||
union mysockaddr *source, time_t now, int *cacheable);
|
||||
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
|
||||
|
||||
@@ -1942,7 +1968,7 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
|
||||
int filter_servers(int seed, int flags, int *lowout, int *highout);
|
||||
int is_local_answer(time_t now, int first, char *name);
|
||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
|
||||
char *name, char *limit, int first, int last, int ede);
|
||||
char *name, size_t limit, int first, int last, int ede);
|
||||
int server_samegroup(struct server *a, struct server *b);
|
||||
#ifdef HAVE_DNSSEC
|
||||
int dnssec_server(struct server *server, char *keyname, int is_ds, int *firstp, int *lastp);
|
||||
|
||||
+253
-255
@@ -1,5 +1,5 @@
|
||||
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
|
||||
and Copyright (c) 2012-2025 Simon Kelley
|
||||
and Copyright (c) 2012-2026 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
|
||||
@@ -144,7 +144,7 @@ static int is_check_date(unsigned long curtime)
|
||||
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.
|
||||
Init state->buff with a MAXDNAMESTR+1 buffer.
|
||||
|
||||
After each call which returns 1, state->op points to the next byte of data.
|
||||
On returning 0, the end has been reached.
|
||||
@@ -177,7 +177,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
|
||||
if ((state->c = state->end - state->ip) != 0)
|
||||
{
|
||||
state->op = state->ip;
|
||||
state->ip = state->end;;
|
||||
state->ip = state->end;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
@@ -189,24 +189,28 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
|
||||
if (d == (u16)0)
|
||||
{
|
||||
/* domain-name, canonicalise */
|
||||
int len;
|
||||
|
||||
/* If the name is malformed, or runs beyond the end of the RR, abort.
|
||||
under no circumstances should state->ip be beyond state->end since
|
||||
that will cause the run-out code above to turn the negative
|
||||
calculted length into a very large postive value for state->c
|
||||
and overrun the buffer. */
|
||||
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
|
||||
(len = to_wire(state->buff)) == 0)
|
||||
continue;
|
||||
|
||||
state->c = len;
|
||||
state->ip > state->end)
|
||||
return 0;
|
||||
|
||||
/* to_wire() always returns length > 0 */
|
||||
state->c = to_wire(state->buff);
|
||||
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;
|
||||
|
||||
/* Plain data preceding a domain-name.
|
||||
Handle short RR, if there are no bytes left, return finished. */
|
||||
if ((state->end - state->ip) < d &&
|
||||
(d = state->end - state->ip) == 0)
|
||||
return 0;
|
||||
|
||||
state->op = state->ip;
|
||||
state->c = d;
|
||||
state->ip += d;
|
||||
@@ -370,45 +374,42 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
||||
|
||||
GETSHORT(type_covered, p);
|
||||
p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */
|
||||
|
||||
if (gotkey)
|
||||
|
||||
if (type_covered == type)
|
||||
{
|
||||
/* If there's more than one SIG, ensure they all have same keyname */
|
||||
if (extract_name(header, plen, &p, keyname, EXTR_NAME_COMPARE, 0) != 1)
|
||||
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
gotkey = 1;
|
||||
|
||||
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
|
||||
return 0;
|
||||
|
||||
|
||||
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
|
||||
the name of the zone containing the RRset. We can't tell that
|
||||
for certain, but we can check that the RRset name is equal to
|
||||
or encloses the signers name, which should be enough to stop
|
||||
an attacker using signatures made with the key of an unrelated
|
||||
zone he controls. Note that the root key is always allowed. */
|
||||
if (*keyname != 0)
|
||||
zone he controls. Note that the root key is always allowed.
|
||||
Ignore sigs which aren't valid */
|
||||
if (*daemon->workspacename == 0 || hostname_issubdomain(daemon->workspacename, name) != 0)
|
||||
{
|
||||
char *name_start;
|
||||
for (name_start = name; !hostname_isequal(name_start, keyname); )
|
||||
if ((name_start = strchr(name_start, '.')))
|
||||
name_start++; /* chop a label off and try again */
|
||||
else
|
||||
return 0;
|
||||
if (gotkey)
|
||||
{
|
||||
/* If there's more than one valid SIG, they must all have same keyname */
|
||||
if (!hostname_isequal(keyname, daemon->workspacename))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(keyname, daemon->workspacename);
|
||||
gotkey = 1;
|
||||
}
|
||||
|
||||
if (gotkey)
|
||||
{
|
||||
if (!expand_workspace(&sigs, &sig_sz, sigidx))
|
||||
return 0;
|
||||
|
||||
sigs[sigidx++] = pdata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (type_covered == type)
|
||||
{
|
||||
if (!expand_workspace(&sigs, &sig_sz, sigidx))
|
||||
return 0;
|
||||
|
||||
sigs[sigidx++] = pdata;
|
||||
}
|
||||
|
||||
p = pdata + 6; /* restore for ADD_RDLEN */
|
||||
}
|
||||
@@ -438,8 +439,6 @@ int dec_counter(int *counter, char *message)
|
||||
/* Validate a single RRset (class, type, name) in the supplied DNS reply
|
||||
Return code:
|
||||
STAT_SECURE if it validates.
|
||||
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
|
||||
(In this case *wildcard_out points to the "body" of the wildcard within name.)
|
||||
STAT_BOGUS signature is wrong, bad packet.
|
||||
STAT_ABANDONED validation abandoned do to excess resource usage.
|
||||
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
|
||||
@@ -455,7 +454,7 @@ int dec_counter(int *counter, char *message)
|
||||
ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
|
||||
*/
|
||||
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
|
||||
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
|
||||
char *name, char *keyname, int *wildcard_offset_out, struct blockdata *key, int keylen,
|
||||
int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
|
||||
{
|
||||
unsigned char *p;
|
||||
@@ -463,13 +462,15 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
struct crec *crecp = NULL;
|
||||
short *rr_desc = rrfilter_desc(type);
|
||||
u32 sig_expiration, sig_inception;
|
||||
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
|
||||
|
||||
unsigned long curtime = time(0);
|
||||
unsigned long curtime = time(0);
|
||||
int time_check = is_check_date(curtime);
|
||||
int failflags = DNSSEC_FAIL_NOSIG;
|
||||
|
||||
if (wildcard_out)
|
||||
*wildcard_out = NULL;
|
||||
if (sigidx != 0)
|
||||
failflags |= DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
|
||||
|
||||
if (wildcard_offset_out)
|
||||
*wildcard_offset_out = 0;
|
||||
|
||||
name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
||||
|
||||
@@ -485,7 +486,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
int i, wire_len, sig_len;
|
||||
const struct nettle_hash *hash;
|
||||
void *ctx;
|
||||
char *name_start;
|
||||
u32 nsigttl, ttl, orig_ttl;
|
||||
|
||||
failflags &= ~DNSSEC_FAIL_NOSIG;
|
||||
@@ -546,10 +546,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
|
||||
*ttl_out = ttl;
|
||||
}
|
||||
|
||||
|
||||
/* Don't trust rdlen not to be too small and give us a negative sig_len
|
||||
It has already been checked that it doesn't run us off the end
|
||||
of the packet. */
|
||||
if ((sig_len = rdlen - (p - psav)) <= 0)
|
||||
return STAT_BOGUS;
|
||||
|
||||
sig = p;
|
||||
sig_len = rdlen - (p - psav);
|
||||
|
||||
nsigttl = htonl(orig_ttl);
|
||||
|
||||
hash->update(ctx, 18, psav);
|
||||
@@ -571,11 +575,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
|
||||
return STAT_BOGUS;
|
||||
|
||||
name_start = name;
|
||||
|
||||
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
||||
if (labels < name_labels)
|
||||
/* If the name is already the wildcard, we're not going to change it. */
|
||||
if (labels < name_labels &&
|
||||
!(name_labels - labels == 1 && name[0] == '*' && name[1] == '.'))
|
||||
{
|
||||
char *name_start = name;
|
||||
|
||||
for (j = name_labels - labels; j != 0; j--)
|
||||
{
|
||||
while (*name_start != '.' && *name_start != 0)
|
||||
@@ -584,15 +590,17 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
name_start++;
|
||||
}
|
||||
|
||||
if (wildcard_out)
|
||||
*wildcard_out = name_start+1;
|
||||
|
||||
if (wildcard_offset_out)
|
||||
*wildcard_offset_out = name_start - name + 1;
|
||||
|
||||
name_start--;
|
||||
*name_start = '*';
|
||||
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
|
||||
memmove(name, name_start, strlen(name_start) + 1);
|
||||
}
|
||||
|
||||
wire_len = to_wire(name_start);
|
||||
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
|
||||
|
||||
wire_len = to_wire(name);
|
||||
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
|
||||
hash->update(ctx, 4, p); /* class and type */
|
||||
hash->update(ctx, 4, (unsigned char *)&nsigttl);
|
||||
|
||||
@@ -657,7 +665,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
}
|
||||
}
|
||||
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
|
||||
|
||||
/* namebuff used for workspace above, restore to leave unchanged on exit */
|
||||
p = (unsigned char*)(rrset[0]);
|
||||
@@ -687,7 +695,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
return STAT_ABANDONED;
|
||||
|
||||
if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
|
||||
return STAT_SECURE;
|
||||
|
||||
/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
|
||||
keys, all of which we have to try. Since many failing keys is not likely for
|
||||
@@ -837,7 +845,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
rather then O(keys x DSs) */
|
||||
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
|
||||
hash->update(ctx, (unsigned int)rdlen, psave);
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
|
||||
|
||||
from_wire(name);
|
||||
|
||||
@@ -951,10 +959,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
a.log.keytag = keytag;
|
||||
a.log.algo = algo;
|
||||
if (algo_digest_name(algo))
|
||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0);
|
||||
else
|
||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
|
||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", !algo_digest_name(algo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +997,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
char *keyname, int class, int *validate_counter)
|
||||
{
|
||||
unsigned char *p = (unsigned char *)(header+1);
|
||||
int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
|
||||
int qtype, qclass, rc, i, neganswer = 0, prim_ok = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
|
||||
int aclass, atype, rdlen, flags;
|
||||
unsigned long ttl;
|
||||
union all_addr a;
|
||||
@@ -1003,7 +1008,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
if (RCODE(header) == SERVFAIL)
|
||||
servfail = neganswer = nons = 1;
|
||||
else
|
||||
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
|
||||
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &prim_ok, &nons, &neg_ttl, validate_counter);
|
||||
|
||||
p = (unsigned char *)(header+1);
|
||||
if (ntohs(header->qdcount) != 1 ||
|
||||
@@ -1018,29 +1023,41 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
|
||||
if (!servfail)
|
||||
{
|
||||
/* If the answer carries no proof of non-existence, we fall back to asking if the
|
||||
zone is unsigned, but that question is answered by the very DS record we're
|
||||
chasing here, so we'd loop getting nowhere. Treat the zone as unsigned and let
|
||||
the checks below decide if that's acceptable. */
|
||||
if (STAT_ISEQUAL(rc, STAT_NEED_DS) && hostname_isequal(name, keyname))
|
||||
rc = STAT_INSECURE;
|
||||
|
||||
if (STAT_ISEQUAL(rc, STAT_INSECURE))
|
||||
{
|
||||
if (option_bool(OPT_BOGUSPRIV) &&
|
||||
(flags = in_arpa_name_2_addr(name, &a)) &&
|
||||
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
|
||||
/* A INSECURE DS answer is OK if it's negative and there's a CNAME answer to the DS answer which is
|
||||
signed, since that's enough to prove that the DS record doesn't exist. */
|
||||
if (!neganswer || !prim_ok)
|
||||
{
|
||||
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
|
||||
neganswer = 1;
|
||||
nons = 0; /* If we're faking a DS, fake one with an NS. */
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
|
||||
{
|
||||
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
|
||||
neganswer = 1;
|
||||
nons = 0; /* If we're faking a DS, fake one with an NS. */
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
|
||||
return STAT_BOGUS | DNSSEC_FAIL_INDET;
|
||||
if (option_bool(OPT_BOGUSPRIV) &&
|
||||
(flags = in_arpa_name_2_addr(name, &a)) &&
|
||||
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
|
||||
{
|
||||
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming that's OK for a RFC-1918 address"), name);
|
||||
neganswer = 1;
|
||||
nons = 0; /* If we're faking a DS, fake one with an NS. */
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
|
||||
{
|
||||
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming non-DNSSEC domain-specific server"), name);
|
||||
neganswer = 1;
|
||||
nons = 0; /* If we're faking a DS, fake one with an NS. */
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
|
||||
return STAT_BOGUS | DNSSEC_FAIL_INDET;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1097,7 +1114,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
a.log.keytag = keytag;
|
||||
a.log.algo = algo;
|
||||
a.log.digest = digest;
|
||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0);
|
||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 1);
|
||||
neg_ttl = ttl;
|
||||
}
|
||||
else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
|
||||
@@ -1137,44 +1154,47 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
}
|
||||
|
||||
flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
|
||||
|
||||
|
||||
if (neganswer)
|
||||
{
|
||||
if (RCODE(header) == NXDOMAIN)
|
||||
flags |= F_NXDOMAIN;
|
||||
|
||||
/* We only cache validated DS records, DNSSECOK flag hijacked
|
||||
to store presence/absence of NS. */
|
||||
if (nons)
|
||||
{
|
||||
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
|
||||
my_syslog(LOG_WARNING, _("negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server"), name);
|
||||
nons = 0;
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else
|
||||
/* We only cache validated DS records, DNSSECOK flag hijacked
|
||||
to store presence/absence of NS. */
|
||||
flags &= ~F_DNSSECOK;
|
||||
}
|
||||
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
|
||||
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
|
||||
}
|
||||
|
||||
|
||||
return cache_neg_ds(name, flags, class, now, neg_ttl);
|
||||
|
||||
}
|
||||
|
||||
int cache_neg_ds(char *name, int flags, int class, time_t now, int ttl)
|
||||
{
|
||||
cache_start_insert();
|
||||
|
||||
/* Use TTL from NSEC for negative cache entries */
|
||||
if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
|
||||
if (!cache_insert(name, NULL, class, now, ttl, flags))
|
||||
return STAT_ABANDONED;
|
||||
|
||||
cache_end_insert();
|
||||
|
||||
if (neganswer)
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
|
||||
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
/* 4034 6.1 */
|
||||
static int hostname_cmp(const char *a, const char *b)
|
||||
{
|
||||
@@ -1239,15 +1259,23 @@ static int hostname_cmp(const char *a, const char *b)
|
||||
}
|
||||
}
|
||||
|
||||
static int check_type_bitmap(unsigned char *p, int type)
|
||||
{
|
||||
int offset = (type & 0xff) >> 3;
|
||||
|
||||
if (p[0] == type >> 8 && p[1] > offset && (p[offset + 2] & (0x80 >> (type & 0x07))))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
||||
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
|
||||
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
|
||||
{
|
||||
int i, rc, rdlen;
|
||||
unsigned char *p, *psave;
|
||||
int offset = (type & 0xff) >> 3;
|
||||
int mask = 0x80 >> (type & 0x07);
|
||||
|
||||
|
||||
if (nons)
|
||||
*nons = 1;
|
||||
|
||||
@@ -1291,17 +1319,6 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
/* rdlen is now length of type map, and p points to it
|
||||
packet checked to be as long as rdlen implies in prove_non_existence() */
|
||||
|
||||
/* check that the first typemap is complete. */
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
/* RFC 6672 5.3.4.1. */
|
||||
#define DNAME_OFFSET (T_DNAME >> 3)
|
||||
#define DNAME_MASK (0x80 >> (T_DNAME & 0x07))
|
||||
if (p[0] == 0 && (p[1] >= DNAME_OFFSET + 1) && (p[2 + DNAME_OFFSET] & DNAME_MASK) != 0 &&
|
||||
hostname_issubdomain(name, workspace1) == 1)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
rc = hostname_cmp(workspace1, name);
|
||||
|
||||
if (rc == 0)
|
||||
@@ -1310,42 +1327,37 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
if (type == T_NSEC || type == T_RRSIG)
|
||||
return 0;
|
||||
|
||||
/* NSEC with the same name as the RR we're testing, check
|
||||
that the type in question doesn't appear in the type map */
|
||||
if (p[0] == 0 && p[1] >= 1)
|
||||
{
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
/* A CNAME answer would also be valid, so if there's a CNAME is should
|
||||
have been returned. */
|
||||
if ((p[2] & (0x80 >> T_CNAME)) != 0)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. For the root DS,
|
||||
this is expected. */
|
||||
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
}
|
||||
|
||||
while (rdlen > 0)
|
||||
{
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
{
|
||||
/* Does the NSEC say our type exists? */
|
||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
break; /* finished checking */
|
||||
}
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && check_type_bitmap(p, T_NS))
|
||||
*nons = 0;
|
||||
|
||||
rdlen -= p[1];
|
||||
p += p[1];
|
||||
/* NSEC with the same name as the RR we're testing, check
|
||||
that the type in question doesn't appear in the type map */
|
||||
if (check_type_bitmap(p, type))
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
/* A CNAME answer would also be valid, so if there's a CNAME it should
|
||||
have been returned. */
|
||||
if (check_type_bitmap(p, T_CNAME))
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. For the root DS,
|
||||
this is expected. */
|
||||
if (name_labels != 0 && type == T_DS && check_type_bitmap(p, T_SOA))
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
/* RFC 6672 5.3.4.1. */
|
||||
if (check_type_bitmap(p, T_DNAME) && hostname_issubdomain(name, workspace1) == 1)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
rdlen -= p[1] + 2;
|
||||
p += p[1] + 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1381,13 +1393,13 @@ static int hash_name(char *in, unsigned char **out, struct nettle_hash const *ha
|
||||
|
||||
hash->update(ctx, to_wire(in), (unsigned char *)in);
|
||||
hash->update(ctx, salt_len, salt);
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
|
||||
|
||||
for(i = 0; i < iterations; i++)
|
||||
{
|
||||
hash->update(ctx, hash->digest_size, digest);
|
||||
hash->update(ctx, salt_len, salt);
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
|
||||
}
|
||||
|
||||
from_wire(in);
|
||||
@@ -1419,7 +1431,10 @@ static int base32_decode(char *in, unsigned char *out)
|
||||
oc |= 1;
|
||||
mask = mask >> 1;
|
||||
if (((++on) & 7) == 0)
|
||||
*p++ = oc;
|
||||
{
|
||||
*p++ = oc;
|
||||
oc = 0;
|
||||
}
|
||||
oc = oc << 1;
|
||||
}
|
||||
}
|
||||
@@ -1430,6 +1445,7 @@ static int base32_decode(char *in, unsigned char *out)
|
||||
return p - out;
|
||||
}
|
||||
|
||||
/* return 1 if we can prove record _doesn't_ exist */
|
||||
static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
|
||||
char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels)
|
||||
{
|
||||
@@ -1466,50 +1482,35 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
we just need to check the type map. p points to the RR data for the record.
|
||||
Note we have packet length up to rdlen bytes checked. */
|
||||
|
||||
int offset = (type & 0xff) >> 3;
|
||||
int mask = 0x80 >> (type & 0x07);
|
||||
|
||||
p += hash_len; /* skip next-domain hash */
|
||||
rdlen -= p - psave;
|
||||
|
||||
/* check that the first typemap is complete. */
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
if (p[0] == 0 && p[1] >= 1)
|
||||
{
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
/* A CNAME answer would also be valid, so if there's a CNAME is should
|
||||
have been returned. */
|
||||
if ((p[2] & (0x80 >> T_CNAME)) != 0)
|
||||
return 0;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. For the root DS,
|
||||
this is expected. */
|
||||
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (rdlen > 0)
|
||||
{
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
return 0;
|
||||
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && check_type_bitmap(p, T_NS))
|
||||
*nons = 0;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
{
|
||||
/* Does the NSEC3 say our type exists? */
|
||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||
return 0;
|
||||
|
||||
break; /* finished checking */
|
||||
}
|
||||
|
||||
rdlen -= p[1];
|
||||
p += p[1];
|
||||
/* Does the NSEC3 say our type exists? */
|
||||
if (check_type_bitmap(p, type))
|
||||
return 0;
|
||||
|
||||
/* A CNAME answer would also be valid, so if there's a CNAME it should
|
||||
have been returned. */
|
||||
if (check_type_bitmap(p, T_CNAME))
|
||||
return 0;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. For the root DS,
|
||||
this is expected. */
|
||||
if (name_labels != 0 && type == T_DS && check_type_bitmap(p, T_SOA))
|
||||
return 0;
|
||||
|
||||
rdlen -= p[1] + 2;
|
||||
p += p[1] + 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -1545,11 +1546,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
|
||||
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
||||
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
|
||||
char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter)
|
||||
char *workspace2, char *name, int type, int wild_offset, int *nons, int *validate_counter)
|
||||
{
|
||||
unsigned char *salt, *p, *digest;
|
||||
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
|
||||
struct nettle_hash const *hash;
|
||||
struct nettle_hash const *hash = NULL;
|
||||
char *closest_encloser, *next_closest, *wildcard;
|
||||
|
||||
if (nons)
|
||||
@@ -1643,9 +1644,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
if (*closest_encloser == '.')
|
||||
closest_encloser++;
|
||||
|
||||
if (wildname && hostname_isequal(closest_encloser, wildname))
|
||||
if (wild_offset != 0 && closest_encloser - name == wild_offset)
|
||||
break;
|
||||
|
||||
|
||||
if (dec_counter(validate_counter, NULL))
|
||||
return DNSSEC_FAIL_WORK;
|
||||
|
||||
@@ -1687,18 +1688,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
/* Finally, check that there's no seat of wildcard synthesis */
|
||||
if (!wildname)
|
||||
if (wild_offset == 0)
|
||||
{
|
||||
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
wildcard--;
|
||||
*wildcard = '*';
|
||||
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
|
||||
memmove(name, wildcard, strlen(wildcard) + 1);
|
||||
|
||||
if (dec_counter(validate_counter, NULL))
|
||||
return DNSSEC_FAIL_WORK;
|
||||
|
||||
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
|
||||
@@ -1710,7 +1713,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
|
||||
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
||||
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass,
|
||||
char *wildname, int *nons, int *nsec_ttl, int *validate_counter)
|
||||
int wild_offset, int *nons, int *nsec_ttl, int *validate_counter)
|
||||
{
|
||||
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
||||
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
||||
@@ -1858,7 +1861,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
||||
if (type_found == T_NSEC)
|
||||
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
|
||||
else if (type_found == T_NSEC3)
|
||||
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
|
||||
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wild_offset, nons, validate_counter);
|
||||
else
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
}
|
||||
@@ -1934,7 +1937,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
if (name_start == 0)
|
||||
if (name_start <= 1)
|
||||
break;
|
||||
|
||||
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
|
||||
@@ -1965,7 +1968,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
|
||||
if the nons argument is non-NULL.
|
||||
*/
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
|
||||
int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
|
||||
int *class, int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_counter)
|
||||
{
|
||||
static unsigned char **targets = NULL;
|
||||
static int target_sz = 0;
|
||||
@@ -1994,8 +1997,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
if (neganswer)
|
||||
*neganswer = 0;
|
||||
|
||||
if (RCODE(header) == SERVFAIL)
|
||||
return STAT_BOGUS | DNSSEC_FAIL_UPSTREAM;
|
||||
|
||||
if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1)
|
||||
if (ntohs(header->qdcount) != 1)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
|
||||
@@ -2183,47 +2189,32 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
else
|
||||
{
|
||||
/* Not done, validate now */
|
||||
int sigcnt, rrcnt;
|
||||
char *wildname;
|
||||
int sigcnt, rrcnt, wild_offset;
|
||||
|
||||
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
|
||||
if (sigcnt == 0)
|
||||
{
|
||||
/* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
|
||||
if (type1 == T_NSEC || type1 == T_NSEC3)
|
||||
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
|
||||
else if (nons && i >= ntohs(header->ancount))
|
||||
/* If we're validating a DS reply, rather than looking for the value of AD bit,
|
||||
we only care that NSEC and NSEC3 RRs in the auth section are signed.
|
||||
Return SECURE even if others (SOA....) are not. */
|
||||
rc = STAT_SECURE;
|
||||
else
|
||||
{
|
||||
/* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
|
||||
if (check_unsigned && i < ntohs(header->ancount))
|
||||
{
|
||||
rc = zone_status(name, class1, keyname, now);
|
||||
if (STAT_ISEQUAL(rc, STAT_SECURE))
|
||||
rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
|
||||
|
||||
if (class)
|
||||
*class = class1; /* Class for NEED_DS or NEED_KEY */
|
||||
}
|
||||
else
|
||||
rc = STAT_INSECURE;
|
||||
|
||||
if (!STAT_ISEQUAL(rc, STAT_INSECURE))
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
/* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
|
||||
if (sigcnt == 0 && (type1 == T_NSEC || type1 == T_NSEC3))
|
||||
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
|
||||
else if (sigcnt == 0 && nons && i >= ntohs(header->ancount))
|
||||
/* If we're validating a DS reply, rather than looking for the value of AD bit,
|
||||
we only care that NSEC and NSEC3 RRs in the auth section are signed.
|
||||
Return SECURE even if others (SOA....) are not. */
|
||||
rc = STAT_SECURE;
|
||||
else if (sigcnt == 0 && (!check_unsigned || i >= ntohs(header->ancount)))
|
||||
/* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
|
||||
rc = STAT_INSECURE;
|
||||
else
|
||||
{
|
||||
/* explore_rrset() gives us key name from sigs in keyname.
|
||||
/* explore_rrset() gives us zone name from sigs in keyname, if
|
||||
it didn't find a key, use the name we're validating.
|
||||
Can't overwrite name here. */
|
||||
strcpy(daemon->workspacename, keyname);
|
||||
if (sigcnt == 0)
|
||||
strcpy(daemon->workspacename, name);
|
||||
else
|
||||
strcpy(daemon->workspacename, keyname);
|
||||
|
||||
rc = zone_status(daemon->workspacename, class1, keyname, now);
|
||||
|
||||
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
|
||||
@@ -2233,12 +2224,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Zone is insecure, don't need to validate RRset */
|
||||
/* If zone is insecure, don't need to validate RRset, and rc remains as STAT_INSECURE*/
|
||||
if (STAT_ISEQUAL(rc, STAT_SECURE))
|
||||
{
|
||||
unsigned long sig_ttl;
|
||||
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
|
||||
rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter);
|
||||
rrcnt, name, keyname, &wild_offset, NULL, 0, 0, 0, &sig_ttl, validate_counter);
|
||||
|
||||
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
|
||||
{
|
||||
@@ -2247,7 +2238,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
|
||||
/* rc is now STAT_SECURE */
|
||||
|
||||
/* Note that RR is validated */
|
||||
daemon->rr_status[i] = sig_ttl;
|
||||
@@ -2273,8 +2264,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
|
||||
That's not a problem since if the RRsets later fail
|
||||
we'll return BOGUS then. */
|
||||
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
|
||||
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
|
||||
if (wild_offset != 0 &&
|
||||
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wild_offset, NULL, NULL, validate_counter))) != 0)
|
||||
return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
|
||||
|
||||
rc = STAT_SECURE;
|
||||
@@ -2286,6 +2277,20 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
secure = STAT_INSECURE;
|
||||
}
|
||||
|
||||
|
||||
/* For a DS record, we are interested also in if the answer to the DS query was
|
||||
a CNAME RRset which validated. That's proof that the DS doesn't exist,
|
||||
even if it's a CNAME which is not signed, and therefore we have no proof
|
||||
of what it actually _is_. This return tells us that the answer to
|
||||
primary query is secure, even is the whole answer is insecure, because
|
||||
something down the CNAME list doesn't validate or doesn't exist.
|
||||
Note that prim_ok is only valid when neganswer is true, ie either
|
||||
the answer is the requested record or it's a CNAME that ends
|
||||
in a missing answer or an unsigned zone.
|
||||
*/
|
||||
if (prim_ok)
|
||||
*prim_ok = !targets[0];
|
||||
|
||||
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
|
||||
for (j = 0; j <targetidx; j++)
|
||||
if ((p2 = targets[j]))
|
||||
@@ -2296,20 +2301,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
if (!extract_name(header, plen, &p2, name, EXTR_NAME_EXTRACT, 10))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
|
||||
|
||||
/* For anything other than a DS record, this situation is OK if either
|
||||
the answer is in an unsigned zone, or there's NSEC records.
|
||||
For a DS record, we return INSECURE, which almost always turns
|
||||
into BOGUS in the caller. */
|
||||
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
|
||||
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype)
|
||||
This situation is OK if either the answer is in an unsigned zone, or there's NSEC records. */
|
||||
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, 0, nons, nsec_ttl, validate_counter)) != 0)
|
||||
{
|
||||
if (rc_nsec & DNSSEC_FAIL_WORK)
|
||||
return STAT_ABANDONED;
|
||||
|
||||
/* Empty DS without NSECS */
|
||||
if (qtype == T_DS)
|
||||
return STAT_INSECURE;
|
||||
|
||||
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
|
||||
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
|
||||
@@ -2349,7 +2346,7 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
||||
}
|
||||
}
|
||||
|
||||
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name,
|
||||
size_t dnssec_generate_query(struct dns_header *header, size_t outlen, char *name,
|
||||
int class, int id, int type)
|
||||
{
|
||||
unsigned char *p;
|
||||
@@ -2373,7 +2370,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
|
||||
PUTSHORT(type, p);
|
||||
PUTSHORT(class, p);
|
||||
|
||||
return add_do_bit(header, p - (unsigned char *)header, end);
|
||||
return add_do_bit(header, p - (unsigned char *)header, outlen);
|
||||
}
|
||||
|
||||
int errflags_to_ede(int status)
|
||||
@@ -2381,8 +2378,9 @@ int errflags_to_ede(int status)
|
||||
/* We can end up with more than one flag set for some errors,
|
||||
so this encodes a rough priority so the (eg) No sig is reported
|
||||
before no-unexpired-sig. */
|
||||
|
||||
if (status & DNSSEC_FAIL_NYV)
|
||||
if (status & DNSSEC_FAIL_UPSTREAM)
|
||||
return EDE_US_SERVFAIL;
|
||||
else if (status & DNSSEC_FAIL_NYV)
|
||||
return EDE_SIG_NYV;
|
||||
else if (status & DNSSEC_FAIL_EXP)
|
||||
return EDE_SIG_EXP;
|
||||
|
||||
+8
-5
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -79,7 +79,9 @@ void build_server_array(void)
|
||||
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
|
||||
daemon->serverarray[count] = serv;
|
||||
|
||||
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
|
||||
/* serverarray may be unallocated if we have no servers yet. */
|
||||
if (daemon->serverarray)
|
||||
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
|
||||
|
||||
/* servers need the location in the array to find all the whole
|
||||
set of equivalent servers from a pointer to a single one. */
|
||||
@@ -406,12 +408,13 @@ int is_local_answer(time_t now, int first, char *name)
|
||||
return rc;
|
||||
}
|
||||
|
||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
|
||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, size_t limit, int first, int last, int ede)
|
||||
{
|
||||
int trunc = 0, anscount = 0;
|
||||
unsigned char *p;
|
||||
int start;
|
||||
union all_addr addr;
|
||||
char *out_end = ((char *)header) + limit;
|
||||
|
||||
setup_reply(header, flags, ede);
|
||||
|
||||
@@ -441,7 +444,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
|
||||
else
|
||||
addr.addr4 = srv->addr;
|
||||
|
||||
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
|
||||
if (add_resource_record(header, out_end, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
|
||||
anscount++;
|
||||
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0);
|
||||
}
|
||||
@@ -456,7 +459,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
|
||||
else
|
||||
addr.addr6 = srv->addr;
|
||||
|
||||
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
|
||||
if (add_resource_record(header, out_end, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
|
||||
anscount++;
|
||||
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0);
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -162,12 +162,12 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
|
||||
if (c->indexed)
|
||||
{
|
||||
unsigned int index = ntohl(addr->addr4.s_addr) - ntohl(c->start.s_addr);
|
||||
snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index);
|
||||
snprintf(name, MAXDNAMESTR, "%s%u", c->prefix ? c->prefix : "", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c->prefix)
|
||||
strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
|
||||
strncpy(name, c->prefix, MAXDNAMESTR - ADDRSTRLEN);
|
||||
|
||||
inet_ntop(AF_INET, &addr->addr4, name + strlen(name), ADDRSTRLEN);
|
||||
for (p = name; *p; p++)
|
||||
@@ -175,8 +175,8 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
|
||||
*p = '-';
|
||||
}
|
||||
|
||||
strncat(name, ".", MAXDNAME);
|
||||
strncat(name, c->domain, MAXDNAME);
|
||||
strncat(name, ".", MAXDNAMESTR - strlen(name) - 1);
|
||||
strncat(name, c->domain, MAXDNAMESTR - strlen(name) - 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
|
||||
if (c->indexed)
|
||||
{
|
||||
u64 index = addr6part(&addr->addr6) - addr6part(&c->start6);
|
||||
snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index);
|
||||
snprintf(name, MAXDNAMESTR, "%s%llu", c->prefix ? c->prefix : "", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -196,21 +196,21 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
|
||||
char frag[6];
|
||||
|
||||
if (c->prefix)
|
||||
strncpy(name, c->prefix, MAXDNAME);
|
||||
strncpy(name, c->prefix, MAXDNAMESTR);
|
||||
|
||||
for (i = 0; i < 16; i += 2)
|
||||
{
|
||||
sprintf(frag, "%s%02x%02x", i == 0 ? "" : "-", addr->addr6.s6_addr[i], addr->addr6.s6_addr[i+1]);
|
||||
strncat(name, frag, MAXDNAME);
|
||||
strncat(name, frag, MAXDNAMESTR - strlen(name) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
strncat(name, ".", MAXDNAME);
|
||||
strncat(name, c->domain, MAXDNAME);
|
||||
|
||||
strncat(name, ".", MAXDNAMESTR - strlen(name) - 1);
|
||||
strncat(name, c->domain, MAXDNAMESTR - strlen(name) - 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+29
-26
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -85,7 +85,8 @@ void dump_init(void)
|
||||
/* count existing records */
|
||||
while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), RW_READ))
|
||||
{
|
||||
lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR);
|
||||
if (lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR) == (off_t)-1)
|
||||
break;
|
||||
packet_count++;
|
||||
}
|
||||
}
|
||||
@@ -128,6 +129,23 @@ void dump_packet_icmp(int mask, void *packet, size_t len,
|
||||
do_dump_packet(mask, packet, len, src, dst, -1, IPPROTO_ICMP);
|
||||
}
|
||||
|
||||
static u32 calc_data_checksum(u32 sum, void *packet, size_t len)
|
||||
{
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < len/2; i++)
|
||||
sum += ((u16 *)packet)[i];
|
||||
if (len & 1) /* last byte, in case length is odd. */
|
||||
sum += ((unsigned char *)packet)[len - 1];
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
if (sum != 0xffff)
|
||||
sum = ~sum;
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static void do_dump_packet(int mask, void *packet, size_t len,
|
||||
union mysockaddr *src, union mysockaddr *dst, int port, int proto)
|
||||
{
|
||||
@@ -225,13 +243,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
|
||||
udp.uh_dport = dst->in.sin_port;
|
||||
}
|
||||
|
||||
ip.ip_sum = 0;
|
||||
for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
|
||||
sum += ((u16 *)&ip)[i];
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
|
||||
|
||||
ip.ip_sum = 0; /* for the calculation */
|
||||
ip.ip_sum = calc_data_checksum(0, &ip, sizeof(struct ip));
|
||||
|
||||
/* start UDP/ICMP checksum */
|
||||
sum = ip.ip_src.s_addr & 0xffff;
|
||||
sum += (ip.ip_src.s_addr >> 16) & 0xffff;
|
||||
@@ -239,9 +253,6 @@ static void do_dump_packet(int mask, void *packet, size_t len,
|
||||
sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
|
||||
}
|
||||
|
||||
if (len & 1)
|
||||
((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
|
||||
|
||||
if (proto == IPPROTO_UDP)
|
||||
{
|
||||
/* Add Remaining part of the pseudoheader. Note that though the
|
||||
@@ -251,17 +262,12 @@ static void do_dump_packet(int mask, void *packet, size_t len,
|
||||
sum += htons(IPPROTO_UDP);
|
||||
sum += htons(sizeof(struct udphdr) + len);
|
||||
|
||||
udp.uh_sum = 0;
|
||||
udp.uh_sum = 0; /* for the calculation */
|
||||
udp.uh_ulen = htons(sizeof(struct udphdr) + len);
|
||||
|
||||
for (i = 0; i < sizeof(struct udphdr)/2; i++)
|
||||
sum += ((u16 *)&udp)[i];
|
||||
for (i = 0; i < (len + 1) / 2; i++)
|
||||
sum += ((u16 *)packet)[i];
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
|
||||
|
||||
udp.uh_sum = calc_data_checksum(sum, packet, len);
|
||||
|
||||
pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
|
||||
}
|
||||
else
|
||||
@@ -273,12 +279,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
|
||||
sum += htons(proto);
|
||||
sum += htons(len);
|
||||
|
||||
icmp->icmp6_cksum = 0;
|
||||
for (i = 0; i < (len + 1) / 2; i++)
|
||||
sum += ((u16 *)packet)[i];
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
icmp->icmp6_cksum = (sum == 0xffff) ? sum : ~sum;
|
||||
icmp->icmp6_cksum = 0; /* for the calculation */
|
||||
icmp->icmp6_cksum = calc_data_checksum(sum, packet, len);
|
||||
|
||||
|
||||
pcap_header.incl_len = pcap_header.orig_len = ipsz + len;
|
||||
}
|
||||
|
||||
+29
-24
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -100,13 +100,14 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t
|
||||
replace == 1 ->replace existing or add option
|
||||
replace == 2 ->relpace existing option only.
|
||||
*/
|
||||
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
size_t add_pseudoheader(struct dns_header *header, size_t plen, size_t out_size,
|
||||
int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
|
||||
{
|
||||
unsigned char *lenp, *datap, *p, *udp_len, *buff = NULL;
|
||||
unsigned char *lenp = NULL, *datap = NULL, *p, *udp_len, *buff = NULL;
|
||||
int rdlen = 0, is_sign, is_last;
|
||||
unsigned short flags = set_do ? 0x8000 : 0, rcode = 0;
|
||||
|
||||
unsigned char *limit = ((unsigned char *)header) + out_size;
|
||||
|
||||
p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
|
||||
|
||||
if (is_sign)
|
||||
@@ -247,9 +248,9 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l
|
||||
return p - (unsigned char *)header;
|
||||
}
|
||||
|
||||
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit)
|
||||
size_t add_do_bit(struct dns_header *header, size_t plen, size_t outlen)
|
||||
{
|
||||
return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1, 0);
|
||||
return add_pseudoheader(header, plen, outlen, 0, NULL, 0, 1, 0);
|
||||
}
|
||||
|
||||
static unsigned char char64(unsigned char c)
|
||||
@@ -276,14 +277,16 @@ void edns0_needs_mac(union mysockaddr *addr, time_t now)
|
||||
|
||||
/* OPT_ADD_MAC = MAC is added (if available)
|
||||
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
|
||||
OPT_STRIP_MAC = MAC is removed */
|
||||
static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
OPT_STRIP_MAC = MAC is removed
|
||||
Handle only six byte MACs. */
|
||||
static size_t add_dns_client(struct dns_header *header, size_t plen, size_t outlen,
|
||||
union mysockaddr *l3, time_t now, int *cacheablep)
|
||||
{
|
||||
int replace = 0, maclen = 0;
|
||||
unsigned char mac[DHCP_CHADDR_MAX];
|
||||
char encode[18]; /* handle 6 byte MACs ONLY */
|
||||
|
||||
char encode[9] = { 0 };
|
||||
char *encoded = encode;
|
||||
|
||||
if ((option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) && (maclen = find_mac(l3, mac, 1, now)) == 6)
|
||||
{
|
||||
if (option_bool(OPT_STRIP_MAC))
|
||||
@@ -291,7 +294,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
|
||||
*cacheablep = 0;
|
||||
|
||||
if (option_bool(OPT_MAC_HEX))
|
||||
print_mac(encode, mac, maclen);
|
||||
encoded = print_mac(mac, maclen);
|
||||
else
|
||||
{
|
||||
encoder(mac, encode);
|
||||
@@ -303,7 +306,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
|
||||
replace = 2;
|
||||
|
||||
if (replace != 0 || maclen == 6)
|
||||
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace);
|
||||
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encoded, strlen(encoded), 0, replace);
|
||||
|
||||
return plen;
|
||||
}
|
||||
@@ -312,7 +315,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
|
||||
/* OPT_ADD_MAC = MAC is added (if available)
|
||||
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
|
||||
OPT_STRIP_MAC = MAC is removed */
|
||||
static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
static size_t add_mac(struct dns_header *header, size_t plen, size_t outlen,
|
||||
union mysockaddr *l3, time_t now, int *cacheablep)
|
||||
{
|
||||
int maclen = 0, replace = 0;
|
||||
@@ -328,7 +331,7 @@ static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *lim
|
||||
replace = 2;
|
||||
|
||||
if (replace != 0 || maclen != 0)
|
||||
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
|
||||
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
|
||||
|
||||
return plen;
|
||||
}
|
||||
@@ -409,7 +412,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source,
|
||||
/* OPT_CLIENT_SUBNET = client subnet is added
|
||||
OPT_CLIENT_SUBNET + OPT_STRIP_ECS = client subnet is replaced
|
||||
OPT_STRIP_ECS = client subnet is removed */
|
||||
static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
static size_t add_source_addr(struct dns_header *header, size_t plen, size_t outlen,
|
||||
union mysockaddr *source, int *cacheable)
|
||||
{
|
||||
/* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
|
||||
@@ -439,7 +442,7 @@ static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned c
|
||||
return plen;
|
||||
}
|
||||
|
||||
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
|
||||
return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
|
||||
}
|
||||
|
||||
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
|
||||
@@ -469,6 +472,8 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
|
||||
{
|
||||
GETSHORT(code, p);
|
||||
GETSHORT(len, p);
|
||||
if (i + 4 + len > rdlen)
|
||||
break; /* malformed: option body extends beyond RDATA */
|
||||
if (code == EDNS0_OPTION_CLIENT_SUBNET)
|
||||
{
|
||||
if (peer)
|
||||
@@ -514,7 +519,7 @@ struct umbrella_opt {
|
||||
u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
|
||||
};
|
||||
|
||||
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
|
||||
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, size_t outlen, union mysockaddr *source, int *cacheable)
|
||||
{
|
||||
*cacheable = 0;
|
||||
|
||||
@@ -546,28 +551,28 @@ static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned
|
||||
PUTLONG(daemon->umbrella_asset, u);
|
||||
}
|
||||
|
||||
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
|
||||
return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
|
||||
}
|
||||
|
||||
/* Set *check_subnet if we add a client subnet option, which needs to checked
|
||||
in the reply. Set *cacheable to zero if we add an option which the answer
|
||||
may depend on. */
|
||||
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
|
||||
size_t add_edns0_config(struct dns_header *header, size_t plen, size_t outlen,
|
||||
union mysockaddr *source, time_t now, int *cacheable)
|
||||
{
|
||||
*cacheable = 1;
|
||||
|
||||
plen = add_mac(header, plen, limit, source, now, cacheable);
|
||||
plen = add_dns_client(header, plen, limit, source, now, cacheable);
|
||||
plen = add_mac(header, plen, outlen, source, now, cacheable);
|
||||
plen = add_dns_client(header, plen, outlen, source, now, cacheable);
|
||||
|
||||
if (daemon->dns_client_id)
|
||||
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMCPEID,
|
||||
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMCPEID,
|
||||
(unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
|
||||
|
||||
if (option_bool(OPT_UMBRELLA))
|
||||
plen = add_umbrella_opt(header, plen, limit, source, cacheable);
|
||||
plen = add_umbrella_opt(header, plen, outlen, source, cacheable);
|
||||
|
||||
plen = add_source_addr(header, plen, limit, source, cacheable);
|
||||
plen = add_source_addr(header, plen, outlen, source, cacheable);
|
||||
|
||||
return plen;
|
||||
}
|
||||
|
||||
+256
-194
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -135,9 +135,9 @@ static void log_query_mysockaddr(unsigned int flags, char *name, union mysockadd
|
||||
}
|
||||
|
||||
static void server_send(struct server *server, int fd,
|
||||
const void *header, size_t plen, int flags)
|
||||
const void *header, size_t plen)
|
||||
{
|
||||
while (retry_send(sendto(fd, header, plen, flags,
|
||||
while (retry_send(sendto(fd, header, plen, 0,
|
||||
&server->addr.sa,
|
||||
sa_len(&server->addr))));
|
||||
}
|
||||
@@ -177,7 +177,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
int forwarded = 0;
|
||||
int ede = EDE_UNSET;
|
||||
unsigned short rrtype, rrclass;
|
||||
|
||||
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
|
||||
unsigned int casediff = 0;
|
||||
unsigned int *bitvector = NULL;
|
||||
|
||||
gotname = extract_request(header, plen, daemon->namebuff, &rrtype, &rrclass);
|
||||
|
||||
/* Check for retry on existing query.
|
||||
@@ -197,16 +200,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
|
||||
{
|
||||
struct frec_src *src;
|
||||
unsigned int casediff = 0;
|
||||
unsigned int *bitvector = NULL;
|
||||
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
|
||||
|
||||
/* Get the case-scambled version of the query to resend. This is important because we
|
||||
may fall through below and forward the query in the packet buffer again and we
|
||||
want to use the same case scrambling as the first time. */
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
plen = forward->stash_len;
|
||||
|
||||
|
||||
for (src = &forward->frec_src; src; src = src->next)
|
||||
if (src->orig_id == id &&
|
||||
sockaddr_isequal(&src->source, udpaddr))
|
||||
@@ -217,6 +211,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
old_src = 1;
|
||||
/* If a query is retried, use the log_id for the retry when logging the answer. */
|
||||
src->log_id = daemon->log_id;
|
||||
/* Get the case-scambled version of the query to resend. This is important because we
|
||||
may fall through below and forward the query in the packet buffer again and we
|
||||
want to use the same case scrambling as the first time. */
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
plen = forward->stash_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -242,9 +241,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
and never resetting until the frec gets deleted by
|
||||
aging followed by the receipt of a different query. This
|
||||
is a bit of a DoS vuln. Avoid by explicitly deleting the
|
||||
frec once it expires. */
|
||||
if (difftime(now, forward->time) >= TIMEOUT)
|
||||
free_frec(forward);
|
||||
frec once it expires. The deletion is done at the end of
|
||||
this function, unless we set forward to NULL here. */
|
||||
if (difftime(now, forward->time) < TIMEOUT)
|
||||
forward = NULL;
|
||||
|
||||
goto reply;
|
||||
}
|
||||
|
||||
@@ -263,7 +264,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
|
||||
The original query we sent is now in packet buffer and the query name in the
|
||||
new instance is on daemon->namebuff. */
|
||||
|
||||
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
plen = forward->stash_len;
|
||||
|
||||
if (extract_name(header, forward->stash_len, NULL, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
|
||||
{
|
||||
unsigned int i, gobig = 0;
|
||||
@@ -312,7 +316,6 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
src->fd = udpfd;
|
||||
src->encode_bitmap = casediff;
|
||||
src->encode_bigmap = bitvector;
|
||||
|
||||
src->udp_pkt_size = (unsigned short)replylimit;
|
||||
|
||||
/* closely spaced identical queries cannot be a try and a retry, so
|
||||
@@ -375,7 +378,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
plen = add_do_bit(header, plen, ((unsigned char *) header) + daemon->edns_pktsz);
|
||||
plen = add_do_bit(header, plen, daemon->edns_pktsz);
|
||||
|
||||
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
||||
this allows it to select auth servers when one is returning bad data. */
|
||||
@@ -388,18 +391,15 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
forward->frec_src.orig_id = ntohs(header->id);
|
||||
forward->new_id = get_id();
|
||||
header->id = ntohs(forward->new_id);
|
||||
forward->frec_src.encode_bitmap = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
|
||||
forward->frec_src.encode_bitmap = casediff = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
|
||||
forward->frec_src.encode_bigmap = NULL;
|
||||
|
||||
if (!extract_name(header, plen, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
|
||||
if (casediff != 0 && !extract_name(header, plen, NULL, (char *)&casediff, EXTR_NAME_FLIP, 1))
|
||||
goto reply;
|
||||
|
||||
/* Keep copy of query for retries and move to TCP */
|
||||
if (!(forward->stash = blockdata_alloc((char *)header, plen)))
|
||||
{
|
||||
free_frec(forward);
|
||||
goto reply; /* no mem. return REFUSED */
|
||||
}
|
||||
goto reply; /* no mem. return REFUSED */
|
||||
|
||||
forward->stash_len = plen;
|
||||
forward->frec_src.log_id = daemon->log_id;
|
||||
@@ -514,7 +514,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
forwarded = 0;
|
||||
|
||||
/* Advertise the size of UDP reply we can accept. */
|
||||
plen = add_pseudoheader(header, plen, (unsigned char *)(header + daemon->edns_pktsz), 0, NULL, 0, 0, 0);
|
||||
plen = add_pseudoheader(header, plen, daemon->edns_pktsz, 0, NULL, 0, 0, 0);
|
||||
|
||||
/* check for send errors here (no route to host)
|
||||
if we fail to send to all nameservers, send back an error
|
||||
@@ -582,14 +582,26 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
}
|
||||
|
||||
/* could not send on, prepare to return */
|
||||
header->id = htons(forward->frec_src.orig_id);
|
||||
free_frec(forward); /* cancel */
|
||||
ede = EDE_NETERR;
|
||||
|
||||
reply:
|
||||
if (udpfd != -1)
|
||||
{
|
||||
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, (char *)(header + replylimit), first, last, ede)))
|
||||
/* The query now in the buffer can have changed from what arrived during
|
||||
the preparation for forwarding in two respects.
|
||||
1) The header->id field may have changed.
|
||||
2) The case of letters in the query may have been changed.
|
||||
|
||||
Restore both of these changes before using it to build an error return for the client.
|
||||
The original id is held in local variable id, and the difference in case
|
||||
is encoded in casediff and also bitvector if the difference spreads beyond the first
|
||||
32 characters.
|
||||
*/
|
||||
header->id = htons(id);
|
||||
if (casediff != 0)
|
||||
extract_name(header, plen, NULL, (char *)(bitvector ? bitvector : &casediff), EXTR_NAME_FLIP, bitvector ? casediff : 1);
|
||||
|
||||
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, replylimit, first, last, ede)))
|
||||
return;
|
||||
|
||||
if (fwd_flags & FREC_HAS_PHEADER)
|
||||
@@ -597,9 +609,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
u16 swap = htons((u16)ede);
|
||||
|
||||
if (ede != EDE_UNSET)
|
||||
plen = add_pseudoheader(header, plen, (unsigned char *)(header + replylimit), EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, 0, 0);
|
||||
plen = add_pseudoheader(header, plen, replylimit, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, 0, 0);
|
||||
else
|
||||
plen = add_pseudoheader(header, plen, (unsigned char *)(header + replylimit), 0, NULL, 0, 0, 0);
|
||||
plen = add_pseudoheader(header, plen, replylimit, 0, NULL, 0, 0, 0);
|
||||
}
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
@@ -619,6 +631,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
}
|
||||
|
||||
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
|
||||
|
||||
/* This has to be after last use of bitvector. */
|
||||
if (forward)
|
||||
free_frec(forward);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -695,7 +712,7 @@ static struct ipsets *domain_find_sets(struct ipsets *setlist, const char *domai
|
||||
|
||||
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
||||
int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader,
|
||||
union mysockaddr *query_source, unsigned char *limit, int ede)
|
||||
union mysockaddr *query_source, size_t outlen, int ede)
|
||||
{
|
||||
unsigned char *pheader, *sizep;
|
||||
struct ipsets *ipsets = NULL, *nftsets = NULL;
|
||||
@@ -724,7 +741,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
/* Get extended RCODE. */
|
||||
rcode |= sizep[2] << 4;
|
||||
|
||||
if (option_bool(OPT_CLIENT_SUBNET) && !check_source(header, plen, pheader, query_source))
|
||||
if (option_bool(OPT_CLIENT_SUBNET) && !check_source(header, n, pheader, query_source))
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
|
||||
return 0;
|
||||
@@ -879,7 +896,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
if (pheader && ede != EDE_UNSET)
|
||||
{
|
||||
u16 swap = htons((u16)ede);
|
||||
n = add_pseudoheader(header, n, limit, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1);
|
||||
n = add_pseudoheader(header, n, outlen, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1);
|
||||
}
|
||||
|
||||
if (RCODE(header) == NXDOMAIN)
|
||||
@@ -954,13 +971,14 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
|
||||
would invite infinite loops, since the answers to DNSKEY and DS queries
|
||||
will not be cached, so they'll be repeated. */
|
||||
ds_retry:
|
||||
if (forward->flags & FREC_DNSKEY_QUERY)
|
||||
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
|
||||
else if (forward->flags & FREC_DS_QUERY)
|
||||
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
|
||||
else
|
||||
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, &orig->validate_counter);
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, NULL, &orig->validate_counter);
|
||||
|
||||
if (STAT_ISEQUAL(status, STAT_ABANDONED))
|
||||
log_resource = 1;
|
||||
@@ -1026,7 +1044,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
allocation of a new one: third arg of get_new_frec() does that. */
|
||||
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, STAT_ISEQUAL(status, STAT_NEED_DS), NULL, NULL)) != -1 &&
|
||||
(server = daemon->serverarray[serverind]) &&
|
||||
(nn = dnssec_generate_query(header, ((unsigned char *) header) + daemon->edns_pktsz,
|
||||
(nn = dnssec_generate_query(header, daemon->edns_pktsz,
|
||||
daemon->keyname, forward->class, get_id(),
|
||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS)) &&
|
||||
(fd = allocate_rfd(&rfds, server)) != -1 &&
|
||||
@@ -1074,7 +1092,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
set_outgoing_mark(orig, fd);
|
||||
#endif
|
||||
|
||||
server_send(server, fd, header, nn, 0);
|
||||
server_send(server, fd, header, nn);
|
||||
server->queries++;
|
||||
#ifdef HAVE_DUMPFILE
|
||||
dump_packet_udp(DUMP_SEC_QUERY, (void *)header, (size_t)nn, NULL, &server->addr, fd);
|
||||
@@ -1083,6 +1101,18 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there's no server for the parent of a domain-specific server's domain,
|
||||
assume that said server's contents it legitimately unsigned, as if the parent
|
||||
contained a negative DS record. This is part of the same logic that's found
|
||||
in dnssec_validate_ds() when it gets a negative DS repsonse. */
|
||||
if (STAT_ISEQUAL(status, STAT_NEED_DS) && serverind == -1 && lookup_domain(daemon->keyname, F_DOMAINSRV, NULL, NULL) &&
|
||||
cache_neg_ds(daemon->keyname, F_FORWARD | F_DS | F_NEG | F_DNSSECOK, forward->class, now, DNSSEC_ASSUMED_DS_TTL) == STAT_OK)
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("no server for parent domain of %s, assuming unsigned domain"), daemon->keyname);
|
||||
blockdata_free(stash);
|
||||
goto ds_retry;
|
||||
}
|
||||
|
||||
/* error unwind */
|
||||
free_rfds(&rfds);
|
||||
@@ -1192,10 +1222,10 @@ void reply_query(int fd, time_t now)
|
||||
server = daemon->serverarray[serv];
|
||||
if (server->sfd && server->sfd->fd == fd)
|
||||
break;
|
||||
|
||||
if (serv == last)
|
||||
return;
|
||||
}
|
||||
|
||||
if (serv == last)
|
||||
return;
|
||||
}
|
||||
|
||||
/* spoof check: answer must come from known server, also
|
||||
@@ -1254,7 +1284,7 @@ void reply_query(int fd, time_t now)
|
||||
had replies from all to avoid filling the forwarding table when
|
||||
everything is broken */
|
||||
|
||||
/* decrement count of replies recieved if we sent to more than one server. */
|
||||
/* decrement count of replies received if we sent to more than one server. */
|
||||
if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED)
|
||||
return;
|
||||
|
||||
@@ -1276,7 +1306,8 @@ void reply_query(int fd, time_t now)
|
||||
server->query_latency = server->mma_latency/128;
|
||||
|
||||
/* Flip the bits back in the query name. */
|
||||
if (!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
if (forward->frec_src.encode_bitmap != 0 &&
|
||||
!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
return;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
@@ -1399,6 +1430,9 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
|
||||
|
||||
a.log.ede = ede;
|
||||
log_query(F_SECSTAT, domain, &a, result, 0);
|
||||
|
||||
if (ede == EDE_US_SERVFAIL)
|
||||
ede = EDE_DNSSEC_BOGUS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,7 +1460,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
|
||||
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_HAS_PHEADER), &forward->frec_src.source,
|
||||
((unsigned char *)header) + daemon->edns_pktsz, ede)))
|
||||
daemon->edns_pktsz, ede)))
|
||||
{
|
||||
struct frec_src *src, *prev;
|
||||
int do_trunc;
|
||||
@@ -1929,7 +1963,7 @@ void receive_query(struct listener *listen, time_t now)
|
||||
{
|
||||
int cacheable;
|
||||
|
||||
n = add_edns0_config(header, n, ((unsigned char *)header) + daemon->edns_pktsz, &source_addr, now, &cacheable);
|
||||
n = add_edns0_config(header, n, daemon->edns_pktsz, &source_addr, now, &cacheable);
|
||||
saved_question = blockdata_alloc((char *) header, (size_t)n);
|
||||
|
||||
if (!cacheable)
|
||||
@@ -1963,12 +1997,12 @@ void receive_query(struct listener *listen, time_t now)
|
||||
{
|
||||
u16 swap = htons(ede);
|
||||
|
||||
m = add_pseudoheader(header, m, ((unsigned char *) header) + daemon->edns_pktsz,
|
||||
EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
|
||||
m = add_pseudoheader(header, m, daemon->edns_pktsz, EDNS0_OPTION_EDE,
|
||||
(unsigned char *)&swap, 2, do_bit, 0);
|
||||
}
|
||||
else
|
||||
m = add_pseudoheader(header, m, ((unsigned char *) header) + daemon->edns_pktsz,
|
||||
0, NULL, 0, do_bit, 0);
|
||||
m = add_pseudoheader(header, m, daemon->edns_pktsz, 0,
|
||||
NULL, 0, do_bit, 0);
|
||||
}
|
||||
|
||||
#ifdef HAVE_DUMPFILE
|
||||
@@ -2015,20 +2049,21 @@ void receive_query(struct listener *listen, time_t now)
|
||||
}
|
||||
|
||||
|
||||
/* Send query in packet, qsize to a server determined by first,last,start and
|
||||
get the reply. return reply size. */
|
||||
static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, size_t qsize,
|
||||
int have_mark, unsigned int mark, struct server **servp)
|
||||
/* Send query in header, qsize to a server determined by first,last,start and
|
||||
get the reply into the buffer passed by outbuff. Return reply size. */
|
||||
static ssize_t tcp_talk(int first, int last, int start, struct dns_header *header, size_t qsize,
|
||||
struct iovec *recvbuff, int have_mark, unsigned int mark, struct server **servp)
|
||||
{
|
||||
int firstsendto = -1;
|
||||
u16 *length = (u16 *)packet;
|
||||
unsigned char *payload = &packet[2];
|
||||
struct dns_header *header = (struct dns_header *)payload;
|
||||
unsigned int rsize;
|
||||
u16 length;
|
||||
unsigned int rsize = 0;
|
||||
int class, rclass, type, rtype;
|
||||
unsigned char *p;
|
||||
struct blockdata *saved_question;
|
||||
struct timeval tv;
|
||||
struct iovec sendio[2];
|
||||
#ifdef MSG_FASTOPEN
|
||||
struct msghdr msg;
|
||||
#endif
|
||||
|
||||
(void)mark;
|
||||
(void)have_mark;
|
||||
@@ -2040,10 +2075,12 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
GETSHORT(type, p);
|
||||
GETSHORT(class, p);
|
||||
|
||||
/* Save question for retry. */
|
||||
if (!(saved_question = blockdata_alloc((char *)header, (size_t)qsize)))
|
||||
return 0;
|
||||
|
||||
length = htons(qsize);
|
||||
sendio[0].iov_base = &length;
|
||||
sendio[0].iov_len = sizeof(length);
|
||||
sendio[1].iov_base = header;
|
||||
sendio[1].iov_len = qsize;
|
||||
|
||||
while (1)
|
||||
{
|
||||
int data_sent = 0, fatal = 0;
|
||||
@@ -2065,10 +2102,6 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
*servp = serv = daemon->serverarray[start];
|
||||
|
||||
retry:
|
||||
blockdata_retrieve(saved_question, qsize, header);
|
||||
|
||||
*length = htons(qsize);
|
||||
|
||||
if (serv->tcpfd == -1)
|
||||
{
|
||||
if ((serv->tcpfd = socket(serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
|
||||
@@ -2090,7 +2123,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
#if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO)
|
||||
/* TCP connections by default take ages to time out.
|
||||
Set shorter timeouts more appropriate for a DNS server.
|
||||
We set the recieve timeout as twice the send timeout; we
|
||||
We set the receive timeout as twice the send timeout; we
|
||||
want to fail quickly on a non-responsive server, but give it time to get an
|
||||
answer. */
|
||||
tv.tv_sec = TCP_TIMEOUT;
|
||||
@@ -2099,10 +2132,17 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
tv.tv_sec += TCP_TIMEOUT;
|
||||
setsockopt(serv->tcpfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
#endif
|
||||
|
||||
#ifdef MSG_FASTOPEN
|
||||
server_send(serv, serv->tcpfd, packet, qsize + sizeof(u16), MSG_FASTOPEN);
|
||||
|
||||
msg.msg_name = &serv->addr.sa;
|
||||
msg.msg_namelen = sa_len(&serv->addr);
|
||||
msg.msg_iov = sendio;
|
||||
msg.msg_iovlen = 2;
|
||||
msg.msg_control = NULL;
|
||||
msg.msg_controllen = 0;
|
||||
msg.msg_flags = 0;
|
||||
|
||||
while (retry_send(sendmsg(serv->tcpfd, &msg, MSG_FASTOPEN)));
|
||||
|
||||
if (errno == 0)
|
||||
data_sent = 1;
|
||||
else if (errno == ETIMEDOUT || errno == EHOSTUNREACH || errno == EINPROGRESS || errno == ECONNREFUSED)
|
||||
@@ -2128,12 +2168,13 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
serv->flags &= ~SERV_GOT_TCP;
|
||||
}
|
||||
|
||||
/* We us the _ONCE veriant of read_write() here because we've set a timeout on the tcp socket
|
||||
/* We use the _ONCE variant of read_write() here because we've set a timeout on the tcp socket
|
||||
and wish to abort if the whole data is not read/written within the timeout. */
|
||||
if ((!data_sent && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) ||
|
||||
!read_write(serv->tcpfd, (unsigned char *)length, sizeof (*length), RW_READ_ONCE) ||
|
||||
!read_write(serv->tcpfd, payload, (rsize = ntohs(*length)), RW_READ_ONCE))
|
||||
{
|
||||
if ((!data_sent && !read_writev(serv->tcpfd, sendio, 2, RW_WRITE_ONCE)) ||
|
||||
!read_write(serv->tcpfd, (unsigned char *)&length, sizeof(length), RW_READ_ONCE) ||
|
||||
!expand_buf(recvbuff, (rsize = ntohs(length))) ||
|
||||
!read_write(serv->tcpfd, recvbuff->iov_base, rsize, RW_READ_ONCE))
|
||||
{
|
||||
/* We get data then EOF, reopen connection to same server,
|
||||
else try next. This avoids DoS from a server which accepts
|
||||
connections and then closes them. */
|
||||
@@ -2146,28 +2187,31 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
else
|
||||
goto failed;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the question section of the reply doesn't match the question we sent, then
|
||||
someone might be attempting to insert bogus values into the cache by
|
||||
sending replies containing questions and bogus answers.
|
||||
Try another server, or give up */
|
||||
struct dns_header *header = (struct dns_header *)recvbuff->iov_base;
|
||||
p = (unsigned char *)(header+1);
|
||||
if (rsize < (unsigned int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || ntohs(header->qdcount) != 1 ||
|
||||
extract_name(header, rsize, &p, daemon->namebuff, EXTR_NAME_COMPARE, 4) != 1)
|
||||
continue;
|
||||
|
||||
GETSHORT(rtype, p);
|
||||
GETSHORT(rclass, p);
|
||||
|
||||
/* If the question section of the reply doesn't match the question we sent, then
|
||||
someone might be attempting to insert bogus values into the cache by
|
||||
sending replies containing questions and bogus answers.
|
||||
Try another server, or give up */
|
||||
p = (unsigned char *)(header+1);
|
||||
if (extract_name(header, rsize, &p, daemon->namebuff, EXTR_NAME_COMPARE, 4) != 1)
|
||||
continue;
|
||||
GETSHORT(rtype, p);
|
||||
GETSHORT(rclass, p);
|
||||
|
||||
if (type != rtype || class != rclass)
|
||||
continue;
|
||||
if (type != rtype || class != rclass)
|
||||
continue;
|
||||
}
|
||||
|
||||
serv->flags |= SERV_GOT_TCP;
|
||||
|
||||
*servp = serv;
|
||||
blockdata_free(saved_question);
|
||||
return rsize;
|
||||
}
|
||||
|
||||
blockdata_free(saved_question);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2179,19 +2223,16 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
int class, char *name, struct server *server,
|
||||
int *keycount, int *validatecount)
|
||||
{
|
||||
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
struct dns_header *new_header = (struct dns_header *)&packet[2];
|
||||
int start, first, last, new_status;
|
||||
ssize_t n = *plenp;
|
||||
int log_save = daemon->log_display_id;
|
||||
struct iovec recvbuff;
|
||||
|
||||
recvbuff.iov_len = 0;
|
||||
recvbuff.iov_base = NULL;
|
||||
|
||||
*plenp = 0;
|
||||
|
||||
if (!packet)
|
||||
return STAT_ABANDONED;
|
||||
|
||||
memcpy(new_header, header, n);
|
||||
|
||||
/* Set TCP flag in logs. */
|
||||
daemon->log_display_id = -daemon->log_display_id;
|
||||
|
||||
@@ -2209,10 +2250,11 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, name, &server->addr,
|
||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
||||
|
||||
if ((n = tcp_talk(first, last, start, packet, n, 0, 0, &server)) == 0)
|
||||
if ((n = tcp_talk(first, last, start, header, n, &recvbuff, 0, 0, &server)) == 0)
|
||||
new_status = STAT_ABANDONED;
|
||||
else
|
||||
{
|
||||
struct dns_header *new_header = (struct dns_header *)recvbuff.iov_base;
|
||||
new_status = tcp_key_recurse(now, status, new_header, n, class, daemon->namebuff, daemon->keyname, server, 0, 0, keycount, validatecount);
|
||||
|
||||
if (STAT_ISEQUAL(status, STAT_OK))
|
||||
@@ -2223,7 +2265,7 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
|
||||
if (n >= daemon->edns_pktsz)
|
||||
{
|
||||
/* still too bIg, strip optional sections and try again. */
|
||||
/* still too big, strip optional sections and try again. */
|
||||
new_header->nscount = htons(0);
|
||||
new_header->arcount = htons(0);
|
||||
n = resize_packet(new_header, n, NULL, 0);
|
||||
@@ -2237,6 +2279,10 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
}
|
||||
}
|
||||
|
||||
/* we have succeeded and are no longer blocked talking
|
||||
on a TCP connection, so if the watchdog alarm goes off,
|
||||
ignore it. */
|
||||
daemon->forward_to_tcp = NULL;
|
||||
/* return the stripped or truncated reply. */
|
||||
memcpy(header, new_header, n);
|
||||
*plenp = n;
|
||||
@@ -2245,7 +2291,7 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
}
|
||||
|
||||
daemon->log_display_id = log_save;
|
||||
free(packet);
|
||||
free(recvbuff.iov_base);
|
||||
return new_status;
|
||||
}
|
||||
|
||||
@@ -2255,9 +2301,12 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
int have_mark, unsigned int mark, int *keycount, int *validatecount)
|
||||
{
|
||||
int first, last, start, new_status;
|
||||
unsigned char *packet = NULL;
|
||||
struct dns_header *new_header = NULL;
|
||||
struct iovec new_packet;
|
||||
struct dns_header *query_header = NULL, *new_header;
|
||||
|
||||
new_packet.iov_base = NULL;
|
||||
new_packet.iov_len = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
size_t m;
|
||||
@@ -2270,7 +2319,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
|
||||
else
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, validatecount);
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, NULL, validatecount);
|
||||
|
||||
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
|
||||
break;
|
||||
@@ -2291,34 +2340,21 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
}
|
||||
|
||||
/* Can't validate because we need a key/DS whose name now in keyname.
|
||||
Make query for same, and recurse to validate */
|
||||
if (!packet)
|
||||
{
|
||||
packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
new_header = (struct dns_header *)&packet[2];
|
||||
}
|
||||
|
||||
if (!packet)
|
||||
{
|
||||
new_status = STAT_ABANDONED;
|
||||
break;
|
||||
}
|
||||
|
||||
m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, 0,
|
||||
Make query for same in UDP packet buffer, recurse to validate*/
|
||||
query_header = (struct dns_header *)daemon->packet;
|
||||
daemon->srv_save = NULL;
|
||||
|
||||
m = dnssec_generate_query(query_header, daemon->edns_pktsz, keyname, class, 0,
|
||||
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS);
|
||||
|
||||
if ((start = dnssec_server(server, keyname, STAT_ISEQUAL(new_status, STAT_NEED_DS), &first, &last)) == -1)
|
||||
if ((start = dnssec_server(server, keyname, STAT_ISEQUAL(new_status, STAT_NEED_DS), &first, &last)) == -1 ||
|
||||
(m = tcp_talk(first, last, start, query_header, m, &new_packet, have_mark, mark, &server)) == 0)
|
||||
{
|
||||
new_status = STAT_ABANDONED;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0)
|
||||
{
|
||||
new_status = STAT_ABANDONED;
|
||||
break;
|
||||
}
|
||||
|
||||
new_header = new_packet.iov_base;
|
||||
log_save = daemon->log_display_id;
|
||||
daemon->log_display_id = -(++daemon->log_id);
|
||||
|
||||
@@ -2335,8 +2371,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet)
|
||||
free(packet);
|
||||
free(new_packet.iov_base);
|
||||
|
||||
return new_status;
|
||||
}
|
||||
@@ -2346,11 +2381,11 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
/* The daemon forks before calling this: it should deal with one connection,
|
||||
blocking as necessary, and then return. Note, need to be a bit careful
|
||||
about resources for debug mode, when the fork is suppressed: that's
|
||||
done by the caller. */
|
||||
unsigned char *tcp_request(int confd, time_t now,
|
||||
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
|
||||
done by the caller, which also frees bigbuff. */
|
||||
void tcp_request(int confd, time_t now, struct iovec *bigbuff,
|
||||
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
|
||||
{
|
||||
size_t size = 0, saved_size = 0;
|
||||
size_t size = 0;
|
||||
int norebind = 0;
|
||||
#ifdef HAVE_CONNTRACK
|
||||
int allowed = 1;
|
||||
@@ -2359,16 +2394,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
int local_auth = 0;
|
||||
#endif
|
||||
int checking_disabled, do_bit = 0, ad_reqd = 0, have_pseudoheader = 0;
|
||||
struct blockdata *saved_question = NULL;
|
||||
unsigned short qtype;
|
||||
unsigned int gotname = 0;
|
||||
/* Max TCP packet + slop + size */
|
||||
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
unsigned char *payload = &packet[2];
|
||||
u16 tcp_len;
|
||||
/* largest field in header is 16-bits, so this is still sufficiently aligned */
|
||||
struct dns_header *header = (struct dns_header *)payload;
|
||||
u16 *length = (u16 *)packet;
|
||||
u16 tcp_len, out_len;
|
||||
struct dns_header *header, *out_header;
|
||||
struct server *serv;
|
||||
struct in_addr dst_addr_4;
|
||||
union mysockaddr peer_addr;
|
||||
@@ -2377,10 +2406,14 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
unsigned char *pheader;
|
||||
unsigned int mark = 0;
|
||||
int have_mark = 0;
|
||||
int first, last, filtered, do_stale = 0;
|
||||
|
||||
if (!packet || getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
|
||||
return packet;
|
||||
int first, last, filtered, do_stale = 0, closed = 0;
|
||||
struct iovec out_iov[2];
|
||||
|
||||
bigbuff->iov_base = NULL;
|
||||
bigbuff->iov_len = 0;
|
||||
|
||||
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_CONNTRACK
|
||||
/* Get connection mark of incoming query to set on outgoing connections. */
|
||||
@@ -2401,7 +2434,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (option_bool(OPT_LOCAL_SERVICE))
|
||||
{
|
||||
struct addrlist *addr;
|
||||
|
||||
|
||||
if (peer_addr.sa.sa_family == AF_INET6)
|
||||
{
|
||||
for (addr = daemon->interface_addrs; addr; addr = addr->next)
|
||||
@@ -2424,7 +2457,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
{
|
||||
prettyprint_addr(&peer_addr, daemon->addrbuff);
|
||||
my_syslog(LOG_WARNING, _("ignoring query from non-local network %s"), daemon->addrbuff);
|
||||
return packet;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2442,18 +2475,35 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (query_count >= TCP_MAX_QUERIES)
|
||||
break;
|
||||
|
||||
/* Now get the query into the normal UDP packet buffer.
|
||||
Ignore queries longer than this. If we're answering locally,
|
||||
copy the query into the output buffer, but for forwarding, tcp_talk()
|
||||
wants the query in different buffer from the reply.
|
||||
Note that we overwrote any saved UDP query - this only matters in debug mode. */
|
||||
daemon->srv_save = NULL;
|
||||
if (!read_write(confd, (unsigned char *)&tcp_len, sizeof(tcp_len), RW_READ) ||
|
||||
!(size = ntohs(tcp_len)) ||
|
||||
!read_write(confd, payload, size, RW_READ))
|
||||
!(size = ntohs(tcp_len)) || size > (size_t)daemon->packet_buff_sz ||
|
||||
!read_write(confd, (unsigned char *)daemon->packet, size, RW_READ))
|
||||
break;
|
||||
|
||||
if (size < (int)sizeof(struct dns_header))
|
||||
|
||||
/* header == query */
|
||||
header = (struct dns_header *)daemon->packet;
|
||||
|
||||
if (size < (int)sizeof(struct dns_header) || (header->hb3 & HB3_QR))
|
||||
continue;
|
||||
|
||||
/* Clear buffer beyond request to avoid risk of
|
||||
information disclosure. */
|
||||
memset(payload + size, 0, 65536 - size);
|
||||
/* Make sure we have a buffer big enough for the largest answer. */
|
||||
expand_buf(bigbuff, 65536 + MAXDNAME + RRFIXEDSZ);
|
||||
out_header = bigbuff->iov_base;
|
||||
|
||||
/* Add edns0 pheader to query */
|
||||
size = add_edns0_config(header, size, daemon->packet_buff_sz, &peer_addr, now, &cacheable);
|
||||
|
||||
/* Clear buffer to avoid risk of information disclosure. */
|
||||
memset(bigbuff->iov_base, 0, bigbuff->iov_len);
|
||||
/* Copy query into output buffer for local answering */
|
||||
memcpy(out_header, header, size);
|
||||
|
||||
query_count++;
|
||||
|
||||
/* log_query gets called indirectly all over the place, so
|
||||
@@ -2472,9 +2522,6 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
ede = EDE_INVALID_DATA;
|
||||
else
|
||||
{
|
||||
if (saved_question)
|
||||
blockdata_free(saved_question);
|
||||
|
||||
do_bit = 0;
|
||||
|
||||
if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
|
||||
@@ -2489,10 +2536,6 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
do_bit = 1; /* do bit */
|
||||
}
|
||||
|
||||
size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &cacheable);
|
||||
saved_question = blockdata_alloc((char *)header, (size_t)size);
|
||||
saved_size = size;
|
||||
|
||||
log_query_mysockaddr((auth_dns ? F_NOERR | F_AUTH : 0) | F_QUERY | F_FORWARD, daemon->namebuff,
|
||||
&peer_addr, NULL, qtype);
|
||||
|
||||
@@ -2545,15 +2588,15 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
else if (!allowed)
|
||||
{
|
||||
ede = EDE_BLOCKED;
|
||||
m = answer_disallowed(header, size, (u32)mark, daemon->namebuff);
|
||||
m = answer_disallowed(out_header, size, (u32)mark, daemon->namebuff);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_AUTH
|
||||
else if (auth_dns)
|
||||
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth);
|
||||
m = answer_auth(out_header, ((char *) out_header) + 65536, (size_t)size, now, &peer_addr, local_auth);
|
||||
#endif
|
||||
else
|
||||
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
|
||||
m = answer_request(out_header, ((char *) out_header) + 65536, (size_t)size,
|
||||
dst_addr_4, netmask, now, ad_reqd, do_bit, !cacheable, &stale, &filtered);
|
||||
}
|
||||
}
|
||||
@@ -2561,15 +2604,12 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
/* Do this by steam now we're not in the select() loop */
|
||||
check_log_writer(1);
|
||||
|
||||
if (m == 0 && ede == EDE_UNSET && saved_question)
|
||||
if (!flags && m == 0 && ede == EDE_UNSET)
|
||||
{
|
||||
struct server *master;
|
||||
int start;
|
||||
int no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
|
||||
|
||||
blockdata_retrieve(saved_question, (size_t)saved_size, header);
|
||||
size = saved_size;
|
||||
|
||||
/* save state of "cd" flag in query */
|
||||
checking_disabled = header->hb4 & HB4_CD;
|
||||
|
||||
@@ -2598,7 +2638,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
|
||||
size = add_do_bit(header, size, daemon->edns_pktsz);
|
||||
|
||||
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
||||
this allows it to select auth servers when one is returning bad data. */
|
||||
@@ -2608,12 +2648,14 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
#endif
|
||||
|
||||
/* Loop round available servers until we succeed in connecting to one. */
|
||||
if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0)
|
||||
if ((m = tcp_talk(first, last, start, header, size, bigbuff, have_mark, mark, &serv)) == 0)
|
||||
ede = EDE_NETERR;
|
||||
else
|
||||
{
|
||||
/* just in case tcp_talk() expanded buffer - should never happen */
|
||||
out_header = bigbuff->iov_base;
|
||||
/* get query name again for logging - may have been overwritten */
|
||||
if (!extract_name(header, (unsigned int)size, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
if (!extract_name(out_header, (unsigned int)size, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
strcpy(daemon->namebuff, "query");
|
||||
log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL, 0);
|
||||
|
||||
@@ -2629,7 +2671,8 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
{
|
||||
int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||
int validatecount = daemon->limit[LIMIT_CRYPTO];
|
||||
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
|
||||
/* tcp_key_recurse() may overwrite packetbuf, and thuse *header is now invalid */
|
||||
int status = tcp_key_recurse(now, STAT_OK, out_header, m, 0, daemon->namebuff, daemon->keyname,
|
||||
serv, have_mark, mark, &keycount, &validatecount);
|
||||
char *result, *domain = "result";
|
||||
|
||||
@@ -2655,12 +2698,14 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
no_cache_dnssec = 1;
|
||||
bogusanswer = 1;
|
||||
|
||||
if (extract_name(header, m, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
if (extract_name(out_header, m, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
domain = daemon->namebuff;
|
||||
}
|
||||
|
||||
a.log.ede = ede;
|
||||
log_query(F_SECSTAT, domain, &a, result, 0);
|
||||
if (ede == EDE_US_SERVFAIL)
|
||||
ede = EDE_DNSSEC_BOGUS;
|
||||
|
||||
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
|
||||
@@ -2677,18 +2722,18 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
/* restore CD bit to the value in the query */
|
||||
if (checking_disabled)
|
||||
header->hb4 |= HB4_CD;
|
||||
out_header->hb4 |= HB4_CD;
|
||||
else
|
||||
header->hb4 &= ~HB4_CD;
|
||||
out_header->hb4 &= ~HB4_CD;
|
||||
|
||||
/* Never cache answers which are contingent on the source or MAC address EDSN0 option,
|
||||
since the cache is ignorant of such things. */
|
||||
if (!cacheable)
|
||||
no_cache_dnssec = 1;
|
||||
|
||||
m = process_reply(header, now, serv, (unsigned int)m,
|
||||
m = process_reply(out_header, now, serv, (unsigned int)m,
|
||||
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
|
||||
ad_reqd, do_bit, !have_pseudoheader, &peer_addr, ((unsigned char *)header) + 65536, ede);
|
||||
ad_reqd, do_bit, !have_pseudoheader, &peer_addr, 65536, ede);
|
||||
|
||||
/* process_reply() adds pheader itself */
|
||||
have_pseudoheader = 0;
|
||||
@@ -2697,14 +2742,22 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
}
|
||||
}
|
||||
|
||||
/* Fresh data is in the cache now. Go on with the queries the client
|
||||
had already sent, if the connection is still open. */
|
||||
if (do_stale)
|
||||
break;
|
||||
{
|
||||
if (closed)
|
||||
break;
|
||||
do_stale = 0;
|
||||
daemon->log_source_addr = &peer_addr;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* In case of local answer or no connections made. */
|
||||
if (m == 0)
|
||||
{
|
||||
if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff,
|
||||
((char *) header) + 65536, first, last, ede)))
|
||||
if (!(m = make_local_answer(flags, gotname, size, out_header, daemon->namebuff,
|
||||
65536, first, last, ede)))
|
||||
break;
|
||||
}
|
||||
else if (ede == EDE_UNSET)
|
||||
@@ -2720,14 +2773,12 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
u16 swap = htons((u16)ede);
|
||||
|
||||
if (ede != EDE_UNSET)
|
||||
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
|
||||
m = add_pseudoheader(out_header, m, 65536, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
|
||||
else
|
||||
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, 0, NULL, 0, do_bit, 0);
|
||||
m = add_pseudoheader(out_header, m, 65536, 0, NULL, 0, do_bit, 0);
|
||||
}
|
||||
|
||||
check_log_writer(1);
|
||||
|
||||
*length = htons(m);
|
||||
check_log_writer(1);
|
||||
|
||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||
#ifdef HAVE_AUTH
|
||||
@@ -2737,34 +2788,45 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
report_addresses(header, m, mark);
|
||||
#endif
|
||||
|
||||
if (!read_write(confd, packet, m + sizeof(u16), RW_WRITE))
|
||||
/* use scatter-gather IO so that length doesn't end up in separate packet. */
|
||||
out_len = htons(m);
|
||||
out_iov[0].iov_len = sizeof(out_len);
|
||||
out_iov[0].iov_base = &out_len;
|
||||
out_iov[1].iov_len = m;
|
||||
out_iov[1].iov_base = bigbuff->iov_base;
|
||||
if (!read_writev(confd, out_iov, 2, RW_WRITE))
|
||||
break;
|
||||
|
||||
/* If we answered with stale data, this process will now try and get fresh data into
|
||||
the cache and cannot therefore accept new queries. Close the incoming
|
||||
connection to signal that to the client. Then set do_stale and loop round
|
||||
once more to try and get fresh data, after which we exit. */
|
||||
once more to try and get fresh data, after which we exit. A client which
|
||||
has already sent its next query is waiting for the answer and would lose
|
||||
it, so keep the connection open then and answer it after the refresh. */
|
||||
if (stale)
|
||||
{
|
||||
shutdown(confd, SHUT_RDWR);
|
||||
close(confd);
|
||||
char c;
|
||||
|
||||
if (recv(confd, &c, 1, MSG_PEEK | MSG_DONTWAIT) != 1)
|
||||
{
|
||||
shutdown(confd, SHUT_RDWR);
|
||||
close(confd);
|
||||
closed = 1;
|
||||
}
|
||||
do_stale = 1;
|
||||
/* Don't mark the query with the source when refreshing stale data. */
|
||||
daemon->log_source_addr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we ran once to get fresh data, confd is already closed. */
|
||||
if (!do_stale)
|
||||
|
||||
/* If we ran once to get fresh data, confd may be closed already. */
|
||||
if (!closed)
|
||||
{
|
||||
shutdown(confd, SHUT_RDWR);
|
||||
close(confd);
|
||||
}
|
||||
|
||||
blockdata_free(saved_question);
|
||||
|
||||
check_log_writer(1);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/* return a UDP socket bound to a random port, have to cope with straying into
|
||||
@@ -3265,7 +3327,7 @@ void resend_query(void)
|
||||
{
|
||||
if (daemon->srv_save)
|
||||
server_send(daemon->srv_save, daemon->fd_save,
|
||||
daemon->packet, daemon->packet_len, 0);
|
||||
daemon->packet, daemon->packet_len);
|
||||
}
|
||||
|
||||
/* A server record is going away, remove references to it */
|
||||
|
||||
+14
-24
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -253,7 +253,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
|
||||
}
|
||||
|
||||
/* supplied data may just exceed normal buffer (unlikely) */
|
||||
if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME &&
|
||||
if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAMESTR &&
|
||||
!(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len)))
|
||||
continue;
|
||||
|
||||
@@ -261,8 +261,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
|
||||
data.hostname_len + data.ed_len + data.clid_len, RW_READ))
|
||||
continue;
|
||||
|
||||
/* CLID into packet */
|
||||
for (p = daemon->packet, i = 0; i < data.clid_len; i++)
|
||||
/* CLID into packet: limit to 100 bytes to avoid overflowing buffer. */
|
||||
for (p = daemon->packet, i = 0; i < data.clid_len && i < 100; i++)
|
||||
{
|
||||
p += sprintf(p, "%.2x", buf[i]);
|
||||
if (i != data.clid_len - 1)
|
||||
@@ -533,18 +533,12 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
|
||||
close(pipeout[0]);
|
||||
else
|
||||
{
|
||||
while (fgets(daemon->packet, daemon->packet_buff_sz, fp))
|
||||
{
|
||||
/* do not include new lines, log will append them */
|
||||
size_t len = strlen(daemon->packet);
|
||||
if (len > 0)
|
||||
{
|
||||
--len;
|
||||
if (daemon->packet[len] == '\n')
|
||||
daemon->packet[len] = 0;
|
||||
}
|
||||
send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet);
|
||||
}
|
||||
char *line = NULL;
|
||||
size_t linesz = 0;
|
||||
|
||||
while (get_line_alloc(fp, &line, &linesz))
|
||||
send_event(event_fd, EVENT_SCRIPT_LOG, 0, line);
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
@@ -718,13 +712,7 @@ static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, ch
|
||||
break;
|
||||
|
||||
if (next && (next != buf))
|
||||
{
|
||||
char *p;
|
||||
/* No "=" in value */
|
||||
if ((p = strchr((char *)buf, '=')))
|
||||
*p = 0;
|
||||
val = (char *)buf;
|
||||
}
|
||||
val = (char *)buf;
|
||||
}
|
||||
|
||||
my_setenv(env, val, err);
|
||||
@@ -740,9 +728,11 @@ static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end,
|
||||
if (!buf || (buf == end))
|
||||
return NULL;
|
||||
|
||||
for (next = buf; *next != 0; next++)
|
||||
for (next = buf; ; next++)
|
||||
if (next == end)
|
||||
return NULL;
|
||||
else if (*next == 0)
|
||||
break;
|
||||
|
||||
if (next != buf)
|
||||
{
|
||||
|
||||
+26
-11
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -40,7 +40,7 @@ static char *inotify_buffer;
|
||||
points to, made absolute if relative.
|
||||
If path doesn't exist or is not a symlink, return NULL.
|
||||
Return value is malloc'ed */
|
||||
static char *my_readlink(char *path)
|
||||
static char *my_readlink(int errfd, char *path)
|
||||
{
|
||||
ssize_t rc, size = 64;
|
||||
char *buf;
|
||||
@@ -59,7 +59,10 @@ static char *my_readlink(char *path)
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
die(_("cannot access path %s: %s"), path, EC_MISC);
|
||||
{
|
||||
send_event(errfd, EVENT_LINK_ERR, errno, path);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
else if (rc < size-1)
|
||||
{
|
||||
@@ -85,15 +88,18 @@ static char *my_readlink(char *path)
|
||||
}
|
||||
}
|
||||
|
||||
void inotify_dnsmasq_init()
|
||||
void inotify_dnsmasq_init(int errfd)
|
||||
{
|
||||
struct resolvc *res;
|
||||
inotify_buffer = safe_malloc(INOTIFY_SZ);
|
||||
daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
|
||||
if (daemon->inotifyfd == -1)
|
||||
die(_("failed to create inotify: %s"), NULL, EC_MISC);
|
||||
|
||||
{
|
||||
send_event(errfd, EVENT_INOTFY_ERR, errno, NULL);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
if (daemon->port == 0 || option_bool(OPT_NO_RESOLV))
|
||||
return;
|
||||
|
||||
@@ -105,10 +111,13 @@ void inotify_dnsmasq_init()
|
||||
strcpy(path, res->name);
|
||||
|
||||
/* Follow symlinks until we reach a non-symlink, or a non-existent file. */
|
||||
while ((new_path = my_readlink(path)))
|
||||
while ((new_path = my_readlink(errfd, path)))
|
||||
{
|
||||
if (links-- == 0)
|
||||
die(_("too many symlinks following %s"), res->name, EC_MISC);
|
||||
{
|
||||
send_event(errfd, EVENT_TMSL_ERR, 0, res->name);
|
||||
_exit(0);
|
||||
}
|
||||
free(path);
|
||||
path = new_path;
|
||||
}
|
||||
@@ -124,12 +133,18 @@ void inotify_dnsmasq_init()
|
||||
*d = '/';
|
||||
|
||||
if (res->wd == -1 && errno == ENOENT)
|
||||
die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
|
||||
{
|
||||
send_event(errfd, EVENT_RESOLV_ERR, 0, res->name);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (res->wd == -1)
|
||||
die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
|
||||
|
||||
{
|
||||
send_event(errfd, EVENT_IFILE_ERR, errno, res->name);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+25
-15
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -35,7 +35,7 @@ static int read_leases(time_t now, FILE *leasestream)
|
||||
|
||||
Check various buffers are big enough for the code below */
|
||||
|
||||
#if (DHCP_BUFF_SZ < 255) || (MAXDNAME < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764)
|
||||
#if (DHCP_BUFF_SZ < 255) || (MAXDNAMESTR < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764)
|
||||
# error Buffer size breakage in leasefile parsing.
|
||||
#endif
|
||||
|
||||
@@ -73,7 +73,9 @@ static int read_leases(time_t now, FILE *leasestream)
|
||||
if (lease)
|
||||
{
|
||||
opt_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
|
||||
|
||||
if (opt_len < 0)
|
||||
continue;
|
||||
|
||||
if (strcmp(daemon->dhcp_buff3, "vendorclass") == 0)
|
||||
lease_set_vendorclass(lease, (unsigned char *)daemon->packet, opt_len);
|
||||
else if (strcmp(daemon->dhcp_buff3, "agent-info") == 0)
|
||||
@@ -99,6 +101,8 @@ static int read_leases(time_t now, FILE *leasestream)
|
||||
|
||||
|
||||
hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type);
|
||||
if (hw_len < 0)
|
||||
hw_len = 0;
|
||||
/* For backwards compatibility, no explicit MAC address type means ether. */
|
||||
if (hw_type == 0 && hw_len != 0)
|
||||
hw_type = ARPHRD_ETHER;
|
||||
@@ -131,7 +135,11 @@ static int read_leases(time_t now, FILE *leasestream)
|
||||
die (_("too many stored leases"), NULL, EC_MISC);
|
||||
|
||||
if (strcmp(daemon->packet, "*") != 0)
|
||||
clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
|
||||
{
|
||||
clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
|
||||
if (clid_len < 0)
|
||||
clid_len = 0;
|
||||
}
|
||||
|
||||
lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet,
|
||||
hw_len, hw_type, clid_len, now, 0);
|
||||
@@ -519,7 +527,7 @@ void lease_update_slaac(time_t now)
|
||||
|
||||
/* Find interfaces associated with leases at start-up. This gets updated as
|
||||
we do DHCP transactions, but information about directly-connected subnets
|
||||
is useful from scrips and necessary for determining SLAAC addresses from
|
||||
is useful from scripts and necessary for determining SLAAC addresses from
|
||||
start-time. */
|
||||
void lease_find_interfaces(time_t now)
|
||||
{
|
||||
@@ -1019,6 +1027,13 @@ static void kill_name(struct dhcp_lease *lease)
|
||||
lease->old_hostname = lease->hostname;
|
||||
|
||||
lease->hostname = lease->fqdn = NULL;
|
||||
|
||||
#ifdef HAVE_DHCP6
|
||||
/* A lease which had a hostname when slaac_add_addrs() was called on it
|
||||
may have slaac-address records. Since it no longer has a hostname,
|
||||
they are no-longer valid, so update to remove them */
|
||||
slaac_add_addrs(lease, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void lease_calc_fqdns(void)
|
||||
@@ -1143,6 +1158,10 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch
|
||||
file_dirty = 1;
|
||||
dns_dirty = 1;
|
||||
lease->flags |= LEASE_CHANGED; /* run script on change */
|
||||
#ifdef HAVE_DHCP6
|
||||
slaac_add_addrs(lease, dnsmasq_time(), 0);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
|
||||
@@ -1242,15 +1261,6 @@ int do_script_run(time_t now)
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_DHCP6
|
||||
struct slaac_address *slaac, *tmp;
|
||||
for (slaac = lease->slaac_address; slaac; slaac = tmp)
|
||||
{
|
||||
tmp = slaac->next;
|
||||
free(slaac);
|
||||
}
|
||||
#endif
|
||||
kill_name(lease);
|
||||
#ifdef HAVE_SCRIPT
|
||||
queue_script(ACTION_DEL, lease, lease->old_hostname, now);
|
||||
#endif
|
||||
@@ -1259,7 +1269,7 @@ int do_script_run(time_t now)
|
||||
#endif
|
||||
old_leases = lease->next;
|
||||
|
||||
free(lease->hostname);
|
||||
kill_name(lease); /* frees slaac_addr, hostname and fqdn */
|
||||
free(lease->clid);
|
||||
free(lease->extradata);
|
||||
free(lease->agent_id);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -97,27 +97,30 @@ int log_start(struct passwd *ent_pw, int errfd)
|
||||
entries_alloced = 1;
|
||||
}
|
||||
|
||||
/* If we're running as root and going to change uid later,
|
||||
change the ownership here so that the file is always owned by
|
||||
the dnsmasq user. Then logrotate can just copy the owner.
|
||||
Failure of the chown call is OK, (for instance when started as non-root).
|
||||
|
||||
If we've created a file with group-id root, we also make
|
||||
the file group-writable. This gives processes in the root group
|
||||
write access to the file and avoids the problem that on some systems,
|
||||
once the file is owned by the dnsmasq user, it can't be written
|
||||
whilst dnsmasq is running as root during startup.
|
||||
*/
|
||||
if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0)
|
||||
{
|
||||
struct stat ls;
|
||||
if (getgid() == 0 && fstat(log_fd, &ls) == 0 && ls.st_gid == 0 &&
|
||||
(ls.st_mode & S_IWGRP) == 0)
|
||||
(void)fchmod(log_fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
|
||||
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
|
||||
ret = errno;
|
||||
|
||||
/* Only mess with permissions for regular files, not (eg) /dev/null */
|
||||
if (fstat(log_fd, &ls) == 0 && S_ISREG(ls.st_mode))
|
||||
{
|
||||
/* If we're running as root and going to change uid later,
|
||||
change the ownership here so that the file is always owned by
|
||||
the dnsmasq user. Then logrotate can just copy the owner. */
|
||||
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
|
||||
ret = errno;
|
||||
|
||||
/* If we've created a file with group-id root, we also make
|
||||
the file group-writable. This gives processes in the root group
|
||||
write access to the file and avoids the problem that on some systems,
|
||||
once the file is owned by the dnsmasq user, it can't be written
|
||||
whilst dnsmasq is running as root during startup.
|
||||
Failure of the chown call is OK, (for instance when started as non-root). */
|
||||
if (getgid() == 0 && ls.st_gid == 0 && (ls.st_mode & S_IWGRP) == 0)
|
||||
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+14
-19
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -1254,19 +1254,14 @@ void warn_bound_listeners(void)
|
||||
int advice = 0;
|
||||
|
||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||
if (!iface->dns_auth)
|
||||
if (!iface->dns_auth && !iface->warned &&
|
||||
iface->addr.sa.sa_family == AF_INET && !private_net(iface->addr.in.sin_addr, 1))
|
||||
{
|
||||
if (iface->addr.sa.sa_family == AF_INET)
|
||||
{
|
||||
if (!private_net(iface->addr.in.sin_addr, 1))
|
||||
{
|
||||
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
|
||||
iface->warned = advice = 1;
|
||||
my_syslog(LOG_WARNING,
|
||||
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
|
||||
daemon->addrbuff, iface->name);
|
||||
}
|
||||
}
|
||||
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
|
||||
iface->warned = advice = 1;
|
||||
my_syslog(LOG_WARNING,
|
||||
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
|
||||
daemon->addrbuff, iface->name);
|
||||
}
|
||||
|
||||
if (advice)
|
||||
@@ -1462,7 +1457,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsi
|
||||
|
||||
/* when using random ports, servers which would otherwise use
|
||||
the INADDR_ANY/port0 socket have sfd set to NULL, this is
|
||||
anything without an explictly set source port. */
|
||||
anything without an explicitly set source port. */
|
||||
if (!daemon->osport)
|
||||
{
|
||||
errno = 0;
|
||||
@@ -1699,19 +1694,19 @@ void check_servers(int no_loop_check)
|
||||
int reload_servers(char *fname)
|
||||
{
|
||||
FILE *f;
|
||||
char *line;
|
||||
char *line = NULL;
|
||||
size_t linesz = 0;
|
||||
int gotone = 0;
|
||||
|
||||
/* buff happens to be MAXDNAME long... */
|
||||
if (!(f = fopen(fname, "r")))
|
||||
{
|
||||
my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
mark_servers(SERV_FROM_RESOLV);
|
||||
|
||||
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
|
||||
|
||||
while (get_line_alloc(f, &line, &linesz))
|
||||
{
|
||||
union mysockaddr addr, source_addr;
|
||||
char *token = strtok(line, " \t\n\r");
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+161
-115
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -23,6 +23,9 @@ static volatile int mem_recover = 0;
|
||||
static jmp_buf mem_jmp;
|
||||
static int one_file(char *file, int hard_opt);
|
||||
|
||||
static void *opt_malloc_real(const char *func, unsigned int line, size_t size);
|
||||
#define opt_malloc(x) opt_malloc_real(__func__, __LINE__, (x))
|
||||
|
||||
/* Solaris headers don't have facility names. */
|
||||
#ifdef HAVE_SOLARIS_NETWORK
|
||||
static const struct {
|
||||
@@ -197,6 +200,7 @@ struct myoption {
|
||||
#define LOPT_DO_ENCODE 388
|
||||
#define LOPT_LEASEQUERY 389
|
||||
#define LOPT_SPLIT_RELAY 390
|
||||
#define LOPT_LOG_MALLOC 391
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
static const struct option opts[] =
|
||||
@@ -398,6 +402,7 @@ static const struct myoption opts[] =
|
||||
{ "no-ident", 0, 0, LOPT_NO_IDENT },
|
||||
{ "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
|
||||
{ "leasequery", 2, 0, LOPT_LEASEQUERY },
|
||||
{ "log-malloc", 0, 0, LOPT_LOG_MALLOC },
|
||||
{ NULL, 0, 0, 0 }
|
||||
};
|
||||
|
||||
@@ -606,6 +611,7 @@ static struct {
|
||||
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
|
||||
{ LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
|
||||
{ LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
|
||||
{ LOPT_LOG_MALLOC, OPT_LOG_MALLOC, NULL, gettext_noop("Log memory allocation for debugging."), NULL },
|
||||
{ 0, 0, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
@@ -652,13 +658,13 @@ static void unhide_metas(char *cp)
|
||||
*cp = unhide_meta(*cp);
|
||||
}
|
||||
|
||||
static void *opt_malloc(size_t size)
|
||||
static void *opt_malloc_real(const char *func, unsigned int line, size_t size)
|
||||
{
|
||||
void *ret;
|
||||
|
||||
if (mem_recover)
|
||||
{
|
||||
ret = whine_malloc(size);
|
||||
ret = whine_malloc_real(func, line, size);
|
||||
if (!ret)
|
||||
longjmp(mem_jmp, 1);
|
||||
}
|
||||
@@ -739,7 +745,7 @@ static int numeric_check(char *a)
|
||||
{
|
||||
char *p;
|
||||
|
||||
if (!a)
|
||||
if (!a || *a == 0)
|
||||
return 0;
|
||||
|
||||
unhide_metas(a);
|
||||
@@ -765,11 +771,14 @@ static int strtoul_check(char *a, u32 *res)
|
||||
|
||||
if (!numeric_check(a))
|
||||
return 0;
|
||||
|
||||
x = strtoul(a, NULL, 10);
|
||||
if (errno || x > UINT32_MAX) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
if (errno || x > UINT32_MAX)
|
||||
{
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*res = (u32)x;
|
||||
return 1;
|
||||
}
|
||||
@@ -1377,7 +1386,7 @@ static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
|
||||
netid = netid->next;
|
||||
/* Note: don't use dhcp_netid_free() here, since that
|
||||
frees a list linked on netid->next. Where a netid_list
|
||||
is used that's because the the ->next pointers in the
|
||||
is used that's because the ->next pointers in the
|
||||
netids are being used to temporarily construct
|
||||
a list of valid tags. */
|
||||
free(tmplist->list->net);
|
||||
@@ -1465,58 +1474,45 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
||||
|
||||
while (arg)
|
||||
{
|
||||
char *start = arg;
|
||||
comma = split(arg);
|
||||
|
||||
for (cp = arg; *cp; cp++)
|
||||
|
||||
if (strstr(arg, "option:") == arg)
|
||||
start = arg+7;
|
||||
#ifdef HAVE_DHCP6
|
||||
else if (strstr(arg, "option6:") == arg)
|
||||
{
|
||||
start = arg+8;
|
||||
is6 = 1;
|
||||
}
|
||||
#endif
|
||||
else if (strstr(arg, "option4:") == arg)
|
||||
start = arg+8;
|
||||
|
||||
for (cp = start; *cp; cp++)
|
||||
if (*cp < '0' || *cp > '9')
|
||||
break;
|
||||
|
||||
if (!*cp)
|
||||
{
|
||||
new->opt = atoi(arg);
|
||||
new->opt = atoi(start);
|
||||
opt_len = 0;
|
||||
option_ok = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (strstr(arg, "option:") == arg)
|
||||
|
||||
/* option*:<opt>|<optname> must follow tag and vendor string. */
|
||||
if (start != arg)
|
||||
{
|
||||
if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
|
||||
if ((new->opt = lookup_dhcp_opt(is6 ? AF_INET6: AF_INET, start)) != -1)
|
||||
{
|
||||
opt_len = lookup_dhcp_len(AF_INET, new->opt);
|
||||
opt_len = lookup_dhcp_len(is6 ? AF_INET6: AF_INET, new->opt);
|
||||
/* option:<optname> must follow tag and vendor string. */
|
||||
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
|
||||
option_ok = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_DHCP6
|
||||
else if (strstr(arg, "option6:") == arg)
|
||||
{
|
||||
for (cp = arg+8; *cp; cp++)
|
||||
if (*cp < '0' || *cp > '9')
|
||||
break;
|
||||
|
||||
if (!*cp)
|
||||
{
|
||||
new->opt = atoi(arg+8);
|
||||
opt_len = 0;
|
||||
option_ok = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
|
||||
{
|
||||
opt_len = lookup_dhcp_len(AF_INET6, new->opt);
|
||||
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
|
||||
option_ok = 1;
|
||||
}
|
||||
}
|
||||
/* option6:<opt>|<optname> must follow tag and vendor string. */
|
||||
is6 = 1;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
else if (strstr(arg, "vendor:") == arg)
|
||||
{
|
||||
new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
|
||||
@@ -1695,7 +1691,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
||||
parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL);
|
||||
new->flags |= DHOPT_HEX;
|
||||
}
|
||||
else if (is_dec)
|
||||
else if (is_dec && !(opt_len & OT_DHCP6_VENDOR))
|
||||
{
|
||||
int i, val = atoi(comma);
|
||||
/* assume numeric arg is 1 byte except for
|
||||
@@ -1896,6 +1892,48 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
||||
comma = split(arg);
|
||||
}
|
||||
|
||||
new->val = newp;
|
||||
new->len = p - newp;
|
||||
}
|
||||
else if (comma && (opt_len & OT_DHCP6_VENDOR))
|
||||
{
|
||||
/* First arg is Enterprise ID (4 bytes)
|
||||
subsequent are length fields (2 bytes each) + string */
|
||||
int enterprise, i, commas = 1;
|
||||
unsigned char *p, *newp;
|
||||
|
||||
for (i = 0; comma[i]; i++)
|
||||
if (comma[i] == ',')
|
||||
commas++;
|
||||
|
||||
newp = opt_malloc(strlen(comma)+(2*commas)+4);
|
||||
p = newp;
|
||||
arg = comma;
|
||||
comma = split(arg);
|
||||
|
||||
if (atoi_check(arg, &enterprise))
|
||||
PUTLONG(enterprise, p);
|
||||
else
|
||||
goto_err(_("bad or missing enterprise ID in dhcp-option"));
|
||||
|
||||
arg = comma;
|
||||
comma = split(arg);
|
||||
|
||||
if (!arg || !*arg)
|
||||
goto_err(_("missing vendor class in dhcp-option"));
|
||||
|
||||
while (arg && *arg)
|
||||
{
|
||||
u16 len = strlen(arg);
|
||||
unhide_metas(arg);
|
||||
PUTSHORT(len, p);
|
||||
memcpy(p, arg, len);
|
||||
p += len;
|
||||
|
||||
arg = comma;
|
||||
comma = split(arg);
|
||||
}
|
||||
|
||||
new->val = newp;
|
||||
new->len = p - newp;
|
||||
}
|
||||
@@ -1959,7 +1997,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
||||
|
||||
if (flags == DHOPT_PXE_OPT && (new->flags & DHOPT_VENDOR))
|
||||
goto_err(_("No vendor-encap options allowed in dhcp-option-pxe"));
|
||||
|
||||
|
||||
if (flags == DHOPT_MATCH)
|
||||
{
|
||||
if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
|
||||
@@ -2794,7 +2832,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
break;
|
||||
}
|
||||
|
||||
case LOPT_CPE_ID: /* --add-dns-client */
|
||||
case LOPT_CPE_ID: /* --add-cpe-id */
|
||||
if (arg)
|
||||
daemon->dns_client_id = opt_string_alloc(arg);
|
||||
break;
|
||||
@@ -3463,6 +3501,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
}
|
||||
else if (strcmp(arg, "auth") == 0)
|
||||
set_option_bool(OPT_AUTH_LOG);
|
||||
else if (strcmp(arg, "only_failed") == 0)
|
||||
set_option_bool(OPT_LOG_ONLY_FAILED);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4572,39 +4612,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
only allowed for agent-options. */
|
||||
|
||||
arg = comma;
|
||||
if ((comma = split(arg)))
|
||||
if (option == 'U' && strstr(arg, "enterprise:") == arg)
|
||||
{
|
||||
if (option != 'U' || strstr(arg, "enterprise:") != arg)
|
||||
comma = split(arg);
|
||||
new->enterprise = atoi(arg+11);
|
||||
arg = comma;
|
||||
}
|
||||
|
||||
if (arg)
|
||||
{
|
||||
for (dig = 0, colon = 0, p = (unsigned char *)arg; *p; p++)
|
||||
if (isxdigit(*p))
|
||||
dig = 1;
|
||||
else if (*p == ':')
|
||||
colon = 1;
|
||||
else
|
||||
break;
|
||||
|
||||
unhide_metas(arg);
|
||||
if (option == 'U' || option == 'j' || *p || !dig || !colon)
|
||||
{
|
||||
free(new->netid.net);
|
||||
ret_err_free(gen_err, new);
|
||||
new->len = strlen(arg);
|
||||
new->data = opt_malloc(new->len);
|
||||
memcpy(new->data, arg, new->len);
|
||||
}
|
||||
else
|
||||
new->enterprise = atoi(arg+11);
|
||||
{
|
||||
new->len = parse_hex(comma, (unsigned char *)arg, strlen(arg), NULL, NULL);
|
||||
new->data = opt_malloc(new->len);
|
||||
memcpy(new->data, arg, new->len);
|
||||
}
|
||||
}
|
||||
else
|
||||
comma = arg;
|
||||
|
||||
for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++)
|
||||
if (isxdigit(*p))
|
||||
dig = 1;
|
||||
else if (*p == ':')
|
||||
colon = 1;
|
||||
else
|
||||
break;
|
||||
|
||||
unhide_metas(comma);
|
||||
if (option == 'U' || option == 'j' || *p || !dig || !colon)
|
||||
else if (option != 'U' || new->enterprise == 0)
|
||||
{
|
||||
new->len = strlen(comma);
|
||||
new->data = opt_malloc(new->len);
|
||||
memcpy(new->data, comma, new->len);
|
||||
}
|
||||
else
|
||||
{
|
||||
new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL);
|
||||
new->data = opt_malloc(new->len);
|
||||
memcpy(new->data, comma, new->len);
|
||||
free(new->netid.net);
|
||||
ret_err_free(gen_err, new);
|
||||
}
|
||||
|
||||
switch (option)
|
||||
@@ -4627,7 +4669,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
}
|
||||
new->next = daemon->dhcp_vendors;
|
||||
daemon->dhcp_vendors = new;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4950,7 +4992,8 @@ err:
|
||||
arg = NULL; /* provoke error below */
|
||||
}
|
||||
|
||||
if (!domain || !arg || !(new->name = canonicalise_opt(domain)))
|
||||
if (!domain || !arg || !new->intr ||
|
||||
!(new->name = canonicalise_opt(domain)))
|
||||
ret_err(option == LOPT_DYNHOST ?
|
||||
_("bad dynamic host") : _("bad interface name"));
|
||||
|
||||
@@ -5003,7 +5046,7 @@ err:
|
||||
new->target = target;
|
||||
new->ttl = ttl;
|
||||
|
||||
for (arg += arglen+1; *arg && isspace((unsigned char)*arg); arg++);
|
||||
for (arg += arglen+1; arg != last && (!*arg || isspace((unsigned char)*arg)); arg++);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -5460,9 +5503,20 @@ err:
|
||||
static void read_file(char *file, FILE *f, int hard_opt, int from_script)
|
||||
{
|
||||
volatile int lineno = 0;
|
||||
char *buff = daemon->namebuff;
|
||||
char *buff;
|
||||
size_t buffsz;
|
||||
|
||||
/* Memory allocation failure longjmps here if mem_recover == 1 */
|
||||
if (hard_opt != 0)
|
||||
{
|
||||
setjmp(mem_jmp);
|
||||
mem_recover = 1;
|
||||
}
|
||||
|
||||
while (fgets(buff, MAXDNAME, f))
|
||||
buff = NULL;
|
||||
buffsz = 0;
|
||||
|
||||
while (get_line_alloc(f, &buff, &buffsz))
|
||||
{
|
||||
int white, i;
|
||||
volatile int option;
|
||||
@@ -5470,15 +5524,7 @@ static void read_file(char *file, FILE *f, int hard_opt, int from_script)
|
||||
size_t len;
|
||||
|
||||
option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
|
||||
|
||||
/* Memory allocation failure longjmps here if mem_recover == 1 */
|
||||
if (option != 0 || hard_opt == LOPT_REV_SERV)
|
||||
{
|
||||
if (setjmp(mem_jmp))
|
||||
continue;
|
||||
mem_recover = 1;
|
||||
}
|
||||
|
||||
|
||||
arg = NULL;
|
||||
lineno++;
|
||||
errmess = NULL;
|
||||
@@ -5729,7 +5775,7 @@ struct hostsfile *expand_filelist(struct hostsfile *list)
|
||||
struct dirent **namelist;
|
||||
|
||||
/* find largest used index */
|
||||
for (i = SRC_AH, ah = list; ah; ah = ah->next)
|
||||
for (last = NULL, i = SRC_AH, ah = list; ah; ah = ah->next)
|
||||
{
|
||||
last = ah;
|
||||
|
||||
@@ -5937,23 +5983,17 @@ void reread_dhcp(void)
|
||||
|
||||
void read_opts(int argc, char **argv, char *compile_opts)
|
||||
{
|
||||
size_t argbuf_size = MAXDNAME;
|
||||
char *argbuf = opt_malloc(argbuf_size);
|
||||
/* Note that both /000 and '.' are allowed within labels. These get
|
||||
represented in presentation format using NAME_ESCAPE as an escape
|
||||
character. In theory, if all the characters in a name were /000 or
|
||||
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
||||
presentation format would be twice as long as the spec. */
|
||||
char *buff = opt_malloc((MAXDNAME * 2) + 1);
|
||||
size_t argbuf_size = 300;
|
||||
char *argbuf = safe_malloc(argbuf_size);
|
||||
int option, testmode = 0;
|
||||
char *arg, *conffile = NULL;
|
||||
|
||||
opterr = 0;
|
||||
|
||||
|
||||
daemon = opt_malloc(sizeof(struct daemon));
|
||||
memset(daemon, 0, sizeof(struct daemon));
|
||||
daemon->namebuff = buff;
|
||||
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
|
||||
daemon->namebuff = safe_malloc(MAXDNAMESTR+1);
|
||||
daemon->workspacename = safe_malloc(MAXDNAMESTR+1);
|
||||
daemon->addrbuff = safe_malloc(ADDRSTRLEN);
|
||||
|
||||
/* Set defaults - everything else is zero or NULL */
|
||||
@@ -6191,9 +6231,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
/* create default, if not specified */
|
||||
if (daemon->authserver && !daemon->hostmaster)
|
||||
{
|
||||
strcpy(buff, "hostmaster.");
|
||||
strcat(buff, daemon->authserver);
|
||||
daemon->hostmaster = opt_string_alloc(buff);
|
||||
strcpy(daemon->namebuff, "hostmaster.");
|
||||
strncat(daemon->namebuff, daemon->authserver, MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
daemon->hostmaster = opt_string_alloc(daemon->namebuff);
|
||||
}
|
||||
|
||||
if (!daemon->dhcp_pxe_vendors)
|
||||
@@ -6208,11 +6248,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
{
|
||||
struct mx_srv_record *mx;
|
||||
|
||||
if (gethostname(buff, MAXDNAME) == -1)
|
||||
if (gethostname(daemon->namebuff, MAXDNAMESTR) == -1)
|
||||
die(_("cannot get host-name: %s"), NULL, EC_MISC);
|
||||
|
||||
for (mx = daemon->mxnames; mx; mx = mx->next)
|
||||
if (!mx->issrv && hostname_isequal(mx->name, buff))
|
||||
if (!mx->issrv && hostname_isequal(mx->name, daemon->namebuff))
|
||||
break;
|
||||
|
||||
if ((daemon->mxtarget || option_bool(OPT_LOCALMX)) && !mx)
|
||||
@@ -6221,12 +6261,12 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
mx->next = daemon->mxnames;
|
||||
mx->issrv = 0;
|
||||
mx->target = NULL;
|
||||
mx->name = opt_string_alloc(buff);
|
||||
mx->name = opt_string_alloc(daemon->namebuff);
|
||||
daemon->mxnames = mx;
|
||||
}
|
||||
|
||||
if (!daemon->mxtarget)
|
||||
daemon->mxtarget = opt_string_alloc(buff);
|
||||
daemon->mxtarget = opt_string_alloc(daemon->namebuff);
|
||||
|
||||
for (mx = daemon->mxnames; mx; mx = mx->next)
|
||||
if (!mx->issrv && !mx->target)
|
||||
@@ -6241,7 +6281,8 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
|
||||
if (option_bool(OPT_RESOLV_DOMAIN))
|
||||
{
|
||||
char *line;
|
||||
char *line = NULL;
|
||||
size_t linesz = 0;
|
||||
FILE *f;
|
||||
|
||||
if (option_bool(OPT_NO_RESOLV) ||
|
||||
@@ -6252,7 +6293,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
if (!(f = fopen((daemon->resolv_files)->name, "r")))
|
||||
die(_("failed to read %s: %s"), (daemon->resolv_files)->name, EC_FILE);
|
||||
|
||||
while ((line = fgets(buff, MAXDNAME, f)))
|
||||
while (get_line_alloc(f, &line, &linesz))
|
||||
{
|
||||
char *token = strtok(line, " \t\n\r");
|
||||
|
||||
@@ -6261,7 +6302,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
|
||||
if ((token = strtok(NULL, " \t\n\r")) &&
|
||||
(daemon->domain_suffix = canonicalise_opt(token)))
|
||||
break;
|
||||
{
|
||||
/* We don't call get_line_alloc() until it returns false and frees the automatically */
|
||||
get_line_alloc(NULL, &line, &linesz);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
@@ -6272,7 +6317,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
|
||||
if (daemon->domain_suffix)
|
||||
{
|
||||
/* add domain for any srv record without one. */
|
||||
/* add domain for any srv record without one. */
|
||||
struct mx_srv_record *srv;
|
||||
|
||||
for (srv = daemon->mxnames; srv; srv = srv->next)
|
||||
@@ -6280,13 +6325,14 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
strchr(srv->name, '.') &&
|
||||
strchr(srv->name, '.') == strrchr(srv->name, '.'))
|
||||
{
|
||||
if (strlen(srv->name) + 1 + strlen(daemon->domain_suffix) > MAXDNAME)
|
||||
if (strlen(srv->name) + 1 + strlen(daemon->domain_suffix) > MAXDNAMESTR)
|
||||
die(_("srv-host name %s too long after domain appended"), srv->name, EC_MISC);
|
||||
strcpy(buff, srv->name);
|
||||
strcat(buff, ".");
|
||||
strcat(buff, daemon->domain_suffix);
|
||||
strcpy(daemon->namebuff, srv->name);
|
||||
strcat(daemon->namebuff, ".");
|
||||
strcat(daemon->namebuff, daemon->domain_suffix);
|
||||
free(srv->name);
|
||||
srv->name = opt_string_alloc(buff);
|
||||
if (!(srv->name = canonicalise_opt(daemon->namebuff)))
|
||||
die(_("bad srv-host name %s after domain appended"), srv->name, EC_MISC);
|
||||
}
|
||||
}
|
||||
else if (option_bool(OPT_DHCP_FQDN))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -100,13 +100,13 @@ void poll_listen(int fd, short event)
|
||||
{
|
||||
/* Array too small. Extend. */
|
||||
struct pollfd *new;
|
||||
nfds_t newsize = (arrsize == 0) ? 64 : arrsize * 2;
|
||||
|
||||
arrsize = (arrsize == 0) ? 64 : arrsize * 2;
|
||||
|
||||
if (!(new = whine_realloc(pollfds, arrsize * sizeof(struct pollfd))))
|
||||
if (!(new = whine_realloc(pollfds, newsize * sizeof(struct pollfd))))
|
||||
return;
|
||||
|
||||
pollfds = new;
|
||||
arrsize = newsize;
|
||||
}
|
||||
|
||||
memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+21
-9
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -58,7 +58,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
|
||||
unsigned int preferred, unsigned int valid, void *vparam);
|
||||
static int iface_search(struct in6_addr *local, int prefix,
|
||||
int scope, int if_index, int flags,
|
||||
unsigned int prefered, unsigned int valid, void *vparam);
|
||||
unsigned int preferred, unsigned int valid, void *vparam);
|
||||
static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
|
||||
static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
|
||||
static unsigned int calc_lifetime(struct ra_interface *ra);
|
||||
@@ -215,11 +215,8 @@ void icmp6_packet(time_t now)
|
||||
if (opt_sz == 0 || opt_sz > rem)
|
||||
return; /* Bad packet */
|
||||
|
||||
if (p[0] == ICMP6_OPT_SOURCE_MAC && ((opt_sz - 2) * 3 - 1 < MAXDNAME))
|
||||
{
|
||||
print_mac(daemon->namebuff, &p[2], opt_sz - 2);
|
||||
mac = daemon->namebuff;
|
||||
}
|
||||
if (p[0] == ICMP6_OPT_SOURCE_MAC)
|
||||
mac = print_mac(&p[2], opt_sz - 2);
|
||||
}
|
||||
|
||||
if (!option_bool(OPT_QUIET_RA))
|
||||
@@ -435,7 +432,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad
|
||||
sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name);
|
||||
if ((f = fopen(daemon->namebuff, "r")))
|
||||
{
|
||||
if (fgets(daemon->namebuff, MAXDNAME, f))
|
||||
if (fgets(daemon->namebuff, MAXDNAMESTR, f))
|
||||
mtu = atoi(daemon->namebuff);
|
||||
fclose(f);
|
||||
}
|
||||
@@ -611,6 +608,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
|
||||
int do_slaac = 0;
|
||||
int deprecate = 0;
|
||||
int constructed = 0;
|
||||
int have_lease = 0;
|
||||
int adv_router = 0;
|
||||
int off_link = 0;
|
||||
unsigned int time = 0xffffffff;
|
||||
@@ -663,6 +661,8 @@ static int add_prefixes(struct in6_addr *local, int prefix,
|
||||
if (time < ((unsigned int)(3 * param->adv_interval)))
|
||||
time = 3 * param->adv_interval;
|
||||
}
|
||||
if (context->flags & CONTEXT_SETLEASE)
|
||||
have_lease = 1;
|
||||
|
||||
if (context->flags & CONTEXT_DEPRECATE)
|
||||
deprecate = 1;
|
||||
@@ -700,7 +700,19 @@ static int add_prefixes(struct in6_addr *local, int prefix,
|
||||
/* configured time is ceiling */
|
||||
if (!constructed || valid > time)
|
||||
valid = time;
|
||||
|
||||
|
||||
/* For CONSTRUCTED prefixes an explicitly configured lease time is
|
||||
also a floor: an upstream delegating with very short lifetimes
|
||||
(mobile links) would otherwise churn the addresses of any client
|
||||
that sleeps through a few RAs. Deprecation still wins below. */
|
||||
if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
|
||||
{
|
||||
if (valid < time)
|
||||
valid = time;
|
||||
if (preferred < time)
|
||||
preferred = time;
|
||||
}
|
||||
|
||||
if (flags & IFACE_DEPRECATED)
|
||||
preferred = 0;
|
||||
|
||||
|
||||
+43
-41
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -106,11 +106,16 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||
|
||||
p = l + (unsigned char *)header;
|
||||
}
|
||||
else if (label_type == 0x00)
|
||||
{ /* label_type = 0 -> label. */
|
||||
namelen += l + 1; /* include period */
|
||||
else if (label_type == 0x00) /* label_type = 0 -> label. */
|
||||
{
|
||||
/* reject wire-format names > MAXDNAME bytes
|
||||
This also ensures that internal-format
|
||||
names don't exceed MAXDNAMESTR characters. */
|
||||
namelen += l + 1;
|
||||
/* namelen == MAXDNAME means terminator will overflow. */
|
||||
if (namelen >= MAXDNAME)
|
||||
return 0;
|
||||
|
||||
if (!CHECK_LEN(header, p, plen, l))
|
||||
return 0;
|
||||
|
||||
@@ -119,13 +124,13 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||
{
|
||||
unsigned char c = *p;
|
||||
|
||||
if (c == 0 || c == '.' || c == NAME_ESCAPE)
|
||||
if (IS_NAME_ESCAPE(c))
|
||||
{
|
||||
*cp++ = NAME_ESCAPE;
|
||||
*cp++ = c+1;
|
||||
}
|
||||
else
|
||||
*cp++ = c;
|
||||
*cp++ = c;
|
||||
}
|
||||
else if (flip)
|
||||
{
|
||||
@@ -317,7 +322,7 @@ unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t
|
||||
else if (label_type == 0x40)
|
||||
{
|
||||
/* Extended label type */
|
||||
unsigned int count;
|
||||
unsigned int count, llen;
|
||||
|
||||
if (!CHECK_LEN(header, ansp, plen, 2))
|
||||
return NULL;
|
||||
@@ -328,9 +333,12 @@ unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t
|
||||
count = *(ansp++); /* Bits in bitstring */
|
||||
|
||||
if (count == 0) /* count == 0 means 256 bits */
|
||||
ansp += 32;
|
||||
llen = 32;
|
||||
else
|
||||
ansp += ((count-1)>>3)+1;
|
||||
llen = ((count-1)>>3)+1;
|
||||
|
||||
if (!ADD_RDLEN(header, ansp, plen, llen))
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{ /* label type == 0 Bottom six bits is length */
|
||||
@@ -440,7 +448,7 @@ int private_net6(struct in6_addr *a, int ban_localhost)
|
||||
(IN6_IS_ADDR_LOOPBACK(a) && ban_localhost) || /* RFC 6303 4.3 */
|
||||
IN6_IS_ADDR_LINKLOCAL(a) || /* RFC 6303 4.5 */
|
||||
IN6_IS_ADDR_SITELOCAL(a) ||
|
||||
((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */
|
||||
(((unsigned char *)a)[0] & 0xfe) == 0xfc || /* RFC 4196 3.1 */
|
||||
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
|
||||
}
|
||||
|
||||
@@ -495,7 +503,7 @@ int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
|
||||
header->hb3 &= ~HB3_AA;
|
||||
#ifdef HAVE_DNSSEC
|
||||
/* remove validated flag from this RR, since we changed it! */
|
||||
if (option_bool(OPT_DNSSEC_VALID) && i < ntohs(header->ancount))
|
||||
if (option_bool(OPT_DNSSEC_VALID) && i < daemon->rr_status_sz && i < ntohs(header->ancount))
|
||||
daemon->rr_status[i] = 0;
|
||||
#endif
|
||||
done = 1;
|
||||
@@ -537,7 +545,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
|
||||
|
||||
for (i = 0; i < ntohs(header->nscount); i++)
|
||||
{
|
||||
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
|
||||
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 10))
|
||||
return 0; /* bad packet */
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
@@ -552,7 +560,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
|
||||
soa_len = strlen(daemon->workspacename);
|
||||
|
||||
/* SOA must be for the name we're interested in. */
|
||||
if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0)
|
||||
if (hostname_issubdomain(daemon->workspacename, name) != 0)
|
||||
{
|
||||
int prefix = name_len - soa_len;
|
||||
|
||||
@@ -577,11 +585,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
|
||||
{
|
||||
len = to_wire(daemon->workspacename);
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
addr.rrblock.datalen += len;
|
||||
}
|
||||
}
|
||||
@@ -599,15 +604,14 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
|
||||
int secflag = 0;
|
||||
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
addr.rrblock.datalen += 20;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
|
||||
if (option_bool(OPT_DNSSEC_VALID) &&
|
||||
i + ntohs(header->ancount) < daemon->rr_status_sz &&
|
||||
daemon->rr_status[i + ntohs(header->ancount)] != 0)
|
||||
{
|
||||
secflag = F_DNSSECOK;
|
||||
|
||||
@@ -932,10 +936,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
||||
{
|
||||
/* Copy the rest of the RR and end. */
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
addr.rrblock.datalen += endrr - p1;
|
||||
}
|
||||
else if (desc == 0)
|
||||
@@ -943,7 +945,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
||||
/* Name, extract it then re-encode. */
|
||||
int len;
|
||||
|
||||
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
|
||||
/* rdlen may lie, and extract_name() advances p1 past where it says the record ends. */
|
||||
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0) || (p1 > endrr))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 2;
|
||||
@@ -951,11 +954,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
||||
|
||||
len = to_wire(name);
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, name, len))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
addr.rrblock.datalen += len;
|
||||
}
|
||||
else
|
||||
@@ -965,11 +965,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
||||
desc = endrr - p1;
|
||||
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, desc))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
addr.rrblock.datalen += desc;
|
||||
p1 += desc;
|
||||
}
|
||||
@@ -1427,6 +1424,11 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen)
|
||||
return check_bad_address(header, qlen, daemon->ignore_addr, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Nameoffset > 0 means that the name of the new record already exists at the given offset,
|
||||
so use a "jump" to that.
|
||||
Nameoffset == 0 means use the first variable argument as the name of the new record.
|
||||
nameoffset < 0 means use the first variable argument as the start of the new record name,
|
||||
then "jump" to -nameoffset to complete it. */
|
||||
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
|
||||
unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
|
||||
{
|
||||
@@ -1453,7 +1455,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
||||
else
|
||||
{
|
||||
char *name = va_arg(ap, char *);
|
||||
if (name && !(p = do_rfc1035_name(p, name, limit)))
|
||||
if (name && !(p = do_rfc1035_name(p, name, (unsigned char *)limit)))
|
||||
goto truncated;
|
||||
|
||||
if (nameoffset < 0)
|
||||
@@ -1517,7 +1519,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
||||
/* get domain-name answer arg and store it in RDATA field */
|
||||
if (offset)
|
||||
*offset = p - (unsigned char *)header;
|
||||
if (!(p = do_rfc1035_name(p, va_arg(ap, char *), limit)))
|
||||
if (!(p = do_rfc1035_name(p, va_arg(ap, char *), (unsigned char *)limit)))
|
||||
goto truncated;
|
||||
CHECK_LIMIT(1);
|
||||
*p++ = 0;
|
||||
|
||||
+18
-15
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -413,7 +413,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
|
||||
struct dhcp_context *context_tmp;
|
||||
for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
|
||||
{
|
||||
inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
|
||||
inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAMESTR);
|
||||
if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
|
||||
{
|
||||
inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
|
||||
@@ -597,7 +597,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
|
||||
if (mess->file[0])
|
||||
{
|
||||
memcpy(daemon->dhcp_buff2, mess->file, sizeof(mess->file));
|
||||
daemon->dhcp_buff2[sizeof(mess->file) + 1] = 0; /* ensure zero term. */
|
||||
daemon->dhcp_buff2[sizeof(mess->file)] = 0; /* ensure zero term. */
|
||||
id.net = (char *)daemon->dhcp_buff2;
|
||||
id.next = netid;
|
||||
netid = &id;
|
||||
@@ -1313,7 +1313,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
|
||||
unsigned char *mac = extended_hwaddr(ltmp->hwaddr_type, ltmp->hwaddr_len,
|
||||
ltmp->hwaddr, ltmp->clid_len, ltmp->clid, &len);
|
||||
my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s"),
|
||||
daemon->addrbuff, print_mac(daemon->namebuff, mac, len));
|
||||
daemon->addrbuff, print_mac(mac, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1537,7 +1537,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
|
||||
{
|
||||
inet_ntop(AF_INET, <mp->addr, daemon->addrbuff, ADDRSTRLEN);
|
||||
my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"),
|
||||
print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len),
|
||||
print_mac(ltmp->hwaddr, ltmp->hwaddr_len),
|
||||
daemon->addrbuff);
|
||||
lease = ltmp;
|
||||
}
|
||||
@@ -1611,7 +1611,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
|
||||
lease->extradata_size = lease->extradata_len = 0;
|
||||
|
||||
add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
|
||||
add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
|
||||
lease_add_extradata(lease, (unsigned char *)client_hostname,
|
||||
client_hostname ? strlen(client_hostname) : 0, 0);
|
||||
add_extradata_opt(lease, oui);
|
||||
add_extradata_opt(lease, serial);
|
||||
add_extradata_opt(lease, class);
|
||||
@@ -1918,16 +1919,18 @@ static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
|
||||
static void log_packet(char *type, void *addr, unsigned char *ext_mac,
|
||||
int mac_len, char *interface, char *string, char *err, u32 xid)
|
||||
{
|
||||
char *mac = "";
|
||||
|
||||
if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
|
||||
return;
|
||||
|
||||
daemon->addrbuff[0] = daemon->namebuff[0] = 0;
|
||||
daemon->addrbuff[0] = 0;
|
||||
|
||||
if (addr)
|
||||
inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
|
||||
|
||||
if (ext_mac)
|
||||
print_mac(daemon->namebuff, ext_mac, mac_len);
|
||||
mac = print_mac(ext_mac, mac_len);
|
||||
|
||||
if (option_bool(OPT_LOG_OPTS))
|
||||
my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s%s%s%s",
|
||||
@@ -1936,7 +1939,7 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
|
||||
interface,
|
||||
daemon->addrbuff,
|
||||
addr ? " " : "",
|
||||
daemon->namebuff,
|
||||
mac,
|
||||
ext_mac ? " " : "",
|
||||
string ? string : "",
|
||||
err ? err : "");
|
||||
@@ -1946,16 +1949,16 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
|
||||
interface,
|
||||
daemon->addrbuff,
|
||||
addr ? " " : "",
|
||||
daemon->namebuff,
|
||||
mac,
|
||||
ext_mac ? " " : "",
|
||||
string ? string : "",
|
||||
err ? err : "");
|
||||
|
||||
#ifdef HAVE_UBUS
|
||||
if (!strcmp(type, "DHCPACK"))
|
||||
ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
|
||||
ubus_event_bcast("dhcp.ack", mac, addr ? daemon->addrbuff : NULL, string, interface);
|
||||
else if (!strcmp(type, "DHCPRELEASE"))
|
||||
ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
|
||||
ubus_event_bcast("dhcp.release", mac, addr ? daemon->addrbuff : NULL, string, interface);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1963,7 +1966,7 @@ static void log_options(unsigned char *start, u32 xid)
|
||||
{
|
||||
while (*start != OPTION_END)
|
||||
{
|
||||
char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME);
|
||||
char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAMESTR);
|
||||
|
||||
my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s %s",
|
||||
ntohl(xid), option_len(start), start[0], optname, daemon->namebuff);
|
||||
@@ -2642,7 +2645,7 @@ static void do_options(struct dhcp_context *context,
|
||||
for (i = 0; req_options[i] != OPTION_END; i++)
|
||||
{
|
||||
char *s = option_string(AF_INET, req_options[i], NULL, 0, NULL, 0);
|
||||
q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
|
||||
q += snprintf(q, MAXDNAMESTR - (q - daemon->namebuff),
|
||||
"%d%s%s%s",
|
||||
req_options[i],
|
||||
strlen(s) != 0 ? ":" : "",
|
||||
@@ -3248,7 +3251,7 @@ unsigned int relay_reply4(struct dhcp_packet *mess, size_t sz, char *arrival_int
|
||||
|
||||
/* delete agent info before return RFC 3046 para 2.1 */
|
||||
*opt = OPTION_END;
|
||||
memset(opt + 1, 0, option_len(opt) + 2);
|
||||
memset(opt + 1, 0, option_len(opt) + 1);
|
||||
}
|
||||
}
|
||||
else if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
|
||||
|
||||
+161
-147
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
struct state {
|
||||
unsigned char *clid;
|
||||
int multicast_dest, clid_len, ia_type, interface, hostname_auth, lease_allocate;
|
||||
int clid_len, ia_type, interface, hostname_auth, lease_allocate;
|
||||
char *client_hostname, *hostname, *domain, *send_domain;
|
||||
struct dhcp_context *context;
|
||||
struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
|
||||
struct in6_addr *relay_address, *fallback, *ll_addr, *ula_addr;
|
||||
unsigned int xid, fqdn_flags, iaid;
|
||||
char *iface_name;
|
||||
void *packet_options, *end;
|
||||
@@ -34,13 +34,14 @@ struct state {
|
||||
};
|
||||
|
||||
static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz,
|
||||
struct in6_addr *client_addr, int is_unicast, time_t now);
|
||||
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now);
|
||||
struct in6_addr *client_addr, struct in6_addr *link_address, time_t now);
|
||||
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, time_t now);
|
||||
static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts);
|
||||
static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string);
|
||||
static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
|
||||
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize);
|
||||
static void *opt6_next(uint8_t *opts, uint8_t *end);
|
||||
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, int minsize);
|
||||
static void *opt6_first(uint8_t *opt, uint8_t *end);
|
||||
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end);
|
||||
static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
|
||||
static void get_context_tag(struct state *state, struct dhcp_context *context);
|
||||
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
|
||||
@@ -62,11 +63,12 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
|
||||
#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
|
||||
#define opt6_type(opt) (opt6_uint(opt, -4, 2))
|
||||
#define opt6_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[4+(i)]))
|
||||
#define opt6_next(opt, end) (opt6_first(opt6_ptr((opt), opt6_len((opt))), (end)))
|
||||
|
||||
#define opt6_user_vendor_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[2+(i)]))
|
||||
#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
|
||||
#define opt6_user_vendor_next(opt, end) (opt6_next(((uint8_t *) opt) - 2, end))
|
||||
|
||||
#define opt6_user_vendor_len(opt) ((int)(opt6_uint((opt), -4, 2)))
|
||||
#define opt6_user_vendor_next(opt, end) (opt6_user_vendor_first(opt6_user_vendor_ptr((opt), opt6_user_vendor_len((opt))), (end)))
|
||||
|
||||
|
||||
unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,
|
||||
struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
|
||||
@@ -80,6 +82,10 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
|
||||
return 0;
|
||||
|
||||
msg_type = *((unsigned char *)daemon->dhcp_packet.iov_base);
|
||||
|
||||
/* request from a client must be multicast RFC-9915 section 16 */
|
||||
if (msg_type != DHCP6RELAYFORW && !multicast_dest)
|
||||
return 0;
|
||||
|
||||
/* Mark these so we only match each at most once, to avoid tangled linked lists */
|
||||
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
|
||||
@@ -87,7 +93,6 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
|
||||
|
||||
reset_counter();
|
||||
state.context = context;
|
||||
state.multicast_dest = multicast_dest;
|
||||
state.interface = interface;
|
||||
state.iface_name = iface_name;
|
||||
state.fallback = fallback;
|
||||
@@ -95,10 +100,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
|
||||
state.ula_addr = ula_addr;
|
||||
state.mac_len = 0;
|
||||
state.tags = NULL;
|
||||
state.link_address = NULL;
|
||||
|
||||
if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr,
|
||||
IN6_IS_ADDR_MULTICAST(client_addr), now))
|
||||
|
||||
if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, NULL, now))
|
||||
return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
|
||||
|
||||
return 0;
|
||||
@@ -106,15 +109,22 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
|
||||
|
||||
/* This cost me blood to write, it will probably cost you blood to understand - srk. */
|
||||
static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz,
|
||||
struct in6_addr *client_addr, int is_unicast, time_t now)
|
||||
struct in6_addr *client_addr, struct in6_addr *link_address, time_t now)
|
||||
{
|
||||
uint8_t *end = inbuff + sz;
|
||||
uint8_t *opts = inbuff + 34;
|
||||
int msg_type = *inbuff;
|
||||
int msg_type;
|
||||
unsigned char *outmsgtypep;
|
||||
uint8_t *opt;
|
||||
struct dhcp_vendor *vendor;
|
||||
|
||||
/* must have at least msg_type+trans_id
|
||||
which is 1 + 3 = 4 */
|
||||
if (sz < 4)
|
||||
return 0;
|
||||
|
||||
msg_type = *inbuff;
|
||||
|
||||
/* if not an encapsulated relayed message, just do the stuff */
|
||||
if (msg_type != DHCP6RELAYFORW)
|
||||
{
|
||||
@@ -126,7 +136,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
link_address == NULL means there's no relay in use, so we try and find the client's
|
||||
MAC address from the local ND cache. */
|
||||
|
||||
if (!state->link_address)
|
||||
if (!link_address)
|
||||
get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now);
|
||||
else
|
||||
{
|
||||
@@ -134,9 +144,9 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
struct shared_network *share = NULL;
|
||||
state->context = NULL;
|
||||
|
||||
if (!IN6_IS_ADDR_LOOPBACK(state->link_address) &&
|
||||
!IN6_IS_ADDR_LINKLOCAL(state->link_address) &&
|
||||
!IN6_IS_ADDR_MULTICAST(state->link_address))
|
||||
if (!IN6_IS_ADDR_LOOPBACK(link_address) &&
|
||||
!IN6_IS_ADDR_LINKLOCAL(link_address) &&
|
||||
!IN6_IS_ADDR_MULTICAST(link_address))
|
||||
for (c = daemon->dhcp6; c; c = c->next)
|
||||
{
|
||||
for (share = daemon->shared_networks; share; share = share->next)
|
||||
@@ -145,7 +155,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
continue;
|
||||
|
||||
if (share->if_index != 0 ||
|
||||
!IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6))
|
||||
!IN6_ARE_ADDR_EQUAL(link_address, &share->match_addr6))
|
||||
continue;
|
||||
|
||||
if ((c->flags & CONTEXT_DHCP) &&
|
||||
@@ -158,8 +168,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
if (share ||
|
||||
((c->flags & CONTEXT_DHCP) &&
|
||||
!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
|
||||
is_same_net6(state->link_address, &c->start6, c->prefix) &&
|
||||
is_same_net6(state->link_address, &c->end6, c->prefix)))
|
||||
is_same_net6(link_address, &c->start6, c->prefix) &&
|
||||
is_same_net6(link_address, &c->end6, c->prefix)))
|
||||
{
|
||||
c->preferred = c->valid = 0xffffffff;
|
||||
c->current = state->context;
|
||||
@@ -169,7 +179,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
|
||||
if (!state->context)
|
||||
{
|
||||
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, link_address, daemon->addrbuff, ADDRSTRLEN);
|
||||
my_syslog(MS_DHCP | LOG_WARNING,
|
||||
_("no address range available for DHCPv6 request from relay at %s"),
|
||||
daemon->addrbuff);
|
||||
@@ -184,7 +194,9 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dhcp6_no_relay(state, msg_type, inbuff, sz, is_unicast, now);
|
||||
state->relay_address = link_address;
|
||||
|
||||
return dhcp6_no_relay(state, msg_type, inbuff, sz, now);
|
||||
}
|
||||
|
||||
/* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
|
||||
@@ -231,11 +243,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
|
||||
}
|
||||
|
||||
for (opt = opts; opt; opt = opt6_next(opt, end))
|
||||
for (opt = opt6_first(opts, end); opt; opt = opt6_next(opt, end))
|
||||
{
|
||||
if ((uint8_t *)opt6_ptr(opt, 0) + opt6_len(opt) > end)
|
||||
return 0;
|
||||
|
||||
/* Don't copy MAC address into reply. */
|
||||
if (opt6_type(opt) != OPTION6_CLIENT_MAC)
|
||||
{
|
||||
@@ -246,13 +255,12 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
/* the packet data is unaligned, copy to aligned storage */
|
||||
memcpy(&align, inbuff + 2, IN6ADDRSZ);
|
||||
|
||||
|
||||
/* RFC6221 para 4 */
|
||||
if (!IN6_IS_ADDR_UNSPECIFIED(&align))
|
||||
state->link_address = &align;
|
||||
/* zero is_unicast since that is now known to refer to the
|
||||
relayed packet, not the original sent by the client */
|
||||
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now))
|
||||
/* RFC6221 para 4 says if link_address in encapsulation
|
||||
is zero, ignore it, and, by implication, use the link
|
||||
address of any enclosing encapsulation or, failing that
|
||||
of the arrival interface on the server. */
|
||||
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr,
|
||||
IN6_IS_ADDR_UNSPECIFIED(&align) ? link_address : &align, now))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
@@ -264,7 +272,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now)
|
||||
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, time_t now)
|
||||
{
|
||||
void *opt;
|
||||
int i, o, o1, start_opts, start_msg;
|
||||
@@ -290,7 +298,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
state->hostname = NULL;
|
||||
state->client_hostname = NULL;
|
||||
state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */
|
||||
|
||||
|
||||
/* set tag with name == interface */
|
||||
iface_id.net = state->iface_name;
|
||||
iface_id.next = state->tags;
|
||||
@@ -340,22 +348,17 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1);
|
||||
|
||||
if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND || msg_type == DHCP6IREQ)
|
||||
if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND)
|
||||
{
|
||||
/* Above message types must be multicast 3315 Section 15. */
|
||||
if (!state->multicast_dest)
|
||||
/* SOLICIT, CONFIRM and REBIND messages MUST NOT have a server-id. 3315 para 15.x */
|
||||
if (opt)
|
||||
return 0;
|
||||
|
||||
/* server-id must match except for SOLICIT, CONFIRM and REBIND messages, which MUST NOT
|
||||
have a server-id. 3315 para 15.x */
|
||||
if (msg_type == DHCP6IREQ)
|
||||
{
|
||||
/* If server-id provided in IREQ, it must match. */
|
||||
if (opt && (opt6_len(opt) != daemon->duid_len ||
|
||||
memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
|
||||
return 0;
|
||||
}
|
||||
else if (opt)
|
||||
}
|
||||
else if (msg_type == DHCP6IREQ)
|
||||
{
|
||||
/* If server-id provided in IREQ, it must match. */
|
||||
if (opt && (opt6_len(opt) != daemon->duid_len ||
|
||||
memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
@@ -370,18 +373,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
put_opt6(daemon->duid, daemon->duid_len);
|
||||
end_opt6(o);
|
||||
|
||||
if (is_unicast &&
|
||||
(msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE))
|
||||
|
||||
{
|
||||
outmsgtype = DHCP6REPLY;
|
||||
o1 = new_opt6(OPTION6_STATUS_CODE);
|
||||
put_opt6_short(DHCP6USEMULTI);
|
||||
put_opt6_string("Use multicast");
|
||||
end_opt6(o1);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* match vendor and user class options */
|
||||
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
|
||||
{
|
||||
@@ -404,14 +395,28 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
if (opt6_len(opt) < 4)
|
||||
continue;
|
||||
|
||||
if (vendor->enterprise != opt6_uint(opt, 0, 4))
|
||||
if (vendor->enterprise != 0 && vendor->enterprise != opt6_uint(opt, 0, 4))
|
||||
continue;
|
||||
|
||||
|
||||
/* matching enterprise, no string match. */
|
||||
if (vendor->enterprise != 0 && vendor->len == 0)
|
||||
{
|
||||
vendor->netid.next = state->tags;
|
||||
state->tags = &vendor->netid;
|
||||
break;
|
||||
}
|
||||
|
||||
offset = 4;
|
||||
|
||||
/* If we're going to search the strings below, there must be at least one empty string to search
|
||||
I think a vendor_class option with just the enterprise number is valid. */
|
||||
if (opt6_len(opt) < 6)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
|
||||
for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
|
||||
|
||||
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
|
||||
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, offset), enc_end);
|
||||
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
|
||||
for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
|
||||
if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
|
||||
{
|
||||
@@ -469,10 +474,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
if (state->mac_len != 0)
|
||||
{
|
||||
if (option_bool(OPT_LOG_OPTS))
|
||||
{
|
||||
print_mac(daemon->dhcp_buff, state->mac, state->mac_len);
|
||||
my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid, daemon->dhcp_buff);
|
||||
}
|
||||
my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid,
|
||||
print_mac(state->mac, state->mac_len));
|
||||
|
||||
for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next)
|
||||
if ((unsigned)mac_opt->hwaddr_len == state->mac_len &&
|
||||
@@ -499,7 +502,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
state->fqdn_flags &= ~0x04;
|
||||
|
||||
if (len != 0 && len < 255)
|
||||
if (len >= 1 && len < 255)
|
||||
{
|
||||
unsigned char *pp, *op = opt6_ptr(opt, 1);
|
||||
char *pq = daemon->dhcp_buff;
|
||||
@@ -674,7 +677,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
for (c = state->context; c; c = c->current)
|
||||
c->flags &= ~CONTEXT_CONF_USED;
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
unsigned int min_time = 0xffffffff;
|
||||
@@ -838,7 +841,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
if (ignore)
|
||||
return 0;
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
unsigned int min_time = 0xffffffff;
|
||||
@@ -949,7 +952,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
unsigned int min_time = 0xffffffff;
|
||||
@@ -1084,7 +1087,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
|
||||
@@ -1160,7 +1163,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
log6_quiet(state, "DHCPRELEASE", NULL, NULL);
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
int made_ia = 0;
|
||||
@@ -1225,7 +1228,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
log6_quiet(state, "DHCPDECLINE", NULL, NULL);
|
||||
|
||||
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
|
||||
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
|
||||
{
|
||||
void *ia_option, *ia_end;
|
||||
int made_ia = 0;
|
||||
@@ -1304,7 +1307,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
|
||||
|
||||
log_tags(tagif, state->xid);
|
||||
|
||||
done:
|
||||
/* Fill in the message type. Note that we store the offset,
|
||||
not a direct pointer, since the packet memory may have been
|
||||
reallocated. */
|
||||
@@ -1532,7 +1534,7 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
|
||||
for (i = 0; i < opt6_len(oro) - 1; i += 2)
|
||||
{
|
||||
char *s = option_string(AF_INET6, opt6_uint(oro, i, 2), NULL, 0, NULL, 0);
|
||||
q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
|
||||
q += snprintf(q, MAXDNAMESTR - (q - daemon->namebuff),
|
||||
"%d%s%s%s",
|
||||
opt6_uint(oro, i, 2),
|
||||
strlen(s) != 0 ? ":" : "",
|
||||
@@ -1596,9 +1598,11 @@ static void get_context_tag(struct state *state, struct dhcp_context *context)
|
||||
|
||||
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option)
|
||||
{
|
||||
state->ia_type = opt6_type(opt);
|
||||
*ia_option = NULL;
|
||||
|
||||
/* callee ensures packet is long enough for opt6_len(opt) to be valid and believe-able. */
|
||||
state->ia_type = opt6_type(opt);
|
||||
|
||||
if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
|
||||
return 0;
|
||||
|
||||
@@ -1923,10 +1927,11 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
|
||||
lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
|
||||
|
||||
if (opt6_len(opt) >= 6)
|
||||
for (enc_opt = opt6_ptr(opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
|
||||
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 4), enc_end);
|
||||
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
|
||||
{
|
||||
lease->vendorclass_count++;
|
||||
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
|
||||
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1975,16 +1980,17 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
|
||||
}
|
||||
}
|
||||
|
||||
if (state->link_address)
|
||||
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
|
||||
if (state->relay_address)
|
||||
inet_ntop(AF_INET6, state->relay_address, daemon->addrbuff, ADDRSTRLEN);
|
||||
|
||||
lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0);
|
||||
lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->relay_address ? strlen(daemon->addrbuff) : 0, 0);
|
||||
|
||||
if ((opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
|
||||
{
|
||||
void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
|
||||
for (enc_opt = opt6_ptr(opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
|
||||
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
|
||||
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 0), enc_end);
|
||||
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
|
||||
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1999,10 +2005,10 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op
|
||||
void *opt;
|
||||
char *desc = nest ? "nest" : "sent";
|
||||
|
||||
if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts)
|
||||
if (!option_bool(OPT_LOG_OPTS))
|
||||
return;
|
||||
|
||||
for (opt = start_opts; opt; opt = opt6_next(opt, end_opts))
|
||||
for (opt = opt6_first(start_opts, end_opts); opt; opt = opt6_next(opt, end_opts))
|
||||
{
|
||||
int type = opt6_type(opt);
|
||||
void *ia_options = NULL;
|
||||
@@ -2035,16 +2041,22 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op
|
||||
}
|
||||
else if (type == OPTION6_STATUS_CODE)
|
||||
{
|
||||
int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
|
||||
memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
|
||||
daemon->namebuff[len + opt6_len(opt) - 2] = 0;
|
||||
if (opt6_len(opt) >= 2)
|
||||
{
|
||||
int slen = opt6_len(opt) - 2;
|
||||
int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
|
||||
if (slen > (MAXDNAMESTR) - len - 1)
|
||||
slen = (MAXDNAMESTR) - len - 1;
|
||||
memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), slen);
|
||||
daemon->namebuff[len + slen] = 0;
|
||||
}
|
||||
optname = "status";
|
||||
}
|
||||
else
|
||||
{
|
||||
/* account for flag byte on FQDN */
|
||||
int offset = type == OPTION6_FQDN ? 1 : 0;
|
||||
optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAME);
|
||||
optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAMESTR);
|
||||
}
|
||||
|
||||
my_syslog(MS_DHCP | LOG_INFO, "%u %s size:%3d option:%3d %s %s",
|
||||
@@ -2063,14 +2075,6 @@ static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, c
|
||||
|
||||
static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string)
|
||||
{
|
||||
int clid_len = state->clid_len;
|
||||
|
||||
/* avoid buffer overflow */
|
||||
if (clid_len > 100)
|
||||
clid_len = 100;
|
||||
|
||||
print_mac(daemon->namebuff, state->clid, clid_len);
|
||||
|
||||
if (addr)
|
||||
{
|
||||
inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, DHCP_BUFF_SZ - 1);
|
||||
@@ -2085,58 +2089,56 @@ static void log6_packet(struct state *state, char *type, struct in6_addr *addr,
|
||||
type,
|
||||
state->iface_name,
|
||||
daemon->dhcp_buff2,
|
||||
daemon->namebuff,
|
||||
print_mac(state->clid, state->clid_len),
|
||||
string ? string : "");
|
||||
else
|
||||
my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s",
|
||||
type,
|
||||
state->iface_name,
|
||||
daemon->dhcp_buff2,
|
||||
daemon->namebuff,
|
||||
print_mac(state->clid, state->clid_len),
|
||||
string ? string : "");
|
||||
}
|
||||
|
||||
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize)
|
||||
static void *opt6_find(uint8_t *opts, uint8_t *end, unsigned int search, int minsize)
|
||||
{
|
||||
u16 opt, opt_len;
|
||||
void *start;
|
||||
|
||||
if (!opts)
|
||||
return NULL;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (end - opts < 4)
|
||||
return NULL;
|
||||
|
||||
start = opts;
|
||||
GETSHORT(opt, opts);
|
||||
GETSHORT(opt_len, opts);
|
||||
|
||||
if (opt_len > (end - opts))
|
||||
return NULL;
|
||||
|
||||
if (opt == search && (opt_len >= minsize))
|
||||
return start;
|
||||
|
||||
opts += opt_len;
|
||||
}
|
||||
for (opts = opt6_first(opts, end); opts; opts = opt6_next(opts, end))
|
||||
if (opt6_type(opts) == search && opt6_len(opts) >= minsize)
|
||||
return opts;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *opt6_next(uint8_t *opts, uint8_t *end)
|
||||
static void *opt6_first(uint8_t *opt, uint8_t *end)
|
||||
{
|
||||
u16 opt_len;
|
||||
|
||||
if (end - opts < 4)
|
||||
if (!opt)
|
||||
return NULL;
|
||||
|
||||
opts += 2;
|
||||
GETSHORT(opt_len, opts);
|
||||
|
||||
if (opt_len >= (end - opts))
|
||||
|
||||
/* make sure we have option number and length. */
|
||||
if ((uint8_t *)opt6_ptr(opt, 0) > end)
|
||||
return NULL;
|
||||
|
||||
return opts + opt_len;
|
||||
|
||||
/* make sure we have bytes promised by length. */
|
||||
if ((uint8_t *)opt6_ptr(opt, opt6_len(opt)) > end)
|
||||
return NULL;
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end)
|
||||
{
|
||||
if (!opt)
|
||||
return NULL;
|
||||
|
||||
/* make sure we have length. */
|
||||
if ((uint8_t *)opt6_user_vendor_ptr(opt, 0) > end)
|
||||
return NULL;
|
||||
|
||||
/* make sure we have bytes promised by length. */
|
||||
if ((uint8_t *)opt6_user_vendor_ptr(opt, opt6_user_vendor_len(opt)) > end)
|
||||
return NULL;
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
|
||||
@@ -2152,12 +2154,12 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* return 0 if we're not configured to relay. */
|
||||
int relay_upstream6(int iface_index, ssize_t sz,
|
||||
struct in6_addr *peer_address, u32 scope_id, time_t now)
|
||||
{
|
||||
unsigned char *header;
|
||||
unsigned char *inbuff = daemon->dhcp_packet.iov_base;
|
||||
int msg_type = *inbuff;
|
||||
int hopcount, o;
|
||||
struct in6_addr multicast;
|
||||
unsigned int maclen, mactype;
|
||||
@@ -2174,10 +2176,22 @@ int relay_upstream6(int iface_index, ssize_t sz,
|
||||
|
||||
inet_pton(AF_INET6, ALL_SERVERS, &multicast);
|
||||
get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now);
|
||||
|
||||
/* We need at least four bytes for valid client/server message
|
||||
(type, and transaction ID) A relay-originated message needs more. */
|
||||
if (sz < 4)
|
||||
return 1;
|
||||
|
||||
/* Get hop count from nested relayed message */
|
||||
if (msg_type == DHCP6RELAYFORW)
|
||||
hopcount = *((unsigned char *)inbuff+1) + 1;
|
||||
if (inbuff[0] == DHCP6RELAYFORW)
|
||||
{
|
||||
/* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
|
||||
which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */
|
||||
if (sz < 38)
|
||||
return 1;
|
||||
|
||||
/* Get hop count from nested relayed message. */
|
||||
hopcount = inbuff[1] + 1;
|
||||
}
|
||||
else
|
||||
hopcount = 0;
|
||||
|
||||
@@ -2243,7 +2257,7 @@ int relay_upstream6(int iface_index, ssize_t sz,
|
||||
{
|
||||
inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN);
|
||||
if (IN6_ARE_ADDR_EQUAL(&relay->server.addr6, &multicast))
|
||||
snprintf(daemon->namebuff, MAXDNAME, _("multicast via %s"), relay->interface);
|
||||
snprintf(daemon->namebuff, MAXDNAMESTR, _("multicast via %s"), relay->interface);
|
||||
else
|
||||
inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN);
|
||||
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->namebuff);
|
||||
|
||||
+12
-38
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -337,27 +337,6 @@ short *rrfilter_desc(int type)
|
||||
return p+1;
|
||||
}
|
||||
|
||||
int expand_workspace(unsigned char ***wkspc, int *szp, int new)
|
||||
{
|
||||
unsigned char **p;
|
||||
int old = *szp;
|
||||
|
||||
if (old >= new+1)
|
||||
return 1;
|
||||
|
||||
new += 5;
|
||||
|
||||
if (!(p = whine_realloc(*wkspc, new * sizeof(unsigned char *))))
|
||||
return 0;
|
||||
|
||||
memset(p+old, 0, new-old);
|
||||
|
||||
*wkspc = p;
|
||||
*szp = new;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert from presentation format to wire format, in place.
|
||||
Also map UC -> LC.
|
||||
Note that using extract_name to get presentation format
|
||||
@@ -365,14 +344,6 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
|
||||
thus generating names in canonical form.
|
||||
Calling to_wire followed by from_wire is almost an identity,
|
||||
except that the UC remains mapped to LC.
|
||||
|
||||
Note that both /000 and '.' are allowed within labels. These get
|
||||
represented in presentation format using NAME_ESCAPE as an escape
|
||||
character. In theory, if all the characters in a name were /000 or
|
||||
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
||||
presentation format would be twice as long as the spec (1024).
|
||||
The buffers are all declared as 2049 (allowing for the trailing zero)
|
||||
for this reason.
|
||||
*/
|
||||
int to_wire(char *name)
|
||||
{
|
||||
@@ -418,14 +389,17 @@ void from_wire(char *name)
|
||||
len = *l;
|
||||
memmove(l, l+1, len);
|
||||
for (p = l; p < l + len; p++)
|
||||
if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
|
||||
{
|
||||
memmove(p+1, p, 1 + last - p);
|
||||
len++;
|
||||
*p++ = NAME_ESCAPE;
|
||||
(*p)++;
|
||||
}
|
||||
|
||||
{
|
||||
unsigned char c = *p;
|
||||
if (IS_NAME_ESCAPE(c))
|
||||
{
|
||||
memmove(p+1, p, 1 + last - p);
|
||||
last++;
|
||||
len++;
|
||||
*p++ = NAME_ESCAPE;
|
||||
(*p)++;
|
||||
}
|
||||
}
|
||||
l[len] = '.';
|
||||
}
|
||||
|
||||
|
||||
+68
-68
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -27,87 +27,87 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
|
||||
struct slaac_address *slaac, *old, **up;
|
||||
struct dhcp_context *context;
|
||||
int dns_dirty = 0;
|
||||
|
||||
if (!(lease->flags & LEASE_HAVE_HWADDR) ||
|
||||
(lease->flags & (LEASE_TA | LEASE_NA)) ||
|
||||
lease->last_interface == 0 ||
|
||||
!lease->hostname)
|
||||
return ;
|
||||
|
||||
|
||||
old = lease->slaac_address;
|
||||
lease->slaac_address = NULL;
|
||||
|
||||
for (context = daemon->dhcp6; context; context = context->next)
|
||||
if ((context->flags & CONTEXT_RA_NAME) &&
|
||||
!(context->flags & CONTEXT_OLD) &&
|
||||
lease->last_interface == context->if_index)
|
||||
{
|
||||
struct in6_addr addr = context->start6;
|
||||
if (lease->hwaddr_len == 6 &&
|
||||
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
|
||||
if ((lease->flags & LEASE_HAVE_HWADDR) &&
|
||||
!(lease->flags & (LEASE_TA | LEASE_NA)) &&
|
||||
lease->last_interface != 0 &&
|
||||
lease->hostname)
|
||||
{
|
||||
for (context = daemon->dhcp6; context; context = context->next)
|
||||
if ((context->flags & CONTEXT_RA_NAME) &&
|
||||
!(context->flags & CONTEXT_OLD) &&
|
||||
lease->last_interface == context->if_index)
|
||||
{
|
||||
/* convert MAC address to EUI-64 */
|
||||
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
|
||||
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
|
||||
addr.s6_addr[11] = 0xff;
|
||||
addr.s6_addr[12] = 0xfe;
|
||||
}
|
||||
struct in6_addr addr = context->start6;
|
||||
if (lease->hwaddr_len == 6 &&
|
||||
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
|
||||
{
|
||||
/* convert MAC address to EUI-64 */
|
||||
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
|
||||
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
|
||||
addr.s6_addr[11] = 0xff;
|
||||
addr.s6_addr[12] = 0xfe;
|
||||
}
|
||||
#if defined(ARPHRD_EUI64)
|
||||
else if (lease->hwaddr_len == 8 &&
|
||||
lease->hwaddr_type == ARPHRD_EUI64)
|
||||
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
|
||||
else if (lease->hwaddr_len == 8 &&
|
||||
lease->hwaddr_type == ARPHRD_EUI64)
|
||||
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
|
||||
#endif
|
||||
#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
|
||||
else if (lease->clid_len == 9 &&
|
||||
lease->clid[0] == ARPHRD_EUI64 &&
|
||||
lease->hwaddr_type == ARPHRD_IEEE1394)
|
||||
/* FireWire has EUI-64 identifier as clid */
|
||||
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
|
||||
else if (lease->clid_len == 9 &&
|
||||
lease->clid[0] == ARPHRD_EUI64 &&
|
||||
lease->hwaddr_type == ARPHRD_IEEE1394)
|
||||
/* FireWire has EUI-64 identifier as clid */
|
||||
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
|
||||
#endif
|
||||
else
|
||||
continue;
|
||||
|
||||
addr.s6_addr[8] ^= 0x02;
|
||||
|
||||
/* check if we already have this one */
|
||||
for (up = &old, slaac = old; slaac; slaac = slaac->next)
|
||||
{
|
||||
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
|
||||
{
|
||||
*up = slaac->next;
|
||||
/* recheck when DHCPv4 goes through init-reboot */
|
||||
if (force)
|
||||
{
|
||||
slaac->ping_time = now;
|
||||
slaac->backoff = 1;
|
||||
dns_dirty = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
up = &slaac->next;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
/* No, make new one */
|
||||
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
|
||||
{
|
||||
slaac->ping_time = now;
|
||||
slaac->backoff = 1;
|
||||
slaac->addr = addr;
|
||||
/* Do RA's to prod it */
|
||||
ra_start_unsolicited(now, context);
|
||||
addr.s6_addr[8] ^= 0x02;
|
||||
|
||||
/* check if we already have this one */
|
||||
for (up = &old, slaac = old; slaac; slaac = slaac->next)
|
||||
{
|
||||
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
|
||||
{
|
||||
*up = slaac->next;
|
||||
/* recheck when DHCPv4 goes through init-reboot */
|
||||
if (force)
|
||||
{
|
||||
slaac->ping_time = now;
|
||||
slaac->backoff = 1;
|
||||
dns_dirty = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
up = &slaac->next;
|
||||
}
|
||||
|
||||
/* No, make new one */
|
||||
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
|
||||
{
|
||||
slaac->ping_time = now;
|
||||
slaac->backoff = 1;
|
||||
slaac->addr = addr;
|
||||
/* Do RA's to prod it */
|
||||
ra_start_unsolicited(now, context);
|
||||
}
|
||||
|
||||
if (slaac)
|
||||
{
|
||||
slaac->next = lease->slaac_address;
|
||||
lease->slaac_address = slaac;
|
||||
}
|
||||
}
|
||||
|
||||
if (slaac)
|
||||
{
|
||||
slaac->next = lease->slaac_address;
|
||||
lease->slaac_address = slaac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old || dns_dirty)
|
||||
lease_update_dns(1);
|
||||
|
||||
/* Free any no reused */
|
||||
/* Free any not reused */
|
||||
for (; old; old = slaac)
|
||||
{
|
||||
slaac = old->next;
|
||||
|
||||
+35
-19
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -251,15 +251,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
|
||||
for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
|
||||
{
|
||||
tftp_cnt++;
|
||||
|
||||
if (sockaddr_isequal(&peer, &transfer->peer))
|
||||
{
|
||||
if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
|
||||
{
|
||||
/* Handle repeated RRQ or abandoned transfer from same host and port
|
||||
by unlinking and reusing the struct transfer. */
|
||||
by abandoning old transfer. */
|
||||
*up = transfer->next;
|
||||
free_transfer(transfer);
|
||||
break;
|
||||
}
|
||||
else
|
||||
@@ -268,12 +267,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tftp_cnt++;
|
||||
}
|
||||
|
||||
/* Enforce simultaneous transfer limit. In non-single-port mode
|
||||
this is done by not listening on the server socket when
|
||||
too many transfers are in progress. */
|
||||
if (!transfer && tftp_cnt >= daemon->tftp_max)
|
||||
if (tftp_cnt >= daemon->tftp_max)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,12 +303,9 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
#endif
|
||||
}
|
||||
|
||||
/* May reuse struct transfer from abandoned transfer in single port mode. */
|
||||
if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
|
||||
if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
|
||||
return;
|
||||
|
||||
memset(transfer, 0, sizeof(struct tftp_transfer));
|
||||
|
||||
if (option_bool(OPT_SINGLE_PORT))
|
||||
transfer->sockfd = listen->tftpfd;
|
||||
else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
|
||||
@@ -434,17 +432,17 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
{
|
||||
if (prefix[0] == '/')
|
||||
daemon->namebuff[0] = 0;
|
||||
strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, prefix, MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
if (prefix[strlen(prefix)-1] != '/')
|
||||
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, "/", MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
|
||||
if (option_bool(OPT_TFTP_APREF_IP))
|
||||
{
|
||||
size_t oldlen = strlen(daemon->namebuff);
|
||||
struct stat statbuf;
|
||||
|
||||
strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, daemon->addrbuff, MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, "/", MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
|
||||
/* remove unique-directory if it doesn't exist */
|
||||
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
|
||||
@@ -475,7 +473,7 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
size_t oldlen = strlen(daemon->namebuff);
|
||||
struct stat statbuf;
|
||||
|
||||
snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
|
||||
snprintf(daemon->namebuff + oldlen, MAXDNAMESTR - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
|
||||
macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
|
||||
|
||||
/* remove unique-directory if it doesn't exist */
|
||||
@@ -495,7 +493,7 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
|
||||
}
|
||||
else if (filename[0] == '/')
|
||||
daemon->namebuff[0] = 0;
|
||||
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
|
||||
strncat(daemon->namebuff, filename, MAXDNAMESTR - strlen(daemon->namebuff));
|
||||
|
||||
/* check permissions and open file */
|
||||
if ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff)))
|
||||
@@ -545,8 +543,20 @@ static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *p
|
||||
int fd = -1;
|
||||
|
||||
/* trick to ban moving out of the subtree */
|
||||
if (prefix && strstr(namebuff, "/../"))
|
||||
goto perm;
|
||||
if (prefix)
|
||||
{
|
||||
char *suspect = namebuff;
|
||||
|
||||
/* Every occurrence has to be checked: a harmless leading match
|
||||
such as "/..data" otherwise hides a later "/../" from here. */
|
||||
while ((suspect = strstr(suspect, "/..")))
|
||||
{
|
||||
if (suspect[3] == '/' || suspect[3] == 0)
|
||||
goto perm;
|
||||
|
||||
suspect += 3;
|
||||
}
|
||||
}
|
||||
|
||||
if ((fd = open(namebuff, O_RDONLY)) == -1)
|
||||
{
|
||||
@@ -622,7 +632,7 @@ void check_tftp_listeners(time_t now)
|
||||
{
|
||||
/* Use workspace to receive (small) request/ACK, to avoid overwriting precomputed reply */
|
||||
char *packet = daemon->workspacename;
|
||||
ssize_t plen = MAXDNAME * 2;
|
||||
ssize_t plen = MAXDNAMESTR;
|
||||
struct listener *listener;
|
||||
struct tftp_transfer *transfer, *tmp, **up;
|
||||
|
||||
@@ -833,6 +843,12 @@ static void free_transfer(struct tftp_transfer *transfer)
|
||||
close(transfer->file->fd);
|
||||
free(transfer->file);
|
||||
}
|
||||
|
||||
/* freeing this transfer and then allocating a new one
|
||||
may result in the same memory, but the contents of
|
||||
the transfer is no longer valid to cache. */
|
||||
if (daemon->srv_save == transfer)
|
||||
daemon->srv_save = NULL;
|
||||
|
||||
free(transfer);
|
||||
}
|
||||
@@ -852,7 +868,7 @@ static char *next(char **p, char *end)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* If we don't do anything, don't write the the input/ouptut
|
||||
/* If we don't do anything, don't write the input/output
|
||||
buffer. This allows us to pass in safe read-only strings constants. */
|
||||
static void sanitise(char *buf)
|
||||
{
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
|
||||
+234
-95
@@ -1,4 +1,4 @@
|
||||
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
||||
/* dnsmasq is Copyright (c) 2000-2026 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
|
||||
@@ -105,8 +105,6 @@ u32 rand32(void)
|
||||
|
||||
u64 rand64(void)
|
||||
{
|
||||
static int outleft = 0;
|
||||
|
||||
if (outleft < 2)
|
||||
{
|
||||
if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
|
||||
@@ -138,14 +136,15 @@ static int check_name(char *in)
|
||||
{
|
||||
/* remove trailing .
|
||||
also fail empty string and label > 63 chars */
|
||||
size_t dotgap = 0, l = strlen(in);
|
||||
size_t dotgap = 0, wiresize = 0, l = strlen(in);
|
||||
char c;
|
||||
int nowhite = 0;
|
||||
int idn_encode = 0;
|
||||
int hasuscore = 0;
|
||||
int hasucase = 0;
|
||||
|
||||
if (l == 0 || l > MAXDNAME) return 0;
|
||||
if (l == 0)
|
||||
return 0;
|
||||
|
||||
if (in[l-1] == '.')
|
||||
{
|
||||
@@ -156,7 +155,10 @@ static int check_name(char *in)
|
||||
for (; (c = *in); in++)
|
||||
{
|
||||
if (c == '.')
|
||||
dotgap = 0;
|
||||
{
|
||||
wiresize += dotgap + 1;
|
||||
dotgap = 0;
|
||||
}
|
||||
else if (++dotgap > MAXLABEL)
|
||||
return 0;
|
||||
else if (isascii((unsigned char)c) && iscntrl((unsigned char)c))
|
||||
@@ -198,6 +200,10 @@ static int check_name(char *in)
|
||||
idn_encode = idn_encode || hasucase;
|
||||
#endif
|
||||
|
||||
/* length of final label and terminaton added */
|
||||
if (!idn_encode && wiresize + dotgap + 2 > MAXDNAME)
|
||||
return 0; /* wire representation too long */
|
||||
|
||||
return (idn_encode) ? 2 : 1;
|
||||
}
|
||||
|
||||
@@ -266,6 +272,10 @@ char *canonicalise(char *in, int *nomem)
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* IDN library doesnt call our malloc wrapper, so log this by steam */
|
||||
if (ret)
|
||||
malloc_log(ret, strlen(ret)+1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -281,20 +291,24 @@ char *canonicalise(char *in, int *nomem)
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
|
||||
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, unsigned char *limit)
|
||||
{
|
||||
int j;
|
||||
|
||||
/* Never make a name larger than the RFC limit */
|
||||
if (!limit || (limit - p > MAXDNAME))
|
||||
limit = p + MAXDNAME;
|
||||
|
||||
while (sval && *sval)
|
||||
{
|
||||
unsigned char *cp = p++;
|
||||
|
||||
if (limit && p > (unsigned char*)limit)
|
||||
if (p > limit)
|
||||
return NULL;
|
||||
|
||||
for (j = 0; *sval && (*sval != '.'); sval++, j++)
|
||||
{
|
||||
if (limit && p + 1 > (unsigned char*)limit)
|
||||
if (p + 1 > limit)
|
||||
return NULL;
|
||||
|
||||
if (*sval == NAME_ESCAPE)
|
||||
@@ -323,13 +337,15 @@ void *safe_malloc(size_t size)
|
||||
}
|
||||
|
||||
/* Ensure limited size string is always terminated.
|
||||
* Can be replaced by (void)strlcpy() on some platforms */
|
||||
Can be replaced by (void)strlcpy() on some platforms.
|
||||
src may be NULL in which case we return an empty string. */
|
||||
void safe_strncpy(char *dest, const char *src, size_t size)
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
dest[size-1] = '\0';
|
||||
strncpy(dest, src, size-1);
|
||||
dest[0] = dest[size-1] = '\0';
|
||||
if (src)
|
||||
strncpy(dest, src, size-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,26 +357,6 @@ void safe_pipe(int *fd, int read_noblock)
|
||||
die(_("cannot create pipe: %s"), NULL, EC_MISC);
|
||||
}
|
||||
|
||||
void *whine_malloc(size_t size)
|
||||
{
|
||||
void *ret = calloc(1, size);
|
||||
|
||||
if (!ret)
|
||||
my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *whine_realloc(void *ptr, size_t size)
|
||||
{
|
||||
void *ret = realloc(ptr, size);
|
||||
|
||||
if (!ret)
|
||||
my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
|
||||
{
|
||||
if (s1->sa.sa_family == s2->sa.sa_family)
|
||||
@@ -443,10 +439,14 @@ int hostname_issubdomain(char *a, char *b)
|
||||
for (ap = a; *ap; ap++);
|
||||
for (bp = b; *bp; bp++);
|
||||
|
||||
/* a shorter than b or a empty. */
|
||||
if ((bp - b) < (ap - a) || ap == a)
|
||||
/* anything is a subdomain of the root domain. */
|
||||
if (ap == a)
|
||||
return (bp == b) ? 2 : 1;
|
||||
|
||||
/* b shorter than a */
|
||||
if ((bp - b) < (ap - a))
|
||||
return 0;
|
||||
|
||||
|
||||
do
|
||||
{
|
||||
c1 = (unsigned char) *(--ap);
|
||||
@@ -470,7 +470,6 @@ int hostname_issubdomain(char *a, char *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
time_t dnsmasq_time(void)
|
||||
{
|
||||
#ifdef HAVE_BROKEN_RTC
|
||||
@@ -595,6 +594,7 @@ void prettyprint_time(char *buf, unsigned int t)
|
||||
else
|
||||
{
|
||||
unsigned int x, p = 0;
|
||||
buf[0] = '\0';
|
||||
if ((x = t/86400))
|
||||
p += sprintf(&buf[p], "%ud", x);
|
||||
if ((x = (t/3600)%24))
|
||||
@@ -692,44 +692,24 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask
|
||||
return count;
|
||||
}
|
||||
|
||||
/* _note_ may copy buffer */
|
||||
int expand_buf(struct iovec *iov, size_t size)
|
||||
char *print_mac(unsigned char *mac, int len)
|
||||
{
|
||||
void *new;
|
||||
|
||||
if (size <= (size_t)iov->iov_len)
|
||||
return 1;
|
||||
|
||||
if (!(new = whine_malloc(size)))
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (iov->iov_base)
|
||||
{
|
||||
memcpy(new, iov->iov_base, iov->iov_len);
|
||||
free(iov->iov_base);
|
||||
}
|
||||
|
||||
iov->iov_base = new;
|
||||
iov->iov_len = size;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *print_mac(char *buff, unsigned char *mac, int len)
|
||||
{
|
||||
char *p = buff;
|
||||
int i;
|
||||
|
||||
if (len == 0)
|
||||
sprintf(p, "<null>");
|
||||
else
|
||||
for (i = 0; i < len; i++)
|
||||
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
|
||||
static struct iovec buff = { NULL, 0 };
|
||||
|
||||
return buff;
|
||||
/* each byte is two digits plus ':' except that last
|
||||
which is two digits plus terminator. */
|
||||
if (len == 0 || !expand_buf(&buff, len*3))
|
||||
return "<null>";
|
||||
else
|
||||
{
|
||||
char *p = buff.iov_base;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
|
||||
}
|
||||
|
||||
return buff.iov_base;
|
||||
}
|
||||
|
||||
/* rc is return from sendto and friends.
|
||||
@@ -777,43 +757,60 @@ int retry_send(ssize_t rc)
|
||||
"once" fails on EAGAIN, as this a timeout.
|
||||
This indicates a timeout of a TCP socket.
|
||||
*/
|
||||
int read_write(int fd, unsigned char *packet, int size, int rw)
|
||||
int read_writev(int fd, struct iovec *iov, int iovcnt, int rw)
|
||||
{
|
||||
ssize_t n, done;
|
||||
|
||||
for (done = 0; done < size; done += n)
|
||||
{
|
||||
if (rw & 1)
|
||||
n = read(fd, &packet[done], (size_t)(size - done));
|
||||
else
|
||||
n = write(fd, &packet[done], (size_t)(size - done));
|
||||
|
||||
if (n == 0)
|
||||
return 0;
|
||||
int cur = 0;
|
||||
ssize_t n, done = 0;
|
||||
|
||||
while (cur < iovcnt)
|
||||
{
|
||||
iov[cur].iov_len -= done;
|
||||
iov[cur].iov_base = ((char *)iov[cur].iov_base) + done;
|
||||
|
||||
if (rw & 1)
|
||||
n = readv(fd, &iov[cur], iovcnt - cur);
|
||||
else
|
||||
n = writev(fd, &iov[cur], iovcnt - cur);
|
||||
|
||||
iov[cur].iov_len += done;
|
||||
iov[cur].iov_base = ((char *)iov[cur].iov_base) - done;
|
||||
|
||||
if (n == -1)
|
||||
{
|
||||
n = 0; /* don't mess with counter when we loop. */
|
||||
|
||||
if (errno == EINTR || errno == ENOMEM || errno == ENOBUFS)
|
||||
continue;
|
||||
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
/* "once" variant */
|
||||
if (rw & 2)
|
||||
return 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(rw & 2) && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||||
continue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n == 0 && (rw & 1))
|
||||
return 0;
|
||||
|
||||
done += n;
|
||||
while (cur < iovcnt && (size_t)done >= iov[cur].iov_len)
|
||||
done -= iov[cur++].iov_len;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int read_write(int fd, unsigned char *packet, int size, int rw)
|
||||
{
|
||||
struct iovec iov;
|
||||
|
||||
/* size == 0 is not an error, just a NOOP. */
|
||||
if (size == 0)
|
||||
return 1;
|
||||
|
||||
iov.iov_len = (size_t)size;
|
||||
iov.iov_base = packet;
|
||||
|
||||
return read_writev(fd, &iov, 1, rw);
|
||||
}
|
||||
|
||||
/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */
|
||||
void close_fds(long max_fd, int spare1, int spare2, int spare3)
|
||||
{
|
||||
@@ -949,3 +946,145 @@ int kernel_version(void)
|
||||
return version * 256 + (split ? atoi(split) : 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define hash_ptr(x) (((uintptr_t)(x)) & 0xffffff)
|
||||
|
||||
void *whine_malloc_real(const char *func, unsigned int line, size_t size)
|
||||
{
|
||||
void *ret = calloc(1, size);
|
||||
|
||||
if (!ret)
|
||||
my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
|
||||
else if (daemon->log_malloc)
|
||||
my_syslog(LOG_INFO, _("malloc: %s:%u %zu bytes at %x"), func, line, size, hash_ptr(ret));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *whine_realloc_real(const char *wrapper, const char *func, unsigned int line, void *ptr, size_t size)
|
||||
{
|
||||
unsigned int old = hash_ptr(ptr);
|
||||
void *ret = realloc(ptr, size);
|
||||
|
||||
if (!ret)
|
||||
my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size);
|
||||
else if (daemon->log_malloc)
|
||||
{
|
||||
if (!wrapper)
|
||||
wrapper = "realloc";
|
||||
my_syslog(LOG_INFO, _("%s: %s:%u %zu bytes from %x to %x"), wrapper, func, line, size, old, hash_ptr(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* _note_ may copy buffer */
|
||||
int expand_buf_real(const char *func, unsigned int line, struct iovec *iov, size_t size)
|
||||
{
|
||||
void *new;
|
||||
|
||||
if (size <= (size_t)iov->iov_len)
|
||||
return 1;
|
||||
|
||||
if (!(new = whine_realloc_real("expand_buf", func, line, iov->iov_base, size)))
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
iov->iov_base = new;
|
||||
iov->iov_len = size;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int expand_workspace_real(const char *func, unsigned int line, unsigned char ***wkspc, int *szp, int new)
|
||||
{
|
||||
unsigned char **p;
|
||||
int old = *szp;
|
||||
|
||||
if (old >= new+1)
|
||||
return 1;
|
||||
|
||||
new += 5;
|
||||
|
||||
if (!(p = whine_realloc_real("expand_workspace", func, line, *wkspc, new * sizeof(unsigned char *))))
|
||||
return 0;
|
||||
|
||||
memset(p+old, 0, (new-old) * sizeof(unsigned char *));
|
||||
|
||||
*wkspc = p;
|
||||
*szp = new;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void malloc_log_real(const char *func, unsigned int line, void *mem, size_t size)
|
||||
{
|
||||
if (mem && daemon->log_malloc)
|
||||
my_syslog(LOG_INFO, _("malloc: %s:%u %zu bytes at %x"), func, line, size, hash_ptr(mem));
|
||||
}
|
||||
|
||||
#undef free
|
||||
void free_real(const char *func, unsigned int line, void *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
if (daemon->log_malloc)
|
||||
my_syslog(LOG_INFO, _("free: %s:%u block at %x"), func, line, hash_ptr(ptr));
|
||||
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* get lines from f, expand buffer as required. Buffer is freed when
|
||||
EOF reached and we return zero. Buffer also freed if f == NULL. */
|
||||
int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
|
||||
{
|
||||
size_t cnt = 0;
|
||||
char *buff = *buffp;
|
||||
|
||||
while (1) {
|
||||
int c = f ? getc(f) : EOF;
|
||||
|
||||
if (cnt == *sizep)
|
||||
{
|
||||
void *new;
|
||||
|
||||
if ((new = whine_realloc(buff, cnt + 1024)))
|
||||
{
|
||||
buff = *buffp = new;
|
||||
*sizep = cnt + 1024;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* allocation failed, ignore line.
|
||||
Whine_realloc will have complained. */
|
||||
while (c != '\n' && c != EOF)
|
||||
c = getc(f);
|
||||
|
||||
cnt = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
buff[cnt] = 0;
|
||||
|
||||
if (c == '\n')
|
||||
return 1;
|
||||
|
||||
if (c == EOF)
|
||||
{
|
||||
/* handle last line without '\n' */
|
||||
if (cnt != 0)
|
||||
return 1;
|
||||
|
||||
free(buff);
|
||||
*buffp = NULL;
|
||||
*sizep = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
buff[cnt++] = c;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user