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;
}
}
}
}
-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;
-
- if (new_sz > new)
+ int old = *szp;
+
+ 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;
}
} 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.
+static unsigned char **rrset = NULL, **sigs = NULL;
- 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)
+/* 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 unsigned char **rrset = NULL, **sigs = NULL;
- static int rrset_sz = 0, sig_sz = 0;
-
+ 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. */
+ /* 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;
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;
}
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;
}
}
if (!ADD_RDLEN(header, p, plen, rdlen))
- return STAT_BOGUS;
+ return 0;
}
- /* RRset empty */
- if (rrsetidx == 0)
- return STAT_INSECURE;
+ *sigcnt = sigidx;
+ *rrcnt = rrsetidx;
+
+ 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.
- /* no RRSIGs */
- if (sigidx == 0)
- return STAT_NO_SIG;
+ 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
unused, and used as workspace by the sort. */
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;
-
+
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY;
/* 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_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_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)
{
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--)
{
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 &&
recp1->uid == (unsigned int)class &&
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;
{
/* 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);
/* commit cache insert. */
cache_end_insert();
- return STAT_SECURE;
+ return STAT_OK;
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
/* 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)
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)
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. */
- if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname)))
+ if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname)))
{
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
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;
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 val;
+ return STAT_OK;
}
+
/* 4034 6.1 */
static int hostname_cmp(const char *a, const char *b)
{
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++)
/* 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)
{
if (!CHECK_LEN(header, p, plen, rdlen))
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++)
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 */
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)
{
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
- return STAT_BOGUS;
+ return 0;
break; /* finshed checking */
}
rdlen -= p[1];
p += p[1];
}
-
+
return 1;
}
else if (rc < 0)
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
wrap around case, name-hash falls between NSEC3 name-hash and end */
if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
- return 1;
+ {
+ 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)
- return 1;
+ {
+ if ((flags & 0x01) && nons) /* opt out */
+ *nons = 0;
+
+ return 1;
+ }
}
}
}
+
return 0;
}
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).
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 */
+
+/* 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)
+{
+ 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 *neganswer, int *nons)
+ int *class, int check_unsigned, int *neganswer, int *nons)
{
- 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;
+ 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;
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;
-
+
GETSHORT(qtype, p1);
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:
- 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)))
- return STAT_BOGUS; /* bad packet */
-
- GETSHORT(type2, p1);
- GETSHORT(class2, p1);
- p1 += 4; /* TTL */
- GETSHORT(rdlen2, p1);
-
- if (rc == 1 && qclass == class2)
- {
- /* 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))
- return STAT_BOGUS;
-
- p1 = ans_start;
- goto cname_loop;
- }
- }
-
- 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;
- }
-
+ if (qtype != T_CNAME)
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ if (!(p1 = skip_name(p1, header, plen, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type2, p1);
+ p1 += 6; /* class, TTL */
+ GETSHORT(rdlen2, p1);
+
+ if (type2 == T_CNAME)
+ {
+ if (!expand_workspace(&targets, &target_sz, targetidx))
+ return STAT_BOGUS;
+
+ targets[targetidx++] = p1; /* pointer to target name */
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen2))
+ return STAT_BOGUS;
+ }
+
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
if (!extract_name(header, plen, &p1, name, 1, 10))
/* 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;
char *wildname;
int have_wildcard = 0;
- rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0);
-
- if (rc == STAT_SECURE_WILDCARD)
- {
- have_wildcard = 1;
-
- /* An attacker replay a wildcard answer with a different
- answer and overlay a genuine RR. To prove this
- hasn't happened, the answer must prove that
- the gennuine record doesn't exist. Check that here. */
- if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
- return STAT_BOGUS; /* No NSECs or bad packet */
-
- 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 (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+ return STAT_BOGUS;
- if (rc == STAT_NO_SIG)
+ /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
+ if (sigcnt == 0)
+ {
+ if (check_unsigned)
{
- /* 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;
+ 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;
}
- /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
- cache_start_insert();
+ /* 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);
- for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
+ if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
{
- if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
- return STAT_BOGUS; /* bad packet */
+ 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)
+ {
+ have_wildcard = 1;
- GETSHORT(type2, p2);
- GETSHORT(class2, p2);
- GETLONG(ttl, p2);
- GETSHORT(rdlen2, p2);
-
- if (!CHECK_LEN(header, p2, plen, rdlen2))
- return STAT_BOGUS; /* bad packet */
-
- if (class2 == class1 && rc == 1)
- {
- psave = p2;
+ /* An attacker replay a wildcard answer with a different
+ answer and overlay a genuine RR. To prove this
+ hasn't happened, the answer must prove that
+ the gennuine record doesn't exist. Check that here. */
+ if (!(nsec_type = 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 (type1 == T_DS && type2 == T_DS)
- {
- if (rdlen2 < 4)
- return STAT_BOGUS; /* bad packet */
-
- GETSHORT(keytag, p2);
- algo = *p2++;
- digest = *p2++;
-
- /* Cache needs to known class for DNSSEC stuff */
- a.addr.dnssec.class = class2;
-
- if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
- {
- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
- blockdata_free(key);
- else
- {
- a.addr.keytag = keytag;
- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
- crecp->addr.ds.digest = digest;
- crecp->addr.ds.keydata = key;
- crecp->addr.ds.algo = algo;
- crecp->addr.ds.keytag = keytag;
- crecp->addr.ds.keylen = rdlen2 - 4;
- }
- }
- }
- else if (type2 == T_RRSIG)
- {
- if (rdlen2 < 18)
- return STAT_BOGUS; /* bad packet */
+ 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_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++)
+ {
+ if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type2, p2);
+ GETSHORT(class2, p2);
+ GETLONG(ttl, p2);
+ GETSHORT(rdlen2, p2);
+
+ if (!CHECK_LEN(header, p2, plen, rdlen2))
+ return STAT_BOGUS; /* bad packet */
+
+ if (class2 == class1 && rc == 1)
+ {
+ psave = p2;
- GETSHORT(type_covered, p2);
-
- if (type_covered == type1 &&
- (type_covered == T_A || type_covered == T_AAAA ||
- type_covered == T_CNAME || type_covered == T_DS ||
- type_covered == T_DNSKEY || type_covered == T_PTR))
+ if (type1 == T_DS && type2 == T_DS)
{
- a.addr.dnssec.type = type_covered;
- a.addr.dnssec.class = class1;
+ if (rdlen2 < 4)
+ return STAT_BOGUS; /* bad packet */
- algo = *p2++;
- p2 += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(keytag, p2);
+ algo = *p2++;
+ digest = *p2++;
+
+ /* Cache needs to known class for DNSSEC stuff */
+ a.addr.dnssec.class = class2;
- /* We don't cache sigs for wildcard answers, because to reproduce the
- answer from the cache will require one or more NSEC/NSEC3 records
- which we don't cache. The lack of the RRSIG ensures that a query for
- this RRset asking for a secure answer will always be forwarded. */
- if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
+ if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
{
- if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
+ if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
blockdata_free(key);
else
{
- crecp->addr.sig.keydata = key;
- crecp->addr.sig.keylen = rdlen2;
- crecp->addr.sig.keytag = keytag;
- crecp->addr.sig.type_covered = type_covered;
- crecp->addr.sig.algo = algo;
+ a.addr.keytag = keytag;
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
+ crecp->addr.ds.digest = digest;
+ crecp->addr.ds.keydata = key;
+ crecp->addr.ds.algo = algo;
+ crecp->addr.ds.keytag = keytag;
+ crecp->addr.ds.keylen = rdlen2 - 4;
+ }
+ }
+ }
+ else if (type2 == T_RRSIG)
+ {
+ if (rdlen2 < 18)
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type_covered, p2);
+
+ if (type_covered == type1 &&
+ (type_covered == T_A || type_covered == T_AAAA ||
+ type_covered == T_CNAME || type_covered == T_DS ||
+ type_covered == T_DNSKEY || type_covered == T_PTR))
+ {
+ a.addr.dnssec.type = type_covered;
+ a.addr.dnssec.class = class1;
+
+ algo = *p2++;
+ p2 += 13; /* labels, orig_ttl, expiration, inception */
+ GETSHORT(keytag, p2);
+
+ /* We don't cache sigs for wildcard answers, because to reproduce the
+ answer from the cache will require one or more NSEC/NSEC3 records
+ which we don't cache. The lack of the RRSIG ensures that a query for
+ this RRset asking for a secure answer will always be forwarded. */
+ if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
+ {
+ if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
+ blockdata_free(key);
+ else
+ {
+ crecp->addr.sig.keydata = key;
+ crecp->addr.sig.keylen = rdlen2;
+ crecp->addr.sig.keytag = keytag;
+ crecp->addr.sig.type_covered = type_covered;
+ crecp->addr.sig.algo = algo;
+ }
}
}
}
+
+ p2 = psave;
}
- p2 = psave;
+ if (!ADD_RDLEN(header, p2, plen, rdlen2))
+ return STAT_BOGUS; /* bad packet */
}
- if (!ADD_RDLEN(header, p2, plen, rdlen2))
- return STAT_BOGUS; /* bad packet */
+ cache_end_insert();
}
-
- cache_end_insert();
}
}
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)))
- {
- /* 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. */
- }
-
- /* 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)))
- return STAT_BOGUS; /* bad packet */
-
- GETSHORT(type, p);
- GETSHORT(class, p);
- p += 4; /* TTL */
- GETSHORT(rdlen, p);
-
- /* Not target, loop */
- if (rc == 2 || qclass != class)
- {
- if (!ADD_RDLEN(header, p, plen, rdlen))
- return STAT_BOGUS;
- continue;
- }
-
- /* 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);
+ /* 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]))
+ {
+ if (neganswer)
+ *neganswer = 1;
- /* NSECs can't be wildcards. */
- if (rc == STAT_SECURE_WILDCARD)
- rc = STAT_BOGUS;
+ if (!extract_name(header, plen, &p2, name, 1, 10))
+ return STAT_BOGUS; /* bad packet */
+
+ /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
- if (rc != STAT_SECURE)
+ /* 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)))
+ {
+ /* Empty DS without NSECS */
+ if (qtype == T_DS)
+ return STAT_BOGUS;
+ 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;
- }
-
- if (nsec_type == T_NSEC)
- rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL);
- else
- rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
- keyname, name, type, wildname, NULL);
-
- if (rc != STAT_SECURE)
- return rc;
- }
-
- if (rc != STAT_SECURE)
- {
- if (rc == STAT_NO_SIG)
- rc = STAT_INSECURE;
- return rc;
- }
+ }
+
+ return STAT_BOGUS; /* signed zone, no NSECs */
+ }
+ }
- /* Loop down CNAME chain/ */
- if (!cname_count-- ||
- !extract_name(header, plen, &p, name, 1, 0) ||
- !(p = skip_questions(header, plen)))
- return STAT_BOGUS;
-
- break;
- }
+ if (nsec_type == T_NSEC)
+ 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, qtype, NULL, nons);
- /* End of CNAME chain */
- return STAT_INSECURE;
- }
+ if (rc != STAT_SECURE)
+ return rc;
+ }
+
+ return STAT_SECURE;
}
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,
#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.
+
+ /* 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.
*/
- status = STAT_TRUNCATED;
- }
- else if (forward->flags & FREC_DNSKEY_QUERY)
- status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
- else if (forward->flags & FREC_DS_QUERY)
- {
- status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
- /* 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
+ if (header->hb3 & HB3_TC)
+ status = STAT_TRUNCATED;
+
+ while (1)
{
- status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
- if (status == STAT_NO_SIG)
+ /* 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 (option_bool(OPT_DNSSEC_NO_SIGN))
- status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
+ if (forward->flags & FREC_DNSKEY_QUERY)
+ status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+ else if (forward->flags & FREC_DS_QUERY)
+ status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
- status = 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)
- {
- struct frec *new, *orig;
-
- /* Free any saved query */
- if (forward->stash)
- blockdata_free(forward->stash);
-
- /* Now save reply pending receipt of key data */
- if (!(forward->stash = blockdata_alloc((char *)header, n)))
- return;
- forward->stash_len = n;
- anotherkey:
- /* Find the original query that started it all.... */
- for (orig = forward; orig->dependent; orig = orig->dependent);
-
- if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
- status = STAT_INSECURE;
- else
+ /* Can't validate, as we're missing key data. Put this
+ answer aside, whilst we get that. */
+ if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
- int fd;
- struct frec *next = new->next;
- *new = *forward; /* copy everything, then overwrite */
- new->next = next;
- 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);
+ struct frec *new, *orig;
- new->dependent = forward; /* to find query awaiting new one. */
- forward->blocking_query = new; /* for garbage cleaning */
- /* validate routines leave name of required record in daemon->keyname */
- if (status == STAT_NEED_KEY)
- {
- new->flags |= FREC_DNSKEY_QUERY;
- nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
- daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
- }
- 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);
- }
- if ((hash = hash_questions(header, nn, daemon->namebuff)))
- memcpy(new->hash, hash, HASH_SIZE);
- new->new_id = get_id();
- header->id = htons(new->new_id);
- /* Save query for retransmission */
- if (!(new->stash = blockdata_alloc((char *)header, nn)))
+ /* Free any saved query */
+ if (forward->stash)
+ blockdata_free(forward->stash);
+
+ /* Now save reply pending receipt of key data */
+ if (!(forward->stash = blockdata_alloc((char *)header, n)))
return;
-
- new->stash_len = nn;
+ forward->stash_len = n;
- /* Don't resend this. */
- daemon->srv_save = NULL;
+ /* Find the original query that started it all.... */
+ for (orig = forward; orig->dependent; orig = orig->dependent);
- if (server->sfd)
- fd = server->sfd->fd;
+ if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
+ status = STAT_ABANDONED;
else
{
- fd = -1;
+ int fd;
+ struct frec *next = new->next;
+ *new = *forward; /* copy everything, then overwrite */
+ new->next = next;
+ new->blocking_query = NULL;
+ new->sentto = server;
+ new->rfd4 = NULL;
#ifdef HAVE_IPV6
- if (server->addr.sa.sa_family == AF_INET6)
+ new->rfd6 = NULL;
+#endif
+ new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+
+ new->dependent = forward; /* to find query awaiting new one. */
+ forward->blocking_query = new; /* for garbage cleaning */
+ /* validate routines leave name of required record in daemon->keyname */
+ if (status == STAT_NEED_KEY)
+ {
+ new->flags |= FREC_DNSKEY_QUERY;
+ nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
+ daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
+ }
+ else
{
- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
- fd = new->rfd6->fd;
+ new->flags |= FREC_DS_QUERY;
+ nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
+ daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
}
+ if ((hash = hash_questions(header, nn, daemon->namebuff)))
+ memcpy(new->hash, hash, HASH_SIZE);
+ new->new_id = get_id();
+ header->id = htons(new->new_id);
+ /* Save query for retransmission */
+ new->stash = blockdata_alloc((char *)header, nn);
+ new->stash_len = nn;
+
+ /* Don't resend this. */
+ daemon->srv_save = NULL;
+
+ if (server->sfd)
+ fd = server->sfd->fd;
else
+ {
+ fd = -1;
+#ifdef HAVE_IPV6
+ if (server->addr.sa.sa_family == AF_INET6)
+ {
+ if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
+ fd = new->rfd6->fd;
+ }
+ else
#endif
+ {
+ if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
+ fd = new->rfd4->fd;
+ }
+ }
+
+ if (fd != -1)
{
- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
- fd = new->rfd4->fd;
+ while (retry_send(sendto(fd, (char *)header, nn, 0,
+ &server->addr.sa,
+ sa_len(&server->addr))));
+ server->queries++;
}
- }
-
- if (fd != -1)
- {
- while (retry_send(sendto(fd, (char *)header, nn, 0,
- &server->addr.sa,
- 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;
{
char *result, *domain = "result";
- if (forward->work_counter == 0)
+ if (status == STAT_ABANDONED)
{
result = "ABANDONED";
status = STAT_BOGUS;
if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL))
domain = daemon->namebuff;
-
+
log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result);
}
}
#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;
- /* 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 = dnssec_validate_by_ds(now, header, n, name, keyname, class);
- else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+ while (1)
{
- new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
- if (status == STAT_NEED_DS)
+ /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
+ if (--(*keycount) == 0)
+ 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)
+ new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+ else
+ new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
+
+ 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 (!packet)
{
- 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;
+ packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+ payload = &packet[2];
+ new_header = (struct dns_header *)payload;
+ length = (u16 *)packet;
}
- }
- 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 (!packet)
{
- 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;
+ new_status = STAT_ABANDONED;
+ 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)
- {
- size_t m;
- unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
- unsigned char *payload = &packet[2];
- struct dns_header *new_header = (struct dns_header *)payload;
- u16 *length = (u16 *)packet;
- unsigned char c1, c2;
-
- if (!packet)
- return STAT_INSECURE;
-
- another_tcp_key:
+
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
!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
{
- 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;
- }
+ new_status = STAT_ABANDONED;
+ break;
}
+
+ m = (c1 << 8) | c2;
- free(packet);
+ new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
+
+ if (new_status != STAT_OK)
+ break;
}
+
+ if (packet)
+ free(packet);
+
return new_status;
}
#endif
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;
f->dependent = NULL;
f->blocking_query = NULL;
f->stash = NULL;
- f->orig_domain = NULL;
#endif
daemon->frec_list = 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);
target = f;
else
{
- if (difftime(now, f->time) >= 4*TIMEOUT)
- {
- free_frec(f);
- target = f;
- }
-
- if (!oldest || difftime(f->time, oldest->time) <= 0)
- oldest = f;
+#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)