mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
Major rationalisation of DNSSEC validation.
Much gnarly special-case code removed and replaced with correct general implementaion. Checking of zone-status moved to DNSSEC code, where it should be, vastly simplifying query-forwarding code.
This commit is contained in:
@@ -586,12 +586,8 @@ struct hostsfile {
|
||||
#define STAT_NEED_KEY 5
|
||||
#define STAT_TRUNCATED 6
|
||||
#define STAT_SECURE_WILDCARD 7
|
||||
#define STAT_NO_SIG 8
|
||||
#define STAT_NO_DS 9
|
||||
#define STAT_NO_NS 10
|
||||
#define STAT_NEED_DS_NEG 11
|
||||
#define STAT_CHASE_CNAME 12
|
||||
#define STAT_INSECURE_DS 13
|
||||
#define STAT_OK 8
|
||||
#define STAT_ABANDONED 9
|
||||
|
||||
#define FREC_NOREBIND 1
|
||||
#define FREC_CHECKING_DISABLED 2
|
||||
@@ -601,8 +597,7 @@ struct hostsfile {
|
||||
#define FREC_AD_QUESTION 32
|
||||
#define FREC_DO_QUESTION 64
|
||||
#define FREC_ADDED_PHEADER 128
|
||||
#define FREC_CHECK_NOSIGN 256
|
||||
#define FREC_TEST_PKTSZ 512
|
||||
#define FREC_TEST_PKTSZ 256
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
#define HASH_SIZE 20 /* SHA-1 digest size */
|
||||
@@ -626,9 +621,7 @@ struct frec {
|
||||
#ifdef HAVE_DNSSEC
|
||||
int class, work_counter;
|
||||
struct blockdata *stash; /* Saved reply, whilst we validate */
|
||||
struct blockdata *orig_domain; /* domain of original query, whilst
|
||||
we're seeing is if in unsigned domain */
|
||||
size_t stash_len, name_start, name_len;
|
||||
size_t stash_len;
|
||||
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
|
||||
struct frec *blocking_query; /* Query which is blocking us. */
|
||||
#endif
|
||||
@@ -1162,8 +1155,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
|
||||
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
|
||||
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
|
||||
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
|
||||
int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
|
||||
int check_unsigned, int *neganswer, int *nons);
|
||||
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
|
||||
size_t filter_rrsigs(struct dns_header *header, size_t plen);
|
||||
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
|
||||
|
||||
666
src/dnssec.c
666
src/dnssec.c
@@ -65,8 +65,10 @@ static char *algo_digest_name(int algo)
|
||||
case 8: return "sha256";
|
||||
case 10: return "sha512";
|
||||
case 12: return "gosthash94";
|
||||
#ifndef NO_NETTLE_ECC
|
||||
case 13: return "sha256";
|
||||
case 14: return "sha384";
|
||||
#endif
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
@@ -592,30 +594,30 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
|
||||
}
|
||||
}
|
||||
|
||||
static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
|
||||
static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
|
||||
{
|
||||
unsigned char **p;
|
||||
int new_sz = *sz;
|
||||
int old = *szp;
|
||||
|
||||
if (new_sz > new)
|
||||
if (old >= new+1)
|
||||
return 1;
|
||||
|
||||
if (new >= 100)
|
||||
return 0;
|
||||
|
||||
new_sz += 5;
|
||||
new += 5;
|
||||
|
||||
if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))
|
||||
if (!(p = whine_malloc(new * sizeof(unsigned char **))))
|
||||
return 0;
|
||||
|
||||
if (*wkspc)
|
||||
if (old != 0 && *wkspc)
|
||||
{
|
||||
memcpy(p, *wkspc, *sz * sizeof(unsigned char **));
|
||||
memcpy(p, *wkspc, old * sizeof(unsigned char **));
|
||||
free(*wkspc);
|
||||
}
|
||||
|
||||
*wkspc = p;
|
||||
*sz = new_sz;
|
||||
*szp = new;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -706,47 +708,28 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
|
||||
} while (swap);
|
||||
}
|
||||
|
||||
/* 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.
|
||||
(In this case *wildcard_out points to the "body" of the wildcard within name.)
|
||||
STAT_NO_SIG no RRsigs found.
|
||||
STAT_INSECURE RRset empty.
|
||||
STAT_BOGUS signature is wrong, bad packet.
|
||||
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, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
|
||||
{
|
||||
static unsigned char **rrset = NULL, **sigs = NULL;
|
||||
|
||||
/* Get pointers to RRset menbers and signature(s) for same.
|
||||
Check signatures, and return keyname associated in keyname. */
|
||||
static int explore_rrset(struct dns_header *header, size_t plen, int class, int type,
|
||||
char *name, char *keyname, int *sigcnt, int *rrcnt)
|
||||
{
|
||||
static int rrset_sz = 0, sig_sz = 0;
|
||||
|
||||
unsigned char *p;
|
||||
int rrsetidx, sigidx, res, rdlen, j, name_labels;
|
||||
struct crec *crecp = NULL;
|
||||
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
|
||||
u16 *rr_desc = get_desc(type);
|
||||
|
||||
if (wildcard_out)
|
||||
*wildcard_out = NULL;
|
||||
int rrsetidx, sigidx, j, rdlen, res;
|
||||
int name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
||||
int gotkey = 0;
|
||||
|
||||
if (!(p = skip_questions(header, plen)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
||||
|
||||
/* look for RRSIGs for this RRset and get pointers to each RR in the set. */
|
||||
for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
|
||||
j != 0; j--)
|
||||
{
|
||||
unsigned char *pstart, *pdata;
|
||||
int stype, sclass;
|
||||
int stype, sclass, algo, type_covered, labels, sig_expiration, sig_inception;
|
||||
|
||||
pstart = p;
|
||||
|
||||
@@ -762,14 +745,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
GETSHORT(rdlen, p);
|
||||
|
||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||
return STAT_BOGUS;
|
||||
return 0;
|
||||
|
||||
if (res == 1 && sclass == class)
|
||||
{
|
||||
if (stype == type)
|
||||
{
|
||||
if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
|
||||
return STAT_BOGUS;
|
||||
return 0;
|
||||
|
||||
rrset[rrsetidx++] = pstart;
|
||||
}
|
||||
@@ -777,14 +760,54 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
if (stype == T_RRSIG)
|
||||
{
|
||||
if (rdlen < 18)
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
return 0; /* bad packet */
|
||||
|
||||
GETSHORT(type_covered, p);
|
||||
algo = *p++;
|
||||
labels = *p++;
|
||||
p += 4; /* orig_ttl */
|
||||
GETLONG(sig_expiration, p);
|
||||
GETLONG(sig_inception, p);
|
||||
p += 2; /* key_tag */
|
||||
|
||||
if (type_covered == type)
|
||||
if (gotkey)
|
||||
{
|
||||
/* If there's more than one SIG, ensure they all have same keyname */
|
||||
if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
gotkey = 1;
|
||||
|
||||
if (!extract_name(header, plen, &p, keyname, 1, 0))
|
||||
return 0;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
char *name_start;
|
||||
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
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't count signatures for algos we don't support */
|
||||
if (check_date_range(sig_inception, sig_expiration) &&
|
||||
labels <= name_labels &&
|
||||
type_covered == type &&
|
||||
algo_digest_name(algo))
|
||||
{
|
||||
if (!expand_workspace(&sigs, &sig_sz, sigidx))
|
||||
return STAT_BOGUS;
|
||||
return 0;
|
||||
|
||||
sigs[sigidx++] = pdata;
|
||||
}
|
||||
@@ -794,16 +817,44 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||
return STAT_BOGUS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RRset empty */
|
||||
if (rrsetidx == 0)
|
||||
return STAT_INSECURE;
|
||||
*sigcnt = sigidx;
|
||||
*rrcnt = rrsetidx;
|
||||
|
||||
/* no RRSIGs */
|
||||
if (sigidx == 0)
|
||||
return STAT_NO_SIG;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
(In this case *wildcard_out points to the "body" of the wildcard within name.)
|
||||
STAT_BOGUS signature is wrong, bad packet.
|
||||
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
|
||||
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
||||
|
||||
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.
|
||||
|
||||
Call explore_rrset first to find and count RRs and sigs.
|
||||
*/
|
||||
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
|
||||
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
|
||||
{
|
||||
unsigned char *p;
|
||||
int rdlen, j, name_labels;
|
||||
struct crec *crecp = NULL;
|
||||
int algo, labels, orig_ttl, key_tag;
|
||||
u16 *rr_desc = get_desc(type);
|
||||
|
||||
if (wildcard_out)
|
||||
*wildcard_out = NULL;
|
||||
|
||||
name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
||||
|
||||
/* Sort RRset records into canonical order.
|
||||
Note that at this point keyname and daemon->workspacename buffs are
|
||||
@@ -828,41 +879,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
algo = *p++;
|
||||
labels = *p++;
|
||||
GETLONG(orig_ttl, p);
|
||||
GETLONG(sig_expiration, p);
|
||||
GETLONG(sig_inception, p);
|
||||
p += 8; /* sig_expiration, sig_inception already checked */
|
||||
GETSHORT(key_tag, p);
|
||||
|
||||
if (!extract_name(header, plen, &p, keyname, 1, 0))
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* 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))) ||
|
||||
if (!(hash = hash_find(algo_digest_name(algo))) ||
|
||||
!hash_init(hash, &ctx, &digest))
|
||||
continue;
|
||||
|
||||
@@ -971,10 +994,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
/* The DNS packet is expected to contain the answer to a DNSKEY query.
|
||||
Put all DNSKEYs in the answer which are valid into the cache.
|
||||
return codes:
|
||||
STAT_SECURE At least one valid DNSKEY found and in cache.
|
||||
STAT_OK Done, key(s) in cache.
|
||||
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
|
||||
or self-sign for DNSKEY RRset is not valid, bad packet.
|
||||
STAT_NEED_DS DS records to validate a key not found, name in keyname
|
||||
STAT_NEED_DNSKEY DNSKEY records to validate a key not found, name in keyname
|
||||
*/
|
||||
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
|
||||
{
|
||||
@@ -1001,23 +1025,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
return STAT_NEED_DS;
|
||||
}
|
||||
|
||||
/* If we've cached that DS provably doesn't exist, result must be INSECURE */
|
||||
if (crecp->flags & F_NEG)
|
||||
return STAT_INSECURE_DS;
|
||||
|
||||
/* 4035 5.2
|
||||
If the validator does not support any of the algorithms listed in an
|
||||
authenticated DS RRset, then the resolver has no supported
|
||||
authentication path leading from the parent to the child. The
|
||||
resolver should treat this case as it would the case of an
|
||||
authenticated NSEC RRset proving that no DS RRset exists, */
|
||||
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
|
||||
if (hash_find(ds_digest_name(recp1->addr.ds.digest)))
|
||||
break;
|
||||
|
||||
if (!recp1)
|
||||
return STAT_INSECURE_DS;
|
||||
|
||||
/* NOTE, we need to find ONE DNSKEY which matches the DS */
|
||||
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
|
||||
{
|
||||
@@ -1070,6 +1077,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
void *ctx;
|
||||
unsigned char *digest, *ds_digest;
|
||||
const struct nettle_hash *hash;
|
||||
int sigcnt, rrcnt;
|
||||
|
||||
if (recp1->addr.ds.algo == algo &&
|
||||
recp1->addr.ds.keytag == keytag &&
|
||||
@@ -1088,10 +1096,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
from_wire(name);
|
||||
|
||||
if (recp1->addr.ds.keylen == (int)hash->digest_size &&
|
||||
if (!(recp1->flags & F_NEG) &&
|
||||
recp1->addr.ds.keylen == (int)hash->digest_size &&
|
||||
(ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
|
||||
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
|
||||
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
|
||||
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
|
||||
sigcnt != 0 && rrcnt != 0 &&
|
||||
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
|
||||
NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
|
||||
{
|
||||
valid = 1;
|
||||
break;
|
||||
@@ -1112,7 +1124,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
{
|
||||
/* Ensure we have type, class TTL and length */
|
||||
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
GETSHORT(qclass, p);
|
||||
@@ -1198,7 +1210,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
/* commit cache insert. */
|
||||
cache_end_insert();
|
||||
return STAT_SECURE;
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
|
||||
@@ -1207,12 +1219,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
/* The DNS packet is expected to contain the answer to a DS query
|
||||
Put all DSs in the answer which are valid into the cache.
|
||||
Also handles replies which prove that there's no DS at this location,
|
||||
either because the zone is unsigned or this isn't a zone cut. These are
|
||||
cached too.
|
||||
return codes:
|
||||
STAT_SECURE At least one valid DS found and in cache.
|
||||
STAT_NO_DS It's proved there's no DS here.
|
||||
STAT_NO_NS It's proved there's no DS _or_ NS here.
|
||||
STAT_OK At least one valid DS found and in cache.
|
||||
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
|
||||
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
|
||||
STAT_NEED_DS DS record needed.
|
||||
*/
|
||||
|
||||
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
|
||||
@@ -1230,7 +1244,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
if (qtype != T_DS || qclass != class)
|
||||
val = STAT_BOGUS;
|
||||
else
|
||||
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons);
|
||||
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
|
||||
/* Note dnssec_validate_reply() will have cached positive answers */
|
||||
|
||||
if (val == STAT_INSECURE)
|
||||
@@ -1243,10 +1257,6 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
|
||||
val = STAT_BOGUS;
|
||||
|
||||
/* If we return STAT_NO_SIG, name contains the name of the DS query */
|
||||
if (val == STAT_NO_SIG)
|
||||
return val;
|
||||
|
||||
/* 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. */
|
||||
@@ -1256,8 +1266,11 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
if (val != STAT_SECURE)
|
||||
return val;
|
||||
|
||||
/* By here, the answer is proved secure, and a positive answer has been cached. */
|
||||
if (val == STAT_SECURE && neganswer)
|
||||
if (neganswer)
|
||||
{
|
||||
int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
|
||||
unsigned long ttl, minttl = ULONG_MAX;
|
||||
@@ -1317,14 +1330,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
||||
|
||||
cache_end_insert();
|
||||
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS");
|
||||
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
|
||||
}
|
||||
}
|
||||
|
||||
return nons ? STAT_NO_NS : STAT_NO_DS;
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/* 4034 6.1 */
|
||||
static int hostname_cmp(const char *a, const char *b)
|
||||
@@ -1452,7 +1464,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
int mask = 0x80 >> (type & 0x07);
|
||||
|
||||
if (nons)
|
||||
*nons = 0;
|
||||
*nons = 1;
|
||||
|
||||
/* Find NSEC record that proves name doesn't exist */
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
@@ -1480,8 +1492,21 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
||||
/* rdlen is now length of type map, and p points to it */
|
||||
|
||||
/* 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 = 1;
|
||||
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
if (rdlen >= 2 && p[0] == 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)
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. */
|
||||
if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
while (rdlen >= 2)
|
||||
{
|
||||
@@ -1586,7 +1611,7 @@ static int base32_decode(char *in, unsigned char *out)
|
||||
static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
|
||||
char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)
|
||||
{
|
||||
int i, hash_len, salt_len, base32_len, rdlen;
|
||||
int i, hash_len, salt_len, base32_len, rdlen, flags;
|
||||
unsigned char *p, *psave;
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
@@ -1599,7 +1624,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
p += 8; /* class, type, TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
psave = p;
|
||||
p += 4; /* algo, flags, iterations */
|
||||
p++; /* algo */
|
||||
flags = *p++; /* flags */
|
||||
p += 2; /* iterations */
|
||||
salt_len = *p++; /* salt_len */
|
||||
p += salt_len; /* salt */
|
||||
hash_len = *p++; /* p now points to next hashed name */
|
||||
@@ -1626,8 +1653,21 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
return 0;
|
||||
|
||||
/* 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 = 1;
|
||||
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
|
||||
*nons = 0;
|
||||
|
||||
if (rdlen >= 2 && p[0] == 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)
|
||||
return 0;
|
||||
|
||||
/* If the SOA bit is set for a DS record, then we have the
|
||||
DS from the wrong side of the delegation. */
|
||||
if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (rdlen >= 2)
|
||||
{
|
||||
@@ -1635,7 +1675,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
{
|
||||
/* Does the NSEC3 say our type exists? */
|
||||
if (offset < p[1] && (p[offset+2] & mask) != 0)
|
||||
return STAT_BOGUS;
|
||||
return 0;
|
||||
|
||||
break; /* finshed checking */
|
||||
}
|
||||
@@ -1651,16 +1691,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
||||
/* 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)
|
||||
{
|
||||
if ((flags & 0x01) && nons) /* opt out */
|
||||
*nons = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
if ((flags & 0x01) && nons) /* opt out */
|
||||
*nons = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1673,7 +1724,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
char *closest_encloser, *next_closest, *wildcard;
|
||||
|
||||
if (nons)
|
||||
*nons = 0;
|
||||
*nons = 1;
|
||||
|
||||
/* Look though the NSEC3 records to find the first one with
|
||||
an algorithm we support (currently only algo == 1).
|
||||
@@ -1814,15 +1865,80 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
||||
return STAT_SECURE;
|
||||
}
|
||||
|
||||
/* 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, int *neganswer, int *nons)
|
||||
/* Check signing status of name.
|
||||
returns:
|
||||
STAT_SECURE zone is signed.
|
||||
STAT_INSECURE zone proved unsigned.
|
||||
STAT_NEED_DS require DS record of name returned in keyname.
|
||||
|
||||
name returned unaltered.
|
||||
*/
|
||||
static int zone_status(char *name, int class, char *keyname, time_t now)
|
||||
{
|
||||
unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
|
||||
int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
|
||||
int i, j, rc, nsec_count, cname_count = CNAME_CHAIN;
|
||||
int nsec_type = 0, have_answer = 0;
|
||||
int name_start = strlen(name);
|
||||
struct crec *crecp;
|
||||
char *p;
|
||||
|
||||
while (1)
|
||||
{
|
||||
strcpy(keyname, &name[name_start]);
|
||||
|
||||
if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
|
||||
return STAT_NEED_DS;
|
||||
else
|
||||
do
|
||||
{
|
||||
if (crecp->uid == (unsigned int)class)
|
||||
{
|
||||
/* F_DNSSECOK misused in DS cache records to non-existance of NS record.
|
||||
F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
|
||||
but that's because there's no NS record either, ie this isn't the start
|
||||
of a zone. We only prove that the DNS tree below a node is unsigned when
|
||||
we prove that we're at a zone cut AND there's no DS record.
|
||||
*/
|
||||
if (crecp->flags & F_NEG)
|
||||
{
|
||||
if (crecp->flags & F_DNSSECOK)
|
||||
return STAT_INSECURE; /* proved no DS here */
|
||||
}
|
||||
else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo))
|
||||
return STAT_INSECURE; /* algo we can't use - insecure */
|
||||
}
|
||||
}
|
||||
while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
|
||||
|
||||
if (name_start == 0)
|
||||
break;
|
||||
|
||||
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
|
||||
|
||||
if (p != name)
|
||||
p++;
|
||||
|
||||
name_start = p - name;
|
||||
}
|
||||
|
||||
return STAT_SECURE;
|
||||
}
|
||||
|
||||
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3)
|
||||
Return code:
|
||||
STAT_SECURE if it validates.
|
||||
STAT_INSECURE at least one RRset not validated, because in unsigned zone.
|
||||
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
|
||||
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
|
||||
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
||||
*/
|
||||
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
|
||||
int *class, int check_unsigned, int *neganswer, int *nons)
|
||||
{
|
||||
static unsigned char **targets = NULL;
|
||||
static int target_sz = 0;
|
||||
|
||||
unsigned char *ans_start, *p1, *p2, **nsecs;
|
||||
int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
|
||||
int i, j, rc, nsec_count;
|
||||
int nsec_type;
|
||||
|
||||
if (neganswer)
|
||||
*neganswer = 0;
|
||||
@@ -1833,7 +1949,17 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
|
||||
return STAT_INSECURE;
|
||||
|
||||
qname = p1 = (unsigned char *)(header+1);
|
||||
p1 = (unsigned char *)(header+1);
|
||||
|
||||
/* Find all the targets we're looking for answers to.
|
||||
The zeroth array element is for the query, subsequent ones
|
||||
for CNAME targets, unless the query is for a CNAME. */
|
||||
|
||||
if (!expand_workspace(&targets, &target_sz, 0))
|
||||
return STAT_BOGUS;
|
||||
|
||||
targets[0] = p1;
|
||||
targetidx = 1;
|
||||
|
||||
if (!extract_name(header, plen, &p1, name, 1, 4))
|
||||
return STAT_BOGUS;
|
||||
@@ -1842,61 +1968,32 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
GETSHORT(qclass, p1);
|
||||
ans_start = p1;
|
||||
|
||||
if (qtype == T_ANY)
|
||||
have_answer = 1;
|
||||
|
||||
/* Can't validate an RRISG query */
|
||||
/* Can't validate an RRSIG query */
|
||||
if (qtype == T_RRSIG)
|
||||
return STAT_INSECURE;
|
||||
|
||||
cname_loop:
|
||||
if (qtype != T_CNAME)
|
||||
for (j = ntohs(header->ancount); j != 0; j--)
|
||||
{
|
||||
/* leave pointer to missing name in qname */
|
||||
|
||||
if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
|
||||
if (!(p1 = skip_name(p1, header, plen, 10)))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(type2, p1);
|
||||
GETSHORT(class2, p1);
|
||||
p1 += 4; /* TTL */
|
||||
p1 += 6; /* class, TTL */
|
||||
GETSHORT(rdlen2, p1);
|
||||
|
||||
if (rc == 1 && qclass == class2)
|
||||
if (type2 == T_CNAME)
|
||||
{
|
||||
/* Do we have an answer for the question? */
|
||||
if (type2 == qtype)
|
||||
{
|
||||
have_answer = 1;
|
||||
break;
|
||||
}
|
||||
else if (type2 == T_CNAME)
|
||||
{
|
||||
qname = p1;
|
||||
|
||||
/* looped CNAMES */
|
||||
if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0))
|
||||
if (!expand_workspace(&targets, &target_sz, targetidx))
|
||||
return STAT_BOGUS;
|
||||
|
||||
p1 = ans_start;
|
||||
goto cname_loop;
|
||||
}
|
||||
targets[targetidx++] = p1; /* pointer to target name */
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen2))
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
if (neganswer && !have_answer)
|
||||
*neganswer = 1;
|
||||
|
||||
/* No data, therefore no sigs */
|
||||
if (ntohs(header->ancount) + ntohs(header->nscount) == 0)
|
||||
{
|
||||
*keyname = 0;
|
||||
return STAT_NO_SIG;
|
||||
}
|
||||
|
||||
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
|
||||
{
|
||||
if (!extract_name(header, plen, &p1, name, 1, 10))
|
||||
@@ -1931,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
/* Not done, validate now */
|
||||
if (j == i)
|
||||
{
|
||||
int ttl, keytag, algo, digest, type_covered;
|
||||
int ttl, keytag, algo, digest, type_covered, sigcnt, rrcnt;
|
||||
unsigned char *psave;
|
||||
struct all_addr a;
|
||||
struct blockdata *key;
|
||||
@@ -1939,7 +2036,63 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
char *wildname;
|
||||
int have_wildcard = 0;
|
||||
|
||||
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0);
|
||||
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
||||
return STAT_BOGUS;
|
||||
|
||||
/* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
|
||||
if (sigcnt == 0)
|
||||
{
|
||||
if (check_unsigned)
|
||||
{
|
||||
rc = zone_status(name, class1, keyname, now);
|
||||
if (rc == STAT_SECURE)
|
||||
rc = STAT_BOGUS;
|
||||
if (class)
|
||||
*class = class1; /* Class for NEED_DS or NEED_DNSKEY */
|
||||
}
|
||||
else
|
||||
rc = STAT_INSECURE;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* explore_rrset() gives us key name from sigs in keyname.
|
||||
Can't overwrite name here. */
|
||||
strcpy(daemon->workspacename, keyname);
|
||||
rc = zone_status(daemon->workspacename, class1, keyname, now);
|
||||
if (rc != STAT_SECURE)
|
||||
{
|
||||
/* Zone is insecure, don't need to validate RRset */
|
||||
if (class)
|
||||
*class = class1; /* Class for NEED_DS or NEED_DNSKEY */
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
|
||||
|
||||
if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
|
||||
{
|
||||
if (class)
|
||||
*class = class1; /* Class for DS or DNSKEY */
|
||||
return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
|
||||
|
||||
/* Note if we've validated either the answer to the question
|
||||
or the target of a CNAME. Any not noted will need NSEC or
|
||||
to be in unsigned space. */
|
||||
|
||||
for (j = 0; j <targetidx; j++)
|
||||
if ((p2 = targets[j]))
|
||||
{
|
||||
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
|
||||
targets[j] = NULL;
|
||||
}
|
||||
|
||||
if (rc == STAT_SECURE_WILDCARD)
|
||||
{
|
||||
@@ -1949,39 +2102,25 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
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 = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
|
||||
return STAT_BOGUS; /* No NSECs or bad packet */
|
||||
|
||||
/* Note that we may not yet have validated the NSEC/NSEC3 RRsets. Since the check
|
||||
below returns either SECURE or BOGUS, that's not a problem. If the RRsets later fail
|
||||
we'll return BOGUS then. */
|
||||
|
||||
if (nsec_type == T_NSEC)
|
||||
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
|
||||
else
|
||||
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
|
||||
keyname, name, type1, wildname, NULL);
|
||||
|
||||
if (rc != STAT_SECURE)
|
||||
return rc;
|
||||
}
|
||||
else if (rc != STAT_SECURE)
|
||||
{
|
||||
if (class)
|
||||
*class = class1; /* Class for DS or DNSKEY */
|
||||
|
||||
if (rc == STAT_NO_SIG)
|
||||
{
|
||||
/* If we dropped off the end of a CNAME chain, return
|
||||
STAT_NO_SIG and the last name is keyname. This is used for proving non-existence
|
||||
if DS records in CNAME chains. */
|
||||
if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount))
|
||||
/* No CNAME chain, or no sig in answer section, return empty name. */
|
||||
*keyname = 0;
|
||||
else if (!extract_name(header, plen, &qname, keyname, 1, 0))
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
if (rc == STAT_BOGUS)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
|
||||
/* Also note if the RRset is the answer to the question, or the target of a CNAME */
|
||||
cache_start_insert();
|
||||
|
||||
for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
|
||||
@@ -2078,148 +2217,55 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
cache_end_insert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
/* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
|
||||
if (have_answer)
|
||||
return STAT_SECURE;
|
||||
|
||||
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
|
||||
/* First marshall the NSEC records, if we've not done it previously */
|
||||
if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
|
||||
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
|
||||
for (j = 0; j <targetidx; j++)
|
||||
if ((p2 = targets[j]))
|
||||
{
|
||||
/* No NSEC records. If we dropped off the end of a CNAME chain, return
|
||||
STAT_NO_SIG and the last name is keyname. This is used for proving non-existence
|
||||
if DS records in CNAME chains. */
|
||||
if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */
|
||||
*keyname = 0;
|
||||
else if (!extract_name(header, plen, &qname, keyname, 1, 0))
|
||||
return STAT_BOGUS;
|
||||
return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into
|
||||
an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */
|
||||
}
|
||||
if (neganswer)
|
||||
*neganswer = 1;
|
||||
|
||||
/* Get name of missing answer */
|
||||
if (!extract_name(header, plen, &qname, name, 1, 0))
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (nsec_type == T_NSEC)
|
||||
return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
|
||||
else
|
||||
return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
|
||||
}
|
||||
|
||||
/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
|
||||
Needed for proving answer in unsigned space.
|
||||
Return STAT_NEED_*
|
||||
STAT_BOGUS - error
|
||||
STAT_INSECURE - name of first non-secure record in name
|
||||
*/
|
||||
int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
|
||||
{
|
||||
unsigned char *p = (unsigned char *)(header+1);
|
||||
int type, class, qclass, rdlen, j, rc;
|
||||
int cname_count = CNAME_CHAIN;
|
||||
char *wildname;
|
||||
|
||||
/* Get question */
|
||||
if (!extract_name(header, plen, &p, name, 1, 4))
|
||||
return STAT_BOGUS;
|
||||
|
||||
p +=2; /* type */
|
||||
GETSHORT(qclass, p);
|
||||
|
||||
while (1)
|
||||
{
|
||||
for (j = ntohs(header->ancount); j != 0; j--)
|
||||
{
|
||||
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
|
||||
if (!extract_name(header, plen, &p2, name, 1, 10))
|
||||
return STAT_BOGUS; /* bad packet */
|
||||
|
||||
GETSHORT(type, p);
|
||||
GETSHORT(class, p);
|
||||
p += 4; /* TTL */
|
||||
GETSHORT(rdlen, p);
|
||||
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
|
||||
|
||||
/* Not target, loop */
|
||||
if (rc == 2 || qclass != class)
|
||||
/* 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. */
|
||||
if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
|
||||
{
|
||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||
/* Empty DS without NSECS */
|
||||
if (qtype == T_DS)
|
||||
return STAT_BOGUS;
|
||||
continue;
|
||||
else
|
||||
{
|
||||
rc = zone_status(name, qclass, keyname, now);
|
||||
if (rc != STAT_SECURE)
|
||||
{
|
||||
if (class)
|
||||
*class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Got to end of CNAME chain. */
|
||||
if (type != T_CNAME)
|
||||
return STAT_INSECURE;
|
||||
|
||||
/* validate CNAME chain, return if insecure or need more data */
|
||||
rc = validate_rrset(now, header, plen, class, type, name, keyname, &wildname, NULL, 0, 0, 0);
|
||||
|
||||
if (rc == STAT_SECURE_WILDCARD)
|
||||
{
|
||||
int nsec_type, nsec_count, i;
|
||||
unsigned char **nsecs;
|
||||
|
||||
/* An attacker can 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 genuine record doesn't exist. Check that here. */
|
||||
if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class)))
|
||||
return STAT_BOGUS; /* No NSECs or bad packet */
|
||||
|
||||
/* Note that we're called here because something didn't validate in validate_reply,
|
||||
so we can't assume that any NSEC records have been validated. We do them by steam here */
|
||||
|
||||
for (i = 0; i < nsec_count; i++)
|
||||
{
|
||||
unsigned char *p1 = nsecs[i];
|
||||
|
||||
if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0))
|
||||
return STAT_BOGUS;
|
||||
|
||||
rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0);
|
||||
|
||||
/* NSECs can't be wildcards. */
|
||||
if (rc == STAT_SECURE_WILDCARD)
|
||||
rc = STAT_BOGUS;
|
||||
|
||||
if (rc != STAT_SECURE)
|
||||
return rc;
|
||||
return STAT_BOGUS; /* signed zone, no NSECs */
|
||||
}
|
||||
}
|
||||
|
||||
if (nsec_type == T_NSEC)
|
||||
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL);
|
||||
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
|
||||
else
|
||||
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
|
||||
keyname, name, type, wildname, NULL);
|
||||
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
|
||||
|
||||
if (rc != STAT_SECURE)
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (rc != STAT_SECURE)
|
||||
{
|
||||
if (rc == STAT_NO_SIG)
|
||||
rc = STAT_INSECURE;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Loop down CNAME chain/ */
|
||||
if (!cname_count-- ||
|
||||
!extract_name(header, plen, &p, name, 1, 0) ||
|
||||
!(p = skip_questions(header, plen)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* End of CNAME chain */
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
return STAT_SECURE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
557
src/forward.c
557
src/forward.c
@@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
|
||||
static unsigned short get_id(void);
|
||||
static void free_frec(struct frec *f);
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
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);
|
||||
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname);
|
||||
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
|
||||
char *name, char *keyname);
|
||||
#endif
|
||||
|
||||
|
||||
/* Send a UDP packet with its source address set as "source"
|
||||
unless nowild is true, when we just send it with the kernel default */
|
||||
int send_from(int fd, int nowild, char *packet, size_t len,
|
||||
@@ -825,66 +816,39 @@ void reply_query(int fd, int family, time_t now)
|
||||
#ifdef HAVE_DNSSEC
|
||||
if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
|
||||
{
|
||||
int status;
|
||||
int status = 0;
|
||||
|
||||
/* We've had a reply already, which we're validating. Ignore this duplicate */
|
||||
if (forward->blocking_query)
|
||||
return;
|
||||
|
||||
if (header->hb3 & HB3_TC)
|
||||
{
|
||||
/* Truncated answer can't be validated.
|
||||
If this is an answer to a DNSSEC-generated query, we still
|
||||
need to get the client to retry over TCP, so return
|
||||
an answer with the TC bit set, even if the actual answer fits.
|
||||
*/
|
||||
if (header->hb3 & HB3_TC)
|
||||
status = STAT_TRUNCATED;
|
||||
}
|
||||
else if (forward->flags & FREC_DNSKEY_QUERY)
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
|
||||
would invite infinite loops, since the answers to DNSKEY and DS queries
|
||||
will not be cached, so they'll be repeated. */
|
||||
if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
|
||||
{
|
||||
if (forward->flags & FREC_DNSKEY_QUERY)
|
||||
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
else if (forward->flags & FREC_DS_QUERY)
|
||||
{
|
||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
/* Provably no DS, everything below is insecure, even if signatures are offered */
|
||||
if (status == STAT_NO_DS)
|
||||
/* We only cache sigs when we've validated a reply.
|
||||
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||
DS chain, so we don't return replies from cache missing sigs. */
|
||||
status = STAT_INSECURE_DS;
|
||||
else if (status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
{
|
||||
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
|
||||
if (status == STAT_INSECURE)
|
||||
status = STAT_INSECURE_DS;
|
||||
}
|
||||
else
|
||||
status = STAT_INSECURE_DS;
|
||||
}
|
||||
else if (status == STAT_NO_NS)
|
||||
status = STAT_BOGUS;
|
||||
}
|
||||
else if (forward->flags & FREC_CHECK_NOSIGN)
|
||||
{
|
||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
if (status != STAT_NEED_KEY)
|
||||
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
|
||||
if (status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
|
||||
else
|
||||
status = STAT_INSECURE;
|
||||
}
|
||||
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class,
|
||||
option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
|
||||
}
|
||||
|
||||
/* 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_DS_NEG || status == STAT_NEED_KEY)
|
||||
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
|
||||
{
|
||||
struct frec *new, *orig;
|
||||
|
||||
@@ -897,12 +861,11 @@ void reply_query(int fd, int family, time_t now)
|
||||
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;
|
||||
status = STAT_ABANDONED;
|
||||
else
|
||||
{
|
||||
int fd;
|
||||
@@ -912,11 +875,10 @@ void reply_query(int fd, int family, time_t now)
|
||||
new->blocking_query = NULL;
|
||||
new->sentto = server;
|
||||
new->rfd4 = NULL;
|
||||
new->orig_domain = NULL;
|
||||
#ifdef HAVE_IPV6
|
||||
new->rfd6 = NULL;
|
||||
#endif
|
||||
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
|
||||
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
|
||||
|
||||
new->dependent = forward; /* to find query awaiting new one. */
|
||||
forward->blocking_query = new; /* for garbage cleaning */
|
||||
@@ -929,9 +891,6 @@ void reply_query(int fd, int family, time_t now)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status == STAT_NEED_DS_NEG)
|
||||
new->flags |= FREC_CHECK_NOSIGN;
|
||||
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, server->edns_pktsz);
|
||||
@@ -941,9 +900,7 @@ void reply_query(int fd, int family, time_t now)
|
||||
new->new_id = get_id();
|
||||
header->id = htons(new->new_id);
|
||||
/* Save query for retransmission */
|
||||
if (!(new->stash = blockdata_alloc((char *)header, nn)))
|
||||
return;
|
||||
|
||||
new->stash = blockdata_alloc((char *)header, nn);
|
||||
new->stash_len = nn;
|
||||
|
||||
/* Don't resend this. */
|
||||
@@ -975,94 +932,34 @@ void reply_query(int fd, int family, time_t now)
|
||||
sa_len(&server->addr))));
|
||||
server->queries++;
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ok, we reached far enough up the chain-of-trust that we can validate something.
|
||||
Now wind back down, pulling back answers which wouldn't previously validate
|
||||
and validate them with the new data. 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)
|
||||
{
|
||||
/* Validated original answer, all done. */
|
||||
if (!forward->dependent)
|
||||
break;
|
||||
|
||||
/* validated subsdiary query, (and cached result)
|
||||
pop that and return to the previous query we were working on. */
|
||||
struct frec *prev = forward->dependent;
|
||||
free_frec(forward);
|
||||
forward = prev;
|
||||
forward->blocking_query = NULL; /* already gone */
|
||||
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
|
||||
n = forward->stash_len;
|
||||
|
||||
if (status == STAT_SECURE)
|
||||
{
|
||||
if (forward->flags & FREC_DNSKEY_QUERY)
|
||||
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
else if (forward->flags & FREC_DS_QUERY)
|
||||
{
|
||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
/* Provably no DS, everything below is insecure, even if signatures are offered */
|
||||
if (status == STAT_NO_DS)
|
||||
/* We only cache sigs when we've validated a reply.
|
||||
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||
DS chain, so we don't return replies from cache missing sigs. */
|
||||
status = STAT_INSECURE_DS;
|
||||
else if (status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
{
|
||||
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
|
||||
if (status == STAT_INSECURE)
|
||||
status = STAT_INSECURE_DS;
|
||||
}
|
||||
else
|
||||
status = STAT_INSECURE_DS;
|
||||
}
|
||||
else if (status == STAT_NO_NS)
|
||||
status = STAT_BOGUS;
|
||||
}
|
||||
else if (forward->flags & FREC_CHECK_NOSIGN)
|
||||
{
|
||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||
if (status != STAT_NEED_KEY)
|
||||
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
|
||||
if (status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
|
||||
else
|
||||
status = STAT_INSECURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
|
||||
goto anotherkey;
|
||||
}
|
||||
}
|
||||
|
||||
no_cache_dnssec = 0;
|
||||
|
||||
if (status == STAT_INSECURE_DS)
|
||||
{
|
||||
/* We only cache sigs when we've validated a reply.
|
||||
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||
DS chain, so we don't return replies from cache missing sigs. */
|
||||
status = STAT_INSECURE;
|
||||
no_cache_dnssec = 1;
|
||||
}
|
||||
|
||||
if (status == STAT_TRUNCATED)
|
||||
header->hb3 |= HB3_TC;
|
||||
else
|
||||
{
|
||||
char *result, *domain = "result";
|
||||
|
||||
if (forward->work_counter == 0)
|
||||
if (status == STAT_ABANDONED)
|
||||
{
|
||||
result = "ABANDONED";
|
||||
status = STAT_BOGUS;
|
||||
@@ -1415,315 +1312,49 @@ void receive_query(struct listener *listen, time_t now)
|
||||
}
|
||||
|
||||
#ifdef HAVE_DNSSEC
|
||||
|
||||
/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
|
||||
and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or
|
||||
STAT_NEED_DS_NEG and keyname if we need to do the query. */
|
||||
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
|
||||
char *name, char *keyname)
|
||||
{
|
||||
int status = dnssec_chase_cname(now, header, plen, name, keyname);
|
||||
|
||||
if (status != STAT_INSECURE)
|
||||
return status;
|
||||
|
||||
/* Store the domain we're trying to check. */
|
||||
forward->name_start = strlen(name);
|
||||
forward->name_len = forward->name_start + 1;
|
||||
if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
return do_check_sign(forward, 0, now, name, keyname);
|
||||
}
|
||||
|
||||
/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */
|
||||
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname)
|
||||
{
|
||||
/* get domain we're checking back from blockdata store, it's stored on the original query. */
|
||||
while (forward->dependent && !forward->orig_domain)
|
||||
forward = forward->dependent;
|
||||
|
||||
blockdata_retrieve(forward->orig_domain, forward->name_len, name);
|
||||
|
||||
while (1)
|
||||
{
|
||||
char *p;
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
struct crec *crecp;
|
||||
|
||||
/* Haven't received answer, see if in cache */
|
||||
if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS)))
|
||||
{
|
||||
/* put name of DS record we're missing into keyname */
|
||||
strcpy(keyname, &name[forward->name_start]);
|
||||
/* and wait for reply to arrive */
|
||||
return STAT_NEED_DS_NEG;
|
||||
}
|
||||
|
||||
/* F_DNSSECOK misused in DS cache records to non-existance of NS record */
|
||||
if (!(crecp->flags & F_NEG))
|
||||
status = STAT_SECURE;
|
||||
else if (crecp->flags & F_DNSSECOK)
|
||||
status = STAT_NO_DS;
|
||||
else
|
||||
status = STAT_NO_NS;
|
||||
}
|
||||
|
||||
/* Have entered non-signed part of DNS tree. */
|
||||
if (status == STAT_NO_DS)
|
||||
return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE;
|
||||
|
||||
if (status == STAT_BOGUS)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (status == STAT_NO_SIG && *keyname != 0)
|
||||
{
|
||||
/* There is a validated CNAME chain that doesn't end in a DS record. Start
|
||||
the search again in that domain. */
|
||||
blockdata_free(forward->orig_domain);
|
||||
forward->name_start = strlen(keyname);
|
||||
forward->name_len = forward->name_start + 1;
|
||||
if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
strcpy(name, keyname);
|
||||
status = 0; /* force to cache when we iterate. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* There's a proven DS record, or we're within a zone, where there doesn't need
|
||||
to be a DS record. Add a name and try again.
|
||||
If we've already tried the whole name, then fail */
|
||||
|
||||
if (forward->name_start == 0)
|
||||
return STAT_BOGUS;
|
||||
|
||||
for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--);
|
||||
|
||||
if (p != name)
|
||||
p++;
|
||||
|
||||
forward->name_start = p - name;
|
||||
status = 0; /* force to cache when we iterate. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Move down from the root, until we find a signed non-existance of a DS, in which case
|
||||
an unsigned answer is OK, or we find a signed DS, in which case there should be
|
||||
a signature, and the answer is BOGUS */
|
||||
static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name,
|
||||
char *keyname, struct server *server, int *keycount)
|
||||
{
|
||||
size_t m;
|
||||
unsigned char *packet, *payload;
|
||||
u16 *length;
|
||||
int status, name_len;
|
||||
struct blockdata *block;
|
||||
|
||||
char *name_start;
|
||||
|
||||
/* Get first insecure entry in CNAME chain */
|
||||
status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
|
||||
if (status == STAT_BOGUS)
|
||||
return STAT_BOGUS;
|
||||
|
||||
if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16))))
|
||||
return STAT_BOGUS;
|
||||
|
||||
payload = &packet[2];
|
||||
header = (struct dns_header *)payload;
|
||||
length = (u16 *)packet;
|
||||
|
||||
/* Stash the name away, since the buffer will be trashed when we recurse */
|
||||
name_len = strlen(name) + 1;
|
||||
name_start = name + name_len - 1;
|
||||
|
||||
if (!(block = blockdata_alloc(name, name_len)))
|
||||
{
|
||||
free(packet);
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
unsigned char c1, c2;
|
||||
struct crec *crecp;
|
||||
|
||||
if (--(*keycount) == 0)
|
||||
{
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS)))
|
||||
{
|
||||
if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))
|
||||
{
|
||||
/* Found a secure denial of DS - delegation is indeed insecure */
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
|
||||
Add another label and continue. */
|
||||
|
||||
if (name_start == name)
|
||||
{
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_BOGUS; /* run out of labels */
|
||||
}
|
||||
|
||||
name_start -= 2;
|
||||
while (*name_start != '.' && name_start != name)
|
||||
name_start--;
|
||||
if (name_start != name)
|
||||
name_start++;
|
||||
}
|
||||
|
||||
/* Can't find it in the cache, have to send a query */
|
||||
|
||||
m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
|
||||
|
||||
*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))
|
||||
{
|
||||
m = (c1 << 8) | c2;
|
||||
|
||||
/* Note this trashes all three name workspaces */
|
||||
status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
|
||||
|
||||
if (status == STAT_NO_DS)
|
||||
{
|
||||
/* Found a secure denial of DS - delegation is indeed insecure */
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
if (status == STAT_NO_SIG && *keyname != 0)
|
||||
{
|
||||
/* There is a validated CNAME chain that doesn't end in a DS record. Start
|
||||
the search again in that domain. */
|
||||
blockdata_free(block);
|
||||
name_len = strlen(keyname) + 1;
|
||||
name_start = name + name_len - 1;
|
||||
|
||||
if (!(block = blockdata_alloc(keyname, name_len)))
|
||||
return STAT_BOGUS;
|
||||
|
||||
strcpy(name, keyname);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status == STAT_BOGUS)
|
||||
{
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_BOGUS;
|
||||
}
|
||||
|
||||
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
|
||||
Add another label and continue. */
|
||||
|
||||
/* Get name we're checking back. */
|
||||
blockdata_retrieve(block, name_len, name);
|
||||
|
||||
if (name_start == name)
|
||||
{
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_BOGUS; /* run out of labels */
|
||||
}
|
||||
|
||||
name_start -= 2;
|
||||
while (*name_start != '.' && name_start != name)
|
||||
name_start--;
|
||||
if (name_start != name)
|
||||
name_start++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* IO failure */
|
||||
free(packet);
|
||||
blockdata_free(block);
|
||||
return STAT_BOGUS; /* run out of labels */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
int new_status;
|
||||
unsigned char *packet = NULL;
|
||||
size_t m;
|
||||
unsigned char *payload = NULL;
|
||||
struct dns_header *new_header = NULL;
|
||||
u16 *length = NULL;
|
||||
unsigned char c1, c2;
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
||||
if (--(*keycount) == 0)
|
||||
return STAT_INSECURE;
|
||||
|
||||
if (status == STAT_NEED_KEY)
|
||||
new_status = STAT_ABANDONED;
|
||||
else if (status == STAT_NEED_KEY)
|
||||
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
|
||||
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
|
||||
{
|
||||
else if (status == STAT_NEED_DS)
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||
if (status == STAT_NEED_DS)
|
||||
{
|
||||
if (new_status == STAT_NO_DS)
|
||||
new_status = STAT_INSECURE_DS;
|
||||
if (new_status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
{
|
||||
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
|
||||
if (new_status == STAT_INSECURE)
|
||||
new_status = STAT_INSECURE_DS;
|
||||
}
|
||||
else
|
||||
new_status = STAT_INSECURE_DS;
|
||||
}
|
||||
else if (new_status == STAT_NO_NS)
|
||||
new_status = STAT_BOGUS;
|
||||
}
|
||||
}
|
||||
else if (status == STAT_CHASE_CNAME)
|
||||
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
||||
else
|
||||
{
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
|
||||
|
||||
if (new_status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
|
||||
else
|
||||
new_status = STAT_INSECURE;
|
||||
}
|
||||
}
|
||||
if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
|
||||
break;
|
||||
|
||||
/* 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)
|
||||
if (!packet)
|
||||
{
|
||||
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;
|
||||
packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
|
||||
payload = &packet[2];
|
||||
new_header = (struct dns_header *)payload;
|
||||
length = (u16 *)packet;
|
||||
}
|
||||
|
||||
if (!packet)
|
||||
return STAT_INSECURE;
|
||||
{
|
||||
new_status = STAT_ABANDONED;
|
||||
break;
|
||||
}
|
||||
|
||||
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, server->edns_pktsz);
|
||||
|
||||
@@ -1733,65 +1364,22 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
||||
!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 = STAT_ABANDONED;
|
||||
break;
|
||||
}
|
||||
|
||||
m = (c1 << 8) | c2;
|
||||
|
||||
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
|
||||
|
||||
if (new_status == STAT_SECURE)
|
||||
{
|
||||
/* 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 || status == STAT_NEED_DS_NEG)
|
||||
{
|
||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||
if (status == STAT_NEED_DS)
|
||||
{
|
||||
if (new_status == STAT_NO_DS)
|
||||
new_status = STAT_INSECURE_DS;
|
||||
else if (new_status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
{
|
||||
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
|
||||
if (new_status == STAT_INSECURE)
|
||||
new_status = STAT_INSECURE_DS;
|
||||
}
|
||||
else
|
||||
new_status = STAT_INSECURE_DS;
|
||||
}
|
||||
else if (new_status == STAT_NO_NS)
|
||||
new_status = STAT_BOGUS;
|
||||
}
|
||||
}
|
||||
else if (status == STAT_CHASE_CNAME)
|
||||
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
||||
else
|
||||
{
|
||||
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
|
||||
|
||||
if (new_status == STAT_NO_SIG)
|
||||
{
|
||||
if (option_bool(OPT_DNSSEC_NO_SIGN))
|
||||
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
|
||||
else
|
||||
new_status = STAT_INSECURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
|
||||
goto another_tcp_key;
|
||||
}
|
||||
if (new_status != STAT_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet)
|
||||
free(packet);
|
||||
}
|
||||
|
||||
return new_status;
|
||||
}
|
||||
#endif
|
||||
@@ -2075,19 +1663,10 @@ unsigned char *tcp_request(int confd, time_t now,
|
||||
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
|
||||
{
|
||||
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);
|
||||
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
|
||||
char *result, *domain = "result";
|
||||
|
||||
if (status == STAT_INSECURE_DS)
|
||||
{
|
||||
/* We only cache sigs when we've validated a reply.
|
||||
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||
DS chain, so we don't return replies from cache missing sigs. */
|
||||
status = STAT_INSECURE;
|
||||
no_cache_dnssec = 1;
|
||||
}
|
||||
|
||||
if (keycount == 0)
|
||||
if (status == STAT_ABANDONED)
|
||||
{
|
||||
result = "ABANDONED";
|
||||
status = STAT_BOGUS;
|
||||
@@ -2179,7 +1758,6 @@ static struct frec *allocate_frec(time_t now)
|
||||
f->dependent = NULL;
|
||||
f->blocking_query = NULL;
|
||||
f->stash = NULL;
|
||||
f->orig_domain = NULL;
|
||||
#endif
|
||||
daemon->frec_list = f;
|
||||
}
|
||||
@@ -2248,12 +1826,6 @@ static void free_frec(struct frec *f)
|
||||
f->stash = NULL;
|
||||
}
|
||||
|
||||
if (f->orig_domain)
|
||||
{
|
||||
blockdata_free(f->orig_domain);
|
||||
f->orig_domain = NULL;
|
||||
}
|
||||
|
||||
/* Anything we're waiting on is pointless now, too */
|
||||
if (f->blocking_query)
|
||||
free_frec(f->blocking_query);
|
||||
@@ -2281,15 +1853,24 @@ struct frec *get_new_frec(time_t now, int *wait, int force)
|
||||
target = f;
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_DNSSEC
|
||||
/* Don't free DNSSEC sub-queries here, as we may end up with
|
||||
dangling references to them. They'll go when their "real" query
|
||||
is freed. */
|
||||
if (!f->dependent)
|
||||
#endif
|
||||
{
|
||||
if (difftime(now, f->time) >= 4*TIMEOUT)
|
||||
{
|
||||
free_frec(f);
|
||||
target = f;
|
||||
}
|
||||
|
||||
|
||||
if (!oldest || difftime(f->time, oldest->time) <= 0)
|
||||
oldest = f;
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user