mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-12 16:50:54 +00:00
Compare commits
29
Commits
v2.90test2
...
v2.90deb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3705ec5592 | ||
|
|
b6769234bc | ||
|
|
214a046f47 | ||
|
|
b38da6b191 | ||
|
|
9621c16a78 | ||
|
|
3ae7f1ab0d | ||
|
|
39de57499e | ||
|
|
3c91bca943 | ||
|
|
76bceb06c4 | ||
|
|
6f23a0a75e | ||
|
|
06945c4b77 | ||
|
|
c5aa221e44 | ||
|
|
bfefd6e38c | ||
|
|
59d30390c9 | ||
|
|
51471cafa5 | ||
|
|
be73efc020 | ||
|
|
40595f80d9 | ||
|
|
8c8e5385fd | ||
|
|
3de7289bd6 | ||
|
|
febeea9d01 | ||
|
|
762a3f2430 | ||
|
|
6d35601da4 | ||
|
|
a827127c77 | ||
|
|
d4a6f3a93e | ||
|
|
86c15032ba | ||
|
|
12ddb2a4b9 | ||
|
|
db07664f2a | ||
|
|
1205fc3541 | ||
|
|
3a8ebcac77 |
No files matched your search
@@ -26,6 +26,44 @@ version 2.90
|
|||||||
when the usptream server is a DOH/DOT proxy. Thanks to Justin He
|
when the usptream server is a DOH/DOT proxy. Thanks to Justin He
|
||||||
for the bug report.
|
for the bug report.
|
||||||
|
|
||||||
|
Add configurable caching for arbitrary RR-types.
|
||||||
|
|
||||||
|
Add --filter-rr option, to filter arbitrary RR-types.
|
||||||
|
--filter-rr=ANY has a special meaning: it filters the
|
||||||
|
answers to queries for the ANY RR-type.
|
||||||
|
|
||||||
|
Add limits on the resources used to do DNSSEC validation.
|
||||||
|
DNSSEC introduces a potential CPU DoS, because a crafted domain
|
||||||
|
can force a validator to a large number of cryptographic
|
||||||
|
operations whilst attempting to do validation. When using TCP
|
||||||
|
transport a DNSKEY RRset contain thousands of members and any
|
||||||
|
RRset can have thousands of signatures. The potential number
|
||||||
|
of signature validations to follow the RFC for validation
|
||||||
|
for one RRset is the cross product of the keys and signatures,
|
||||||
|
so millions. In practice, the actual numbers are much lower,
|
||||||
|
so attacks can be mitigated by limiting the amount of
|
||||||
|
cryptographic "work" to a much lower amount. The actual
|
||||||
|
limits are number a signature validation fails per RRset(20),
|
||||||
|
number of signature validations and hash computations
|
||||||
|
per query(200), number of sub-queries to fetch DS and DNSKEY
|
||||||
|
RRsets per query(40), and the number of iterations in a
|
||||||
|
NSEC3 record(150). These values are sensible, but there is, as yet,
|
||||||
|
no standardisation on the values for a "conforming" domain, so a
|
||||||
|
new option --dnssec-limit is provided should they need to be altered.
|
||||||
|
The algorithm to validate DS records has also been altered to reduce
|
||||||
|
the maximum work from cross product of the number of DS records and
|
||||||
|
number of DNSKEYs to the cross product of the number of DS records
|
||||||
|
and supported DS digest types. As the number of DS digest types
|
||||||
|
is in single figures, this reduces the exposure.
|
||||||
|
|
||||||
|
Credit is due to Elias Heftrig, Haya Schulmann, Niklas Vogel,
|
||||||
|
and Michael Waidner from the German National Research Center for
|
||||||
|
Applied Cybersecurity ATHENE for finding this vulnerability.
|
||||||
|
|
||||||
|
CVE 2023-50387 and CVE 2023-50868 apply.
|
||||||
|
Note that the is a security vulnerablity only when DNSSEC validation
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
|
||||||
version 2.89
|
version 2.89
|
||||||
Fix bug introduced in 2.88 (commit fe91134b) which can result
|
Fix bug introduced in 2.88 (commit fe91134b) which can result
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ LDFLAGS =
|
|||||||
COPTS =
|
COPTS =
|
||||||
RPM_OPT_FLAGS =
|
RPM_OPT_FLAGS =
|
||||||
LIBS =
|
LIBS =
|
||||||
|
LUA = lua
|
||||||
|
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
@@ -60,8 +61,8 @@ idn2_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFI
|
|||||||
idn2_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --libs libidn2`
|
idn2_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --libs libidn2`
|
||||||
ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack`
|
ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack`
|
||||||
ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
|
ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
|
||||||
lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.4`
|
lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags $(LUA)`
|
||||||
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.4`
|
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs $(LUA)`
|
||||||
nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \
|
nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \
|
||||||
HAVE_CRYPTOHASH $(PKG_CONFIG) --cflags nettle \
|
HAVE_CRYPTOHASH $(PKG_CONFIG) --cflags nettle \
|
||||||
HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle`
|
HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle`
|
||||||
|
|||||||
Vendored
+15
-3
@@ -1,7 +1,19 @@
|
|||||||
dnsmasq (2.90-1) experimental; urgency=medium
|
dnsmasq (2.90-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sven Geuer ]
|
||||||
|
* Relax limits imposed by d/t/functions.d/ip-addr.patterns to allow for
|
||||||
|
successful tests on ci.debian.net.
|
||||||
|
|
||||||
|
-- Simon Kelley <simon@thekelleys.org.uk> Wed, 14 Feb 2024 11:33:14 +0000
|
||||||
|
|
||||||
|
dnsmasq (2.90-1) unstable; urgency=medium
|
||||||
|
|
||||||
[ Simon Kelley ]
|
[ Simon Kelley ]
|
||||||
* New upstream. (closes: #1033165)
|
* New upstream. (closes: #1033165)
|
||||||
|
* Move hard-coding of Lua version from the upstream Makefile
|
||||||
|
to d/rules.
|
||||||
|
* Security fixes for Keytrap - DNSSEC validation CPU exhaustion.
|
||||||
|
CVE-2023-50387 and CVE-2023-50868
|
||||||
[ Sven Geuer ]
|
[ Sven Geuer ]
|
||||||
* Introduce autokpgtests per d/tests/* (closes: #1034135).
|
* Introduce autokpgtests per d/tests/* (closes: #1034135).
|
||||||
* Switch to dpkg-source 3.0 (quilt) format (closes: #1007041).
|
* Switch to dpkg-source 3.0 (quilt) format (closes: #1007041).
|
||||||
@@ -11,7 +23,7 @@ dnsmasq (2.90-1) experimental; urgency=medium
|
|||||||
* Rename d/systemd.service to d/dnsmasq.service.
|
* Rename d/systemd.service to d/dnsmasq.service.
|
||||||
* Rename d/systemd@.service to d/dnsmasq@.service.
|
* Rename d/systemd@.service to d/dnsmasq@.service.
|
||||||
* Refactor d/rules to use the DH sequencer and fix major lintian issues
|
* Refactor d/rules to use the DH sequencer and fix major lintian issues
|
||||||
(closes: #844989, #1040923).
|
(closes: #844989, #1040923, #1063551).
|
||||||
Modified files:
|
Modified files:
|
||||||
- d/rules
|
- d/rules
|
||||||
Complete rewrite making use of debhelper and its tools, fixes lintian
|
Complete rewrite making use of debhelper and its tools, fixes lintian
|
||||||
@@ -78,7 +90,7 @@ dnsmasq (2.90-1) experimental; urgency=medium
|
|||||||
* Introduce d/u/metadata.
|
* Introduce d/u/metadata.
|
||||||
* Fix lintian issue duplicate-short-description.
|
* Fix lintian issue duplicate-short-description.
|
||||||
* Fix lintian issue capitalization-error-in-description.
|
* Fix lintian issue capitalization-error-in-description.
|
||||||
* Bump LUA version to 5.4 (closes: #1050750).
|
* Bump Lua version to 5.4 (closes: #1050750).
|
||||||
Modified files:
|
Modified files:
|
||||||
- d/control
|
- d/control
|
||||||
- d/t/functions.d/log.patterns
|
- d/t/functions.d/log.patterns
|
||||||
|
|||||||
Vendored
+1
-1
@@ -87,7 +87,7 @@ override_dh_auto_install:
|
|||||||
BUILDDIR=debian/auto-build/dnsmasq-base-lua \
|
BUILDDIR=debian/auto-build/dnsmasq-base-lua \
|
||||||
DESTDIR=$(CURDIR)/debian/dnsmasq-base-lua \
|
DESTDIR=$(CURDIR)/debian/dnsmasq-base-lua \
|
||||||
PREFIX=$(PREFIX) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
PREFIX=$(PREFIX) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||||
COPTS="$(COPTS) -DHAVE_LUASCRIPT"
|
LUA=lua5.4 COPTS="$(COPTS) -DHAVE_LUASCRIPT"
|
||||||
dh_auto_build -p dnsmasq-utils -D contrib/lease-tools
|
dh_auto_build -p dnsmasq-utils -D contrib/lease-tools
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
?: veth1@if?: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
*: veth1@if*: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||||
link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||||
inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1
|
inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1
|
||||||
valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec
|
valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec
|
||||||
|
|||||||
+14
-1
@@ -386,7 +386,11 @@ Remove A records from answers. No IPv4 addresses will be returned.
|
|||||||
Remove AAAA records from answers. No IPv6 addresses will be returned.
|
Remove AAAA records from answers. No IPv6 addresses will be returned.
|
||||||
.TP
|
.TP
|
||||||
.B --filter-rr=<rrtype>[,<rrtype>...]
|
.B --filter-rr=<rrtype>[,<rrtype>...]
|
||||||
Remove records of the specified type(s) from answers.
|
Remove records of the specified type(s) from answers. The otherwise-nonsensical --filter-rr=ANY has
|
||||||
|
a special meaning: it filters replies to queries for type ANY. Everything other than A, AAAA, MX and CNAME
|
||||||
|
records are removed. Since ANY queries with forged source addresses can be used in DNS amplification attacks
|
||||||
|
(replies to ANY queries can be large) this defangs such attacks, whilst still supporting the
|
||||||
|
one remaining possible use of ANY queries. See RFC 8482 para 4.3 for details.
|
||||||
.TP
|
.TP
|
||||||
.B --cache-rr=<rrtype>[,<rrtype>...]
|
.B --cache-rr=<rrtype>[,<rrtype>...]
|
||||||
By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types.
|
By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types.
|
||||||
@@ -927,6 +931,15 @@ Authenticated Data bit correctly in all cases is not technically possible. If th
|
|||||||
when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation
|
when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation
|
||||||
within dnsmasq is a better option. See --dnssec for details.
|
within dnsmasq is a better option. See --dnssec for details.
|
||||||
.TP
|
.TP
|
||||||
|
.B --dnssec-limits=<limit>[,<limit>.......]
|
||||||
|
Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains
|
||||||
|
can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code
|
||||||
|
applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the
|
||||||
|
domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and
|
||||||
|
hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150).
|
||||||
|
The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so
|
||||||
|
\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values.
|
||||||
|
.TP
|
||||||
.B --dnssec-debug
|
.B --dnssec-debug
|
||||||
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
|
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
|
||||||
and don't convert replies which do not validate to responses with
|
and don't convert replies which do not validate to responses with
|
||||||
|
|||||||
+50
-14
@@ -802,9 +802,6 @@ void cache_end_insert(void)
|
|||||||
read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
|
read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
|
||||||
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
|
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
|
||||||
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
|
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
|
||||||
|
|
||||||
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
|
|
||||||
{
|
|
||||||
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
|
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
|
||||||
|
|
||||||
if (flags & F_RR)
|
if (flags & F_RR)
|
||||||
@@ -829,7 +826,6 @@ void cache_end_insert(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
new_chain = tmp;
|
new_chain = tmp;
|
||||||
}
|
}
|
||||||
@@ -838,7 +834,18 @@ void cache_end_insert(void)
|
|||||||
if (daemon->pipe_to_parent != -1)
|
if (daemon->pipe_to_parent != -1)
|
||||||
{
|
{
|
||||||
ssize_t m = -1;
|
ssize_t m = -1;
|
||||||
|
|
||||||
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
|
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
|
||||||
|
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
/* Sneak out possibly updated crypto HWM values. */
|
||||||
|
m = daemon->metrics[METRIC_CRYPTO_HWM];
|
||||||
|
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
|
||||||
|
m = daemon->metrics[METRIC_SIG_FAIL_HWM];
|
||||||
|
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
|
||||||
|
m = daemon->metrics[METRIC_WORK_HWM];
|
||||||
|
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
new_chain = NULL;
|
new_chain = NULL;
|
||||||
@@ -857,7 +864,7 @@ int cache_recv_insert(time_t now, int fd)
|
|||||||
|
|
||||||
cache_start_insert();
|
cache_start_insert();
|
||||||
|
|
||||||
while(1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
|
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
|
||||||
@@ -865,13 +872,29 @@ int cache_recv_insert(time_t now, int fd)
|
|||||||
|
|
||||||
if (m == -1)
|
if (m == -1)
|
||||||
{
|
{
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
/* Sneak in possibly updated crypto HWM. */
|
||||||
|
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
|
||||||
|
return 0;
|
||||||
|
if (m > daemon->metrics[METRIC_CRYPTO_HWM])
|
||||||
|
daemon->metrics[METRIC_CRYPTO_HWM] = m;
|
||||||
|
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
|
||||||
|
return 0;
|
||||||
|
if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
|
||||||
|
daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
|
||||||
|
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
|
||||||
|
return 0;
|
||||||
|
if (m > daemon->metrics[METRIC_WORK_HWM])
|
||||||
|
daemon->metrics[METRIC_WORK_HWM] = m;
|
||||||
|
#endif
|
||||||
cache_end_insert();
|
cache_end_insert();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
|
if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
|
||||||
!read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
|
!read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
|
||||||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), 1))
|
!read_write(fd, (unsigned char *)&flags, sizeof(flags), 1) ||
|
||||||
|
!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
daemon->namebuff[m] = 0;
|
daemon->namebuff[m] = 0;
|
||||||
@@ -902,11 +925,6 @@ int cache_recv_insert(time_t now, int fd)
|
|||||||
{
|
{
|
||||||
unsigned short class = C_IN;
|
unsigned short class = C_IN;
|
||||||
|
|
||||||
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
|
|
||||||
{
|
|
||||||
if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
|
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
|
||||||
&& !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
|
&& !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -924,8 +942,6 @@ int cache_recv_insert(time_t now, int fd)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
|
crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1809,8 +1825,18 @@ static void dump_cache_entry(struct crec *cache, time_t now)
|
|||||||
p = buff;
|
p = buff;
|
||||||
|
|
||||||
*a = 0;
|
*a = 0;
|
||||||
if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
|
|
||||||
|
if (cache->flags & F_REVERSE)
|
||||||
|
{
|
||||||
|
if ((cache->flags & F_NEG))
|
||||||
|
n = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (strlen(n) == 0)
|
||||||
n = "<Root>";
|
n = "<Root>";
|
||||||
|
}
|
||||||
|
|
||||||
p += sprintf(p, "%-30.30s ", sanitise(n));
|
p += sprintf(p, "%-30.30s ", sanitise(n));
|
||||||
if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
|
if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
|
||||||
a = sanitise(cache_get_cname_target(cache));
|
a = sanitise(cache_get_cname_target(cache));
|
||||||
@@ -1893,6 +1919,11 @@ void dump_cache(time_t now)
|
|||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
|
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]);
|
||||||
|
my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]);
|
||||||
|
my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]);
|
||||||
|
#endif
|
||||||
|
|
||||||
blockdata_report();
|
blockdata_report();
|
||||||
my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
|
my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
|
||||||
@@ -2052,6 +2083,11 @@ static char *edestr(int ede)
|
|||||||
case EDE_NO_AUTH: return "no reachable authority";
|
case EDE_NO_AUTH: return "no reachable authority";
|
||||||
case EDE_NETERR: return "network error";
|
case EDE_NETERR: return "network error";
|
||||||
case EDE_INVALID_DATA: return "invalid data";
|
case EDE_INVALID_DATA: return "invalid data";
|
||||||
|
case EDE_SIG_E_B_V: return "signature expired before valid";
|
||||||
|
case EDE_TOO_EARLY: return "too early";
|
||||||
|
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";
|
||||||
default: return "unknown";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -22,7 +22,10 @@
|
|||||||
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
|
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
|
||||||
#define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */
|
#define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */
|
||||||
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
|
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
|
||||||
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
|
#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. */
|
||||||
|
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
|
||||||
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
||||||
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
|
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
|
||||||
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
||||||
|
|||||||
+5
-2
@@ -112,8 +112,11 @@
|
|||||||
#define EDE_NO_AUTH 22 /* No Reachable Authority */
|
#define EDE_NO_AUTH 22 /* No Reachable Authority */
|
||||||
#define EDE_NETERR 23 /* Network error */
|
#define EDE_NETERR 23 /* Network error */
|
||||||
#define EDE_INVALID_DATA 24 /* Invalid Data */
|
#define EDE_INVALID_DATA 24 /* Invalid Data */
|
||||||
|
#define EDE_SIG_E_B_V 25 /* Signature Expired before Valid */
|
||||||
|
#define EDE_TOO_EARLY 26 /* To Early */
|
||||||
|
#define EDE_UNS_NS3_ITER 27 /* Unsupported NSEC3 Iterations Value */
|
||||||
|
#define EDE_UNABLE_POLICY 28 /* Unable to conform to policy */
|
||||||
|
#define EDE_SYNTHESIZED 29 /* Synthesized */
|
||||||
|
|
||||||
|
|
||||||
struct dns_header {
|
struct dns_header {
|
||||||
|
|||||||
+2
-10
@@ -131,20 +131,12 @@ int main (int argc, char **argv)
|
|||||||
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
||||||
presentation format would be twice as long as the spec. */
|
presentation format would be twice as long as the spec. */
|
||||||
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
|
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
|
||||||
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
|
|
||||||
/* one char flag per possible RR in answer section (may get extended). */
|
/* one char flag per possible RR in answer section (may get extended). */
|
||||||
daemon->rr_status_sz = 64;
|
daemon->rr_status_sz = 64;
|
||||||
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
|
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
|
||||||
/* CONNTRACK UBUS code uses this buffer, so if not allocated above,
|
|
||||||
we need to allocate it here. */
|
|
||||||
if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
|
|
||||||
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
if (!daemon->lease_file)
|
if (!daemon->lease_file)
|
||||||
{
|
{
|
||||||
@@ -429,8 +421,8 @@ int main (int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_INOTIFY
|
#ifdef HAVE_INOTIFY
|
||||||
if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6)
|
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
|
||||||
&& (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs))
|
daemon->dynamic_dirs)
|
||||||
inotify_dnsmasq_init();
|
inotify_dnsmasq_init();
|
||||||
else
|
else
|
||||||
daemon->inotifyfd = -1;
|
daemon->inotifyfd = -1;
|
||||||
|
|||||||
+19
-9
@@ -341,7 +341,7 @@ union all_addr {
|
|||||||
in the cache flags. */
|
in the cache flags. */
|
||||||
struct datablock {
|
struct datablock {
|
||||||
unsigned short rrtype;
|
unsigned short rrtype;
|
||||||
unsigned char datalen;
|
unsigned char datalen; /* also length of SOA in negative records. */
|
||||||
char data[];
|
char data[];
|
||||||
} rrdata;
|
} rrdata;
|
||||||
};
|
};
|
||||||
@@ -757,6 +757,9 @@ struct dyndir {
|
|||||||
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
|
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
|
||||||
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
|
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
|
||||||
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
|
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
|
||||||
|
#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 STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
|
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
|
||||||
|
|
||||||
@@ -794,7 +797,7 @@ struct frec {
|
|||||||
struct blockdata *stash; /* Saved reply, whilst we validate */
|
struct blockdata *stash; /* Saved reply, whilst we validate */
|
||||||
size_t stash_len;
|
size_t stash_len;
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
int class, work_counter;
|
int class, work_counter, validate_counter;
|
||||||
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
||||||
struct frec *next_dependent; /* list of above. */
|
struct frec *next_dependent; /* list of above. */
|
||||||
struct frec *blocking_query; /* Query which is blocking us. */
|
struct frec *blocking_query; /* Query which is blocking us. */
|
||||||
@@ -831,6 +834,12 @@ struct frec {
|
|||||||
#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
|
#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
|
||||||
#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
|
#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
|
||||||
|
|
||||||
|
#define LIMIT_SIG_FAIL 0
|
||||||
|
#define LIMIT_CRYPTO 1
|
||||||
|
#define LIMIT_WORK 2
|
||||||
|
#define LIMIT_NSEC3_ITERS 3
|
||||||
|
#define LIMIT_MAX 4
|
||||||
|
|
||||||
struct dhcp_lease {
|
struct dhcp_lease {
|
||||||
int clid_len; /* length of client identifier */
|
int clid_len; /* length of client identifier */
|
||||||
unsigned char *clid; /* clientid */
|
unsigned char *clid; /* clientid */
|
||||||
@@ -1233,16 +1242,14 @@ extern struct daemon {
|
|||||||
char *packet; /* packet buffer */
|
char *packet; /* packet buffer */
|
||||||
int packet_buff_sz; /* size of above */
|
int packet_buff_sz; /* size of above */
|
||||||
char *namebuff; /* MAXDNAME size buffer */
|
char *namebuff; /* MAXDNAME size buffer */
|
||||||
#if (defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)) || defined(HAVE_DNSSEC)
|
|
||||||
/* CONNTRACK UBUS code uses this buffer, as well as DNSSEC code. */
|
|
||||||
char *workspacename;
|
char *workspacename;
|
||||||
#endif
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
char *keyname; /* MAXDNAME size buffer */
|
char *keyname; /* MAXDNAME size buffer */
|
||||||
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
|
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
|
||||||
int rr_status_sz;
|
int rr_status_sz;
|
||||||
int dnssec_no_time_check;
|
int dnssec_no_time_check;
|
||||||
int back_to_the_future;
|
int back_to_the_future;
|
||||||
|
int limit[LIMIT_MAX];
|
||||||
#endif
|
#endif
|
||||||
struct frec *frec_list;
|
struct frec *frec_list;
|
||||||
struct frec_src *free_frec_src;
|
struct frec_src *free_frec_src;
|
||||||
@@ -1376,6 +1383,7 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr);
|
|||||||
int is_rev_synth(int flag, union all_addr *addr, char *name);
|
int is_rev_synth(int flag, union all_addr *addr, char *name);
|
||||||
|
|
||||||
/* rfc1035.c */
|
/* rfc1035.c */
|
||||||
|
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff);
|
||||||
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||||
char *name, int isExtract, int extrabytes);
|
char *name, int isExtract, int extrabytes);
|
||||||
unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
|
unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
|
||||||
@@ -1386,7 +1394,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen,
|
|||||||
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
|
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
|
||||||
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
|
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
|
||||||
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
|
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
|
||||||
int check_rebind, int no_cache_dnssec, int secure, int *doctored);
|
int check_rebind, int no_cache_dnssec, int secure);
|
||||||
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
|
||||||
void report_addresses(struct dns_header *header, size_t len, u32 mark);
|
void report_addresses(struct dns_header *header, size_t len, u32 mark);
|
||||||
#endif
|
#endif
|
||||||
@@ -1417,10 +1425,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
|
|||||||
/* dnssec.c */
|
/* dnssec.c */
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
|
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
|
||||||
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
|
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
|
||||||
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
|
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 dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
|
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 check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
|
||||||
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
|
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
|
||||||
size_t filter_rrsigs(struct dns_header *header, size_t plen);
|
size_t filter_rrsigs(struct dns_header *header, size_t plen);
|
||||||
int setup_timestamp(void);
|
int setup_timestamp(void);
|
||||||
|
|||||||
+290
-176
@@ -424,12 +424,24 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int dec_counter(int *counter, char *message)
|
||||||
|
{
|
||||||
|
if ((*counter)-- == 0)
|
||||||
|
{
|
||||||
|
my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Validate a single RRset (class, type, name) in the supplied DNS reply
|
/* Validate a single RRset (class, type, name) in the supplied DNS reply
|
||||||
Return code:
|
Return code:
|
||||||
STAT_SECURE if it validates.
|
STAT_SECURE if it validates.
|
||||||
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
|
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.)
|
(In this case *wildcard_out points to the "body" of the wildcard within name.)
|
||||||
STAT_BOGUS signature is wrong, bad packet.
|
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)
|
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
|
||||||
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
||||||
|
|
||||||
@@ -444,10 +456,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
|||||||
*/
|
*/
|
||||||
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
|
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, char **wildcard_out, struct blockdata *key, int keylen,
|
||||||
int algo_in, int keytag_in, unsigned long *ttl_out)
|
int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
|
||||||
{
|
{
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
int rdlen, j, name_labels, algo, labels, key_tag;
|
int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt;
|
||||||
struct crec *crecp = NULL;
|
struct crec *crecp = NULL;
|
||||||
short *rr_desc = rrfilter_desc(type);
|
short *rr_desc = rrfilter_desc(type);
|
||||||
u32 sig_expiration, sig_inception;
|
u32 sig_expiration, sig_inception;
|
||||||
@@ -467,7 +479,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
|
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
|
||||||
|
|
||||||
/* Now try all the sigs to try and find one which validates */
|
/* Now try all the sigs to try and find one which validates */
|
||||||
for (j = 0; j <sigidx; j++)
|
for (sig_fail_cnt = daemon->limit[LIMIT_SIG_FAIL], j = 0; j <sigidx; j++)
|
||||||
{
|
{
|
||||||
unsigned char *psav, *sig, *digest;
|
unsigned char *psav, *sig, *digest;
|
||||||
int i, wire_len, sig_len;
|
int i, wire_len, sig_len;
|
||||||
@@ -654,19 +666,37 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
|
|
||||||
if (key)
|
if (key)
|
||||||
{
|
{
|
||||||
if (algo_in == algo && keytag_in == key_tag &&
|
if (algo_in == algo && keytag_in == key_tag)
|
||||||
verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
|
{
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return STAT_ABANDONED;
|
||||||
|
|
||||||
|
if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||||
return STAT_SECURE;
|
return STAT_SECURE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* iterate through all possible keys 4035 5.3.1 */
|
/* iterate through all possible keys 4035 5.3.1 */
|
||||||
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
|
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
|
||||||
if (crecp->addr.key.algo == algo &&
|
if (crecp->addr.key.algo == algo &&
|
||||||
crecp->addr.key.keytag == key_tag &&
|
crecp->addr.key.keytag == key_tag &&
|
||||||
crecp->uid == (unsigned int)class &&
|
crecp->uid == (unsigned int)class)
|
||||||
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
|
{
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
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 (labels < name_labels) ? STAT_SECURE_WILDCARD : 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
|
||||||
|
a legitimate domain, set a limit on how many can fail. */
|
||||||
|
if ((daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM])
|
||||||
|
daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1);
|
||||||
|
if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails")))
|
||||||
|
return STAT_ABANDONED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,30 +711,36 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
STAT_OK Done, key(s) in cache.
|
STAT_OK Done, key(s) in cache.
|
||||||
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
|
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
|
||||||
or self-sign for DNSKEY RRset is not valid, bad packet.
|
or self-sign for DNSKEY RRset is not valid, bad packet.
|
||||||
|
STAT_ABANDONED resource exhaustion.
|
||||||
STAT_NEED_DS DS records to validate a key not found, name in keyname
|
STAT_NEED_DS DS records to validate a key not found, name in keyname
|
||||||
STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname
|
|
||||||
*/
|
*/
|
||||||
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
|
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
|
||||||
|
char *keyname, int class, int *validate_counter)
|
||||||
{
|
{
|
||||||
unsigned char *psave, *p = (unsigned char *)(header+1);
|
unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr;
|
||||||
struct crec *crecp, *recp1;
|
struct crec *crecp, *recp1;
|
||||||
int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
|
int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt;
|
||||||
unsigned long ttl, sig_ttl;
|
unsigned long ttl, sig_ttl;
|
||||||
struct blockdata *key;
|
|
||||||
union all_addr a;
|
union all_addr a;
|
||||||
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
|
int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE;
|
||||||
|
char valid_digest[255];
|
||||||
|
static unsigned char **cached_digest;
|
||||||
|
static size_t cached_digest_size = 0;
|
||||||
|
|
||||||
if (ntohs(header->qdcount) != 1 ||
|
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
|
||||||
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
|
|
||||||
!extract_name(header, plen, &p, name, 1, 4))
|
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
|
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
|
||||||
|
|
||||||
GETSHORT(qtype, p);
|
GETSHORT(qtype, p);
|
||||||
GETSHORT(qclass, p);
|
GETSHORT(qclass, p);
|
||||||
|
|
||||||
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
|
if (qtype != T_DNSKEY || qclass != class ||
|
||||||
|
!explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) ||
|
||||||
|
rrcnt == 0)
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
|
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
|
||||||
|
|
||||||
|
if (sigcnt == 0)
|
||||||
|
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
|
||||||
|
|
||||||
/* See if we have cached a DS record which validates this key */
|
/* See if we have cached a DS record which validates this key */
|
||||||
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
|
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
|
||||||
{
|
{
|
||||||
@@ -713,7 +749,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* NOTE, we need to find ONE DNSKEY which matches the DS */
|
/* NOTE, we need to find ONE DNSKEY which matches the DS */
|
||||||
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
|
for (j = ntohs(header->ancount); j != 0; j--)
|
||||||
{
|
{
|
||||||
/* Ensure we have type, class TTL and length */
|
/* Ensure we have type, class TTL and length */
|
||||||
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
||||||
@@ -724,7 +760,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
GETLONG(ttl, p);
|
GETLONG(ttl, p);
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p);
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* bad packet */
|
||||||
|
|
||||||
if (qclass != class || qtype != T_DNSKEY || rc == 2)
|
if (qclass != class || qtype != T_DNSKEY || rc == 2)
|
||||||
@@ -733,97 +769,133 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rdlen < 5)
|
||||||
|
return STAT_BOGUS; /* min 1 byte key! */
|
||||||
|
|
||||||
psave = p;
|
psave = p;
|
||||||
|
|
||||||
GETSHORT(flags, p);
|
GETSHORT(flags, p);
|
||||||
if (*p++ != 3)
|
if (*p++ != 3)
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
|
{
|
||||||
|
p = psave + rdlen;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
keyaddr = p;
|
||||||
key = NULL;
|
keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4);
|
||||||
|
|
||||||
|
p = psave + rdlen;
|
||||||
|
|
||||||
/* key must have zone key flag set */
|
/* key must have zone key flag set */
|
||||||
if (flags & 0x100)
|
if (!(flags & 0x100))
|
||||||
{
|
|
||||||
key = blockdata_alloc((char*)p, rdlen - 4);
|
|
||||||
failflags &= ~DNSSEC_FAIL_NOZONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = psave;
|
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
|
||||||
{
|
|
||||||
if (key)
|
|
||||||
blockdata_free(key);
|
|
||||||
return STAT_BOGUS; /* bad packet */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No zone key flag or malloc failure */
|
|
||||||
if (!key)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
failflags &= ~DNSSEC_FAIL_NOZONE;
|
||||||
|
|
||||||
|
/* clear digest cache. */
|
||||||
|
memset(valid_digest, 0, sizeof(valid_digest));
|
||||||
|
|
||||||
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
|
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
|
||||||
{
|
{
|
||||||
void *ctx;
|
void *ctx;
|
||||||
unsigned char *digest, *ds_digest;
|
unsigned char *digest, *ds_digest;
|
||||||
const struct nettle_hash *hash;
|
const struct nettle_hash *hash;
|
||||||
int sigcnt, rrcnt;
|
|
||||||
int wire_len;
|
int wire_len;
|
||||||
|
|
||||||
if (recp1->addr.ds.algo == algo &&
|
if ((recp1->flags & F_NEG) ||
|
||||||
recp1->addr.ds.keytag == keytag &&
|
recp1->addr.ds.algo != algo ||
|
||||||
recp1->uid == (unsigned int)class)
|
recp1->addr.ds.keytag != keytag ||
|
||||||
{
|
recp1->uid != (unsigned int)class)
|
||||||
failflags &= ~DNSSEC_FAIL_NOKEY;
|
continue;
|
||||||
|
|
||||||
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
|
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
|
||||||
continue;
|
continue;
|
||||||
else
|
|
||||||
failflags &= ~DNSSEC_FAIL_NODSSUP;
|
failflags &= ~DNSSEC_FAIL_NODSSUP;
|
||||||
|
|
||||||
|
if (recp1->addr.ds.keylen != (int)hash->digest_size ||
|
||||||
|
!(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (valid_digest[recp1->addr.ds.digest])
|
||||||
|
digest = cached_digest[recp1->addr.ds.digest];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* computing a hash is a unit of crypto work. */
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return STAT_ABANDONED;
|
||||||
|
|
||||||
if (!hash_init(hash, &ctx, &digest))
|
if (!hash_init(hash, &ctx, &digest))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
wire_len = to_wire(name);
|
wire_len = to_wire(name);
|
||||||
|
|
||||||
/* Note that digest may be different between DSs, so
|
/* Note that digest may be different between DSs, so
|
||||||
we can't move this outside the loop. */
|
we can't move this outside the loop. We keep
|
||||||
|
copies of each digest we make for this key,
|
||||||
|
so maximum digest work is O(keys x digests_types)
|
||||||
|
rather then O(keys x DSs) */
|
||||||
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
|
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
|
||||||
hash->update(ctx, (unsigned int)rdlen, psave);
|
hash->update(ctx, (unsigned int)rdlen, psave);
|
||||||
hash->digest(ctx, hash->digest_size, digest);
|
hash->digest(ctx, hash->digest_size, digest);
|
||||||
|
|
||||||
from_wire(name);
|
from_wire(name);
|
||||||
|
|
||||||
if (!(recp1->flags & F_NEG) &&
|
if (recp1->addr.ds.digest >= cached_digest_size)
|
||||||
recp1->addr.ds.keylen == (int)hash->digest_size &&
|
|
||||||
(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
|
|
||||||
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
|
|
||||||
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
|
|
||||||
rrcnt != 0)
|
|
||||||
{
|
{
|
||||||
if (sigcnt == 0)
|
unsigned char **new;
|
||||||
continue;
|
|
||||||
else
|
/* whine_malloc zeros memory */
|
||||||
failflags &= ~DNSSEC_FAIL_NOSIG;
|
if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *))))
|
||||||
|
{
|
||||||
|
if (cached_digest_size != 0)
|
||||||
|
{
|
||||||
|
memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *));
|
||||||
|
free(cached_digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
cached_digest_size = recp1->addr.ds.digest + 5;
|
||||||
|
cached_digest = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recp1->addr.ds.digest < cached_digest_size)
|
||||||
|
{
|
||||||
|
if (!cached_digest[recp1->addr.ds.digest])
|
||||||
|
cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen);
|
||||||
|
|
||||||
|
if (cached_digest[recp1->addr.ds.digest])
|
||||||
|
{
|
||||||
|
memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen);
|
||||||
|
valid_digest[recp1->addr.ds.digest] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0)
|
||||||
|
{
|
||||||
|
/* Found the key validated by a DS record.
|
||||||
|
Now check the self-sig for the entire key RRset using that key.
|
||||||
|
Note that validate_rrset() will never return STAT_NEED_KEY here,
|
||||||
|
since we supply the key it will use as an argument. */
|
||||||
|
struct blockdata *key;
|
||||||
|
|
||||||
|
if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4)))
|
||||||
|
break;
|
||||||
|
|
||||||
rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
|
rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
|
||||||
NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
|
NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter);
|
||||||
|
|
||||||
failflags &= rc;
|
|
||||||
|
|
||||||
if (STAT_ISEQUAL(rc, STAT_SECURE))
|
|
||||||
{
|
|
||||||
valid = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
blockdata_free(key);
|
blockdata_free(key);
|
||||||
}
|
|
||||||
|
|
||||||
if (valid)
|
if (STAT_ISEQUAL(rc, STAT_ABANDONED))
|
||||||
{
|
return rc;
|
||||||
|
|
||||||
|
/* can't validate KEY RRset with this key, see if there's another that
|
||||||
|
will, which is validated by another DS. */
|
||||||
|
if (!STAT_ISEQUAL(rc, STAT_SECURE))
|
||||||
|
break;
|
||||||
|
|
||||||
/* DNSKEY RRset determined to be OK, now cache it. */
|
/* DNSKEY RRset determined to be OK, now cache it. */
|
||||||
cache_start_insert();
|
cache_start_insert();
|
||||||
|
|
||||||
@@ -847,23 +919,22 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* bad packet */
|
||||||
|
|
||||||
if (qclass == class && rc == 1)
|
|
||||||
{
|
|
||||||
psave = p;
|
psave = p;
|
||||||
|
|
||||||
if (qtype == T_DNSKEY)
|
if (qclass == class && rc == 1 && qtype == T_DNSKEY)
|
||||||
{
|
{
|
||||||
if (rdlen < 4)
|
if (rdlen < 4)
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* min 1 byte key! */
|
||||||
|
|
||||||
GETSHORT(flags, p);
|
GETSHORT(flags, p);
|
||||||
if (*p++ != 3)
|
if (*p++ == 3)
|
||||||
return STAT_BOGUS;
|
{
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
||||||
|
|
||||||
if ((key = blockdata_alloc((char*)p, rdlen - 4)))
|
if (!(key = blockdata_alloc((char*)p, rdlen - 4)))
|
||||||
{
|
return STAT_BOGUS;
|
||||||
|
|
||||||
a.key.keylen = rdlen - 4;
|
a.key.keylen = rdlen - 4;
|
||||||
a.key.keydata = key;
|
a.key.keydata = key;
|
||||||
a.key.algo = algo;
|
a.key.algo = algo;
|
||||||
@@ -875,8 +946,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
blockdata_free(key);
|
blockdata_free(key);
|
||||||
return STAT_BOGUS;
|
return STAT_BOGUS;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
a.log.keytag = keytag;
|
a.log.keytag = keytag;
|
||||||
a.log.algo = algo;
|
a.log.algo = algo;
|
||||||
if (algo_digest_name(algo))
|
if (algo_digest_name(algo))
|
||||||
@@ -885,19 +955,16 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
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 (not supported)", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
p = psave;
|
p = psave + rdlen;
|
||||||
}
|
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
|
||||||
return STAT_BOGUS; /* bad packet */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* commit cache insert. */
|
/* commit cache insert. */
|
||||||
cache_end_insert();
|
cache_end_insert();
|
||||||
return STAT_OK;
|
return STAT_OK;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
|
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
|
||||||
return STAT_BOGUS | failflags;
|
return STAT_BOGUS | failflags;
|
||||||
@@ -916,9 +983,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
|
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
|
||||||
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
|
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
|
||||||
STAT_NEED_DS DS record needed.
|
STAT_NEED_DS DS record needed.
|
||||||
|
STAT_ABANDONED resource exhaustion.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
|
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
|
||||||
|
char *keyname, int class, int *validate_counter)
|
||||||
{
|
{
|
||||||
unsigned char *p = (unsigned char *)(header+1);
|
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, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
|
||||||
@@ -943,7 +1012,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
servfail = neganswer = nons = 1;
|
servfail = neganswer = nons = 1;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
|
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
|
||||||
|
|
||||||
if (STAT_ISEQUAL(rc, STAT_INSECURE))
|
if (STAT_ISEQUAL(rc, STAT_INSECURE))
|
||||||
{
|
{
|
||||||
@@ -977,6 +1046,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
|
|
||||||
for (i = 0; i < ntohs(header->ancount); i++)
|
for (i = 0; i < ntohs(header->ancount); i++)
|
||||||
{
|
{
|
||||||
|
unsigned char *psave;
|
||||||
|
|
||||||
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* bad packet */
|
||||||
|
|
||||||
@@ -988,14 +1059,15 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* bad packet */
|
||||||
|
|
||||||
|
psave = p;
|
||||||
|
|
||||||
if (aclass == class && atype == T_DS && rc == 1)
|
if (aclass == class && atype == T_DS && rc == 1)
|
||||||
{
|
{
|
||||||
int algo, digest, keytag;
|
int algo, digest, keytag;
|
||||||
unsigned char *psave = p;
|
|
||||||
struct blockdata *key;
|
struct blockdata *key;
|
||||||
|
|
||||||
if (rdlen < 4)
|
if (rdlen < 5)
|
||||||
return STAT_BOGUS; /* bad packet */
|
return STAT_BOGUS; /* min 1 byte digest! */
|
||||||
|
|
||||||
GETSHORT(keytag, p);
|
GETSHORT(keytag, p);
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
@@ -1006,7 +1078,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
a.log.keytag = keytag;
|
a.log.keytag = keytag;
|
||||||
a.log.algo = algo;
|
a.log.algo = algo;
|
||||||
a.log.digest = digest;
|
a.log.digest = digest;
|
||||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS 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 (not supported)", 0);
|
||||||
neg_ttl = ttl;
|
neg_ttl = ttl;
|
||||||
}
|
}
|
||||||
else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
|
else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
|
||||||
@@ -1027,16 +1099,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
a.log.keytag = keytag;
|
a.log.keytag = keytag;
|
||||||
a.log.algo = algo;
|
a.log.algo = algo;
|
||||||
a.log.digest = digest;
|
a.log.digest = digest;
|
||||||
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu", 0);
|
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 0);
|
||||||
found_supported = 1;
|
found_supported = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p = psave;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
p = psave + rdlen;
|
||||||
return STAT_BOGUS; /* bad packet */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_end_insert();
|
cache_end_insert();
|
||||||
@@ -1139,6 +1208,7 @@ static int hostname_cmp(const char *a, const char *b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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,
|
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)
|
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
|
||||||
{
|
{
|
||||||
@@ -1158,12 +1228,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
|
|
||||||
p = nsecs[i];
|
p = nsecs[i];
|
||||||
if (!extract_name(header, plen, &p, workspace1, 1, 10))
|
if (!extract_name(header, plen, &p, workspace1, 1, 10))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
p += 8; /* class, type, TTL */
|
p += 8; /* class, type, TTL */
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p);
|
||||||
psave = p;
|
psave = p;
|
||||||
if (!extract_name(header, plen, &p, workspace2, 1, 10))
|
if (!extract_name(header, plen, &p, workspace2, 1, 0))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
/* If NSEC comes from wildcard expansion, use original wildcard
|
/* If NSEC comes from wildcard expansion, use original wildcard
|
||||||
as name for computation. */
|
as name for computation. */
|
||||||
@@ -1191,12 +1261,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
{
|
{
|
||||||
/* 4035 para 5.4. Last sentence */
|
/* 4035 para 5.4. Last sentence */
|
||||||
if (type == T_NSEC || type == T_RRSIG)
|
if (type == T_NSEC || type == T_RRSIG)
|
||||||
return 1;
|
return 0;
|
||||||
|
|
||||||
/* NSEC with the same name as the RR we're testing, check
|
/* NSEC with the same name as the RR we're testing, check
|
||||||
that the type in question doesn't appear in the type map */
|
that the type in question doesn't appear in the type map */
|
||||||
rdlen -= p - psave;
|
rdlen -= p - psave;
|
||||||
/* rdlen is now length of type map, and p points to it */
|
/* 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() */
|
||||||
|
|
||||||
/* If we can prove that there's no NS record, return that information. */
|
/* If we can prove that there's no NS record, return that information. */
|
||||||
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
|
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
|
||||||
@@ -1207,25 +1278,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
/* A CNAME answer would also be valid, so if there's a CNAME is should
|
/* A CNAME answer would also be valid, so if there's a CNAME is should
|
||||||
have been returned. */
|
have been returned. */
|
||||||
if ((p[2] & (0x80 >> T_CNAME)) != 0)
|
if ((p[2] & (0x80 >> T_CNAME)) != 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
/* If the SOA bit is set for a DS record, then we have the
|
/* 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,
|
DS from the wrong side of the delegation. For the root DS,
|
||||||
this is expected. */
|
this is expected. */
|
||||||
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (rdlen >= 2)
|
while (rdlen >= 2)
|
||||||
{
|
{
|
||||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
if (p[0] == type >> 8)
|
if (p[0] == type >> 8)
|
||||||
{
|
{
|
||||||
/* Does the NSEC say our type exists? */
|
/* Does the NSEC say our type exists? */
|
||||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
break; /* finished checking */
|
break; /* finished checking */
|
||||||
}
|
}
|
||||||
@@ -1234,24 +1305,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
p += p[1];
|
p += p[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (rc == -1)
|
else if (rc == -1)
|
||||||
{
|
{
|
||||||
/* Normal case, name falls between NSEC name and next domain name,
|
/* Normal case, name falls between NSEC name and next domain name,
|
||||||
wrap around case, name falls between NSEC name (rc == -1) and end */
|
wrap around case, name falls between NSEC name (rc == -1) and end */
|
||||||
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
|
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* wrap around case, name falls between start and next domain name */
|
/* wrap around case, name falls between start and next domain name */
|
||||||
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
|
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return digest length, or zero on error */
|
/* return digest length, or zero on error */
|
||||||
@@ -1325,13 +1396,16 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
for (i = 0; i < nsec_count; i++)
|
for (i = 0; i < nsec_count; i++)
|
||||||
if ((p = nsecs[i]))
|
if ((p = nsecs[i]))
|
||||||
{
|
{
|
||||||
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
|
||||||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
p += 8; /* class, type, TTL */
|
p += 8; /* class, type, TTL */
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p);
|
||||||
|
|
||||||
psave = p;
|
psave = p;
|
||||||
|
|
||||||
|
/* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */
|
||||||
p++; /* algo */
|
p++; /* algo */
|
||||||
flags = *p++; /* flags */
|
flags = *p++; /* flags */
|
||||||
p += 2; /* iterations */
|
p += 2; /* iterations */
|
||||||
@@ -1339,9 +1413,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
p += salt_len; /* salt */
|
p += salt_len; /* salt */
|
||||||
hash_len = *p++; /* p now points to next hashed name */
|
hash_len = *p++; /* p now points to next hashed name */
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p, plen, hash_len))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (digest_len == base32_len && hash_len == base32_len)
|
if (digest_len == base32_len && hash_len == base32_len)
|
||||||
{
|
{
|
||||||
int rc = memcmp(workspace2, digest, digest_len);
|
int rc = memcmp(workspace2, digest, digest_len);
|
||||||
@@ -1349,7 +1420,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
{
|
{
|
||||||
/* We found an NSEC3 whose hashed name exactly matches the query, so
|
/* We found an NSEC3 whose hashed name exactly matches the query, so
|
||||||
we just need to check the type map. p points to the RR data for the record. */
|
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 offset = (type & 0xff) >> 3;
|
||||||
int mask = 0x80 >> (type & 0x07);
|
int mask = 0x80 >> (type & 0x07);
|
||||||
@@ -1357,9 +1429,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
p += hash_len; /* skip next-domain hash */
|
p += hash_len; /* skip next-domain hash */
|
||||||
rdlen -= p - psave;
|
rdlen -= p - psave;
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (rdlen >= 2 && p[0] == 0)
|
if (rdlen >= 2 && p[0] == 0)
|
||||||
{
|
{
|
||||||
/* If we can prove that there's no NS record, return that information. */
|
/* If we can prove that there's no NS record, return that information. */
|
||||||
@@ -1424,8 +1493,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
|
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
||||||
char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
|
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)
|
||||||
{
|
{
|
||||||
unsigned char *salt, *p, *digest;
|
unsigned char *salt, *p, *digest;
|
||||||
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
|
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
|
||||||
@@ -1445,7 +1515,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
for (i = 0; i < nsec_count; i++)
|
for (i = 0; i < nsec_count; i++)
|
||||||
{
|
{
|
||||||
if (!(p = skip_name(nsecs[i], header, plen, 15)))
|
if (!(p = skip_name(nsecs[i], header, plen, 15)))
|
||||||
return 0; /* bad packet */
|
return DNSSEC_FAIL_BADPACKET; /* bad packet */
|
||||||
|
|
||||||
p += 10; /* type, class, TTL, rdlen */
|
p += 10; /* type, class, TTL, rdlen */
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
@@ -1456,22 +1526,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
|
|
||||||
/* No usable NSEC3s */
|
/* No usable NSEC3s */
|
||||||
if (i == nsec_count)
|
if (i == nsec_count)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
p++; /* flags */
|
p++; /* flags */
|
||||||
|
|
||||||
GETSHORT (iterations, p);
|
GETSHORT (iterations, p);
|
||||||
/* Upper-bound iterations, to avoid DoS.
|
/* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
|
||||||
Strictly, there are lower bounds for small keys, but
|
if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
|
||||||
since we don't have key size info here, at least limit
|
return DNSSEC_FAIL_NSEC3_ITERS;
|
||||||
to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
|
|
||||||
if (iterations > 2500)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
salt_len = *p++;
|
salt_len = *p++;
|
||||||
salt = p;
|
salt = p;
|
||||||
if (!CHECK_LEN(header, salt, plen, salt_len))
|
|
||||||
return 0; /* bad packet */
|
|
||||||
|
|
||||||
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
|
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
|
||||||
for (i = 0; i < nsec_count; i++)
|
for (i = 0; i < nsec_count; i++)
|
||||||
@@ -1502,9 +1567,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
if (salt_len != *p++)
|
if (salt_len != *p++)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p, plen, salt_len))
|
|
||||||
return 0; /* bad packet */
|
|
||||||
|
|
||||||
if (memcmp(p, salt, salt_len) != 0)
|
if (memcmp(p, salt, salt_len) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -1512,11 +1574,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
nsecs[i] = nsec3p;
|
nsecs[i] = nsec3p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return DNSSEC_FAIL_WORK;
|
||||||
|
|
||||||
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
|
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
|
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
|
||||||
return 1;
|
return 0;
|
||||||
|
|
||||||
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
|
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
|
||||||
or an answer inferred from a wildcard record. */
|
or an answer inferred from a wildcard record. */
|
||||||
@@ -1531,15 +1596,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
if (wildname && hostname_isequal(closest_encloser, wildname))
|
if (wildname && hostname_isequal(closest_encloser, wildname))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return DNSSEC_FAIL_WORK;
|
||||||
|
|
||||||
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
|
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
for (i = 0; i < nsec_count; i++)
|
for (i = 0; i < nsec_count; i++)
|
||||||
if ((p = nsecs[i]))
|
if ((p = nsecs[i]))
|
||||||
{
|
{
|
||||||
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
if (!extract_name(header, plen, &p, workspace1, 1, 0))
|
||||||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
return 0;
|
|
||||||
|
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||||
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
if (digest_len == base32_len &&
|
if (digest_len == base32_len &&
|
||||||
memcmp(digest, workspace2, digest_len) == 0)
|
memcmp(digest, workspace2, digest_len) == 0)
|
||||||
@@ -1554,14 +1624,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
while ((closest_encloser = strchr(closest_encloser, '.')));
|
while ((closest_encloser = strchr(closest_encloser, '.')));
|
||||||
|
|
||||||
if (!closest_encloser || !next_closest)
|
if (!closest_encloser || !next_closest)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */
|
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return DNSSEC_FAIL_WORK;
|
||||||
|
|
||||||
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
|
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
|
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
/* Finally, check that there's no seat of wildcard synthesis */
|
/* Finally, check that there's no seat of wildcard synthesis */
|
||||||
if (!wildname)
|
if (!wildname)
|
||||||
@@ -1572,17 +1645,22 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
wildcard--;
|
wildcard--;
|
||||||
*wildcard = '*';
|
*wildcard = '*';
|
||||||
|
|
||||||
|
if (dec_counter(validate_counter, NULL))
|
||||||
|
return DNSSEC_FAIL_WORK;
|
||||||
|
|
||||||
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
|
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
|
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
/* 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)
|
||||||
{
|
{
|
||||||
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
||||||
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
||||||
@@ -1594,7 +1672,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
|
|
||||||
/* Move to NS section */
|
/* Move to NS section */
|
||||||
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
|
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
auth_start = p;
|
auth_start = p;
|
||||||
|
|
||||||
@@ -1603,13 +1681,16 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
unsigned char *pstart = p;
|
unsigned char *pstart = p;
|
||||||
|
|
||||||
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
|
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
GETSHORT(type, p);
|
GETSHORT(type, p);
|
||||||
GETSHORT(class, p);
|
GETSHORT(class, p);
|
||||||
GETLONG(ttl, p);
|
GETLONG(ttl, p);
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p);
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
if (class == qclass && (type == T_NSEC || type == T_NSEC3))
|
if (class == qclass && (type == T_NSEC || type == T_NSEC3))
|
||||||
{
|
{
|
||||||
if (nsec_ttl)
|
if (nsec_ttl)
|
||||||
@@ -1622,12 +1703,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
|
|
||||||
/* No mixed NSECing 'round here, thankyouverymuch */
|
/* No mixed NSECing 'round here, thankyouverymuch */
|
||||||
if (type_found != 0 && type_found != type)
|
if (type_found != 0 && type_found != type)
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
|
||||||
type_found = type;
|
type_found = type;
|
||||||
|
|
||||||
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
|
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
if (type == T_NSEC)
|
if (type == T_NSEC)
|
||||||
{
|
{
|
||||||
@@ -1642,30 +1723,33 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
int res, j, rdlen1, type1, class1;
|
int res, j, rdlen1, type1, class1;
|
||||||
|
|
||||||
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
|
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
rrsig_labels[nsecs_found] = NULL;
|
rrsig_labels[nsecs_found] = NULL;
|
||||||
|
|
||||||
for (j = ntohs(header->nscount); j != 0; j--)
|
for (j = ntohs(header->nscount); j != 0; j--)
|
||||||
{
|
{
|
||||||
|
unsigned char *psav;
|
||||||
|
|
||||||
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
|
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
GETSHORT(type1, p1);
|
GETSHORT(type1, p1);
|
||||||
GETSHORT(class1, p1);
|
GETSHORT(class1, p1);
|
||||||
p1 += 4; /* TTL */
|
p1 += 4; /* TTL */
|
||||||
GETSHORT(rdlen1, p1);
|
GETSHORT(rdlen1, p1);
|
||||||
|
|
||||||
|
psav = p1;
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p1, plen, rdlen1))
|
if (!CHECK_LEN(header, p1, plen, rdlen1))
|
||||||
return 0;
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
if (res == 1 && class1 == qclass && type1 == T_RRSIG)
|
if (res == 1 && class1 == qclass && type1 == T_RRSIG)
|
||||||
{
|
{
|
||||||
int type_covered;
|
int type_covered;
|
||||||
unsigned char *psav = p1;
|
|
||||||
|
|
||||||
if (rdlen1 < 18)
|
if (rdlen1 < 18)
|
||||||
return 0; /* bad packet */
|
return DNSSEC_FAIL_BADPACKET; /* bad packet */
|
||||||
|
|
||||||
GETSHORT(type_covered, p1);
|
GETSHORT(type_covered, p1);
|
||||||
|
|
||||||
@@ -1677,33 +1761,56 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
if (!rrsig_labels[nsecs_found])
|
if (!rrsig_labels[nsecs_found])
|
||||||
rrsig_labels[nsecs_found] = p1;
|
rrsig_labels[nsecs_found] = p1;
|
||||||
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
|
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
p1 = psav;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
p1 = psav + rdlen1;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Must have found at least one sig. */
|
/* Must have found at least one sig. */
|
||||||
if (!rrsig_labels[nsecs_found])
|
if (!rrsig_labels[nsecs_found])
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
|
}
|
||||||
|
else if (type == T_NSEC3)
|
||||||
|
{
|
||||||
|
/* Decode the packet structure enough to check that rdlen is big enough
|
||||||
|
to contain everything other than the type bitmap.
|
||||||
|
(packet checked to be long enough to contain rdlen above)
|
||||||
|
We don't need to do any further length checks in check_nes3_coverage()
|
||||||
|
or prove_non_existence_nsec3() */
|
||||||
|
|
||||||
|
int salt_len, hash_len;
|
||||||
|
unsigned char *psav = p;
|
||||||
|
|
||||||
|
if (rdlen < 5)
|
||||||
|
return DNSSEC_FAIL_BADPACKET;
|
||||||
|
|
||||||
|
p += 4; /* algo, flags, iterations */
|
||||||
|
salt_len = *p++; /* salt_len */
|
||||||
|
if (rdlen < (6 + salt_len))
|
||||||
|
return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */
|
||||||
|
|
||||||
|
p += salt_len; /* salt */
|
||||||
|
hash_len = *p++;
|
||||||
|
if (rdlen < (6 + salt_len + hash_len))
|
||||||
|
return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */
|
||||||
|
|
||||||
|
p = psav;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsecset[nsecs_found++] = pstart;
|
nsecset[nsecs_found++] = pstart;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
p += rdlen;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type_found == T_NSEC)
|
if (type_found == T_NSEC)
|
||||||
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
|
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
|
||||||
else if (type_found == T_NSEC3)
|
else if (type_found == T_NSEC3)
|
||||||
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
|
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
|
||||||
else
|
else
|
||||||
return 0;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check signing status of name.
|
/* Check signing status of name.
|
||||||
@@ -1798,16 +1905,17 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
|
|||||||
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
|
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
|
||||||
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
|
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
|
||||||
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
||||||
|
STAT_ABANDONED resource exhaustion.
|
||||||
|
|
||||||
daemon->rr_status points to a char array which corressponds to the RRs in the
|
daemon->rr_status points to a char array which corressponds to the RRs in the
|
||||||
answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't.
|
answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't.
|
||||||
|
|
||||||
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
|
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
|
||||||
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
|
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
|
||||||
is the nons argument is non-NULL.
|
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 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 *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
|
||||||
{
|
{
|
||||||
static unsigned char **targets = NULL;
|
static unsigned char **targets = NULL;
|
||||||
static int target_sz = 0;
|
static int target_sz = 0;
|
||||||
@@ -1816,7 +1924,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
|
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
|
||||||
int i, j, rc = STAT_INSECURE;
|
int i, j, rc = STAT_INSECURE;
|
||||||
int secure = STAT_SECURE;
|
int secure = STAT_SECURE;
|
||||||
|
int rc_nsec;
|
||||||
/* extend rr_status if necessary */
|
/* extend rr_status if necessary */
|
||||||
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
|
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
|
||||||
{
|
{
|
||||||
@@ -1982,9 +2090,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
{
|
{
|
||||||
unsigned long sig_ttl;
|
unsigned long sig_ttl;
|
||||||
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
|
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
|
||||||
rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
|
rrcnt, name, keyname, &wildname, 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))
|
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
|
||||||
{
|
{
|
||||||
if (class)
|
if (class)
|
||||||
*class = class1; /* Class for DS or DNSKEY */
|
*class = class1; /* Class for DS or DNSKEY */
|
||||||
@@ -2018,8 +2126,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
That's not a problem since if the RRsets later fail
|
That's not a problem since if the RRsets later fail
|
||||||
we'll return BOGUS then. */
|
we'll return BOGUS then. */
|
||||||
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
|
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
|
||||||
!prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
|
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
|
return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
|
||||||
|
|
||||||
rc = STAT_SECURE;
|
rc = STAT_SECURE;
|
||||||
}
|
}
|
||||||
@@ -2044,20 +2152,24 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
|
|
||||||
/* For anything other than a DS record, this situation is OK if either
|
/* For anything other than a DS record, this situation is OK if either
|
||||||
the answer is in an unsigned zone, or there's a NSEC records. */
|
the answer is in an unsigned zone, or there's a NSEC records. */
|
||||||
if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
|
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
|
||||||
{
|
{
|
||||||
|
if (rc_nsec & DNSSEC_FAIL_WORK)
|
||||||
|
return STAT_ABANDONED;
|
||||||
|
|
||||||
/* Empty DS without NSECS */
|
/* Empty DS without NSECS */
|
||||||
if (qtype == T_DS)
|
if (qtype == T_DS)
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
|
return STAT_BOGUS | rc_nsec;
|
||||||
|
|
||||||
if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
|
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
|
||||||
|
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
|
||||||
{
|
{
|
||||||
if (class)
|
if (class)
|
||||||
*class = qclass; /* Class for NEED_DS or NEED_KEY */
|
*class = qclass; /* Class for NEED_DS or NEED_KEY */
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
|
return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2139,6 +2251,8 @@ int errflags_to_ede(int status)
|
|||||||
return EDE_NO_DNSKEY;
|
return EDE_NO_DNSKEY;
|
||||||
else if (status & DNSSEC_FAIL_NODSSUP)
|
else if (status & DNSSEC_FAIL_NODSSUP)
|
||||||
return EDE_USUPDS;
|
return EDE_USUPDS;
|
||||||
|
else if (status & DNSSEC_FAIL_NSEC3_ITERS)
|
||||||
|
return EDE_UNS_NS3_ITER;
|
||||||
else if (status & DNSSEC_FAIL_NONSEC)
|
else if (status & DNSSEC_FAIL_NONSEC)
|
||||||
return EDE_NO_NSEC;
|
return EDE_NO_NSEC;
|
||||||
else if (status & DNSSEC_FAIL_INDET)
|
else if (status & DNSSEC_FAIL_INDET)
|
||||||
|
|||||||
+131
-79
@@ -338,7 +338,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
if (ad_reqd)
|
if (ad_reqd)
|
||||||
forward->flags |= FREC_AD_QUESTION;
|
forward->flags |= FREC_AD_QUESTION;
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
forward->work_counter = DNSSEC_WORK;
|
forward->work_counter = daemon->limit[LIMIT_WORK];
|
||||||
|
forward->validate_counter = daemon->limit[LIMIT_CRYPTO];
|
||||||
if (do_bit)
|
if (do_bit)
|
||||||
forward->flags |= FREC_DO_QUESTION;
|
forward->flags |= FREC_DO_QUESTION;
|
||||||
#endif
|
#endif
|
||||||
@@ -687,13 +688,12 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
{
|
{
|
||||||
unsigned char *pheader, *sizep;
|
unsigned char *pheader, *sizep;
|
||||||
struct ipsets *ipsets = NULL, *nftsets = NULL;
|
struct ipsets *ipsets = NULL, *nftsets = NULL;
|
||||||
int munged = 0, is_sign;
|
int is_sign;
|
||||||
unsigned int rcode = RCODE(header);
|
unsigned int rcode = RCODE(header);
|
||||||
size_t plen;
|
size_t plen;
|
||||||
|
|
||||||
(void)ad_reqd;
|
(void)ad_reqd;
|
||||||
(void)do_bit;
|
(void)do_bit;
|
||||||
(void)bogusanswer;
|
|
||||||
|
|
||||||
#ifdef HAVE_IPSET
|
#ifdef HAVE_IPSET
|
||||||
if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
|
if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
|
||||||
@@ -782,10 +782,38 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
server->flags |= SERV_WARNED_RECURSIVE;
|
server->flags |= SERV_WARNED_RECURSIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header->hb3 & HB3_TC)
|
||||||
|
{
|
||||||
|
log_query(F_UPSTREAM, NULL, NULL, "truncated", 0);
|
||||||
|
header->ancount = htons(0);
|
||||||
|
header->nscount = htons(0);
|
||||||
|
header->arcount = htons(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(header->hb3 & HB3_TC) && (!bogusanswer || (header->hb4 & HB4_CD)))
|
||||||
|
{
|
||||||
|
if (rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) &&
|
||||||
|
(check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)))
|
||||||
|
{
|
||||||
|
/* if we forwarded a query for a locally known name (because it was for
|
||||||
|
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
|
||||||
|
since we know that the domain exists, even if upstream doesn't */
|
||||||
|
header->hb3 |= HB3_AA;
|
||||||
|
SET_RCODE(header, NOERROR);
|
||||||
|
cache_secure = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daemon->doctors && do_doctor(header, n, daemon->namebuff))
|
||||||
|
cache_secure = 0;
|
||||||
|
|
||||||
|
/* check_for_bogus_wildcard() does it's own caching, so
|
||||||
|
don't call extract_addresses() if it triggers. */
|
||||||
if (daemon->bogus_addr && rcode != NXDOMAIN &&
|
if (daemon->bogus_addr && rcode != NXDOMAIN &&
|
||||||
check_for_bogus_wildcard(header, n, daemon->namebuff, now))
|
check_for_bogus_wildcard(header, n, daemon->namebuff, now))
|
||||||
{
|
{
|
||||||
munged = 1;
|
header->ancount = htons(0);
|
||||||
|
header->nscount = htons(0);
|
||||||
|
header->arcount = htons(0);
|
||||||
SET_RCODE(header, NXDOMAIN);
|
SET_RCODE(header, NXDOMAIN);
|
||||||
header->hb3 &= ~HB3_AA;
|
header->hb3 &= ~HB3_AA;
|
||||||
cache_secure = 0;
|
cache_secure = 0;
|
||||||
@@ -793,62 +821,50 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int doctored = 0;
|
int rc = extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure);
|
||||||
|
|
||||||
if (rcode == NXDOMAIN &&
|
if (rc != 0)
|
||||||
extract_request(header, n, daemon->namebuff, NULL))
|
|
||||||
{
|
{
|
||||||
if (check_for_local_domain(daemon->namebuff, now) ||
|
header->ancount = htons(0);
|
||||||
lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))
|
header->nscount = htons(0);
|
||||||
{
|
header->arcount = htons(0);
|
||||||
/* if we forwarded a query for a locally known name (because it was for
|
|
||||||
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
|
|
||||||
since we know that the domain exists, even if upstream doesn't */
|
|
||||||
munged = 1;
|
|
||||||
header->hb3 |= HB3_AA;
|
|
||||||
SET_RCODE(header, NOERROR);
|
|
||||||
cache_secure = 0;
|
cache_secure = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
|
if (rc == 1)
|
||||||
{
|
{
|
||||||
case 1:
|
|
||||||
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
|
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
|
||||||
munged = 1;
|
|
||||||
cache_secure = 0;
|
|
||||||
ede = EDE_BLOCKED;
|
ede = EDE_BLOCKED;
|
||||||
break;
|
|
||||||
|
|
||||||
/* extract_addresses() found a malformed answer. */
|
|
||||||
case 2:
|
|
||||||
munged = 1;
|
|
||||||
SET_RCODE(header, SERVFAIL);
|
|
||||||
cache_secure = 0;
|
|
||||||
ede = EDE_OTHER;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rcode == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0)
|
if (rc == 2)
|
||||||
ede = EDE_FILTERED;
|
{
|
||||||
|
/* extract_addresses() found a malformed answer. */
|
||||||
|
SET_RCODE(header, SERVFAIL);
|
||||||
|
ede = EDE_OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (doctored)
|
if (RCODE(header) == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0)
|
||||||
cache_secure = 0;
|
ede = EDE_FILTERED;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
|
{
|
||||||
|
if (bogusanswer)
|
||||||
|
{
|
||||||
|
if (!(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG))
|
||||||
{
|
{
|
||||||
/* Bogus reply, turn into SERVFAIL */
|
/* Bogus reply, turn into SERVFAIL */
|
||||||
SET_RCODE(header, SERVFAIL);
|
SET_RCODE(header, SERVFAIL);
|
||||||
munged = 1;
|
header->ancount = htons(0);
|
||||||
|
header->nscount = htons(0);
|
||||||
|
header->arcount = htons(0);
|
||||||
|
ede = EDE_DNSSEC_BOGUS;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (option_bool(OPT_DNSSEC_VALID))
|
else if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
|
||||||
{
|
|
||||||
header->hb4 &= ~HB4_AD;
|
|
||||||
|
|
||||||
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
|
|
||||||
header->hb4 |= HB4_AD;
|
header->hb4 |= HB4_AD;
|
||||||
|
|
||||||
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */
|
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */
|
||||||
@@ -857,19 +873,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* do this after extract_addresses. Ensure NODATA reply and remove
|
/* the code above can elide sections of the packet. Find the new length here
|
||||||
nameserver info. */
|
and put back pseudoheader if it was removed. */
|
||||||
if (munged)
|
|
||||||
{
|
|
||||||
header->ancount = htons(0);
|
|
||||||
header->nscount = htons(0);
|
|
||||||
header->arcount = htons(0);
|
|
||||||
header->hb3 &= ~HB3_TC;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide
|
|
||||||
sections of the packet. Find the new length here and put back pseudoheader
|
|
||||||
if it was removed. */
|
|
||||||
n = resize_packet(header, n, pheader, plen);
|
n = resize_packet(header, n, pheader, plen);
|
||||||
|
|
||||||
if (pheader && ede != EDE_UNSET)
|
if (pheader && ede != EDE_UNSET)
|
||||||
@@ -888,6 +893,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||||
ssize_t plen, int status, time_t now)
|
ssize_t plen, int status, time_t now)
|
||||||
{
|
{
|
||||||
|
struct frec *orig;
|
||||||
|
int log_resource = 0;
|
||||||
|
|
||||||
daemon->log_display_id = forward->frec_src.log_id;
|
daemon->log_display_id = forward->frec_src.log_id;
|
||||||
|
|
||||||
/* We've had a reply already, which we're validating. Ignore this duplicate */
|
/* We've had a reply already, which we're validating. Ignore this duplicate */
|
||||||
@@ -913,26 +921,27 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find the original query that started it all.... */
|
||||||
|
for (orig = forward; orig->dependent; orig = orig->dependent);
|
||||||
|
|
||||||
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
|
/* 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
|
would invite infinite loops, since the answers to DNSKEY and DS queries
|
||||||
will not be cached, so they'll be repeated. */
|
will not be cached, so they'll be repeated. */
|
||||||
if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
|
if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
|
||||||
{
|
{
|
||||||
if (forward->flags & FREC_DNSKEY_QUERY)
|
if (forward->flags & FREC_DNSKEY_QUERY)
|
||||||
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
|
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
|
||||||
else if (forward->flags & FREC_DS_QUERY)
|
else if (forward->flags & FREC_DS_QUERY)
|
||||||
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
|
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
|
||||||
else
|
else
|
||||||
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
|
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
|
||||||
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
|
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL, &orig->validate_counter);
|
||||||
#ifdef HAVE_DUMPFILE
|
|
||||||
if (STAT_ISEQUAL(status, STAT_BOGUS))
|
|
||||||
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
|
|
||||||
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (STAT_ISEQUAL(status, STAT_ABANDONED))
|
||||||
|
log_resource = 1;
|
||||||
|
|
||||||
/* Can't validate, as we're missing key data. Put this
|
/* Can't validate, as we're missing key data. Put this
|
||||||
answer aside, whilst we get that. */
|
answer aside, whilst we get that. */
|
||||||
if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
|
if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
|
||||||
@@ -975,18 +984,19 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (orig->work_counter-- == 0)
|
||||||
|
{
|
||||||
|
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
|
||||||
|
log_resource = 1;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
struct server *server;
|
struct server *server;
|
||||||
struct frec *orig;
|
|
||||||
void *hash;
|
void *hash;
|
||||||
size_t nn;
|
size_t nn;
|
||||||
int serverind, fd;
|
int serverind, fd;
|
||||||
struct randfd_list *rfds = NULL;
|
struct randfd_list *rfds = NULL;
|
||||||
|
|
||||||
/* Find the original query that started it all.... */
|
|
||||||
for (orig = forward; orig->dependent; orig = orig->dependent);
|
|
||||||
|
|
||||||
/* Make sure we don't expire and free the orig frec during the
|
/* Make sure we don't expire and free the orig frec during the
|
||||||
allocation of a new one: third arg of get_new_frec() does that. */
|
allocation of a new one: third arg of get_new_frec() does that. */
|
||||||
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
|
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
|
||||||
@@ -995,7 +1005,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
|||||||
daemon->keyname, forward->class,
|
daemon->keyname, forward->class,
|
||||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) &&
|
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) &&
|
||||||
(hash = hash_questions(header, nn, daemon->namebuff)) &&
|
(hash = hash_questions(header, nn, daemon->namebuff)) &&
|
||||||
--orig->work_counter != 0 &&
|
|
||||||
(fd = allocate_rfd(&rfds, server)) != -1 &&
|
(fd = allocate_rfd(&rfds, server)) != -1 &&
|
||||||
(new = get_new_frec(now, server, 1)))
|
(new = get_new_frec(now, server, 1)))
|
||||||
{
|
{
|
||||||
@@ -1061,6 +1070,21 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
|||||||
status = STAT_ABANDONED;
|
status = STAT_ABANDONED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log_resource)
|
||||||
|
{
|
||||||
|
/* Log the actual validation that made us barf. */
|
||||||
|
unsigned char *p = (unsigned char *)(header+1);
|
||||||
|
if (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1)
|
||||||
|
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
|
||||||
|
daemon->namebuff[0] ? daemon->namebuff : ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_DUMPFILE
|
||||||
|
if (STAT_ISEQUAL(status, STAT_BOGUS) || STAT_ISEQUAL(status, STAT_ABANDONED))
|
||||||
|
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
|
||||||
|
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Validated original answer, all done. */
|
/* Validated original answer, all done. */
|
||||||
if (!forward->dependent)
|
if (!forward->dependent)
|
||||||
return_reply(now, forward, header, plen, status);
|
return_reply(now, forward, header, plen, status);
|
||||||
@@ -1333,6 +1357,12 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
|
|||||||
log_query(F_SECSTAT, domain, &a, result, 0);
|
log_query(F_SECSTAT, domain, &a, result, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||||
|
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter;
|
||||||
|
|
||||||
|
if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||||
|
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (option_bool(OPT_NO_REBIND))
|
if (option_bool(OPT_NO_REBIND))
|
||||||
@@ -2014,7 +2044,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
|||||||
/* Recurse down the key hierarchy */
|
/* Recurse down the key hierarchy */
|
||||||
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
|
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
|
||||||
int class, char *name, char *keyname, struct server *server,
|
int class, char *name, char *keyname, struct server *server,
|
||||||
int have_mark, unsigned int mark, int *keycount)
|
int have_mark, unsigned int mark, int *keycount, int *validatecount)
|
||||||
{
|
{
|
||||||
int first, last, start, new_status;
|
int first, last, start, new_status;
|
||||||
unsigned char *packet = NULL;
|
unsigned char *packet = NULL;
|
||||||
@@ -2026,20 +2056,34 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
int log_save;
|
int log_save;
|
||||||
|
|
||||||
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
||||||
if (--(*keycount) == 0)
|
if (STAT_ISEQUAL(status, STAT_NEED_KEY))
|
||||||
new_status = STAT_ABANDONED;
|
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount);
|
||||||
else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
|
|
||||||
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
|
||||||
else if (STAT_ISEQUAL(status, STAT_NEED_DS))
|
else if (STAT_ISEQUAL(status, STAT_NEED_DS))
|
||||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
|
||||||
else
|
else
|
||||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
|
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
|
||||||
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
|
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL, validatecount);
|
||||||
|
|
||||||
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY))
|
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if ((*keycount)-- == 0)
|
||||||
|
{
|
||||||
|
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
|
||||||
|
new_status = STAT_ABANDONED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (STAT_ISEQUAL(new_status, STAT_ABANDONED))
|
||||||
|
{
|
||||||
|
/* Log the actual validation that made us barf. */
|
||||||
|
unsigned char *p = (unsigned char *)(header+1);
|
||||||
|
if (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1)
|
||||||
|
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
|
||||||
|
daemon->namebuff[0] ? daemon->namebuff : ".");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Can't validate because we need a key/DS whose name now in keyname.
|
/* Can't validate because we need a key/DS whose name now in keyname.
|
||||||
Make query for same, and recurse to validate */
|
Make query for same, and recurse to validate */
|
||||||
if (!packet)
|
if (!packet)
|
||||||
@@ -2070,7 +2114,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr,
|
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr,
|
||||||
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
||||||
|
|
||||||
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
|
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server,
|
||||||
|
have_mark, mark, keycount, validatecount);
|
||||||
|
|
||||||
daemon->log_display_id = log_save;
|
daemon->log_display_id = log_save;
|
||||||
|
|
||||||
@@ -2375,9 +2420,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
|
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
|
||||||
{
|
{
|
||||||
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
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,
|
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
|
||||||
serv, have_mark, mark, &keycount);
|
serv, have_mark, mark, &keycount, &validatecount);
|
||||||
char *result, *domain = "result";
|
char *result, *domain = "result";
|
||||||
|
|
||||||
union all_addr a;
|
union all_addr a;
|
||||||
@@ -2403,6 +2449,12 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
}
|
}
|
||||||
|
|
||||||
log_query(F_SECSTAT, domain, &a, result, 0);
|
log_query(F_SECSTAT, domain, &a, result, 0);
|
||||||
|
|
||||||
|
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
|
||||||
|
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
|
||||||
|
|
||||||
|
if ((daemon->limit[LIMIT_WORK] - keycount) > (int)daemon->metrics[METRIC_WORK_HWM])
|
||||||
|
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - keycount;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ void inotify_dnsmasq_init()
|
|||||||
if (daemon->inotifyfd == -1)
|
if (daemon->inotifyfd == -1)
|
||||||
die(_("failed to create inotify: %s"), NULL, EC_MISC);
|
die(_("failed to create inotify: %s"), NULL, EC_MISC);
|
||||||
|
|
||||||
if (option_bool(OPT_NO_RESOLV))
|
if (daemon->port == 0 || option_bool(OPT_NO_RESOLV))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (res = daemon->resolv_files; res; res = res->next)
|
for (res = daemon->resolv_files; res; res = res->next)
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ const char * metric_names[] = {
|
|||||||
"dns_local_answered",
|
"dns_local_answered",
|
||||||
"dns_stale_answered",
|
"dns_stale_answered",
|
||||||
"dns_unanswered",
|
"dns_unanswered",
|
||||||
|
"dnssec_max_crypto_use",
|
||||||
|
"dnssec_max_sig_fail",
|
||||||
|
"dnssec_max_work",
|
||||||
"bootp",
|
"bootp",
|
||||||
"pxe",
|
"pxe",
|
||||||
"dhcp_ack",
|
"dhcp_ack",
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ enum {
|
|||||||
METRIC_DNS_LOCAL_ANSWERED,
|
METRIC_DNS_LOCAL_ANSWERED,
|
||||||
METRIC_DNS_STALE_ANSWERED,
|
METRIC_DNS_STALE_ANSWERED,
|
||||||
METRIC_DNS_UNANSWERED_QUERY,
|
METRIC_DNS_UNANSWERED_QUERY,
|
||||||
|
METRIC_CRYPTO_HWM,
|
||||||
|
METRIC_SIG_FAIL_HWM,
|
||||||
|
METRIC_WORK_HWM,
|
||||||
METRIC_BOOTP,
|
METRIC_BOOTP,
|
||||||
METRIC_PXE,
|
METRIC_PXE,
|
||||||
METRIC_DHCPACK,
|
METRIC_DHCPACK,
|
||||||
|
|||||||
+28
-1
@@ -191,6 +191,7 @@ struct myoption {
|
|||||||
#define LOPT_NO_DHCP6 382
|
#define LOPT_NO_DHCP6 382
|
||||||
#define LOPT_NO_DHCP4 383
|
#define LOPT_NO_DHCP4 383
|
||||||
#define LOPT_MAX_PROCS 384
|
#define LOPT_MAX_PROCS 384
|
||||||
|
#define LOPT_DNSSEC_LIMITS 385
|
||||||
|
|
||||||
#ifdef HAVE_GETOPT_LONG
|
#ifdef HAVE_GETOPT_LONG
|
||||||
static const struct option opts[] =
|
static const struct option opts[] =
|
||||||
@@ -364,6 +365,7 @@ static const struct myoption opts[] =
|
|||||||
{ "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
|
{ "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
|
||||||
{ "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
|
{ "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
|
||||||
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
|
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
|
||||||
|
{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
|
||||||
{ "dhcp-relay", 1, 0, LOPT_RELAY },
|
{ "dhcp-relay", 1, 0, LOPT_RELAY },
|
||||||
{ "ra-param", 1, 0, LOPT_RA_PARAM },
|
{ "ra-param", 1, 0, LOPT_RA_PARAM },
|
||||||
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
|
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
|
||||||
@@ -568,6 +570,7 @@ static struct {
|
|||||||
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
|
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
|
||||||
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
|
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
|
||||||
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
|
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
|
||||||
|
{ LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
|
||||||
{ LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
|
{ LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
|
||||||
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
|
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
|
||||||
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
|
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
|
||||||
@@ -5258,6 +5261,24 @@ err:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
|
case LOPT_DNSSEC_LIMITS:
|
||||||
|
{
|
||||||
|
int lim, val;
|
||||||
|
|
||||||
|
for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma)
|
||||||
|
{
|
||||||
|
comma = split(arg);
|
||||||
|
|
||||||
|
if (!atoi_check(arg, &val))
|
||||||
|
ret_err(gen_err);
|
||||||
|
|
||||||
|
if (val != 0)
|
||||||
|
daemon->limit[lim] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
|
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
|
||||||
daemon->timestamp_file = opt_string_alloc(arg);
|
daemon->timestamp_file = opt_string_alloc(arg);
|
||||||
break;
|
break;
|
||||||
@@ -5844,6 +5865,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
|||||||
daemon = opt_malloc(sizeof(struct daemon));
|
daemon = opt_malloc(sizeof(struct daemon));
|
||||||
memset(daemon, 0, sizeof(struct daemon));
|
memset(daemon, 0, sizeof(struct daemon));
|
||||||
daemon->namebuff = buff;
|
daemon->namebuff = buff;
|
||||||
|
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
|
||||||
daemon->addrbuff = safe_malloc(ADDRSTRLEN);
|
daemon->addrbuff = safe_malloc(ADDRSTRLEN);
|
||||||
|
|
||||||
/* Set defaults - everything else is zero or NULL */
|
/* Set defaults - everything else is zero or NULL */
|
||||||
@@ -5868,7 +5890,12 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
|||||||
daemon->randport_limit = 1;
|
daemon->randport_limit = 1;
|
||||||
daemon->host_index = SRC_AH;
|
daemon->host_index = SRC_AH;
|
||||||
daemon->max_procs = MAX_PROCS;
|
daemon->max_procs = MAX_PROCS;
|
||||||
daemon->max_procs_used = 0;
|
#ifdef HAVE_DNSSEC
|
||||||
|
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
|
||||||
|
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
|
||||||
|
daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK;
|
||||||
|
daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* See comment above make_servers(). Optimises server-read code. */
|
/* See comment above make_servers(). Optimises server-read code. */
|
||||||
mark_servers(0);
|
mark_servers(0);
|
||||||
|
|||||||
+241
-120
@@ -384,14 +384,23 @@ static int private_net6(struct in6_addr *a, int ban_localhost)
|
|||||||
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
|
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored)
|
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
|
||||||
{
|
{
|
||||||
|
unsigned char *p;
|
||||||
int i, qtype, qclass, rdlen;
|
int i, qtype, qclass, rdlen;
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
for (i = count; i != 0; i--)
|
if (!(p = skip_questions(header, qlen)))
|
||||||
|
return done;
|
||||||
|
|
||||||
|
for (i = 0; i < ntohs(header->ancount) + ntohs(header->arcount); i++)
|
||||||
{
|
{
|
||||||
if (!(p = skip_name(p, header, qlen, 10)))
|
/* Skip over auth section */
|
||||||
return 0; /* bad packet */
|
if (i == ntohs(header->ancount) && !(p = skip_section(p, ntohs(header->nscount), header, qlen)))
|
||||||
|
return done;
|
||||||
|
|
||||||
|
if (!extract_name(header, qlen, &p, namebuff, 1, 10))
|
||||||
|
return done; /* bad packet */
|
||||||
|
|
||||||
GETSHORT(qtype, p);
|
GETSHORT(qtype, p);
|
||||||
GETSHORT(qclass, p);
|
GETSHORT(qclass, p);
|
||||||
@@ -401,57 +410,77 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *
|
|||||||
if (qclass == C_IN && qtype == T_A)
|
if (qclass == C_IN && qtype == T_A)
|
||||||
{
|
{
|
||||||
struct doctor *doctor;
|
struct doctor *doctor;
|
||||||
struct in_addr addr;
|
union all_addr addr;
|
||||||
|
|
||||||
if (!CHECK_LEN(header, p, qlen, INADDRSZ))
|
if (!CHECK_LEN(header, p, qlen, INADDRSZ))
|
||||||
return 0;
|
return done;
|
||||||
|
|
||||||
/* alignment */
|
/* alignment */
|
||||||
memcpy(&addr, p, INADDRSZ);
|
memcpy(&addr.addr4, p, INADDRSZ);
|
||||||
|
|
||||||
for (doctor = daemon->doctors; doctor; doctor = doctor->next)
|
for (doctor = daemon->doctors; doctor; doctor = doctor->next)
|
||||||
{
|
{
|
||||||
if (doctor->end.s_addr == 0)
|
if (doctor->end.s_addr == 0)
|
||||||
{
|
{
|
||||||
if (!is_same_net(doctor->in, addr, doctor->mask))
|
if (!is_same_net(doctor->in, addr.addr4, doctor->mask))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (ntohl(doctor->in.s_addr) > ntohl(addr.s_addr) ||
|
else if (ntohl(doctor->in.s_addr) > ntohl(addr.addr4.s_addr) ||
|
||||||
ntohl(doctor->end.s_addr) < ntohl(addr.s_addr))
|
ntohl(doctor->end.s_addr) < ntohl(addr.addr4.s_addr))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
addr.s_addr &= ~doctor->mask.s_addr;
|
addr.addr4.s_addr &= ~doctor->mask.s_addr;
|
||||||
addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr);
|
addr.addr4.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr);
|
||||||
/* Since we munged the data, the server it came from is no longer authoritative */
|
/* Since we munged the data, the server it came from is no longer authoritative */
|
||||||
header->hb3 &= ~HB3_AA;
|
header->hb3 &= ~HB3_AA;
|
||||||
*doctored = 1;
|
#ifdef HAVE_DNSSEC
|
||||||
memcpy(p, &addr, INADDRSZ);
|
/* remove validated flag from this RR, since we changed it! */
|
||||||
|
if (option_bool(OPT_DNSSEC_VALID) && i < ntohs(header->ancount))
|
||||||
|
daemon->rr_status[i] = 0;
|
||||||
|
#endif
|
||||||
|
done = 1;
|
||||||
|
memcpy(p, &addr.addr4, INADDRSZ);
|
||||||
|
log_query(F_FORWARD | F_CONFIG | F_IPV4, namebuff, &addr, NULL, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p, qlen, rdlen))
|
if (!ADD_RDLEN(header, p, qlen, rdlen))
|
||||||
return 0; /* bad packet */
|
return done; /* bad packet */
|
||||||
}
|
}
|
||||||
|
|
||||||
return p;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
|
/* Find SOA RR in auth section to get TTL for negative caching of name.
|
||||||
|
Cache said SOA and return the difference in length between name and the name of the
|
||||||
|
SOA RR so we can look it up again.
|
||||||
|
*/
|
||||||
|
static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int no_cache, time_t now)
|
||||||
{
|
{
|
||||||
unsigned char *p;
|
unsigned char *p, *psave;
|
||||||
int qtype, qclass, rdlen;
|
int qtype, qclass, rdlen;
|
||||||
unsigned long ttl, minttl = ULONG_MAX;
|
unsigned long ttl, minttl;
|
||||||
int i, found_soa = 0;
|
int i, j;
|
||||||
|
size_t name_len, soa_len, len;
|
||||||
|
union all_addr addr;
|
||||||
|
|
||||||
/* first move to NS section and find TTL from any SOA section */
|
/* first move to NS section and find TTL from SOA RR */
|
||||||
if (!(p = skip_questions(header, qlen)) ||
|
if (!(p = skip_questions(header, qlen)) ||
|
||||||
!(p = do_doctor(p, ntohs(header->ancount), header, qlen, doctored)))
|
!(p = skip_section(p, ntohs(header->ancount), header, qlen)))
|
||||||
return 0; /* bad packet */
|
return 0; /* bad packet */
|
||||||
|
|
||||||
for (i = ntohs(header->nscount); i != 0; i--)
|
name_len = strlen(name);
|
||||||
|
|
||||||
|
if (substring)
|
||||||
|
*substring = name_len;
|
||||||
|
|
||||||
|
if (ttlp)
|
||||||
|
*ttlp = daemon->neg_ttl;
|
||||||
|
|
||||||
|
for (i = 0; i < ntohs(header->nscount); i++)
|
||||||
{
|
{
|
||||||
if (!(p = skip_name(p, header, qlen, 10)))
|
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
|
||||||
return 0; /* bad packet */
|
return 0; /* bad packet */
|
||||||
|
|
||||||
GETSHORT(qtype, p);
|
GETSHORT(qtype, p);
|
||||||
@@ -459,40 +488,112 @@ static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
|
|||||||
GETLONG(ttl, p);
|
GETLONG(ttl, p);
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p);
|
||||||
|
|
||||||
|
psave = p;
|
||||||
|
|
||||||
if ((qclass == C_IN) && (qtype == T_SOA))
|
if ((qclass == C_IN) && (qtype == T_SOA))
|
||||||
{
|
{
|
||||||
found_soa = 1;
|
soa_len = strlen(daemon->workspacename);
|
||||||
if (ttl < minttl)
|
|
||||||
minttl = ttl;
|
|
||||||
|
|
||||||
/* MNAME */
|
/* SOA must be for the name we're interested in. */
|
||||||
if (!(p = skip_name(p, header, qlen, 0)))
|
if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0)
|
||||||
|
{
|
||||||
|
int prefix = name_len - soa_len;
|
||||||
|
|
||||||
|
if (!no_cache)
|
||||||
|
{
|
||||||
|
if (!(addr.rrblock.rrdata = blockdata_alloc(NULL, 0)))
|
||||||
return 0;
|
return 0;
|
||||||
/* RNAME */
|
addr.rrblock.rrtype = T_SOA;
|
||||||
if (!(p = skip_name(p, header, qlen, 20)))
|
addr.rrblock.datalen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < 2; j++) /* MNAME, RNAME */
|
||||||
|
{
|
||||||
|
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
|
||||||
|
{
|
||||||
|
if (!no_cache)
|
||||||
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!no_cache)
|
||||||
|
{
|
||||||
|
len = to_wire(daemon->workspacename);
|
||||||
|
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
|
||||||
|
{
|
||||||
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.rrblock.datalen += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p, qlen, 20))
|
||||||
|
{
|
||||||
|
if (!no_cache)
|
||||||
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rest of RR */
|
||||||
|
if (!no_cache && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
|
||||||
|
{
|
||||||
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.rrblock.datalen += 20;
|
||||||
|
|
||||||
|
if (!no_cache)
|
||||||
|
{
|
||||||
|
int secflag = 0;
|
||||||
|
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
|
||||||
|
{
|
||||||
|
secflag = F_DNSSECOK;
|
||||||
|
|
||||||
|
/* limit TTL based on signature. */
|
||||||
|
if (daemon->rr_status[i + ntohs(header->ancount)] < ttl)
|
||||||
|
ttl = daemon->rr_status[i + ntohs(header->ancount)];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!cache_insert(name + prefix, &addr, C_IN, now, ttl, F_FORWARD | F_RR | F_KEYTAG | secflag))
|
||||||
|
{
|
||||||
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p += 16; /* SERIAL REFRESH RETRY EXPIRE */
|
p += 16; /* SERIAL REFRESH RETRY EXPIRE */
|
||||||
|
|
||||||
GETLONG(ttl, p); /* minTTL */
|
GETLONG(minttl, p); /* minTTL */
|
||||||
if (ttl < minttl)
|
if (ttl < minttl)
|
||||||
minttl = ttl;
|
minttl = ttl;
|
||||||
|
|
||||||
|
if (substring)
|
||||||
|
*substring = prefix;
|
||||||
|
|
||||||
|
if (ttlp)
|
||||||
|
*ttlp = minttl;
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
else if (!ADD_RDLEN(header, p, qlen, rdlen))
|
}
|
||||||
|
|
||||||
|
p = psave;
|
||||||
|
|
||||||
|
if (!ADD_RDLEN(header, p, qlen, rdlen))
|
||||||
return 0; /* bad packet */
|
return 0; /* bad packet */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rewrite addresses in additional section too */
|
|
||||||
if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored))
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!found_soa)
|
|
||||||
minttl = daemon->neg_ttl;
|
|
||||||
|
|
||||||
return minttl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print TXT reply to log */
|
/* Print TXT reply to log */
|
||||||
static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
|
static int log_txt(char *name, unsigned char *p, const int ardlen, int flag)
|
||||||
{
|
{
|
||||||
unsigned char *p1 = p;
|
unsigned char *p1 = p;
|
||||||
|
|
||||||
@@ -514,7 +615,7 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
|
|||||||
}
|
}
|
||||||
|
|
||||||
*p3 = 0;
|
*p3 = 0;
|
||||||
log_query(secflag | F_FORWARD, name, NULL, (char*)p1, 0);
|
log_query(flag, name, NULL, (char*)p1, 0);
|
||||||
/* restore */
|
/* restore */
|
||||||
memmove(p1 + 1, p1, i);
|
memmove(p1 + 1, p1, i);
|
||||||
*p1 = len;
|
*p1 = len;
|
||||||
@@ -531,10 +632,10 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
|
|||||||
*/
|
*/
|
||||||
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
|
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
|
||||||
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
|
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
|
||||||
int no_cache_dnssec, int secure, int *doctored)
|
int no_cache_dnssec, int secure)
|
||||||
{
|
{
|
||||||
unsigned char *p, *p1, *endrr, *namep;
|
unsigned char *p, *p1, *endrr, *namep;
|
||||||
int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
|
int j, qtype, qclass, aqtype, aqclass, ardlen, res;
|
||||||
unsigned long ttl = 0;
|
unsigned long ttl = 0;
|
||||||
union all_addr addr;
|
union all_addr addr;
|
||||||
#ifdef HAVE_IPSET
|
#ifdef HAVE_IPSET
|
||||||
@@ -557,25 +658,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
|
|
||||||
cache_start_insert();
|
cache_start_insert();
|
||||||
|
|
||||||
/* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */
|
|
||||||
if (daemon->doctors || option_bool(OPT_DNSSEC_VALID))
|
|
||||||
{
|
|
||||||
searched_soa = 1;
|
|
||||||
ttl = find_soa(header, qlen, doctored);
|
|
||||||
|
|
||||||
if (*doctored)
|
|
||||||
{
|
|
||||||
if (secure)
|
|
||||||
return 0;
|
|
||||||
#ifdef HAVE_DNSSEC
|
|
||||||
if (option_bool(OPT_DNSSEC_VALID))
|
|
||||||
for (j = 0; j < ntohs(header->ancount); j++)
|
|
||||||
if (daemon->rr_status[j] != 0)
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namep = p = (unsigned char *)(header+1);
|
namep = p = (unsigned char *)(header+1);
|
||||||
|
|
||||||
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
|
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
|
||||||
@@ -624,7 +706,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR))
|
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR))
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
|
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
|
||||||
{
|
{
|
||||||
/* validated RR anywhere in CNAME chain, don't cache. */
|
/* validated RR anywhere in CNAME chain, don't cache. */
|
||||||
if (cname_short || aqtype == T_CNAME)
|
if (cname_short || aqtype == T_CNAME)
|
||||||
@@ -673,17 +755,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
|
|
||||||
if (!found && !option_bool(OPT_NO_NEG))
|
if (!found && !option_bool(OPT_NO_NEG))
|
||||||
{
|
{
|
||||||
if (!searched_soa)
|
/* For reverse records, we use the name field to store the SOA name. */
|
||||||
{
|
int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
|
||||||
searched_soa = 1;
|
|
||||||
ttl = find_soa(header, qlen, doctored);
|
|
||||||
}
|
|
||||||
|
|
||||||
flags |= F_NEG | (secure ? F_DNSSECOK : 0);
|
flags |= F_NEG | (secure ? F_DNSSECOK : 0);
|
||||||
if (name_encoding && ttl)
|
if (name_encoding && ttl)
|
||||||
{
|
{
|
||||||
flags |= F_REVERSE | name_encoding;
|
flags |= F_REVERSE | name_encoding;
|
||||||
cache_insert(NULL, &addr, C_IN, now, ttl, flags);
|
if (!have_soa)
|
||||||
|
flags |= F_NO_RR; /* Marks no SOA found. */
|
||||||
|
cache_insert(name + substring, &addr, C_IN, now, ttl, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
|
log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
|
||||||
@@ -705,7 +786,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
addrlen = IN6ADDRSZ;
|
addrlen = IN6ADDRSZ;
|
||||||
flags |= F_IPV6;
|
flags |= F_IPV6;
|
||||||
}
|
}
|
||||||
else if (qtype != T_CNAME && (qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype)))
|
else if (qtype != T_CNAME &&
|
||||||
|
(qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype) || rr_on_list(daemon->cache_rr, T_ANY)))
|
||||||
flags |= F_RR;
|
flags |= F_RR;
|
||||||
else
|
else
|
||||||
insert = 0; /* NOTE: do not cache data from CNAME queries. */
|
insert = 0; /* NOTE: do not cache data from CNAME queries. */
|
||||||
@@ -732,17 +814,18 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
GETSHORT(ardlen, p1);
|
GETSHORT(ardlen, p1);
|
||||||
endrr = p1+ardlen;
|
endrr = p1+ardlen;
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, endrr, qlen, 0))
|
||||||
|
return 2; /* bad packet */
|
||||||
|
|
||||||
/* Not what we're looking for? */
|
/* Not what we're looking for? */
|
||||||
if (aqclass != C_IN || res == 2)
|
if (aqclass != C_IN || res == 2)
|
||||||
{
|
{
|
||||||
p1 = endrr;
|
p1 = endrr;
|
||||||
if (!CHECK_LEN(header, p1, qlen, 0))
|
|
||||||
return 2; /* bad packet */
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
|
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
|
||||||
{
|
{
|
||||||
secflag = F_DNSSECOK;
|
secflag = F_DNSSECOK;
|
||||||
|
|
||||||
@@ -787,7 +870,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
|
|
||||||
found = 1;
|
found = 1;
|
||||||
}
|
}
|
||||||
else if (aqtype != qtype)
|
else if (qtype == T_ANY || aqtype != qtype)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
|
if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
|
||||||
@@ -838,7 +921,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
{
|
{
|
||||||
/* Copy the rest of the RR and end. */
|
/* Copy the rest of the RR and end. */
|
||||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
|
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;
|
addr.rrblock.datalen += endrr - p1;
|
||||||
}
|
}
|
||||||
else if (desc == 0)
|
else if (desc == 0)
|
||||||
@@ -935,26 +1021,17 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
blockdata_free(addr.rrblock.rrdata);
|
blockdata_free(addr.rrblock.rrdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aqtype == T_TXT)
|
|
||||||
{
|
|
||||||
if (!CHECK_LEN(header, p1, qlen, ardlen))
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
log_txt(name, p1, ardlen, secflag | F_UPSTREAM);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int negflag = F_UPSTREAM;
|
|
||||||
|
|
||||||
/* We're filtering this RRtype. It will be removed from the
|
/* We're filtering this RRtype. It will be removed from the
|
||||||
returned packet in process_reply() but gets cached here anyway
|
returned packet in process_reply() but gets cached here anyway
|
||||||
and will be filtered again on the way out of the cache. Here,
|
and will be filtered again on the way out of the cache. Here,
|
||||||
we just need to alter the logging. */
|
we just need to alter the logging. */
|
||||||
if (rr_on_list(daemon->filter_rr, qtype))
|
if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype))
|
||||||
negflag = F_NEG | F_CONFIG;
|
secflag = F_NEG | F_CONFIG;
|
||||||
|
|
||||||
log_query(negflag | flags | F_FORWARD | secflag, name, &addr, NULL, aqtype);
|
if (aqtype == T_TXT)
|
||||||
}
|
log_txt(name, p1, ardlen, flags | F_FORWARD | F_UPSTREAM | secflag);
|
||||||
|
else
|
||||||
|
log_query(flags | F_FORWARD | F_UPSTREAM | secflag, name, &addr, NULL, aqtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
p1 = endrr;
|
p1 = endrr;
|
||||||
@@ -974,22 +1051,24 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
|
|
||||||
log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0);
|
log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0);
|
||||||
|
|
||||||
if (!searched_soa)
|
if (insert && !option_bool(OPT_NO_NEG))
|
||||||
{
|
{
|
||||||
searched_soa = 1;
|
int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
|
||||||
ttl = find_soa(header, qlen, doctored);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If there's no SOA to get the TTL from, but there is a CNAME
|
/* If there's no SOA to get the TTL from, but there is a CNAME
|
||||||
pointing at this, inherit its TTL */
|
pointing at this, inherit its TTL */
|
||||||
if (insert && !option_bool(OPT_NO_NEG) && (ttl || cpp))
|
if (ttl || cpp)
|
||||||
{
|
{
|
||||||
if (ttl == 0)
|
if (!ttl)
|
||||||
ttl = cttl;
|
ttl = cttl;
|
||||||
|
|
||||||
if (flags & F_RR)
|
addr.rrdata.datalen = substring;
|
||||||
addr.rrdata.rrtype = qtype;
|
addr.rrdata.rrtype = qtype;
|
||||||
|
|
||||||
|
if (!have_soa)
|
||||||
|
flags |= F_NO_RR; /* Marks no SOA found. */
|
||||||
|
}
|
||||||
|
|
||||||
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
|
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
|
||||||
if (newc && cpp)
|
if (newc && cpp)
|
||||||
{
|
{
|
||||||
@@ -1001,15 +1080,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header->hb3 & HB3_TC)
|
/* Don't cache replies from non-recursive nameservers, since we may get a
|
||||||
log_query(F_UPSTREAM, NULL, NULL, "truncated", 0);
|
|
||||||
|
|
||||||
/* Don't put stuff from a truncated packet into the cache.
|
|
||||||
Don't cache replies from non-recursive nameservers, since we may get a
|
|
||||||
reply containing a CNAME but not its target, even though the target
|
reply containing a CNAME but not its target, even though the target
|
||||||
does exist. */
|
does exist. */
|
||||||
if (!(header->hb3 & HB3_TC) &&
|
if (!(header->hb4 & HB4_CD) &&
|
||||||
!(header->hb4 & HB4_CD) &&
|
|
||||||
(header->hb4 & HB4_RA) &&
|
(header->hb4 & HB4_RA) &&
|
||||||
!no_cache_dnssec)
|
!no_cache_dnssec)
|
||||||
cache_end_insert();
|
cache_end_insert();
|
||||||
@@ -1242,8 +1316,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu
|
|||||||
GETSHORT(qtype, p);
|
GETSHORT(qtype, p);
|
||||||
GETSHORT(qclass, p);
|
GETSHORT(qclass, p);
|
||||||
GETLONG(ttl, p);
|
GETLONG(ttl, p);
|
||||||
GETSHORT(rdlen, p);
|
GETSHORT(rdlen, p)
|
||||||
|
|
||||||
if (ttlp)
|
if (ttlp)
|
||||||
*ttlp = ttl;
|
*ttlp = ttl;
|
||||||
|
|
||||||
@@ -1296,8 +1369,9 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
|
|||||||
/* Found a bogus address. Insert that info here, since there no SOA record
|
/* Found a bogus address. Insert that info here, since there no SOA record
|
||||||
to get the ttl from in the normal processing */
|
to get the ttl from in the normal processing */
|
||||||
cache_start_insert();
|
cache_start_insert();
|
||||||
cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN);
|
cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | F_NXDOMAIN);
|
||||||
cache_end_insert();
|
cache_end_insert();
|
||||||
|
log_query(F_CONFIG | F_FORWARD | F_NEG | F_NXDOMAIN, name, NULL, NULL, 0);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -1500,12 +1574,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
union all_addr addr;
|
union all_addr addr;
|
||||||
int nameoffset;
|
int nameoffset;
|
||||||
unsigned short flag;
|
unsigned short flag;
|
||||||
int q, ans, anscount = 0, addncount = 0;
|
int ans, anscount = 0, nscount = 0, addncount = 0;
|
||||||
struct crec *crecp;
|
struct crec *crecp, *soa_lookup = NULL;
|
||||||
int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1;
|
int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1;
|
||||||
struct mx_srv_record *rec;
|
struct mx_srv_record *rec;
|
||||||
size_t len;
|
size_t len;
|
||||||
int rd_bit = (header->hb3 & HB3_RD);
|
int rd_bit = (header->hb3 & HB3_RD);
|
||||||
|
int count = 255; /* catch loops */
|
||||||
|
|
||||||
if (stale)
|
if (stale)
|
||||||
*stale = 0;
|
*stale = 0;
|
||||||
@@ -1514,7 +1589,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
*filtered = 0;
|
*filtered = 0;
|
||||||
|
|
||||||
/* never answer queries with RD unset, to avoid cache snooping. */
|
/* never answer queries with RD unset, to avoid cache snooping. */
|
||||||
if (ntohs(header->ancount) != 0 ||
|
if ( ntohs(header->qdcount) != 1 ||
|
||||||
|
ntohs(header->ancount) != 0 ||
|
||||||
ntohs(header->nscount) != 0 ||
|
ntohs(header->nscount) != 0 ||
|
||||||
ntohs(header->qdcount) == 0 ||
|
ntohs(header->qdcount) == 0 ||
|
||||||
OPCODE(header) != QUERY )
|
OPCODE(header) != QUERY )
|
||||||
@@ -1534,10 +1610,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
/* now process each question, answers go in RRs after the question */
|
/* now process each question, answers go in RRs after the question */
|
||||||
p = (unsigned char *)(header+1);
|
p = (unsigned char *)(header+1);
|
||||||
|
|
||||||
for (q = ntohs(header->qdcount); q != 0; q--)
|
|
||||||
{
|
|
||||||
int count = 255; /* catch loops */
|
|
||||||
|
|
||||||
/* save pointer to name for copying into answers */
|
/* save pointer to name for copying into answers */
|
||||||
nameoffset = p - (unsigned char *)header;
|
nameoffset = p - (unsigned char *)header;
|
||||||
|
|
||||||
@@ -1564,6 +1636,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
stale_flag = F_STALE;
|
stale_flag = F_STALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (crecp->flags & F_NEG)
|
||||||
|
soa_lookup = crecp;
|
||||||
|
|
||||||
if (crecp->flags & F_NXDOMAIN)
|
if (crecp->flags & F_NXDOMAIN)
|
||||||
{
|
{
|
||||||
if (qtype == T_CNAME)
|
if (qtype == T_CNAME)
|
||||||
@@ -1768,6 +1843,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
if (crecp->flags & F_NXDOMAIN)
|
if (crecp->flags & F_NXDOMAIN)
|
||||||
nxdomain = 1;
|
nxdomain = 1;
|
||||||
log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0);
|
log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0);
|
||||||
|
soa_lookup = crecp;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1872,13 +1948,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((crecp = cache_find_by_name(NULL, name, now, flag | F_NXDOMAIN)))
|
if ((crecp = cache_find_by_name(NULL, name, now, flag)))
|
||||||
{
|
{
|
||||||
int localise = 0;
|
int localise = 0;
|
||||||
|
|
||||||
/* See if a putative address is on the network from which we received
|
/* See if a putative address is on the network from which we received
|
||||||
the query, is so we'll filter other answers. */
|
the query, is so we'll filter other answers. */
|
||||||
if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
|
if (!(crecp->flags & F_NEG) && local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
|
||||||
{
|
{
|
||||||
struct crec *save = crecp;
|
struct crec *save = crecp;
|
||||||
do {
|
do {
|
||||||
@@ -1918,7 +1994,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
if (!(crecp->flags & (F_HOSTS | F_DHCP)))
|
if (!(crecp->flags & (F_HOSTS | F_DHCP)))
|
||||||
auth = 0;
|
auth = 0;
|
||||||
|
|
||||||
if (rr_on_list(daemon->filter_rr, qtype) &&
|
if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype) &&
|
||||||
!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG)))
|
!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG)))
|
||||||
{
|
{
|
||||||
/* We have a cached answer but we're filtering it. */
|
/* We have a cached answer but we're filtering it. */
|
||||||
@@ -1931,14 +2007,18 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
*filtered = 1;
|
*filtered = 1;
|
||||||
}
|
}
|
||||||
else if (crecp->flags & F_NEG)
|
else if (crecp->flags & F_NEG)
|
||||||
|
{
|
||||||
|
if (qtype != T_ANY)
|
||||||
{
|
{
|
||||||
ans = 1;
|
ans = 1;
|
||||||
auth = 0;
|
auth = 0;
|
||||||
|
soa_lookup = crecp;
|
||||||
if (crecp->flags & F_NXDOMAIN)
|
if (crecp->flags & F_NXDOMAIN)
|
||||||
nxdomain = 1;
|
nxdomain = 1;
|
||||||
|
|
||||||
log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
|
log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* If we are returning local answers depending on network,
|
/* If we are returning local answers depending on network,
|
||||||
@@ -2102,12 +2182,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
|
|
||||||
if (flags & F_NXDOMAIN)
|
if (flags & F_NXDOMAIN)
|
||||||
nxdomain = 1;
|
nxdomain = 1;
|
||||||
else if (rr_on_list(daemon->filter_rr, qtype))
|
else if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype))
|
||||||
flags |= F_NEG | F_CONFIG;
|
flags |= F_NEG | F_CONFIG;
|
||||||
|
|
||||||
auth = 0;
|
auth = 0;
|
||||||
ans = 1;
|
ans = 1;
|
||||||
|
|
||||||
|
if (flags & F_NEG)
|
||||||
|
soa_lookup = crecp;
|
||||||
|
|
||||||
if (!(flags & F_NEG))
|
if (!(flags & F_NEG))
|
||||||
{
|
{
|
||||||
if (flags & F_KEYTAG)
|
if (flags & F_KEYTAG)
|
||||||
@@ -2144,7 +2227,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!ans && rr_on_list(daemon->filter_rr, qtype))
|
if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype))
|
||||||
{
|
{
|
||||||
/* We don't have a cached answer and when we get an answer from upstream we're going to
|
/* We don't have a cached answer and when we get an answer from upstream we're going to
|
||||||
filter it anyway. If we have a cached answer for the domain for another RRtype then
|
filter it anyway. If we have a cached answer for the domain for another RRtype then
|
||||||
@@ -2168,6 +2251,40 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
|
|
||||||
if (!ans)
|
if (!ans)
|
||||||
return 0; /* failed to answer a question */
|
return 0; /* failed to answer a question */
|
||||||
|
|
||||||
|
/* We found a negative record. See if we have an SOA record to
|
||||||
|
return in the AUTH section.
|
||||||
|
|
||||||
|
For FORWARD NEG records, the addr.rrdata.datalen field of the othewise
|
||||||
|
empty addr is used to held an offset in to the name which yields the SOA
|
||||||
|
name. For REVERSE NEG records, the otherwise empty name field holds the
|
||||||
|
SOA name. If soa_name has zero length, then no SOA is known. soa_lookup
|
||||||
|
MUST be a neg record here.
|
||||||
|
|
||||||
|
If the F_NO_RR flag is set, there was no SOA record supplied with the RR. */
|
||||||
|
if (soa_lookup && !(soa_lookup->flags & F_NO_RR))
|
||||||
|
{
|
||||||
|
char *soa_name = soa_lookup->flags & F_REVERSE ? cache_get_name(soa_lookup) : name + soa_lookup->addr.rrdata.datalen;
|
||||||
|
|
||||||
|
crecp = NULL;
|
||||||
|
while ((crecp = cache_find_by_name(crecp, soa_name, now, F_RR)))
|
||||||
|
if (crecp->addr.rrblock.rrtype == T_SOA)
|
||||||
|
{
|
||||||
|
char *rrdata;
|
||||||
|
|
||||||
|
if (!(crecp->flags & F_NEG) &&
|
||||||
|
(rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL)) &&
|
||||||
|
add_resource_record(header, limit, &trunc, 0, &ansp,
|
||||||
|
crec_ttl(crecp, now), NULL, T_SOA, C_IN, "t",
|
||||||
|
soa_name, crecp->addr.rrblock.datalen, rrdata))
|
||||||
|
{
|
||||||
|
nscount++;
|
||||||
|
|
||||||
|
if (!(crecp->flags & F_DNSSECOK))
|
||||||
|
sec_data = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create an additional data section, for stuff in SRV and MX record replies. */
|
/* create an additional data section, for stuff in SRV and MX record replies. */
|
||||||
@@ -2191,7 +2308,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
if (add_resource_record(header, limit, NULL, rec->offset, &ansp,
|
if (add_resource_record(header, limit, NULL, rec->offset, &ansp,
|
||||||
crec_ttl(crecp, now), NULL, type, C_IN,
|
crec_ttl(crecp, now), NULL, type, C_IN,
|
||||||
crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr))
|
crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr))
|
||||||
|
{
|
||||||
addncount++;
|
addncount++;
|
||||||
|
if (!(crecp->flags & F_DNSSECOK))
|
||||||
|
sec_data = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2216,7 +2337,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
else
|
else
|
||||||
SET_RCODE(header, NOERROR); /* no error */
|
SET_RCODE(header, NOERROR); /* no error */
|
||||||
header->ancount = htons(anscount);
|
header->ancount = htons(anscount);
|
||||||
header->nscount = htons(0);
|
header->nscount = htons(nscount);
|
||||||
header->arcount = htons(addncount);
|
header->arcount = htons(addncount);
|
||||||
|
|
||||||
len = ansp - (unsigned char *)header;
|
len = ansp - (unsigned char *)header;
|
||||||
|
|||||||
@@ -213,6 +213,14 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode)
|
|||||||
if (i < ntohs(header->ancount) && type == qtype && class == qclass)
|
if (i < ntohs(header->ancount) && type == qtype && class == qclass)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (qtype == T_ANY && rr_on_list(daemon->filter_rr, T_ANY))
|
||||||
|
{
|
||||||
|
/* Filter replies to ANY queries in the spirit of
|
||||||
|
RFC RFC 8482 para 4.3 */
|
||||||
|
if (class != C_IN ||
|
||||||
|
type == T_A || type == T_AAAA || type == T_MX || type == T_CNAME)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Only looking at answer section now. */
|
/* Only looking at answer section now. */
|
||||||
|
|||||||
+1
-1
@@ -119,7 +119,7 @@ int rr_on_list(struct rrlist *list, unsigned short rr)
|
|||||||
{
|
{
|
||||||
while (list)
|
while (list)
|
||||||
{
|
{
|
||||||
if (list->rr == rr || list->rr == T_ANY)
|
if (list->rr == rr)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
list = list->next;
|
list = list->next;
|
||||||
|
|||||||
Reference in new issue
Block a user