mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-06 15:51:41 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a857daa351 | ||
|
|
f01d7be6c6 | ||
|
|
d387380a25 | ||
|
|
f2e4c277c4 | ||
|
|
5107ace14a | ||
|
|
7b1eae4f50 | ||
|
|
c152dc8492 | ||
|
|
7bcca0060f | ||
|
|
d68c2ca2b7 | ||
|
|
de73a497ca | ||
|
|
e3ec15af10 | ||
|
|
dac74312da | ||
|
|
2ecd9bd5c0 | ||
|
|
a0ab18f6eb | ||
|
|
ebe95a831f | ||
|
|
ee4158678a | ||
|
|
83349b8aa4 | ||
|
|
7fa836e105 | ||
|
|
1633e30834 | ||
|
|
c8ca33f810 | ||
|
|
e243c072b5 | ||
|
|
da4f372271 | ||
|
|
610e782a29 | ||
|
|
854cf26907 | ||
|
|
bb201c211a | ||
|
|
12fae49fff | ||
|
|
fd372273bd | ||
|
|
b98d22c191 | ||
|
|
160f6507c3 | ||
|
|
613d6c5249 |
No files matched your search
@@ -17,6 +17,56 @@ version 2.69
|
|||||||
dnsmasq, [fe80::] with the link-local address.
|
dnsmasq, [fe80::] with the link-local address.
|
||||||
Thanks to Tsachi Kimeldorfer for championing this.
|
Thanks to Tsachi Kimeldorfer for championing this.
|
||||||
|
|
||||||
|
DNSSEC validation and caching. Dnsmasq needs to be
|
||||||
|
compiled with this enabled, with
|
||||||
|
|
||||||
|
make dnsmasq COPTS=-DHAVE_DNSSEC
|
||||||
|
|
||||||
|
this add dependencies on the nettle crypto library and the
|
||||||
|
gmp maths library. It's possible to have these linked
|
||||||
|
statically with
|
||||||
|
|
||||||
|
make dnsmasq COPTS='-DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC'
|
||||||
|
|
||||||
|
which bloats the dnsmasq binary to over a megabyte, but
|
||||||
|
saves the size of the shared libraries which are five
|
||||||
|
times that size.
|
||||||
|
To enable, DNSSEC, you will need a set of
|
||||||
|
trust-anchors. Now that the TLDs are signed, this can be
|
||||||
|
the keys for the root zone, and for convenience they are
|
||||||
|
included in trust-anchors.conf in the dnsmasq
|
||||||
|
distribution. You should of course check that these are
|
||||||
|
legitimate and up-to-date. So, adding
|
||||||
|
|
||||||
|
conf-file=/path/to/trust-anchors.conf
|
||||||
|
dnssec
|
||||||
|
|
||||||
|
to your config is all thats needed to get things
|
||||||
|
working. The upstream nameservers have to be DNSSEC-capable
|
||||||
|
too, of course. Many ISP nameservers aren't, but the
|
||||||
|
Google public nameservers (8.8.8.8 and 8.8.4.4) are.
|
||||||
|
When DNSSEC is configured, dnsmasq validates any queries
|
||||||
|
for domains which are signed. Query results which are
|
||||||
|
bogus are replaced with SERVFAIL replies, and results
|
||||||
|
which are correctly signed have the AD bit set. In
|
||||||
|
addition, and just as importantly, dnsmasq supplies
|
||||||
|
correct DNSSEC information to clients which are doing
|
||||||
|
their own validation, and caches DNSKEY, DS and RRSIG
|
||||||
|
records, which significantly improve the performance of
|
||||||
|
downstream validators. Setting --log-queries will show
|
||||||
|
DNSSEC in action.
|
||||||
|
|
||||||
|
The development of DNSSEC in dnsmasq was started by
|
||||||
|
Giovanni Bajo, to whom huge thanks are owed. It has been
|
||||||
|
supported by Comcast, whose techfund grant has allowed for
|
||||||
|
an invaluable period of full-time work to get it to
|
||||||
|
a workable state.
|
||||||
|
|
||||||
|
Add --rev-server. Thanks to Dave Taht for suggesting this.
|
||||||
|
|
||||||
|
Add --servers-file. Allows dynamic update of upstream servers
|
||||||
|
full access to configuration.
|
||||||
|
|
||||||
|
|
||||||
version 2.68
|
version 2.68
|
||||||
Use random addresses for DHCPv6 temporary address
|
Use random addresses for DHCPv6 temporary address
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CON
|
|||||||
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.1`
|
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.1`
|
||||||
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`
|
||||||
nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed`
|
nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed`
|
||||||
|
gmp_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --copy -lgmp`
|
||||||
sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
|
sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
|
||||||
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
|
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ all : $(BUILDDIR)
|
|||||||
@cd $(BUILDDIR) && $(MAKE) \
|
@cd $(BUILDDIR) && $(MAKE) \
|
||||||
top="$(top)" \
|
top="$(top)" \
|
||||||
build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags)" \
|
build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags)" \
|
||||||
build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs)" \
|
build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs)" \
|
||||||
-f $(top)/Makefile dnsmasq
|
-f $(top)/Makefile dnsmasq
|
||||||
|
|
||||||
mostly_clean :
|
mostly_clean :
|
||||||
@@ -101,7 +102,7 @@ all-i18n : $(BUILDDIR)
|
|||||||
top="$(top)" \
|
top="$(top)" \
|
||||||
i18n=-DLOCALEDIR=\'\"$(LOCALEDIR)\"\' \
|
i18n=-DLOCALEDIR=\'\"$(LOCALEDIR)\"\' \
|
||||||
build_cflags="$(version) $(dbus_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) `$(PKG_CONFIG) --cflags libidn`" \
|
build_cflags="$(version) $(dbus_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) `$(PKG_CONFIG) --cflags libidn`" \
|
||||||
build_libs="$(dbus_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) `$(PKG_CONFIG) --libs libidn`" \
|
build_libs="$(dbus_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs) `$(PKG_CONFIG) --libs libidn`" \
|
||||||
-f $(top)/Makefile dnsmasq
|
-f $(top)/Makefile dnsmasq
|
||||||
for f in `cd $(PO); echo *.po`; do \
|
for f in `cd $(PO); echo *.po`; do \
|
||||||
cd $(top) && cd $(BUILDDIR) && $(MAKE) top="$(top)" -f $(top)/Makefile $${f%.po}.mo; \
|
cd $(top) && cd $(BUILDDIR) && $(MAKE) top="$(top)" -f $(top)/Makefile $${f%.po}.mo; \
|
||||||
|
|||||||
+16
-11
@@ -9,19 +9,24 @@ shift
|
|||||||
|
|
||||||
in=`cat`
|
in=`cat`
|
||||||
|
|
||||||
if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
|
|
||||||
echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
|
|
||||||
if [ $op = "--libs" ]; then
|
|
||||||
pkg=`$pkg --static $op $*`
|
|
||||||
echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \
|
if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \
|
||||||
echo $in | grep $search >/dev/null 2>&1; then
|
echo $in | grep $search >/dev/null 2>&1; then
|
||||||
pkg=`$pkg $op $*`
|
|
||||||
echo "$pkg"
|
|
||||||
|
|
||||||
|
if [ $op = "--copy" ]; then
|
||||||
|
pkg="$*"
|
||||||
|
else
|
||||||
|
pkg=`$pkg $op $*`
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
|
||||||
|
echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
|
||||||
|
if [ $op = "--libs" ] || [ $op = "--copy" ]; then
|
||||||
|
echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic"
|
||||||
|
else
|
||||||
|
echo "$pkg"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$pkg"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
From: Jesse Glick <jglick@cloudbees.com>
|
||||||
|
To: dnsmasq-discuss@lists.thekelleys.org.uk
|
||||||
|
Subject: Re: [Dnsmasq-discuss] Ability to delegate to one server but fall
|
||||||
|
back to another after NXDOMAIN?
|
||||||
|
|
||||||
|
|
||||||
|
On Wed, Jan 15, 2014 at 12:30 PM, Simon Kelley <simon@thekelleys.org.uk> wrote:
|
||||||
|
> > There's a (very old) patch in contrib/try-all-ns that would make a starting point
|
||||||
|
This does not apply against trunk, so I tried to rework it. The
|
||||||
|
following appears to do what I expect:
|
||||||
|
|
||||||
|
diff --git a/src/forward.c b/src/forward.c
|
||||||
|
index 8167229..76070b5 100644
|
||||||
|
--- a/src/forward.c
|
||||||
|
+++ b/src/forward.c
|
||||||
|
@@ -610,7 +610,11 @@ void reply_query(int fd, int family, time_t now)
|
||||||
|
|
||||||
|
if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) &&
|
||||||
|
!option_bool(OPT_ORDER) &&
|
||||||
|
- forward->forwardall == 0)
|
||||||
|
+ forward->forwardall == 0 ||
|
||||||
|
+ /* try each in turn */
|
||||||
|
+ RCODE(header) == NXDOMAIN &&
|
||||||
|
+ option_bool(OPT_ORDER) &&
|
||||||
|
+ server->next != NULL)
|
||||||
|
/* for broken servers, attempt to send to another one. */
|
||||||
|
{
|
||||||
|
unsigned char *pheader;
|
||||||
|
|
||||||
Vendored
+6
@@ -1,3 +1,9 @@
|
|||||||
|
dnsmasq (2.69-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* New upstream.
|
||||||
|
|
||||||
|
-- Simon Kelley <simon@thekelleys.org.uk> Tue, 4 Feb 2014 16:28:12 +0000
|
||||||
|
|
||||||
dnsmasq (2.68-1) unstable; urgency=low
|
dnsmasq (2.68-1) unstable; urgency=low
|
||||||
|
|
||||||
* New upstream. (closes: #730553)
|
* New upstream. (closes: #730553)
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
Source: dnsmasq
|
Source: dnsmasq
|
||||||
Section: net
|
Section: net
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-depends: gettext, libnetfilter-conntrack-dev [linux-any], libidn11-dev, libdbus-1-dev (>=0.61)
|
Build-depends: gettext, libnetfilter-conntrack-dev [linux-any],
|
||||||
|
libidn11-dev, libdbus-1-dev (>=0.61), libgmp-dev, nettle-dev (>=2.4-3)
|
||||||
Maintainer: Simon Kelley <simon@thekelleys.org.uk>
|
Maintainer: Simon Kelley <simon@thekelleys.org.uk>
|
||||||
Standards-Version: 3.9.3
|
Standards-Version: 3.9.3
|
||||||
|
|
||||||
|
|||||||
Vendored
+7
-1
@@ -19,6 +19,10 @@ LDFLAGS = $(shell dpkg-buildflags --get LDFLAGS)
|
|||||||
|
|
||||||
DEB_COPTS = $(COPTS)
|
DEB_COPTS = $(COPTS)
|
||||||
|
|
||||||
|
# The nettle library in Debian is too old to include
|
||||||
|
# ECC support.
|
||||||
|
DEB_COPTS += -DNO_NETTLE_ECC
|
||||||
|
|
||||||
TARGET = install-i18n
|
TARGET = install-i18n
|
||||||
|
|
||||||
DEB_BUILD_ARCH_OS := $(shell dpkg-architecture -qDEB_BUILD_ARCH_OS)
|
DEB_BUILD_ARCH_OS := $(shell dpkg-architecture -qDEB_BUILD_ARCH_OS)
|
||||||
@@ -77,7 +81,7 @@ ifneq (,$(filter uselua,$(DEB_BUILD_OPTIONS)))
|
|||||||
DEB_COPTS += -DHAVE_LUASCRIPT
|
DEB_COPTS += -DHAVE_LUASCRIPT
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter usednssec,$(DEB_BUILD_OPTIONS)))
|
ifeq (,$(filter nodnssec,$(DEB_BUILD_OPTIONS)))
|
||||||
DEB_COPTS += -DHAVE_DNSSEC
|
DEB_COPTS += -DHAVE_DNSSEC
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -126,12 +130,14 @@ binary-arch: checkroot
|
|||||||
-d debian/base/usr/share/doc/$(package) \
|
-d debian/base/usr/share/doc/$(package) \
|
||||||
-d debian/base/usr/share/doc/$(package)/examples \
|
-d debian/base/usr/share/doc/$(package)/examples \
|
||||||
-d debian/base/var/run \
|
-d debian/base/var/run \
|
||||||
|
-d debian/base/usr/share/$(package) \
|
||||||
-d debian/base/var/lib/misc
|
-d debian/base/var/lib/misc
|
||||||
make $(TARGET) PREFIX=/usr DESTDIR=`pwd`/debian/base CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" COPTS="$(DEB_COPTS)" CC=gcc
|
make $(TARGET) PREFIX=/usr DESTDIR=`pwd`/debian/base CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" COPTS="$(DEB_COPTS)" CC=gcc
|
||||||
ifeq (,$(findstring nodocs,$(DEB_BUILD_OPTIONS)))
|
ifeq (,$(findstring nodocs,$(DEB_BUILD_OPTIONS)))
|
||||||
install -m 644 doc.html debian/base/usr/share/doc/$(package)/.
|
install -m 644 doc.html debian/base/usr/share/doc/$(package)/.
|
||||||
install -m 644 setup.html debian/base/usr/share/doc/$(package)/.
|
install -m 644 setup.html debian/base/usr/share/doc/$(package)/.
|
||||||
install -m 644 dnsmasq.conf.example debian/base/usr/share/doc/$(package)/examples/.
|
install -m 644 dnsmasq.conf.example debian/base/usr/share/doc/$(package)/examples/.
|
||||||
|
install -m 644 trust-anchors.conf debian/base/usr/share/$(package)/.
|
||||||
install -m 644 FAQ debian/base/usr/share/doc/$(package)/.
|
install -m 644 FAQ debian/base/usr/share/doc/$(package)/.
|
||||||
gzip -9 debian/base/usr/share/doc/$(package)/FAQ
|
gzip -9 debian/base/usr/share/doc/$(package)/FAQ
|
||||||
install -m 644 CHANGELOG debian/base/usr/share/doc/$(package)/changelog
|
install -m 644 CHANGELOG debian/base/usr/share/doc/$(package)/changelog
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
# Never forward addresses in the non-routed address spaces.
|
# Never forward addresses in the non-routed address spaces.
|
||||||
#bogus-priv
|
#bogus-priv
|
||||||
|
|
||||||
|
# Uncomment these to enable DNSSEC validation and caching:
|
||||||
|
# (Requires dnsmasq to be built with DNSSEC option.)
|
||||||
|
#conf-file=%%PREFIX%%/share/dnsmasq/trust-anchors.conf
|
||||||
|
#dnssec
|
||||||
|
|
||||||
# Uncomment this to filter useless windows-originated DNS requests
|
# Uncomment this to filter useless windows-originated DNS requests
|
||||||
# which can trigger dial-on-demand links needlessly.
|
# which can trigger dial-on-demand links needlessly.
|
||||||
|
|||||||
+30
-6
@@ -13,7 +13,10 @@ Dnsmasq accepts DNS queries and either answers them from a small, local,
|
|||||||
cache or forwards them to a real, recursive, DNS server. It loads the
|
cache or forwards them to a real, recursive, DNS server. It loads the
|
||||||
contents of /etc/hosts so that local hostnames
|
contents of /etc/hosts so that local hostnames
|
||||||
which do not appear in the global DNS can be resolved and also answers
|
which do not appear in the global DNS can be resolved and also answers
|
||||||
DNS queries for DHCP configured hosts. It can also act as the authoritative DNS server for one or more domains, allowing local names to appear in the global DNS.
|
DNS queries for DHCP configured hosts. It can also act as the
|
||||||
|
authoritative DNS server for one or more domains, allowing local names
|
||||||
|
to appear in the global DNS. It can be configured to do DNSSEC
|
||||||
|
validation.
|
||||||
.PP
|
.PP
|
||||||
The dnsmasq DHCP server supports static address assignments and multiple
|
The dnsmasq DHCP server supports static address assignments and multiple
|
||||||
networks. It automatically
|
networks. It automatically
|
||||||
@@ -412,6 +415,14 @@ source address specified but the port may be specified directly as
|
|||||||
part of the source address. Forcing queries to an interface is not
|
part of the source address. Forcing queries to an interface is not
|
||||||
implemented on all platforms supported by dnsmasq.
|
implemented on all platforms supported by dnsmasq.
|
||||||
.TP
|
.TP
|
||||||
|
.B --rev-server=<ip-address>/<prefix-len>,<ipaddr>[#<port>][@<source-ip>|<interface>[#<port>]]
|
||||||
|
This is functionally the same as
|
||||||
|
.B --server,
|
||||||
|
but provides some syntactic sugar to make specifying address-to-name queries easier. For example
|
||||||
|
.B --rev-server=1.2.3.0/24,192.168.0.1
|
||||||
|
is exactly equivalent to
|
||||||
|
.B --server=/3.2.1.in-addr.arpa/192.168.0.1
|
||||||
|
.TP
|
||||||
.B \-A, --address=/<domain>/[domain/]<ipaddr>
|
.B \-A, --address=/<domain>/[domain/]<ipaddr>
|
||||||
Specify an IP address to return for any host in the given domains.
|
Specify an IP address to return for any host in the given domains.
|
||||||
Queries in the domains are never forwarded and always replied to
|
Queries in the domains are never forwarded and always replied to
|
||||||
@@ -587,12 +598,15 @@ validation by clients more efficient. Note that validation by clients is the mos
|
|||||||
clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between
|
clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between
|
||||||
the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC
|
the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC
|
||||||
trust anchors provided, see
|
trust anchors provided, see
|
||||||
.B --dnsskey.
|
.B --trust-anchor.
|
||||||
Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled.
|
Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled.
|
||||||
.TP
|
.TP
|
||||||
.B --dnskey=[<class>],<domain>,<flags>,<algorithm>,<base64-key>
|
.B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
|
||||||
Provide DNSKEY records to act a trust anchors for DNSSEC validation. Typically these will be the keys for root zone,
|
Provide DS records to act a trust anchors for DNSSEC
|
||||||
but trust anchors for limited domains are also possible.
|
validation. Typically these will be the DS record(s) for Zone Signing
|
||||||
|
key(s) of the root zone,
|
||||||
|
but trust anchors for limited domains are also possible. The current
|
||||||
|
root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||||
.TP
|
.TP
|
||||||
.B --proxy-dnssec
|
.B --proxy-dnssec
|
||||||
Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an
|
Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an
|
||||||
@@ -601,7 +615,10 @@ dnsmasq and the upstream servers, and the trustworthiness of the upstream server
|
|||||||
.TP
|
.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 BOGUS replies to SERVFAIL responses.
|
and don't convert replies which do not validate to responses with
|
||||||
|
a return code of SERVFAIL. Note that
|
||||||
|
setting this may affect DNS behaviour in bad ways, it is not an
|
||||||
|
extra-logging flag and should not be set in production.
|
||||||
.TP
|
.TP
|
||||||
.B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]]
|
.B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]]
|
||||||
Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain
|
Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain
|
||||||
@@ -1661,6 +1678,13 @@ files. If extension(s) are given, any files which end in those
|
|||||||
extensions are skipped. Any files whose names end in ~ or start with . or start and end
|
extensions are skipped. Any files whose names end in ~ or start with . or start and end
|
||||||
with # are always skipped. This flag may be given on the command
|
with # are always skipped. This flag may be given on the command
|
||||||
line or in a configuration file.
|
line or in a configuration file.
|
||||||
|
.TP
|
||||||
|
.B --servers-file=<file>
|
||||||
|
A special case of
|
||||||
|
.B --conf-file
|
||||||
|
which differs in two respects. Firstly, only --server and --rev-server are allowed
|
||||||
|
in the configuration file included. Secondly, the file is re-read and the configuration
|
||||||
|
therein is updated when dnsmasq recieves SIGHUP.
|
||||||
.SH CONFIG FILE
|
.SH CONFIG FILE
|
||||||
At startup, dnsmasq reads
|
At startup, dnsmasq reads
|
||||||
.I /etc/dnsmasq.conf,
|
.I /etc/dnsmasq.conf,
|
||||||
|
|||||||
+61
-25
@@ -55,7 +55,9 @@ static const struct {
|
|||||||
{ 41, "OPT" },
|
{ 41, "OPT" },
|
||||||
{ 43, "DS" },
|
{ 43, "DS" },
|
||||||
{ 46, "RRSIG" },
|
{ 46, "RRSIG" },
|
||||||
|
{ 47, "NSEC" },
|
||||||
{ 48, "DNSKEY" },
|
{ 48, "DNSKEY" },
|
||||||
|
{ 50, "NSEC3" },
|
||||||
{ 249, "TKEY" },
|
{ 249, "TKEY" },
|
||||||
{ 250, "TSIG" },
|
{ 250, "TSIG" },
|
||||||
{ 251, "IXFR" },
|
{ 251, "IXFR" },
|
||||||
@@ -446,19 +448,21 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
|
|||||||
int freed_all = flags & F_REVERSE;
|
int freed_all = flags & F_REVERSE;
|
||||||
int free_avail = 0;
|
int free_avail = 0;
|
||||||
|
|
||||||
if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
|
/* Don't log DNSSEC records here, done elsewhere */
|
||||||
ttl = daemon->max_cache_ttl;
|
|
||||||
|
|
||||||
/* Don't log keys here, done elsewhere */
|
|
||||||
if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
|
if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
|
||||||
log_query(flags | F_UPSTREAM, name, addr, NULL);
|
{
|
||||||
|
log_query(flags | F_UPSTREAM, name, addr, NULL);
|
||||||
|
/* Don;t mess with TTL for DNSSEC records. */
|
||||||
|
if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
|
||||||
|
ttl = daemon->max_cache_ttl;
|
||||||
|
}
|
||||||
|
|
||||||
/* if previous insertion failed give up now. */
|
/* if previous insertion failed give up now. */
|
||||||
if (insert_error)
|
if (insert_error)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* First remove any expired entries and entries for the name/address we
|
/* First remove any expired entries and entries for the name/address we
|
||||||
are currently inserting. Fail is we attempt to delete a name from
|
are currently inserting. Fail if we attempt to delete a name from
|
||||||
/etc/hosts or DHCP. */
|
/etc/hosts or DHCP. */
|
||||||
if (!cache_scan_free(name, addr, now, flags))
|
if (!cache_scan_free(name, addr, now, flags))
|
||||||
{
|
{
|
||||||
@@ -592,9 +596,12 @@ void cache_end_insert(void)
|
|||||||
new_chain = NULL;
|
new_chain = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned short prot)
|
struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
|
||||||
{
|
{
|
||||||
struct crec *ans;
|
struct crec *ans;
|
||||||
|
int no_rr = prot & F_NO_RR;
|
||||||
|
|
||||||
|
prot &= ~F_NO_RR;
|
||||||
|
|
||||||
if (crecp) /* iterating */
|
if (crecp) /* iterating */
|
||||||
ans = crecp->next;
|
ans = crecp->next;
|
||||||
@@ -643,7 +650,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!insert)
|
if (!insert && !no_rr)
|
||||||
{
|
{
|
||||||
insert = up;
|
insert = up;
|
||||||
ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
|
ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
|
||||||
@@ -683,7 +690,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
|
struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
|
||||||
time_t now, unsigned short prot)
|
time_t now, unsigned int prot)
|
||||||
{
|
{
|
||||||
struct crec *ans;
|
struct crec *ans;
|
||||||
#ifdef HAVE_IPV6
|
#ifdef HAVE_IPV6
|
||||||
@@ -980,7 +987,7 @@ void cache_reload(void)
|
|||||||
struct cname *a;
|
struct cname *a;
|
||||||
struct interface_name *intr;
|
struct interface_name *intr;
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
struct dnskey *key;
|
struct ds_config *ds;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cache_inserted = cache_live_freed = 0;
|
cache_inserted = cache_live_freed = 0;
|
||||||
@@ -1026,17 +1033,17 @@ void cache_reload(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
for (key = daemon->dnskeys; key; key = key->next)
|
for (ds = daemon->ds; ds; ds = ds->next)
|
||||||
if ((cache = whine_malloc(sizeof(struct crec))) &&
|
if ((cache = whine_malloc(sizeof(struct crec))) &&
|
||||||
(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
|
(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
|
||||||
{
|
{
|
||||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
|
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
|
||||||
cache->name.namep = key->name;
|
cache->name.namep = ds->name;
|
||||||
cache->addr.key.keylen = key->keylen;
|
cache->addr.ds.keylen = ds->digestlen;
|
||||||
cache->addr.key.algo = key->algo;
|
cache->addr.ds.algo = ds->algo;
|
||||||
cache->addr.key.flags = key->flags;
|
cache->addr.ds.keytag = ds->keytag;
|
||||||
cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
|
cache->addr.ds.digest = ds->digest_type;
|
||||||
cache->uid = key->class;
|
cache->uid = ds->class;
|
||||||
cache_hash(cache);
|
cache_hash(cache);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1298,12 +1305,10 @@ void dump_cache(time_t now)
|
|||||||
{
|
{
|
||||||
if (cache->flags & F_DNSKEY)
|
if (cache->flags & F_DNSKEY)
|
||||||
{
|
{
|
||||||
char tp[20];
|
|
||||||
/* RRSIG */
|
/* RRSIG */
|
||||||
querystr("", tp, cache->addr.sig.type_covered);
|
|
||||||
a = daemon->addrbuff;
|
a = daemon->addrbuff;
|
||||||
sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
|
sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
|
||||||
cache->addr.sig.algo, tp);
|
cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1379,14 +1384,45 @@ char *record_source(int index)
|
|||||||
return "<unknown>";
|
return "<unknown>";
|
||||||
}
|
}
|
||||||
|
|
||||||
void querystr(char *desc, char *str, unsigned short type)
|
char *querystr(char *desc, unsigned short type)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
int len = 10; /* strlen("type=xxxxx") */
|
||||||
|
const char *types = NULL;
|
||||||
|
static char *buff = NULL;
|
||||||
|
static int bufflen = 0;
|
||||||
|
|
||||||
sprintf(str, "%s[type=%d]", desc, type);
|
|
||||||
for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
|
for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
|
||||||
if (typestr[i].type == type)
|
if (typestr[i].type == type)
|
||||||
sprintf(str,"%s[%s]", desc, typestr[i].name);
|
{
|
||||||
|
types = typestr[i].name;
|
||||||
|
len = strlen(types);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
len += 3; /* braces, terminator */
|
||||||
|
len += strlen(desc);
|
||||||
|
|
||||||
|
if (!buff || bufflen < len)
|
||||||
|
{
|
||||||
|
if (buff)
|
||||||
|
free(buff);
|
||||||
|
else if (len < 20)
|
||||||
|
len = 20;
|
||||||
|
|
||||||
|
buff = whine_malloc(len);
|
||||||
|
bufflen = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buff)
|
||||||
|
{
|
||||||
|
if (types)
|
||||||
|
sprintf(buff, "%s[%s]", desc, types);
|
||||||
|
else
|
||||||
|
sprintf(buff, "%s[type=%d]", desc, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff ? buff : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
|
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
|
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
|
||||||
#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
|
#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
|
||||||
#define KEYBLOCK_LEN 35 /* choose to mininise fragmentation when storing DNSSEC keys */
|
#define KEYBLOCK_LEN 35 /* choose to mininise fragmentation when storing DNSSEC keys */
|
||||||
|
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
|
||||||
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
||||||
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
||||||
#define FORWARD_TIME 20 /* or 20 seconds */
|
#define FORWARD_TIME 20 /* or 20 seconds */
|
||||||
|
|||||||
+15
-113
@@ -108,108 +108,6 @@ static void remove_watch(DBusWatch *watch, void *data)
|
|||||||
w = data; /* no warning */
|
w = data; /* no warning */
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_update_server(union mysockaddr *addr,
|
|
||||||
union mysockaddr *source_addr,
|
|
||||||
const char *interface,
|
|
||||||
const char *domain)
|
|
||||||
{
|
|
||||||
struct server *serv;
|
|
||||||
|
|
||||||
/* See if there is a suitable candidate, and unmark */
|
|
||||||
for (serv = daemon->servers; serv; serv = serv->next)
|
|
||||||
if ((serv->flags & SERV_FROM_DBUS) &&
|
|
||||||
(serv->flags & SERV_MARK))
|
|
||||||
{
|
|
||||||
if (domain)
|
|
||||||
{
|
|
||||||
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (serv->flags & SERV_HAS_DOMAIN)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
serv->flags &= ~SERV_MARK;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serv && (serv = whine_malloc(sizeof (struct server))))
|
|
||||||
{
|
|
||||||
/* Not found, create a new one. */
|
|
||||||
memset(serv, 0, sizeof(struct server));
|
|
||||||
|
|
||||||
if (domain && !(serv->domain = whine_malloc(strlen(domain)+1)))
|
|
||||||
{
|
|
||||||
free(serv);
|
|
||||||
serv = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
serv->next = daemon->servers;
|
|
||||||
daemon->servers = serv;
|
|
||||||
serv->flags = SERV_FROM_DBUS;
|
|
||||||
if (domain)
|
|
||||||
{
|
|
||||||
strcpy(serv->domain, domain);
|
|
||||||
serv->flags |= SERV_HAS_DOMAIN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serv)
|
|
||||||
{
|
|
||||||
if (interface)
|
|
||||||
strcpy(serv->interface, interface);
|
|
||||||
else
|
|
||||||
serv->interface[0] = 0;
|
|
||||||
|
|
||||||
if (source_addr->in.sin_family == AF_INET &&
|
|
||||||
addr->in.sin_addr.s_addr == 0 &&
|
|
||||||
serv->domain)
|
|
||||||
serv->flags |= SERV_NO_ADDR;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
serv->flags &= ~SERV_NO_ADDR;
|
|
||||||
serv->addr = *addr;
|
|
||||||
serv->source_addr = *source_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mark_dbus(void)
|
|
||||||
{
|
|
||||||
struct server *serv;
|
|
||||||
|
|
||||||
/* mark everything from DBUS */
|
|
||||||
for (serv = daemon->servers; serv; serv = serv->next)
|
|
||||||
if (serv->flags & SERV_FROM_DBUS)
|
|
||||||
serv->flags |= SERV_MARK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanup_dbus()
|
|
||||||
{
|
|
||||||
struct server *serv, *tmp, **up;
|
|
||||||
|
|
||||||
/* unlink and free anything still marked. */
|
|
||||||
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
|
|
||||||
{
|
|
||||||
tmp = serv->next;
|
|
||||||
if (serv->flags & SERV_MARK)
|
|
||||||
{
|
|
||||||
server_gone(serv);
|
|
||||||
*up = serv->next;
|
|
||||||
if (serv->domain)
|
|
||||||
free(serv->domain);
|
|
||||||
free(serv);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
up = &serv->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dbus_read_servers(DBusMessage *message)
|
static void dbus_read_servers(DBusMessage *message)
|
||||||
{
|
{
|
||||||
DBusMessageIter iter;
|
DBusMessageIter iter;
|
||||||
@@ -218,7 +116,7 @@ static void dbus_read_servers(DBusMessage *message)
|
|||||||
|
|
||||||
dbus_message_iter_init(message, &iter);
|
dbus_message_iter_init(message, &iter);
|
||||||
|
|
||||||
mark_dbus();
|
mark_servers(SERV_FROM_DBUS);
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
@@ -289,13 +187,13 @@ static void dbus_read_servers(DBusMessage *message)
|
|||||||
domain = NULL;
|
domain = NULL;
|
||||||
|
|
||||||
if (!skip)
|
if (!skip)
|
||||||
add_update_server(&addr, &source_addr, NULL, domain);
|
add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain);
|
||||||
|
|
||||||
} while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
|
} while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unlink and free anything still marked. */
|
/* unlink and free anything still marked. */
|
||||||
cleanup_dbus();
|
cleanup_servers();
|
||||||
}
|
}
|
||||||
|
|
||||||
static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||||
@@ -319,7 +217,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
strings ? "Expected array of string" : "Expected array of string arrays");
|
strings ? "Expected array of string" : "Expected array of string arrays");
|
||||||
}
|
}
|
||||||
|
|
||||||
mark_dbus();
|
mark_servers(SERV_FROM_DBUS);
|
||||||
|
|
||||||
/* array_iter points to each "as" element in the outer array */
|
/* array_iter points to each "as" element in the outer array */
|
||||||
dbus_message_iter_recurse(&iter, &array_iter);
|
dbus_message_iter_recurse(&iter, &array_iter);
|
||||||
@@ -327,6 +225,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
{
|
{
|
||||||
const char *str = NULL;
|
const char *str = NULL;
|
||||||
union mysockaddr addr, source_addr;
|
union mysockaddr addr, source_addr;
|
||||||
|
int flags = 0;
|
||||||
char interface[IF_NAMESIZE];
|
char interface[IF_NAMESIZE];
|
||||||
char *str_addr, *str_domain = NULL;
|
char *str_addr, *str_domain = NULL;
|
||||||
|
|
||||||
@@ -416,16 +315,19 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
memset(&interface, 0, sizeof(interface));
|
memset(&interface, 0, sizeof(interface));
|
||||||
|
|
||||||
/* parse the IP address */
|
/* parse the IP address */
|
||||||
addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, NULL);
|
if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
|
||||||
|
{
|
||||||
if (addr_err)
|
|
||||||
{
|
|
||||||
error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
|
error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
|
||||||
"Invalid IP address '%s': %s",
|
"Invalid IP address '%s': %s",
|
||||||
str, addr_err);
|
str, addr_err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 0.0.0.0 for server address == NULL, for Dbus */
|
||||||
|
if (addr.in.sin_family == AF_INET &&
|
||||||
|
addr.in.sin_addr.s_addr == 0)
|
||||||
|
flags |= SERV_NO_ADDR;
|
||||||
|
|
||||||
if (strings)
|
if (strings)
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
@@ -439,7 +341,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
else
|
else
|
||||||
p = NULL;
|
p = NULL;
|
||||||
|
|
||||||
add_update_server(&addr, &source_addr, interface, str_domain);
|
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain);
|
||||||
} while ((str_domain = p));
|
} while ((str_domain = p));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -454,7 +356,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
dbus_message_iter_get_basic(&string_iter, &str);
|
dbus_message_iter_get_basic(&string_iter, &str);
|
||||||
dbus_message_iter_next (&string_iter);
|
dbus_message_iter_next (&string_iter);
|
||||||
|
|
||||||
add_update_server(&addr, &source_addr, interface, str);
|
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str);
|
||||||
} while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
|
} while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +364,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
|||||||
dbus_message_iter_next(&array_iter);
|
dbus_message_iter_next(&array_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_dbus();
|
cleanup_servers();
|
||||||
|
|
||||||
if (dup)
|
if (dup)
|
||||||
free(dup);
|
free(dup);
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
#define T_RRSIG 46
|
#define T_RRSIG 46
|
||||||
#define T_NSEC 47
|
#define T_NSEC 47
|
||||||
#define T_DNSKEY 48
|
#define T_DNSKEY 48
|
||||||
|
#define T_NSEC3 50
|
||||||
#define T_TKEY 249
|
#define T_TKEY 249
|
||||||
#define T_TSIG 250
|
#define T_TSIG 250
|
||||||
#define T_AXFR 252
|
#define T_AXFR 252
|
||||||
|
|||||||
+23
-6
@@ -98,7 +98,10 @@ int main (int argc, char **argv)
|
|||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
daemon->keyname = safe_malloc(MAXDNAME);
|
{
|
||||||
|
daemon->keyname = safe_malloc(MAXDNAME);
|
||||||
|
daemon->workspacename = safe_malloc(MAXDNAME);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
@@ -144,7 +147,7 @@ int main (int argc, char **argv)
|
|||||||
if (option_bool(OPT_DNSSEC_VALID))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (!daemon->dnskeys)
|
if (!daemon->ds)
|
||||||
die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF);
|
die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF);
|
||||||
|
|
||||||
if (daemon->cachesize < CACHESIZ)
|
if (daemon->cachesize < CACHESIZ)
|
||||||
@@ -1114,7 +1117,7 @@ static void async_event(int pipe, time_t now)
|
|||||||
{
|
{
|
||||||
pid_t p;
|
pid_t p;
|
||||||
struct event_desc ev;
|
struct event_desc ev;
|
||||||
int i;
|
int i, check = 0;
|
||||||
char *msg;
|
char *msg;
|
||||||
|
|
||||||
/* NOTE: the memory used to return msg is leaked: use msgs in events only
|
/* NOTE: the memory used to return msg is leaked: use msgs in events only
|
||||||
@@ -1125,11 +1128,25 @@ static void async_event(int pipe, time_t now)
|
|||||||
{
|
{
|
||||||
case EVENT_RELOAD:
|
case EVENT_RELOAD:
|
||||||
clear_cache_and_reload(now);
|
clear_cache_and_reload(now);
|
||||||
if (daemon->port != 0 && daemon->resolv_files && option_bool(OPT_NO_POLL))
|
|
||||||
|
if (daemon->port != 0)
|
||||||
{
|
{
|
||||||
reload_servers(daemon->resolv_files->name);
|
if (daemon->resolv_files && option_bool(OPT_NO_POLL))
|
||||||
check_servers();
|
{
|
||||||
|
reload_servers(daemon->resolv_files->name);
|
||||||
|
check = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daemon->servers_file)
|
||||||
|
{
|
||||||
|
read_servers_file();
|
||||||
|
check = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check)
|
||||||
|
check_servers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
rerun_scripts();
|
rerun_scripts();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+26
-13
@@ -295,10 +295,10 @@ struct cname {
|
|||||||
struct cname *next;
|
struct cname *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dnskey {
|
struct ds_config {
|
||||||
char *name, *key;
|
char *name, *digest;
|
||||||
int keylen, class, algo, flags;
|
int digestlen, class, algo, keytag, digest_type;
|
||||||
struct dnskey *next;
|
struct ds_config *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ADDRLIST_LITERAL 1
|
#define ADDRLIST_LITERAL 1
|
||||||
@@ -423,6 +423,7 @@ struct crec {
|
|||||||
#define F_DNSSEC (1u<<22)
|
#define F_DNSSEC (1u<<22)
|
||||||
#define F_KEYTAG (1u<<23)
|
#define F_KEYTAG (1u<<23)
|
||||||
#define F_SECSTAT (1u<<24)
|
#define F_SECSTAT (1u<<24)
|
||||||
|
#define F_NO_RR (1u<<25)
|
||||||
|
|
||||||
|
|
||||||
/* struct sockaddr is not large enough to hold any address,
|
/* struct sockaddr is not large enough to hold any address,
|
||||||
@@ -455,6 +456,7 @@ union mysockaddr {
|
|||||||
#define SERV_COUNTED 512 /* workspace for log code */
|
#define SERV_COUNTED 512 /* workspace for log code */
|
||||||
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
|
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
|
||||||
#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */
|
#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */
|
||||||
|
#define SERV_FROM_FILE 4096 /* read from --servers-file */
|
||||||
|
|
||||||
struct serverfd {
|
struct serverfd {
|
||||||
int fd;
|
int fd;
|
||||||
@@ -532,12 +534,14 @@ struct hostsfile {
|
|||||||
#define STAT_NEED_DS 4
|
#define STAT_NEED_DS 4
|
||||||
#define STAT_NEED_KEY 5
|
#define STAT_NEED_KEY 5
|
||||||
#define STAT_TRUNCATED 6
|
#define STAT_TRUNCATED 6
|
||||||
|
#define STAT_SECURE_WILDCARD 7
|
||||||
|
|
||||||
#define FREC_NOREBIND 1
|
#define FREC_NOREBIND 1
|
||||||
#define FREC_CHECKING_DISABLED 2
|
#define FREC_CHECKING_DISABLED 2
|
||||||
#define FREC_HAS_SUBNET 4
|
#define FREC_HAS_SUBNET 4
|
||||||
#define FREC_DNSKEY_QUERY 8
|
#define FREC_DNSKEY_QUERY 8
|
||||||
#define FREC_DS_QUERY 16
|
#define FREC_DS_QUERY 16
|
||||||
|
#define FREC_AD_QUESTION 32
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
#define HASH_SIZE 20 /* SHA-1 digest size */
|
#define HASH_SIZE 20 /* SHA-1 digest size */
|
||||||
@@ -559,7 +563,7 @@ struct frec {
|
|||||||
time_t time;
|
time_t time;
|
||||||
unsigned char *hash[HASH_SIZE];
|
unsigned char *hash[HASH_SIZE];
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
int class;
|
int class, work_counter;
|
||||||
struct blockdata *stash; /* Saved reply, whilst we validate */
|
struct blockdata *stash; /* Saved reply, whilst we validate */
|
||||||
size_t stash_len;
|
size_t stash_len;
|
||||||
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
||||||
@@ -862,6 +866,7 @@ extern struct daemon {
|
|||||||
unsigned int options, options2;
|
unsigned int options, options2;
|
||||||
struct resolvc default_resolv, *resolv_files;
|
struct resolvc default_resolv, *resolv_files;
|
||||||
time_t last_resolv;
|
time_t last_resolv;
|
||||||
|
char *servers_file;
|
||||||
struct mx_srv_record *mxnames;
|
struct mx_srv_record *mxnames;
|
||||||
struct naptr *naptr;
|
struct naptr *naptr;
|
||||||
struct txt_record *txt, *rr;
|
struct txt_record *txt, *rr;
|
||||||
@@ -928,7 +933,7 @@ extern struct daemon {
|
|||||||
struct prefix_class *prefix_classes;
|
struct prefix_class *prefix_classes;
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
struct dnskey *dnskeys;
|
struct ds_config *ds;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* globally used stuff for DNS */
|
/* globally used stuff for DNS */
|
||||||
@@ -937,6 +942,7 @@ extern struct daemon {
|
|||||||
char *namebuff; /* MAXDNAME size buffer */
|
char *namebuff; /* MAXDNAME size buffer */
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
char *keyname; /* MAXDNAME size buffer */
|
char *keyname; /* MAXDNAME size buffer */
|
||||||
|
char *workspacename; /* ditto */
|
||||||
#endif
|
#endif
|
||||||
unsigned int local_answer, queries_forwarded, auth_answer;
|
unsigned int local_answer, queries_forwarded, auth_answer;
|
||||||
struct frec *frec_list;
|
struct frec *frec_list;
|
||||||
@@ -990,12 +996,12 @@ extern struct daemon {
|
|||||||
void cache_init(void);
|
void cache_init(void);
|
||||||
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg);
|
void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg);
|
||||||
char *record_source(int index);
|
char *record_source(int index);
|
||||||
void querystr(char *desc, char *str, unsigned short type);
|
char *querystr(char *desc, unsigned short type);
|
||||||
struct crec *cache_find_by_addr(struct crec *crecp,
|
struct crec *cache_find_by_addr(struct crec *crecp,
|
||||||
struct all_addr *addr, time_t now,
|
struct all_addr *addr, time_t now,
|
||||||
unsigned short prot);
|
unsigned int prot);
|
||||||
struct crec *cache_find_by_name(struct crec *crecp,
|
struct crec *cache_find_by_name(struct crec *crecp,
|
||||||
char *name, time_t now, unsigned short prot);
|
char *name, time_t now, unsigned int prot);
|
||||||
void cache_end_insert(void);
|
void cache_end_insert(void);
|
||||||
void cache_start_insert(void);
|
void cache_start_insert(void);
|
||||||
struct crec *cache_insert(char *name, struct all_addr *addr,
|
struct crec *cache_insert(char *name, struct all_addr *addr,
|
||||||
@@ -1031,6 +1037,7 @@ 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);
|
||||||
unsigned char *skip_questions(struct dns_header *header, size_t plen);
|
unsigned char *skip_questions(struct dns_header *header, size_t plen);
|
||||||
|
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
|
||||||
unsigned int extract_request(struct dns_header *header, size_t qlen,
|
unsigned int extract_request(struct dns_header *header, size_t qlen,
|
||||||
char *name, unsigned short *typep);
|
char *name, unsigned short *typep);
|
||||||
size_t setup_reply(struct dns_header *header, size_t qlen,
|
size_t setup_reply(struct dns_header *header, size_t qlen,
|
||||||
@@ -1040,7 +1047,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
|
|||||||
time_t now, char **ipsets, int is_sign, int checkrebind,
|
time_t now, char **ipsets, int is_sign, int checkrebind,
|
||||||
int no_cache, int secure, int *doctored);
|
int no_cache, int secure, int *doctored);
|
||||||
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||||
struct in_addr local_addr, struct in_addr local_netmask, time_t now);
|
struct in_addr local_addr, struct in_addr local_netmask,
|
||||||
|
time_t now, int *ad_reqd);
|
||||||
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
|
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
|
||||||
struct bogus_addr *addr, time_t now);
|
struct bogus_addr *addr, time_t now);
|
||||||
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
|
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
|
||||||
@@ -1104,9 +1112,6 @@ void prettyprint_time(char *buf, unsigned int t);
|
|||||||
int prettyprint_addr(union mysockaddr *addr, char *buf);
|
int prettyprint_addr(union mysockaddr *addr, char *buf);
|
||||||
int parse_hex(char *in, unsigned char *out, int maxlen,
|
int parse_hex(char *in, unsigned char *out, int maxlen,
|
||||||
unsigned int *wildcard_mask, int *mac_type);
|
unsigned int *wildcard_mask, int *mac_type);
|
||||||
#ifdef HAVE_DNSSEC
|
|
||||||
int parse_base64(char *in, char *out);
|
|
||||||
#endif
|
|
||||||
int memcmp_masked(unsigned char *a, unsigned char *b, int len,
|
int memcmp_masked(unsigned char *a, unsigned char *b, int len,
|
||||||
unsigned int mask);
|
unsigned int mask);
|
||||||
int expand_buf(struct iovec *iov, size_t size);
|
int expand_buf(struct iovec *iov, size_t size);
|
||||||
@@ -1130,6 +1135,7 @@ void read_opts (int argc, char **argv, char *compile_opts);
|
|||||||
char *option_string(int prot, unsigned int opt, unsigned char *val,
|
char *option_string(int prot, unsigned int opt, unsigned char *val,
|
||||||
int opt_len, char *buf, int buf_len);
|
int opt_len, char *buf, int buf_len);
|
||||||
void reread_dhcp(void);
|
void reread_dhcp(void);
|
||||||
|
void read_servers_file(void);
|
||||||
void set_option_bool(unsigned int opt);
|
void set_option_bool(unsigned int opt);
|
||||||
void reset_option_bool(unsigned int opt);
|
void reset_option_bool(unsigned int opt);
|
||||||
struct hostsfile *expand_filelist(struct hostsfile *list);
|
struct hostsfile *expand_filelist(struct hostsfile *list);
|
||||||
@@ -1153,6 +1159,13 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp);
|
|||||||
int random_sock(int family);
|
int random_sock(int family);
|
||||||
void pre_allocate_sfds(void);
|
void pre_allocate_sfds(void);
|
||||||
int reload_servers(char *fname);
|
int reload_servers(char *fname);
|
||||||
|
void mark_servers(int flag);
|
||||||
|
void cleanup_servers(void);
|
||||||
|
void add_update_server(int flags,
|
||||||
|
union mysockaddr *addr,
|
||||||
|
union mysockaddr *source_addr,
|
||||||
|
const char *interface,
|
||||||
|
const char *domain);
|
||||||
void check_servers(void);
|
void check_servers(void);
|
||||||
int enumerate_interfaces(int reset);
|
int enumerate_interfaces(int reset);
|
||||||
void create_wildcard_listeners(void);
|
void create_wildcard_listeners(void);
|
||||||
|
|||||||
+622
-130
@@ -21,6 +21,10 @@
|
|||||||
|
|
||||||
#include <nettle/rsa.h>
|
#include <nettle/rsa.h>
|
||||||
#include <nettle/dsa.h>
|
#include <nettle/dsa.h>
|
||||||
|
#ifndef NO_NETTLE_ECC
|
||||||
|
# include <nettle/ecdsa.h>
|
||||||
|
# include <nettle/ecc-curve.h>
|
||||||
|
#endif
|
||||||
#include <nettle/nettle-meta.h>
|
#include <nettle/nettle-meta.h>
|
||||||
#include <gmp.h>
|
#include <gmp.h>
|
||||||
|
|
||||||
@@ -208,9 +212,83 @@ static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned
|
|||||||
return nettle_dsa_sha1_verify_digest(key, digest, sig_struct);
|
return nettle_dsa_sha1_verify_digest(key, digest, sig_struct);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
|
#ifndef NO_NETTLE_ECC
|
||||||
unsigned char *digest, int algo)
|
static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len,
|
||||||
|
unsigned char *sig, size_t sig_len,
|
||||||
|
unsigned char *digest, size_t digest_len, int algo)
|
||||||
{
|
{
|
||||||
|
unsigned char *p;
|
||||||
|
unsigned int t;
|
||||||
|
struct ecc_point *key;
|
||||||
|
|
||||||
|
static struct ecc_point *key_256 = NULL, *key_384 = NULL;
|
||||||
|
static mpz_t x, y;
|
||||||
|
static struct dsa_signature *sig_struct;
|
||||||
|
|
||||||
|
if (!sig_struct)
|
||||||
|
{
|
||||||
|
if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
nettle_dsa_signature_init(sig_struct);
|
||||||
|
mpz_init(x);
|
||||||
|
mpz_init(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (algo)
|
||||||
|
{
|
||||||
|
case 13:
|
||||||
|
if (!key_256)
|
||||||
|
{
|
||||||
|
if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
nettle_ecc_point_init(key_256, &nettle_secp_256r1);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = key_256;
|
||||||
|
t = 32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 14:
|
||||||
|
if (!key_384)
|
||||||
|
{
|
||||||
|
if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
nettle_ecc_point_init(key_384, &nettle_secp_384r1);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = key_384;
|
||||||
|
t = 48;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sig_len != 2*t || key_len != 2*t ||
|
||||||
|
(p = blockdata_retrieve(key_data, key_len, NULL)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mpz_import(x, t , 1, 1, 0, 0, p);
|
||||||
|
mpz_import(y, t , 1, 1, 0, 0, p + t);
|
||||||
|
|
||||||
|
if (!ecc_point_set(key, x, y))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);
|
||||||
|
mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);
|
||||||
|
|
||||||
|
return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
|
||||||
|
unsigned char *digest, size_t digest_len, int algo)
|
||||||
|
{
|
||||||
|
(void)digest_len;
|
||||||
|
|
||||||
switch (algo)
|
switch (algo)
|
||||||
{
|
{
|
||||||
case 1: case 5: case 7: case 8: case 10:
|
case 1: case 5: case 7: case 8: case 10:
|
||||||
@@ -218,6 +296,11 @@ static int verify(struct blockdata *key_data, unsigned int key_len, unsigned cha
|
|||||||
|
|
||||||
case 3: case 6:
|
case 3: case 6:
|
||||||
return dsa_verify(key_data, key_len, sig, sig_len, digest, algo);
|
return dsa_verify(key_data, key_len, sig, sig_len, digest, algo);
|
||||||
|
|
||||||
|
#ifndef NO_NETTLE_ECC
|
||||||
|
case 13: case 14:
|
||||||
|
return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -415,6 +498,8 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
|
|||||||
int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
|
int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
|
||||||
u16 *dp1, *dp2;
|
u16 *dp1, *dp2;
|
||||||
unsigned char *end1, *end2;
|
unsigned char *end1, *end2;
|
||||||
|
/* Note that these have been determined to be OK previously,
|
||||||
|
so we don't need to check for NULL return here. */
|
||||||
unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
|
unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
|
||||||
unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
|
unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
|
||||||
|
|
||||||
@@ -476,12 +561,15 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
|
|||||||
/* 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_INSECURE can't validate (no RRSIG, bad packet).
|
STAT_INSECURE can't validate (no RRSIG, bad packet).
|
||||||
STAT_BOGUS signature is wrong.
|
STAT_BOGUS signature is wrong.
|
||||||
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)
|
||||||
|
|
||||||
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
|
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
|
||||||
otherwise find the key in the cache.
|
otherwise find the key in the cache.
|
||||||
|
|
||||||
|
name is unchanged on exit. keyname is used as workspace and trashed.
|
||||||
*/
|
*/
|
||||||
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
|
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
|
||||||
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
|
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
|
||||||
@@ -505,7 +593,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
j != 0; j--)
|
j != 0; j--)
|
||||||
{
|
{
|
||||||
unsigned char *pstart, *pdata;
|
unsigned char *pstart, *pdata;
|
||||||
int stype, sclass, ttl;
|
int stype, sclass;
|
||||||
|
|
||||||
pstart = p;
|
pstart = p;
|
||||||
|
|
||||||
@@ -514,7 +602,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
|
|
||||||
GETSHORT(stype, p);
|
GETSHORT(stype, p);
|
||||||
GETSHORT(sclass, p);
|
GETSHORT(sclass, p);
|
||||||
GETLONG(ttl, p);
|
p += 4; /* TTL */
|
||||||
|
|
||||||
pdata = p;
|
pdata = p;
|
||||||
|
|
||||||
@@ -531,6 +619,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
{
|
{
|
||||||
unsigned char **new;
|
unsigned char **new;
|
||||||
|
|
||||||
|
/* Protect against insane/maliciuos queries which bloat the workspace
|
||||||
|
and eat CPU in the sort */
|
||||||
|
if (rrsetidx >= 100)
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
/* expand */
|
/* expand */
|
||||||
if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
|
if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
|
||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
@@ -589,18 +682,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
|
|
||||||
/* Sort RRset records into canonical order.
|
/* Sort RRset records into canonical order.
|
||||||
Note that at this point keyname and name buffs are
|
Note that at this point keyname and daemon->workspacename buffs are
|
||||||
unused, and used as workspace by the sort. */
|
unused, and used as workspace by the sort. */
|
||||||
sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname);
|
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 (j = 0; j <sigidx; j++)
|
||||||
{
|
{
|
||||||
unsigned char *psav, *sig;
|
unsigned char *psav, *sig, *digest;
|
||||||
int i, wire_len, sig_len;
|
int i, wire_len, sig_len;
|
||||||
const struct nettle_hash *hash;
|
const struct nettle_hash *hash;
|
||||||
void *ctx;
|
void *ctx;
|
||||||
unsigned char *digest;
|
char *name_start;
|
||||||
u32 nsigttl;
|
u32 nsigttl;
|
||||||
|
|
||||||
p = sigs[j];
|
p = sigs[j];
|
||||||
@@ -618,6 +711,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
if (!extract_name(header, plen, &p, keyname, 1, 0))
|
if (!extract_name(header, plen, &p, keyname, 1, 0))
|
||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
|
||||||
|
the name of the zone containing the RRset. We can't tell that
|
||||||
|
for certain, but we can check that the RRset name is equal to
|
||||||
|
or encloses the signers name, which should be enough to stop
|
||||||
|
an attacker using signatures made with the key of an unrelated
|
||||||
|
zone he controls. Note that the root key is always allowed. */
|
||||||
|
if (*keyname != 0)
|
||||||
|
{
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
for (name_start = name; !hostname_isequal(name_start, keyname); )
|
||||||
|
if ((name_start = strchr(name_start, '.')))
|
||||||
|
name_start++; /* chop a label off and try again */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failed = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bad sig, try another */
|
||||||
|
if (failed)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Other 5.3.1 checks */
|
||||||
if (!check_date_range(sig_inception, sig_expiration) ||
|
if (!check_date_range(sig_inception, sig_expiration) ||
|
||||||
labels > name_labels ||
|
labels > name_labels ||
|
||||||
!(hash = hash_find(algo_digest_name(algo))) ||
|
!(hash = hash_find(algo_digest_name(algo))) ||
|
||||||
@@ -642,13 +760,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
{
|
{
|
||||||
int seg;
|
int seg;
|
||||||
unsigned char *end, *cp;
|
unsigned char *end, *cp;
|
||||||
char *name_start = name;
|
|
||||||
u16 len, *dp;
|
u16 len, *dp;
|
||||||
|
|
||||||
p = rrset[i];
|
p = rrset[i];
|
||||||
if (!extract_name(header, plen, &p, name, 1, 10))
|
if (!extract_name(header, plen, &p, name, 1, 10))
|
||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
name_start = name;
|
||||||
|
|
||||||
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
||||||
if (labels < name_labels)
|
if (labels < name_labels)
|
||||||
{
|
{
|
||||||
@@ -698,7 +817,7 @@ 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, algo))
|
verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||||
return STAT_SECURE;
|
return STAT_SECURE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -708,8 +827,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
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 == class &&
|
crecp->uid == class &&
|
||||||
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, algo))
|
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||||
return STAT_SECURE;
|
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,7 +860,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
GETSHORT(qclass, p);
|
GETSHORT(qclass, p);
|
||||||
|
|
||||||
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
|
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
|
||||||
return STAT_INSECURE;
|
return STAT_BOGUS;
|
||||||
|
|
||||||
/* 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)))
|
||||||
@@ -775,7 +894,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
|
|
||||||
GETSHORT(flags, p);
|
GETSHORT(flags, p);
|
||||||
if (*p++ != 3)
|
if (*p++ != 3)
|
||||||
return STAT_INSECURE;
|
return STAT_BOGUS;
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
||||||
key = NULL;
|
key = NULL;
|
||||||
@@ -865,7 +984,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
|
|
||||||
GETSHORT(flags, p);
|
GETSHORT(flags, p);
|
||||||
if (*p++ != 3)
|
if (*p++ != 3)
|
||||||
return STAT_INSECURE;
|
return STAT_BOGUS;
|
||||||
algo = *p++;
|
algo = *p++;
|
||||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
||||||
|
|
||||||
@@ -961,7 +1080,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|||||||
GETSHORT(qclass, p);
|
GETSHORT(qclass, p);
|
||||||
|
|
||||||
if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
|
if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
|
||||||
return STAT_INSECURE;
|
return STAT_BOGUS;
|
||||||
|
|
||||||
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
|
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
|
||||||
|
|
||||||
@@ -1039,14 +1158,454 @@ static int hostname_cmp(const char *a, const char *b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find all the NSEC or NSEC3 records in a reply.
|
||||||
|
return an array of pointers to them. */
|
||||||
|
static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd)
|
||||||
|
{
|
||||||
|
static unsigned char **nsecset = NULL;
|
||||||
|
static int nsecset_sz = 0;
|
||||||
|
|
||||||
|
int type_found = -1;
|
||||||
|
unsigned char *p = skip_questions(header, plen);
|
||||||
|
int type, class, rdlen, i, nsecs_found;
|
||||||
|
|
||||||
|
/* Move to NS section */
|
||||||
|
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
|
||||||
|
{
|
||||||
|
unsigned char *pstart = p;
|
||||||
|
|
||||||
|
if (!(p = skip_name(p, header, plen, 10)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
GETSHORT(type, p);
|
||||||
|
GETSHORT(class, p);
|
||||||
|
p += 4; /* TTL */
|
||||||
|
GETSHORT(rdlen, p);
|
||||||
|
|
||||||
|
if (class == class_reqd && (type == T_NSEC || type == T_NSEC3))
|
||||||
|
{
|
||||||
|
/* No mixed NSECing 'round here, thankyouverymuch */
|
||||||
|
if (type_found == T_NSEC && type == T_NSEC3)
|
||||||
|
return 0;
|
||||||
|
if (type_found == T_NSEC3 && type == T_NSEC)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
type_found = type;
|
||||||
|
|
||||||
|
if (nsecs_found == nsecset_sz)
|
||||||
|
{
|
||||||
|
unsigned char **new;
|
||||||
|
|
||||||
|
/* Protect against insane/malicious queries which bloat the workspace
|
||||||
|
and eat CPU in the sort */
|
||||||
|
if (nsecs_found >= 100)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* expand */
|
||||||
|
if (!(new = whine_malloc((nsecset_sz + 5) * sizeof(unsigned char **))))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (nsecset)
|
||||||
|
{
|
||||||
|
memcpy(new, nsecset, nsecset_sz * sizeof(unsigned char **));
|
||||||
|
free(nsecset);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsecset = new;
|
||||||
|
nsecset_sz += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsecset[nsecs_found++] = pstart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*nsecsetp = nsecset;
|
||||||
|
*nsecsetl = nsecs_found;
|
||||||
|
|
||||||
|
return type_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int prove_non_existance_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
|
||||||
|
char *workspace1, char *workspace2, char *name, int type)
|
||||||
|
{
|
||||||
|
int i, rc, rdlen;
|
||||||
|
unsigned char *p, *psave;
|
||||||
|
int offset = (type & 0xff) >> 3;
|
||||||
|
int mask = 0x80 >> (type & 0x07);
|
||||||
|
|
||||||
|
/* Find NSEC record that proves name doesn't exist */
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
{
|
||||||
|
p = nsecs[i];
|
||||||
|
if (!extract_name(header, plen, &p, workspace1, 1, 10))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
p += 8; /* class, type, TTL */
|
||||||
|
GETSHORT(rdlen, p);
|
||||||
|
psave = p;
|
||||||
|
if (!extract_name(header, plen, &p, workspace2, 1, 10))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
rc = hostname_cmp(workspace1, name);
|
||||||
|
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
/* 4035 para 5.4. Last sentence */
|
||||||
|
if (type == T_NSEC || type == T_RRSIG)
|
||||||
|
return STAT_SECURE;
|
||||||
|
|
||||||
|
/* NSEC with the same name as the RR we're testing, check
|
||||||
|
that the type in question doesn't appear in the type map */
|
||||||
|
rdlen -= p - psave;
|
||||||
|
/* rdlen is now length of type map, and p points to it */
|
||||||
|
|
||||||
|
while (rdlen >= 2)
|
||||||
|
{
|
||||||
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
if (p[0] == type >> 8)
|
||||||
|
{
|
||||||
|
/* Does the NSEC say our type exists? */
|
||||||
|
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
|
break; /* finshed checking */
|
||||||
|
}
|
||||||
|
|
||||||
|
rdlen -= p[1];
|
||||||
|
p += p[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
else if (rc == -1)
|
||||||
|
{
|
||||||
|
/* Normal case, name falls between NSEC name and next domain name,
|
||||||
|
wrap around case, name falls between NSEC name (rc == -1) and end */
|
||||||
|
if (hostname_cmp(workspace2, name) == 1 || hostname_cmp(workspace1, workspace2) == 1)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* wrap around case, name falls between start and next domain name */
|
||||||
|
if (hostname_cmp(workspace1, workspace2) == 1 && hostname_cmp(workspace2, name) == 1)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return STAT_BOGUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return digest length, or zero on error */
|
||||||
|
static int hash_name(char *in, unsigned char **out, struct nettle_hash const *hash,
|
||||||
|
unsigned char *salt, int salt_len, int iterations)
|
||||||
|
{
|
||||||
|
void *ctx;
|
||||||
|
unsigned char *digest;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!hash_init(hash, &ctx, &digest))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hash->update(ctx, to_wire(in), (unsigned char *)in);
|
||||||
|
hash->update(ctx, salt_len, salt);
|
||||||
|
hash->digest(ctx, hash->digest_size, digest);
|
||||||
|
|
||||||
|
for(i = 0; i < iterations; i++)
|
||||||
|
{
|
||||||
|
hash->update(ctx, hash->digest_size, digest);
|
||||||
|
hash->update(ctx, salt_len, salt);
|
||||||
|
hash->digest(ctx, hash->digest_size, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
from_wire(in);
|
||||||
|
|
||||||
|
*out = digest;
|
||||||
|
return hash->digest_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode base32 to first "." or end of string */
|
||||||
|
static int base32_decode(char *in, unsigned char *out)
|
||||||
|
{
|
||||||
|
int oc, on, c, mask, i;
|
||||||
|
unsigned char *p = out;
|
||||||
|
|
||||||
|
for (c = *in, oc = 0, on = 0; c != 0 && c != '.'; c = *++in)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
c -= '0';
|
||||||
|
else if (c >= 'a' && c <= 'v')
|
||||||
|
c -= 'a', c += 10;
|
||||||
|
else if (c >= 'A' && c <= 'V')
|
||||||
|
c -= 'A', c += 10;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (mask = 0x10, i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
if (c & mask)
|
||||||
|
oc |= 1;
|
||||||
|
mask = mask >> 1;
|
||||||
|
if (((++on) & 7) == 0)
|
||||||
|
*p++ = oc;
|
||||||
|
oc = oc << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((on & 7) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return p - out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int prove_non_existance_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
|
||||||
|
char *workspace1, char *workspace2, char *name, int type)
|
||||||
|
{
|
||||||
|
unsigned char *salt, *p, *digest;
|
||||||
|
int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0;
|
||||||
|
struct nettle_hash const *hash;
|
||||||
|
char *closest_encloser, *next_closest, *wildcard;
|
||||||
|
|
||||||
|
/* Look though the NSEC3 records to find the first one with
|
||||||
|
an algorithm we support (currently only algo == 1).
|
||||||
|
|
||||||
|
Take the algo, iterations, and salt of that record
|
||||||
|
as the ones we're going to use, and prune any
|
||||||
|
that don't match. */
|
||||||
|
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
{
|
||||||
|
if (!(p = skip_name(nsecs[i], header, plen, 15)))
|
||||||
|
return STAT_INSECURE; /* bad packet */
|
||||||
|
|
||||||
|
p += 10; /* type, class, TTL, rdlen */
|
||||||
|
algo = *p++;
|
||||||
|
|
||||||
|
if (algo == 1)
|
||||||
|
break; /* known algo */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No usable NSEC3s */
|
||||||
|
if (i == nsec_count)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
|
p++; /* flags */
|
||||||
|
GETSHORT (iterations, p);
|
||||||
|
salt_len = *p++;
|
||||||
|
salt = p;
|
||||||
|
if (!CHECK_LEN(header, salt, plen, salt_len))
|
||||||
|
return STAT_INSECURE; /* bad packet */
|
||||||
|
|
||||||
|
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
{
|
||||||
|
unsigned char *nsec3p = nsecs[i];
|
||||||
|
int this_iter;
|
||||||
|
|
||||||
|
nsecs[i] = NULL; /* Speculative, will be restored if OK. */
|
||||||
|
|
||||||
|
if (!(p = skip_name(nsec3p, header, plen, 15)))
|
||||||
|
return STAT_INSECURE; /* bad packet */
|
||||||
|
|
||||||
|
p += 10; /* type, class, TTL, rdlen */
|
||||||
|
|
||||||
|
if (*p++ != algo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
p++; /* flags */
|
||||||
|
|
||||||
|
GETSHORT(this_iter, p);
|
||||||
|
if (this_iter != iterations)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (salt_len != *p++)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p, plen, salt_len))
|
||||||
|
return STAT_INSECURE; /* bad packet */
|
||||||
|
|
||||||
|
if (memcmp(p, salt, salt_len) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* All match, put the pointer back */
|
||||||
|
nsecs[i] = nsec3p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Algo is checked as 1 above */
|
||||||
|
if (!(hash = hash_find("sha1")))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
/* Now, we need the "closest encloser NSEC3" */
|
||||||
|
closest_encloser = name;
|
||||||
|
next_closest = NULL;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (*closest_encloser == '.')
|
||||||
|
closest_encloser++;
|
||||||
|
|
||||||
|
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
if ((p = nsecs[i]))
|
||||||
|
{
|
||||||
|
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||||
|
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
if (digest_len == base32_len &&
|
||||||
|
memcmp(digest, workspace2, digest_len) == 0)
|
||||||
|
break; /* Gotit */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != nsec_count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
next_closest = closest_encloser;
|
||||||
|
}
|
||||||
|
while ((closest_encloser = strchr(closest_encloser, '.')));
|
||||||
|
|
||||||
|
/* No usable NSEC3s */
|
||||||
|
if (i == nsec_count)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
|
if (!next_closest)
|
||||||
|
{
|
||||||
|
/* We found an NSEC3 whose hashed name exactly matches the query, so
|
||||||
|
Now we just need to check the type map. p points to the RR data for the record. */
|
||||||
|
int rdlen;
|
||||||
|
unsigned char *psave;
|
||||||
|
int offset = (type & 0xff) >> 3;
|
||||||
|
int mask = 0x80 >> (type & 0x07);
|
||||||
|
|
||||||
|
p += 8; /* class, type, TTL */
|
||||||
|
GETSHORT(rdlen, p);
|
||||||
|
psave = p;
|
||||||
|
p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */
|
||||||
|
hash_len = *p++;
|
||||||
|
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||||
|
return STAT_INSECURE; /* bad packet */
|
||||||
|
p += hash_len;
|
||||||
|
rdlen -= p - psave;
|
||||||
|
|
||||||
|
while (rdlen >= 2)
|
||||||
|
{
|
||||||
|
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
if (p[0] == type >> 8)
|
||||||
|
{
|
||||||
|
/* Does the NSEC3 say our type exists? */
|
||||||
|
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
|
break; /* finshed checking */
|
||||||
|
}
|
||||||
|
|
||||||
|
rdlen -= p[1];
|
||||||
|
p += p[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for NSEC3 that proves the non-existance of the next-closest encloser */
|
||||||
|
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
if ((p = nsecs[i]))
|
||||||
|
{
|
||||||
|
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||||
|
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
|
||||||
|
hash_len = *p++; /* p now points to next hashed name */
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
if (digest_len == base32_len && hash_len == base32_len)
|
||||||
|
{
|
||||||
|
if (memcmp(workspace2, digest, digest_len) <= 0)
|
||||||
|
{
|
||||||
|
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
|
||||||
|
wrap around case, name-hash falls between NSEC3 name-hash and end */
|
||||||
|
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* wrap around case, name falls between start and next domain name */
|
||||||
|
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, check that there's no seat of wildcard synthesis */
|
||||||
|
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
|
wildcard--;
|
||||||
|
*wildcard = '*';
|
||||||
|
|
||||||
|
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
for (i = 0; i < nsec_count; i++)
|
||||||
|
if ((p = nsecs[i]))
|
||||||
|
{
|
||||||
|
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||||
|
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
|
||||||
|
hash_len = *p++; /* p now points to next hashed name */
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
if (digest_len == base32_len && hash_len == base32_len)
|
||||||
|
{
|
||||||
|
if (memcmp(workspace2, digest, digest_len) <= 0)
|
||||||
|
{
|
||||||
|
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
|
||||||
|
wrap around case, name-hash falls between NSEC3 name-hash and end */
|
||||||
|
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* wrap around case, name falls between start and next domain name */
|
||||||
|
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
|
||||||
|
return STAT_SECURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return STAT_BOGUS;
|
||||||
|
}
|
||||||
|
|
||||||
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
|
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
|
||||||
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
|
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
|
||||||
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)
|
||||||
{
|
{
|
||||||
unsigned char *ans_start, *p1, *p2;
|
unsigned char *ans_start, *p1, *p2, **nsecs;
|
||||||
int type1, class1, rdlen1, type2, class2, rdlen2;
|
int type1, class1, rdlen1, type2, class2, rdlen2;
|
||||||
int i, j, rc, have_nsec, have_nsec_equal, cname_count = 5;
|
int i, j, rc, nsec_count, cname_count = 10;
|
||||||
|
int nsec_type = 0;
|
||||||
|
|
||||||
|
if (RCODE(header) == SERVFAIL)
|
||||||
|
return STAT_BOGUS;
|
||||||
|
|
||||||
if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
|
if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
|
||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
@@ -1094,7 +1653,33 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
struct blockdata *key;
|
struct blockdata *key;
|
||||||
struct crec *crecp;
|
struct crec *crecp;
|
||||||
|
|
||||||
if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
|
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0);
|
||||||
|
|
||||||
|
if (rc == STAT_SECURE_WILDCARD)
|
||||||
|
{
|
||||||
|
/* An attacker replay a wildcard answer with a different
|
||||||
|
answer and overlay a genuine RR. To prove this
|
||||||
|
hasn't happened, the answer must prove that
|
||||||
|
the gennuine record doesn't exist. Check that here. */
|
||||||
|
if (!nsec_type)
|
||||||
|
{
|
||||||
|
nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
|
||||||
|
|
||||||
|
if (nsec_type == 0)
|
||||||
|
return STAT_INSECURE; /* Bad packet */
|
||||||
|
if (nsec_type == -1)
|
||||||
|
return STAT_BOGUS; /* No NSECs */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nsec_type == T_NSEC)
|
||||||
|
rc = prove_non_existance_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||||
|
else
|
||||||
|
rc = prove_non_existance_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||||
|
|
||||||
|
if (rc != STAT_SECURE)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
else if (rc != STAT_SECURE)
|
||||||
{
|
{
|
||||||
if (class)
|
if (class)
|
||||||
*class = class1; /* Class for DS or DNSKEY */
|
*class = class1; /* Class for DS or DNSKEY */
|
||||||
@@ -1163,6 +1748,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
type_covered == T_DNSKEY || type_covered == T_PTR))
|
type_covered == T_DNSKEY || type_covered == T_PTR))
|
||||||
{
|
{
|
||||||
a.addr.dnssec.type = type_covered;
|
a.addr.dnssec.type = type_covered;
|
||||||
|
a.addr.dnssec.class = class1;
|
||||||
|
|
||||||
algo = *p2++;
|
algo = *p2++;
|
||||||
p2 += 13; /* labels, orig_ttl, expiration, inception */
|
p2 += 13; /* labels, orig_ttl, expiration, inception */
|
||||||
@@ -1229,7 +1815,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
{
|
{
|
||||||
/* Do we have an answer for the question? */
|
/* Do we have an answer for the question? */
|
||||||
if (type1 == type2)
|
if (type1 == type2)
|
||||||
return RCODE(header) == NXDOMAIN ? STAT_INSECURE : STAT_SECURE;
|
return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
|
||||||
else if (type2 == T_CNAME)
|
else if (type2 == T_CNAME)
|
||||||
{
|
{
|
||||||
/* looped CNAMES */
|
/* looped CNAMES */
|
||||||
@@ -1246,117 +1832,25 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
return STAT_INSECURE;
|
return STAT_INSECURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NXDOMAIN or NODATA reply, look for NSEC records to support that.
|
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
|
||||||
At this point, p1 points to the start of the auth section.
|
|
||||||
Use keyname as workspace */
|
/* First marshall the NSEC records, if we've not done it previously */
|
||||||
for (have_nsec = 0, have_nsec_equal = 0, p2 = NULL, rdlen2 = 0, j = ntohs(header->nscount); j != 0; j--)
|
if (!nsec_type)
|
||||||
{
|
{
|
||||||
unsigned char *nsec_start = p1;
|
nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
|
||||||
if (!extract_name(header, plen, &p1, keyname, 1, 10))
|
|
||||||
return STAT_INSECURE; /* bad packet */
|
|
||||||
|
|
||||||
GETSHORT(type2, p1);
|
if (nsec_type == 0)
|
||||||
GETSHORT(class2, p1);
|
return STAT_INSECURE; /* Bad packet */
|
||||||
p1 += 4; /* TTL */
|
if (nsec_type == -1)
|
||||||
GETSHORT(rdlen1, p1);
|
return STAT_BOGUS; /* No NSECs */
|
||||||
|
|
||||||
if (class1 == class2 && type2 == T_NSEC)
|
|
||||||
{
|
|
||||||
have_nsec = 1;
|
|
||||||
rc = hostname_cmp(name, keyname);
|
|
||||||
|
|
||||||
if (rc >= 0)
|
|
||||||
{
|
|
||||||
if (p2)
|
|
||||||
{
|
|
||||||
unsigned char *psave = p2;
|
|
||||||
/* new NSEC is smaller than name,
|
|
||||||
is it bigger than previous one? */
|
|
||||||
|
|
||||||
/* get previous one into name buffer */
|
|
||||||
if (!extract_name(header, plen, &psave, name, 1, 0))
|
|
||||||
return STAT_INSECURE; /* bad packet */
|
|
||||||
|
|
||||||
if (hostname_cmp(name, keyname) < 0)
|
|
||||||
{
|
|
||||||
p2 = nsec_start;
|
|
||||||
rdlen2 = rdlen1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* restore query name */
|
|
||||||
psave = (unsigned char *)(header+1);
|
|
||||||
if (!extract_name(header, plen, &psave, name, 1, 0))
|
|
||||||
return STAT_INSECURE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* There was no previous best candidate */
|
|
||||||
p2 = nsec_start;
|
|
||||||
rdlen2 = rdlen1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rc == 0)
|
|
||||||
have_nsec_equal = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
|
||||||
return STAT_INSECURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nsec_type == T_NSEC)
|
||||||
if (p2)
|
return prove_non_existance_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||||
{
|
else
|
||||||
unsigned char *psave;
|
return prove_non_existance_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||||
p2 = skip_name(p2, header, plen, 0);
|
|
||||||
p2 += 10; /* type, class, ttl, rdlen */
|
|
||||||
psave = p2;
|
|
||||||
extract_name(header, plen, &p2, keyname, 1, 0);
|
|
||||||
rdlen2 -= p2 - psave;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* At this point, have_nsec is set if there's at least one NSEC
|
|
||||||
have_nsec_equal is set if there's an NSEC with the same name as the query;
|
|
||||||
p2 points to the type bit maps of the biggest NSEC smaller than or equal to the query
|
|
||||||
or NULL if the query is smaller than all of them.
|
|
||||||
Keyname holds the next domain name for that NSEC.
|
|
||||||
rdlen2 is the length of the bitmap field */
|
|
||||||
|
|
||||||
|
|
||||||
if (RCODE(header) == NOERROR && have_nsec_equal)
|
|
||||||
{
|
|
||||||
int offset = (type1 & 0xff) >> 3;
|
|
||||||
int mask = 0x80 >> (type1 & 0x07);
|
|
||||||
|
|
||||||
while (rdlen2 >= 2)
|
|
||||||
{
|
|
||||||
if (p2[0] == type1 >> 8)
|
|
||||||
{
|
|
||||||
/* Does the NSEC say our type exists? */
|
|
||||||
if (offset < p2[1] &&
|
|
||||||
(p2[offset+2] & mask) != 0)
|
|
||||||
return STAT_INSECURE;
|
|
||||||
|
|
||||||
break; /* finshed checking */
|
|
||||||
}
|
|
||||||
|
|
||||||
rdlen2 -= p2[1];
|
|
||||||
p2 += p2[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return STAT_SECURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RCODE(header) == NXDOMAIN && have_nsec)
|
|
||||||
{
|
|
||||||
if (!p2 || hostname_cmp(name, keyname) < 0)
|
|
||||||
return STAT_SECURE; /* Before the first, or in a proven gap */
|
|
||||||
}
|
|
||||||
|
|
||||||
return STAT_INSECURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
|
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
|
||||||
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
||||||
{
|
{
|
||||||
@@ -1368,12 +1862,12 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
unsigned long ac;
|
unsigned long ac = flags + 0x300 + alg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
|
|
||||||
for (i = 0; i < keylen; ++i)
|
for (i = 0; i < keylen; ++i)
|
||||||
ac += (i & 1) ? key[i] : key[i] << 8;
|
ac += (i & 1) ? key[i] : key[i] << 8;
|
||||||
|
|
||||||
ac += (ac >> 16) & 0xffff;
|
ac += (ac >> 16) & 0xffff;
|
||||||
return ac & 0xffff;
|
return ac & 0xffff;
|
||||||
}
|
}
|
||||||
@@ -1382,9 +1876,7 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
|||||||
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
|
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
|
||||||
{
|
{
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
char types[20];
|
char *types = querystr("dnssec-query", type);
|
||||||
|
|
||||||
querystr("dnssec-query", types, type);
|
|
||||||
|
|
||||||
if (addr->sa.sa_family == AF_INET)
|
if (addr->sa.sa_family == AF_INET)
|
||||||
log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
|
log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
|
||||||
|
|||||||
+172
-152
@@ -234,7 +234,8 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp,
|
|||||||
|
|
||||||
static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||||
struct all_addr *dst_addr, unsigned int dst_iface,
|
struct all_addr *dst_addr, unsigned int dst_iface,
|
||||||
struct dns_header *header, size_t plen, time_t now, struct frec *forward)
|
struct dns_header *header, size_t plen, time_t now,
|
||||||
|
struct frec *forward, int ad_reqd)
|
||||||
{
|
{
|
||||||
char *domain = NULL;
|
char *domain = NULL;
|
||||||
int type = 0, norebind = 0;
|
int type = 0, norebind = 0;
|
||||||
@@ -249,16 +250,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
#endif
|
#endif
|
||||||
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
||||||
|
|
||||||
/* RFC 4035: sect 4.6 para 2 */
|
|
||||||
header->hb4 &= ~HB4_AD;
|
|
||||||
|
|
||||||
/* may be no servers available. */
|
/* may be no servers available. */
|
||||||
if (!daemon->servers)
|
if (!daemon->servers)
|
||||||
forward = NULL;
|
forward = NULL;
|
||||||
else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
|
else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
/* If we've already got an answer to this query, but we're awaiting keys for vaildation,
|
/* If we've already got an answer to this query, but we're awaiting keys for validation,
|
||||||
there's no point retrying the query, retry the key query instead...... */
|
there's no point retrying the query, retry the key query instead...... */
|
||||||
if (forward->blocking_query)
|
if (forward->blocking_query)
|
||||||
{
|
{
|
||||||
@@ -334,6 +332,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
forward->flags |= FREC_NOREBIND;
|
forward->flags |= FREC_NOREBIND;
|
||||||
if (header->hb4 & HB4_CD)
|
if (header->hb4 & HB4_CD)
|
||||||
forward->flags |= FREC_CHECKING_DISABLED;
|
forward->flags |= FREC_CHECKING_DISABLED;
|
||||||
|
if (ad_reqd)
|
||||||
|
forward->flags |= FREC_AD_QUESTION;
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
forward->work_counter = DNSSEC_WORK;
|
||||||
|
#endif
|
||||||
|
|
||||||
header->id = htons(forward->new_id);
|
header->id = htons(forward->new_id);
|
||||||
|
|
||||||
@@ -503,13 +506,15 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
||||||
int no_cache, int cache_secure, int check_subnet, union mysockaddr *query_source)
|
int no_cache, int cache_secure, int ad_reqd, int check_subnet, union mysockaddr *query_source)
|
||||||
{
|
{
|
||||||
unsigned char *pheader, *sizep;
|
unsigned char *pheader, *sizep;
|
||||||
char **sets = 0;
|
char **sets = 0;
|
||||||
int munged = 0, is_sign;
|
int munged = 0, is_sign;
|
||||||
size_t plen;
|
size_t plen;
|
||||||
|
|
||||||
|
(void)ad_reqd;
|
||||||
|
|
||||||
#ifdef HAVE_IPSET
|
#ifdef HAVE_IPSET
|
||||||
/* Similar algorithm to search_servers. */
|
/* Similar algorithm to search_servers. */
|
||||||
struct ipsets *ipset_pos;
|
struct ipsets *ipset_pos;
|
||||||
@@ -535,15 +540,13 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
|
|
||||||
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
|
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
|
||||||
{
|
{
|
||||||
if (!is_sign)
|
unsigned short udpsz;
|
||||||
{
|
unsigned char *psave = sizep;
|
||||||
unsigned short udpsz;
|
|
||||||
unsigned char *psave = sizep;
|
|
||||||
|
|
||||||
GETSHORT(udpsz, sizep);
|
GETSHORT(udpsz, sizep);
|
||||||
if (udpsz > daemon->edns_pktsz)
|
|
||||||
PUTSHORT(daemon->edns_pktsz, psave);
|
if (!is_sign && udpsz > daemon->edns_pktsz)
|
||||||
}
|
PUTSHORT(daemon->edns_pktsz, psave);
|
||||||
|
|
||||||
if (check_subnet && !check_source(header, plen, pheader, query_source))
|
if (check_subnet && !check_source(header, plen, pheader, query_source))
|
||||||
{
|
{
|
||||||
@@ -619,7 +622,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
if (option_bool(OPT_DNSSEC_VALID))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
header->hb4 &= ~HB4_AD;
|
header->hb4 &= ~HB4_AD;
|
||||||
|
|
||||||
if (!(header->hb4 & HB4_CD) && cache_secure)
|
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
|
||||||
header->hb4 |= HB4_AD;
|
header->hb4 |= HB4_AD;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -705,7 +708,7 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
|
if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
|
||||||
{
|
{
|
||||||
header->hb3 &= ~(HB3_QR | HB3_TC);
|
header->hb3 &= ~(HB3_QR | HB3_TC);
|
||||||
forward_query(-1, NULL, NULL, 0, header, nn, now, forward);
|
forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -780,10 +783,26 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
answer aside, whilst we get that. */
|
answer aside, whilst we get that. */
|
||||||
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
||||||
{
|
{
|
||||||
struct frec *new;
|
struct frec *new, *orig;
|
||||||
|
|
||||||
if ((new = get_new_frec(now, NULL, 1)))
|
/* Free any saved query */
|
||||||
|
if (forward->stash)
|
||||||
|
blockdata_free(forward->stash);
|
||||||
|
|
||||||
|
/* Now save reply pending receipt of key data */
|
||||||
|
if (!(forward->stash = blockdata_alloc((char *)header, n)))
|
||||||
|
return;
|
||||||
|
forward->stash_len = n;
|
||||||
|
|
||||||
|
anotherkey:
|
||||||
|
/* Find the original query that started it all.... */
|
||||||
|
for (orig = forward; orig->dependent; orig = orig->dependent);
|
||||||
|
|
||||||
|
if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
|
||||||
|
status = STAT_INSECURE;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
|
int fd;
|
||||||
struct frec *next = new->next;
|
struct frec *next = new->next;
|
||||||
*new = *forward; /* copy everything, then overwrite */
|
*new = *forward; /* copy everything, then overwrite */
|
||||||
new->next = next;
|
new->next = next;
|
||||||
@@ -794,80 +813,67 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
#endif
|
#endif
|
||||||
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
|
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
|
||||||
|
|
||||||
/* Free any saved query */
|
new->dependent = forward; /* to find query awaiting new one. */
|
||||||
if (forward->stash)
|
forward->blocking_query = new; /* for garbage cleaning */
|
||||||
blockdata_free(forward->stash);
|
/* validate routines leave name of required record in daemon->keyname */
|
||||||
|
if (status == STAT_NEED_KEY)
|
||||||
/* Now save reply pending receipt of key data */
|
{
|
||||||
if (!(forward->stash = blockdata_alloc((char *)header, n)))
|
new->flags |= FREC_DNSKEY_QUERY;
|
||||||
free_frec(new); /* malloc failure, unwind */
|
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
|
||||||
|
daemon->keyname, forward->class, T_DNSKEY, &server->addr);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int fd;
|
new->flags |= FREC_DS_QUERY;
|
||||||
|
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
|
||||||
|
daemon->keyname, forward->class, T_DS, &server->addr);
|
||||||
|
}
|
||||||
|
if ((hash = hash_questions(header, nn, daemon->namebuff)))
|
||||||
|
memcpy(new->hash, hash, HASH_SIZE);
|
||||||
|
new->new_id = get_id();
|
||||||
|
header->id = htons(new->new_id);
|
||||||
|
/* Save query for retransmission */
|
||||||
|
new->stash = blockdata_alloc((char *)header, nn);
|
||||||
|
new->stash_len = nn;
|
||||||
|
|
||||||
forward->stash_len = n;
|
/* Don't resend this. */
|
||||||
|
daemon->srv_save = NULL;
|
||||||
|
|
||||||
new->dependent = forward; /* to find query awaiting new one. */
|
if (server->sfd)
|
||||||
forward->blocking_query = new; /* for garbage cleaning */
|
fd = server->sfd->fd;
|
||||||
/* validate routines leave name of required record in daemon->keyname */
|
else
|
||||||
if (status == STAT_NEED_KEY)
|
{
|
||||||
{
|
fd = -1;
|
||||||
new->flags |= FREC_DNSKEY_QUERY;
|
|
||||||
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
|
|
||||||
daemon->keyname, forward->class, T_DNSKEY, &server->addr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
new->flags |= FREC_DS_QUERY;
|
|
||||||
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
|
|
||||||
daemon->keyname, forward->class, T_DS, &server->addr);
|
|
||||||
}
|
|
||||||
if ((hash = hash_questions(header, nn, daemon->namebuff)))
|
|
||||||
memcpy(new->hash, hash, HASH_SIZE);
|
|
||||||
new->new_id = get_id();
|
|
||||||
header->id = htons(new->new_id);
|
|
||||||
/* Save query for retransmission */
|
|
||||||
new->stash = blockdata_alloc((char *)header, nn);
|
|
||||||
new->stash_len = nn;
|
|
||||||
|
|
||||||
/* Don't resend this. */
|
|
||||||
daemon->srv_save = NULL;
|
|
||||||
|
|
||||||
if (server->sfd)
|
|
||||||
fd = server->sfd->fd;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fd = -1;
|
|
||||||
#ifdef HAVE_IPV6
|
#ifdef HAVE_IPV6
|
||||||
if (server->addr.sa.sa_family == AF_INET6)
|
if (server->addr.sa.sa_family == AF_INET6)
|
||||||
{
|
|
||||||
if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
|
|
||||||
fd = new->rfd6->fd;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
|
|
||||||
fd = new->rfd4->fd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fd != -1)
|
|
||||||
{
|
{
|
||||||
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
|
if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
|
||||||
server->queries++;
|
fd = new->rfd6->fd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
|
||||||
|
fd = new->rfd4->fd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
if (fd != -1)
|
||||||
|
{
|
||||||
|
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
|
||||||
|
server->queries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok, we reached far enough up the chain-of-trust that we can validate something.
|
/* Ok, we reached far enough up the chain-of-trust that we can validate something.
|
||||||
Now wind back down, pulling back answers which wouldn't previously validate
|
Now wind back down, pulling back answers which wouldn't previously validate
|
||||||
and validate them with the new data. Failure to find needed data here is an internal error.
|
and validate them with the new data. Note that if an answer needs multiple
|
||||||
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
|
keys to validate, we may find another key is needed, in which case we set off
|
||||||
return it to the original requestor. */
|
down another branch of the tree. Once we get to the original answer
|
||||||
|
(FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */
|
||||||
while (forward->dependent)
|
while (forward->dependent)
|
||||||
{
|
{
|
||||||
struct frec *prev = forward->dependent;
|
struct frec *prev = forward->dependent;
|
||||||
@@ -887,18 +893,23 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
|
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
|
||||||
|
|
||||||
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
||||||
{
|
goto anotherkey;
|
||||||
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
|
|
||||||
status = STAT_INSECURE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == STAT_TRUNCATED)
|
if (status == STAT_TRUNCATED)
|
||||||
header->hb3 |= HB3_TC;
|
header->hb3 |= HB3_TC;
|
||||||
else
|
else
|
||||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
|
{
|
||||||
status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
char *result;
|
||||||
|
|
||||||
|
if (forward->work_counter == 0)
|
||||||
|
result = "ABANDONED";
|
||||||
|
else
|
||||||
|
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
||||||
|
|
||||||
|
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||||
|
}
|
||||||
|
|
||||||
no_cache_dnssec = 0;
|
no_cache_dnssec = 0;
|
||||||
|
|
||||||
@@ -906,17 +917,17 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
cache_secure = 1;
|
cache_secure = 1;
|
||||||
else if (status == STAT_BOGUS)
|
else if (status == STAT_BOGUS)
|
||||||
no_cache_dnssec = 1;
|
no_cache_dnssec = 1;
|
||||||
|
|
||||||
/* restore CD bit to the value in the query */
|
|
||||||
if (forward->flags & FREC_CHECKING_DISABLED)
|
|
||||||
header->hb4 |= HB4_CD;
|
|
||||||
else
|
|
||||||
header->hb4 &= ~HB4_CD;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* restore CD bit to the value in the query */
|
||||||
|
if (forward->flags & FREC_CHECKING_DISABLED)
|
||||||
|
header->hb4 |= HB4_CD;
|
||||||
|
else
|
||||||
|
header->hb4 &= ~HB4_CD;
|
||||||
|
|
||||||
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
|
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
|
||||||
forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
||||||
{
|
{
|
||||||
header->id = htons(forward->orig_id);
|
header->id = htons(forward->orig_id);
|
||||||
header->hb4 |= HB4_RA; /* recursion if available */
|
header->hb4 |= HB4_RA; /* recursion if available */
|
||||||
@@ -1117,12 +1128,10 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
|
|
||||||
if (extract_request(header, (size_t)n, daemon->namebuff, &type))
|
if (extract_request(header, (size_t)n, daemon->namebuff, &type))
|
||||||
{
|
{
|
||||||
char types[20];
|
|
||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
struct auth_zone *zone;
|
struct auth_zone *zone;
|
||||||
#endif
|
#endif
|
||||||
|
char *types = querystr(auth_dns ? "auth" : "query", type);
|
||||||
querystr(auth_dns ? "auth" : "query", types, type);
|
|
||||||
|
|
||||||
if (listen->family == AF_INET)
|
if (listen->family == AF_INET)
|
||||||
log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff,
|
log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff,
|
||||||
@@ -1160,8 +1169,9 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
|
int ad_reqd;
|
||||||
m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
|
m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
|
||||||
dst_addr_4, netmask, now);
|
dst_addr_4, netmask, now, &ad_reqd);
|
||||||
|
|
||||||
if (m >= 1)
|
if (m >= 1)
|
||||||
{
|
{
|
||||||
@@ -1170,7 +1180,7 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
daemon->local_answer++;
|
daemon->local_answer++;
|
||||||
}
|
}
|
||||||
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
|
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
|
||||||
header, (size_t)n, now, NULL))
|
header, (size_t)n, now, NULL, ad_reqd))
|
||||||
daemon->queries_forwarded++;
|
daemon->queries_forwarded++;
|
||||||
else
|
else
|
||||||
daemon->local_answer++;
|
daemon->local_answer++;
|
||||||
@@ -1178,59 +1188,72 @@ void receive_query(struct listener *listen, time_t now)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
static int tcp_key_recurse(time_t now, int status, int class, char *keyname, struct server *server)
|
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 *keycount)
|
||||||
{
|
{
|
||||||
/* Recurse up the key heirarchy */
|
/* Recurse up the key heirarchy */
|
||||||
size_t n;
|
|
||||||
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
|
||||||
unsigned char *payload = &packet[2];
|
|
||||||
struct dns_header *header = (struct dns_header *)payload;
|
|
||||||
u16 *length = (u16 *)packet;
|
|
||||||
int new_status;
|
int new_status;
|
||||||
unsigned char c1, c2;
|
|
||||||
|
|
||||||
n = dnssec_generate_query(header, ((char *) header) + 65536, keyname, class,
|
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
||||||
status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
|
if (--(*keycount) == 0)
|
||||||
|
return STAT_INSECURE;
|
||||||
|
|
||||||
*length = htons(n);
|
if (status == STAT_NEED_KEY)
|
||||||
|
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
||||||
if (!read_write(server->tcpfd, packet, n + sizeof(u16), 0) ||
|
else if (status == STAT_NEED_DS)
|
||||||
!read_write(server->tcpfd, &c1, 1, 1) ||
|
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||||
!read_write(server->tcpfd, &c2, 1, 1) ||
|
|
||||||
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
|
|
||||||
{
|
|
||||||
close(server->tcpfd);
|
|
||||||
server->tcpfd = -1;
|
|
||||||
new_status = STAT_INSECURE;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
|
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
|
||||||
|
|
||||||
|
/* Can't validate because we need a key/DS whose name now in keyname.
|
||||||
|
Make query for same, and recurse to validate */
|
||||||
|
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
||||||
{
|
{
|
||||||
n = (c1 << 8) | c2;
|
size_t m;
|
||||||
|
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||||
|
unsigned char *payload = &packet[2];
|
||||||
|
struct dns_header *new_header = (struct dns_header *)payload;
|
||||||
|
u16 *length = (u16 *)packet;
|
||||||
|
unsigned char c1, c2;
|
||||||
|
|
||||||
if (status == STAT_NEED_KEY)
|
if (!packet)
|
||||||
new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
return STAT_INSECURE;
|
||||||
|
|
||||||
|
another_tcp_key:
|
||||||
|
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
|
||||||
|
new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
|
||||||
|
|
||||||
|
*length = htons(m);
|
||||||
|
|
||||||
|
if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) ||
|
||||||
|
!read_write(server->tcpfd, &c1, 1, 1) ||
|
||||||
|
!read_write(server->tcpfd, &c2, 1, 1) ||
|
||||||
|
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
|
||||||
|
new_status = STAT_INSECURE;
|
||||||
else
|
else
|
||||||
new_status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
|
||||||
|
|
||||||
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
|
||||||
{
|
{
|
||||||
if ((new_status = tcp_key_recurse(now, new_status, class, daemon->keyname, server) == STAT_SECURE))
|
m = (c1 << 8) | c2;
|
||||||
|
|
||||||
|
if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
|
||||||
{
|
{
|
||||||
if (status == STAT_NEED_KEY)
|
/* Reached a validated record, now try again at this level.
|
||||||
new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
Note that we may get ANOTHER NEED_* if an answer needs more than one key.
|
||||||
|
If so, go round again. */
|
||||||
|
|
||||||
|
if (status == STAT_NEED_KEY)
|
||||||
|
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
||||||
|
else if (status == STAT_NEED_DS)
|
||||||
|
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||||
else
|
else
|
||||||
new_status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
|
||||||
|
|
||||||
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
||||||
{
|
goto another_tcp_key;
|
||||||
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
|
|
||||||
status = STAT_INSECURE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
free(packet);
|
free(packet);
|
||||||
|
}
|
||||||
|
|
||||||
return new_status;
|
return new_status;
|
||||||
}
|
}
|
||||||
@@ -1249,7 +1272,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
int local_auth = 0;
|
int local_auth = 0;
|
||||||
#endif
|
#endif
|
||||||
int checking_disabled, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
|
int checking_disabled, ad_question, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
|
||||||
size_t m;
|
size_t m;
|
||||||
unsigned short qtype;
|
unsigned short qtype;
|
||||||
unsigned int gotname;
|
unsigned int gotname;
|
||||||
@@ -1285,16 +1308,12 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
if ((checking_disabled = header->hb4 & HB4_CD))
|
if ((checking_disabled = header->hb4 & HB4_CD))
|
||||||
no_cache_dnssec = 1;
|
no_cache_dnssec = 1;
|
||||||
|
|
||||||
/* RFC 4035: sect 4.6 para 2 */
|
|
||||||
header->hb4 &= ~HB4_AD;
|
|
||||||
|
|
||||||
if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
|
if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
|
||||||
{
|
{
|
||||||
char types[20];
|
|
||||||
#ifdef HAVE_AUTH
|
#ifdef HAVE_AUTH
|
||||||
struct auth_zone *zone;
|
struct auth_zone *zone;
|
||||||
#endif
|
#endif
|
||||||
querystr(auth_dns ? "auth" : "query", types, qtype);
|
char *types = querystr(auth_dns ? "auth" : "query", qtype);
|
||||||
|
|
||||||
if (peer_addr.sa.sa_family == AF_INET)
|
if (peer_addr.sa.sa_family == AF_INET)
|
||||||
log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff,
|
log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff,
|
||||||
@@ -1331,7 +1350,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
{
|
{
|
||||||
/* m > 0 if answered from cache */
|
/* m > 0 if answered from cache */
|
||||||
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
|
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
|
||||||
dst_addr_4, netmask, now);
|
dst_addr_4, netmask, now, &ad_question);
|
||||||
|
|
||||||
/* Do this by steam now we're not in the select() loop */
|
/* Do this by steam now we're not in the select() loop */
|
||||||
check_log_writer(NULL);
|
check_log_writer(NULL);
|
||||||
@@ -1412,7 +1431,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
if (option_bool(OPT_DNSSEC_VALID))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
{
|
{
|
||||||
size = add_do_bit(header, size, ((char *) header) + 65536);
|
size = add_do_bit(header, size, ((char *) header) + 65536);
|
||||||
header->hb4 |= HB4_CD;
|
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
||||||
|
this allows it to select auth servers when one is returning bad data. */
|
||||||
|
if (option_bool(OPT_DNSSEC_DEBUG))
|
||||||
|
header->hb4 |= HB4_CD;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1463,18 +1485,16 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
|
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
|
||||||
{
|
{
|
||||||
int class, status;
|
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||||
|
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
|
||||||
|
char *result;
|
||||||
|
|
||||||
status = dnssec_validate_reply(now, header, m, daemon->namebuff, daemon->keyname, &class);
|
if (keycount == 0)
|
||||||
|
result = "ABANDONED";
|
||||||
|
else
|
||||||
|
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
||||||
|
|
||||||
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||||
{
|
|
||||||
if ((status = tcp_key_recurse(now, status, class, daemon->keyname, last_server)) == STAT_SECURE)
|
|
||||||
status = dnssec_validate_reply(now, header, m, daemon->namebuff, daemon->keyname, &class);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
|
|
||||||
status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
|
||||||
|
|
||||||
if (status == STAT_BOGUS)
|
if (status == STAT_BOGUS)
|
||||||
no_cache_dnssec = 1;
|
no_cache_dnssec = 1;
|
||||||
@@ -1513,7 +1533,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
|
|
||||||
m = process_reply(header, now, last_server, (unsigned int)m,
|
m = process_reply(header, now, last_server, (unsigned int)m,
|
||||||
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
|
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
|
||||||
cache_secure, check_subnet, &peer_addr);
|
cache_secure, ad_question, check_subnet, &peer_addr);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+132
-80
@@ -1254,87 +1254,187 @@ void pre_allocate_sfds(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mark_servers(int flag)
|
||||||
|
{
|
||||||
|
struct server *serv;
|
||||||
|
|
||||||
|
/* mark everything with argument flag */
|
||||||
|
for (serv = daemon->servers; serv; serv = serv->next)
|
||||||
|
if (serv->flags & flag)
|
||||||
|
serv->flags |= SERV_MARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_servers(void)
|
||||||
|
{
|
||||||
|
struct server *serv, *tmp, **up;
|
||||||
|
|
||||||
|
/* unlink and free anything still marked. */
|
||||||
|
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
|
||||||
|
{
|
||||||
|
tmp = serv->next;
|
||||||
|
if (serv->flags & SERV_MARK)
|
||||||
|
{
|
||||||
|
server_gone(serv);
|
||||||
|
*up = serv->next;
|
||||||
|
if (serv->domain)
|
||||||
|
free(serv->domain);
|
||||||
|
free(serv);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
up = &serv->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_update_server(int flags,
|
||||||
|
union mysockaddr *addr,
|
||||||
|
union mysockaddr *source_addr,
|
||||||
|
const char *interface,
|
||||||
|
const char *domain)
|
||||||
|
{
|
||||||
|
struct server *serv, *next = NULL;
|
||||||
|
char *domain_str = NULL;
|
||||||
|
|
||||||
|
/* See if there is a suitable candidate, and unmark */
|
||||||
|
for (serv = daemon->servers; serv; serv = serv->next)
|
||||||
|
if (serv->flags & SERV_MARK)
|
||||||
|
{
|
||||||
|
if (domain)
|
||||||
|
{
|
||||||
|
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (serv->flags & SERV_HAS_DOMAIN)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serv)
|
||||||
|
{
|
||||||
|
domain_str = serv->domain;
|
||||||
|
next = serv->next;
|
||||||
|
}
|
||||||
|
else if ((serv = whine_malloc(sizeof (struct server))))
|
||||||
|
{
|
||||||
|
/* Not found, create a new one. */
|
||||||
|
if (domain && !(domain_str = whine_malloc(strlen(domain)+1)))
|
||||||
|
{
|
||||||
|
free(serv);
|
||||||
|
serv = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct server *s;
|
||||||
|
/* Add to the end of the chain, for order */
|
||||||
|
if (!daemon->servers)
|
||||||
|
daemon->servers = serv;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (s = daemon->servers; s->next; s = s->next);
|
||||||
|
s->next = serv;
|
||||||
|
}
|
||||||
|
if (domain)
|
||||||
|
strcpy(domain_str, domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serv)
|
||||||
|
{
|
||||||
|
memset(serv, 0, sizeof(struct server));
|
||||||
|
serv->flags = flags;
|
||||||
|
serv->domain = domain_str;
|
||||||
|
serv->next = next;
|
||||||
|
serv->queries = serv->failed_queries = 0;
|
||||||
|
|
||||||
|
if (domain)
|
||||||
|
serv->flags |= SERV_HAS_DOMAIN;
|
||||||
|
|
||||||
|
if (interface)
|
||||||
|
strcpy(serv->interface, interface);
|
||||||
|
if (addr)
|
||||||
|
serv->addr = *addr;
|
||||||
|
if (source_addr)
|
||||||
|
serv->source_addr = *source_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void check_servers(void)
|
void check_servers(void)
|
||||||
{
|
{
|
||||||
struct irec *iface;
|
struct irec *iface;
|
||||||
struct server *new, *tmp, *ret = NULL;
|
struct server *serv;
|
||||||
int port = 0;
|
int port = 0;
|
||||||
|
|
||||||
/* interface may be new since startup */
|
/* interface may be new since startup */
|
||||||
if (!option_bool(OPT_NOWILD))
|
if (!option_bool(OPT_NOWILD))
|
||||||
enumerate_interfaces(0);
|
enumerate_interfaces(0);
|
||||||
|
|
||||||
for (new = daemon->servers; new; new = tmp)
|
for (serv = daemon->servers; serv; serv = serv->next)
|
||||||
{
|
{
|
||||||
tmp = new->next;
|
if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)))
|
||||||
|
|
||||||
if (!(new->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)))
|
|
||||||
{
|
{
|
||||||
port = prettyprint_addr(&new->addr, daemon->namebuff);
|
port = prettyprint_addr(&serv->addr, daemon->namebuff);
|
||||||
|
|
||||||
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
|
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
|
||||||
if (new->addr.sa.sa_family == AF_INET &&
|
if (serv->addr.sa.sa_family == AF_INET &&
|
||||||
new->addr.in.sin_addr.s_addr == 0)
|
serv->addr.in.sin_addr.s_addr == 0)
|
||||||
{
|
{
|
||||||
free(new);
|
serv->flags |= SERV_MARK;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||||
if (sockaddr_isequal(&new->addr, &iface->addr))
|
if (sockaddr_isequal(&serv->addr, &iface->addr))
|
||||||
break;
|
break;
|
||||||
if (iface)
|
if (iface)
|
||||||
{
|
{
|
||||||
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
|
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
|
||||||
free(new);
|
serv->flags |= SERV_MARK;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do we need a socket set? */
|
/* Do we need a socket set? */
|
||||||
if (!new->sfd &&
|
if (!serv->sfd &&
|
||||||
!(new->sfd = allocate_sfd(&new->source_addr, new->interface)) &&
|
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) &&
|
||||||
errno != 0)
|
errno != 0)
|
||||||
{
|
{
|
||||||
my_syslog(LOG_WARNING,
|
my_syslog(LOG_WARNING,
|
||||||
_("ignoring nameserver %s - cannot make/bind socket: %s"),
|
_("ignoring nameserver %s - cannot make/bind socket: %s"),
|
||||||
daemon->namebuff, strerror(errno));
|
daemon->namebuff, strerror(errno));
|
||||||
free(new);
|
serv->flags |= SERV_MARK;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reverse order - gets it right. */
|
if (!(serv->flags & SERV_NO_REBIND))
|
||||||
new->next = ret;
|
|
||||||
ret = new;
|
|
||||||
|
|
||||||
if (!(new->flags & SERV_NO_REBIND))
|
|
||||||
{
|
{
|
||||||
if (new->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV))
|
if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV))
|
||||||
{
|
{
|
||||||
char *s1, *s2;
|
char *s1, *s2;
|
||||||
if (!(new->flags & SERV_HAS_DOMAIN))
|
if (!(serv->flags & SERV_HAS_DOMAIN))
|
||||||
s1 = _("unqualified"), s2 = _("names");
|
s1 = _("unqualified"), s2 = _("names");
|
||||||
else if (strlen(new->domain) == 0)
|
else if (strlen(serv->domain) == 0)
|
||||||
s1 = _("default"), s2 = "";
|
s1 = _("default"), s2 = "";
|
||||||
else
|
else
|
||||||
s1 = _("domain"), s2 = new->domain;
|
s1 = _("domain"), s2 = serv->domain;
|
||||||
|
|
||||||
if (new->flags & SERV_NO_ADDR)
|
if (serv->flags & SERV_NO_ADDR)
|
||||||
my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2);
|
my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2);
|
||||||
else if (new->flags & SERV_USE_RESOLV)
|
else if (serv->flags & SERV_USE_RESOLV)
|
||||||
my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2);
|
my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2);
|
||||||
else if (!(new->flags & SERV_LITERAL_ADDRESS))
|
else if (!(serv->flags & SERV_LITERAL_ADDRESS))
|
||||||
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2);
|
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2);
|
||||||
}
|
}
|
||||||
else if (new->interface[0] != 0)
|
else if (serv->interface[0] != 0)
|
||||||
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, new->interface);
|
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
|
||||||
else
|
else
|
||||||
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
|
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon->servers = ret;
|
cleanup_servers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return zero if no servers found, in that case we keep polling.
|
/* Return zero if no servers found, in that case we keep polling.
|
||||||
@@ -1343,9 +1443,6 @@ int reload_servers(char *fname)
|
|||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
char *line;
|
char *line;
|
||||||
struct server *old_servers = NULL;
|
|
||||||
struct server *new_servers = NULL;
|
|
||||||
struct server *serv;
|
|
||||||
int gotone = 0;
|
int gotone = 0;
|
||||||
|
|
||||||
/* buff happens to be MAXDNAME long... */
|
/* buff happens to be MAXDNAME long... */
|
||||||
@@ -1355,26 +1452,7 @@ int reload_servers(char *fname)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* move old servers to free list - we can reuse the memory
|
mark_servers(SERV_FROM_RESOLV);
|
||||||
and not risk malloc if there are the same or fewer new servers.
|
|
||||||
Servers which were specced on the command line go to the new list. */
|
|
||||||
for (serv = daemon->servers; serv;)
|
|
||||||
{
|
|
||||||
struct server *tmp = serv->next;
|
|
||||||
if (serv->flags & SERV_FROM_RESOLV)
|
|
||||||
{
|
|
||||||
serv->next = old_servers;
|
|
||||||
old_servers = serv;
|
|
||||||
/* forward table rules reference servers, so have to blow them away */
|
|
||||||
server_gone(serv);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
serv->next = new_servers;
|
|
||||||
new_servers = serv;
|
|
||||||
}
|
|
||||||
serv = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
|
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
|
||||||
{
|
{
|
||||||
@@ -1434,38 +1512,12 @@ int reload_servers(char *fname)
|
|||||||
continue;
|
continue;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (old_servers)
|
add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL);
|
||||||
{
|
|
||||||
serv = old_servers;
|
|
||||||
old_servers = old_servers->next;
|
|
||||||
}
|
|
||||||
else if (!(serv = whine_malloc(sizeof (struct server))))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* this list is reverse ordered:
|
|
||||||
it gets reversed again in check_servers */
|
|
||||||
serv->next = new_servers;
|
|
||||||
new_servers = serv;
|
|
||||||
serv->addr = addr;
|
|
||||||
serv->source_addr = source_addr;
|
|
||||||
serv->domain = NULL;
|
|
||||||
serv->interface[0] = 0;
|
|
||||||
serv->sfd = NULL;
|
|
||||||
serv->flags = SERV_FROM_RESOLV;
|
|
||||||
serv->queries = serv->failed_queries = 0;
|
|
||||||
gotone = 1;
|
gotone = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free any memory not used. */
|
|
||||||
while (old_servers)
|
|
||||||
{
|
|
||||||
struct server *tmp = old_servers->next;
|
|
||||||
free(old_servers);
|
|
||||||
old_servers = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon->servers = new_servers;
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
cleanup_servers();
|
||||||
|
|
||||||
return gotone;
|
return gotone;
|
||||||
}
|
}
|
||||||
|
|||||||
+172
-72
@@ -139,8 +139,10 @@ struct myoption {
|
|||||||
#define LOPT_QUIET_DHCP6 327
|
#define LOPT_QUIET_DHCP6 327
|
||||||
#define LOPT_QUIET_RA 328
|
#define LOPT_QUIET_RA 328
|
||||||
#define LOPT_SEC_VALID 329
|
#define LOPT_SEC_VALID 329
|
||||||
#define LOPT_DNSKEY 330
|
#define LOPT_TRUST_ANCHOR 330
|
||||||
#define LOPT_DNSSEC_DEBUG 331
|
#define LOPT_DNSSEC_DEBUG 331
|
||||||
|
#define LOPT_REV_SERV 332
|
||||||
|
#define LOPT_SERVERS_FILE 333
|
||||||
|
|
||||||
#ifdef HAVE_GETOPT_LONG
|
#ifdef HAVE_GETOPT_LONG
|
||||||
static const struct option opts[] =
|
static const struct option opts[] =
|
||||||
@@ -157,6 +159,7 @@ static const struct myoption opts[] =
|
|||||||
{ "user", 2, 0, 'u' },
|
{ "user", 2, 0, 'u' },
|
||||||
{ "group", 2, 0, 'g' },
|
{ "group", 2, 0, 'g' },
|
||||||
{ "resolv-file", 2, 0, 'r' },
|
{ "resolv-file", 2, 0, 'r' },
|
||||||
|
{ "servers-file", 1, 0, LOPT_SERVERS_FILE },
|
||||||
{ "mx-host", 1, 0, 'm' },
|
{ "mx-host", 1, 0, 'm' },
|
||||||
{ "mx-target", 1, 0, 't' },
|
{ "mx-target", 1, 0, 't' },
|
||||||
{ "cache-size", 2, 0, 'c' },
|
{ "cache-size", 2, 0, 'c' },
|
||||||
@@ -178,6 +181,7 @@ static const struct myoption opts[] =
|
|||||||
{ "pid-file", 2, 0, 'x' },
|
{ "pid-file", 2, 0, 'x' },
|
||||||
{ "strict-order", 0, 0, 'o' },
|
{ "strict-order", 0, 0, 'o' },
|
||||||
{ "server", 1, 0, 'S' },
|
{ "server", 1, 0, 'S' },
|
||||||
|
{ "rev-server", 1, 0, LOPT_REV_SERV },
|
||||||
{ "local", 1, 0, LOPT_LOCAL },
|
{ "local", 1, 0, LOPT_LOCAL },
|
||||||
{ "address", 1, 0, 'A' },
|
{ "address", 1, 0, 'A' },
|
||||||
{ "conf-file", 2, 0, 'C' },
|
{ "conf-file", 2, 0, 'C' },
|
||||||
@@ -277,7 +281,7 @@ static const struct myoption opts[] =
|
|||||||
{ "ipset", 1, 0, LOPT_IPSET },
|
{ "ipset", 1, 0, LOPT_IPSET },
|
||||||
{ "synth-domain", 1, 0, LOPT_SYNTH },
|
{ "synth-domain", 1, 0, LOPT_SYNTH },
|
||||||
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
||||||
{ "dnskey", 1, 0, LOPT_DNSKEY },
|
{ "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
|
||||||
{ "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
|
{ "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
|
||||||
#ifdef OPTION6_PREFIX_CLASS
|
#ifdef OPTION6_PREFIX_CLASS
|
||||||
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
|
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
|
||||||
@@ -347,7 +351,9 @@ static struct {
|
|||||||
{ 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
|
{ 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
|
||||||
{ 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
|
{ 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
|
||||||
{ 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE },
|
{ 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE },
|
||||||
|
{ LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
|
||||||
{ 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL },
|
{ 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL },
|
||||||
|
{ LOPT_REV_SERV, ARG_DUP, "<addr>/<prefix>,<ipaddr>", gettext_noop("Specify address of upstream servers for reverse address queries"), NULL },
|
||||||
{ LOPT_LOCAL, ARG_DUP, "/<domain>/", gettext_noop("Never forward queries to specified domains."), NULL },
|
{ LOPT_LOCAL, ARG_DUP, "/<domain>/", gettext_noop("Never forward queries to specified domains."), NULL },
|
||||||
{ 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL },
|
{ 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL },
|
||||||
{ 't', ARG_ONE, "<host_name>", gettext_noop("Specify default target in an MX record."), NULL },
|
{ 't', ARG_ONE, "<host_name>", gettext_noop("Specify default target in an MX record."), NULL },
|
||||||
@@ -430,7 +436,7 @@ static struct {
|
|||||||
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
|
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
|
||||||
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
|
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
|
||||||
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
|
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
|
||||||
{ LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL },
|
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
|
||||||
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
|
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
|
||||||
#ifdef OPTION6_PREFIX_CLASS
|
#ifdef OPTION6_PREFIX_CLASS
|
||||||
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
|
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
|
||||||
@@ -591,6 +597,18 @@ static int atoi_check16(char *a, int *res)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
static int atoi_check8(char *a, int *res)
|
||||||
|
{
|
||||||
|
if (!(atoi_check(a, res)) ||
|
||||||
|
*res < 0 ||
|
||||||
|
*res > 0xff)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void add_txt(char *name, char *txt)
|
static void add_txt(char *name, char *txt)
|
||||||
{
|
{
|
||||||
size_t len = strlen(txt);
|
size_t len = strlen(txt);
|
||||||
@@ -674,6 +692,13 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
|
|||||||
char *scope_id;
|
char *scope_id;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!arg || strlen(arg) == 0)
|
||||||
|
{
|
||||||
|
*flags |= SERV_NO_ADDR;
|
||||||
|
*interface = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if ((source = split_chr(arg, '@')) && /* is there a source. */
|
if ((source = split_chr(arg, '@')) && /* is there a source. */
|
||||||
(portno = split_chr(source, '#')) &&
|
(portno = split_chr(source, '#')) &&
|
||||||
!atoi_check16(portno, &source_port))
|
!atoi_check16(portno, &source_port))
|
||||||
@@ -752,6 +777,54 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct server *add_rev4(struct in_addr addr, int msize)
|
||||||
|
{
|
||||||
|
struct server *serv = opt_malloc(sizeof(struct server));
|
||||||
|
in_addr_t a = ntohl(addr.s_addr) >> 8;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
memset(serv, 0, sizeof(struct server));
|
||||||
|
p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */
|
||||||
|
|
||||||
|
if (msize == 24)
|
||||||
|
p += sprintf(p, "%d.", a & 0xff);
|
||||||
|
a = a >> 8;
|
||||||
|
if (msize != 8)
|
||||||
|
p += sprintf(p, "%d.", a & 0xff);
|
||||||
|
a = a >> 8;
|
||||||
|
p += sprintf(p, "%d.in-addr.arpa", a & 0xff);
|
||||||
|
|
||||||
|
serv->flags = SERV_HAS_DOMAIN;
|
||||||
|
serv->next = daemon->servers;
|
||||||
|
daemon->servers = serv;
|
||||||
|
|
||||||
|
return serv;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct server *add_rev6(struct in6_addr *addr, int msize)
|
||||||
|
{
|
||||||
|
struct server *serv = opt_malloc(sizeof(struct server));
|
||||||
|
char *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memset(serv, 0, sizeof(struct server));
|
||||||
|
p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
|
||||||
|
|
||||||
|
for (i = msize-1; i >= 0; i -= 4)
|
||||||
|
{
|
||||||
|
int dig = ((unsigned char *)addr)[i>>3];
|
||||||
|
p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
|
||||||
|
}
|
||||||
|
p += sprintf(p, "ip6.arpa");
|
||||||
|
|
||||||
|
serv->flags = SERV_HAS_DOMAIN;
|
||||||
|
serv->next = daemon->servers;
|
||||||
|
daemon->servers = serv;
|
||||||
|
|
||||||
|
return serv;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
|
|
||||||
static int is_tag_prefix(char *arg)
|
static int is_tag_prefix(char *arg)
|
||||||
@@ -1315,7 +1388,7 @@ void reset_option_bool(unsigned int opt)
|
|||||||
daemon->options2 &= ~(1u << (opt - 32));
|
daemon->options2 &= ~(1u << (opt - 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line)
|
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
char *comma;
|
char *comma;
|
||||||
@@ -1519,6 +1592,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case LOPT_SERVERS_FILE:
|
||||||
|
daemon->servers_file = opt_string_alloc(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'm': /* --mx-host */
|
case 'm': /* --mx-host */
|
||||||
{
|
{
|
||||||
int pref = 1;
|
int pref = 1;
|
||||||
@@ -1814,34 +1891,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
ret_err(gen_err);
|
ret_err(gen_err);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* generate the equivalent of
|
/* generate the equivalent of
|
||||||
local=/<domain>/
|
local=/<domain>/
|
||||||
local=/xxx.yyy.zzz.in-addr.arpa/ */
|
local=/xxx.yyy.zzz.in-addr.arpa/ */
|
||||||
struct server *serv = opt_malloc(sizeof(struct server));
|
struct server *serv = add_rev4(new->start, msize);
|
||||||
in_addr_t a = ntohl(new->start.s_addr) >> 8;
|
serv->flags |= SERV_NO_ADDR;
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(serv, 0, sizeof(struct server));
|
|
||||||
serv->domain = d;
|
|
||||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
|
||||||
serv->next = daemon->servers;
|
|
||||||
daemon->servers = serv;
|
|
||||||
|
|
||||||
serv = opt_malloc(sizeof(struct server));
|
|
||||||
memset(serv, 0, sizeof(struct server));
|
|
||||||
p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */
|
|
||||||
|
|
||||||
if (msize == 24)
|
|
||||||
p += sprintf(p, "%d.", a & 0xff);
|
|
||||||
a = a >> 8;
|
|
||||||
if (msize != 8)
|
|
||||||
p += sprintf(p, "%d.", a & 0xff);
|
|
||||||
a = a >> 8;
|
|
||||||
p += sprintf(p, "%d.in-addr.arpa", a & 0xff);
|
|
||||||
|
|
||||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
|
||||||
serv->next = daemon->servers;
|
|
||||||
daemon->servers = serv;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1877,29 +1931,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
/* generate the equivalent of
|
/* generate the equivalent of
|
||||||
local=/<domain>/
|
local=/<domain>/
|
||||||
local=/xxx.yyy.zzz.ip6.arpa/ */
|
local=/xxx.yyy.zzz.ip6.arpa/ */
|
||||||
struct server *serv = opt_malloc(sizeof(struct server));
|
struct server *serv = add_rev6(&new->start6, msize);
|
||||||
char *p;
|
serv->flags |= SERV_NO_ADDR;
|
||||||
|
|
||||||
memset(serv, 0, sizeof(struct server));
|
|
||||||
serv->domain = d;
|
|
||||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
|
||||||
serv->next = daemon->servers;
|
|
||||||
daemon->servers = serv;
|
|
||||||
|
|
||||||
serv = opt_malloc(sizeof(struct server));
|
|
||||||
memset(serv, 0, sizeof(struct server));
|
|
||||||
p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
|
|
||||||
|
|
||||||
for (i = msize-1; i >= 0; i -= 4)
|
|
||||||
{
|
|
||||||
int dig = ((unsigned char *)&new->start6)[i>>3];
|
|
||||||
p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
|
|
||||||
}
|
|
||||||
p += sprintf(p, "ip6.arpa");
|
|
||||||
|
|
||||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
|
||||||
serv->next = daemon->servers;
|
|
||||||
daemon->servers = serv;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2126,6 +2159,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
memset(newlist, 0, sizeof(struct server));
|
memset(newlist, 0, sizeof(struct server));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (servers_only && option == 'S')
|
||||||
|
newlist->flags |= SERV_FROM_FILE;
|
||||||
|
|
||||||
if (option == 'A')
|
if (option == 'A')
|
||||||
{
|
{
|
||||||
newlist->flags |= SERV_LITERAL_ADDRESS;
|
newlist->flags |= SERV_LITERAL_ADDRESS;
|
||||||
@@ -2170,6 +2206,40 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case LOPT_REV_SERV: /* --rev-server */
|
||||||
|
{
|
||||||
|
char *string;
|
||||||
|
int size;
|
||||||
|
struct server *serv;
|
||||||
|
struct in_addr addr4;
|
||||||
|
#ifdef HAVE_IPV6
|
||||||
|
struct in6_addr addr6;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unhide_metas(arg);
|
||||||
|
if (!arg || !(comma=split(arg)) || !(string = split_chr(arg, '/')) || !atoi_check(string, &size))
|
||||||
|
ret_err(gen_err);
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, arg, &addr4))
|
||||||
|
serv = add_rev4(addr4, size);
|
||||||
|
#ifdef HAVE_IPV6
|
||||||
|
else if (inet_pton(AF_INET6, arg, &addr6))
|
||||||
|
serv = add_rev6(&addr6, size);
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
ret_err(gen_err);
|
||||||
|
|
||||||
|
string = parse_server(comma, &serv->addr, &serv->source_addr, serv->interface, &serv->flags);
|
||||||
|
|
||||||
|
if (string)
|
||||||
|
ret_err(string);
|
||||||
|
|
||||||
|
if (servers_only)
|
||||||
|
serv->flags |= SERV_FROM_FILE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case LOPT_IPSET: /* --ipset */
|
case LOPT_IPSET: /* --ipset */
|
||||||
#ifndef HAVE_IPSET
|
#ifndef HAVE_IPSET
|
||||||
ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
|
ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
|
||||||
@@ -3675,10 +3745,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
case LOPT_DNSKEY:
|
case LOPT_TRUST_ANCHOR:
|
||||||
{
|
{
|
||||||
struct dnskey *new = opt_malloc(sizeof(struct dnskey));
|
struct ds_config *new = opt_malloc(sizeof(struct ds_config));
|
||||||
char *key64, *algo = NULL;
|
char *cp, *cp1, *keyhex, *digest, *algo = NULL;
|
||||||
|
int len;
|
||||||
|
|
||||||
new->class = C_IN;
|
new->class = C_IN;
|
||||||
|
|
||||||
@@ -3700,19 +3771,29 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!comma || !algo || !(key64 = split(algo)) ||
|
if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
|
||||||
!atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
|
!atoi_check16(comma, &new->keytag) ||
|
||||||
|
!atoi_check8(algo, &new->algo) ||
|
||||||
|
!atoi_check8(digest, &new->digest_type) ||
|
||||||
!(new->name = canonicalise_opt(arg)))
|
!(new->name = canonicalise_opt(arg)))
|
||||||
ret_err(_("bad DNSKEY"));
|
ret_err(_("bad trust anchor"));
|
||||||
|
|
||||||
/* Upper bound on length */
|
/* Upper bound on length */
|
||||||
new->key = opt_malloc((3*strlen(key64)/4)+1);
|
len = (2*strlen(keyhex))+1;
|
||||||
unhide_metas(key64);
|
new->digest = opt_malloc(len);
|
||||||
if ((new->keylen = parse_base64(key64, new->key)) == -1)
|
unhide_metas(keyhex);
|
||||||
ret_err(_("bad base64 in DNSKEY"));
|
/* 4034: "Whitespace is allowed within digits" */
|
||||||
|
for (cp = keyhex; *cp; )
|
||||||
|
if (isspace(*cp))
|
||||||
|
for (cp1 = cp; *cp1; cp1++)
|
||||||
|
*cp1 = *(cp1+1);
|
||||||
|
else
|
||||||
|
cp++;
|
||||||
|
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
|
||||||
|
ret_err(_("bad HEX in trust anchor"));
|
||||||
|
|
||||||
new->next = daemon->dnskeys;
|
new->next = daemon->ds;
|
||||||
daemon->dnskeys = new;
|
daemon->ds = new;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3733,12 +3814,13 @@ static void read_file(char *file, FILE *f, int hard_opt)
|
|||||||
|
|
||||||
while (fgets(buff, MAXDNAME, f))
|
while (fgets(buff, MAXDNAME, f))
|
||||||
{
|
{
|
||||||
int white, i, option = hard_opt;
|
int white, i;
|
||||||
|
volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
|
||||||
char *errmess, *p, *arg = NULL, *start;
|
char *errmess, *p, *arg = NULL, *start;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
/* Memory allocation failure longjmps here if mem_recover == 1 */
|
/* Memory allocation failure longjmps here if mem_recover == 1 */
|
||||||
if (option != 0)
|
if (option != 0 || hard_opt == LOPT_REV_SERV)
|
||||||
{
|
{
|
||||||
if (setjmp(mem_jmp))
|
if (setjmp(mem_jmp))
|
||||||
continue;
|
continue;
|
||||||
@@ -3839,13 +3921,15 @@ static void read_file(char *file, FILE *f, int hard_opt)
|
|||||||
errmess = _("extraneous parameter");
|
errmess = _("extraneous parameter");
|
||||||
else if (opts[i].has_arg == 1 && !arg)
|
else if (opts[i].has_arg == 1 && !arg)
|
||||||
errmess = _("missing parameter");
|
errmess = _("missing parameter");
|
||||||
|
else if (hard_opt == LOPT_REV_SERV && option != 'S' && option != LOPT_REV_SERV)
|
||||||
|
errmess = _("illegal option");
|
||||||
}
|
}
|
||||||
|
|
||||||
oops:
|
oops:
|
||||||
if (errmess)
|
if (errmess)
|
||||||
strcpy(daemon->namebuff, errmess);
|
strcpy(daemon->namebuff, errmess);
|
||||||
|
|
||||||
if (errmess || !one_opt(option, arg, buff, _("error"), 0))
|
if (errmess || !one_opt(option, arg, buff, _("error"), 0, hard_opt == LOPT_REV_SERV))
|
||||||
{
|
{
|
||||||
sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
|
sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
|
||||||
if (hard_opt != 0)
|
if (hard_opt != 0)
|
||||||
@@ -4027,6 +4111,22 @@ struct hostsfile *expand_filelist(struct hostsfile *list)
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void read_servers_file(void)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
if (!(f = fopen(daemon->servers_file, "r")))
|
||||||
|
{
|
||||||
|
my_syslog(LOG_ERR, _("cannot read %s: %s"), daemon->servers_file, strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_servers(SERV_FROM_FILE);
|
||||||
|
cleanup_servers();
|
||||||
|
|
||||||
|
read_file(daemon->servers_file, f, LOPT_REV_SERV);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_DHCP
|
#ifdef HAVE_DHCP
|
||||||
void reread_dhcp(void)
|
void reread_dhcp(void)
|
||||||
@@ -4220,9 +4320,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef HAVE_GETOPT_LONG
|
#ifdef HAVE_GETOPT_LONG
|
||||||
if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1))
|
if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1, 0))
|
||||||
#else
|
#else
|
||||||
if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1))
|
if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1, 0))
|
||||||
#endif
|
#endif
|
||||||
die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF);
|
die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF);
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-81
@@ -337,7 +337,7 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen)
|
|||||||
return ansp;
|
return ansp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen)
|
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen)
|
||||||
{
|
{
|
||||||
int i, rdlen;
|
int i, rdlen;
|
||||||
|
|
||||||
@@ -1242,7 +1242,7 @@ int check_for_local_domain(char *name, time_t now)
|
|||||||
struct ptr_record *ptr;
|
struct ptr_record *ptr;
|
||||||
struct naptr *naptr;
|
struct naptr *naptr;
|
||||||
|
|
||||||
if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME)) &&
|
if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) &&
|
||||||
(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
|
(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -1452,7 +1452,8 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
|
|||||||
|
|
||||||
/* return zero if we can't answer from cache, or packet size if we can */
|
/* return zero if we can't answer from cache, or packet size if we can */
|
||||||
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||||
struct in_addr local_addr, struct in_addr local_netmask, time_t now)
|
struct in_addr local_addr, struct in_addr local_netmask,
|
||||||
|
time_t now, int *ad_reqd)
|
||||||
{
|
{
|
||||||
char *name = daemon->namebuff;
|
char *name = daemon->namebuff;
|
||||||
unsigned char *p, *ansp, *pheader;
|
unsigned char *p, *ansp, *pheader;
|
||||||
@@ -1468,10 +1469,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
struct mx_srv_record *rec;
|
struct mx_srv_record *rec;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
/* Don't return AD set even for local data if checking disabled. */
|
/* Don't return AD set if checking disabled. */
|
||||||
if (header->hb4 & HB4_CD)
|
if (header->hb4 & HB4_CD)
|
||||||
sec_data = 0;
|
sec_data = 0;
|
||||||
|
|
||||||
|
/* RFC 6840 5.7 */
|
||||||
|
*ad_reqd = header->hb4 & HB4_AD;
|
||||||
|
|
||||||
/* If there is an RFC2671 pseudoheader then it will be overwritten by
|
/* If there is an RFC2671 pseudoheader then it will be overwritten by
|
||||||
partial replies, so we have to do a dry run to see if we can answer
|
partial replies, so we have to do a dry run to see if we can answer
|
||||||
the query. We check to see if the do bit is set, if so we always
|
the query. We check to see if the do bit is set, if so we always
|
||||||
@@ -1490,6 +1494,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
GETSHORT(flags, pheader);
|
GETSHORT(flags, pheader);
|
||||||
|
|
||||||
sec_reqd = flags & 0x8000; /* do bit */
|
sec_reqd = flags & 0x8000; /* do bit */
|
||||||
|
*ad_reqd = 1;
|
||||||
|
|
||||||
/* If our client is advertising a larger UDP packet size
|
/* If our client is advertising a larger UDP packet size
|
||||||
than we allow, trim it so that we don't get an overlarge
|
than we allow, trim it so that we don't get an overlarge
|
||||||
@@ -1551,37 +1556,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS || qtype == T_RRSIG))
|
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS))
|
||||||
{
|
{
|
||||||
int gotone = 0, have_rrsig = 0;
|
int gotone = 0;
|
||||||
struct blockdata *keydata;
|
struct blockdata *keydata;
|
||||||
|
|
||||||
/* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */
|
/* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */
|
||||||
crecp = NULL;
|
if (sec_reqd)
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
|
||||||
if (crecp->uid == qclass && (qtype == T_RRSIG || crecp->addr.sig.type_covered == qtype))
|
|
||||||
{
|
|
||||||
have_rrsig = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qtype == T_RRSIG && have_rrsig)
|
|
||||||
{
|
{
|
||||||
ans = gotone = 1;
|
|
||||||
auth = 0;
|
|
||||||
}
|
|
||||||
else if (qtype == T_DS && have_rrsig)
|
|
||||||
{
|
|
||||||
auth = 0;
|
|
||||||
crecp = NULL;
|
crecp = NULL;
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
|
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||||
if (crecp->uid == qclass)
|
if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype)
|
||||||
{
|
break;
|
||||||
ans = gotone = 1;
|
}
|
||||||
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
|
|
||||||
|
if (!sec_reqd || crecp)
|
||||||
|
{
|
||||||
|
if (qtype == T_DS)
|
||||||
|
{
|
||||||
|
crecp = NULL;
|
||||||
|
while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
|
||||||
|
if (crecp->uid == qclass)
|
||||||
{
|
{
|
||||||
struct all_addr a;
|
gotone = 1;
|
||||||
a.addr.keytag = crecp->addr.ds.keytag;
|
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
|
||||||
|
{
|
||||||
|
struct all_addr a;
|
||||||
|
a.addr.keytag = crecp->addr.ds.keytag;
|
||||||
log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
|
log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
|
||||||
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||||
crec_ttl(crecp, now), &nameoffset,
|
crec_ttl(crecp, now), &nameoffset,
|
||||||
@@ -1589,20 +1590,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
crecp->addr.ds.keytag, crecp->addr.ds.algo, crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
|
crecp->addr.ds.keytag, crecp->addr.ds.algo, crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
|
||||||
anscount++;
|
anscount++;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else /* DNSKEY */
|
||||||
else if (qtype == T_DNSKEY)
|
{
|
||||||
{
|
crecp = NULL;
|
||||||
crecp = NULL;
|
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
|
if (crecp->uid == qclass)
|
||||||
if (crecp->uid == qclass)
|
|
||||||
{
|
|
||||||
if ((crecp->flags & F_CONFIG) || have_rrsig) /* Return configured keys without an RRISG */
|
|
||||||
{
|
{
|
||||||
if (!(crecp->flags & F_CONFIG))
|
gotone = 1;
|
||||||
auth = 0, gotone = 1;
|
|
||||||
ans = 1;
|
|
||||||
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
|
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
|
||||||
{
|
{
|
||||||
struct all_addr a;
|
struct all_addr a;
|
||||||
@@ -1615,30 +1612,27 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
anscount++;
|
anscount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now do RRSIGs */
|
/* Now do RRSIGs */
|
||||||
if (gotone)
|
if (gotone)
|
||||||
{
|
{
|
||||||
crecp = NULL;
|
ans = 1;
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
auth = 0;
|
||||||
if (crecp->uid == qclass && (qtype == T_RRSIG || (sec_reqd && crecp->addr.sig.type_covered == qtype)) &&
|
if (!dryrun && sec_reqd)
|
||||||
!dryrun &&
|
{
|
||||||
(keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
|
crecp = NULL;
|
||||||
{
|
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||||
if (qtype == T_RRSIG)
|
if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype &&
|
||||||
|
(keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
|
||||||
{
|
{
|
||||||
char types[20];
|
|
||||||
querystr("rrsig", types, crecp->addr.sig.type_covered);
|
|
||||||
log_query(F_RRNAME, name, NULL, types);
|
|
||||||
}
|
|
||||||
if ((keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)) &&
|
|
||||||
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||||
crec_ttl(crecp, now), &nameoffset,
|
crec_ttl(crecp, now), &nameoffset,
|
||||||
T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata))
|
T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata);
|
||||||
anscount++;
|
anscount++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1742,24 +1736,25 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
else if (crecp->flags & F_DNSSECOK)
|
else if (crecp->flags & F_DNSSECOK)
|
||||||
{
|
{
|
||||||
int gotsig = 0;
|
int gotsig = 0;
|
||||||
|
struct crec *rr_crec = NULL;
|
||||||
|
|
||||||
crecp = NULL;
|
while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DS | F_DNSKEY)))
|
|
||||||
{
|
{
|
||||||
if (crecp->addr.sig.type_covered == T_PTR && crecp->uid == C_IN)
|
if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN)
|
||||||
{
|
{
|
||||||
char *sigdata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL);
|
char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
|
||||||
gotsig = 1;
|
gotsig = 1;
|
||||||
|
|
||||||
if (!dryrun &&
|
if (!dryrun &&
|
||||||
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||||
crecp->ttd - now, &nameoffset,
|
rr_crec->ttd - now, &nameoffset,
|
||||||
T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata))
|
T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata))
|
||||||
anscount++;
|
anscount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Need to re-run original cache search */
|
|
||||||
crecp = gotsig ? cache_find_by_addr(NULL, &addr, now, is_arpa) : NULL;
|
if (!gotsig)
|
||||||
|
crecp = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1923,7 +1918,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cname_restart:
|
cname_restart:
|
||||||
if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME)))
|
if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME | (dryrun ? F_NO_RR : 0))))
|
||||||
{
|
{
|
||||||
int localise = 0;
|
int localise = 0;
|
||||||
|
|
||||||
@@ -1954,7 +1949,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
else if (crecp->flags & F_DNSSECOK)
|
else if (crecp->flags & F_DNSSECOK)
|
||||||
{
|
{
|
||||||
/* We're returning validated data, need to return the RRSIG too. */
|
/* We're returning validated data, need to return the RRSIG too. */
|
||||||
|
struct crec *rr_crec = NULL;
|
||||||
int sigtype = type;
|
int sigtype = type;
|
||||||
/* The signature may have expired even though the data is still in cache,
|
/* The signature may have expired even though the data is still in cache,
|
||||||
forward instead of answering from cache if so. */
|
forward instead of answering from cache if so. */
|
||||||
@@ -1963,23 +1958,23 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
if (crecp->flags & F_CNAME)
|
if (crecp->flags & F_CNAME)
|
||||||
sigtype = T_CNAME;
|
sigtype = T_CNAME;
|
||||||
|
|
||||||
crecp = NULL;
|
while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY)))
|
||||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DS | F_DNSKEY)))
|
|
||||||
{
|
{
|
||||||
if (crecp->addr.sig.type_covered == sigtype && crecp->uid == C_IN)
|
if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN)
|
||||||
{
|
{
|
||||||
char *sigdata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL);
|
char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL);
|
||||||
gotsig = 1;
|
gotsig = 1;
|
||||||
|
|
||||||
if (!dryrun &&
|
if (!dryrun &&
|
||||||
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||||
crecp->ttd - now, &nameoffset,
|
rr_crec->ttd - now, &nameoffset,
|
||||||
T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata))
|
T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata))
|
||||||
anscount++;
|
anscount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Need to re-run original cache search */
|
|
||||||
crecp = gotsig ? cache_find_by_name(NULL, name, now, flag | F_CNAME) : NULL;
|
if (!gotsig)
|
||||||
|
crecp = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -2072,7 +2067,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
if (qtype == T_CNAME || qtype == T_ANY)
|
if (qtype == T_CNAME || qtype == T_ANY)
|
||||||
{
|
{
|
||||||
if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
|
if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
|
||||||
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
|
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0)))))
|
||||||
{
|
{
|
||||||
if (!(crecp->flags & F_DNSSECOK))
|
if (!(crecp->flags & F_DNSSECOK))
|
||||||
sec_data = 0;
|
sec_data = 0;
|
||||||
@@ -2111,7 +2106,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) &&
|
if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) &&
|
||||||
cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP))
|
cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR))
|
||||||
{
|
{
|
||||||
ans = 1;
|
ans = 1;
|
||||||
if (!dryrun)
|
if (!dryrun)
|
||||||
@@ -2264,16 +2259,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|||||||
header->nscount = htons(0);
|
header->nscount = htons(0);
|
||||||
header->arcount = htons(addncount);
|
header->arcount = htons(addncount);
|
||||||
|
|
||||||
header->hb4 &= ~HB4_AD;
|
|
||||||
len = ansp - (unsigned char *)header;
|
len = ansp - (unsigned char *)header;
|
||||||
|
|
||||||
if (have_pseudoheader)
|
if (have_pseudoheader)
|
||||||
{
|
len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
|
||||||
len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
|
|
||||||
if (sec_reqd && sec_data)
|
|
||||||
header->hb4 |= HB4_AD;
|
|
||||||
|
|
||||||
}
|
if (*ad_reqd && sec_data)
|
||||||
|
header->hb4 |= HB4_AD;
|
||||||
|
else
|
||||||
|
header->hb4 &= ~HB4_AD;
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|||||||
-60
@@ -482,66 +482,6 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
|
||||||
static int charval(char c)
|
|
||||||
{
|
|
||||||
if (c >= 'A' && c <= 'Z')
|
|
||||||
return c - 'A';
|
|
||||||
|
|
||||||
if (c >= 'a' && c <= 'z')
|
|
||||||
return c - 'a' + 26;
|
|
||||||
|
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
return c - '0' + 52;
|
|
||||||
|
|
||||||
if (c == '+')
|
|
||||||
return 62;
|
|
||||||
|
|
||||||
if (c == '/')
|
|
||||||
return 63;
|
|
||||||
|
|
||||||
if (c == '=')
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int parse_base64(char *in, char *out)
|
|
||||||
{
|
|
||||||
char *p = out;
|
|
||||||
int i, val[4];
|
|
||||||
|
|
||||||
while (*in)
|
|
||||||
{
|
|
||||||
for (i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
while (*in == ' ')
|
|
||||||
in++;
|
|
||||||
if (*in == 0)
|
|
||||||
return -1;
|
|
||||||
if ((val[i] = charval(*in++)) == -2)
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (*in == ' ')
|
|
||||||
in++;
|
|
||||||
|
|
||||||
if (val[1] == -1)
|
|
||||||
return -1; /* too much padding */
|
|
||||||
|
|
||||||
*p++ = (val[0] << 2) | (val[1] >> 4);
|
|
||||||
|
|
||||||
if (val[2] != -1)
|
|
||||||
*p++ = (val[1] << 4) | ( val[2] >> 2);
|
|
||||||
|
|
||||||
if (val[3] != -1)
|
|
||||||
*p++ = (val[2] << 6) | val[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
return p - out;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* return 0 for no match, or (no matched octets) + 1 */
|
/* return 0 for no match, or (no matched octets) + 1 */
|
||||||
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
|
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
|
||||||
{
|
{
|
||||||
|
|||||||
+6
-5
@@ -1,8 +1,9 @@
|
|||||||
# The root DNSSEC trust anchors, valid as at 30/01/2014
|
# The root DNSSEC trust anchor, valid as at 30/01/2014
|
||||||
|
|
||||||
|
# Note that this is a DS record (ie a hash of the root Zone Signing Key)
|
||||||
|
# If was downloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||||
|
|
||||||
|
trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
|
||||||
|
|
||||||
|
|
||||||
dnskey=.,257,8,AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0=
|
|
||||||
dnskey=.,256,8,AwEAAb8sU6pbYMWRbkRnEuEZw9NSir707TkOcF+UL1XiK4NDJOvXRyX1 95Am5dQ7bRnnuySZ3daf37vvjUUhuIWUAQ4stht8nJfYxVQXDYjSpGH5 I6Hf/0CZEoNP6cNvrQ7AFmKkmv00xWExKQjbvnRPI4bqpMwtHVzn6Wyb BZ6kuqED
|
|
||||||
dnskey=.,256,8,AwEAAYRU41/8smgAvuSojEP4jaj5Yll7WPaUKpYvnz2pnX2VIvRn4jsy Jns80bloenG6X9ebJVy2CFtZQLKHP8DcKmIFotdgs2HolyocY1am/+33 4RtzusM2ojkhjn1FRGtuSE9s2TSz1ISv0yVnFyu+EP/ZkiWnDfWeVrJI SEWBEr4V
|
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user