mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-04 13:48:30 +00:00
Compare commits
21
Commits
v2.69test7
...
v2.69test9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a857daa351 | ||
|
|
f01d7be6c6 | ||
|
|
d387380a25 | ||
|
|
f2e4c277c4 | ||
|
|
5107ace14a | ||
|
|
7b1eae4f50 | ||
|
|
c152dc8492 | ||
|
|
7bcca0060f | ||
|
|
d68c2ca2b7 | ||
|
|
de73a497ca | ||
|
|
e3ec15af10 | ||
|
|
dac74312da | ||
|
|
2ecd9bd5c0 | ||
|
|
a0ab18f6eb | ||
|
|
ebe95a831f | ||
|
|
ee4158678a | ||
|
|
83349b8aa4 | ||
|
|
7fa836e105 | ||
|
|
1633e30834 | ||
|
|
c8ca33f810 | ||
|
|
e243c072b5 |
No files matched your search
@@ -62,7 +62,12 @@ version 2.69
|
||||
an invaluable period of full-time work to get it to
|
||||
a workable state.
|
||||
|
||||
Add --rev-server. Thanks to Dave Taht for suggesting this.
|
||||
|
||||
Add --servers-file. Allows dynamic update of upstream servers
|
||||
full access to configuration.
|
||||
|
||||
|
||||
version 2.68
|
||||
Use random addresses for DHCPv6 temporary address
|
||||
allocations, instead of algorithmically determined stable
|
||||
|
||||
Vendored
+4
@@ -19,6 +19,10 @@ LDFLAGS = $(shell dpkg-buildflags --get LDFLAGS)
|
||||
|
||||
DEB_COPTS = $(COPTS)
|
||||
|
||||
# The nettle library in Debian is too old to include
|
||||
# ECC support.
|
||||
DEB_COPTS += -DNO_NETTLE_ECC
|
||||
|
||||
TARGET = install-i18n
|
||||
|
||||
DEB_BUILD_ARCH_OS := $(shell dpkg-architecture -qDEB_BUILD_ARCH_OS)
|
||||
|
||||
+30
-6
@@ -13,7 +13,10 @@ Dnsmasq accepts DNS queries and either answers them from a small, local,
|
||||
cache or forwards them to a real, recursive, DNS server. It loads the
|
||||
contents of /etc/hosts so that local hostnames
|
||||
which do not appear in the global DNS can be resolved and also answers
|
||||
DNS queries for DHCP configured hosts. It can also act as the authoritative DNS server for one or more domains, allowing local names to appear in the global DNS.
|
||||
DNS queries for DHCP configured hosts. It can also act as the
|
||||
authoritative DNS server for one or more domains, allowing local names
|
||||
to appear in the global DNS. It can be configured to do DNSSEC
|
||||
validation.
|
||||
.PP
|
||||
The dnsmasq DHCP server supports static address assignments and multiple
|
||||
networks. It automatically
|
||||
@@ -412,6 +415,14 @@ source address specified but the port may be specified directly as
|
||||
part of the source address. Forcing queries to an interface is not
|
||||
implemented on all platforms supported by dnsmasq.
|
||||
.TP
|
||||
.B --rev-server=<ip-address>/<prefix-len>,<ipaddr>[#<port>][@<source-ip>|<interface>[#<port>]]
|
||||
This is functionally the same as
|
||||
.B --server,
|
||||
but provides some syntactic sugar to make specifying address-to-name queries easier. For example
|
||||
.B --rev-server=1.2.3.0/24,192.168.0.1
|
||||
is exactly equivalent to
|
||||
.B --server=/3.2.1.in-addr.arpa/192.168.0.1
|
||||
.TP
|
||||
.B \-A, --address=/<domain>/[domain/]<ipaddr>
|
||||
Specify an IP address to return for any host in the given domains.
|
||||
Queries in the domains are never forwarded and always replied to
|
||||
@@ -587,12 +598,15 @@ validation by clients more efficient. Note that validation by clients is the mos
|
||||
clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between
|
||||
the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC
|
||||
trust anchors provided, see
|
||||
.B --dnsskey.
|
||||
.B --trust-anchor.
|
||||
Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled.
|
||||
.TP
|
||||
.B --dnskey=[<class>],<domain>,<flags>,<algorithm>,<base64-key>
|
||||
Provide DNSKEY records to act a trust anchors for DNSSEC validation. Typically these will be the keys for root zone,
|
||||
but trust anchors for limited domains are also possible.
|
||||
.B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
|
||||
Provide DS records to act a trust anchors for DNSSEC
|
||||
validation. Typically these will be the DS record(s) for Zone Signing
|
||||
key(s) of the root zone,
|
||||
but trust anchors for limited domains are also possible. The current
|
||||
root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||
.TP
|
||||
.B --proxy-dnssec
|
||||
Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an
|
||||
@@ -601,7 +615,10 @@ dnsmasq and the upstream servers, and the trustworthiness of the upstream server
|
||||
.TP
|
||||
.B --dnssec-debug
|
||||
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
|
||||
and don't convert BOGUS replies to SERVFAIL responses.
|
||||
and don't convert replies which do not validate to responses with
|
||||
a return code of SERVFAIL. Note that
|
||||
setting this may affect DNS behaviour in bad ways, it is not an
|
||||
extra-logging flag and should not be set in production.
|
||||
.TP
|
||||
.B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]]
|
||||
Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain
|
||||
@@ -1661,6 +1678,13 @@ files. If extension(s) are given, any files which end in those
|
||||
extensions are skipped. Any files whose names end in ~ or start with . or start and end
|
||||
with # are always skipped. This flag may be given on the command
|
||||
line or in a configuration file.
|
||||
.TP
|
||||
.B --servers-file=<file>
|
||||
A special case of
|
||||
.B --conf-file
|
||||
which differs in two respects. Firstly, only --server and --rev-server are allowed
|
||||
in the configuration file included. Secondly, the file is re-read and the configuration
|
||||
therein is updated when dnsmasq recieves SIGHUP.
|
||||
.SH CONFIG FILE
|
||||
At startup, dnsmasq reads
|
||||
.I /etc/dnsmasq.conf,
|
||||
|
||||
+18
-16
@@ -448,19 +448,21 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
|
||||
int freed_all = flags & F_REVERSE;
|
||||
int free_avail = 0;
|
||||
|
||||
if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
|
||||
ttl = daemon->max_cache_ttl;
|
||||
|
||||
/* Don't log keys here, done elsewhere */
|
||||
/* Don't log DNSSEC records here, done elsewhere */
|
||||
if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
|
||||
log_query(flags | F_UPSTREAM, name, addr, NULL);
|
||||
{
|
||||
log_query(flags | F_UPSTREAM, name, addr, NULL);
|
||||
/* Don;t mess with TTL for DNSSEC records. */
|
||||
if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
|
||||
ttl = daemon->max_cache_ttl;
|
||||
}
|
||||
|
||||
/* if previous insertion failed give up now. */
|
||||
if (insert_error)
|
||||
return NULL;
|
||||
|
||||
/* First remove any expired entries and entries for the name/address we
|
||||
are currently inserting. Fail is we attempt to delete a name from
|
||||
are currently inserting. Fail if we attempt to delete a name from
|
||||
/etc/hosts or DHCP. */
|
||||
if (!cache_scan_free(name, addr, now, flags))
|
||||
{
|
||||
@@ -985,7 +987,7 @@ void cache_reload(void)
|
||||
struct cname *a;
|
||||
struct interface_name *intr;
|
||||
#ifdef HAVE_DNSSEC
|
||||
struct dnskey *key;
|
||||
struct ds_config *ds;
|
||||
#endif
|
||||
|
||||
cache_inserted = cache_live_freed = 0;
|
||||
@@ -1031,17 +1033,17 @@ void cache_reload(void)
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
for (key = daemon->dnskeys; key; key = key->next)
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
if ((cache = whine_malloc(sizeof(struct crec))) &&
|
||||
(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
|
||||
(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
|
||||
{
|
||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
|
||||
cache->name.namep = key->name;
|
||||
cache->addr.key.keylen = key->keylen;
|
||||
cache->addr.key.algo = key->algo;
|
||||
cache->addr.key.flags = key->flags;
|
||||
cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
|
||||
cache->uid = key->class;
|
||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
|
||||
cache->name.namep = ds->name;
|
||||
cache->addr.ds.keylen = ds->digestlen;
|
||||
cache->addr.ds.algo = ds->algo;
|
||||
cache->addr.ds.keytag = ds->keytag;
|
||||
cache->addr.ds.digest = ds->digest_type;
|
||||
cache->uid = ds->class;
|
||||
cache_hash(cache);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#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 35 /* choose to mininise fragmentation when storing DNSSEC keys */
|
||||
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
|
||||
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
||||
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
||||
#define FORWARD_TIME 20 /* or 20 seconds */
|
||||
|
||||
+17
-115
@@ -108,108 +108,6 @@ static void remove_watch(DBusWatch *watch, void *data)
|
||||
w = data; /* no warning */
|
||||
}
|
||||
|
||||
static void add_update_server(union mysockaddr *addr,
|
||||
union mysockaddr *source_addr,
|
||||
const char *interface,
|
||||
const char *domain)
|
||||
{
|
||||
struct server *serv;
|
||||
|
||||
/* See if there is a suitable candidate, and unmark */
|
||||
for (serv = daemon->servers; serv; serv = serv->next)
|
||||
if ((serv->flags & SERV_FROM_DBUS) &&
|
||||
(serv->flags & SERV_MARK))
|
||||
{
|
||||
if (domain)
|
||||
{
|
||||
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (serv->flags & SERV_HAS_DOMAIN)
|
||||
continue;
|
||||
}
|
||||
|
||||
serv->flags &= ~SERV_MARK;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!serv && (serv = whine_malloc(sizeof (struct server))))
|
||||
{
|
||||
/* Not found, create a new one. */
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
|
||||
if (domain && !(serv->domain = whine_malloc(strlen(domain)+1)))
|
||||
{
|
||||
free(serv);
|
||||
serv = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
serv->flags = SERV_FROM_DBUS;
|
||||
if (domain)
|
||||
{
|
||||
strcpy(serv->domain, domain);
|
||||
serv->flags |= SERV_HAS_DOMAIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (serv)
|
||||
{
|
||||
if (interface)
|
||||
strcpy(serv->interface, interface);
|
||||
else
|
||||
serv->interface[0] = 0;
|
||||
|
||||
if (source_addr->in.sin_family == AF_INET &&
|
||||
addr->in.sin_addr.s_addr == 0 &&
|
||||
serv->domain)
|
||||
serv->flags |= SERV_NO_ADDR;
|
||||
else
|
||||
{
|
||||
serv->flags &= ~SERV_NO_ADDR;
|
||||
serv->addr = *addr;
|
||||
serv->source_addr = *source_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_dbus(void)
|
||||
{
|
||||
struct server *serv;
|
||||
|
||||
/* mark everything from DBUS */
|
||||
for (serv = daemon->servers; serv; serv = serv->next)
|
||||
if (serv->flags & SERV_FROM_DBUS)
|
||||
serv->flags |= SERV_MARK;
|
||||
}
|
||||
|
||||
static void cleanup_dbus()
|
||||
{
|
||||
struct server *serv, *tmp, **up;
|
||||
|
||||
/* unlink and free anything still marked. */
|
||||
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
|
||||
{
|
||||
tmp = serv->next;
|
||||
if (serv->flags & SERV_MARK)
|
||||
{
|
||||
server_gone(serv);
|
||||
*up = serv->next;
|
||||
if (serv->domain)
|
||||
free(serv->domain);
|
||||
free(serv);
|
||||
}
|
||||
else
|
||||
up = &serv->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void dbus_read_servers(DBusMessage *message)
|
||||
{
|
||||
DBusMessageIter iter;
|
||||
@@ -218,8 +116,8 @@ static void dbus_read_servers(DBusMessage *message)
|
||||
|
||||
dbus_message_iter_init(message, &iter);
|
||||
|
||||
mark_dbus();
|
||||
|
||||
mark_servers(SERV_FROM_DBUS);
|
||||
|
||||
while (1)
|
||||
{
|
||||
int skip = 0;
|
||||
@@ -289,13 +187,13 @@ static void dbus_read_servers(DBusMessage *message)
|
||||
domain = NULL;
|
||||
|
||||
if (!skip)
|
||||
add_update_server(&addr, &source_addr, NULL, domain);
|
||||
add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain);
|
||||
|
||||
} while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
|
||||
}
|
||||
|
||||
/* unlink and free anything still marked. */
|
||||
cleanup_dbus();
|
||||
cleanup_servers();
|
||||
}
|
||||
|
||||
static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
@@ -319,7 +217,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
strings ? "Expected array of string" : "Expected array of string arrays");
|
||||
}
|
||||
|
||||
mark_dbus();
|
||||
mark_servers(SERV_FROM_DBUS);
|
||||
|
||||
/* array_iter points to each "as" element in the outer array */
|
||||
dbus_message_iter_recurse(&iter, &array_iter);
|
||||
@@ -327,6 +225,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
{
|
||||
const char *str = NULL;
|
||||
union mysockaddr addr, source_addr;
|
||||
int flags = 0;
|
||||
char interface[IF_NAMESIZE];
|
||||
char *str_addr, *str_domain = NULL;
|
||||
|
||||
@@ -416,16 +315,19 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
memset(&interface, 0, sizeof(interface));
|
||||
|
||||
/* parse the IP address */
|
||||
addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, NULL);
|
||||
|
||||
if (addr_err)
|
||||
{
|
||||
if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
|
||||
{
|
||||
error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
|
||||
"Invalid IP address '%s': %s",
|
||||
str, addr_err);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* 0.0.0.0 for server address == NULL, for Dbus */
|
||||
if (addr.in.sin_family == AF_INET &&
|
||||
addr.in.sin_addr.s_addr == 0)
|
||||
flags |= SERV_NO_ADDR;
|
||||
|
||||
if (strings)
|
||||
{
|
||||
char *p;
|
||||
@@ -439,7 +341,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
else
|
||||
p = NULL;
|
||||
|
||||
add_update_server(&addr, &source_addr, interface, str_domain);
|
||||
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain);
|
||||
} while ((str_domain = p));
|
||||
}
|
||||
else
|
||||
@@ -454,7 +356,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
dbus_message_iter_get_basic(&string_iter, &str);
|
||||
dbus_message_iter_next (&string_iter);
|
||||
|
||||
add_update_server(&addr, &source_addr, interface, str);
|
||||
add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str);
|
||||
} while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
|
||||
}
|
||||
|
||||
@@ -462,7 +364,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
|
||||
dbus_message_iter_next(&array_iter);
|
||||
}
|
||||
|
||||
cleanup_dbus();
|
||||
cleanup_servers();
|
||||
|
||||
if (dup)
|
||||
free(dup);
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
#define T_RRSIG 46
|
||||
#define T_NSEC 47
|
||||
#define T_DNSKEY 48
|
||||
#define T_NSEC3 50
|
||||
#define T_TKEY 249
|
||||
#define T_TSIG 250
|
||||
#define T_AXFR 252
|
||||
|
||||
+23
-6
@@ -98,7 +98,10 @@ int main (int argc, char **argv)
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
daemon->keyname = safe_malloc(MAXDNAME);
|
||||
{
|
||||
daemon->keyname = safe_malloc(MAXDNAME);
|
||||
daemon->workspacename = safe_malloc(MAXDNAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DHCP
|
||||
@@ -144,7 +147,7 @@ int main (int argc, char **argv)
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (!daemon->dnskeys)
|
||||
if (!daemon->ds)
|
||||
die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF);
|
||||
|
||||
if (daemon->cachesize < CACHESIZ)
|
||||
@@ -1114,7 +1117,7 @@ static void async_event(int pipe, time_t now)
|
||||
{
|
||||
pid_t p;
|
||||
struct event_desc ev;
|
||||
int i;
|
||||
int i, check = 0;
|
||||
char *msg;
|
||||
|
||||
/* NOTE: the memory used to return msg is leaked: use msgs in events only
|
||||
@@ -1125,11 +1128,25 @@ static void async_event(int pipe, time_t now)
|
||||
{
|
||||
case EVENT_RELOAD:
|
||||
clear_cache_and_reload(now);
|
||||
if (daemon->port != 0 && daemon->resolv_files && option_bool(OPT_NO_POLL))
|
||||
|
||||
if (daemon->port != 0)
|
||||
{
|
||||
reload_servers(daemon->resolv_files->name);
|
||||
check_servers();
|
||||
if (daemon->resolv_files && option_bool(OPT_NO_POLL))
|
||||
{
|
||||
reload_servers(daemon->resolv_files->name);
|
||||
check = 1;
|
||||
}
|
||||
|
||||
if (daemon->servers_file)
|
||||
{
|
||||
read_servers_file();
|
||||
check = 1;
|
||||
}
|
||||
|
||||
if (check)
|
||||
check_servers();
|
||||
}
|
||||
|
||||
#ifdef HAVE_DHCP
|
||||
rerun_scripts();
|
||||
#endif
|
||||
|
||||
+22
-10
@@ -295,10 +295,10 @@ struct cname {
|
||||
struct cname *next;
|
||||
};
|
||||
|
||||
struct dnskey {
|
||||
char *name, *key;
|
||||
int keylen, class, algo, flags;
|
||||
struct dnskey *next;
|
||||
struct ds_config {
|
||||
char *name, *digest;
|
||||
int digestlen, class, algo, keytag, digest_type;
|
||||
struct ds_config *next;
|
||||
};
|
||||
|
||||
#define ADDRLIST_LITERAL 1
|
||||
@@ -456,6 +456,7 @@ union mysockaddr {
|
||||
#define SERV_COUNTED 512 /* workspace for log code */
|
||||
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
|
||||
#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */
|
||||
#define SERV_FROM_FILE 4096 /* read from --servers-file */
|
||||
|
||||
struct serverfd {
|
||||
int fd;
|
||||
@@ -533,12 +534,14 @@ struct hostsfile {
|
||||
#define STAT_NEED_DS 4
|
||||
#define STAT_NEED_KEY 5
|
||||
#define STAT_TRUNCATED 6
|
||||
#define STAT_SECURE_WILDCARD 7
|
||||
|
||||
#define FREC_NOREBIND 1
|
||||
#define FREC_CHECKING_DISABLED 2
|
||||
#define FREC_HAS_SUBNET 4
|
||||
#define FREC_DNSKEY_QUERY 8
|
||||
#define FREC_DS_QUERY 16
|
||||
#define FREC_AD_QUESTION 32
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
#define HASH_SIZE 20 /* SHA-1 digest size */
|
||||
@@ -560,7 +563,7 @@ struct frec {
|
||||
time_t time;
|
||||
unsigned char *hash[HASH_SIZE];
|
||||
#ifdef HAVE_DNSSEC
|
||||
int class;
|
||||
int class, work_counter;
|
||||
struct blockdata *stash; /* Saved reply, whilst we validate */
|
||||
size_t stash_len;
|
||||
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
||||
@@ -863,6 +866,7 @@ extern struct daemon {
|
||||
unsigned int options, options2;
|
||||
struct resolvc default_resolv, *resolv_files;
|
||||
time_t last_resolv;
|
||||
char *servers_file;
|
||||
struct mx_srv_record *mxnames;
|
||||
struct naptr *naptr;
|
||||
struct txt_record *txt, *rr;
|
||||
@@ -929,7 +933,7 @@ extern struct daemon {
|
||||
struct prefix_class *prefix_classes;
|
||||
#endif
|
||||
#ifdef HAVE_DNSSEC
|
||||
struct dnskey *dnskeys;
|
||||
struct ds_config *ds;
|
||||
#endif
|
||||
|
||||
/* globally used stuff for DNS */
|
||||
@@ -938,6 +942,7 @@ extern struct daemon {
|
||||
char *namebuff; /* MAXDNAME size buffer */
|
||||
#ifdef HAVE_DNSSEC
|
||||
char *keyname; /* MAXDNAME size buffer */
|
||||
char *workspacename; /* ditto */
|
||||
#endif
|
||||
unsigned int local_answer, queries_forwarded, auth_answer;
|
||||
struct frec *frec_list;
|
||||
@@ -1032,6 +1037,7 @@ 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 char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
|
||||
unsigned int extract_request(struct dns_header *header, size_t qlen,
|
||||
char *name, unsigned short *typep);
|
||||
size_t setup_reply(struct dns_header *header, size_t qlen,
|
||||
@@ -1041,7 +1047,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
|
||||
time_t now, char **ipsets, int is_sign, int checkrebind,
|
||||
int no_cache, int secure, int *doctored);
|
||||
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
struct in_addr local_addr, struct in_addr local_netmask, time_t now);
|
||||
struct in_addr local_addr, struct in_addr local_netmask,
|
||||
time_t now, int *ad_reqd);
|
||||
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
|
||||
struct bogus_addr *addr, time_t now);
|
||||
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
|
||||
@@ -1105,9 +1112,6 @@ 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);
|
||||
@@ -1131,6 +1135,7 @@ void read_opts (int argc, char **argv, char *compile_opts);
|
||||
char *option_string(int prot, unsigned int opt, unsigned char *val,
|
||||
int opt_len, char *buf, int buf_len);
|
||||
void reread_dhcp(void);
|
||||
void read_servers_file(void);
|
||||
void set_option_bool(unsigned int opt);
|
||||
void reset_option_bool(unsigned int opt);
|
||||
struct hostsfile *expand_filelist(struct hostsfile *list);
|
||||
@@ -1154,6 +1159,13 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp);
|
||||
int random_sock(int family);
|
||||
void pre_allocate_sfds(void);
|
||||
int reload_servers(char *fname);
|
||||
void mark_servers(int flag);
|
||||
void cleanup_servers(void);
|
||||
void add_update_server(int flags,
|
||||
union mysockaddr *addr,
|
||||
union mysockaddr *source_addr,
|
||||
const char *interface,
|
||||
const char *domain);
|
||||
void check_servers(void);
|
||||
int enumerate_interfaces(int reset);
|
||||
void create_wildcard_listeners(void);
|
||||
|
||||
+618
-129
@@ -21,6 +21,10 @@
|
||||
|
||||
#include <nettle/rsa.h>
|
||||
#include <nettle/dsa.h>
|
||||
#ifndef NO_NETTLE_ECC
|
||||
# include <nettle/ecdsa.h>
|
||||
# include <nettle/ecc-curve.h>
|
||||
#endif
|
||||
#include <nettle/nettle-meta.h>
|
||||
#include <gmp.h>
|
||||
|
||||
@@ -194,7 +198,7 @@ static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned
|
||||
|
||||
if (key_len < (213 + (t * 24)))
|
||||
return 0;
|
||||
|
||||
|
||||
mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20;
|
||||
mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);
|
||||
mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);
|
||||
@@ -204,13 +208,87 @@ static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned
|
||||
mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21);
|
||||
|
||||
(void)algo;
|
||||
|
||||
|
||||
return nettle_dsa_sha1_verify_digest(key, digest, sig_struct);
|
||||
}
|
||||
|
||||
static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
|
||||
unsigned char *digest, int algo)
|
||||
#ifndef NO_NETTLE_ECC
|
||||
static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len,
|
||||
unsigned char *sig, size_t sig_len,
|
||||
unsigned char *digest, size_t digest_len, int algo)
|
||||
{
|
||||
unsigned char *p;
|
||||
unsigned int t;
|
||||
struct ecc_point *key;
|
||||
|
||||
static struct ecc_point *key_256 = NULL, *key_384 = NULL;
|
||||
static mpz_t x, y;
|
||||
static struct dsa_signature *sig_struct;
|
||||
|
||||
if (!sig_struct)
|
||||
{
|
||||
if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))
|
||||
return 0;
|
||||
|
||||
nettle_dsa_signature_init(sig_struct);
|
||||
mpz_init(x);
|
||||
mpz_init(y);
|
||||
}
|
||||
|
||||
switch (algo)
|
||||
{
|
||||
case 13:
|
||||
if (!key_256)
|
||||
{
|
||||
if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))
|
||||
return 0;
|
||||
|
||||
nettle_ecc_point_init(key_256, &nettle_secp_256r1);
|
||||
}
|
||||
|
||||
key = key_256;
|
||||
t = 32;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
if (!key_384)
|
||||
{
|
||||
if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))
|
||||
return 0;
|
||||
|
||||
nettle_ecc_point_init(key_384, &nettle_secp_384r1);
|
||||
}
|
||||
|
||||
key = key_384;
|
||||
t = 48;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sig_len != 2*t || key_len != 2*t ||
|
||||
(p = blockdata_retrieve(key_data, key_len, NULL)))
|
||||
return 0;
|
||||
|
||||
mpz_import(x, t , 1, 1, 0, 0, p);
|
||||
mpz_import(y, t , 1, 1, 0, 0, p + t);
|
||||
|
||||
if (!ecc_point_set(key, x, y))
|
||||
return 0;
|
||||
|
||||
mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);
|
||||
mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);
|
||||
|
||||
return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
|
||||
unsigned char *digest, size_t digest_len, int algo)
|
||||
{
|
||||
(void)digest_len;
|
||||
|
||||
switch (algo)
|
||||
{
|
||||
case 1: case 5: case 7: case 8: case 10:
|
||||
@@ -218,6 +296,11 @@ static int verify(struct blockdata *key_data, unsigned int key_len, unsigned cha
|
||||
|
||||
case 3: case 6:
|
||||
return dsa_verify(key_data, key_len, sig, sig_len, digest, algo);
|
||||
|
||||
#ifndef NO_NETTLE_ECC
|
||||
case 13: case 14:
|
||||
return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -415,6 +498,8 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
|
||||
int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
|
||||
u16 *dp1, *dp2;
|
||||
unsigned char *end1, *end2;
|
||||
/* Note that these have been determined to be OK previously,
|
||||
so we don't need to check for NULL return here. */
|
||||
unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
|
||||
unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
|
||||
|
||||
@@ -476,12 +561,15 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
|
||||
/* Validate a single RRset (class, type, name) in the supplied DNS reply
|
||||
Return code:
|
||||
STAT_SECURE if it validates.
|
||||
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
|
||||
STAT_INSECURE can't validate (no RRSIG, bad packet).
|
||||
STAT_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.
|
||||
|
||||
name is unchanged on exit. keyname is used as workspace and trashed.
|
||||
*/
|
||||
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
|
||||
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
|
||||
@@ -594,18 +682,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
return STAT_INSECURE;
|
||||
|
||||
/* Sort RRset records into canonical order.
|
||||
Note that at this point keyname and name buffs are
|
||||
Note that at this point keyname and daemon->workspacename buffs are
|
||||
unused, and used as workspace by the sort. */
|
||||
sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname);
|
||||
sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
|
||||
|
||||
/* Now try all the sigs to try and find one which validates */
|
||||
for (j = 0; j <sigidx; j++)
|
||||
{
|
||||
unsigned char *psav, *sig;
|
||||
unsigned char *psav, *sig, *digest;
|
||||
int i, wire_len, sig_len;
|
||||
const struct nettle_hash *hash;
|
||||
void *ctx;
|
||||
unsigned char *digest;
|
||||
char *name_start;
|
||||
u32 nsigttl;
|
||||
|
||||
p = sigs[j];
|
||||
@@ -622,7 +710,32 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
|
||||
if (!extract_name(header, plen, &p, keyname, 1, 0))
|
||||
return STAT_INSECURE;
|
||||
|
||||
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
|
||||
the name of the zone containing the RRset. We can't tell that
|
||||
for certain, but we can check that the RRset name is equal to
|
||||
or encloses the signers name, which should be enough to stop
|
||||
an attacker using signatures made with the key of an unrelated
|
||||
zone he controls. Note that the root key is always allowed. */
|
||||
if (*keyname != 0)
|
||||
{
|
||||
int failed = 0;
|
||||
|
||||
for (name_start = name; !hostname_isequal(name_start, keyname); )
|
||||
if ((name_start = strchr(name_start, '.')))
|
||||
name_start++; /* chop a label off and try again */
|
||||
else
|
||||
{
|
||||
failed = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Bad sig, try another */
|
||||
if (failed)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Other 5.3.1 checks */
|
||||
if (!check_date_range(sig_inception, sig_expiration) ||
|
||||
labels > name_labels ||
|
||||
!(hash = hash_find(algo_digest_name(algo))) ||
|
||||
@@ -647,13 +760,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
{
|
||||
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;
|
||||
|
||||
name_start = name;
|
||||
|
||||
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
||||
if (labels < name_labels)
|
||||
{
|
||||
@@ -703,7 +817,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
if (key)
|
||||
{
|
||||
if (algo_in == algo && keytag_in == key_tag &&
|
||||
verify(key, keylen, sig, sig_len, digest, algo))
|
||||
verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||
return STAT_SECURE;
|
||||
}
|
||||
else
|
||||
@@ -713,8 +827,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
if (crecp->addr.key.algo == algo &&
|
||||
crecp->addr.key.keytag == key_tag &&
|
||||
crecp->uid == class &&
|
||||
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, algo))
|
||||
return STAT_SECURE;
|
||||
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
|
||||
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,7 +860,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
GETSHORT(qclass, p);
|
||||
|
||||
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
|
||||
return STAT_INSECURE;
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* See if we have cached a DS record which validates this key */
|
||||
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
|
||||
@@ -780,7 +894,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
GETSHORT(flags, p);
|
||||
if (*p++ != 3)
|
||||
return STAT_INSECURE;
|
||||
return STAT_BOGUS;
|
||||
algo = *p++;
|
||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
||||
key = NULL;
|
||||
@@ -870,7 +984,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
GETSHORT(flags, p);
|
||||
if (*p++ != 3)
|
||||
return STAT_INSECURE;
|
||||
return STAT_BOGUS;
|
||||
algo = *p++;
|
||||
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
|
||||
|
||||
@@ -966,7 +1080,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
GETSHORT(qclass, p);
|
||||
|
||||
if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
|
||||
return STAT_INSECURE;
|
||||
return STAT_BOGUS;
|
||||
|
||||
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
|
||||
|
||||
@@ -1044,15 +1158,455 @@ static int hostname_cmp(const char *a, const char *b)
|
||||
}
|
||||
}
|
||||
|
||||
/* Find all the NSEC or NSEC3 records in a reply.
|
||||
return an array of pointers to them. */
|
||||
static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd)
|
||||
{
|
||||
static unsigned char **nsecset = NULL;
|
||||
static int nsecset_sz = 0;
|
||||
|
||||
int type_found = -1;
|
||||
unsigned char *p = skip_questions(header, plen);
|
||||
int type, class, rdlen, i, nsecs_found;
|
||||
|
||||
/* Move to NS section */
|
||||
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
|
||||
return 0;
|
||||
|
||||
for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
|
||||
{
|
||||
unsigned char *pstart = p;
|
||||
|
||||
if (!(p = skip_name(p, header, plen, 10)))
|
||||
return 0;
|
||||
|
||||
GETSHORT(type, p);
|
||||
GETSHORT(class, p);
|
||||
p += 4; /* TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
|
||||
if (class == class_reqd && (type == T_NSEC || type == T_NSEC3))
|
||||
{
|
||||
/* No mixed NSECing 'round here, thankyouverymuch */
|
||||
if (type_found == T_NSEC && type == T_NSEC3)
|
||||
return 0;
|
||||
if (type_found == T_NSEC3 && type == T_NSEC)
|
||||
return 0;
|
||||
|
||||
type_found = type;
|
||||
|
||||
if (nsecs_found == nsecset_sz)
|
||||
{
|
||||
unsigned char **new;
|
||||
|
||||
/* Protect against insane/malicious queries which bloat the workspace
|
||||
and eat CPU in the sort */
|
||||
if (nsecs_found >= 100)
|
||||
return 0;
|
||||
|
||||
/* expand */
|
||||
if (!(new = whine_malloc((nsecset_sz + 5) * sizeof(unsigned char **))))
|
||||
return 0;
|
||||
|
||||
if (nsecset)
|
||||
{
|
||||
memcpy(new, nsecset, nsecset_sz * sizeof(unsigned char **));
|
||||
free(nsecset);
|
||||
}
|
||||
|
||||
nsecset = new;
|
||||
nsecset_sz += 5;
|
||||
}
|
||||
|
||||
nsecset[nsecs_found++] = pstart;
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||
return 0;
|
||||
}
|
||||
|
||||
*nsecsetp = nsecset;
|
||||
*nsecsetl = nsecs_found;
|
||||
|
||||
return type_found;
|
||||
}
|
||||
|
||||
static int prove_non_existance_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
|
||||
char *workspace1, char *workspace2, char *name, int type)
|
||||
{
|
||||
int i, rc, rdlen;
|
||||
unsigned char *p, *psave;
|
||||
int offset = (type & 0xff) >> 3;
|
||||
int mask = 0x80 >> (type & 0x07);
|
||||
|
||||
/* Find NSEC record that proves name doesn't exist */
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
{
|
||||
p = nsecs[i];
|
||||
if (!extract_name(header, plen, &p, workspace1, 1, 10))
|
||||
return STAT_INSECURE;
|
||||
p += 8; /* class, type, TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
psave = p;
|
||||
if (!extract_name(header, plen, &p, workspace2, 1, 10))
|
||||
return STAT_INSECURE;
|
||||
|
||||
rc = hostname_cmp(workspace1, name);
|
||||
|
||||
if (rc == 0)
|
||||
{
|
||||
/* 4035 para 5.4. Last sentence */
|
||||
if (type == T_NSEC || type == T_RRSIG)
|
||||
return STAT_SECURE;
|
||||
|
||||
/* NSEC with the same name as the RR we're testing, check
|
||||
that the type in question doesn't appear in the type map */
|
||||
rdlen -= p - psave;
|
||||
/* rdlen is now length of type map, and p points to it */
|
||||
|
||||
while (rdlen >= 2)
|
||||
{
|
||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
{
|
||||
/* Does the NSEC say our type exists? */
|
||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||
return STAT_BOGUS;
|
||||
|
||||
break; /* finshed checking */
|
||||
}
|
||||
|
||||
rdlen -= p[1];
|
||||
p += p[1];
|
||||
}
|
||||
|
||||
return STAT_SECURE;
|
||||
}
|
||||
else if (rc == -1)
|
||||
{
|
||||
/* Normal case, name falls between NSEC name and next domain name,
|
||||
wrap around case, name falls between NSEC name (rc == -1) and end */
|
||||
if (hostname_cmp(workspace2, name) == 1 || hostname_cmp(workspace1, workspace2) == 1)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* wrap around case, name falls between start and next domain name */
|
||||
if (hostname_cmp(workspace1, workspace2) == 1 && hostname_cmp(workspace2, name) == 1)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
}
|
||||
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
/* return digest length, or zero on error */
|
||||
static int hash_name(char *in, unsigned char **out, struct nettle_hash const *hash,
|
||||
unsigned char *salt, int salt_len, int iterations)
|
||||
{
|
||||
void *ctx;
|
||||
unsigned char *digest;
|
||||
int i;
|
||||
|
||||
if (!hash_init(hash, &ctx, &digest))
|
||||
return 0;
|
||||
|
||||
hash->update(ctx, to_wire(in), (unsigned char *)in);
|
||||
hash->update(ctx, salt_len, salt);
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
|
||||
for(i = 0; i < iterations; i++)
|
||||
{
|
||||
hash->update(ctx, hash->digest_size, digest);
|
||||
hash->update(ctx, salt_len, salt);
|
||||
hash->digest(ctx, hash->digest_size, digest);
|
||||
}
|
||||
|
||||
from_wire(in);
|
||||
|
||||
*out = digest;
|
||||
return hash->digest_size;
|
||||
}
|
||||
|
||||
/* Decode base32 to first "." or end of string */
|
||||
static int base32_decode(char *in, unsigned char *out)
|
||||
{
|
||||
int oc, on, c, mask, i;
|
||||
unsigned char *p = out;
|
||||
|
||||
for (c = *in, oc = 0, on = 0; c != 0 && c != '.'; c = *++in)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
c -= '0';
|
||||
else if (c >= 'a' && c <= 'v')
|
||||
c -= 'a', c += 10;
|
||||
else if (c >= 'A' && c <= 'V')
|
||||
c -= 'A', c += 10;
|
||||
else
|
||||
return 0;
|
||||
|
||||
for (mask = 0x10, i = 0; i < 5; i++)
|
||||
{
|
||||
if (c & mask)
|
||||
oc |= 1;
|
||||
mask = mask >> 1;
|
||||
if (((++on) & 7) == 0)
|
||||
*p++ = oc;
|
||||
oc = oc << 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((on & 7) != 0)
|
||||
return 0;
|
||||
|
||||
return p - out;
|
||||
}
|
||||
|
||||
static int prove_non_existance_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
|
||||
char *workspace1, char *workspace2, char *name, int type)
|
||||
{
|
||||
unsigned char *salt, *p, *digest;
|
||||
int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0;
|
||||
struct nettle_hash const *hash;
|
||||
char *closest_encloser, *next_closest, *wildcard;
|
||||
|
||||
/* Look though the NSEC3 records to find the first one with
|
||||
an algorithm we support (currently only algo == 1).
|
||||
|
||||
Take the algo, iterations, and salt of that record
|
||||
as the ones we're going to use, and prune any
|
||||
that don't match. */
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
{
|
||||
if (!(p = skip_name(nsecs[i], header, plen, 15)))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
p += 10; /* type, class, TTL, rdlen */
|
||||
algo = *p++;
|
||||
|
||||
if (algo == 1)
|
||||
break; /* known algo */
|
||||
}
|
||||
|
||||
/* No usable NSEC3s */
|
||||
if (i == nsec_count)
|
||||
return STAT_BOGUS;
|
||||
|
||||
p++; /* flags */
|
||||
GETSHORT (iterations, p);
|
||||
salt_len = *p++;
|
||||
salt = p;
|
||||
if (!CHECK_LEN(header, salt, plen, salt_len))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
{
|
||||
unsigned char *nsec3p = nsecs[i];
|
||||
int this_iter;
|
||||
|
||||
nsecs[i] = NULL; /* Speculative, will be restored if OK. */
|
||||
|
||||
if (!(p = skip_name(nsec3p, header, plen, 15)))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
p += 10; /* type, class, TTL, rdlen */
|
||||
|
||||
if (*p++ != algo)
|
||||
continue;
|
||||
|
||||
p++; /* flags */
|
||||
|
||||
GETSHORT(this_iter, p);
|
||||
if (this_iter != iterations)
|
||||
continue;
|
||||
|
||||
if (salt_len != *p++)
|
||||
continue;
|
||||
|
||||
if (!CHECK_LEN(header, p, plen, salt_len))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
if (memcmp(p, salt, salt_len) != 0)
|
||||
continue;
|
||||
|
||||
/* All match, put the pointer back */
|
||||
nsecs[i] = nsec3p;
|
||||
}
|
||||
|
||||
/* Algo is checked as 1 above */
|
||||
if (!(hash = hash_find("sha1")))
|
||||
return STAT_INSECURE;
|
||||
|
||||
/* Now, we need the "closest encloser NSEC3" */
|
||||
closest_encloser = name;
|
||||
next_closest = NULL;
|
||||
|
||||
do
|
||||
{
|
||||
if (*closest_encloser == '.')
|
||||
closest_encloser++;
|
||||
|
||||
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
if ((p = nsecs[i]))
|
||||
{
|
||||
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (digest_len == base32_len &&
|
||||
memcmp(digest, workspace2, digest_len) == 0)
|
||||
break; /* Gotit */
|
||||
}
|
||||
|
||||
if (i != nsec_count)
|
||||
break;
|
||||
|
||||
next_closest = closest_encloser;
|
||||
}
|
||||
while ((closest_encloser = strchr(closest_encloser, '.')));
|
||||
|
||||
/* No usable NSEC3s */
|
||||
if (i == nsec_count)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (!next_closest)
|
||||
{
|
||||
/* We found an NSEC3 whose hashed name exactly matches the query, so
|
||||
Now we just need to check the type map. p points to the RR data for the record. */
|
||||
int rdlen;
|
||||
unsigned char *psave;
|
||||
int offset = (type & 0xff) >> 3;
|
||||
int mask = 0x80 >> (type & 0x07);
|
||||
|
||||
p += 8; /* class, type, TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
psave = p;
|
||||
p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */
|
||||
hash_len = *p++;
|
||||
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
p += hash_len;
|
||||
rdlen -= p - psave;
|
||||
|
||||
while (rdlen >= 2)
|
||||
{
|
||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
{
|
||||
/* Does the NSEC3 say our type exists? */
|
||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||
return STAT_BOGUS;
|
||||
|
||||
break; /* finshed checking */
|
||||
}
|
||||
|
||||
rdlen -= p[1];
|
||||
p += p[1];
|
||||
}
|
||||
|
||||
return STAT_SECURE;
|
||||
}
|
||||
|
||||
/* Look for NSEC3 that proves the non-existance of the next-closest encloser */
|
||||
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
if ((p = nsecs[i]))
|
||||
{
|
||||
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||
return STAT_INSECURE;
|
||||
|
||||
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
|
||||
hash_len = *p++; /* p now points to next hashed name */
|
||||
|
||||
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (digest_len == base32_len && hash_len == base32_len)
|
||||
{
|
||||
if (memcmp(workspace2, digest, digest_len) <= 0)
|
||||
{
|
||||
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
|
||||
wrap around case, name-hash falls between NSEC3 name-hash and end */
|
||||
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* wrap around case, name falls between start and next domain name */
|
||||
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, check that there's no seat of wildcard synthesis */
|
||||
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
|
||||
return STAT_BOGUS;
|
||||
|
||||
wildcard--;
|
||||
*wildcard = '*';
|
||||
|
||||
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
if ((p = nsecs[i]))
|
||||
{
|
||||
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
|
||||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
|
||||
return STAT_INSECURE;
|
||||
|
||||
p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */
|
||||
hash_len = *p++; /* p now points to next hashed name */
|
||||
|
||||
if (!CHECK_LEN(header, p, plen, hash_len))
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (digest_len == base32_len && hash_len == base32_len)
|
||||
{
|
||||
if (memcmp(workspace2, digest, digest_len) <= 0)
|
||||
{
|
||||
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
|
||||
wrap around case, name-hash falls between NSEC3 name-hash and end */
|
||||
if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* wrap around case, name falls between start and next domain name */
|
||||
if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)
|
||||
return STAT_SECURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
|
||||
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
|
||||
{
|
||||
unsigned char *ans_start, *p1, *p2;
|
||||
unsigned char *ans_start, *p1, *p2, **nsecs;
|
||||
int type1, class1, rdlen1, type2, class2, rdlen2;
|
||||
int i, j, rc, have_nsec, have_nsec_equal, cname_count = 5;
|
||||
int i, j, rc, nsec_count, cname_count = 10;
|
||||
int nsec_type = 0;
|
||||
|
||||
if (RCODE(header) == SERVFAIL)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
|
||||
return STAT_INSECURE;
|
||||
|
||||
@@ -1099,13 +1653,39 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
struct blockdata *key;
|
||||
struct crec *crecp;
|
||||
|
||||
if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
|
||||
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0);
|
||||
|
||||
if (rc == STAT_SECURE_WILDCARD)
|
||||
{
|
||||
/* An attacker replay a wildcard answer with a different
|
||||
answer and overlay a genuine RR. To prove this
|
||||
hasn't happened, the answer must prove that
|
||||
the gennuine record doesn't exist. Check that here. */
|
||||
if (!nsec_type)
|
||||
{
|
||||
nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
|
||||
|
||||
if (nsec_type == 0)
|
||||
return STAT_INSECURE; /* Bad packet */
|
||||
if (nsec_type == -1)
|
||||
return STAT_BOGUS; /* No NSECs */
|
||||
}
|
||||
|
||||
if (nsec_type == T_NSEC)
|
||||
rc = prove_non_existance_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||
else
|
||||
rc = prove_non_existance_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||
|
||||
if (rc != STAT_SECURE)
|
||||
return rc;
|
||||
}
|
||||
else if (rc != STAT_SECURE)
|
||||
{
|
||||
if (class)
|
||||
*class = class1; /* Class for DS or DNSKEY */
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
|
||||
cache_start_insert();
|
||||
|
||||
@@ -1168,6 +1748,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
type_covered == T_DNSKEY || type_covered == T_PTR))
|
||||
{
|
||||
a.addr.dnssec.type = type_covered;
|
||||
a.addr.dnssec.class = class1;
|
||||
|
||||
algo = *p2++;
|
||||
p2 += 13; /* labels, orig_ttl, expiration, inception */
|
||||
@@ -1234,7 +1815,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
{
|
||||
/* Do we have an answer for the question? */
|
||||
if (type1 == type2)
|
||||
return RCODE(header) == NXDOMAIN ? STAT_INSECURE : STAT_SECURE;
|
||||
return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
|
||||
else if (type2 == T_CNAME)
|
||||
{
|
||||
/* looped CNAMES */
|
||||
@@ -1251,117 +1832,25 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
/* NXDOMAIN or NODATA reply, look for NSEC records to support that.
|
||||
At this point, p1 points to the start of the auth section.
|
||||
Use keyname as workspace */
|
||||
for (have_nsec = 0, have_nsec_equal = 0, p2 = NULL, rdlen2 = 0, j = ntohs(header->nscount); j != 0; j--)
|
||||
{
|
||||
unsigned char *nsec_start = p1;
|
||||
if (!extract_name(header, plen, &p1, keyname, 1, 10))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
GETSHORT(type2, p1);
|
||||
GETSHORT(class2, p1);
|
||||
p1 += 4; /* TTL */
|
||||
GETSHORT(rdlen1, p1);
|
||||
|
||||
if (class1 == class2 && type2 == T_NSEC)
|
||||
{
|
||||
have_nsec = 1;
|
||||
rc = hostname_cmp(name, keyname);
|
||||
|
||||
if (rc >= 0)
|
||||
{
|
||||
if (p2)
|
||||
{
|
||||
unsigned char *psave = p2;
|
||||
/* new NSEC is smaller than name,
|
||||
is it bigger than previous one? */
|
||||
|
||||
/* get previous one into name buffer */
|
||||
if (!extract_name(header, plen, &psave, name, 1, 0))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
if (hostname_cmp(name, keyname) < 0)
|
||||
{
|
||||
p2 = nsec_start;
|
||||
rdlen2 = rdlen1;
|
||||
}
|
||||
|
||||
/* restore query name */
|
||||
psave = (unsigned char *)(header+1);
|
||||
if (!extract_name(header, plen, &psave, name, 1, 0))
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There was no previous best candidate */
|
||||
p2 = nsec_start;
|
||||
rdlen2 = rdlen1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
have_nsec_equal = 1;
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
|
||||
|
||||
if (p2)
|
||||
/* First marshall the NSEC records, if we've not done it previously */
|
||||
if (!nsec_type)
|
||||
{
|
||||
unsigned char *psave;
|
||||
p2 = skip_name(p2, header, plen, 0);
|
||||
p2 += 10; /* type, class, ttl, rdlen */
|
||||
psave = p2;
|
||||
extract_name(header, plen, &p2, keyname, 1, 0);
|
||||
rdlen2 -= p2 - psave;
|
||||
nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
|
||||
|
||||
if (nsec_type == 0)
|
||||
return STAT_INSECURE; /* Bad packet */
|
||||
if (nsec_type == -1)
|
||||
return STAT_BOGUS; /* No NSECs */
|
||||
}
|
||||
|
||||
/* At this point, have_nsec is set if there's at least one NSEC
|
||||
have_nsec_equal is set if there's an NSEC with the same name as the query;
|
||||
p2 points to the type bit maps of the biggest NSEC smaller than or equal to the query
|
||||
or NULL if the query is smaller than all of them.
|
||||
Keyname holds the next domain name for that NSEC.
|
||||
rdlen2 is the length of the bitmap field */
|
||||
|
||||
|
||||
if (RCODE(header) == NOERROR && have_nsec_equal)
|
||||
{
|
||||
int offset = (type1 & 0xff) >> 3;
|
||||
int mask = 0x80 >> (type1 & 0x07);
|
||||
|
||||
while (rdlen2 >= 2)
|
||||
{
|
||||
if (p2[0] == type1 >> 8)
|
||||
{
|
||||
/* Does the NSEC say our type exists? */
|
||||
if (offset < p2[1] &&
|
||||
(p2[offset+2] & mask) != 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
break; /* finshed checking */
|
||||
}
|
||||
|
||||
rdlen2 -= p2[1];
|
||||
p2 += p2[1];
|
||||
}
|
||||
|
||||
return STAT_SECURE;
|
||||
}
|
||||
|
||||
if (RCODE(header) == NXDOMAIN && have_nsec)
|
||||
{
|
||||
if (!p2 || hostname_cmp(name, keyname) < 0)
|
||||
return STAT_SECURE; /* Before the first, or in a proven gap */
|
||||
}
|
||||
|
||||
return STAT_INSECURE;
|
||||
if (nsec_type == T_NSEC)
|
||||
return prove_non_existance_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||
else
|
||||
return prove_non_existance_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
|
||||
}
|
||||
|
||||
|
||||
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
|
||||
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
||||
{
|
||||
@@ -1373,12 +1862,12 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned long ac;
|
||||
unsigned long ac = flags + 0x300 + alg;
|
||||
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;
|
||||
}
|
||||
|
||||
+180
-157
@@ -234,7 +234,8 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp,
|
||||
|
||||
static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
struct all_addr *dst_addr, unsigned int dst_iface,
|
||||
struct dns_header *header, size_t plen, time_t now, struct frec *forward)
|
||||
struct dns_header *header, size_t plen, time_t now,
|
||||
struct frec *forward, int ad_reqd)
|
||||
{
|
||||
char *domain = NULL;
|
||||
int type = 0, norebind = 0;
|
||||
@@ -249,16 +250,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
#endif
|
||||
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
||||
|
||||
/* RFC 4035: sect 4.6 para 2 */
|
||||
header->hb4 &= ~HB4_AD;
|
||||
|
||||
/* may be no servers available. */
|
||||
if (!daemon->servers)
|
||||
forward = NULL;
|
||||
else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
|
||||
{
|
||||
#ifdef HAVE_DNSSEC
|
||||
/* If we've already got an answer to this query, but we're awaiting keys for vaildation,
|
||||
/* If we've already got an answer to this query, but we're awaiting keys for validation,
|
||||
there's no point retrying the query, retry the key query instead...... */
|
||||
if (forward->blocking_query)
|
||||
{
|
||||
@@ -334,6 +332,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
forward->flags |= FREC_NOREBIND;
|
||||
if (header->hb4 & HB4_CD)
|
||||
forward->flags |= FREC_CHECKING_DISABLED;
|
||||
if (ad_reqd)
|
||||
forward->flags |= FREC_AD_QUESTION;
|
||||
#ifdef HAVE_DNSSEC
|
||||
forward->work_counter = DNSSEC_WORK;
|
||||
#endif
|
||||
|
||||
header->id = htons(forward->new_id);
|
||||
|
||||
@@ -503,13 +506,15 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
}
|
||||
|
||||
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
||||
int no_cache, int cache_secure, int check_subnet, union mysockaddr *query_source)
|
||||
int no_cache, int cache_secure, int ad_reqd, int check_subnet, union mysockaddr *query_source)
|
||||
{
|
||||
unsigned char *pheader, *sizep;
|
||||
char **sets = 0;
|
||||
int munged = 0, is_sign;
|
||||
size_t plen;
|
||||
|
||||
(void)ad_reqd;
|
||||
|
||||
#ifdef HAVE_IPSET
|
||||
/* Similar algorithm to search_servers. */
|
||||
struct ipsets *ipset_pos;
|
||||
@@ -535,15 +540,13 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
|
||||
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
|
||||
{
|
||||
if (!is_sign)
|
||||
{
|
||||
unsigned short udpsz;
|
||||
unsigned char *psave = sizep;
|
||||
|
||||
GETSHORT(udpsz, sizep);
|
||||
if (udpsz > daemon->edns_pktsz)
|
||||
PUTSHORT(daemon->edns_pktsz, psave);
|
||||
}
|
||||
unsigned short udpsz;
|
||||
unsigned char *psave = sizep;
|
||||
|
||||
GETSHORT(udpsz, sizep);
|
||||
|
||||
if (!is_sign && udpsz > daemon->edns_pktsz)
|
||||
PUTSHORT(daemon->edns_pktsz, psave);
|
||||
|
||||
if (check_subnet && !check_source(header, plen, pheader, query_source))
|
||||
{
|
||||
@@ -551,7 +554,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* RFC 4035 sect 4.6 para 3 */
|
||||
if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
|
||||
header->hb4 &= ~HB4_AD;
|
||||
@@ -619,7 +622,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
header->hb4 &= ~HB4_AD;
|
||||
|
||||
if (!(header->hb4 & HB4_CD) && cache_secure)
|
||||
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
|
||||
header->hb4 |= HB4_AD;
|
||||
#endif
|
||||
|
||||
@@ -705,7 +708,7 @@ void reply_query(int fd, int family, time_t now)
|
||||
if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
|
||||
{
|
||||
header->hb3 &= ~(HB3_QR | HB3_TC);
|
||||
forward_query(-1, NULL, NULL, 0, header, nn, now, forward);
|
||||
forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -775,15 +778,31 @@ void reply_query(int fd, int family, time_t now)
|
||||
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;
|
||||
struct frec *new, *orig;
|
||||
|
||||
if ((new = get_new_frec(now, NULL, 1)))
|
||||
/* Free any saved query */
|
||||
if (forward->stash)
|
||||
blockdata_free(forward->stash);
|
||||
|
||||
/* Now save reply pending receipt of key data */
|
||||
if (!(forward->stash = blockdata_alloc((char *)header, n)))
|
||||
return;
|
||||
forward->stash_len = n;
|
||||
|
||||
anotherkey:
|
||||
/* Find the original query that started it all.... */
|
||||
for (orig = forward; orig->dependent; orig = orig->dependent);
|
||||
|
||||
if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
|
||||
status = STAT_INSECURE;
|
||||
else
|
||||
{
|
||||
int fd;
|
||||
struct frec *next = new->next;
|
||||
*new = *forward; /* copy everything, then overwrite */
|
||||
new->next = next;
|
||||
@@ -794,80 +813,67 @@ void reply_query(int fd, int family, time_t now)
|
||||
#endif
|
||||
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
|
||||
|
||||
/* Free any saved query */
|
||||
if (forward->stash)
|
||||
blockdata_free(forward->stash);
|
||||
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
|
||||
{
|
||||
new->flags |= FREC_DS_QUERY;
|
||||
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
|
||||
daemon->keyname, forward->class, T_DS, &server->addr);
|
||||
}
|
||||
if ((hash = hash_questions(header, nn, daemon->namebuff)))
|
||||
memcpy(new->hash, hash, HASH_SIZE);
|
||||
new->new_id = get_id();
|
||||
header->id = htons(new->new_id);
|
||||
/* Save query for retransmission */
|
||||
new->stash = blockdata_alloc((char *)header, nn);
|
||||
new->stash_len = nn;
|
||||
|
||||
/* Now save reply pending receipt of key data */
|
||||
if (!(forward->stash = blockdata_alloc((char *)header, n)))
|
||||
free_frec(new); /* malloc failure, unwind */
|
||||
/* Don't resend this. */
|
||||
daemon->srv_save = NULL;
|
||||
|
||||
if (server->sfd)
|
||||
fd = server->sfd->fd;
|
||||
else
|
||||
{
|
||||
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
|
||||
{
|
||||
new->flags |= FREC_DS_QUERY;
|
||||
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
|
||||
daemon->keyname, forward->class, T_DS, &server->addr);
|
||||
}
|
||||
if ((hash = hash_questions(header, nn, daemon->namebuff)))
|
||||
memcpy(new->hash, hash, HASH_SIZE);
|
||||
new->new_id = get_id();
|
||||
header->id = htons(new->new_id);
|
||||
/* Save query for retransmission */
|
||||
new->stash = blockdata_alloc((char *)header, nn);
|
||||
new->stash_len = nn;
|
||||
|
||||
/* Don't resend this. */
|
||||
daemon->srv_save = NULL;
|
||||
|
||||
if (server->sfd)
|
||||
fd = server->sfd->fd;
|
||||
else
|
||||
{
|
||||
fd = -1;
|
||||
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)
|
||||
if (server->addr.sa.sa_family == AF_INET6)
|
||||
{
|
||||
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
|
||||
server->queries++;
|
||||
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;
|
||||
}
|
||||
|
||||
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. */
|
||||
and validate them with the new data. Note that if an answer needs multiple
|
||||
keys to validate, we may find another key is needed, in which case we set off
|
||||
down another branch of the tree. Once we get to the original answer
|
||||
(FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */
|
||||
while (forward->dependent)
|
||||
{
|
||||
struct frec *prev = forward->dependent;
|
||||
@@ -887,18 +893,23 @@ void reply_query(int fd, int family, time_t now)
|
||||
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
|
||||
|
||||
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
||||
{
|
||||
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
|
||||
status = STAT_INSECURE;
|
||||
}
|
||||
goto anotherkey;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == STAT_TRUNCATED)
|
||||
header->hb3 |= HB3_TC;
|
||||
else
|
||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
|
||||
status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
||||
{
|
||||
char *result;
|
||||
|
||||
if (forward->work_counter == 0)
|
||||
result = "ABANDONED";
|
||||
else
|
||||
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
||||
|
||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||
}
|
||||
|
||||
no_cache_dnssec = 0;
|
||||
|
||||
@@ -906,17 +917,17 @@ void reply_query(int fd, int family, time_t now)
|
||||
cache_secure = 1;
|
||||
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
|
||||
#endif
|
||||
|
||||
/* restore CD bit to the value in the query */
|
||||
if (forward->flags & FREC_CHECKING_DISABLED)
|
||||
header->hb4 |= HB4_CD;
|
||||
else
|
||||
header->hb4 &= ~HB4_CD;
|
||||
|
||||
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
|
||||
forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
||||
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
||||
{
|
||||
header->id = htons(forward->orig_id);
|
||||
header->hb4 |= HB4_RA; /* recursion if available */
|
||||
@@ -1158,8 +1169,9 @@ void receive_query(struct listener *listen, time_t now)
|
||||
else
|
||||
#endif
|
||||
{
|
||||
int ad_reqd;
|
||||
m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
|
||||
dst_addr_4, netmask, now);
|
||||
dst_addr_4, netmask, now, &ad_reqd);
|
||||
|
||||
if (m >= 1)
|
||||
{
|
||||
@@ -1168,7 +1180,7 @@ void receive_query(struct listener *listen, time_t now)
|
||||
daemon->local_answer++;
|
||||
}
|
||||
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
|
||||
header, (size_t)n, now, NULL))
|
||||
header, (size_t)n, now, NULL, ad_reqd))
|
||||
daemon->queries_forwarded++;
|
||||
else
|
||||
daemon->local_answer++;
|
||||
@@ -1176,60 +1188,73 @@ 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)
|
||||
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
|
||||
int class, char *name, char *keyname, struct server *server, int *keycount)
|
||||
{
|
||||
/* Recurse up the key heirarchy */
|
||||
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);
|
||||
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
||||
if (--(*keycount) == 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
*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;
|
||||
}
|
||||
if (status == STAT_NEED_KEY)
|
||||
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
||||
else if (status == STAT_NEED_DS)
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||
else
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
|
||||
|
||||
/* Can't validate because we need a key/DS whose name now in keyname.
|
||||
Make query for same, and recurse to validate */
|
||||
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
||||
{
|
||||
n = (c1 << 8) | c2;
|
||||
size_t m;
|
||||
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
unsigned char *payload = &packet[2];
|
||||
struct dns_header *new_header = (struct dns_header *)payload;
|
||||
u16 *length = (u16 *)packet;
|
||||
unsigned char c1, c2;
|
||||
|
||||
if (!packet)
|
||||
return STAT_INSECURE;
|
||||
|
||||
another_tcp_key:
|
||||
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
|
||||
new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
|
||||
|
||||
if (status == STAT_NEED_KEY)
|
||||
new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
||||
*length = htons(m);
|
||||
|
||||
if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) ||
|
||||
!read_write(server->tcpfd, &c1, 1, 1) ||
|
||||
!read_write(server->tcpfd, &c2, 1, 1) ||
|
||||
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
|
||||
new_status = STAT_INSECURE;
|
||||
else
|
||||
new_status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, class);
|
||||
|
||||
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
||||
{
|
||||
if ((new_status = tcp_key_recurse(now, new_status, class, daemon->keyname, server) == STAT_SECURE))
|
||||
m = (c1 << 8) | c2;
|
||||
|
||||
if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
|
||||
{
|
||||
if (status == STAT_NEED_KEY)
|
||||
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);
|
||||
/* Reached a validated record, now try again at this level.
|
||||
Note that we may get ANOTHER NEED_* if an answer needs more than one key.
|
||||
If so, go round again. */
|
||||
|
||||
if (status == STAT_NEED_KEY)
|
||||
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
||||
else if (status == STAT_NEED_DS)
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||
else
|
||||
new_status = dnssec_validate_reply(now, header, n, name, 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;
|
||||
}
|
||||
goto another_tcp_key;
|
||||
}
|
||||
}
|
||||
|
||||
free(packet);
|
||||
}
|
||||
|
||||
free(packet);
|
||||
|
||||
|
||||
return new_status;
|
||||
}
|
||||
#endif
|
||||
@@ -1247,7 +1272,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
#ifdef HAVE_AUTH
|
||||
int local_auth = 0;
|
||||
#endif
|
||||
int checking_disabled, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
|
||||
int checking_disabled, ad_question, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
|
||||
size_t m;
|
||||
unsigned short qtype;
|
||||
unsigned int gotname;
|
||||
@@ -1283,9 +1308,6 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if ((checking_disabled = header->hb4 & HB4_CD))
|
||||
no_cache_dnssec = 1;
|
||||
|
||||
/* RFC 4035: sect 4.6 para 2 */
|
||||
header->hb4 &= ~HB4_AD;
|
||||
|
||||
if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
|
||||
{
|
||||
#ifdef HAVE_AUTH
|
||||
@@ -1328,7 +1350,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
{
|
||||
/* m > 0 if answered from cache */
|
||||
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
|
||||
dst_addr_4, netmask, now);
|
||||
dst_addr_4, netmask, now, &ad_question);
|
||||
|
||||
/* Do this by steam now we're not in the select() loop */
|
||||
check_log_writer(NULL);
|
||||
@@ -1409,7 +1431,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
size = add_do_bit(header, size, ((char *) header) + 65536);
|
||||
header->hb4 |= HB4_CD;
|
||||
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
||||
this allows it to select auth servers when one is returning bad data. */
|
||||
if (option_bool(OPT_DNSSEC_DEBUG))
|
||||
header->hb4 |= HB4_CD;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1460,22 +1485,20 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
#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"));
|
||||
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
|
||||
char *result;
|
||||
|
||||
if (keycount == 0)
|
||||
result = "ABANDONED";
|
||||
else
|
||||
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
|
||||
|
||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||
|
||||
if (status == STAT_BOGUS)
|
||||
no_cache_dnssec = 1;
|
||||
|
||||
|
||||
if (status == STAT_SECURE)
|
||||
cache_secure = 1;
|
||||
}
|
||||
@@ -1510,7 +1533,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
m = process_reply(header, now, last_server, (unsigned int)m,
|
||||
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
|
||||
cache_secure, check_subnet, &peer_addr);
|
||||
cache_secure, ad_question, check_subnet, &peer_addr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
+135
-83
@@ -1254,87 +1254,187 @@ void pre_allocate_sfds(void)
|
||||
}
|
||||
}
|
||||
|
||||
void mark_servers(int flag)
|
||||
{
|
||||
struct server *serv;
|
||||
|
||||
/* mark everything with argument flag */
|
||||
for (serv = daemon->servers; serv; serv = serv->next)
|
||||
if (serv->flags & flag)
|
||||
serv->flags |= SERV_MARK;
|
||||
}
|
||||
|
||||
void cleanup_servers(void)
|
||||
{
|
||||
struct server *serv, *tmp, **up;
|
||||
|
||||
/* unlink and free anything still marked. */
|
||||
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
|
||||
{
|
||||
tmp = serv->next;
|
||||
if (serv->flags & SERV_MARK)
|
||||
{
|
||||
server_gone(serv);
|
||||
*up = serv->next;
|
||||
if (serv->domain)
|
||||
free(serv->domain);
|
||||
free(serv);
|
||||
}
|
||||
else
|
||||
up = &serv->next;
|
||||
}
|
||||
}
|
||||
|
||||
void add_update_server(int flags,
|
||||
union mysockaddr *addr,
|
||||
union mysockaddr *source_addr,
|
||||
const char *interface,
|
||||
const char *domain)
|
||||
{
|
||||
struct server *serv, *next = NULL;
|
||||
char *domain_str = NULL;
|
||||
|
||||
/* See if there is a suitable candidate, and unmark */
|
||||
for (serv = daemon->servers; serv; serv = serv->next)
|
||||
if (serv->flags & SERV_MARK)
|
||||
{
|
||||
if (domain)
|
||||
{
|
||||
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (serv->flags & SERV_HAS_DOMAIN)
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (serv)
|
||||
{
|
||||
domain_str = serv->domain;
|
||||
next = serv->next;
|
||||
}
|
||||
else if ((serv = whine_malloc(sizeof (struct server))))
|
||||
{
|
||||
/* Not found, create a new one. */
|
||||
if (domain && !(domain_str = whine_malloc(strlen(domain)+1)))
|
||||
{
|
||||
free(serv);
|
||||
serv = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct server *s;
|
||||
/* Add to the end of the chain, for order */
|
||||
if (!daemon->servers)
|
||||
daemon->servers = serv;
|
||||
else
|
||||
{
|
||||
for (s = daemon->servers; s->next; s = s->next);
|
||||
s->next = serv;
|
||||
}
|
||||
if (domain)
|
||||
strcpy(domain_str, domain);
|
||||
}
|
||||
}
|
||||
|
||||
if (serv)
|
||||
{
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
serv->flags = flags;
|
||||
serv->domain = domain_str;
|
||||
serv->next = next;
|
||||
serv->queries = serv->failed_queries = 0;
|
||||
|
||||
if (domain)
|
||||
serv->flags |= SERV_HAS_DOMAIN;
|
||||
|
||||
if (interface)
|
||||
strcpy(serv->interface, interface);
|
||||
if (addr)
|
||||
serv->addr = *addr;
|
||||
if (source_addr)
|
||||
serv->source_addr = *source_addr;
|
||||
}
|
||||
}
|
||||
|
||||
void check_servers(void)
|
||||
{
|
||||
struct irec *iface;
|
||||
struct server *new, *tmp, *ret = NULL;
|
||||
struct server *serv;
|
||||
int port = 0;
|
||||
|
||||
/* interface may be new since startup */
|
||||
if (!option_bool(OPT_NOWILD))
|
||||
enumerate_interfaces(0);
|
||||
|
||||
for (new = daemon->servers; new; new = tmp)
|
||||
for (serv = daemon->servers; serv; serv = serv->next)
|
||||
{
|
||||
tmp = new->next;
|
||||
|
||||
if (!(new->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)))
|
||||
if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)))
|
||||
{
|
||||
port = prettyprint_addr(&new->addr, daemon->namebuff);
|
||||
port = prettyprint_addr(&serv->addr, daemon->namebuff);
|
||||
|
||||
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
|
||||
if (new->addr.sa.sa_family == AF_INET &&
|
||||
new->addr.in.sin_addr.s_addr == 0)
|
||||
if (serv->addr.sa.sa_family == AF_INET &&
|
||||
serv->addr.in.sin_addr.s_addr == 0)
|
||||
{
|
||||
free(new);
|
||||
serv->flags |= SERV_MARK;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (iface = daemon->interfaces; iface; iface = iface->next)
|
||||
if (sockaddr_isequal(&new->addr, &iface->addr))
|
||||
if (sockaddr_isequal(&serv->addr, &iface->addr))
|
||||
break;
|
||||
if (iface)
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
|
||||
free(new);
|
||||
serv->flags |= SERV_MARK;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Do we need a socket set? */
|
||||
if (!new->sfd &&
|
||||
!(new->sfd = allocate_sfd(&new->source_addr, new->interface)) &&
|
||||
if (!serv->sfd &&
|
||||
!(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) &&
|
||||
errno != 0)
|
||||
{
|
||||
my_syslog(LOG_WARNING,
|
||||
_("ignoring nameserver %s - cannot make/bind socket: %s"),
|
||||
daemon->namebuff, strerror(errno));
|
||||
free(new);
|
||||
serv->flags |= SERV_MARK;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* reverse order - gets it right. */
|
||||
new->next = ret;
|
||||
ret = new;
|
||||
|
||||
if (!(new->flags & SERV_NO_REBIND))
|
||||
if (!(serv->flags & SERV_NO_REBIND))
|
||||
{
|
||||
if (new->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV))
|
||||
if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV))
|
||||
{
|
||||
char *s1, *s2;
|
||||
if (!(new->flags & SERV_HAS_DOMAIN))
|
||||
if (!(serv->flags & SERV_HAS_DOMAIN))
|
||||
s1 = _("unqualified"), s2 = _("names");
|
||||
else if (strlen(new->domain) == 0)
|
||||
else if (strlen(serv->domain) == 0)
|
||||
s1 = _("default"), s2 = "";
|
||||
else
|
||||
s1 = _("domain"), s2 = new->domain;
|
||||
s1 = _("domain"), s2 = serv->domain;
|
||||
|
||||
if (new->flags & SERV_NO_ADDR)
|
||||
if (serv->flags & SERV_NO_ADDR)
|
||||
my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2);
|
||||
else if (new->flags & SERV_USE_RESOLV)
|
||||
else if (serv->flags & SERV_USE_RESOLV)
|
||||
my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2);
|
||||
else if (!(new->flags & SERV_LITERAL_ADDRESS))
|
||||
else if (!(serv->flags & SERV_LITERAL_ADDRESS))
|
||||
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2);
|
||||
}
|
||||
else if (new->interface[0] != 0)
|
||||
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, new->interface);
|
||||
else if (serv->interface[0] != 0)
|
||||
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
|
||||
else
|
||||
my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port);
|
||||
}
|
||||
}
|
||||
|
||||
daemon->servers = ret;
|
||||
|
||||
cleanup_servers();
|
||||
}
|
||||
|
||||
/* Return zero if no servers found, in that case we keep polling.
|
||||
@@ -1343,9 +1443,6 @@ int reload_servers(char *fname)
|
||||
{
|
||||
FILE *f;
|
||||
char *line;
|
||||
struct server *old_servers = NULL;
|
||||
struct server *new_servers = NULL;
|
||||
struct server *serv;
|
||||
int gotone = 0;
|
||||
|
||||
/* buff happens to be MAXDNAME long... */
|
||||
@@ -1354,28 +1451,9 @@ int reload_servers(char *fname)
|
||||
my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* move old servers to free list - we can reuse the memory
|
||||
and not risk malloc if there are the same or fewer new servers.
|
||||
Servers which were specced on the command line go to the new list. */
|
||||
for (serv = daemon->servers; serv;)
|
||||
{
|
||||
struct server *tmp = serv->next;
|
||||
if (serv->flags & SERV_FROM_RESOLV)
|
||||
{
|
||||
serv->next = old_servers;
|
||||
old_servers = serv;
|
||||
/* forward table rules reference servers, so have to blow them away */
|
||||
server_gone(serv);
|
||||
}
|
||||
else
|
||||
{
|
||||
serv->next = new_servers;
|
||||
new_servers = serv;
|
||||
}
|
||||
serv = tmp;
|
||||
}
|
||||
|
||||
|
||||
mark_servers(SERV_FROM_RESOLV);
|
||||
|
||||
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
|
||||
{
|
||||
union mysockaddr addr, source_addr;
|
||||
@@ -1434,38 +1512,12 @@ int reload_servers(char *fname)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (old_servers)
|
||||
{
|
||||
serv = old_servers;
|
||||
old_servers = old_servers->next;
|
||||
}
|
||||
else if (!(serv = whine_malloc(sizeof (struct server))))
|
||||
continue;
|
||||
|
||||
/* this list is reverse ordered:
|
||||
it gets reversed again in check_servers */
|
||||
serv->next = new_servers;
|
||||
new_servers = serv;
|
||||
serv->addr = addr;
|
||||
serv->source_addr = source_addr;
|
||||
serv->domain = NULL;
|
||||
serv->interface[0] = 0;
|
||||
serv->sfd = NULL;
|
||||
serv->flags = SERV_FROM_RESOLV;
|
||||
serv->queries = serv->failed_queries = 0;
|
||||
add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL);
|
||||
gotone = 1;
|
||||
}
|
||||
|
||||
/* Free any memory not used. */
|
||||
while (old_servers)
|
||||
{
|
||||
struct server *tmp = old_servers->next;
|
||||
free(old_servers);
|
||||
old_servers = tmp;
|
||||
}
|
||||
|
||||
daemon->servers = new_servers;
|
||||
fclose(f);
|
||||
cleanup_servers();
|
||||
|
||||
return gotone;
|
||||
}
|
||||
|
||||
+173
-73
@@ -139,8 +139,10 @@ struct myoption {
|
||||
#define LOPT_QUIET_DHCP6 327
|
||||
#define LOPT_QUIET_RA 328
|
||||
#define LOPT_SEC_VALID 329
|
||||
#define LOPT_DNSKEY 330
|
||||
#define LOPT_TRUST_ANCHOR 330
|
||||
#define LOPT_DNSSEC_DEBUG 331
|
||||
#define LOPT_REV_SERV 332
|
||||
#define LOPT_SERVERS_FILE 333
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
static const struct option opts[] =
|
||||
@@ -157,6 +159,7 @@ static const struct myoption opts[] =
|
||||
{ "user", 2, 0, 'u' },
|
||||
{ "group", 2, 0, 'g' },
|
||||
{ "resolv-file", 2, 0, 'r' },
|
||||
{ "servers-file", 1, 0, LOPT_SERVERS_FILE },
|
||||
{ "mx-host", 1, 0, 'm' },
|
||||
{ "mx-target", 1, 0, 't' },
|
||||
{ "cache-size", 2, 0, 'c' },
|
||||
@@ -178,6 +181,7 @@ static const struct myoption opts[] =
|
||||
{ "pid-file", 2, 0, 'x' },
|
||||
{ "strict-order", 0, 0, 'o' },
|
||||
{ "server", 1, 0, 'S' },
|
||||
{ "rev-server", 1, 0, LOPT_REV_SERV },
|
||||
{ "local", 1, 0, LOPT_LOCAL },
|
||||
{ "address", 1, 0, 'A' },
|
||||
{ "conf-file", 2, 0, 'C' },
|
||||
@@ -277,7 +281,7 @@ static const struct myoption opts[] =
|
||||
{ "ipset", 1, 0, LOPT_IPSET },
|
||||
{ "synth-domain", 1, 0, LOPT_SYNTH },
|
||||
{ "dnssec", 0, 0, LOPT_SEC_VALID },
|
||||
{ "dnskey", 1, 0, LOPT_DNSKEY },
|
||||
{ "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
|
||||
{ "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
|
||||
#ifdef OPTION6_PREFIX_CLASS
|
||||
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
|
||||
@@ -347,7 +351,9 @@ static struct {
|
||||
{ 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
|
||||
{ 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
|
||||
{ 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE },
|
||||
{ LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
|
||||
{ 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL },
|
||||
{ LOPT_REV_SERV, ARG_DUP, "<addr>/<prefix>,<ipaddr>", gettext_noop("Specify address of upstream servers for reverse address queries"), NULL },
|
||||
{ LOPT_LOCAL, ARG_DUP, "/<domain>/", gettext_noop("Never forward queries to specified domains."), NULL },
|
||||
{ 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL },
|
||||
{ 't', ARG_ONE, "<host_name>", gettext_noop("Specify default target in an MX record."), NULL },
|
||||
@@ -430,7 +436,7 @@ static struct {
|
||||
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
|
||||
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
|
||||
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
|
||||
{ LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL },
|
||||
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
|
||||
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
|
||||
#ifdef OPTION6_PREFIX_CLASS
|
||||
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
|
||||
@@ -590,6 +596,18 @@ static int atoi_check16(char *a, int *res)
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
static int atoi_check8(char *a, int *res)
|
||||
{
|
||||
if (!(atoi_check(a, res)) ||
|
||||
*res < 0 ||
|
||||
*res > 0xff)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void add_txt(char *name, char *txt)
|
||||
{
|
||||
@@ -674,6 +692,13 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
|
||||
char *scope_id;
|
||||
#endif
|
||||
|
||||
if (!arg || strlen(arg) == 0)
|
||||
{
|
||||
*flags |= SERV_NO_ADDR;
|
||||
*interface = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((source = split_chr(arg, '@')) && /* is there a source. */
|
||||
(portno = split_chr(source, '#')) &&
|
||||
!atoi_check16(portno, &source_port))
|
||||
@@ -752,6 +777,54 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct server *add_rev4(struct in_addr addr, int msize)
|
||||
{
|
||||
struct server *serv = opt_malloc(sizeof(struct server));
|
||||
in_addr_t a = ntohl(addr.s_addr) >> 8;
|
||||
char *p;
|
||||
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */
|
||||
|
||||
if (msize == 24)
|
||||
p += sprintf(p, "%d.", a & 0xff);
|
||||
a = a >> 8;
|
||||
if (msize != 8)
|
||||
p += sprintf(p, "%d.", a & 0xff);
|
||||
a = a >> 8;
|
||||
p += sprintf(p, "%d.in-addr.arpa", a & 0xff);
|
||||
|
||||
serv->flags = SERV_HAS_DOMAIN;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
|
||||
return serv;
|
||||
|
||||
}
|
||||
|
||||
static struct server *add_rev6(struct in6_addr *addr, int msize)
|
||||
{
|
||||
struct server *serv = opt_malloc(sizeof(struct server));
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
|
||||
|
||||
for (i = msize-1; i >= 0; i -= 4)
|
||||
{
|
||||
int dig = ((unsigned char *)addr)[i>>3];
|
||||
p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
|
||||
}
|
||||
p += sprintf(p, "ip6.arpa");
|
||||
|
||||
serv->flags = SERV_HAS_DOMAIN;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
|
||||
return serv;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DHCP
|
||||
|
||||
static int is_tag_prefix(char *arg)
|
||||
@@ -1315,7 +1388,7 @@ void reset_option_bool(unsigned int opt)
|
||||
daemon->options2 &= ~(1u << (opt - 32));
|
||||
}
|
||||
|
||||
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line)
|
||||
static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
|
||||
{
|
||||
int i;
|
||||
char *comma;
|
||||
@@ -1518,6 +1591,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
daemon->resolv_files = list;
|
||||
break;
|
||||
}
|
||||
|
||||
case LOPT_SERVERS_FILE:
|
||||
daemon->servers_file = opt_string_alloc(arg);
|
||||
break;
|
||||
|
||||
case 'm': /* --mx-host */
|
||||
{
|
||||
@@ -1814,34 +1891,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
ret_err(gen_err);
|
||||
else
|
||||
{
|
||||
/* generate the equivalent of
|
||||
local=/<domain>/
|
||||
local=/xxx.yyy.zzz.in-addr.arpa/ */
|
||||
struct server *serv = opt_malloc(sizeof(struct server));
|
||||
in_addr_t a = ntohl(new->start.s_addr) >> 8;
|
||||
char *p;
|
||||
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
serv->domain = d;
|
||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
|
||||
serv = opt_malloc(sizeof(struct server));
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */
|
||||
|
||||
if (msize == 24)
|
||||
p += sprintf(p, "%d.", a & 0xff);
|
||||
a = a >> 8;
|
||||
if (msize != 8)
|
||||
p += sprintf(p, "%d.", a & 0xff);
|
||||
a = a >> 8;
|
||||
p += sprintf(p, "%d.in-addr.arpa", a & 0xff);
|
||||
|
||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
/* generate the equivalent of
|
||||
local=/<domain>/
|
||||
local=/xxx.yyy.zzz.in-addr.arpa/ */
|
||||
struct server *serv = add_rev4(new->start, msize);
|
||||
serv->flags |= SERV_NO_ADDR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1877,29 +1931,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
/* generate the equivalent of
|
||||
local=/<domain>/
|
||||
local=/xxx.yyy.zzz.ip6.arpa/ */
|
||||
struct server *serv = opt_malloc(sizeof(struct server));
|
||||
char *p;
|
||||
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
serv->domain = d;
|
||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
|
||||
serv = opt_malloc(sizeof(struct server));
|
||||
memset(serv, 0, sizeof(struct server));
|
||||
p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */
|
||||
|
||||
for (i = msize-1; i >= 0; i -= 4)
|
||||
{
|
||||
int dig = ((unsigned char *)&new->start6)[i>>3];
|
||||
p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
|
||||
}
|
||||
p += sprintf(p, "ip6.arpa");
|
||||
|
||||
serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR;
|
||||
serv->next = daemon->servers;
|
||||
daemon->servers = serv;
|
||||
struct server *serv = add_rev6(&new->start6, msize);
|
||||
serv->flags |= SERV_NO_ADDR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2126,6 +2159,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
memset(newlist, 0, sizeof(struct server));
|
||||
}
|
||||
|
||||
if (servers_only && option == 'S')
|
||||
newlist->flags |= SERV_FROM_FILE;
|
||||
|
||||
if (option == 'A')
|
||||
{
|
||||
newlist->flags |= SERV_LITERAL_ADDRESS;
|
||||
@@ -2170,6 +2206,40 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
break;
|
||||
}
|
||||
|
||||
case LOPT_REV_SERV: /* --rev-server */
|
||||
{
|
||||
char *string;
|
||||
int size;
|
||||
struct server *serv;
|
||||
struct in_addr addr4;
|
||||
#ifdef HAVE_IPV6
|
||||
struct in6_addr addr6;
|
||||
#endif
|
||||
|
||||
unhide_metas(arg);
|
||||
if (!arg || !(comma=split(arg)) || !(string = split_chr(arg, '/')) || !atoi_check(string, &size))
|
||||
ret_err(gen_err);
|
||||
|
||||
if (inet_pton(AF_INET, arg, &addr4))
|
||||
serv = add_rev4(addr4, size);
|
||||
#ifdef HAVE_IPV6
|
||||
else if (inet_pton(AF_INET6, arg, &addr6))
|
||||
serv = add_rev6(&addr6, size);
|
||||
#endif
|
||||
else
|
||||
ret_err(gen_err);
|
||||
|
||||
string = parse_server(comma, &serv->addr, &serv->source_addr, serv->interface, &serv->flags);
|
||||
|
||||
if (string)
|
||||
ret_err(string);
|
||||
|
||||
if (servers_only)
|
||||
serv->flags |= SERV_FROM_FILE;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LOPT_IPSET: /* --ipset */
|
||||
#ifndef HAVE_IPSET
|
||||
ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
|
||||
@@ -3675,10 +3745,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
case LOPT_DNSKEY:
|
||||
case LOPT_TRUST_ANCHOR:
|
||||
{
|
||||
struct dnskey *new = opt_malloc(sizeof(struct dnskey));
|
||||
char *key64, *algo = NULL;
|
||||
struct ds_config *new = opt_malloc(sizeof(struct ds_config));
|
||||
char *cp, *cp1, *keyhex, *digest, *algo = NULL;
|
||||
int len;
|
||||
|
||||
new->class = C_IN;
|
||||
|
||||
@@ -3700,20 +3771,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
}
|
||||
}
|
||||
|
||||
if (!comma || !algo || !(key64 = split(algo)) ||
|
||||
!atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
|
||||
if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
|
||||
!atoi_check16(comma, &new->keytag) ||
|
||||
!atoi_check8(algo, &new->algo) ||
|
||||
!atoi_check8(digest, &new->digest_type) ||
|
||||
!(new->name = canonicalise_opt(arg)))
|
||||
ret_err(_("bad DNSKEY"));
|
||||
ret_err(_("bad trust anchor"));
|
||||
|
||||
/* Upper bound on length */
|
||||
new->key = opt_malloc((3*strlen(key64)/4)+1);
|
||||
unhide_metas(key64);
|
||||
if ((new->keylen = parse_base64(key64, new->key)) == -1)
|
||||
ret_err(_("bad base64 in DNSKEY"));
|
||||
len = (2*strlen(keyhex))+1;
|
||||
new->digest = opt_malloc(len);
|
||||
unhide_metas(keyhex);
|
||||
/* 4034: "Whitespace is allowed within digits" */
|
||||
for (cp = keyhex; *cp; )
|
||||
if (isspace(*cp))
|
||||
for (cp1 = cp; *cp1; cp1++)
|
||||
*cp1 = *(cp1+1);
|
||||
else
|
||||
cp++;
|
||||
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
|
||||
ret_err(_("bad HEX in trust anchor"));
|
||||
|
||||
new->next = daemon->ds;
|
||||
daemon->ds = new;
|
||||
|
||||
new->next = daemon->dnskeys;
|
||||
daemon->dnskeys = new;
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -3733,12 +3814,13 @@ static void read_file(char *file, FILE *f, int hard_opt)
|
||||
|
||||
while (fgets(buff, MAXDNAME, f))
|
||||
{
|
||||
int white, i, option = hard_opt;
|
||||
int white, i;
|
||||
volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
|
||||
char *errmess, *p, *arg = NULL, *start;
|
||||
size_t len;
|
||||
|
||||
/* Memory allocation failure longjmps here if mem_recover == 1 */
|
||||
if (option != 0)
|
||||
if (option != 0 || hard_opt == LOPT_REV_SERV)
|
||||
{
|
||||
if (setjmp(mem_jmp))
|
||||
continue;
|
||||
@@ -3839,13 +3921,15 @@ static void read_file(char *file, FILE *f, int hard_opt)
|
||||
errmess = _("extraneous parameter");
|
||||
else if (opts[i].has_arg == 1 && !arg)
|
||||
errmess = _("missing parameter");
|
||||
else if (hard_opt == LOPT_REV_SERV && option != 'S' && option != LOPT_REV_SERV)
|
||||
errmess = _("illegal option");
|
||||
}
|
||||
|
||||
oops:
|
||||
if (errmess)
|
||||
strcpy(daemon->namebuff, errmess);
|
||||
|
||||
if (errmess || !one_opt(option, arg, buff, _("error"), 0))
|
||||
if (errmess || !one_opt(option, arg, buff, _("error"), 0, hard_opt == LOPT_REV_SERV))
|
||||
{
|
||||
sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
|
||||
if (hard_opt != 0)
|
||||
@@ -4027,6 +4111,22 @@ struct hostsfile *expand_filelist(struct hostsfile *list)
|
||||
return list;
|
||||
}
|
||||
|
||||
void read_servers_file(void)
|
||||
{
|
||||
FILE *f;
|
||||
|
||||
if (!(f = fopen(daemon->servers_file, "r")))
|
||||
{
|
||||
my_syslog(LOG_ERR, _("cannot read %s: %s"), daemon->servers_file, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
mark_servers(SERV_FROM_FILE);
|
||||
cleanup_servers();
|
||||
|
||||
read_file(daemon->servers_file, f, LOPT_REV_SERV);
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_DHCP
|
||||
void reread_dhcp(void)
|
||||
@@ -4220,9 +4320,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1))
|
||||
if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1, 0))
|
||||
#else
|
||||
if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1))
|
||||
if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1, 0))
|
||||
#endif
|
||||
die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF);
|
||||
}
|
||||
|
||||
+61
-64
@@ -337,7 +337,7 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen)
|
||||
return ansp;
|
||||
}
|
||||
|
||||
static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen)
|
||||
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen)
|
||||
{
|
||||
int i, rdlen;
|
||||
|
||||
@@ -1452,7 +1452,8 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
|
||||
|
||||
/* return zero if we can't answer from cache, or packet size if we can */
|
||||
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
struct in_addr local_addr, struct in_addr local_netmask, time_t now)
|
||||
struct in_addr local_addr, struct in_addr local_netmask,
|
||||
time_t now, int *ad_reqd)
|
||||
{
|
||||
char *name = daemon->namebuff;
|
||||
unsigned char *p, *ansp, *pheader;
|
||||
@@ -1468,10 +1469,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
struct mx_srv_record *rec;
|
||||
size_t len;
|
||||
|
||||
/* Don't return AD set even for local data if checking disabled. */
|
||||
/* Don't return AD set if checking disabled. */
|
||||
if (header->hb4 & HB4_CD)
|
||||
sec_data = 0;
|
||||
|
||||
|
||||
/* RFC 6840 5.7 */
|
||||
*ad_reqd = header->hb4 & HB4_AD;
|
||||
|
||||
/* If there is an RFC2671 pseudoheader then it will be overwritten by
|
||||
partial replies, so we have to do a dry run to see if we can answer
|
||||
the query. We check to see if the do bit is set, if so we always
|
||||
@@ -1490,6 +1494,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
GETSHORT(flags, pheader);
|
||||
|
||||
sec_reqd = flags & 0x8000; /* do bit */
|
||||
*ad_reqd = 1;
|
||||
|
||||
/* If our client is advertising a larger UDP packet size
|
||||
than we allow, trim it so that we don't get an overlarge
|
||||
@@ -1551,37 +1556,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS || qtype == T_RRSIG))
|
||||
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS))
|
||||
{
|
||||
int gotone = 0, have_rrsig = 0;
|
||||
int gotone = 0;
|
||||
struct blockdata *keydata;
|
||||
|
||||
/* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||
if (crecp->uid == qclass && (qtype == T_RRSIG || crecp->addr.sig.type_covered == qtype))
|
||||
{
|
||||
have_rrsig = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (qtype == T_RRSIG && have_rrsig)
|
||||
if (sec_reqd)
|
||||
{
|
||||
ans = gotone = 1;
|
||||
auth = 0;
|
||||
}
|
||||
else if (qtype == T_DS && have_rrsig)
|
||||
{
|
||||
auth = 0;
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
|
||||
if (crecp->uid == qclass)
|
||||
{
|
||||
ans = gotone = 1;
|
||||
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
|
||||
{
|
||||
struct all_addr a;
|
||||
a.addr.keytag = crecp->addr.ds.keytag;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||
if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!sec_reqd || crecp)
|
||||
{
|
||||
if (qtype == T_DS)
|
||||
{
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
|
||||
if (crecp->uid == qclass)
|
||||
{
|
||||
gotone = 1;
|
||||
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
|
||||
{
|
||||
struct all_addr a;
|
||||
a.addr.keytag = crecp->addr.ds.keytag;
|
||||
log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
|
||||
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||
crec_ttl(crecp, now), &nameoffset,
|
||||
@@ -1589,20 +1590,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
crecp->addr.ds.keytag, crecp->addr.ds.algo, crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
|
||||
anscount++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (qtype == T_DNSKEY)
|
||||
{
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
|
||||
if (crecp->uid == qclass)
|
||||
{
|
||||
if ((crecp->flags & F_CONFIG) || have_rrsig) /* Return configured keys without an RRISG */
|
||||
}
|
||||
}
|
||||
}
|
||||
else /* DNSKEY */
|
||||
{
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
|
||||
if (crecp->uid == qclass)
|
||||
{
|
||||
if (!(crecp->flags & F_CONFIG))
|
||||
auth = 0, gotone = 1;
|
||||
ans = 1;
|
||||
gotone = 1;
|
||||
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
|
||||
{
|
||||
struct all_addr a;
|
||||
@@ -1615,26 +1612,27 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
anscount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Now do RRSIGs */
|
||||
if (gotone)
|
||||
{
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||
if (crecp->uid == qclass && (qtype == T_RRSIG || (sec_reqd && crecp->addr.sig.type_covered == qtype)) &&
|
||||
!dryrun &&
|
||||
(keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
|
||||
{
|
||||
if (qtype == T_RRSIG)
|
||||
log_query(F_RRNAME, name, NULL, querystr("rrsig", crecp->addr.sig.type_covered));
|
||||
if ((keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)) &&
|
||||
ans = 1;
|
||||
auth = 0;
|
||||
if (!dryrun && sec_reqd)
|
||||
{
|
||||
crecp = NULL;
|
||||
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS)))
|
||||
if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype &&
|
||||
(keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL)))
|
||||
{
|
||||
add_resource_record(header, limit, &trunc, nameoffset, &ansp,
|
||||
crec_ttl(crecp, now), &nameoffset,
|
||||
T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata))
|
||||
anscount++;
|
||||
}
|
||||
T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata);
|
||||
anscount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2260,17 +2258,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
||||
header->ancount = htons(anscount);
|
||||
header->nscount = htons(0);
|
||||
header->arcount = htons(addncount);
|
||||
|
||||
header->hb4 &= ~HB4_AD;
|
||||
|
||||
len = ansp - (unsigned char *)header;
|
||||
|
||||
if (have_pseudoheader)
|
||||
{
|
||||
len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
|
||||
if (sec_reqd && sec_data)
|
||||
header->hb4 |= HB4_AD;
|
||||
|
||||
}
|
||||
len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
|
||||
|
||||
if (*ad_reqd && sec_data)
|
||||
header->hb4 |= HB4_AD;
|
||||
else
|
||||
header->hb4 &= ~HB4_AD;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
-60
@@ -482,66 +482,6 @@ 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)
|
||||
{
|
||||
|
||||
+6
-5
@@ -1,8 +1,9 @@
|
||||
# The root DNSSEC trust anchors, valid as at 30/01/2014
|
||||
# The root DNSSEC trust anchor, valid as at 30/01/2014
|
||||
|
||||
# Note that this is a DS record (ie a hash of the root Zone Signing Key)
|
||||
# If was downloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||
|
||||
trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
|
||||
|
||||
|
||||
dnskey=.,257,8,AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0=
|
||||
dnskey=.,256,8,AwEAAb8sU6pbYMWRbkRnEuEZw9NSir707TkOcF+UL1XiK4NDJOvXRyX1 95Am5dQ7bRnnuySZ3daf37vvjUUhuIWUAQ4stht8nJfYxVQXDYjSpGH5 I6Hf/0CZEoNP6cNvrQ7AFmKkmv00xWExKQjbvnRPI4bqpMwtHVzn6Wyb BZ6kuqED
|
||||
dnskey=.,256,8,AwEAAYRU41/8smgAvuSojEP4jaj5Yll7WPaUKpYvnz2pnX2VIvRn4jsy Jns80bloenG6X9ebJVy2CFtZQLKHP8DcKmIFotdgs2HolyocY1am/+33 4RtzusM2ojkhjn1FRGtuSE9s2TSz1ISv0yVnFyu+EP/ZkiWnDfWeVrJI SEWBEr4V
|
||||
|
||||
|
||||
Reference in new issue
Block a user