Compare commits
14 Commits
v2.91
...
v2.92test1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdce03f928 | ||
|
|
d390dc0338 | ||
|
|
105c25e561 | ||
|
|
67e07b7fe8 | ||
|
|
f5659b406b | ||
|
|
484fea238a | ||
|
|
1e587bec57 | ||
|
|
581c201aa8 | ||
|
|
5487f6979e | ||
|
|
99f12e3541 | ||
|
|
7c1212e3d1 | ||
|
|
0ccbdf8087 | ||
|
|
57f0489f38 | ||
|
|
3e659bd4ec |
22
CHANGELOG
22
CHANGELOG
@@ -1,3 +1,25 @@
|
||||
version 2.92
|
||||
Redesign the interaction between DNSSEC vaildation and per-domain
|
||||
servers, specified as --server=/<domain>/<ip-address>. This should
|
||||
just work in all cases now. If the normal chain-of-trust exists into
|
||||
the delegated domain then whether the domain is signed or not, DNSSEC
|
||||
validation will function normally. In the case the delgated domain
|
||||
is an "overlay" on top of the global DNS and no NS and/or DS records
|
||||
exist connecting it to the global dns, then if the domain is
|
||||
unsigned the situation will be handled by synthesising a
|
||||
proof-of-non-existance-of-DS for the domain and queries will be
|
||||
answered unvalidated; this action will be logged. A signed domain
|
||||
without chain-of-trust can be validated if a suitable trust-anchor
|
||||
is provided using --trust-anchor. This change should be backwards
|
||||
compatible for all existing working configurations; it extends the
|
||||
space of possible configurations which are functional.
|
||||
|
||||
Fix a couple of problems with DNSSEC validation and DNAME. One
|
||||
could cause validation failure on correct domains, and the other
|
||||
would fail to spot an invalid domain. Thanks to Graham Clinch
|
||||
for spotting the problem.
|
||||
|
||||
|
||||
version 2.91
|
||||
Fix spurious "resource limit exceeded messages". Thanks to
|
||||
Dominik Derigs for the bug report.
|
||||
|
||||
@@ -498,10 +498,7 @@ xxx.internal.thekelleys.org.uk at 192.168.1.1 then giving the flag
|
||||
.B --server=/internal.thekelleys.org.uk/192.168.1.1
|
||||
will send all queries for
|
||||
internal machines to that nameserver, everything else will go to the
|
||||
servers in /etc/resolv.conf. DNSSEC validation is turned off for such
|
||||
private nameservers, UNLESS a
|
||||
.B --trust-anchor
|
||||
is specified for the domain in question. An empty domain specification,
|
||||
servers in /etc/resolv.conf. An empty domain specification,
|
||||
.B //
|
||||
has the special meaning of "unqualified names only" ie names without any
|
||||
dots in them. A non-standard port may be specified as
|
||||
@@ -894,12 +891,15 @@ ie capable of returning DNSSEC records with data. If they are not,
|
||||
then dnsmasq will not be able to determine the trusted status of
|
||||
answers and this means that DNS service will be entirely broken.
|
||||
.TP
|
||||
.B --trust-anchor=<domain>,[<class>,]<key-tag>,<algorithm>,<digest-type>,<digest>
|
||||
.B --trust-anchor=<domain>,[<class>,][<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 Key Signing
|
||||
validation. The class defaults to IN. Typically these will be the DS record(s) for Key Signing
|
||||
key(s) (KSK) of the root zone,
|
||||
but trust anchors for limited domains are also possible. The current
|
||||
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||
but trust anchors for limited domains are also possible.
|
||||
A negative trust anchor (ie. proof that a DS record doesn't exist) may be configured be specifying
|
||||
only the name or only the name and class. This can be useful for forcing dnsmasq to treat zones delegated
|
||||
using \fB--server=/<domain>/<ip-address>\fP as unsigned. The current
|
||||
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
|
||||
.TP
|
||||
.B --dnssec-check-unsigned[=no]
|
||||
As a default, dnsmasq checks that unsigned DNS replies are
|
||||
|
||||
14
src/cache.c
14
src/cache.c
@@ -1448,11 +1448,17 @@ void cache_reload(void)
|
||||
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
|
||||
cache->ttd = daemon->local_ttl;
|
||||
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;
|
||||
if (ds->digestlen != 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR;
|
||||
|
||||
cache_hash(cache);
|
||||
make_non_terminals(cache);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
|
||||
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
|
||||
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
|
||||
#define DNSSEC_ASSUMED_DS_TTL 3600 /* TTL for negative DS records implied by server=/domain/ */
|
||||
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
|
||||
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
|
||||
#define FORWARD_TEST 50 /* try all servers every 50 queries */
|
||||
|
||||
@@ -133,6 +133,7 @@ int main (int argc, char **argv)
|
||||
'.' or NAME_ESCAPE then all would have to be escaped, so the
|
||||
presentation format would be twice as long as the spec. */
|
||||
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
|
||||
daemon->cname = safe_malloc((MAXDNAME * 2) + 1);
|
||||
/* one char flag per possible RR in answer section (may get extended). */
|
||||
daemon->rr_status_sz = 64;
|
||||
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
|
||||
@@ -930,7 +931,8 @@ int main (int argc, char **argv)
|
||||
my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid"));
|
||||
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
my_syslog(LOG_INFO, _("configured with trust anchor for %s keytag %u"),
|
||||
my_syslog(LOG_INFO,
|
||||
ds->digestlen == 0 ? _("configured with negative trust anchor for %s") : _("configured with trust anchor for %s keytag %u"),
|
||||
ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
|
||||
}
|
||||
#endif
|
||||
@@ -2140,7 +2142,7 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
|
||||
cache_recv_insert() calls pop_and_retry_query() after the result
|
||||
arrives via the pipe to the parent. */
|
||||
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
|
||||
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount)
|
||||
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
|
||||
{
|
||||
struct server *s;
|
||||
|
||||
@@ -2214,8 +2216,7 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
|
||||
}
|
||||
}
|
||||
|
||||
status = tcp_from_udp(now, status, header, plen, class, daemon->namebuff, daemon->keyname,
|
||||
server, keycount, validatecount);
|
||||
status = tcp_from_udp(now, status, header, plen, class, name, server, keycount, validatecount);
|
||||
|
||||
/* close upstream connections. */
|
||||
for (s = daemon->servers; s; s = s->next)
|
||||
|
||||
@@ -1248,7 +1248,7 @@ extern struct daemon {
|
||||
char *namebuff; /* MAXDNAME size buffer */
|
||||
char *workspacename;
|
||||
#ifdef HAVE_DNSSEC
|
||||
char *keyname; /* MAXDNAME size buffer */
|
||||
char *keyname, *cname; /* MAXDNAME size buffer */
|
||||
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
|
||||
int rr_status_sz;
|
||||
int dnssec_no_time_check;
|
||||
@@ -1393,8 +1393,8 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||
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);
|
||||
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
||||
unsigned short *typep, unsigned short *classp);
|
||||
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
|
||||
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
|
||||
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
|
||||
@@ -1530,7 +1530,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
|
||||
#ifdef HAVE_DNSSEC
|
||||
void pop_and_retry_query(struct frec *forward, int status, time_t now);
|
||||
int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
|
||||
int class, char *name, char *keyname, struct server *server,
|
||||
int class, char *name, struct server *server,
|
||||
int *keycount, int *validatecount);
|
||||
#endif
|
||||
unsigned char *tcp_request(int confd, time_t now,
|
||||
@@ -1654,7 +1654,7 @@ void send_event(int fd, int event, int data, char *msg);
|
||||
void clear_cache_and_reload(time_t now);
|
||||
#ifdef HAVE_DNSSEC
|
||||
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
|
||||
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount);
|
||||
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount);
|
||||
#endif
|
||||
|
||||
/* netlink.c */
|
||||
|
||||
271
src/dnssec.c
271
src/dnssec.c
@@ -997,49 +997,57 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
unsigned long ttl;
|
||||
union all_addr a;
|
||||
|
||||
if (ntohs(header->qdcount) != 1 ||
|
||||
!(p = skip_name(p, header, plen, 4)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
GETSHORT(qclass, p);
|
||||
|
||||
if (qtype != T_DS || qclass != class)
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
|
||||
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
|
||||
so treat it as such and continue to search for a DS or proof of no existence
|
||||
further down the tree. */
|
||||
if (RCODE(header) == SERVFAIL)
|
||||
servfail = neganswer = nons = 1;
|
||||
else
|
||||
{
|
||||
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
|
||||
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
|
||||
|
||||
p = (unsigned char *)(header+1);
|
||||
if (ntohs(header->qdcount) != 1 ||
|
||||
!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
|
||||
return STAT_BOGUS;
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
GETSHORT(qclass, p);
|
||||
|
||||
if (qtype != T_DS || qclass != class)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (!servfail)
|
||||
{
|
||||
if (STAT_ISEQUAL(rc, STAT_INSECURE))
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
|
||||
return STAT_BOGUS | DNSSEC_FAIL_INDET;
|
||||
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
|
||||
{
|
||||
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
|
||||
neganswer = 1;
|
||||
nons = 0; /* If we're faking a DS, fake one with an NS. */
|
||||
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
|
||||
}
|
||||
else
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
|
||||
return STAT_BOGUS | DNSSEC_FAIL_INDET;
|
||||
}
|
||||
}
|
||||
|
||||
p = (unsigned char *)(header+1);
|
||||
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
|
||||
return STAT_BOGUS;
|
||||
|
||||
p += 4; /* qtype, qclass */
|
||||
|
||||
/* If the key needed to validate the DS is on the same domain as the DS, we'll
|
||||
loop getting nowhere. Stop that now. This can happen of the DS answer comes
|
||||
from the DS's zone, and not the parent zone. */
|
||||
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
|
||||
else
|
||||
{
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
|
||||
return STAT_BOGUS;
|
||||
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
|
||||
{
|
||||
/* If the key needed to validate the DS is on the same domain as the DS, we'll
|
||||
loop getting nowhere. Stop that now. This can happen of the DS answer comes
|
||||
from the DS's zone, and not the parent zone. */
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
if (!STAT_ISEQUAL(rc, STAT_SECURE))
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!STAT_ISEQUAL(rc, STAT_SECURE))
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!neganswer)
|
||||
@@ -1129,9 +1137,19 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
/* We only cache validated DS records, DNSSECOK flag hijacked
|
||||
to store presence/absence of NS. */
|
||||
if (nons)
|
||||
flags &= ~F_DNSSECOK;
|
||||
{
|
||||
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
|
||||
{
|
||||
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
|
||||
nons = 0;
|
||||
}
|
||||
else
|
||||
/* We only cache validated DS records, DNSSECOK flag hijacked
|
||||
to store presence/absence of NS. */
|
||||
flags &= ~F_DNSSECOK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cache_start_insert();
|
||||
|
||||
/* Use TTL from NSEC for negative cache entries */
|
||||
@@ -1236,6 +1254,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
p += 8; /* class, type, TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
psave = p;
|
||||
|
||||
if (!extract_name(header, plen, &p, workspace2, EXTR_NAME_EXTRACT, 0))
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
@@ -1258,7 +1277,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
workspace1--;
|
||||
*workspace1 = '*';
|
||||
}
|
||||
|
||||
|
||||
rdlen -= p - psave;
|
||||
/* rdlen is now length of type map, and p points to it
|
||||
packet checked to be as long as rdlen implies in prove_non_existence() */
|
||||
|
||||
/* check that the first typemap is complete. */
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
/* RFC 6672 5.3.4.1. */
|
||||
#define DNAME_OFFSET (T_DNAME >> 3)
|
||||
#define DNAME_MASK (0x80 >> (T_DNAME & 0x07))
|
||||
if (p[0] == 0 && (p[1] >= DNAME_OFFSET + 1) && (p[2 + DNAME_OFFSET] & DNAME_MASK) != 0 &&
|
||||
hostname_issubdomain(name, workspace1) == 1)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
|
||||
rc = hostname_cmp(workspace1, name);
|
||||
|
||||
if (rc == 0)
|
||||
@@ -1269,16 +1303,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
|
||||
/* 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
|
||||
packet checked to be as long as rdlen implies in prove_non_existence() */
|
||||
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
if (rdlen >= 2 && p[0] == 0)
|
||||
if (p[0] == 0 && p[1] >= 1)
|
||||
{
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
/* A CNAME answer would also be valid, so if there's a CNAME is should
|
||||
have been returned. */
|
||||
if ((p[2] & (0x80 >> T_CNAME)) != 0)
|
||||
@@ -1290,10 +1320,10 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||
return DNSSEC_FAIL_NONSEC;
|
||||
}
|
||||
|
||||
while (rdlen >= 2)
|
||||
|
||||
while (rdlen > 0)
|
||||
{
|
||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
@@ -1433,7 +1463,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
p += hash_len; /* skip next-domain hash */
|
||||
rdlen -= p - psave;
|
||||
|
||||
if (rdlen >= 2 && p[0] == 0)
|
||||
/* check that the first typemap is complete. */
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
if (p[0] == 0 && p[1] >= 1)
|
||||
{
|
||||
/* If we can prove that there's no NS record, return that information. */
|
||||
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
@@ -1451,8 +1485,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (rdlen >= 2)
|
||||
while (rdlen > 0)
|
||||
{
|
||||
if (rdlen < 2 || rdlen < p[1] + 2)
|
||||
return DNSSEC_FAIL_BADPACKET;
|
||||
|
||||
if (p[0] == type >> 8)
|
||||
{
|
||||
/* Does the NSEC3 say our type exists? */
|
||||
@@ -1924,11 +1961,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
static unsigned char **targets = NULL;
|
||||
static int target_sz = 0;
|
||||
|
||||
unsigned char *ans_start, *p1, *p2;
|
||||
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
|
||||
int i, j, rc = STAT_INSECURE;
|
||||
unsigned char *ans_start, *p1, *p2, *p3;
|
||||
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx, gotdname;
|
||||
int i, j, k, rc = STAT_INSECURE;
|
||||
int secure = STAT_SECURE;
|
||||
int rc_nsec;
|
||||
unsigned long ttl;
|
||||
|
||||
/* extend rr_status if necessary */
|
||||
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
|
||||
{
|
||||
@@ -1975,27 +2014,119 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
/* Can't validate an RRSIG query */
|
||||
if (qtype == T_RRSIG)
|
||||
return STAT_INSECURE;
|
||||
|
||||
/* Find CNAME targets. */
|
||||
for (gotdname = i = 0; i < ntohs(header->ancount); i++)
|
||||
{
|
||||
if (!(p1 = skip_name(p1, header, plen, 10)))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(type1, p1);
|
||||
GETSHORT(class1, p1);
|
||||
p1 += 4; /* TTL */
|
||||
GETSHORT(rdlen1, p1);
|
||||
|
||||
if (type1 == T_DNAME)
|
||||
gotdname = 1;
|
||||
|
||||
if (qtype != T_CNAME && qtype != T_ANY && type1 == T_CNAME && class1 == qclass)
|
||||
{
|
||||
if (!expand_workspace(&targets, &target_sz, targetidx))
|
||||
return STAT_BOGUS;
|
||||
|
||||
targets[targetidx++] = p1; /* pointer to target name */
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
if (qtype != T_CNAME && qtype != T_ANY)
|
||||
for (j = ntohs(header->ancount); j != 0; j--)
|
||||
/* A DNAME capable of sythesising a CNAME means we don't need to validate the CNAME,
|
||||
we can just assume that it's valid. RFC 4035 3.2.3 */
|
||||
if (gotdname)
|
||||
for (p1 = ans_start, i = 0; i < ntohs(header->ancount); i++)
|
||||
{
|
||||
if (!(p1 = skip_name(p1, header, plen, 10)))
|
||||
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(type2, p1);
|
||||
p1 += 6; /* class, TTL */
|
||||
GETSHORT(rdlen2, p1);
|
||||
GETSHORT(type1, p1);
|
||||
GETSHORT(class1, p1);
|
||||
p1 += 4; /* TTL */
|
||||
GETSHORT(rdlen1, p1);
|
||||
|
||||
if (type2 == T_CNAME)
|
||||
if (type1 != T_DNAME)
|
||||
{
|
||||
if (!expand_workspace(&targets, &target_sz, targetidx))
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||
return STAT_BOGUS;
|
||||
|
||||
targets[targetidx++] = p1; /* pointer to target name */
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen2))
|
||||
return STAT_BOGUS;
|
||||
else
|
||||
{
|
||||
if (!extract_name(header, plen, &p1, keyname, EXTR_NAME_EXTRACT, 0))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
/* We now have the name of the DNAME in name, and the target in keyname.
|
||||
Look for any CNAMEs which could have been synthesised from this DNAME
|
||||
and pre-qualify them. */
|
||||
for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
|
||||
{
|
||||
if (!extract_name(header, plen, &p2, daemon->cname, EXTR_NAME_EXTRACT, 10))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(type2, p2);
|
||||
GETSHORT(class2, p2);
|
||||
GETLONG(ttl, p2);
|
||||
GETSHORT(rdlen2, p2);
|
||||
|
||||
if (type2 != T_CNAME || class2 != class1)
|
||||
{
|
||||
if (!ADD_RDLEN(header, p2, plen, rdlen2))
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t name_prefix_len = strlen(daemon->cname) - strlen(name);
|
||||
|
||||
if (!extract_name(header, plen, &p2, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
/* We have the name of the CNAME in daemon->cname, and the target in daemon->workspacename.
|
||||
See if the CNAME was sythesised from the DNAME.
|
||||
CNAME must be <subdomain>.<dname>
|
||||
CNAME target must be <subdomain>.<dname_target>
|
||||
<subdomain>s must match for name and target. */
|
||||
if (hostname_issubdomain(name, daemon->cname) == 1 &&
|
||||
hostname_issubdomain(keyname, daemon->workspacename) == 1 &&
|
||||
name_prefix_len == strlen(daemon->workspacename) - strlen(keyname))
|
||||
{
|
||||
char save = daemon->cname[name_prefix_len];
|
||||
daemon->cname[name_prefix_len] = 0;
|
||||
daemon->workspacename[name_prefix_len] = 0;
|
||||
|
||||
if (hostname_isequal(daemon->cname, daemon->workspacename))
|
||||
{
|
||||
/* pre-qualify this as validated */
|
||||
daemon->rr_status[j] = ttl > 0 ? ttl : 1;
|
||||
|
||||
/* and remove it from the targets we need to have validated answers to. */
|
||||
if (class2 == qclass)
|
||||
{
|
||||
daemon->cname[name_prefix_len] = save;
|
||||
for (k = 0; k <targetidx; k++)
|
||||
if ((p3 = targets[k]))
|
||||
{
|
||||
int rc1;
|
||||
if (!(rc1 = extract_name(header, plen, &p3, daemon->cname, EXTR_NAME_COMPARE, 0)))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
if (rc1 == 1)
|
||||
targets[k] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
|
||||
@@ -2014,6 +2145,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
/* Don't try and validate RRSIGs! */
|
||||
if (type1 == T_RRSIG)
|
||||
continue;
|
||||
|
||||
/* Pre-validated by DNAME above don't validate. */
|
||||
if (daemon->rr_status[i] != 0)
|
||||
continue;
|
||||
|
||||
/* Check if we've done this RRset already */
|
||||
for (p2 = ans_start, j = 0; j < i; j++)
|
||||
@@ -2155,7 +2290,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
|
||||
|
||||
/* For anything other than a DS record, this situation is OK if either
|
||||
the answer is in an unsigned zone, or there's a NSEC records. */
|
||||
the answer is in an unsigned zone, or there's NSEC records.
|
||||
For a DS record, we return INSECURE, which almost always turns
|
||||
into BOGUS in the caller. */
|
||||
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
|
||||
{
|
||||
if (rc_nsec & DNSSEC_FAIL_WORK)
|
||||
@@ -2163,7 +2300,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
/* Empty DS without NSECS */
|
||||
if (qtype == T_DS)
|
||||
return STAT_BOGUS | rc_nsec;
|
||||
return STAT_INSECURE;
|
||||
|
||||
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
|
||||
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
|
||||
|
||||
@@ -94,8 +94,7 @@ void build_server_array(void)
|
||||
server=/.example.com/ works.
|
||||
|
||||
A flag of F_SERVER returns an upstream server only.
|
||||
A flag of F_DNSSECOK returns a DNSSEC capable server only and
|
||||
also disables NODOTS servers from consideration.
|
||||
A flag of F_DNSSECOK disables NODOTS servers from consideration.
|
||||
A flag of F_DOMAINSRV returns a domain-specific server only.
|
||||
A flag of F_CONFIG returns anything that generates a local
|
||||
reply of IPv4 or IPV6.
|
||||
@@ -338,12 +337,8 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
|
||||
|
||||
if (i != nlow)
|
||||
{
|
||||
/* If we want a server that can do DNSSEC, and this one can't,
|
||||
return nothing, similarly if were looking only for a server
|
||||
for a particular domain. */
|
||||
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
|
||||
nlow = nhigh;
|
||||
else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
|
||||
/* If we want a server for a particular domain, and this one isn't, return nothing. */
|
||||
if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
|
||||
nlow = nhigh;
|
||||
else
|
||||
nhigh = i;
|
||||
@@ -351,7 +346,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
|
||||
else
|
||||
{
|
||||
/* --local=/domain/, only return if we don't need a server. */
|
||||
if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
|
||||
if (flags & (F_DOMAINSRV | F_SERVER))
|
||||
nhigh = i;
|
||||
}
|
||||
}
|
||||
|
||||
199
src/forward.c
199
src/forward.c
@@ -176,9 +176,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
int first, last, start = 0;
|
||||
int forwarded = 0;
|
||||
int ede = EDE_UNSET;
|
||||
unsigned short rrtype;
|
||||
unsigned short rrtype, rrclass;
|
||||
|
||||
gotname = extract_request(header, plen, daemon->namebuff, &rrtype);
|
||||
gotname = extract_request(header, plen, daemon->namebuff, &rrtype, &rrclass);
|
||||
|
||||
/* Check for retry on existing query.
|
||||
FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
|
||||
@@ -192,7 +192,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
old_reply = 1;
|
||||
fwd_flags = forward->flags;
|
||||
}
|
||||
else if (gotname && (forward = lookup_frec(daemon->namebuff, C_IN, (int)rrtype, -1, fwd_flags,
|
||||
else if (gotname && (forward = lookup_frec(daemon->namebuff, (int)rrclass, (int)rrtype, -1, fwd_flags,
|
||||
FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
|
||||
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
|
||||
{
|
||||
@@ -264,7 +264,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
The original query we sent is now in packet buffer and the query name in the
|
||||
new instance is on daemon->namebuff. */
|
||||
|
||||
if (extract_request(header, forward->stash_len, daemon->workspacename, NULL))
|
||||
if (extract_name(header, forward->stash_len, NULL, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
|
||||
{
|
||||
unsigned int i, gobig = 0;
|
||||
char *s1, *s2;
|
||||
@@ -329,8 +329,6 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
/* new query */
|
||||
if (!forward)
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
if (OPCODE(header) != QUERY)
|
||||
{
|
||||
flags = F_RCODE;
|
||||
@@ -375,7 +373,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
forward->flags = fwd_flags;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
plen = add_do_bit(header, plen, ((unsigned char *) header) + daemon->edns_pktsz);
|
||||
|
||||
@@ -390,11 +388,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
forward->frec_src.orig_id = ntohs(header->id);
|
||||
forward->new_id = get_id();
|
||||
header->id = ntohs(forward->new_id);
|
||||
|
||||
forward->frec_src.encode_bitmap = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
|
||||
forward->frec_src.encode_bigmap = NULL;
|
||||
p = (unsigned char *)(header+1);
|
||||
if (!extract_name(header, plen, &p, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
|
||||
if (!extract_name(header, plen, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
goto reply;
|
||||
|
||||
/* Keep copy of query for retries and move to TCP */
|
||||
@@ -459,7 +456,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
plen = forward->stash_len;
|
||||
/* get query for logging. */
|
||||
gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
||||
gotname = extract_request(header, plen, daemon->namebuff, NULL, NULL);
|
||||
|
||||
/* Find suitable servers: should never fail. */
|
||||
if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last))
|
||||
@@ -708,12 +705,12 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
(void)do_bit;
|
||||
|
||||
#ifdef HAVE_IPSET
|
||||
if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
|
||||
if (daemon->ipsets && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NFTSET
|
||||
if (daemon->nftsets && extract_request(header, n, daemon->namebuff, NULL))
|
||||
if (daemon->nftsets && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
nftsets = domain_find_sets(daemon->nftsets, daemon->namebuff);
|
||||
#endif
|
||||
|
||||
@@ -790,7 +787,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
||||
log_query(F_UPSTREAM, NULL, NULL, "truncated", 0);
|
||||
else if (!bogusanswer || (header->hb4 & HB4_CD))
|
||||
{
|
||||
if (rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) &&
|
||||
if (rcode == NXDOMAIN && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0) &&
|
||||
(check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)))
|
||||
{
|
||||
/* if we forwarded a query for a locally known name (because it was for
|
||||
@@ -922,22 +919,26 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
/* Get the query we sent by UDP */
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
|
||||
if (extract_request(header, forward->stash_len, daemon->namebuff, NULL))
|
||||
log_query(F_UPSTREAM | F_NOEXTRA, daemon->namebuff, NULL, "truncated", 0);
|
||||
|
||||
/* Don't count failed UDP attempt AND TCP */
|
||||
if (status != STAT_OK)
|
||||
orig->work_counter++;
|
||||
|
||||
/* NOTE: Can't move connection marks from UDP to TCP */
|
||||
plen = forward->stash_len;
|
||||
status = swap_to_tcp(forward, now, status, header, &plen, forward->class, forward->sentto, &orig->work_counter, &orig->validate_counter);
|
||||
|
||||
/* We forked a new process. pop_and_retry_query() will be called when is completes. */
|
||||
if (STAT_ISEQUAL(status, STAT_ASYNC))
|
||||
if (!extract_name(header, forward->stash_len, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
status = STAT_ABANDONED;
|
||||
else
|
||||
{
|
||||
forward->flags |= FREC_GONE_TO_TCP;
|
||||
return;
|
||||
log_query(F_UPSTREAM | F_NOEXTRA, daemon->namebuff, NULL, "truncated", 0);
|
||||
|
||||
/* Don't count failed UDP attempt AND TCP */
|
||||
if (status != STAT_OK)
|
||||
orig->work_counter++;
|
||||
|
||||
/* NOTE: Can't move connection marks from UDP to TCP */
|
||||
plen = forward->stash_len;
|
||||
status = swap_to_tcp(forward, now, status, header, &plen, daemon->namebuff, forward->class, forward->sentto, &orig->work_counter, &orig->validate_counter);
|
||||
|
||||
/* We forked a new process. pop_and_retry_query() will be called when is completes. */
|
||||
if (STAT_ISEQUAL(status, STAT_ASYNC))
|
||||
{
|
||||
forward->flags |= FREC_GONE_TO_TCP;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -954,8 +955,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
|
||||
else
|
||||
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
|
||||
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
|
||||
NULL, NULL, NULL, &orig->validate_counter);
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, &orig->validate_counter);
|
||||
|
||||
if (STAT_ISEQUAL(status, STAT_ABANDONED))
|
||||
log_resource = 1;
|
||||
@@ -1094,7 +1094,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
|
||||
if (log_resource)
|
||||
{
|
||||
/* Log the actual validation that made us barf. */
|
||||
if (extract_request(header, plen, daemon->namebuff, NULL))
|
||||
if (extract_name(header, plen, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
|
||||
daemon->namebuff[0] ? daemon->namebuff : ".");
|
||||
}
|
||||
@@ -1271,14 +1271,13 @@ void reply_query(int fd, time_t now)
|
||||
server->query_latency = server->mma_latency/128;
|
||||
|
||||
/* Flip the bits back in the query name. */
|
||||
p = (unsigned char *)(header+1);
|
||||
if (!extract_name(header, n, &p, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
if (!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
|
||||
return;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
if ((forward->sentto->flags & SERV_DO_DNSSEC) && !(forward->flags & FREC_CHECKING_DISABLED))
|
||||
if (!(forward->flags & FREC_CHECKING_DISABLED))
|
||||
{
|
||||
dnssec_validate(forward, header, n, STAT_OK, now);
|
||||
return;
|
||||
@@ -1305,8 +1304,7 @@ static void xor_array(unsigned int *arg1, unsigned int *arg2, unsigned int len)
|
||||
/* Call extract_name() to flip case of query in packet according to the XOR of the bit maps help in arg1 and arg2 */
|
||||
static void flip_queryname(struct dns_header *header, ssize_t len, struct frec_src *arg1, struct frec_src *arg2)
|
||||
{
|
||||
unsigned char *p = (unsigned char *)(header+1);
|
||||
unsigned int *arg1p, *arg2p, arg1len, arg2len, *swapp, swap;
|
||||
unsigned int *arg1p, *arg2p, arg1len, arg2len;
|
||||
|
||||
/* Two cases: bitmap is single 32 bit int, or it's arbitrary-length array of 32bit ints.
|
||||
The two args may be different and of different lengths.
|
||||
@@ -1325,17 +1323,14 @@ static void flip_queryname(struct dns_header *header, ssize_t len, struct frec_s
|
||||
/* make arg1 the longer, if they differ. */
|
||||
if (arg2len > arg1len)
|
||||
{
|
||||
swap = arg1len;
|
||||
swapp = arg1p;
|
||||
arg1len = arg2len;
|
||||
arg1p = arg2p;
|
||||
arg2len = swap;
|
||||
arg2p = swapp;
|
||||
unsigned int swapl = arg1len, *swapp = arg1p;
|
||||
arg1len = arg2len, arg1p = arg2p;
|
||||
arg2len = swapl, arg2p = swapp;
|
||||
}
|
||||
|
||||
/* XOR on shorter length, flip on longer, operate on longer */
|
||||
xor_array(arg1p, arg2p, arg2len);
|
||||
extract_name(header, len, &p, (char *)arg1p, EXTR_NAME_FLIP, arg1len);
|
||||
extract_name(header, len, NULL, (char *)arg1p, EXTR_NAME_FLIP, arg1len);
|
||||
xor_array(arg1p, arg2p, arg2len); /* restore */
|
||||
}
|
||||
|
||||
@@ -1393,7 +1388,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
|
||||
no_cache_dnssec = 1;
|
||||
bogusanswer = 1;
|
||||
|
||||
if (extract_request(header, n, daemon->namebuff, NULL))
|
||||
if (extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
domain = daemon->namebuff;
|
||||
}
|
||||
|
||||
@@ -1822,7 +1817,7 @@ void receive_query(struct listener *listen, time_t now)
|
||||
|
||||
if (OPCODE(header) != QUERY)
|
||||
log_query_mysockaddr(F_QUERY | F_FORWARD, "opcode", &source_addr, "non-query", 0);
|
||||
else if (extract_request(header, (size_t)n, daemon->namebuff, &type))
|
||||
else if (extract_request(header, (size_t)n, daemon->namebuff, &type, NULL))
|
||||
{
|
||||
#ifdef HAVE_AUTH
|
||||
struct auth_zone *zone;
|
||||
@@ -2130,7 +2125,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
|
||||
/* We us the _ONCE veriant of read_write() here because we've set a timeout on the tcp socket
|
||||
and wish to abort if the whole data is not read/written within the timeout. */
|
||||
if ((!data_sent && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) ||
|
||||
if ((!data_sent && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) ||
|
||||
!read_write(serv->tcpfd, (unsigned char *)length, sizeof (*length), RW_READ_ONCE) ||
|
||||
!read_write(serv->tcpfd, payload, (rsize = ntohs(*length)), RW_READ_ONCE))
|
||||
{
|
||||
@@ -2146,7 +2141,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
else
|
||||
goto failed;
|
||||
}
|
||||
|
||||
|
||||
/* If the question section of the reply doesn't match the question we sent, then
|
||||
someone might be attempting to insert bogus values into the cache by
|
||||
sending replies containing questions and bogus answers.
|
||||
@@ -2176,14 +2171,13 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
|
||||
returned truncated. (Which type held in status).
|
||||
Resend the query (in header) via TCP */
|
||||
int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *plenp,
|
||||
int class, char *name, char *keyname, struct server *server,
|
||||
int class, char *name, struct server *server,
|
||||
int *keycount, int *validatecount)
|
||||
{
|
||||
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
struct dns_header *new_header = (struct dns_header *)&packet[2];
|
||||
int start, first, last, new_status;
|
||||
ssize_t n = *plenp;
|
||||
int have_req = extract_request(header, n, keyname, NULL);
|
||||
int log_save = daemon->log_display_id;
|
||||
|
||||
*plenp = 0;
|
||||
@@ -2200,48 +2194,48 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
|
||||
first = start = server->arrayposn;
|
||||
last = first + 1;
|
||||
|
||||
if (!STAT_ISEQUAL(status, STAT_OK) && (!have_req || (start = dnssec_server(server, keyname, &first, &last)) == -1))
|
||||
new_status = STAT_ABANDONED;
|
||||
else if ((n = tcp_talk(first, last, start, packet, n, 0, 0, &server)) == 0)
|
||||
if (!STAT_ISEQUAL(status, STAT_OK) && (start = dnssec_server(server, name, &first, &last)) == -1)
|
||||
new_status = STAT_ABANDONED;
|
||||
else
|
||||
{
|
||||
if (have_req)
|
||||
{
|
||||
if (STAT_ISEQUAL(status, STAT_OK))
|
||||
log_query_mysockaddr(F_SERVER | F_FORWARD, keyname, &server->addr, NULL, 0);
|
||||
else
|
||||
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr,
|
||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
||||
}
|
||||
|
||||
new_status = tcp_key_recurse(now, status, new_header, n, class, name, keyname, server, 0, 0, keycount, validatecount);
|
||||
|
||||
if (STAT_ISEQUAL(status, STAT_OK))
|
||||
{
|
||||
/* downstream query: strip DNSSSEC RRs and see if it will
|
||||
fit in a UDP reply. */
|
||||
rrfilter(new_header, (size_t *)&n, RRFILTER_DNSSEC);
|
||||
log_query_mysockaddr(F_SERVER | F_FORWARD, name, &server->addr, NULL, 0);
|
||||
else
|
||||
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, name, &server->addr,
|
||||
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
|
||||
|
||||
if (n >= daemon->edns_pktsz)
|
||||
if ((n = tcp_talk(first, last, start, packet, n, 0, 0, &server)) == 0)
|
||||
new_status = STAT_ABANDONED;
|
||||
else
|
||||
{
|
||||
new_status = tcp_key_recurse(now, status, new_header, n, class, daemon->namebuff, daemon->keyname, server, 0, 0, keycount, validatecount);
|
||||
|
||||
if (STAT_ISEQUAL(status, STAT_OK))
|
||||
{
|
||||
/* still too bIg, strip optional sections and try again. */
|
||||
new_header->nscount = htons(0);
|
||||
new_header->arcount = htons(0);
|
||||
n = resize_packet(new_header, n, NULL, 0);
|
||||
/* downstream query: strip DNSSSEC RRs and see if it will
|
||||
fit in a UDP reply. */
|
||||
rrfilter(new_header, (size_t *)&n, RRFILTER_DNSSEC);
|
||||
|
||||
if (n >= daemon->edns_pktsz)
|
||||
{
|
||||
/* truncating the packet will break the answers, so remove them too
|
||||
and mark the reply as truncated. */
|
||||
new_header->ancount = htons(0);
|
||||
/* still too bIg, strip optional sections and try again. */
|
||||
new_header->nscount = htons(0);
|
||||
new_header->arcount = htons(0);
|
||||
n = resize_packet(new_header, n, NULL, 0);
|
||||
new_status = STAT_TRUNCATED;
|
||||
if (n >= daemon->edns_pktsz)
|
||||
{
|
||||
/* truncating the packet will break the answers, so remove them too
|
||||
and mark the reply as truncated. */
|
||||
new_header->ancount = htons(0);
|
||||
n = resize_packet(new_header, n, NULL, 0);
|
||||
new_status = STAT_TRUNCATED;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the stripped or truncated reply. */
|
||||
memcpy(header, new_header, n);
|
||||
*plenp = n;
|
||||
}
|
||||
|
||||
/* return the stripped or truncated reply. */
|
||||
memcpy(header, new_header, n);
|
||||
*plenp = n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2271,8 +2265,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
|
||||
else
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
|
||||
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
|
||||
NULL, NULL, NULL, validatecount);
|
||||
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, validatecount);
|
||||
|
||||
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
|
||||
break;
|
||||
@@ -2286,7 +2279,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
if (STAT_ISEQUAL(new_status, STAT_ABANDONED))
|
||||
{
|
||||
/* Log the actual validation that made us barf. */
|
||||
if (extract_request(header, n, daemon->namebuff, NULL))
|
||||
if (extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
|
||||
daemon->namebuff[0] ? daemon->namebuff : ".");
|
||||
break;
|
||||
@@ -2470,13 +2463,27 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
gotname = 0;
|
||||
flags = F_RCODE;
|
||||
}
|
||||
else if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
|
||||
else if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype, NULL)))
|
||||
ede = EDE_INVALID_DATA;
|
||||
else
|
||||
{
|
||||
if (saved_question)
|
||||
blockdata_free(saved_question);
|
||||
|
||||
do_bit = 0;
|
||||
|
||||
if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
|
||||
{
|
||||
unsigned short ede_flags;
|
||||
|
||||
have_pseudoheader = 1;
|
||||
pheader += 4; /* udp_size, ext_rcode */
|
||||
GETSHORT(ede_flags, pheader);
|
||||
|
||||
if (ede_flags & 0x8000)
|
||||
do_bit = 1; /* do bit */
|
||||
}
|
||||
|
||||
size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &cacheable);
|
||||
saved_question = blockdata_alloc((char *)header, (size_t)size);
|
||||
saved_size = size;
|
||||
@@ -2515,20 +2522,6 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
else
|
||||
dst_addr_4.s_addr = 0;
|
||||
|
||||
do_bit = 0;
|
||||
|
||||
if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
|
||||
{
|
||||
unsigned short ede_flags;
|
||||
|
||||
have_pseudoheader = 1;
|
||||
pheader += 4; /* udp_size, ext_rcode */
|
||||
GETSHORT(ede_flags, pheader);
|
||||
|
||||
if (ede_flags & 0x8000)
|
||||
do_bit = 1; /* do bit */
|
||||
}
|
||||
|
||||
ad_reqd = do_bit;
|
||||
/* RFC 6840 5.7 */
|
||||
if (header->hb4 & HB4_AD)
|
||||
@@ -2598,7 +2591,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
start = master->last_server;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
|
||||
|
||||
@@ -2615,7 +2608,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
else
|
||||
{
|
||||
/* get query name again for logging - may have been overwritten */
|
||||
if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
|
||||
if (!extract_name(header, (unsigned int)size, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
strcpy(daemon->namebuff, "query");
|
||||
log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL, 0);
|
||||
|
||||
@@ -2627,7 +2620,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
|
||||
if (checking_disabled || (header->hb4 & HB4_CD))
|
||||
no_cache_dnssec = 1;
|
||||
else if (master->flags & SERV_DO_DNSSEC)
|
||||
else
|
||||
{
|
||||
int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
|
||||
int validatecount = daemon->limit[LIMIT_CRYPTO];
|
||||
@@ -2657,7 +2650,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
no_cache_dnssec = 1;
|
||||
bogusanswer = 1;
|
||||
|
||||
if (extract_request(header, m, daemon->namebuff, NULL))
|
||||
if (extract_name(header, m, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
|
||||
domain = daemon->namebuff;
|
||||
}
|
||||
|
||||
|
||||
@@ -1379,7 +1379,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
|
||||
/* cannot set source _port_ for TCP connections. */
|
||||
if (is_tcp)
|
||||
port = 0;
|
||||
else if (port == 0 && daemon->max_port != 0)
|
||||
else if (port == 0 && daemon->max_port != 0 && daemon->max_port >= daemon->min_port)
|
||||
{
|
||||
/* Bind a random port within the range given by min-port and max-port if either
|
||||
or both are set. Otherwise use the OS's random ephemeral port allocation by
|
||||
@@ -1587,33 +1587,6 @@ void check_servers(int no_loop_check)
|
||||
|
||||
for (count = 0, serv = daemon->servers; serv; serv = serv->next)
|
||||
{
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID))
|
||||
{
|
||||
if (!(serv->flags & SERV_FOR_NODOTS))
|
||||
serv->flags |= SERV_DO_DNSSEC;
|
||||
|
||||
/* Disable DNSSEC validation when using server=/domain/.... servers
|
||||
unless there's a configured trust anchor. */
|
||||
if (strlen(serv->domain) != 0)
|
||||
{
|
||||
struct ds_config *ds;
|
||||
char *domain = serv->domain;
|
||||
|
||||
/* .example.com is valid */
|
||||
while (*domain == '.')
|
||||
domain++;
|
||||
|
||||
for (ds = daemon->ds; ds; ds = ds->next)
|
||||
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
|
||||
break;
|
||||
|
||||
if (!ds)
|
||||
serv->flags &= ~SERV_DO_DNSSEC;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
port = prettyprint_addr(&serv->addr, daemon->namebuff);
|
||||
|
||||
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
|
||||
@@ -1659,10 +1632,6 @@ void check_servers(int no_loop_check)
|
||||
{
|
||||
char *s1, *s2, *s3 = "", *s4 = "";
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
|
||||
s3 = _("(no DNSSEC)");
|
||||
#endif
|
||||
if (serv->flags & SERV_FOR_NODOTS)
|
||||
s1 = _("unqualified"), s2 = _("names");
|
||||
else if (strlen(serv->domain) == 0)
|
||||
|
||||
66
src/option.c
66
src/option.c
@@ -2675,15 +2675,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
|
||||
if (msize > 128)
|
||||
ret_err_free(_("bad prefix length"), new);
|
||||
|
||||
mask = (1LLU << (128 - msize)) - 1LLU;
|
||||
/* prefix==64 overflows the mask calculation */
|
||||
if (msize <= 64)
|
||||
mask = (u64)-1LL;
|
||||
else
|
||||
mask = (1LLU << (128 - msize)) - 1LLU;
|
||||
|
||||
new->is6 = 1;
|
||||
new->prefixlen = msize;
|
||||
|
||||
/* prefix==64 overflows the mask calculation above */
|
||||
if (msize <= 64)
|
||||
mask = (u64)-1LL;
|
||||
|
||||
new->end6 = new->start6;
|
||||
setaddr6part(&new->start6, addrpart & ~mask);
|
||||
setaddr6part(&new->end6, addrpart | mask);
|
||||
@@ -5337,7 +5337,8 @@ err:
|
||||
|
||||
new->class = C_IN;
|
||||
new->name = NULL;
|
||||
|
||||
new->digestlen = 0;
|
||||
|
||||
if ((comma = split(arg)) && (algo = split(comma)))
|
||||
{
|
||||
int class = 0;
|
||||
@@ -5355,29 +5356,37 @@ err:
|
||||
algo = split(comma);
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
if (!(new->name = canonicalise_opt(arg)))
|
||||
ret_err_free(_("bad trust anchor"), new);
|
||||
|
||||
/* Upper bound on length */
|
||||
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((unsigned char)*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)
|
||||
|
||||
if (comma)
|
||||
{
|
||||
free(new->name);
|
||||
ret_err_free(_("bad HEX in trust anchor"), new);
|
||||
if (!algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
|
||||
!atoi_check16(comma, &new->keytag) ||
|
||||
!atoi_check8(algo, &new->algo) ||
|
||||
!atoi_check8(digest, &new->digest_type))
|
||||
{
|
||||
free(new->name);
|
||||
ret_err_free(_("bad trust anchor"), new);
|
||||
}
|
||||
|
||||
/* Upper bound on length */
|
||||
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((unsigned char)*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)
|
||||
{
|
||||
free(new->name);
|
||||
ret_err_free(_("bad HEX in trust anchor"), new);
|
||||
}
|
||||
}
|
||||
|
||||
new->next = daemon->ds;
|
||||
@@ -5926,6 +5935,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
|
||||
daemon->randport_limit = 1;
|
||||
daemon->host_index = SRC_AH;
|
||||
daemon->max_procs = MAX_PROCS;
|
||||
#ifdef HAVE_DUMPFILE
|
||||
daemon->dump_mask = 0xffffffff;
|
||||
#endif
|
||||
#ifdef HAVE_DNSSEC
|
||||
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
|
||||
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
|
||||
|
||||
@@ -29,16 +29,19 @@
|
||||
return = 1 -> extract OK, compare OK, flip OK
|
||||
return = 2 -> extract OK, compare failed.
|
||||
return = 3 -> extract OK, compare failed but only on case.
|
||||
|
||||
If pp == NULL, operate on the query name in the packet.
|
||||
*/
|
||||
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||
char *name, int func, unsigned int parm)
|
||||
{
|
||||
unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
|
||||
unsigned char *cp = (unsigned char *)name, *p1 = NULL;
|
||||
unsigned int j, l, namelen = 0, hops = 0;
|
||||
unsigned int bigmap_counter = 0, bigmap_posn = 0, bigmap_size = parm, bitmap = 0;
|
||||
int retvalue = 1, case_insens = 1, isExtract = 0, flip = 0, extrabytes = (int)parm;
|
||||
unsigned int *bigmap = (unsigned int *)name;
|
||||
|
||||
unsigned char *p = pp ? *pp : (unsigned char *)(header+1);
|
||||
|
||||
if (func == EXTR_NAME_EXTRACT)
|
||||
isExtract = 1, *cp = 0;
|
||||
else if (func == EXTR_NAME_NOCASE)
|
||||
@@ -71,11 +74,14 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
||||
}
|
||||
else if (!flip && *cp != 0)
|
||||
retvalue = 2;
|
||||
|
||||
if (p1) /* we jumped via compression */
|
||||
*pp = p1;
|
||||
else
|
||||
*pp = p;
|
||||
|
||||
if (pp)
|
||||
{
|
||||
if (p1) /* we jumped via compression */
|
||||
*pp = p1;
|
||||
else
|
||||
*pp = p;
|
||||
}
|
||||
|
||||
return retvalue;
|
||||
}
|
||||
@@ -590,18 +596,18 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
|
||||
}
|
||||
|
||||
/* rest of RR */
|
||||
if (!no_cache && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr.rrblock.datalen += 20;
|
||||
|
||||
if (!no_cache)
|
||||
{
|
||||
int secflag = 0;
|
||||
|
||||
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
|
||||
{
|
||||
blockdata_free(addr.rrblock.rrdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr.rrblock.datalen += 20;
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
|
||||
{
|
||||
@@ -1232,7 +1238,8 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
|
||||
|
||||
/* If the packet holds exactly one query
|
||||
return F_IPV4 or F_IPV6 and leave the name from the query in name */
|
||||
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
|
||||
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
||||
unsigned short *typep, unsigned short *classp)
|
||||
{
|
||||
unsigned char *p = (unsigned char *)(header+1);
|
||||
int qtype, qclass;
|
||||
@@ -1257,6 +1264,9 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
||||
if (typep)
|
||||
*typep = qtype;
|
||||
|
||||
if (classp)
|
||||
*classp = qclass;
|
||||
|
||||
if (qclass == C_IN)
|
||||
{
|
||||
if (qtype == T_A)
|
||||
@@ -1268,9 +1278,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
/* F_DNSSECOK as agument to search_servers() inhibits forwarding
|
||||
to servers for domains without a trust anchor. This make the
|
||||
behaviour for DS and DNSKEY queries we forward the same
|
||||
/* Make the behaviour for DS and DNSKEY queries we forward the same
|
||||
as for DS and DNSKEY queries we originate. */
|
||||
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
|
||||
return F_DNSSECOK;
|
||||
|
||||
@@ -377,7 +377,7 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
|
||||
int to_wire(char *name)
|
||||
{
|
||||
unsigned char *l, *p, *q, term;
|
||||
int len;
|
||||
unsigned int len;
|
||||
|
||||
for (l = (unsigned char*)name; *l != 0; l = p)
|
||||
{
|
||||
@@ -409,7 +409,7 @@ int to_wire(char *name)
|
||||
void from_wire(char *name)
|
||||
{
|
||||
unsigned char *l, *p, *last;
|
||||
int len;
|
||||
unsigned int len;
|
||||
|
||||
for (last = (unsigned char *)name; *last != 0; last += *last+1);
|
||||
|
||||
|
||||
@@ -426,7 +426,7 @@ int hostname_order(const char *a, const char *b)
|
||||
|
||||
int hostname_isequal(const char *a, const char *b)
|
||||
{
|
||||
return hostname_order(a, b) == 0;
|
||||
return strlen(a) == strlen(b) && hostname_order(a, b) == 0;
|
||||
}
|
||||
|
||||
/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */
|
||||
|
||||
Reference in New Issue
Block a user