Compare commits

...
97 Commits
Author SHA1 Message Date
Simon Kelley ff4964d01d Re-implement print_mac() to do its own buffer managment.
print_mac() used to output into caller-supplied buffer with no
length checking. The various callers supplied different
length buffers and did their own, ad-hoc, length checking before
calling print_mac(). This was an accident waiting to happen and
it had actually happened in couple of places, with buffer
overflows possible.

print_mac() now returns the address of its own buffer, which
is grown as required.

Thanks to Nicholas Carlini <npc@anthropic.com> for spotting this problem.
2026-05-31 21:37:48 +01:00
Michalis Vasileiadis 45771dc92c Fix OOB writes in contrib/leasequesry/leasequery.c
Thanks to Michalis Vasileiadis for spotting this.

print_mac() writes each MAC byte to its output buffer with unbounded
sprintf and is called from main() with pkt.header.hlen straight out
of a BOOTREPLY. hlen is an attacker-controlled uint8_t (up to 255),
each byte expands to up to 3 chars, and the destination is a 500-byte
stack buffer. A malicious leasequery server that echoes the client's
transaction ID and replies with DHCPLEASEACTIVE and an oversized
hlen overflows that stack buffer.

The patch caps len inside print_mac at DHCP_CHADDR_MAX (16), which is
the actual size of chaddr in the BOOTP header and an upper bound on
any legitimate hardware-address length. The other in-file caller of
print_mac already clamps its length argument to 14 before the call,
so this change is local to the vulnerable path.
2026-05-30 15:58:52 +01:00
Simon Kelley af73e48067 Include fc00::/8 in the set of local addresses.
The current local address range is fd00::/8, but RFC 4193
para 3.1/3.2 reserves fc00::/8 for future use as local addresses,
so add that to the list.

Thanks to Michalis Vasileiadis for spotting this omission.
2026-05-30 13:46:44 +01:00
Simon Kelley 77d4749a5e Fix DNSSEC frec use-after-free in TCP validation path.
Full credit to Jerry Tom <goonsomeway@gmail.com> for finding this.

When DNSSEC validation falls back to TCP (via a forked child process),
the parent uses a uid-based check to detect whether a frec has been
freed and reused before the child returns. It doesn't, however check for
the more likely scenario which is that the frec has simply been
freed via the timeout path in get_new_frec(). This leads to a
use-after-free on an already freed frec.
2026-05-29 17:24:54 +01:00
OpenAI Security Research Team 0375e40824 Fix bounds-checking in contrib/lease-tools/dhcp_release6.c
Credit to OpenAI Security Research Team, outbounddisclosures@openai.com
2026-05-29 16:28:49 +01:00
Simon Kelley 18a139f48f Change "limit" arg to add_pseudoheader() to a length.
Also ripple effects for most of its callers.

This fixes an OOB write caused by bad pointer arithmetic
calculating the pointer-to-end-of-packet used before.

Thanks to Donghyeon Jeong for spotting the bug.
2026-05-17 13:06:17 +01:00
Simon Kelley d5d2bfb74a Change "limit" arg to make_local_answer() to a length.
This fixes an OOB write caused by bad pointer arithmetic
calculating the pointer-to-end-of-packet used before.

Thanks to Donghyeon Jeong for spotting the bug.
2026-05-17 13:05:35 +01:00
Simon Kelley 7f5802c144 Tidy up get_line_alloc() 2026-05-17 00:33:55 +01:00
Simon Kelley 7280e46aa5 Check for too-small packets in relay_upstream6()
Before this fix, the code could read a couple of bytes beyond
valid data when an invalid too-short packet was recieved.

Thanks to Sokhna Walo DIAKHATE <sokhnawalodiakhate@esp.sn>
for the bug report.
2026-05-17 00:24:57 +01:00
Simon Kelley 754177b368 Fix hostname_issubdomain() confusion.
commit 1269f074f8 broke
hostname_issubdomain() and then accidentally fixed
some of the problems by inverting the argument order
in the new uses of the function it introduced.
All the pre-existing calls to hostname_issubdomain()
were left in a non-working state.

This fixes hostname_issubdomain() back to the state
before 1269f074f8, adds
some logic to give correct answers when comparing
to the root domain, and gets right the new calls to
hostname_issubdomain()  added in
1269f074f8

Thanks to Jean Thomas for spotting this problem.
2026-05-16 23:52:29 +01:00
Simon Kelley 228d7ff964 Remove arbitrary limit on config file line lengths.
Stop using the namebuff buffer to read config file
lines. This reduced in size in
014e909f78 and caused
regressions. We now resize the buffer as needed,
so there is no practical limit.

Use the same code to remove the same limit when reading
/etc/ethers and /etc/resolv.conf. Also when copying output
from the lease-change script into the log.

Thanks to Kevin Darbyshire-Bryant for finding the regresssion.
2026-05-16 21:37:11 +01:00
do litli 14094e88be Fix buffer OOB read in find_soa()
In find_soa() extract_name() is called with extrabytes=0 when parsing NS
record names, which means it only validates that the DNS name fits
within the packet but does not check that 10 additional bytes exist for
the type/class/TTL/rdlen fixed fields. Lines 546-549 then
unconditionally read these 10 bytes via GETSHORT/GETLONG macros. An
attacker controlling a DNS zone can craft a NXDOMAIN response where the
NS record name extends to the packet boundary, causing a 10-byte
out-of-bounds read past the valid packet data (CWE-125, CVSS 5.3
Medium). The read stays within the over-allocated packet buffer in
default configurations, limiting crash risk, but accesses data outside
the logical packet boundary. Under certain conditions, the overread may
access stale heap data from prior transactions.

The fix is straightforward: change the extrabytes
argument from 0 to 10, consistent with other call sites in
the same file.

Credit is due to do litli for finding this problem.
2026-05-10 22:03:50 +01:00
Simon Kelley c705607122 Fix problen in from_wire() when names contain escape chars.
Thanks to Royce M <royce@xchglabs.com> for spotting this.
2026-05-10 21:49:40 +01:00
Simon Kelley 9d2c93b608 Sync 3ea905b98b with 7cd050075ea73cd7707fce5b70f0fec25f722f67 2026-05-10 21:49:40 +01:00
Simon Kelley 073082ddc0 Fix buffer overflow vulnerability in extract_addresses() CVE-2026-5172
Thanks to Hugo Martinez Ray for spotting this.

The value of rdlen for an RR can be a lie, allowing the
call to extract_name() at rfc1025.c:952 to advance the value of p1
past the calculated end of the record. The makes the calculation
of bytes remaining in the RR underflow to a huge number and results
in a massive heap OOB read and certain crash.
2026-05-10 21:49:40 +01:00
Simon Kelley 9f651f3398 Updates to a345b075b3b8696a18631b46e53ab654354ed961 following review. 2026-05-10 21:49:40 +01:00
Simon Kelley e3a26d092e Fix broken client subnet validation. CVE-2026-4893
Bug report from Royce M <royce@xchglabs.com>

Location: forward.c:713, edns0.c:421

With --add-subnet enabled, process_reply() passes the OPT record
length (~23 bytes) instead of the packet length to check_source().
All internal bounds checks fail, and the function always returns 1.
ECS source validation per RFC 7871 Section 9.2 is completely bypassed.
2026-05-10 21:49:40 +01:00
Simon Kelley 10e6b5b83e Fix buffer overflow in helper.c with large CLIDs. CVE-2026-4892
Bug reported bt Royce M <royce@xchglabs.com>

Location: helper.c:265-270
DHCPv6 CLIDs can be up to 65535 bytes. When --dhcp-script is configured,
the helper hex-encodes raw CLID bytes via sprintf("%.2x") into daemon->packet (5131 bytes).
A 1000-byte CLID writes ~3000 bytes. The helper process retains root privileges.

Note: log6_packet() correctly caps CLID to 100 bytes for logging, but the helper code path was missed.
2026-05-10 21:49:40 +01:00
Simon Kelley 788b4e0f6c Verify rdlen field in RRSIG packets. CVE-2026-4891
Bug report from Royce M <royce@xchglabs.com>

This avoids crafted packets which give a value for rdlen _less_
then the space taken up by the fixed data and the signer's name
and engender a negative calculated length for the signature.
2026-05-10 21:49:40 +01:00
Simon Kelley 7b151eb606 Fix NSEC bitmap parsing infinite loop. CVE-2026-4890
Report from Royce M <royce@xchglabs.com>.

Location: dnssec.c:1290-1306, dnssec.c:1450-1463

The bitmap window iteration advances by p[1] instead of p[1]+2
(missing the 2-byte window header). With bitmap_length=0, both rdlen and p are
unchanged, causing an infinite loop and dnsmasq stops responding to all queries.

Bug is reachable before RRSIG validation
(confirmed by the source comment at line 2125), so no valid
DNSSEC signatures are needed.
2026-05-10 21:49:40 +01:00
Simon Kelley 014e909f78 Rework storage allocation for domain names. CVE-2026-2291
This is the stable release fix for CVE-2026-2291

Dnsmasq recieves and sends domain names within DNS packets in RFC-1035
format, a string of counted labels terminated by a zero length label.
Internally to dnsmasq, domain names are represented as zero-terminated C strings
with the labels seperated by '.' characters, like the normal presentation
format for domain names.

The RFC-1035 limit for a domain name in wire format is 255 octets, and
when converted to presentation format, this makes a string of
maximum 253 characters. However dnsmasq needs to be able to
represent any 8-bit character within a label, and this causes
problems with /000 (which would terminate the c-string early) and
'.' (which would terminate a label early). To avoid this, dnsmasq
escapes both of these characters, which means that they take two bytes
in the string. The domain name with must charaters in it has four labels
and the 255 octet wire length limits these four labels to a total of
250 characters. The other five octets are the four label length bytes
and the zero length byte terminator. If all 250 characters need escaping
that makes the maximum length of the dnsmasq internal format 503
characters (250 escaped characters in labels, amd three dots between
four labels) Since this format is zero-terminated c-string, buffers
have to be allocated as 504 bytes.

This arrangement grew in a somewhat ad-hoc manner and early dnsmasq
didn't do the escaping trick, and didn't really differentiate between
the lengths of the wire format and the presentation format, since
they were very similar. It also, for reasons lost in time, inherited
a defintion for MAXDNAME from early BIND headers, set at 1025 bytes.
This value was used as the buffer size for both wire and presentation
formats.

This patch makes everything consistent. It declares two constants

MAXDNAME 255     /* max size of wire format domain name */
MAXDNAMESTR 503  /* max size of internal format created from a 255 octet wire format name */

All internal name buffers are allocated as MAXDNAMESTR+1 bytes,
to hold a maximum size internal format name and zero termination.

Names parsed out of incoming packets are rejected if their
wire format is greater than 255 bytes, which ensures that
their internal representation cannot exceed 503 characters.
Names inserted into outgoing packets are failed similarly if
they exceed 255 bytes. (this would in theory be possible
for a legal internal-representaion name if it has at least
one unescaped character in a label.
2026-05-10 21:49:39 +01:00
Simon Kelley 29f8712f02 Fix OOB read/write of rr_status.
Crafted packets can write zeros outside the limits of the rr_status
array, causing a crash and DoS.

Thanks to Haiyang Huang for finding and reprting this.
2026-05-10 21:47:12 +01:00
Simon Kelley 0450f9e28a Fix OOB read when parsing DHCPv6 userclass or vendorclass options.
Credit to Haiyang Huang for finding this.

The fix extends the improved framework for DHCPv6 options added in
b95d5a2577
to the encapsulated vendor and user options.
2026-05-10 16:49:59 +01:00
Sagie D. 0637bcf010 Add support for IPV6 when reading ARP/ND table on *BSD.
The arp_enumerate() function is extended into a per-family
helper arp_enumerate_family(), called sequentially for AF_INET and
AF_INET6, allowing the NDP neighbour cache to be enumerated alongside
the ARP table. An empty table for either family is treated as vacuous
success rather than an error. Both tables are acquired in *BSD via
PF_ROUTE sysctl calls that return raw kernel structures; consequently,
for IPv6, the sockaddr_in6 peer address extracted from them has an
embedded link-local scope ID. This ID is extracted into sin6_scope_id,
and bytes 2-3 of the address are then cleared, per the KAME API
contract.

Signed-off-by: Sagie D.
2026-05-09 21:44:38 +01:00
Simon Kelley 4bcc9650fa Fix memory leak reading ARP cache on *BSD.
arp_enumerate() on *BSD platforms has a bad memory leak.

Thanks to Sagie Duchovne-Nave for finding this problem. His
description is included below:

 arp_enumerate() allocates a heap buffer via expand_buf() to hold the
 kernel ARP table dump retrieved through sysctl(NET_RT_FLAGS). This
 buffer is never freed on any return path -- neither the early error
 returns nor the normal return after iteration -- causing a leak on
 every call.

 The leak is most acute in the DHCPv6 path. get_client_mac() calls
 find_mac() up to five times per packet with lazy=0. Because the
 'updated' flag is local to each find_mac() invocation, a cached
 ARP_EMPTY entry for an unresolvable IPv6 address does not short-
 circuit the kernel lookup: each call falls through to iface_enumerate()
 -> arp_enumerate(), leaking one buffer per call. This yields up to
 five leaked allocations per DHCPv6 SOLICIT packet. The leak size per
 call equals the full system-wide IPv4 ARP table dump across all
 interfaces.

 The condition is readily triggered by a DHCPv6 client whose MAC
 address cannot be resolved via NDP -- which is the common case on
 FreeBSD, because arp_enumerate() queries NET_RT_FLAGS/RTF_LLINFO,
 which returns IPv4 ARP entries only; IPv6 NDP neighbour entries are
 not included. As a result every IPv6 MAC lookup fails unconditionally
 on FreeBSD, every failed lookup produces an ARP_EMPTY record that is
 never promoted, and every subsequent packet for that client leaks five
 buffers.

This fix is by Simon Kelley, not Sagie, so any bugs are my fault.
The fix is to make the buffer statically allocated, so that expand_buf()
is normally a NOOP, but when the ARP cache grows, it grows to match.
This avoids fragmenting the heap with many malloc/free calls.
It's a common design pattern in dnsmasq, but the original author
of this code missed the importance of that static allocation. Since
that was before dnsmasq moved into git, the details are lost to time.
2026-05-04 16:18:33 +01:00
Florian MargaineandClaude Opus 4.6 fc2286805f Preserve existing log file permissions when adding group-write bit.
Commit 1f8f78a49b ("Add root group writeable flag to log file")
introduced a fchmod() call in log_start() that resets the file mode
to a hardcoded value (0660), discarding any pre-existing permissions.
This broke our usage of dnsmasq where we create the log file with
specific permissions before starting dnsmasq in an LXC container
namespace, so that unprivileged users inside the container can read
the log. The hardcoded mode strips those permissions on startup.

Use the existing stat result to OR in S_IWGRP instead, equivalent
to chmod g+w, so that only the group-write bit is added without
disturbing other permission bits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:41:10 +01:00
Simon Kelley d158aeca75 Increase default limit on TCP handler processes from 20 to 30.
There's evidence that modern Chrome may need this. It has been 20
forever, and hardware inflation would support an increase.

https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2026q2/018450.html
2026-04-21 22:27:07 +01:00
Simon Kelley 36d081e374 Fix buffer overlow in log_query()
The addition of "(not supported)" to logs of DS and DNSKEY replies
overflows the buffer used to construct the string.

Re-arrange things to avoid this, and add checks to avoid the same  problem
if the logging calls change in the future.

Thanks to Yiwei Hou for finding this.

The problem exists is DNSSEC is enabled and query logging is also
enabled. The overwrite is of bounded length and the bytes
written are not in control of an attacker, so this is not considered
a likely remote-execution vector.
2026-04-21 22:14:41 +01:00
Toliak Purple 9eb0757da9 Fix crash with mal-formed config option.
Reproduction Steps. Both commands cause a segmentation fault:
```
dnsmasq --interface-name=,
dnsmasq --dynamic-host=,::,
```

Stack Trace (ASAN, v2.90)
```
==1817==ERROR: AddressSanitizer: SEGV on unknown address
0x000000000000 (pc 0x7f0203d22845 bp 0x7ffc3316b920 sp 0x7ffc3316b0a0
T0)
==1817==The signal is caused by a READ memory access.
==1817==Hint: address points to the zero page.
    #0 0x7f0203d22845 in __interceptor_strncmp
../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:488
    #1 0x5563361feeb3 in iface_allowed /opt/dnsmasq/src/network.c:361
    #2 0x556336203aca in iface_allowed_v6 /opt/dnsmasq/src/network.c:622
    #3 0x55633625cc4c in iface_enumerate /opt/dnsmasq/src/netlink.c:291
    #4 0x55633620573c in enumerate_interfaces /opt/dnsmasq/src/network.c:836
    #5 0x55633615c3fe in main /opt/dnsmasq/src/dnsmasq.c:367
```

The fault occurs due to a null-pointer dereference in iface_allowed()
at line 361:
```
if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0)      //// here
int_name->intr is NULL
```

That occurs due to a null-pointer assignment in the option.c at line 4856:
```
new->intr = opt_string_alloc(arg);               //// if arg is empty
string, the opt_string_alloc will return NULL
```
2026-04-21 21:49:35 +01:00
Simon Kelley 2d0e0c7a54 Tweak DHCPv6 replay decapsulation.
RFC 8415 doesn't exclude the possiblity that a relay-forward
message could contain _more_than_one relay-message option. The
existing code could, under certain circumstances, access freed
stack memory in this case.

Thanks to Dejan Alvadzijevic and his fuzzer for finding this
condition.

This patch fixes things so that dnsmasq does at least sensible
things with multiple relay-message options, and no longer
has the (remote) possibility of accessing dead memory.
2026-04-10 13:35:27 +01:00
Simon Kelley cfc2244c2a Bump copyrights to 2026. 2026-04-08 12:08:18 +01:00
Simon Kelley 9ad74926d4 Fix 1-byte buffer overflow in relay_reply4()
Potential SIGSEGV when using DHCPv4-relay.

Thanks to Asim Viladi Oglu Manizada for finding this.
2026-04-06 22:22:43 +01:00
Simon Kelley a6069da373 Check DNS query via TCP doesn't have QR bit set. 2026-04-05 22:50:43 +01:00
Simon Kelley 14f53171ca Sanity checking on DNS replies via TCP. 2026-04-05 22:25:56 +01:00
Simon Kelley 8fd06d2cc0 OOB buffer check with DNS bitstring labels. 2026-03-30 21:19:26 +01:00
Simon Kelley d152e81e42 Tighten check on TFTP pathnames to avoid directory escape. 2026-03-30 21:18:11 +01:00
Simon Kelley e0bec3342b Move logging on TCP timeout from child process to main process.
Callong my_syslog from a signal handler is not good.

Thanks to Dominik Derigs for spotting this.
2026-03-30 20:23:56 +01:00
Dominik dd707937d5 dump: check lseek() return in pcap record-counting loop
When --dump-file points to an existing pcap file, dump_init_file()
counts the existing records by reading each pcap record header and
seeking past the payload.  The lseek() return value was not checked:
if it fails (e.g. EOVERFLOW on a 32-bit build with a very large
incl_len, or any other seek error) the file position does not advance,
the next read_write() call reads the same header again, and the loop
spins forever — hanging daemon startup.

Fix: break out of the loop on lseek failure.  An undercount of
existing records is harmless; a hanging startup is not.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 20:15:44 +01:00
Dominik c5f46412a3 domain: fix strncat buffer-size argument in is_rev_synth()
strncat(dst, src, n) truncates src to n bytes, but n should be the
remaining space in dst, not the total buffer size.  Passing MAXDNAME
as n allowed the combined prefix + address + "." + domain to overflow
the MAXDNAME-byte namebuff if c->domain was close to MAXDNAME chars.

Replace MAXDNAME with MAXDNAME - strlen(name) - 1 at each call site
(lines 178-179 for IPv4, 204 for the IPv6 hex-fragment loop, 208-209
for the IPv6 suffix) so the append is always bounded by actual
remaining capacity.

Security implications: strncat(name, c->domain, MAXDNAME) passes the
full buffer size instead of remaining space. Overflow requires a local
admin to configure a --synth-domain with a domain name > ~960 chars.
The actual exploitable window is narrow: the admin would need a
--synth-domain with a domain name close to 1024 chars (which check_name
permits) combined with a non-trivial prefix or IPv6 address.
Not remotely exploitable.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 20:06:43 +01:00
DominikandClaude Sonnet 4.6 cd04c0ce20 lease: check parse_hex() return value before use as length
parse_hex() returns -1 on malformed input.  Three call sites in
read_leases() used the return value directly as a length argument
without checking for the error sentinel:

  opt_len  → lease_set_vendorclass() / lease_set_agent_id()
              whine_malloc(-1) attempts a ~4 GB allocation, likely
              crashing the process.

  hw_len   → lease_set_hwaddr()
              memcpy(lease->hwaddr, hwaddr, (size_t)-1) performs an
              ~18 EB write, causing an immediate crash.

  clid_len → lease_set_hwaddr() (same path as hw_len)

Exploitation requires a corrupted or attacker-controlled lease file;
it is not directly network-triggerable.  However, any process that can
write to the lease file (e.g. a compromised helper) can crash or
potentially gain code execution in the dnsmasq process at next startup
or lease-file re-read.

Fix: mirror the existing duid path (which already guards its
parse_hex call) by checking for < 0.  For opt_len, skip the record
(continue) to match the "invalid line" behaviour.  For hw_len and
clid_len, treat the failure as a zero-length field so the lease is
still created with the correct address.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:06:34 +01:00
Dominik 0630a45ae1 edns0: bounds-check option length before memcmp in check_source()
The EDNS0 option iteration loop read a 2-byte len field from the
packet and used it directly in:
  opt.scope_netmask = p[3];          // requires len >= 4
  memcmp(p, &opt, len)               // reads len bytes from packet

Neither access was guarded against len exceeding the remaining RDATA.
A crafted DNS reply whose OPT RDATA contains a CLIENT_SUBNET option
with an oversized len field would cause memcmp to read past the end of
the packet buffer, leaking heap memory.

Risk: currently the only caller that reaches this branch is
forward.c:process_reply(), which (before the companion fix) passed an
OPT-RR-scoped plen that made skip_name() return NULL first, so the
unsafe code was never reached.  With that companion fix applied the
path is live: a rogue upstream server or on-path attacker can craft a
reply with a malformed CLIENT_SUBNET len and trigger the OOB read,
potentially leaking adjacent heap contents.

Fix: break out of the loop if the option body would extend past the
RDATA boundary.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:24:11 +01:00
Dominik 3ea905b98b rfc3315: fix integer underflow and heap overflow in log6_opts STATUS_CODE
In log6_opts(), the OPTION6_STATUS_CODE branch did:
  memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
  daemon->namebuff[len + opt6_len(opt) - 2] = 0;

Two bugs:
1. Integer underflow: if opt6_len(opt) < 2, the subtraction wraps to a
   huge size_t, making memcpy write gigabytes and crash with SIGSEGV.

2. Heap overflow: no upper-bound check.  opt6_len() can return up to
   65535, but daemon->namebuff is only (MAXDNAME*2)+1 = 2051 bytes.
   A STATUS_CODE payload larger than ~2045 bytes would overflow the
   heap buffer.

Exploitability: log6_opts() is only ever called on daemon->outpacket,
the outgoing DHCPv6 reply built by dnsmasq itself — NOT on incoming
client packets.  Consequently, these bugs are NOT directly triggerable
by a remote attacker under the current code.  The fix is defensive:
put_opt6_short() (which writes STATUS_CODE options in outgoing packets)
always produces opt6_len >= 2, so neither bug fires in practice today.
However, the unchecked arithmetic is a latent hazard that could become
exploitable if future refactoring passes incoming option data through
log6_opts():
- Guard the block with opt6_len(opt) >= 2 to prevent underflow.
- Cap slen to the remaining space in daemon->namebuff.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:13:01 +01:00
Dominik ca509cb611 dhcp-common: fix OOB reads in DHCPv6 option_string() decoders
Two decoders in option_string() read beyond the end of the supplied
option-value buffer when a malformed DHCPv6 option is received:

1. OT_RFC1035_NAME: after advancing i past the last label (i = l),
   val[i] was read at line 885 to decide whether to append a '.'.
   If the label ends exactly at opt_len, i == opt_len and val[i]
   is one byte past the option data.  Fixed by guarding with
   i < opt_len before the read.

2. OT_CSTRING: the loop opened with while(1) and called GETSHORT
   (a 2-byte read) before checking that two bytes remained, so a
   1- or 0-byte option value caused an immediate 1-byte OOB read.
   Additionally, if the length field exceeded the remaining option
   data the inner body-copy loop read past the end.  Fixed by
   changing the loop condition to while(i+2 <= opt_len) and adding
   a check that the body fits before copying.

Both reads land inside the received packet buffer (no crash), but
they allow a DHCPv6 client to cause dnsmasq to read unrelated packet
bytes and emit them in syslog output.

Security: OOB reads of up to option-length bytes from a
network-supplied DHCPv6 packet; reachable only with --log-dhcp
enabled; no memory corruption, crash, or sensitive data disclosure
beyond adjacent packet bytes.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:09:43 +01:00
Dominik 8ceb8ee9b5 dhcp-common: bounds-check label index before reading in OT_RFC1035_NAME decoder
In option_string(), the RFC1035-name label decoder sets i = l (the
position past the current label) and then reads val[i] to decide
whether to append a '.' separator — but without first checking that
i < opt_len.  If the last label ends exactly at the option boundary
(no NUL terminator), i == opt_len and val[i] reads one byte beyond
the option data.

The read lands inside the packet buffer so no crash occurs, but the
out-of-bounds byte (typically the next DHCPv6 option's type field)
could produce a spurious trailing '.' in --log-dhcp output when a
client sends a domain-search/nis-domain option without a trailing NUL.

Security: OOB read of 1 byte from a network-supplied DHCPv6 packet;
reachable only with --log-dhcp enabled; no memory corruption, crash,
or meaningful information disclosure.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:00:22 +01:00
Dominik 1c4a97edb5 rfc2131: fix off-by-one in BOOTP filename netid NUL termination
After copying the 128-byte DHCP file field into dhcp_buff2, the
sentinel was written to index 129 instead of 128.  The byte at
index 128 was therefore left holding stale data from the previous
DHCP exchange, so a client that fills the entire file field with
non-NUL bytes would produce a 129-character netid tag whose last
byte is heap garbage.  In theory this allows incorrect netid-tag
matching; in practice it requires the client to know the heap
state of a prior transaction and BOOTP use is uncommon.

Security: very-low-severity heap-content exposure that could
influence DHCP option selection via tag mismatch; no memory
corruption or crash.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:59:50 +01:00
Dominik 9e3e0465cd helper: fix OOB read in grab_extradata_lua bounds check
The loop in grab_extradata_lua checked *next != 0 in the loop
condition before the body could test next == end, so when next
reached end the byte *end was read out-of-bounds. The non-Lua
sibling grab_extradata had the correct order (test next == end
before dereferencing). Fix by adopting the same idiom.

Security: a DHCP client can supply a vendor-class option whose
content fills the extradata buffer with no trailing NUL, causing
a 1-byte OOB read in the Lua helper child (already non-root).
Requires HAVE_LUASCRIPT and a configured Lua script. Not feasible
for triggering a crash from remote reliably.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:57:44 +01:00
Dominik b12a9249f6 fix: NUL-terminate buf in prettyprint_time() when t == 0
When t == 0 every conditional branch is skipped and buf is never
written, leaving it without a NUL terminator.  The caller in
dhcp-common.c log_opts() does `p += strlen(p)` immediately after,
scanning uninitialised stack memory until it finds a stray zero byte.

Reachable from the network: a local attacker can craft a DHCP packet
with any time-typed option (e.g. option 51) set to 0x00000000,
triggering this path via the option-logging code. Impact is limited
to stack bytes appearing in syslog output (information disclosure),
not reachable for code execution. Fix by zeroing buf[0] first so
the buffer is always well-formed.

This bug does not look like it could be a remotely triggerable DoS
vector as strlen(buf) on an uninitialised heap buffer will almost
certainly find a NUL byte in adjacent heap metadata before
hitting an unmapped page.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:34:56 +01:00
Dominik 9dec84ca42 fix: rand64() must share global outleft with rand16()/rand32()
rand64() declared its own `static int outleft`, shadowing the global
one while all three functions share the same out[] SURF output buffer.
When rand64() called surf() to refill out[], the global outleft still
indexed into the now-overwritten buffer, so subsequent rand32() or
rand16() calls could return the same words rand64() had already
consumed.  Drop the local static so all three functions coordinate
through the same counter.

Security: rand64() is used to generate DHCPv6 temporary address
interface IDs, which are visible on the local network.  Because of the
shared buffer issue, an observer could reconstruct the out[]
words consumed by rand64() and predict the next rand32() outputs.
rand32() drives the 0x20 case-scrambling bitmap used to harden DNS
queries against cache-poisoning, so this leaks PRNG state across the
DHCPv6 and DNS code paths. I'd still say the overall severity is rather
low as local network access required, and 0x20 encoding is really a
defence-in-depth and not some kind of essential security.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:33:08 +01:00
Dominik 68c149e9dd fix: move fd-match guard out of loop in reply_query()
The `if (serv == last) return;` was inside the loop
`for (serv = first; serv != last; ...)`, making it dead code — the
loop condition ensures the body never runs when serv == last. Replies
arriving on an unrecognised file descriptor were therefore never
rejected by the fd check. Moving the guard after the loop causes it to
fire when no server socket matches.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:32:09 +01:00
Dominik 5ea054b264 fix: zero correct byte count in expand_workspace_real()
memset(p+old, 0, new-old) was missing the sizeof(unsigned char *)
multiplier.  p is unsigned char**, so each slot is pointer-sized (8
instead of 1 byte on 64-bit), yet only new-old bytes were zeroed,
leaving newly allocated DNSSEC workspace pointer slots with garbage values.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:31:20 +01:00
Simon Kelley b95d5a2577 Further tidying of DHCPv6 packet dissection code.
Make things more logical, and put buffer overflow detection in one
new function, opt6_first().
2026-03-25 12:11:54 +00:00
Simon Kelley 5be5dc1f16 New heuristic for disabling DNSSEC for domain-specific nameservers.
If there's no configured server for the parent  of the domain
covered by a domain-specific server, assume that said domain is
not signed. This extends the existing logic which makes similar
decisions for replies to DS queries from tghe parent server.
2026-03-24 12:35:46 +00:00
Simon Kelley 48348a2d77 Fix broken DHCPv6 userclass data in DHCP script.
Exactly the same principle as 53313014b5
2026-03-17 13:40:10 +00:00
Simon Kelley b4a0663654 Accept DHCPv6 vendorclasses with any enterprise number in --dhcp-vendorclass
if not enterprise number is specified. Also accept and match on
enterprise number only.
2026-03-17 13:29:49 +00:00
Simon Kelley 53313014b5 Fix broken DHCPv6 vendorclass data in DHCP script.
The code sending vendorclass data to the DHCP script
was confused about the format of the vendorclass in
the DHCP packet, resulting in broken data passed
to the packet.
2026-03-17 12:09:36 +00:00
Simon Kelley cf08eeee12 Complete 32a54fc8a5 2026-03-15 16:03:29 +00:00
Simon Kelley 0597828b74 Tighten length checking to avoid possible buffer read-overrun in DHCPv6.
Thanks to Dejan Alvadzijevic for spotting the problem.
2026-03-15 16:01:58 +00:00
Simon Kelley c8168e888e Don't call qsort() with a NULL array when no servers defined. 2026-03-10 21:17:41 +00:00
Simon Kelley 3a052d55fb Fix regression handling non-QUERY requests over TCP.
Regression introduced in 729c16a8ac
2026-03-08 22:19:30 +00:00
Simon Kelley 1eab169173 Fix missed hash->digest calls in 4070a74862 2026-03-01 12:33:38 +00:00
Simon Kelley 4070a74862 Fix FTBFS with nettle 4.0.
Thanks to Andreas Metzler for the heads-up.
2026-03-01 12:19:11 +00:00
Simon Kelley 65adc53130 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.
2026-02-28 21:30:57 +00:00
Simon Kelley 66d9658f5f Fix crash with empty DHCP SNAME option.
dhcp-option=66,

sets the servername to nothing, and causes a segfault at DHCP
transaction time. There may be other ways to provoke this, for
instance by using an empty filename.

The patch make safe_strncpy() even safer, by handing a NULL src argument.

Thanks to Jeff Allen for spotting and reporting this.
2026-02-26 15:10:41 +00:00
Thomas Erbesdobler ca30c82647 Fix broken NS responses in certain auth-zone configurations.
If dnsmasq is configured as an authoritatve server for zone transfer
_only_, (ie no interface name or address in --auth-server)
and secondary auth servers are configured, then
queries for NS RRs at the auth zone will get mangled answers.
This problem doesn't occur in AXFR or SOA queries, only NS queries.

Thanks to Thomas Erbesdobler for finding and analysing this problem.
The patch here is substantially his, with a little but of
collateral code tidying by srk.
2026-02-26 13:30:44 +00:00
Simon Kelley 32a54fc8a5 Remove duplicate configured trust anchors.
Well-known trust anchors can turn up in multiple config files.

Leaving the duplicates makes logging messy and costs some
CPU checking the same DS twice.
2026-02-16 22:20:05 +00:00
Simon Kelley 3f2bfca3df Log memory allocated by libidn when --log-malloc active. 2026-02-13 13:15:07 +00:00
Simon Kelley e4d0bb4579 Convert hash_init() to use realloc(). 2026-02-06 15:05:59 +00:00
Clayton O'Neill 84415a87be Fix PXE boot server (PXEBS) responses broken in 2.92
I think I've found a regression in dnsmasq 2.92 that breaks PXE boot server
(PXEBS) responses when running in proxy DHCP mode. Fair warning: I'm not
familiar with the dnsmasq codebase and used AI tooling to help trace through
the source and identify the issue, so please take the analysis below with
appropriate skepticism. PXE boot works fine on 2.91
but fails on 2.92 — the client gets the initial proxy DHCPOFFER, but the PXEBS
ACK on port 4011 never reaches it.

My setup is dnsmasq in proxy DHCP mode serving iPXE to Proxmox VMs via their
virtio-net ROM. Here's a stripped-down version of my config:

  port=0
  enable-tftp
  tftp-root=/tftpboot
  dhcp-range=172.19.74.0,proxy,255.255.255.0
  interface=eno1
  bind-interfaces
  dhcp-match=set:ipxe,175
  pxe-service=tag:ipxe,x86PC,"Network Boot",http://server:8081/boot.ipxe
  pxe-service=tag:!ipxe,x86PC,"Network Boot",undionly.kpxe
  log-dhcp

The issue seems to be in src/dhcp.c in the response routing logic after
dhcp_reply() returns. In 2.91, the destination selection was an if/else-if
chain:

  if (pxe_fd)
      { ... }
  else if (mess->giaddr.s_addr && !is_relay_reply)
      { ... }
  else if (mess->ciaddr.s_addr)
      { ... }
  else
      { ... broadcast to 255.255.255.255:68 ... }

In 2.92, the else between the pxe_fd block and the giaddr/relay check was
removed in commit 4fbe1ad ("Implement RFC-4388 DHCPv4 leasequery") to
accommodate the new is_relay_use_source logic:

  if (pxe_fd)
      { ... }
  if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
      { ... }
  else if (mess->ciaddr.s_addr)
      { ... }
  else
      { ... broadcast to 255.255.255.255:68 ... }

For PXEBS responses, dhcp_reply() in rfc2131.c (around line 924-925) does:

  mess->yiaddr = mess->ciaddr;
  mess->ciaddr.s_addr = 0;

So after dhcp_reply() returns for a PXEBS request, ciaddr is 0, giaddr is 0
(no relay), and is_relay_use_source is 0. In 2.91, the pxe_fd block runs and
the rest of the chain is skipped — dest stays as received from recvmsg, and the
response goes back to the client correctly. In 2.92, the pxe_fd block runs but
then falls through to the standalone if, which is false, so the else block runs
and sets dest to 255.255.255.255 port 68. The client is listening on port 4011
and ignores it.

Here are the relevant dnsmasq logs. With 2.91 (working), I see normal proxy
DHCP and PXE boot server exchanges:

  dnsmasq-dhcp: DHCPDISCOVER(eno1) bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPOFFER(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPREQUEST(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPACK(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 http://infra1.oneill.net:8081/boot.ipxe

With 2.92 (broken), the DHCPDISCOVER/OFFER/REQUEST/ACK cycle and the proxy
PXE response work, but the PXEBS response never reaches the client — it times
out after repeated attempts. The dnsmasq side shows it sending the response,
but the client keeps retrying:

  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe

I tested by restoring the else keyword and the fix appears to work — 2.92 with
the patch below PXE boots successfully. I believe this change preserves the
leasequery behavior since that path only applies when pxe_fd is false (normal
DHCP handling, not port 4011).
2026-02-05 15:38:27 +00:00
Simon Kelley 1537645e8f treat opt_malloc as a wrapper for logging purposes. 2026-02-05 14:53:10 +00:00
Simon Kelley ceab392c60 Improve memory allocation for /etc/hosts etc.
Clearing and reloading the DNS cache involved freeing and re-allocating
many small memory blocks. Doing this frequently with a significant
number of static DNS configuraions or DHCP leases could result
in excessive heap fragmentation and memory use.

This patch extends the pool memory allocation technique used
else where to this memory. On cache clear, the memory is not
freed, but added to a pool of useable but unused data structures
which get re-used to store the new data.
2026-02-05 13:26:08 +00:00
Simon Kelley 5f0f6aa9b1 Enhance --log-malloc
Log callers of wrapper functions of realloc()
These are expand_buf() and expand_workspace()
2026-02-03 22:33:32 +00:00
Simon Kelley 578ea08391 CHANGELOG entry for 2.92 DHCPv6 and relay regression.
Bug introduced in 3ceea9e755
Bug fixed in 714d36229a
2026-02-03 22:06:07 +00:00
Simon Kelley fbfcf87f8b Rewrite blockdata_retrieve() and expand_buf() to use realloc(). 2026-02-01 21:56:30 +00:00
Simon Kelley 19f3e06e7a Fix compiler warning. 2026-02-01 15:44:13 +00:00
Matthias Andree 40d1152059 read_writev: avoid reading past the last iovec elem
If iovcnt is exhausted and the first vector element's operation
is satisfied, the while loop will read past the end of the
iovec array.  This triggers the address sanitizer and leads to
undefined program state.  Avoid reading too far.
2026-02-01 15:36:16 +00:00
Matthias Andree 3c830c4f1c Avoid uninitialized-value warnings from the compiler 2026-02-01 14:02:08 +00:00
Matthias Andree e9f400dd37 base32_decode: avoid shifting into the sign bit
While this won't do harm on systems that do 2's completement,
it triggers the compilers' undefined-behavior sanitizer and
fixes sanitizer error such as the one below (where the 1694... will
vary) and is distracting while debugging.

dnssec.c:1427:12: runtime error: left shift of 1694604366 by 1 places
cannot be represented in type 'int'
2026-02-01 14:00:31 +00:00
Simon Kelley 870f14108f Tidy up memory allocation in read_event() 2026-01-31 21:31:10 +00:00
Simon Kelley 714d36229a Tidy up check for muticast DHCPv6 requests.
Anything sent by a client must be multicast. A relay can unicast
to a server.
2026-01-31 20:40:21 +00:00
Simon Kelley 60e90e2224 Remove DHCPv6 UseMulticast option code.
This almost certainly never worked and was never use, and
it's rendered obsolete in RFC 9915.
2026-01-31 16:50:21 +00:00
Simon Kelley ed2ba5b07a Optimise TCP send.
In the DNS TCP code, there are a couple of places where
we have a buffer containing a message which we need to
send via TCP. The DNS protocol is that this is sent as
<16-bit length in network order><message>

Making two write calls, one for the length and one
for the message causes the TCP stack to send two packets,
one for each. A single packet containing both is
preferable from a performance POV.

Implement a scatter-gather version of our read_write()
wrapper and use it where necessary to send TCP DNS messages.
2026-01-28 14:57:50 +00:00
Simon Kelley 729c16a8ac Rationalise DNS TCP buffer use.
This fixes the plethora of 64k buffers that got allocated when doing
DNSSEC over TCP.

By using the UDP buffer is pass the query into tcp_talk() and
allowing tcp_talk to allocate its output buffer one the size
of the reply is known, we only need to allocate as much memory as
is required. The final reply to the TCP query still needs the 64k
buffer because answer_request() and answer_auth() are not capable
of extending their output buffers.
2026-01-26 15:25:22 +00:00
Simon Kelley 80acb76f46 Don't start malloc() logging until the log system is configured. 2026-01-25 12:20:04 +00:00
Simon Kelley f1fa05d754 Don't log free(NULL) calls. 2026-01-25 12:16:48 +00:00
Simon Kelley b148ba4752 Add --log-malloc debugging option.
Log all blocks malloced and all blocks freed.
2026-01-24 22:16:23 +00:00
Simon Kelley 81f619612e Fix memory allocation in blockdata_retrieve()
This was functionally correct, but every call
malloced a new buffer and freed the previous one, rather
than only doing that when the buffer needed expansion.
2026-01-24 22:00:35 +00:00
Simon Kelley cb321709e9 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.
2026-01-18 18:57:08 +00:00
Simon Kelley 8eb36844a0 Log SERVFAIL from usptream servers.
If we're doing DNSSEC validation and fail because the upstream
reply to our query is SERVFAIL, log this as the reason in
validation result line.

This will make maintainers lives easier when they get reports
of "wrong" validation failure, which is sometimes an upstream problem.
2026-01-18 12:50:57 +00:00
Pavel Bozhko 8fa68a6e1e The only_failed argument has been added to the log-queries parameter.
This may be useful for embedded systems for example:
log-queries without only_failed places a heavy load on the
NAND flash memory. At the same time, logging requests
is necessary to troubleshoot network issues.
2026-01-16 15:33:59 +00:00
Simon Kelley 1269f074f8 Fix DNSSEC failure with spurious RRSIGs.
The presence of wrong RRSIG RRs in replies causes DNSSEC
validation to fail even when the RRs do not require validation
because the zone is unsigned.

This patch solves the problem and tidies up the logic.

Included is some fixes for hostname_issubdomain() which suffered
some confusions about argument order :) I've clarified that and
checked every to the function to make sure they are using the
correct argument order.

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.
2026-01-15 14:41:31 +00:00
Matthias Andree e1faf705c9 Support Inotify in FreeBSD.
FreeBSD 15.0 has added Linux-compatible inotify support, so
enable it by looking if the version matches. Since FreeBSD inotify has
seen a few bug fixes in 2025H2, so only enable it if
__FreeBSD_version >= 1500068.  The latter can be checked through
osreldate.h or sys/param.h; the latter defines more macros that clash
with dnsmasq's, such as MIN and MAX, so use the former.
2026-01-14 20:08:41 +00:00
Simon Kelley f603a4f920 Fix a corner-case in DNSSEC validation with wildcards.
If we have a wildcard record *.example.com and recieve 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, but was being done.

Thanks to Jan Breig for spotting this.
2026-01-12 21:55:41 +00:00
Simon Kelley c090f1d17b Terminate TCP child processes that arise from UDP truncated replies.
These can't be held forever by a client that opens a connection and
then sends nothing, but we can still be DoSsed by a server which
accepts a connection and never replies.

If the TCP process times out, it sends that information to the
parent so that the UDP query can be unblocked. As the TCP
timeout is 150s it's highly unlikely that any client will
still be waiting, but the point is to free resources.
2025-12-07 15:00:29 +00:00
Simon Kelley fa48bdb939 Tidy up code in in do_tcp_connection() which filters incoming connections.
A general rewrite and consolidation of the code that determines
if an incoming TCP connection is allowed, based on --address and --interface.

This was prompted by a bug found by Sławomir Zborowski where a TCP
connection for a valid IPv4 address on one interface which arrived
via a second interface which didn't have an IPv4 address would be
wrongly rejected.
2025-12-07 15:00:24 +00:00
Conrad Kostecki d8f66f4fda Update German translations. 2025-12-07 13:44:40 +00:00
Simon Kelley 629107bd6d Merge OOB memory read fix. CVE-2025-54318 2025-12-06 14:04:23 +00:00
Simon Kelley 052aa0fcf3 Fix bounds checking in check_ia()
A malformed DHCP request can cause out-bounds memory reads, and probably SEGV.
2025-07-15 15:18:12 +01:00
54 changed files with 1830 additions and 1299 deletions

No files matched your search

+47
View File
@@ -1,3 +1,50 @@
version 2.93
Fix a corner-case in DNSSEC validation with wildcards. If we have
a wildcard record *.example.com and recieve 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. As 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 -1
View File
@@ -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
+44 -3
View File
@@ -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;
+9 -3
View File
@@ -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;
}
+7 -3
View File
@@ -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
@@ -1513,7 +1515,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 +1529,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
+60 -75
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
+10 -8
View File
@@ -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
@@ -193,20 +193,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;
}
+41 -15
View File
@@ -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__) */
+214 -134
View File
@@ -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,64 @@ 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 char *store_name(unsigned int namelen, unsigned int index)
{
struct nameblock *block;
char *ret = NULL;
if (namelen > NAMEBLOCK_CHARS)
return NULL;
for (block = hostblocks; block; block = block->next)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
break;
if (!block && ((block = whine_malloc(sizeof(struct nameblock)))))
{
block->next = hostblocks;
block->index = index;
hostblocks = block;
}
if (block && NAMEBLOCK_CHARS - block->last >= namelen)
{
ret = &block->data[block->last];
block->last += namelen;
}
return ret;
}
static void free_names(unsigned int index)
{
struct nameblock *block;
for (block = hostblocks; block; block = block->next)
if (index == UID_NONE || block->index == index)
block->last = 0;
}
/* 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 +503,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 +919,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);
@@ -1000,6 +1064,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 +1078,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 +1330,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 +1409,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 +1425,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 +1450,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 +1468,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 +1486,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 +1494,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 +1575,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 +1590,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 +1610,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 +1648,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 +1657,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 +1749,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 +1800,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 +1820,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 +1867,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 +1900,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 +2229,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 +2246,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 +2304,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_occured(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 +2330,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
if (!option_bool(OPT_LOG))
return;
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occured(flags))
return;
/* F_NOERR is reused here to indicate logs arrising 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 +2354,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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+13 -14
View File
@@ -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);
@@ -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,23 @@ 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++] = ',';
}
}
}
#endif
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{
@@ -937,11 +936,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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
+32 -3
View File
@@ -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)
+151 -88
View File
@@ -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,13 +135,8 @@ 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);
@@ -199,7 +194,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)
@@ -437,14 +432,6 @@ int main (int argc, char **argv)
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 +851,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 +941,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 +1351,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 +1461,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 +1470,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 +1538,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 +1568,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)
{
@@ -1603,7 +1673,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 */
@@ -1941,20 +2010,21 @@ 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;
int flags, auth_dns = 0;
struct in_addr netmask;
int pipefd[2];
struct iovec tcpbuff;
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
netmask.s_addr = 0;
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
@@ -1981,58 +2051,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
@@ -2100,23 +2175,9 @@ 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))
{
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
close(daemon->netlinkfd);
@@ -2133,10 +2194,8 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
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)
@@ -2239,8 +2298,12 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
close(daemon->netlinkfd);
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;
}
}
+49 -25
View File
@@ -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.
@@ -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))
@@ -1205,6 +1213,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 +1271,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;
@@ -1456,13 +1468,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 +1490,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 +1500,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 +1538,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 +1583,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 +1915,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 +1929,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 +1939,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 +1966,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);
+219 -235
View File
@@ -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.
@@ -370,45 +370,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 +435,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 +450,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 +458,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 */
@@ -546,10 +543,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);
@@ -574,7 +575,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
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_start[0] == '*' && name_start[1] == '.'))
{
for (j = name_labels - labels; j != 0; j--)
{
@@ -584,11 +587,11 @@ 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 = '*';
*name_start = '*';
}
wire_len = to_wire(name_start);
@@ -657,7 +660,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 +690,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 +840,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 +954,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 +992,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 +1003,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 ||
@@ -1020,27 +1020,32 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
{
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 +1102,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 +1142,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 +1247,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 +1307,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 +1315,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 +1381,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 +1419,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 +1433,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 +1470,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 +1534,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 +1632,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 && name - closest_encloser == wild_offset)
break;
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
@@ -1687,7 +1676,7 @@ 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;
@@ -1710,7 +1699,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 +1847,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;
}
@@ -1965,7 +1954,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 +1983,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 +2175,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 +2210,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 +2224,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 +2250,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 +2263,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 +2287,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 +2332,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 +2356,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 +2364,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
View File
@@ -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
View File
@@ -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;
}
+3 -2
View File
@@ -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++;
}
}
+29 -24
View File
@@ -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;
}
+188 -160
View File
@@ -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))));
}
@@ -375,7 +375,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. */
@@ -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
@@ -589,7 +589,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
reply:
if (udpfd != -1)
{
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, (char *)(header + replylimit), first, last, ede)))
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, replylimit, first, last, ede)))
return;
if (fwd_flags & FREC_HAS_PHEADER)
@@ -597,9 +597,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)
@@ -695,7 +695,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 +724,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 +879,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 +954,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 +1027,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 +1075,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 +1084,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 +1205,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
@@ -1399,6 +1412,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 +1442,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 +1945,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 +1979,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 +2031,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 +2057,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 +2084,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)
@@ -2099,10 +2114,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 +2150,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 +2169,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 +2205,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 +2232,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 +2247,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 +2261,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 +2273,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 +2283,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 +2301,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 +2322,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 +2353,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 +2363,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 +2376,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;
@@ -2378,9 +2389,13 @@ unsigned char *tcp_request(int confd, time_t now,
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;
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 +2416,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 +2439,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 +2457,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 +2504,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 +2518,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 +2570,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 +2586,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 +2620,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 +2630,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 +2653,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 +2680,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 +2704,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;
@@ -2703,8 +2730,8 @@ unsigned char *tcp_request(int confd, time_t now,
/* 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 +2747,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,7 +2762,13 @@ 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
@@ -2753,18 +2784,15 @@ unsigned char *tcp_request(int confd, time_t now,
daemon->log_source_addr = NULL;
}
}
/* If we ran once to get fresh data, confd is already closed. */
/* If we ran once to get fresh data, confd is already closed. */
if (!do_stale)
{
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 +3293,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 */
+13 -17
View File
@@ -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);
}
}
@@ -740,9 +734,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
View File
@@ -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
View File
@@ -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
+12 -4
View File
@@ -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);
+2 -2
View File
@@ -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
@@ -113,7 +113,7 @@ int log_start(struct passwd *ent_pw, int errfd)
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);
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+6 -6
View File
@@ -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
@@ -1699,19 +1699,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
View File
@@ -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
+80 -70
View File
@@ -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);
}
@@ -3463,6 +3469,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 +4580,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 +4637,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 +4960,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"));
@@ -5460,9 +5471,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 +5492,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 +5743,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 +5951,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 +6199,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 +6216,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 +6229,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 +6249,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 +6261,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");
@@ -6272,7 +6281,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 +6289,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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -7
View File
@@ -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
@@ -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);
}
+32 -16
View File
@@ -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);
@@ -607,7 +615,9 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
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;
@@ -943,7 +953,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;
@@ -1427,6 +1438,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 +1469,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 +1533,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;
+16 -14
View File
@@ -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, &ltmp->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;
}
@@ -1918,16 +1918,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 +1938,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 +1948,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 +1965,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 +2644,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 +3250,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)
+160 -146
View File
@@ -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 encapulation
is zero, ignore it, and, by implication, use the link
address of any enclosing encapsulation or, failing that
of the arrival interface on the 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 &&
@@ -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
View File
@@ -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] = '.';
}
+1 -1
View File
@@ -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 -10
View File
@@ -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
@@ -434,17 +434,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 +475,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 +495,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 +545,13 @@ 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 = strstr(namebuff, "/..");
if (suspect && (suspect[3] == '/' || suspect[3] == 0))
goto perm;
}
if ((fd = open(namebuff, O_RDONLY)) == -1)
{
@@ -622,7 +627,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;
+1 -1
View File
@@ -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 -95
View File
@@ -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,144 @@ int kernel_version(void)
return version * 256 + (split ? atoi(split) : 0);
}
#endif
#define hash_ptr(x) (((unsigned int)(((char *)(x)) - ((char *)NULL))) & 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. */
int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
{
char c;
size_t cnt = 0;
char *buff = *buffp;
while (1) {
c = getc(f);
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);
return 0;
}
buff[cnt++] = c;
}
}