diff --git a/CHANGELOG b/CHANGELOG
index 785eea4..b218821 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,16 @@ version 2.69
Fix infinite loop associated with some --bogus-nxdomain
configs. Thanks fogobogo for the bug report.
+ Fix missing RA RDNS option with configuration like
+ --dhcp-option=option6:23,[::] Thanks to Tsachi Kimeldorfer
+ for spotting the problem.
+
+ Add [fd00::] and [fe80::] as special addresses in DHCPv6
+ options, analogous to [::]. [fd00::] is replaced with the
+ actual ULA of the interface on the machine running
+ dnsmasq, [fe80::] with the link-local address.
+ Thanks to Tsachi Kimeldorfer for championing this.
+
version 2.68
Use random addresses for DHCPv6 temporary address
diff --git a/Makefile b/Makefile
index be809d5..92158e0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+# dnsmasq is Copyright (c) 2000-2014 Simon Kelley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -59,13 +59,16 @@ ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFI
ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.1`
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.1`
+sec_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags libcrypto`
+sec_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs libcrypto`
sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
- dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o domain.o
+ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+ domain.o dnssec.o dnssec-openssl.o blockdata.o
hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
dns-protocol.h radv-protocol.h
@@ -73,8 +76,8 @@ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
all : $(BUILDDIR)
@cd $(BUILDDIR) && $(MAKE) \
top="$(top)" \
- build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags)" \
- build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs)" \
+ build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(sec_cflags)" \
+ build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(sec_libs)" \
-f $(top)/Makefile dnsmasq
mostly_clean :
diff --git a/bld/Android.mk b/bld/Android.mk
index 46e4d03..309d178 100644
--- a/bld/Android.mk
+++ b/bld/Android.mk
@@ -8,7 +8,8 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
netlink.c network.c option.c rfc1035.c \
rfc2131.c tftp.c util.c conntrack.c \
dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
- radv.c slaac.c auth.c ipset.c domain.c
+ radv.c slaac.c auth.c ipset.c domain.c \
+ dnssec.c dnssec-openssl.c blockdata.c
LOCAL_MODULE := dnsmasq
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index e85d272..42d3d5c 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -917,9 +917,11 @@ and to set the time-server address to 192.168.0.4, do
.B --dhcp-option = 42,192.168.0.4
or
.B --dhcp-option = option:ntp-server, 192.168.0.4
-The special address 0.0.0.0 (or [::] for DHCPv6) is taken to mean "the address of the
-machine running dnsmasq". Data types allowed are comma separated
-dotted-quad IP addresses, a decimal number, colon-separated hex digits
+The special address 0.0.0.0 is taken to mean "the address of the
+machine running dnsmasq".
+
+Data types allowed are comma separated
+dotted-quad IPv4 addresses, []-wrapped IPv6 addresses, a decimal number, colon-separated hex digits
and a text string. If the optional tags are given then
this option is only sent when all the tags are matched.
@@ -935,7 +937,9 @@ keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg.
.B --dhcp-option=option6:ntp-server,[1234::56]
-
+For IPv6, [::] means "the global address of
+the machine running dnsmasq", whilst [fd00::] is replaced with the
+ULA, if it exists, and [fe80::] with the link-local address.
Be careful: no checking is done that the correct type of data for the
option number is sent, it is quite possible to
diff --git a/src/auth.c b/src/auth.c
index d31ed60..d6fdef6 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/blockdata.c b/src/blockdata.c
new file mode 100644
index 0000000..f9155d9
--- /dev/null
+++ b/src/blockdata.c
@@ -0,0 +1,119 @@
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
+static struct blockdata *keyblock_free = NULL;
+static unsigned int blockdata_count = 0, blockdata_hwm = 0;
+
+void blockdata_report(void)
+{
+ my_syslog(LOG_INFO, _("DNSSEC memory in use %u, max %u"),
+ blockdata_count * sizeof(struct blockdata), blockdata_hwm * sizeof(struct blockdata));
+}
+
+struct blockdata *blockdata_alloc(char *data, size_t len)
+{
+ struct blockdata *block, *ret = NULL;
+ struct blockdata **prev = &ret;
+ size_t blen;
+
+ while (len > 0)
+ {
+ if (keyblock_free)
+ {
+ block = keyblock_free;
+ keyblock_free = block->next;
+ blockdata_count++;
+ }
+ else if ((block = whine_malloc(sizeof(struct blockdata))))
+ {
+ blockdata_count++;
+ if (blockdata_hwm < blockdata_count)
+ blockdata_hwm = blockdata_count;
+ }
+
+ if (!block)
+ {
+ /* failed to alloc, free partial chain */
+ blockdata_free(ret);
+ return NULL;
+ }
+
+ blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ memcpy(block->key, data, blen);
+ data += blen;
+ len -= blen;
+ *prev = block;
+ prev = &block->next;
+ block->next = NULL;
+ }
+
+ return ret;
+}
+
+size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt)
+{
+ if (*p == NULL)
+ *p = (*key)->key;
+ else if (*p == (*key)->key + KEYBLOCK_LEN)
+ {
+ *key = (*key)->next;
+ if (*key == NULL)
+ return 0;
+ *p = (*key)->key;
+ }
+
+ return MIN(cnt, (size_t)((*key)->key + KEYBLOCK_LEN - (*p)));
+}
+
+void blockdata_free(struct blockdata *blocks)
+{
+ struct blockdata *tmp;
+
+ if (blocks)
+ {
+ for (tmp = blocks; tmp->next; tmp = tmp->next)
+ blockdata_count--;
+ tmp->next = keyblock_free;
+ keyblock_free = blocks;
+ blockdata_count--;
+ }
+}
+
+/* copy blocks into data[], return 1 if data[] unchanged by so doing */
+int blockdata_retrieve(struct blockdata *block, size_t len, void *data)
+{
+ size_t blen;
+ struct blockdata *b;
+ int match = 1;
+
+ for (b = block; len > 0 && b; b = b->next)
+ {
+ blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ if (memcmp(data, b->key, blen) != 0)
+ match = 0;
+ memcpy(data, b->key, blen);
+ data += blen;
+ len -= blen;
+ }
+
+ return match;
+}
+
+#endif
diff --git a/src/bpf.c b/src/bpf.c
index a3c5ad4..f9c6063 100644
--- a/src/bpf.c
+++ b/src/bpf.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/cache.c b/src/cache.c
index 43a7ce9..fbdcae7 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,9 +25,6 @@ static int cache_inserted = 0, cache_live_freed = 0, insert_error;
static union bigname *big_free = NULL;
static int bignames_left, hash_size;
static int uid = 1;
-#ifdef HAVE_DNSSEC
-static struct keydata *keyblock_free = NULL;
-#endif
/* type->string mapping: this is also used by the name-hash function as a mixing table. */
static const struct {
@@ -56,6 +53,8 @@ static const struct {
{ 38, "A6" },
{ 39, "DNAME" },
{ 41, "OPT" },
+ { 43, "DS" },
+ { 46, "RRSIG" },
{ 48, "DNSKEY" },
{ 249, "TKEY" },
{ 250, "TSIG" },
@@ -198,7 +197,7 @@ static void cache_free(struct crec *crecp)
}
#ifdef HAVE_DNSSEC
else if (crecp->flags & (F_DNSKEY | F_DS))
- keydata_free(crecp->addr.key.keydata);
+ blockdata_free(crecp->addr.key.keydata);
#endif
}
@@ -693,7 +692,7 @@ static void add_hosts_cname(struct crec *target)
if (hostname_isequal(cache_get_name(target), a->target) &&
(crec = whine_malloc(sizeof(struct crec))))
{
- crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME;
+ crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME | F_DNSSECOK;
crec->name.namep = a->alias;
crec->addr.cname.target.cache = target;
crec->addr.cname.uid = target->uid;
@@ -830,14 +829,14 @@ static int read_hostsfile(char *filename, int index, int cache_size, struct crec
if (inet_pton(AF_INET, token, &addr) > 0)
{
- flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
+ flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_DNSSECOK;
addrlen = INADDRSZ;
domain_suffix = get_domain(addr.addr.addr4);
}
#ifdef HAVE_IPV6
else if (inet_pton(AF_INET6, token, &addr) > 0)
{
- flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
+ flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_DNSSECOK;
addrlen = IN6ADDRSZ;
domain_suffix = get_domain6(&addr.addr.addr6);
}
@@ -916,12 +915,19 @@ void cache_reload(void)
struct name_list *nl;
struct cname *a;
struct interface_name *intr;
+#ifdef HAVE_DNSSEC
+ struct dnskey *key;
+#endif
cache_inserted = cache_live_freed = 0;
for (i=0; iflags & (F_DNSKEY | F_DS))
+ blockdata_free(cache->addr.key.keydata);
+#endif
tmp = cache->hash_next;
if (cache->flags & (F_HOSTS | F_CONFIG))
{
@@ -948,13 +954,27 @@ void cache_reload(void)
if (hostname_isequal(a->target, intr->name) &&
((cache = whine_malloc(sizeof(struct crec)))))
{
- cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+ cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK;
cache->name.namep = a->alias;
cache->addr.cname.target.int_name = intr;
cache->addr.cname.uid = -1;
cache_hash(cache);
add_hosts_cname(cache); /* handle chains */
}
+
+#ifdef HAVE_DNSSEC
+ for (key = daemon->dnskeys; key; key = key->next)
+ if ((cache = whine_malloc(sizeof(struct crec))) &&
+ (cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
+ {
+ cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
+ cache->name.namep = key->name;
+ cache->uid = key->keylen;
+ cache->addr.key.algo = key->algo;
+ cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
+ cache_hash(cache);
+ }
+#endif
/* borrow the packet buffer for a temporary by-address hash */
memset(daemon->packet, 0, daemon->packet_buff_sz);
@@ -970,7 +990,7 @@ void cache_reload(void)
(cache = whine_malloc(sizeof(struct crec))))
{
cache->name.namep = nl->name;
- cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG | F_DNSSECOK;
add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
}
#ifdef HAVE_IPV6
@@ -978,7 +998,7 @@ void cache_reload(void)
(cache = whine_malloc(sizeof(struct crec))))
{
cache->name.namep = nl->name;
- cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG | F_DNSSECOK;
add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
}
#endif
@@ -1048,7 +1068,7 @@ static void add_dhcp_cname(struct crec *target, time_t ttd)
if (aliasc)
{
- aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG;
+ aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG | F_DNSSECOK;
if (ttd == 0)
aliasc->flags |= F_IMMORTAL;
else
@@ -1136,7 +1156,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
if (crec) /* malloc may fail */
{
- crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
+ crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD | F_DNSSECOK;
if (ttd == 0)
crec->flags |= F_IMMORTAL;
else
@@ -1164,6 +1184,9 @@ void dump_cache(time_t now)
#ifdef HAVE_AUTH
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer);
#endif
+#ifdef HAVE_DNSSEC
+ blockdata_report();
+#endif
/* sum counts from different records for same server */
for (serv = daemon->servers; serv; serv = serv->next)
@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
for (i=0; ihash_next)
{
- char *a, *p = daemon->namebuff;
- p += sprintf(p, "%-40.40s ", cache_get_name(cache));
- if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD))
- a = "";
- else if (cache->flags & F_CNAME)
- {
- a = "";
- if (!is_outdated_cname_pointer(cache))
- a = cache_get_cname_target(cache);
- }
+ char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
+ *a = 0;
+ if (strlen(n) == 0)
+ n = "";
+ p += sprintf(p, "%-40.40s ", n);
+ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+ a = cache_get_cname_target(cache);
#ifdef HAVE_DNSSEC
else if (cache->flags & F_DNSKEY)
{
@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
else if (cache->flags & F_DS)
{
a = daemon->addrbuff;
- sprintf(a, "%5u %3u %3u %u", cache->addr.key.flags_or_keyid,
- cache->addr.key.algo, cache->addr.key.digest, cache->uid);
+ sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+ cache->addr.key.algo, cache->addr.key.digest);
}
#endif
- else
+ else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
{
a = daemon->addrbuff;
if (cache->flags & F_IPV4)
@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
if (addr)
{
+ if (flags & F_KEYTAG)
+ sprintf(daemon->addrbuff, arg, addr->addr.keytag);
+ else
+ {
#ifdef HAVE_IPV6
- inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
- addr, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+ addr, daemon->addrbuff, ADDRSTRLEN);
#else
- strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
+ strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
#endif
+ }
}
+ else
+ dest = arg;
if (flags & F_REVERSE)
{
@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg;
else if (flags & F_UPSTREAM)
source = "reply";
+ else if (flags & F_SECSTAT)
+ source = "validation";
else if (flags & F_AUTH)
source = "auth";
else if (flags & F_SERVER)
@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg;
verb = "from";
}
+ else if (flags & F_DNSSEC)
+ {
+ source = arg;
+ verb = "to";
+ }
else
source = "cached";
@@ -1360,50 +1394,4 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);
}
-#ifdef HAVE_DNSSEC
-struct keydata *keydata_alloc(char *data, size_t len)
-{
- struct keydata *block, *ret = NULL;
- struct keydata **prev = &ret;
- while (len > 0)
- {
- if (keyblock_free)
- {
- block = keyblock_free;
- keyblock_free = block->next;
- }
- else
- block = whine_malloc(sizeof(struct keydata));
-
- if (!block)
- {
- /* failed to alloc, free partial chain */
- keydata_free(ret);
- return NULL;
- }
-
- memcpy(block->key, data, len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len);
- data += KEYBLOCK_LEN;
- len -= KEYBLOCK_LEN;
- *prev = block;
- prev = &block->next;
- block->next = NULL;
- }
-
- return ret;
-}
-
-void keydata_free(struct keydata *blocks)
-{
- struct keydata *tmp;
-
- if (blocks)
- {
- for (tmp = blocks; tmp->next; tmp = tmp->next);
- tmp->next = keyblock_free;
- keyblock_free = blocks;
- }
-}
-#endif
-
-
+
diff --git a/src/config.h b/src/config.h
index 60de687..c9870d4 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -18,7 +18,7 @@
#define MAX_PROCS 20 /* max no children for TCP requests */
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
-#define KEYBLOCK_LEN 140 /* choose to mininise fragmentation when storing DNSSEC keys */
+#define KEYBLOCK_LEN 35 /* choose to mininise fragmentation when storing DNSSEC keys */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
#define FORWARD_TIME 20 /* or 20 seconds */
@@ -139,6 +139,7 @@ RESOLVFILE
/* #define HAVE_DBUS */
/* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */
+/* #define HAVE_DNSSEC */
/* Default locations for important system files. */
@@ -384,7 +385,12 @@ static char *compile_opts =
#ifndef HAVE_AUTH
"no-"
#endif
- "auth";
+"auth "
+#ifndef HAVE_DNSSEC
+"no-"
+#endif
+"DNSSEC";
+
#endif
diff --git a/src/conntrack.c b/src/conntrack.c
index f8105b2..6a5133a 100644
--- a/src/conntrack.c
+++ b/src/conntrack.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dbus.c b/src/dbus.c
index da28a28..5e9fdc9 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index 3f9979e..9d13ac8 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h
index a2750de..4c09614 100644
--- a/src/dhcp-protocol.h
+++ b/src/dhcp-protocol.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp.c b/src/dhcp.c
index 67a82b5..92ea92b 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h
index f8e98ec..5927dc3 100644
--- a/src/dhcp6-protocol.h
+++ b/src/dhcp6-protocol.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 9d4f450..3a83c18 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -23,7 +23,7 @@
struct iface_param {
struct dhcp_context *current;
struct dhcp_relay *relay;
- struct in6_addr fallback, relay_local;
+ struct in6_addr fallback, relay_local, ll_addr, ula_addr;
int ind, addr_match;
};
@@ -158,6 +158,8 @@ void dhcp6_packet(time_t now)
parm.ind = if_index;
parm.addr_match = 0;
memset(&parm.fallback, 0, IN6ADDRSZ);
+ memset(&parm.ll_addr, 0, IN6ADDRSZ);
+ memset(&parm.ula_addr, 0, IN6ADDRSZ);
for (context = daemon->dhcp6; context; context = context->next)
if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
@@ -210,7 +212,7 @@ void dhcp6_packet(time_t now)
lease_prune(NULL, now); /* lose any expired leases */
port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
- sz, &from.sin6_addr, now);
+ &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
lease_update_file(now);
lease_update_dns(0);
@@ -309,6 +311,11 @@ static int complete_context6(struct in6_addr *local, int prefix,
if (if_index == param->ind)
{
+ if (IN6_IS_ADDR_LINKLOCAL(local))
+ param->ll_addr = *local;
+ else if (IN6_IS_ADDR_ULA(local))
+ param->ula_addr = *local;
+
if (!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_LINKLOCAL(local) &&
!IN6_IS_ADDR_MULTICAST(local))
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 2f144a8..90257ad 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -39,17 +39,34 @@
#define C_ANY 255 /* wildcard match */
#define T_A 1
-#define T_NS 2
+#define T_NS 2
+#define T_MD 3
+#define T_MF 4
#define T_CNAME 5
#define T_SOA 6
+#define T_MB 7
+#define T_MG 8
+#define T_MR 9
#define T_PTR 12
+#define T_MINFO 14
#define T_MX 15
#define T_TXT 16
+#define T_RP 17
+#define T_AFSDB 18
+#define T_RT 21
#define T_SIG 24
+#define T_PX 26
#define T_AAAA 28
+#define T_NXT 30
#define T_SRV 33
#define T_NAPTR 35
+#define T_KX 36
+#define T_DNAME 39
#define T_OPT 41
+#define T_DS 43
+#define T_RRSIG 46
+#define T_NSEC 47
+#define T_DNSKEY 48
#define T_TKEY 249
#define T_TSIG 250
#define T_AXFR 252
@@ -78,6 +95,8 @@ struct dns_header {
#define HB4_RCODE 0x0f
#define OPCODE(x) (((x)->hb3 & HB3_OPCODE) >> 3)
+#define SET_OPCODE(x, code) (x)->hb3 = ((x)->hb3 & ~HB3_OPCODE) | code
+
#define RCODE(x) ((x)->hb4 & HB4_RCODE)
#define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code
@@ -117,3 +136,26 @@ struct dns_header {
(cp) += 4; \
}
+#define CHECKED_GETCHAR(var, ptr, len) do { \
+ if ((len) < 1) return 0; \
+ var = *ptr++; \
+ (len) -= 1; \
+ } while (0)
+
+#define CHECKED_GETSHORT(var, ptr, len) do { \
+ if ((len) < 2) return 0; \
+ GETSHORT(var, ptr); \
+ (len) -= 2; \
+ } while (0)
+
+#define CHECKED_GETLONG(var, ptr, len) do { \
+ if ((len) < 4) return 0; \
+ GETLONG(var, ptr); \
+ (len) -= 4; \
+ } while (0)
+
+#define CHECK_LEN(header, pp, plen, len) \
+ ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen))
+
+#define ADD_RDLEN(header, pp, plen, len) \
+ (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 0b31d68..a264f77 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -81,15 +81,25 @@ int main (int argc, char **argv)
umask(022); /* known umask, create leases and pid files as 0644 */
read_opts(argc, argv, compile_opts);
-
+
if (daemon->edns_pktsz < PACKETSZ)
daemon->edns_pktsz = PACKETSZ;
+#ifdef HAVE_DNSSEC
+ /* Enforce min packet big enough for DNSSEC */
+ if (option_bool(OPT_DNSSEC_VALID) && daemon->edns_pktsz < EDNS_PKTSZ)
+ daemon->edns_pktsz = EDNS_PKTSZ;
+#endif
+
daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ?
daemon->edns_pktsz : DNSMASQ_PACKETSZ;
daemon->packet = safe_malloc(daemon->packet_buff_sz);
-
+
daemon->addrbuff = safe_malloc(ADDRSTRLEN);
-
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ daemon->keyname = safe_malloc(MAXDNAME);
+#endif
#ifdef HAVE_DHCP
if (!daemon->lease_file)
@@ -131,6 +141,11 @@ int main (int argc, char **argv)
}
#endif
+#ifdef HAVE_DNSSEC
+ if (daemon->cachesize port != 0)
- get_new_frec(now, &wait);
+ get_new_frec(now, &wait, 0);
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
{
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 724efde..0082510 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -14,7 +14,7 @@
along with this program. If not, see .
*/
-#define COPYRIGHT "Copyright (c) 2000-2013 Simon Kelley"
+#define COPYRIGHT "Copyright (c) 2000-2014 Simon Kelley"
#ifndef NO_LARGEFILE
/* Ensure we can use files >2GB (log files may grow this big) */
@@ -50,12 +50,16 @@
#include
#include "config.h"
+#include "ip6addr.h"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
+#define countof(x) (long)(sizeof(x) / sizeof(x[0]))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
#include "dns-protocol.h"
#include "dhcp-protocol.h"
#ifdef HAVE_DHCP6
@@ -213,7 +217,7 @@ struct event_desc {
#define OPT_NO_OVERRIDE 30
#define OPT_NO_REBIND 31
#define OPT_ADD_MAC 32
-#define OPT_DNSSEC 33
+#define OPT_DNSSEC_PROXY 33
#define OPT_CONSEC_ADDR 34
#define OPT_CONNTRACK 35
#define OPT_FQDN_UPDATE 36
@@ -225,7 +229,8 @@ struct event_desc {
#define OPT_QUIET_DHCP 42
#define OPT_QUIET_DHCP6 43
#define OPT_QUIET_RA 44
-#define OPT_LAST 45
+#define OPT_DNSSEC_VALID 45
+#define OPT_LAST 46
/* extra flags for my_syslog, we use a couple of facilities since they are known
not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -238,6 +243,7 @@ struct all_addr {
#ifdef HAVE_IPV6
struct in6_addr addr6;
#endif
+ unsigned int keytag;
} addr;
};
@@ -282,6 +288,12 @@ struct cname {
struct cname *next;
};
+struct dnskey {
+ char *name, *key;
+ int keylen, algo, flags;
+ struct dnskey *next;
+};
+
#define ADDRLIST_LITERAL 1
#define ADDRLIST_IPV6 2
@@ -331,8 +343,8 @@ union bigname {
union bigname *next; /* freelist */
};
-struct keydata {
- struct keydata *next;
+struct blockdata {
+ struct blockdata *next;
unsigned char key[KEYBLOCK_LEN];
};
@@ -349,14 +361,14 @@ struct crec {
int uid; /* -1 if union is interface-name */
} cname;
struct {
- struct keydata *keydata;
+ struct blockdata *keydata;
unsigned char algo;
unsigned char digest; /* DS only */
- unsigned short flags_or_keyid; /* flags for DNSKEY, keyid for DS */
+ unsigned short keytag;
} key;
} addr;
time_t ttd; /* time to die */
- /* used as keylen if F_DS or F_DNSKEY, index to source for F_HOSTS */
+ /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
int uid;
unsigned short flags;
union {
@@ -391,6 +403,9 @@ struct crec {
#define F_QUERY (1u<<19)
#define F_NOERR (1u<<20)
#define F_AUTH (1u<<21)
+#define F_DNSSEC (1u<<22)
+#define F_KEYTAG (1u<<23)
+#define F_SECSTAT (1u<<24)
/* composites */
#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
@@ -496,9 +511,19 @@ struct hostsfile {
int index; /* matches to cache entries for logging */
};
+
+/* DNSSEC status values. */
+#define STAT_SECURE 1
+#define STAT_INSECURE 2
+#define STAT_BOGUS 3
+#define STAT_NEED_DS 4
+#define STAT_NEED_KEY 5
+
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
#define FREC_HAS_SUBNET 4
+#define FREC_DNSKEY_QUERY 8
+#define FREC_DS_QUERY 16
struct frec {
union mysockaddr source;
@@ -513,6 +538,13 @@ struct frec {
int fd, forwardall, flags;
unsigned int crc;
time_t time;
+#ifdef HAVE_DNSSEC
+ int class;
+ struct blockdata *stash; /* Saved reply, whilst we validate */
+ size_t stash_len;
+ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
+ struct frec *blocking_query; /* Query which is blocking us. */
+#endif
struct frec *next;
};
@@ -875,11 +907,17 @@ extern struct daemon {
#ifdef OPTION6_PREFIX_CLASS
struct prefix_class *prefix_classes;
#endif
+#ifdef HAVE_DNSSEC
+ struct dnskey *dnskeys;
+#endif
/* globally used stuff for DNS */
char *packet; /* packet buffer */
int packet_buff_sz; /* size of above */
char *namebuff; /* MAXDNAME size buffer */
+#ifdef HAVE_DNSSEC
+ char *keyname; /* MAXDNAME size buffer */
+#endif
unsigned int local_answer, queries_forwarded, auth_answer;
struct frec *frec_list;
struct serverfd *sfds;
@@ -950,9 +988,14 @@ void dump_cache(time_t now);
char *cache_get_name(struct crec *crecp);
char *cache_get_cname_target(struct crec *crecp);
struct crec *cache_enumerate(int init);
+
+/* blockdata.c */
#ifdef HAVE_DNSSEC
-struct keydata *keydata_alloc(char *data, size_t len);
-void keydata_free(struct keydata *blocks);
+void blockdata_report(void);
+struct blockdata *blockdata_alloc(char *data, size_t len);
+size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt);
+int blockdata_retrieve(struct blockdata *block, size_t len, void *data);
+void blockdata_free(struct blockdata *blocks);
#endif
/* domain.c */
@@ -964,6 +1007,10 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr);
int is_rev_synth(int flag, struct all_addr *addr, char *name);
/* rfc1035.c */
+int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
+ 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_questions(struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
size_t setup_reply(struct dns_header *header, size_t qlen,
@@ -971,7 +1018,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
time_t now, char **ipsets, int is_sign, int checkrebind,
- int checking_disabled);
+ int no_cache, int secure);
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);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
@@ -984,6 +1031,9 @@ size_t resize_packet(struct dns_header *header, size_t plen,
unsigned char *pheader, size_t hlen);
size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3);
size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source);
+#ifdef HAVE_DNSSEC
+size_t add_do_bit(struct dns_header *header, size_t plen, char *limit);
+#endif
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
int add_resource_record(struct dns_header *header, char *limit, int *truncp,
int nameoffset, unsigned char **pp, unsigned long ttl,
@@ -1001,6 +1051,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen,
int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
+/* dnssec.c */
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
+int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
+
/* util.c */
void rand_init(void);
unsigned short rand16(void);
@@ -1026,6 +1083,9 @@ void prettyprint_time(char *buf, unsigned int t);
int prettyprint_addr(union mysockaddr *addr, char *buf);
int parse_hex(char *in, unsigned char *out, int maxlen,
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,
unsigned int mask);
int expand_buf(struct iovec *iov, size_t size);
@@ -1061,7 +1121,7 @@ void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
-struct frec *get_new_frec(time_t now, int *wait);
+struct frec *get_new_frec(time_t now, int *wait, int force);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, struct all_addr *source,
unsigned int iface);
@@ -1255,7 +1315,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
/* rfc3315.c */
#ifdef HAVE_DHCP6
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
- struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now);
+ struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+ size_t sz, struct in6_addr *client_addr, time_t now);
void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id);
unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h
new file mode 100644
index 0000000..77c5bc5
--- /dev/null
+++ b/src/dnssec-crypto.h
@@ -0,0 +1,83 @@
+/* dnssec-crypto.h is Copyright (c) 2012 Giovanni Bajo
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef DNSSEC_CRYPTO_H
+#define DNSSEC_CRYPTO_H
+
+struct blockdata;
+
+/*
+ * vtable for a signature verification algorithm.
+ *
+ * Each algorithm verifies that a certain signature over a (possibly non-contigous)
+ * array of data has been made with the specified key.
+ *
+ * Sample of usage:
+ *
+ * // First, set the signature we need to check. Notice: data is not copied
+ * // nor consumed, so the pointer must stay valid.
+ * alg->set_signature(sig, 16);
+ *
+ * // Second, get push the data through the corresponding digest algorithm;
+ * // data is consumed immediately, so the buffers can be freed or modified.
+ * digestalg_begin(alg->get_digestalgo());
+ * digestalg_add_data(buf1, 123);
+ * digestalg_add_data(buf2, 45);
+ * digestalg_add_data(buf3, 678);
+ * alg->set_digest(digestalg_final());
+ *
+ * // Third, verify if we got the correct key for this signature.
+ * alg->verify(key1, 16);
+ * alg->verify(key2, 16);
+ */
+
+typedef struct VerifyAlgCtx VerifyAlgCtx;
+
+typedef struct
+{
+ int digest_algo;
+ int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len);
+} VerifyAlg;
+
+struct VerifyAlgCtx
+{
+ const VerifyAlg *vtbl;
+ unsigned char *sig;
+ size_t siglen;
+ unsigned char digest[64]; /* TODO: if memory problems, use VLA */
+};
+
+int verifyalg_supported(int algo);
+VerifyAlgCtx* verifyalg_alloc(int algo);
+void verifyalg_free(VerifyAlgCtx *a);
+int verifyalg_algonum(VerifyAlgCtx *a);
+
+/* Functions to calculate the digest of a key */
+
+/* RFC4034 digest algorithms */
+#define DIGESTALG_SHA1 1
+#define DIGESTALG_SHA256 2
+#define DIGESTALG_MD5 256
+#define DIGESTALG_SHA512 257
+
+int digestalg_supported(int algo);
+void digestalg_begin(int algo);
+void digestalg_add_data(void *data, unsigned len);
+void digestalg_add_keydata(struct blockdata *key, size_t len);
+unsigned char *digestalg_final(void);
+int digestalg_len(void);
+
+#endif /* DNSSEC_CRYPTO_H */
diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c
new file mode 100644
index 0000000..2e25f82
--- /dev/null
+++ b/src/dnssec-openssl.c
@@ -0,0 +1,316 @@
+/* dnssec-openssl.c is Copyright (c) 2012 Giovanni Bajo
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
+#include "dnssec-crypto.h"
+#include
+#include
+#include
+#include
+#include
+
+#define POOL_SIZE 1
+static union _Pool
+{
+ VerifyAlgCtx ctx;
+} Pool[POOL_SIZE];
+static char pool_used = 0;
+
+static void print_hex(unsigned char *data, unsigned len)
+{
+ while (len > 0)
+ {
+ printf("%02x", *data++);
+ --len;
+ }
+ printf("\n");
+}
+
+static int keydata_to_bn(BIGNUM *ret, struct blockdata **key_data, unsigned char **p, unsigned len)
+{
+ size_t cnt;
+ BIGNUM temp;
+
+ BN_init(ret);
+
+ cnt = blockdata_walk(key_data, p, len);
+ BN_bin2bn(*p, cnt, ret);
+ len -= cnt;
+ *p += cnt;
+ while (len > 0)
+ {
+ if (!(cnt = blockdata_walk(key_data, p, len)))
+ return 0;
+ BN_lshift(ret, ret, cnt*8);
+ BN_init(&temp);
+ BN_bin2bn(*p, cnt, &temp);
+ BN_add(ret, ret, &temp);
+ len -= cnt;
+ *p += cnt;
+ }
+ return 1;
+}
+
+static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct blockdata *key_data, unsigned key_len)
+{
+ unsigned char *p = key_data->key;
+ size_t exp_len, mod_len;
+
+ CHECKED_GETCHAR(exp_len, p, key_len);
+ if (exp_len == 0)
+ CHECKED_GETSHORT(exp_len, p, key_len);
+ if (exp_len >= key_len)
+ return 0;
+ mod_len = key_len - exp_len;
+
+ return keydata_to_bn(exp, &key_data, &p, exp_len) &&
+ keydata_to_bn(mod, &key_data, &p, mod_len);
+}
+
+static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct blockdata *key_data, unsigned key_len)
+{
+ unsigned char *p = key_data->key;
+ int T;
+
+ CHECKED_GETCHAR(T, p, key_len);
+ return
+ keydata_to_bn(Q, &key_data, &p, 20) &&
+ keydata_to_bn(P, &key_data, &p, 64+T*8) &&
+ keydata_to_bn(G, &key_data, &p, 64+T*8) &&
+ keydata_to_bn(Y, &key_data, &p, 64+T*8);
+}
+
+static int rsa_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len, int nid, int dlen)
+{
+ int validated = 0;
+
+ RSA *rsa = RSA_new();
+ rsa->e = BN_new();
+ rsa->n = BN_new();
+ if (rsasha1_parse_key(rsa->e, rsa->n, key_data, key_len)
+ && RSA_verify(nid, ctx->digest, dlen, ctx->sig, ctx->siglen, rsa))
+ validated = 1;
+
+ RSA_free(rsa);
+ return validated;
+}
+
+static int rsamd5_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
+{
+ return rsa_verify(ctx, key_data, key_len, NID_md5, 16);
+}
+
+static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
+{
+ return rsa_verify(ctx, key_data, key_len, NID_sha1, 20);
+}
+
+static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
+{
+ return rsa_verify(ctx, key_data, key_len, NID_sha256, 32);
+}
+
+static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
+{
+ return rsa_verify(ctx, key_data, key_len, NID_sha512, 64);
+}
+
+static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
+{
+ static unsigned char asn1_signature[] =
+ {
+ 0x30, 0x2E, // sequence
+ 0x02, 21, // large integer (21 bytes)
+ 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // R
+ 0x02, 21, // large integer (21 bytes)
+ 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // S
+ };
+ int validated = 0;
+
+ /* A DSA signature is made of 2 bignums (R & S). We could parse them manually with BN_bin2bn(),
+ but OpenSSL does not have an API to verify a DSA signature given R and S, and insists
+ in having a ASN.1 BER sequence (as per RFC3279).
+ We prepare a hard-coded ASN.1 sequence, and just fill in the R&S numbers in it. */
+ memcpy(asn1_signature+5, ctx->sig+1, 20);
+ memcpy(asn1_signature+28, ctx->sig+21, 20);
+
+ DSA *dsa = DSA_new();
+ dsa->q = BN_new();
+ dsa->p = BN_new();
+ dsa->g = BN_new();
+ dsa->pub_key = BN_new();
+
+ if (dsasha1_parse_key(dsa->q, dsa->p, dsa->g, dsa->pub_key, key_data, key_len)
+ && DSA_verify(0, ctx->digest, 20, asn1_signature, countof(asn1_signature), dsa) > 0)
+ validated = 1;
+
+ DSA_free(dsa);
+ return validated;
+}
+
+#define VALG_UNSUPPORTED() { \
+ 0,0 \
+ } /**/
+
+#define VALG_VTABLE(alg, digest) { \
+ digest, \
+ alg ## _verify \
+ } /**/
+
+/* Updated registry that merges various RFCs:
+ https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */
+static const VerifyAlg valgs[] =
+{
+ VALG_UNSUPPORTED(), /* 0: reserved */
+ VALG_VTABLE(rsamd5, DIGESTALG_MD5), /* 1: RSAMD5 */
+ VALG_UNSUPPORTED(), /* 2: DH */
+ VALG_VTABLE(dsasha1, DIGESTALG_SHA1), /* 3: DSA */
+ VALG_UNSUPPORTED(), /* 4: ECC */
+ VALG_VTABLE(rsasha1, DIGESTALG_SHA1), /* 5: RSASHA1 */
+ VALG_VTABLE(dsasha1, DIGESTALG_SHA1), /* 6: DSA-NSEC3-SHA1 */
+ VALG_VTABLE(rsasha1, DIGESTALG_SHA1), /* 7: RSASHA1-NSEC3-SHA1 */
+ VALG_VTABLE(rsasha256, DIGESTALG_SHA256), /* 8: RSASHA256 */
+ VALG_UNSUPPORTED(), /* 9: unassigned */
+ VALG_VTABLE(rsasha512, DIGESTALG_SHA512), /* 10: RSASHA512 */
+ VALG_UNSUPPORTED(), /* 11: unassigned */
+ VALG_UNSUPPORTED(), /* 12: ECC-GOST */
+ VALG_UNSUPPORTED(), /* 13: ECDSAP256SHA256 */
+ VALG_UNSUPPORTED(), /* 14: ECDSAP384SHA384 */
+};
+
+/* TODO: remove if we don't need this anymore
+ (to be rechecked if we ever remove OpenSSL) */
+static const int valgctx_size[] =
+{
+ 0, /* 0: reserved */
+ sizeof(VerifyAlgCtx), /* 1: RSAMD5 */
+ 0, /* 2: DH */
+ sizeof(VerifyAlgCtx), /* 3: DSA */
+ 0, /* 4: ECC */
+ sizeof(VerifyAlgCtx), /* 5: RSASHA1 */
+ sizeof(VerifyAlgCtx), /* 6: DSA-NSEC3-SHA1 */
+ sizeof(VerifyAlgCtx), /* 7: RSASHA1-NSEC3-SHA1 */
+ sizeof(VerifyAlgCtx), /* 8: RSASHA256 */
+ 0, /* 9: unassigned */
+ sizeof(VerifyAlgCtx), /* 10: RSASHA512 */
+ 0, /* 11: unassigned */
+ 0, /* 12: ECC-GOST */
+ 0, /* 13: ECDSAP256SHA256 */
+ 0, /* 14: ECDSAP384SHA384 */
+};
+
+int verifyalg_supported(int algo)
+{
+ return (algo < countof(valgctx_size) && valgctx_size[algo] != 0);
+}
+
+VerifyAlgCtx* verifyalg_alloc(int algo)
+{
+ int i;
+ VerifyAlgCtx *ret = 0;
+
+ if (pool_used == (1<vtbl = &valgs[algo];
+ return ret;
+}
+
+void verifyalg_free(VerifyAlgCtx *a)
+{
+ int pool_idx = ((char*)a - (char*)&Pool[0]) / sizeof(Pool[0]);
+ if (pool_idx < 0 || pool_idx >= POOL_SIZE)
+ {
+ free(a);
+ return;
+ }
+
+ pool_used &= ~(1 << pool_idx);
+}
+
+int verifyalg_algonum(VerifyAlgCtx *a)
+{
+ int num = a->vtbl - valgs;
+ if (num < 0 || num >= countof(valgs))
+ return -1;
+ return num;
+}
+
+static EVP_MD_CTX digctx;
+
+int digestalg_supported(int algo)
+{
+ return (algo == DIGESTALG_SHA1 ||
+ algo == DIGESTALG_SHA256 ||
+ algo == DIGESTALG_MD5 ||
+ algo == DIGESTALG_SHA512);
+}
+
+void digestalg_begin(int algo)
+{
+ EVP_MD_CTX_init(&digctx);
+ if (algo == DIGESTALG_SHA1)
+ EVP_DigestInit_ex(&digctx, EVP_sha1(), NULL);
+ else if (algo == DIGESTALG_SHA256)
+ EVP_DigestInit_ex(&digctx, EVP_sha256(), NULL);
+ else if (algo == DIGESTALG_SHA512)
+ EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL);
+ else if (algo == DIGESTALG_MD5)
+ EVP_DigestInit_ex(&digctx, EVP_md5(), NULL);
+}
+
+int digestalg_len()
+{
+ return EVP_MD_CTX_size(&digctx);
+}
+
+void digestalg_add_data(void *data, unsigned len)
+{
+ EVP_DigestUpdate(&digctx, data, len);
+}
+
+void digestalg_add_keydata(struct blockdata *key, size_t len)
+{
+ size_t cnt; unsigned char *p = NULL;
+ while (len)
+ {
+ cnt = blockdata_walk(&key, &p, len);
+ EVP_DigestUpdate(&digctx, p, cnt);
+ p += cnt;
+ len -= cnt;
+ }
+}
+
+unsigned char* digestalg_final(void)
+{
+ static unsigned char digest[32];
+ EVP_DigestFinal(&digctx, digest, NULL);
+ return digest;
+}
+
+#endif /* HAVE_DNSSEC */
diff --git a/src/dnssec.c b/src/dnssec.c
new file mode 100644
index 0000000..712696f
--- /dev/null
+++ b/src/dnssec.c
@@ -0,0 +1,854 @@
+/* dnssec.c is Copyright (c) 2012 Giovanni Bajo
+ and Copyright (c) 2012-2014 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
+#include "dnssec-crypto.h"
+
+#define SERIAL_UNDEF -100
+#define SERIAL_EQ 0
+#define SERIAL_LT -1
+#define SERIAL_GT 1
+
+/* Convert from presentation format to wire format, in place.
+ Also map UC -> LC.
+ Note that using extract_name to get presentation format
+ then calling to_wire() removes compression and maps case,
+ thus generating names in canonical form.
+ Calling to_wire followed by from_wire is almost an identity,
+ except that the UC remains mapped to LC.
+*/
+static int to_wire(char *name)
+{
+ unsigned char *l, *p, term;
+ int len;
+
+ for (l = (unsigned char*)name; *l != 0; l = p)
+ {
+ for (p = l; *p != '.' && *p != 0; p++)
+ if (*p >= 'A' && *p <= 'Z')
+ *p = *p - 'A' + 'a';
+
+ term = *p;
+
+ if ((len = p - l) != 0)
+ memmove(l+1, l, len);
+ *l = len;
+
+ p++;
+
+ if (term == 0)
+ *p = 0;
+ }
+
+ return l + 1 - (unsigned char *)name;
+}
+
+/* Note: no compression allowed in input. */
+static void from_wire(char *name)
+{
+ unsigned char *l;
+ int len;
+
+ for (l = (unsigned char *)name; *l != 0; l += len+1)
+ {
+ len = *l;
+ memmove(l, l+1, len);
+ l[len] = '.';
+ }
+
+ *(l-1) = 0;
+}
+
+/* Input in presentation format */
+static int count_labels(char *name)
+{
+ int i;
+
+ if (*name == 0)
+ return 0;
+
+ for (i = 0; *name; name++)
+ if (*name == '.')
+ i++;
+
+ return i+1;
+}
+
+/* Implement RFC1982 wrapped compare for 32-bit numbers */
+static int serial_compare_32(unsigned long s1, unsigned long s2)
+{
+ if (s1 == s2)
+ return SERIAL_EQ;
+
+ if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) > (1UL<<31)))
+ return SERIAL_LT;
+ if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) < (1UL<<31)))
+ return SERIAL_GT;
+ return SERIAL_UNDEF;
+}
+
+/* Check whether today/now is between date_start and date_end */
+static int check_date_range(unsigned long date_start, unsigned long date_end)
+{
+ unsigned long curtime = time(0);
+
+ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+ return serial_compare_32(curtime, date_start) == SERIAL_GT
+ && serial_compare_32(curtime, date_end) == SERIAL_LT;
+}
+
+static u16 *get_desc(int type)
+{
+ /* List of RRtypes which include domains in the data.
+ 0 -> domain
+ integer -> no of plain bytes
+ -1 -> end
+
+ zero is not a valid RRtype, so the final entry is returned for
+ anything which needs no mangling.
+ */
+
+ static u16 rr_desc[] =
+ {
+ T_NS, 0, -1,
+ T_MD, 0, -1,
+ T_MF, 0, -1,
+ T_CNAME, 0, -1,
+ T_SOA, 0, 0, -1,
+ T_MB, 0, -1,
+ T_MG, 0, -1,
+ T_MR, 0, -1,
+ T_PTR, 0, -1,
+ T_MINFO, 0, 0, -1,
+ T_MX, 2, 0, -1,
+ T_RP, 0, 0, -1,
+ T_AFSDB, 2, 0, -1,
+ T_RT, 2, 0, -1,
+ T_SIG, 18, 0, -1,
+ T_PX, 2, 0, 0, -1,
+ T_NXT, 0, -1,
+ T_KX, 2, 0, -1,
+ T_SRV, 6, 0, -1,
+ T_DNAME, 0, -1,
+ T_RRSIG, 18, 0, -1,
+ T_NSEC, 0, -1,
+ 0, -1 /* wildcard/catchall */
+ };
+
+ u16 *p = rr_desc;
+
+ while (*p != type && *p != 0)
+ while (*p++ != (u16)-1);
+
+ return p+1;
+}
+
+/* Return bytes of canonicalised rdata, when the return value is zero, the remaining
+ data, pointed to by *p, should be used raw. */
+static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff,
+ unsigned char **p, u16 **desc)
+{
+ int d = **desc;
+
+ (*desc)++;
+
+ /* No more data needs mangling */
+ if (d == (u16)-1)
+ return 0;
+
+ if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
+ /* domain-name, canonicalise */
+ return to_wire(buff);
+ else
+ {
+ /* plain data preceding a domain-name, don't run off the end of the data */
+ if ((end - *p) < d)
+ d = end - *p;
+
+ if (d != 0)
+ {
+ memcpy(buff, *p, d);
+ *p += d;
+ }
+
+ return d;
+ }
+}
+
+/* Bubble sort the RRset into the canonical order.
+ Note that the byte-streams from two RRs may get unsynced: consider
+ RRs which have two domain-names at the start and then other data.
+ The domain-names may have different lengths in each RR, but sort equal
+
+ ------------
+ |abcde|fghi|
+ ------------
+ |abcd|efghi|
+ ------------
+
+ leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
+*/
+
+static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
+ unsigned char **rrset, char *buff1, char *buff2)
+{
+ int swap, quit, i;
+
+ do
+ {
+ for (swap = 0, i = 0; i < rrsetidx-1; i++)
+ {
+ int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
+ u16 *dp1, *dp2;
+ unsigned char *end1, *end2;
+ unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
+ unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
+
+ p1 += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen1, p1);
+ end1 = p1 + rdlen1;
+
+ p2 += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen2, p2);
+ end2 = p2 + rdlen2;
+
+ dp1 = dp2 = rr_desc;
+
+ for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
+ {
+ if (left1 != 0)
+ memmove(buff1, buff1 + len1 - left1, left1);
+
+ if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0)
+ {
+ quit = 1;
+ len1 = end1 - p1;
+ memcpy(buff1 + left1, p1, len1);
+ }
+ len1 += left1;
+
+ if (left2 != 0)
+ memmove(buff2, buff2 + len2 - left2, left2);
+
+ if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0)
+ {
+ quit = 1;
+ len2 = end2 - p2;
+ memcpy(buff2 + left2, p2, len2);
+ }
+ len2 += left2;
+
+ if (len1 > len2)
+ left1 = len1 - len2, left2 = 0, len = len2;
+ else
+ left2 = len2 - len1, left1 = 0, len = len1;
+
+ rc = memcmp(buff1, buff2, len);
+
+ if (rc == 1 || (rc == 0 && quit && len1 > len2))
+ {
+ unsigned char *tmp = rrset[i+1];
+ rrset[i+1] = rrset[i];
+ rrset[i] = tmp;
+ swap = quit = 1;
+ }
+ }
+ }
+ } while (swap);
+}
+
+/* Validate a single RRset (class, type, name) in the supplied DNS reply
+ Return code:
+ STAT_SECURE if it validates.
+ STAT_INSECURE can't validate (no RRSIG, bad packet).
+ STAT_BOGUS signature is wrong.
+ 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,
+ otherwise find the key in the cache.
+*/
+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)
+{
+ static unsigned char **rrset = NULL, **sigs = NULL;
+ static int rrset_sz = 0, sig_sz = 0;
+
+ unsigned char *p;
+ int rrsetidx, sigidx, res, rdlen, j, name_labels;
+ struct crec *crecp = NULL;
+ int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
+ u16 *rr_desc = get_desc(type);
+
+ if (!(p = skip_questions(header, plen)))
+ return STAT_INSECURE;
+
+ name_labels = count_labels(name); /* For 4035 5.3.2 check */
+
+ /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
+ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
+ j != 0; j--)
+ {
+ unsigned char *pstart, *pdata;
+ int stype, sclass;
+
+ pstart = p;
+
+ if (!(res = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(stype, p);
+ GETSHORT(sclass, p);
+ p += 4; /* TTL */
+
+ pdata = p;
+
+ GETSHORT(rdlen, p);
+
+ if (res == 1 && sclass == class)
+ {
+ if (stype == type)
+ {
+ if (rrsetidx == rrset_sz)
+ {
+ unsigned char **new;
+
+ /* expand */
+ if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
+ return STAT_INSECURE;
+
+ if (rrset)
+ {
+ memcpy(new, rrset, rrset_sz * sizeof(unsigned char **));
+ free(rrset);
+ }
+
+ rrset = new;
+ rrset_sz += 5;
+ }
+ rrset[rrsetidx++] = pstart;
+ }
+
+ if (stype == T_RRSIG)
+ {
+ if (rdlen < 18)
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(type_covered, p);
+ algo = *p++;
+ labels = *p++;
+ p += 4; /* orig_ttl */
+ GETLONG(sig_expiration, p);
+ GETLONG(sig_inception, p);
+ p = pdata + 2; /* restore for ADD_RDLEN */
+
+ if (type_covered == type &&
+ check_date_range(sig_inception, sig_expiration) &&
+ verifyalg_supported(algo) &&
+ labels <= name_labels)
+ {
+ if (sigidx == sig_sz)
+ {
+ unsigned char **new;
+
+ /* expand */
+ if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **))))
+ return STAT_INSECURE;
+
+ if (sigs)
+ {
+ memcpy(new, sigs, sig_sz * sizeof(unsigned char **));
+ free(sigs);
+ }
+
+ sigs = new;
+ sig_sz += 5;
+ }
+
+ sigs[sigidx++] = pdata;
+ }
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_INSECURE;
+ }
+
+ /* RRset empty, no RRSIGs */
+ if (rrsetidx == 0 || sigidx == 0)
+ return STAT_INSECURE;
+
+ /* Sort RRset records into canonical order.
+ Note that at this point keyname and name buffs are
+ unused, and used as workspace by the sort. */
+ sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname);
+
+ /* Now try all the sigs to try and find one which validates */
+ for (j = 0; j = 18 checked previously */
+ psav = p;
+
+ p += 2; /* type_covered - already checked */
+ algo = *p++;
+ labels = *p++;
+ GETLONG(orig_ttl, p);
+ p += 8; /* sig_expiration and sig_inception */
+ GETSHORT(key_tag, p);
+
+ if (!extract_name(header, plen, &p, keyname, 1, 0))
+ return STAT_INSECURE;
+
+ /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
+ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
+ return STAT_NEED_KEY;
+
+ alg = verifyalg_alloc(algo);
+ alg->sig = p;
+ alg->siglen = rdlen - (p - psav);
+
+ nsigttl = htonl(orig_ttl);
+
+ digestalg_begin(alg->vtbl->digest_algo);
+ digestalg_add_data(psav, 18);
+ wire_len = to_wire(keyname);
+ digestalg_add_data(keyname, wire_len);
+ from_wire(keyname);
+
+ for (i = 0; i < rrsetidx; ++i)
+ {
+ int seg;
+ unsigned char *end, *cp;
+ char *name_start = name;
+ u16 len, *dp;
+
+ p = rrset[i];
+ if (!extract_name(header, plen, &p, name, 1, 10))
+ return STAT_INSECURE;
+
+ /* if more labels than in RRsig name, hash *. 4035 5.3.2 */
+ if (labels < name_labels)
+ {
+ int k;
+ for (k = name_labels - labels; k != 0; k--)
+ while (*name_start != '.' && *name_start != 0)
+ name_start++;
+ name_start--;
+ *name_start = '*';
+ }
+
+ wire_len = to_wire(name_start);
+ digestalg_add_data(name_start, wire_len);
+ digestalg_add_data(p, 4); /* class and type */
+ digestalg_add_data(&nsigttl, 4);
+
+ p += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return STAT_INSECURE;
+
+ end = p + rdlen;
+
+ /* canonicalise rdata and calculate length of same, use name buffer as workspace */
+ cp = p;
+ dp = rr_desc;
+ for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg);
+ len += end - cp;
+ len = htons(len);
+ digestalg_add_data(&len, 2);
+
+ /* Now canonicalise again and digest. */
+ cp = p;
+ dp = rr_desc;
+ while ((seg = get_rdata(header, plen, end, name, &cp, &dp)))
+ digestalg_add_data(name, seg);
+ if (cp != end)
+ digestalg_add_data(cp, end - cp);
+ }
+
+ /* namebuff used for workspace above, restore to leave unchanged on exit */
+ p = (unsigned char*)(rrset[0]);
+ extract_name(header, plen, &p, name, 1, 0);
+
+ memcpy(alg->digest, digestalg_final(), digestalg_len());
+
+ if (key)
+ {
+ if (algo_in == algo && keytag_in == key_tag &&
+ alg->vtbl->verify(alg, key, keylen))
+ return STAT_SECURE;
+ }
+ else
+ {
+ /* iterate through all possible keys 4035 5.3.1 */
+ for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
+ if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag &&
+ alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid))
+ return STAT_SECURE;
+ }
+ }
+
+ return STAT_BOGUS;
+}
+
+/* The DNS packet is expected to contain the answer to a DNSKEY query.
+ Leave name of query in name.
+ Put all DNSKEYs in the answer which are valid into the cache.
+ return codes:
+ STAT_INSECURE bad packet, no DNSKEYs in reply.
+ STAT_SECURE At least one valid DNSKEY found and in cache.
+ STAT_BOGUS No DNSKEYs found, which can be validated with DS,
+ or self-sign for DNSKEY RRset is not valid.
+ STAT_NEED_DS DS records to validate a key not found, name in keyname
+*/
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+ unsigned char *psave, *p = (unsigned char *)(header+1);
+ struct crec *crecp, *recp1;
+ int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
+ struct blockdata *key;
+
+ if (ntohs(header->qdcount) != 1 ||
+ !extract_name(header, plen, &p, name, 1, 4))
+ {
+ strcpy(name, "");
+ return STAT_INSECURE;
+ }
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
+ return STAT_INSECURE;
+
+ /* See if we have cached a DS record which validates this key */
+ if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
+ {
+ strcpy(keyname, name);
+ return STAT_NEED_DS;
+ }
+
+ cache_start_insert();
+
+ /* NOTE, we need to find ONE DNSKEY which matches the DS */
+ for (valid = 0, j = ntohs(header->ancount); j != 0; j--)
+ {
+ /* Ensure we have type, class TTL and length */
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ if (qclass != class || qtype != T_DNSKEY || rc == 2)
+ {
+ if (ADD_RDLEN(header, p, plen, rdlen))
+ continue;
+
+ return STAT_INSECURE; /* bad packet */
+ }
+
+ if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ return STAT_INSECURE; /* bad packet */
+
+ psave = p;
+
+ /* length at least covers flags, protocol and algo now. */
+ GETSHORT(flags, p);
+ if (*p++ != 3)
+ return STAT_INSECURE;
+ algo = *p++;
+ keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+
+ /* Put the key into the cache. Note that if the validation fails, we won't
+ call cache_end_insert() and this will never be committed. */
+ if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
+ (recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
+ {
+ recp1->uid = rdlen - 4;
+ recp1->addr.key.keydata = key;
+ recp1->addr.key.algo = algo;
+ recp1->addr.key.keytag = keytag;
+ }
+
+ p = psave;
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_INSECURE; /* bad packet */
+
+ /* Already determined that message is OK. Just loop stuffing cache */
+ if (valid || !key)
+ continue;
+
+ for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
+ if (recp1->addr.key.algo == algo &&
+ recp1->addr.key.keytag == keytag &&
+ (flags & 0x100) && /* zone key flag */
+ digestalg_supported(recp1->addr.key.digest))
+ {
+ int wire_len = to_wire(name);
+
+ digestalg_begin(recp1->addr.key.digest);
+ digestalg_add_data(name, wire_len);
+ digestalg_add_data((char *)psave, rdlen);
+
+ from_wire(name);
+
+ if (recp1->uid == digestalg_len() &&
+ blockdata_retrieve(recp1->addr.key.keydata, recp1->uid, digestalg_final()) &&
+ validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag))
+ {
+ struct all_addr a;
+ valid = 1;
+ a.addr.keytag = keytag;
+ log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
+ break;
+ }
+ }
+ }
+
+ if (valid)
+ {
+ /* commit cache insert. */
+ cache_end_insert();
+ return STAT_SECURE;
+ }
+
+ log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
+ return STAT_BOGUS;
+}
+
+/* The DNS packet is expected to contain the answer to a DS query
+ Leave name of DS query in name.
+ Put all DSs in the answer which are valid into the cache.
+ return codes:
+ STAT_INSECURE bad packet, no DS in reply.
+ STAT_SECURE At least one valid DS found and in cache.
+ STAT_BOGUS At least one DS found, which fails validation.
+ STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
+*/
+
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+ unsigned char *psave, *p = (unsigned char *)(header+1);
+ struct crec *crecp;
+ int qtype, qclass, val, j, gotone;
+ struct blockdata *key;
+
+ if (ntohs(header->qdcount) != 1 ||
+ !extract_name(header, plen, &p, name, 1, 4))
+ {
+ strcpy(name, "");
+ return STAT_INSECURE;
+ }
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
+ return STAT_INSECURE;
+
+ val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0);
+
+ if (val == STAT_BOGUS)
+ log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
+
+ /* failed to validate or missing key. */
+ if (val != STAT_SECURE)
+ return val;
+
+ cache_start_insert();
+
+ for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
+ {
+ int ttl, rdlen, rc, algo, digest, keytag;
+
+ /* Ensure we have type, class TTL and length */
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ /* check type, class and name, skip if not in DS rrset */
+ if (qclass == class && qtype == T_DS && rc == 1)
+ {
+ if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ return STAT_INSECURE; /* bad packet */
+
+ psave = p;
+ GETSHORT(keytag, p);
+ algo = *p++;
+ digest = *p++;
+
+ /* We've proved that the DS is OK, store it in the cache */
+ if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
+ (crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
+ {
+ struct all_addr a;
+ a.addr.keytag = keytag;
+ log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
+ crecp->addr.key.digest = digest;
+ crecp->addr.key.keydata = key;
+ crecp->addr.key.algo = algo;
+ crecp->addr.key.keytag = keytag;
+ crecp->uid = rdlen - 4;
+ }
+ else
+ return STAT_INSECURE; /* cache problem */
+
+ p = psave;
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_INSECURE; /* bad packet */
+
+ }
+
+ cache_end_insert();
+
+ return STAT_SECURE;
+}
+
+/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
+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;
+ int type1, class1, rdlen1, type2, class2, rdlen2;
+ int i, j, rc;
+
+ if (!(ans_start = skip_questions(header, plen)))
+ return STAT_INSECURE;
+
+ for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+ {
+ if (!extract_name(header, plen, &p1, name, 1, 10))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(type1, p1);
+ GETSHORT(class1, p1);
+ p1 += 4; /* TTL */
+ GETSHORT(rdlen1, p1);
+
+ /* Don't try and validate RRSIGs! */
+ if (type1 != T_RRSIG)
+ {
+ /* Check if we've done this RRset already */
+ for (p2 = ans_start, j = 0; j < i; j++)
+ {
+ if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(type2, p2);
+ GETSHORT(class2, p2);
+ p2 += 4; /* TTL */
+ GETSHORT(rdlen2, p2);
+
+ if (type2 == type1 && class2 == class1 && rc == 1)
+ break; /* Done it before: name, type, class all match. */
+
+ if (!ADD_RDLEN(header, p2, plen, rdlen2))
+ return STAT_INSECURE;
+ }
+
+ /* Not done, validate now */
+ if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
+ {
+ *class = class1; /* Class for DS or DNSKEY */
+ return rc;
+ }
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen1))
+ return STAT_INSECURE;
+ }
+
+ return STAT_SECURE;
+}
+
+
+/* Compute keytag (checksum to quickly index a key). See RFC4034 */
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
+{
+ if (alg == 1)
+ {
+ /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
+ See RFC4034, Appendix B.1 */
+ return key[keylen-4] * 256 + key[keylen-3];
+ }
+ else
+ {
+ unsigned long ac;
+ int i;
+
+ ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
+ for (i = 0; i < keylen; ++i)
+ ac += (i & 1) ? key[i] : key[i] << 8;
+ ac += (ac >> 16) & 0xffff;
+ return ac & 0xffff;
+ }
+}
+
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
+{
+ unsigned char *p;
+ char types[20];
+
+ querystr("dnssec", types, type);
+
+ if (addr->sa.sa_family == AF_INET)
+ log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
+#ifdef HAVE_IPV6
+ else
+ log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);
+#endif
+
+ header->qdcount = htons(1);
+ header->ancount = htons(0);
+ header->nscount = htons(0);
+ header->arcount = htons(0);
+
+ header->hb3 = HB3_RD;
+ SET_OPCODE(header, QUERY);
+ header->hb4 = HB4_CD;
+
+ /* ID filled in later */
+
+ p = (unsigned char *)(header+1);
+
+ p = do_rfc1035_name(p, name);
+ *p++ = 0;
+ PUTSHORT(type, p);
+ PUTSHORT(class, p);
+
+ return add_do_bit(header, p - (unsigned char *)header, end);
+}
+
+#endif /* HAVE_DNSSEC */
diff --git a/src/domain.c b/src/domain.c
index 3df4ab8..fdd5e4f 100644
--- a/src/domain.c
+++ b/src/domain.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/forward.c b/src/forward.c
index 656e39f..8167229 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -270,7 +270,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
- if (!flags && !(forward = get_new_frec(now, NULL)))
+ if (!flags && !(forward = get_new_frec(now, NULL, 0)))
/* table full - server failure. */
flags = F_NEG;
@@ -330,11 +330,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
int forwarded = 0;
if (option_bool(OPT_ADD_MAC))
- plen = add_mac(header, plen, ((char *) header) + PACKETSZ, &forward->source);
+ plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source);
if (option_bool(OPT_CLIENT_SUBNET))
{
- size_t new = add_source_addr(header, plen, ((char *) header) + PACKETSZ, &forward->source);
+ size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source);
if (new != plen)
{
plen = new;
@@ -342,6 +342,14 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
}
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
+ header->hb4 |= HB4_CD;
+ }
+#endif
+
while (1)
{
/* only send to servers dealing with our domain.
@@ -447,7 +455,7 @@ 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,
- int checking_disabled, int check_subnet, union mysockaddr *query_source)
+ int no_cache, int cache_secure, int check_subnet, union mysockaddr *query_source)
{
unsigned char *pheader, *sizep;
char **sets = 0;
@@ -495,11 +503,18 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
}
-
/* RFC 4035 sect 4.6 para 3 */
- if (!is_sign && !option_bool(OPT_DNSSEC))
+ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
+ header->hb4 &= ~HB4_AD;
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
header->hb4 &= ~HB4_AD;
-
+
+ if (cache_secure)
+ header->hb4 |= HB4_AD;
+#endif
+
if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN))
return n;
@@ -512,7 +527,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
if (!option_bool(OPT_LOG))
server->flags |= SERV_WARNED_RECURSIVE;
}
-
+
if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
{
@@ -534,7 +549,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NOERROR);
}
- if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, checking_disabled))
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
@@ -566,7 +581,7 @@ void reply_query(int fd, int family, time_t now)
union mysockaddr serveraddr;
struct frec *forward;
socklen_t addrlen = sizeof(serveraddr);
- ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen);
+ ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen);
size_t nn;
struct server *server;
@@ -592,9 +607,7 @@ void reply_query(int fd, int family, time_t now)
n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) ||
!(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff))))
return;
-
- server = forward->sentto;
-
+
if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) &&
!option_bool(OPT_ORDER) &&
forward->forwardall == 0)
@@ -619,6 +632,8 @@ void reply_query(int fd, int family, time_t now)
}
}
}
+
+ server = forward->sentto;
if ((forward->sentto->flags & SERV_TYPE) == 0)
{
@@ -640,7 +655,7 @@ void reply_query(int fd, int family, time_t now)
if (!option_bool(OPT_ALL_SERVERS))
daemon->last_server = server;
}
-
+
/* If the answer is an error, keep the forward record in place in case
we get a good reply from another server. Kill it when we've
had replies from all to avoid filling the forwarding table when
@@ -648,12 +663,180 @@ void reply_query(int fd, int family, time_t now)
if (forward->forwardall == 0 || --forward->forwardall == 1 ||
(RCODE(header) != REFUSED && RCODE(header) != SERVFAIL))
{
- int check_rebind = !(forward->flags & FREC_NOREBIND);
+ int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0;
- if (!option_bool(OPT_NO_REBIND))
- check_rebind = 0;
+ if (option_bool(OPT_NO_REBIND))
+ check_rebind = !(forward->flags & FREC_NOREBIND);
- if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED,
+ /* Don't cache replies where DNSSEC validation was turned off, either
+ the upstream server told us so, or the original query specified it. */
+ if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
+ no_cache_dnssec = 1;
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
+ {
+ int status;
+
+ /* We've had a reply already, which we're validating. Ignore this duplicate */
+ if (forward->stash)
+ return;
+
+ if (header->hb3 & HB3_TC)
+ {
+ /* Truncated answer can't be validated.
+ The client will retry over TCP, but if this is an answer to a
+ DNSSEC-generated query, we have a problem. Should really re-send
+ over TCP. No-one with any sense will make a DNSKEY or DS RRset
+ exceed 4096, so this may not be a real problem. Just log
+ for now. */
+ if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
+ my_syslog(LOG_ERR, _("Reply to DNSSEC query truncated - validation fails."));
+ status = STAT_INSECURE;
+ }
+ else if (forward->flags & FREC_DNSKEY_QUERY)
+ status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ else if (forward->flags & FREC_DS_QUERY)
+ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ else
+ status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
+
+ /* Can't validate, as we're missing key data. Put this
+ answer aside, whilst we get that. */
+ if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+ {
+ struct frec *new;
+
+ if ((new = get_new_frec(now, NULL, 1)))
+ {
+ struct frec *next = new->next;
+ *new = *forward; /* copy everything, then overwrite */
+ new->next = next;
+ new->stash = NULL;
+ new->blocking_query = NULL;
+ new->rfd4 = NULL;
+#ifdef HAVE_IPV6
+ new->rfd6 = NULL;
+#endif
+ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+
+ if ((forward->stash = blockdata_alloc((char *)header, n)))
+ {
+ int fd;
+
+ forward->stash_len = n;
+
+ new->dependent = forward; /* to find query awaiting new one. */
+ forward->blocking_query = new; /* for garbage cleaning */
+ /* validate routines leave name of required record in daemon->keyname */
+ if (status == STAT_NEED_KEY)
+ {
+ 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 if (status == STAT_NEED_DS)
+ {
+ new->flags |= FREC_DS_QUERY;
+ nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
+ daemon->keyname, forward->class, T_DS, &server->addr);
+ }
+ new->crc = questions_crc(header, nn, daemon->namebuff);
+ new->new_id = get_id(new->crc);
+ header->id = htons(new->new_id);
+
+ /* Don't resend this. */
+ daemon->srv_save = NULL;
+
+ if (server->sfd)
+ fd = server->sfd->fd;
+ else
+ {
+ fd = -1;
+#ifdef HAVE_IPV6
+ 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());
+ server->queries++;
+ }
+ }
+ }
+
+ return;
+ }
+
+ /* 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
+ and validate them with the new data. Failure to find needed data here is an internal error.
+ Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
+ return it to the original requestor. */
+ if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
+ {
+ while (forward->dependent)
+ {
+ struct frec *prev;
+
+ if (status == STAT_SECURE)
+ {
+ if (forward->flags & FREC_DNSKEY_QUERY)
+ status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ else if (forward->flags & FREC_DS_QUERY)
+ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ }
+
+ prev = forward->dependent;
+ free_frec(forward);
+ forward = prev;
+ forward->blocking_query = NULL; /* already gone */
+ blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+ n = forward->stash_len;
+ }
+
+ /* All DNSKEY and DS records done and in cache, now finally validate original
+ answer, provided last DNSKEY is OK. */
+ if (status == STAT_SECURE)
+ status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
+
+ if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+ {
+ my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
+ status = STAT_INSECURE;
+ }
+ }
+
+ log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
+ status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
+
+ no_cache_dnssec = 0;
+
+ if (status == STAT_SECURE)
+ cache_secure = 1;
+ /* TODO return SERVFAIL here */
+ else if (status == STAT_BOGUS)
+ 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
+
+ if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
forward->flags & FREC_HAS_SUBNET, &forward->source)))
{
header->id = htons(forward->orig_id);
@@ -887,7 +1070,7 @@ void receive_query(struct listener *listen, time_t now)
#ifdef HAVE_AUTH
if (auth_dns)
{
- m = answer_auth(header, ((char *) header) + PACKETSZ, (size_t)n, now, &source_addr, local_auth);
+ m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth);
if (m >= 1)
{
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
@@ -898,7 +1081,7 @@ void receive_query(struct listener *listen, time_t now)
else
#endif
{
- m = answer_request(header, ((char *) header) + PACKETSZ, (size_t)n,
+ m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
dst_addr_4, netmask, now);
if (m >= 1)
@@ -915,6 +1098,66 @@ void receive_query(struct listener *listen, time_t now)
}
}
+#ifdef HAVE_DNSSEC
+static int tcp_key_recurse(time_t now, int status, int class, char *keyname, struct server *server)
+{
+ /* 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;
+ unsigned char c1, c2;
+
+ n = dnssec_generate_query(header, ((char *) header) + 65536, keyname, class,
+ status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
+
+ *length = htons(n);
+
+ if (!read_write(server->tcpfd, packet, n + 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))
+ {
+ close(server->tcpfd);
+ server->tcpfd = -1;
+ new_status = STAT_INSECURE;
+ }
+ else
+ {
+ n = (c1 << 8) | c2;
+
+ if (status == STAT_NEED_KEY)
+ new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
+ 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))
+ {
+ if (status == STAT_NEED_KEY)
+ new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
+ 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)
+ {
+ my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
+ status = STAT_INSECURE;
+ }
+ }
+ }
+ }
+
+ free(packet);
+
+ return new_status;
+}
+#endif
+
+
/* The daemon forks before calling this: it should deal with one connection,
blocking as neccessary, and then return. Note, need to be a bit careful
about resources for debug mode, when the fork is suppressed: that's
@@ -927,7 +1170,7 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_AUTH
int local_auth = 0;
#endif
- int checking_disabled, check_subnet;
+ int checking_disabled, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
size_t m;
unsigned short qtype;
unsigned int gotname;
@@ -960,7 +1203,8 @@ unsigned char *tcp_request(int confd, time_t now,
check_subnet = 0;
/* save state of "cd" flag in query */
- checking_disabled = header->hb4 & HB4_CD;
+ if ((checking_disabled = header->hb4 & HB4_CD))
+ no_cache_dnssec = 1;
/* RFC 4035: sect 4.6 para 2 */
header->hb4 &= ~HB4_AD;
@@ -1080,6 +1324,14 @@ unsigned char *tcp_request(int confd, time_t now,
continue;
}
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID))
+ {
+ size = add_do_bit(header, size, ((char *) header) + 65536);
+ header->hb4 |= HB4_CD;
+ }
+#endif
+
#ifdef HAVE_CONNTRACK
/* Copy connection mark of incoming query to outgoing connection. */
if (option_bool(OPT_CONNTRACK))
@@ -1103,7 +1355,8 @@ unsigned char *tcp_request(int confd, time_t now,
if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) ||
!read_write(last_server->tcpfd, &c1, 1, 1) ||
- !read_write(last_server->tcpfd, &c2, 1, 1))
+ !read_write(last_server->tcpfd, &c2, 1, 1) ||
+ !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1))
{
close(last_server->tcpfd);
last_server->tcpfd = -1;
@@ -1111,8 +1364,6 @@ unsigned char *tcp_request(int confd, time_t now,
}
m = (c1 << 8) | c2;
- if (!read_write(last_server->tcpfd, payload, m, 1))
- return packet;
if (!gotname)
strcpy(daemon->namebuff, "query");
@@ -1124,6 +1375,36 @@ unsigned char *tcp_request(int confd, time_t now,
log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
(struct all_addr *)&last_server->addr.in6.sin6_addr, NULL);
#endif
+
+#ifdef HAVE_DNSSEC
+ if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
+ {
+ int class, status;
+
+ status = dnssec_validate_reply(now, header, m, daemon->namebuff, daemon->keyname, &class);
+
+ if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+ {
+ 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)
+ no_cache_dnssec = 1;
+
+ if (status == STAT_SECURE)
+ cache_secure = 1;
+ }
+#endif
+
+ /* restore CD bit to the value in the query */
+ if (checking_disabled)
+ header->hb4 |= HB4_CD;
+ else
+ header->hb4 &= ~HB4_CD;
/* There's no point in updating the cache, since this process will exit and
lose the information after a few queries. We make this call for the alias and
@@ -1133,8 +1414,8 @@ unsigned char *tcp_request(int confd, time_t now,
sending replies containing questions and bogus answers. */
if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff))
m = process_reply(header, now, last_server, (unsigned int)m,
- option_bool(OPT_NO_REBIND) && !norebind, checking_disabled,
- check_subnet, &peer_addr);
+ option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
+ cache_secure, check_subnet, &peer_addr);
break;
}
@@ -1168,6 +1449,9 @@ static struct frec *allocate_frec(time_t now)
f->flags = 0;
#ifdef HAVE_IPV6
f->rfd6 = NULL;
+#endif
+#ifdef HAVE_DNSSEC
+ f->blocking_query = NULL;
#endif
daemon->frec_list = f;
}
@@ -1210,7 +1494,6 @@ static struct randfd *allocate_rfd(int family)
return NULL; /* doom */
}
-
static void free_frec(struct frec *f)
{
if (f->rfd4 && --(f->rfd4->refcount) == 0)
@@ -1226,13 +1509,29 @@ static void free_frec(struct frec *f)
f->rfd6 = NULL;
#endif
+
+#ifdef HAVE_DNSSEC
+ if (f->stash)
+ {
+ blockdata_free(f->stash);
+ f->stash = NULL;
+ }
+
+ /* Anything we're waiting on is pointless now, too */
+ if (f->blocking_query)
+ free_frec(f->blocking_query);
+ f->blocking_query = NULL;
+
+#endif
}
/* if wait==NULL return a free or older than TIMEOUT record.
else return *wait zero if one available, or *wait is delay to
when the oldest in-use record will expire. Impose an absolute
- limit of 4*TIMEOUT before we wipe things (for random sockets) */
-struct frec *get_new_frec(time_t now, int *wait)
+ limit of 4*TIMEOUT before we wipe things (for random sockets).
+ If force is set, always return a result, even if we have
+ to allocate above the limit. */
+struct frec *get_new_frec(time_t now, int *wait, int force)
{
struct frec *f, *oldest, *target;
int count;
@@ -1281,7 +1580,7 @@ struct frec *get_new_frec(time_t now, int *wait)
}
/* none available, calculate time 'till oldest record expires */
- if (count > daemon->ftabsize)
+ if (!force && count > daemon->ftabsize)
{
static time_t last_log = 0;
diff --git a/src/helper.c b/src/helper.c
index 24c2afd..4be53c3 100644
--- a/src/helper.c
+++ b/src/helper.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/ip6addr.h b/src/ip6addr.h
new file mode 100644
index 0000000..c7dcb39
--- /dev/null
+++ b/src/ip6addr.h
@@ -0,0 +1,34 @@
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+
+
+#define IN6_IS_ADDR_ULA(a) \
+ ((((__const uint32_t *) (a))[0] & htonl (0xff000000)) \
+ == htonl (0xfd000000))
+
+#define IN6_IS_ADDR_ULA_ZERO(a) \
+ (((__const uint32_t *) (a))[0] == htonl (0xfd000000) \
+ && ((__const uint32_t *) (a))[1] == 0 \
+ && ((__const uint32_t *) (a))[2] == 0 \
+ && ((__const uint32_t *) (a))[3] == 0)
+
+#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \
+ (((__const uint32_t *) (a))[0] == htonl (0xfe800000) \
+ && ((__const uint32_t *) (a))[1] == 0 \
+ && ((__const uint32_t *) (a))[2] == 0 \
+ && ((__const uint32_t *) (a))[3] == 0)
+
diff --git a/src/lease.c b/src/lease.c
index 7e00804..d8a53d3 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/log.c b/src/log.c
index f532f93..8083a86 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/netlink.c b/src/netlink.c
index 3be94ee..c9d5e22 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/network.c b/src/network.c
index fd49b5c..f068454 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/option.c b/src/option.c
index 8716208..e4885ee 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -138,6 +138,8 @@ struct myoption {
#define LOPT_QUIET_DHCP 326
#define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328
+#define LOPT_SEC_VALID 329
+#define LOPT_DNSKEY 330
#ifdef HAVE_GETOPT_LONG
@@ -274,6 +276,8 @@ static const struct myoption opts[] =
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
{ "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH },
+ { "dnssec", 0, 0, LOPT_SEC_VALID },
+ { "dnskey", 1, 0, LOPT_DNSKEY },
#ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif
@@ -407,7 +411,7 @@ static struct {
{ LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
{ LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
{ LOPT_ADD_SBNET, ARG_ONE, "[,]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL },
- { LOPT_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
+ { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
{ LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
{ LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
{ LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
@@ -424,6 +428,10 @@ static struct {
{ LOPT_AUTHPEER, ARG_DUP, "[,...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
{ LOPT_IPSET, ARG_DUP, "//[,...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ LOPT_SYNTH, ARG_DUP, ",,[]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
+#ifdef HAVE_DNSSEC
+ { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+ { LOPT_DNSKEY, ARG_DUP, ",,", gettext_noop("Specify trust anchor DNSKEY"), NULL },
+#endif
#ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL },
#endif
@@ -3665,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->host_records_tail = new;
break;
}
-
+
+#ifdef HAVE_DNSSEC
+ case LOPT_DNSKEY:
+ {
+ struct dnskey *new = opt_malloc(sizeof(struct dnskey));
+ char *key64, *algo;
+
+ if (!(comma = split(arg)) || !(algo = split(comma)) || !(key64 = split(algo)) ||
+ !atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
+ !(new->name = canonicalise_opt(arg)))
+ ret_err(_("bad DNSKEY"));
+
+
+ /* Upper bound on length */
+ new->key = opt_malloc((3*strlen(key64)/4));
+ unhide_metas(key64);
+ if ((new->keylen = parse_base64(key64, new->key)) == -1)
+ ret_err(_("bad base64 in DNSKEY"));
+
+ new->next = daemon->dnskeys;
+ daemon->dnskeys = new;
+
+ break;
+ }
+#endif
+
default:
- ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"));
+ ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
}
diff --git a/src/outpacket.c b/src/outpacket.c
index 9d64c01..dce68f7 100644
--- a/src/outpacket.c
+++ b/src/outpacket.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/radv-protocol.h b/src/radv-protocol.h
index 8d5b153..9e53d8e 100644
--- a/src/radv-protocol.h
+++ b/src/radv-protocol.h
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/radv.c b/src/radv.c
index 9fd56ab..84d4a3e 100644
--- a/src/radv.c
+++ b/src/radv.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -31,8 +31,8 @@ struct ra_param {
int ind, managed, other, found_context, first;
char *if_name;
struct dhcp_netid *tags;
- struct in6_addr link_local, link_global;
- unsigned int pref_time, adv_interval;
+ struct in6_addr link_local, link_global, ula;
+ unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval;
};
struct search_param {
@@ -206,6 +206,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
struct dhcp_opt *opt_cfg;
struct ra_interface *ra_param = find_iface_param(iface_name);
int done_dns = 0, old_prefix = 0;
+ unsigned int min_pref_time;
#ifdef HAVE_LINUX_NETWORK
FILE *f;
#endif
@@ -228,7 +229,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
parm.if_name = iface_name;
parm.first = 1;
parm.now = now;
- parm.pref_time = 0;
+ parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0;
parm.adv_interval = calc_interval(ra_param);
/* set tag with name == interface */
@@ -245,6 +246,18 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
if (!iface_enumerate(AF_INET6, &parm, add_prefixes))
return;
+ /* Find smallest preferred time within address classes,
+ to use as lifetime for options. This is a rather arbitrary choice. */
+ min_pref_time = 0xffffffff;
+ if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time)
+ min_pref_time = parm.glob_pref_time;
+
+ if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time)
+ min_pref_time = parm.ula_pref_time;
+
+ if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time)
+ min_pref_time = parm.link_pref_time;
+
/* Look for constructed contexts associated with addresses which have gone,
and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */
for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
@@ -358,22 +371,48 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
if (opt_cfg->opt == OPTION6_DNS_SERVER)
{
- struct in6_addr *a = (struct in6_addr *)opt_cfg->val;
-
+ struct in6_addr *a;
+ int len;
+
done_dns = 1;
- if (opt_cfg->len == 0 || (IN6_IS_ADDR_UNSPECIFIED(a) && parm.pref_time != 0))
+
+ if (opt_cfg->len == 0)
continue;
- put_opt6_char(ICMP6_OPT_RDNSS);
- put_opt6_char((opt_cfg->len/8) + 1);
- put_opt6_short(0);
- put_opt6_long(parm.pref_time);
- /* zero means "self" */
- for (i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++)
- if (IN6_IS_ADDR_UNSPECIFIED(a))
- put_opt6(&parm.link_global, IN6ADDRSZ);
- else
- put_opt6(a, IN6ADDRSZ);
+ /* reduce len for any addresses we can't substitute */
+ for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0;
+ i < opt_cfg->len; i += IN6ADDRSZ, a++)
+ if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) ||
+ (IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) ||
+ (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0))
+ len -= IN6ADDRSZ;
+
+ if (len != 0)
+ {
+ put_opt6_char(ICMP6_OPT_RDNSS);
+ put_opt6_char((len/8) + 1);
+ put_opt6_short(0);
+ put_opt6_long(min_pref_time);
+
+ for (a = (struct in6_addr *)opt_cfg->val, i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++)
+ if (IN6_IS_ADDR_UNSPECIFIED(a))
+ {
+ if (parm.glob_pref_time != 0)
+ put_opt6(&parm.link_global, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_ULA_ZERO(a))
+ {
+ if (parm.ula_pref_time != 0)
+ put_opt6(&parm.ula, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+ {
+ if (parm.link_pref_time != 0)
+ put_opt6(&parm.link_local, IN6ADDRSZ);
+ }
+ else
+ put_opt6(a, IN6ADDRSZ);
+ }
}
if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0)
@@ -383,7 +422,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
put_opt6_char(ICMP6_OPT_DNSSL);
put_opt6_char(len + 1);
put_opt6_short(0);
- put_opt6_long(parm.pref_time);
+ put_opt6_long(min_pref_time);
put_opt6(opt_cfg->val, opt_cfg->len);
/* pad */
@@ -392,13 +431,13 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de
}
}
- if (daemon->port == NAMESERVER_PORT && !done_dns && parm.pref_time != 0)
+ if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0)
{
/* default == us, as long as we are supplying DNS service. */
put_opt6_char(ICMP6_OPT_RDNSS);
put_opt6_char(3);
put_opt6_short(0);
- put_opt6_long(parm.pref_time);
+ put_opt6_long(min_pref_time);
put_opt6(&parm.link_local, IN6ADDRSZ);
}
@@ -444,7 +483,16 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (if_index == param->ind)
{
if (IN6_IS_ADDR_LINKLOCAL(local))
- param->link_local = *local;
+ {
+ /* Can there be more than one LL address?
+ Select the one with the longest preferred time
+ if there is. */
+ if (preferred > param->link_pref_time)
+ {
+ param->link_pref_time = preferred;
+ param->link_local = *local;
+ }
+ }
else if (!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_MULTICAST(local))
{
@@ -534,11 +582,22 @@ static int add_prefixes(struct in6_addr *local, int prefix,
/* configured time is ceiling */
if (!constructed || preferred > time)
preferred = time;
-
- if (preferred > param->pref_time)
+
+ if (IN6_IS_ADDR_ULA(local))
{
- param->pref_time = preferred;
- param->link_global = *local;
+ if (preferred > param->ula_pref_time)
+ {
+ param->ula_pref_time = preferred;
+ param->ula = *local;
+ }
+ }
+ else
+ {
+ if (preferred > param->glob_pref_time)
+ {
+ param->glob_pref_time = preferred;
+ param->link_global = *local;
+ }
}
if (real_prefix != 0)
@@ -564,7 +623,6 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (!option_bool(OPT_QUIET_RA))
my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff);
}
-
}
}
}
diff --git a/src/rfc1035.c b/src/rfc1035.c
index c120d55..0b254e3 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,13 +16,6 @@
#include "dnsmasq.h"
-
-#define CHECK_LEN(header, pp, plen, len) \
- ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen))
-
-#define ADD_RDLEN(header, pp, plen, len) \
- (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
-
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes)
{
@@ -274,7 +267,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp)
return 0;
}
-static 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)
{
while(1)
{
@@ -500,7 +493,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t
else if (is_sign &&
i == arcount - 1 &&
class == C_ANY &&
- (type == T_SIG || type == T_TSIG))
+ type == T_TSIG)
*is_sign = 1;
}
@@ -515,7 +508,7 @@ struct macparm {
};
static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
- int optno, unsigned char *opt, size_t optlen)
+ int optno, unsigned char *opt, size_t optlen, int set_do)
{
unsigned char *lenp, *datap, *p;
int rdlen;
@@ -531,7 +524,8 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
- PUTLONG(0, p); /* extended RCODE */
+ PUTSHORT(0, p); /* extended RCODE and version */
+ PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
lenp = p;
PUTSHORT(0, p); /* RDLEN */
rdlen = 0;
@@ -543,7 +537,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
else
{
int i, is_sign;
- unsigned short code, len;
+ unsigned short code, len, flags;
if (ntohs(header->arcount) != 1 ||
!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) ||
@@ -551,14 +545,24 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
(!(p = skip_name(p, header, plen, 10))))
return plen;
- p += 8; /* skip UDP length and RCODE */
-
+ p += 6; /* skip UDP length and RCODE */
+ GETSHORT(flags, p);
+ if (set_do)
+ {
+ p -=2;
+ PUTSHORT(flags | 0x8000, p);
+ }
+
lenp = p;
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return plen; /* bad packet */
datap = p;
+ /* no option to add */
+ if (optno == 0)
+ return plen;
+
/* check if option already there */
for (i = 0; i + 4 < rdlen; i += len + 4)
{
@@ -573,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen; /* Too big */
}
- PUTSHORT(optno, p);
- PUTSHORT(optlen, p);
- memcpy(p, opt, optlen);
- p += optlen;
+ if (optno != 0)
+ {
+ PUTSHORT(optno, p);
+ PUTSHORT(optlen, p);
+ memcpy(p, opt, optlen);
+ p += optlen;
+ }
PUTSHORT(p - datap, lenp);
return p - (unsigned char *)header;
@@ -602,7 +609,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
if (!match)
return 1; /* continue */
- parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen);
+ parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0);
return 0; /* done */
}
@@ -681,9 +688,16 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio
struct subnet_opt opt;
len = calc_subnet_opt(&opt, source);
- return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len);
+ return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0);
}
-
+
+#ifdef HAVE_DNSSEC
+size_t add_do_bit(struct dns_header *header, size_t plen, char *limit)
+{
+ return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1);
+}
+#endif
+
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
{
/* Section 9.2, Check that subnet option in reply matches. */
@@ -878,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
- char **ipsets, int is_sign, int check_rebind, int checking_disabled)
+ char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure)
{
unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
@@ -907,8 +921,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
int found = 0, cname_count = 5;
struct crec *cpp = NULL;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
+ int secflag = secure ? F_DNSSECOK : 0;
unsigned long cttl = ULONG_MAX, attl;
-
+
namep = p;
if (!extract_name(header, qlen, &p, name, 1, 4))
return 0; /* bad packet */
@@ -969,7 +984,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
goto cname_loop;
}
- cache_insert(name, &addr, now, cttl, name_encoding | F_REVERSE);
+ cache_insert(name, &addr, now, cttl, name_encoding | secflag | F_REVERSE);
found = 1;
}
@@ -987,7 +1002,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
ttl = find_soa(header, qlen, NULL);
}
if (ttl)
- cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags);
+ cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag);
}
}
else
@@ -1037,7 +1052,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
if (!cname_count--)
return 0; /* looped CNAMES */
- newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD);
+ newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD | secflag);
if (newc)
{
newc->addr.cname.target.cache = NULL;
@@ -1080,7 +1095,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
#endif
- newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD);
+ newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag);
if (newc && cpp)
{
cpp->addr.cname.target.cache = newc;
@@ -1106,7 +1121,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
pointing at this, inherit its TTL */
if (ttl || cpp)
{
- newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags);
+ newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag);
if (newc && cpp)
{
cpp->addr.cname.target.cache = newc;
@@ -1118,15 +1133,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
/* Don't put stuff from a truncated packet into the cache.
- Don't cache replies where DNSSEC validation was turned off, either
- the upstream server told us so, or the original query specified it.
Don't cache replies from non-recursive nameservers, since we may get a
reply containing a CNAME but not its target, even though the target
does exist. */
if (!(header->hb3 & HB3_TC) &&
!(header->hb4 & HB4_CD) &&
(header->hb4 & HB4_RA) &&
- !checking_disabled)
+ !no_cache_dnssec)
cache_end_insert();
return 0;
@@ -1437,7 +1450,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int dryrun = 0, sec_reqd = 0;
int is_sign;
struct crec *crecp;
- int nxdomain = 0, auth = 1, trunc = 0;
+ int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
/* If there is an RFC2671 pseudoheader then it will be overwritten by
@@ -1612,6 +1625,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
continue;
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
if (crecp->flags & F_NEG)
{
ans = 1;
@@ -1785,6 +1801,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
break;
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
if (crecp->flags & F_CNAME)
{
char *cname_target = cache_get_cname_target(crecp);
@@ -1859,6 +1878,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
{
+ if (!(crecp->flags & F_DNSSECOK))
+ sec_data = 0;
+
ans = 1;
if (!dryrun)
{
@@ -2037,7 +2059,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* truncation */
if (trunc)
header->hb3 |= HB3_TC;
-
+
+ header->hb4 &= ~HB4_AD;
+
+ if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY))
+ if (sec_data)
+ header->hb4 |= HB4_AD;
+
if (nxdomain)
SET_RCODE(header, NXDOMAIN);
else
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 6f685c1..12dd284 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/rfc3315.c b/src/rfc3315.c
index 8a2660f..364277d 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -24,7 +24,7 @@ struct state {
int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate;
char *client_hostname, *hostname, *domain, *send_domain;
struct dhcp_context *context;
- struct in6_addr *link_address, *fallback;
+ struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
unsigned int xid, fqdn_flags;
char *iface_name;
void *packet_options, *end;
@@ -73,7 +73,8 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
- struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now)
+ struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+ size_t sz, struct in6_addr *client_addr, time_t now)
{
struct dhcp_vendor *vendor;
int msg_type;
@@ -93,6 +94,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
state.interface = interface;
state.iface_name = iface_name;
state.fallback = fallback;
+ state.ll_addr = ll_addr;
+ state.ula_addr = ula_addr;
state.mac_len = 0;
state.tags = NULL;
state.link_address = NULL;
@@ -1269,36 +1272,59 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
continue;
}
- if (opt_cfg->opt == OPTION6_DNS_SERVER)
- {
- done_dns = 1;
- if (opt_cfg->len == 0)
- continue;
- }
-
if (opt_cfg->opt == OPTION6_REFRESH_TIME)
done_refresh = 1;
- o = new_opt6(opt_cfg->opt);
if (opt_cfg->flags & DHOPT_ADDR6)
{
- int j;
- struct in6_addr *a = (struct in6_addr *)opt_cfg->val;
- for (j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
- {
- /* zero means "self" (but not in vendorclass options.) */
- if (IN6_IS_ADDR_UNSPECIFIED(a))
- {
- if (!add_local_addrs(state->context))
- put_opt6(state->fallback, IN6ADDRSZ);
+ int len, j;
+ struct in6_addr *a;
+
+ if (opt_cfg->opt == OPTION6_DNS_SERVER)
+ done_dns = 1;
+
+ for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0;
+ j < opt_cfg->len; j += IN6ADDRSZ, a++)
+ if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) ||
+ (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)))
+ len -= IN6ADDRSZ;
+
+ if (len != 0)
+ {
+
+ o = new_opt6(opt_cfg->opt);
+
+ for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
+ {
+ if (IN6_IS_ADDR_UNSPECIFIED(a))
+ {
+ if (!add_local_addrs(state->context))
+ put_opt6(state->fallback, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_ULA_ZERO(a))
+ {
+ if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr))
+ put_opt6(state->ula_addr, IN6ADDRSZ);
+ }
+ else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+ {
+ if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))
+ put_opt6(state->ll_addr, IN6ADDRSZ);
+ }
+ else
+ put_opt6(a, IN6ADDRSZ);
}
- else
- put_opt6(a, IN6ADDRSZ);
- }
+
+ end_opt6(o);
+ }
+ }
+ else
+ {
+ o = new_opt6(opt_cfg->opt);
+ if (opt_cfg->val)
+ put_opt6(opt_cfg->val, opt_cfg->len);
+ end_opt6(o);
}
- else if (opt_cfg->val)
- put_opt6(opt_cfg->val, opt_cfg->len);
- end_opt6(o);
}
if (daemon->port == NAMESERVER_PORT && !done_dns)
diff --git a/src/slaac.c b/src/slaac.c
index 7eb4236..351d680 100644
--- a/src/slaac.c
+++ b/src/slaac.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/tftp.c b/src/tftp.c
index 07b9e2e..a527911 100644
--- a/src/tftp.c
+++ b/src/tftp.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/src/util.c b/src/util.c
index 096297d..d5839fa 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -109,10 +109,10 @@ static int check_name(char *in)
if (in[l-1] == '.')
{
- if (l == 1) return 0;
in[l-1] = 0;
+ nowhite = 1;
}
-
+
for (; (c = *in); in++)
{
if (c == '.')
@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
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 */
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
{