blob: 3ad438d0a0c822937d3eebb5151d9455a9b56140 [file] [log] [blame]
#include "dnsmasq.h"
#include "dnssec-crypto.h"
#include <assert.h>
/* Maximum length in octects of a domain name, in wire format */
#define MAXCDNAME 256
#define SERIAL_UNDEF -100
#define SERIAL_EQ 0
#define SERIAL_LT -1
#define SERIAL_GT 1
/* Implement RFC1982 wrapped compare for 32-bit numbers */
static int serial_compare_32(unsigned long s1, unsigned long s2)
{
if (s1 == s2)
return SERIAL_EQ;
if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
(s1 > s2 && (s1 - s2) > (1UL<<31)))
return SERIAL_LT;
if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
(s1 > s2 && (s1 - s2) < (1UL<<31)))
return SERIAL_GT;
return SERIAL_UNDEF;
}
/* Extract a DNS name from wire format, without handling compression. This is
faster than extract_name() and does not require access to the full dns
packet. */
static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
{
unsigned char *start=rr, *end = rr+maxlen;
int count;
while (rr < end && *rr != 0)
{
count = *rr++;
while (count-- > 0 && rr < end)
{
*buf = *rr++;
if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
return 0;
if (*buf >= 'A' && *buf <= 'Z')
*buf += 'a' - 'A';
buf++;
}
*buf++ = '.';
}
/* Remove trailing dot (if any) */
if (rr != start)
*(--buf) = 0;
if (rr == end)
return 0;
/* Trailing \0 in source data must be consumed */
return rr-start+1;
}
/* process_domain_name() - do operations with domain names in canonicalized wire format.
*
* Handling domain names in wire format can be done with buffers as large as MAXCDNAME (256),
* while the representation format (as created by, eg., extract_name) requires MAXDNAME (1024).
*
* With "canonicalized wire format", we mean the standard DNS wire format, eg:
*
* <3>www<7>example<3>org<0>
*
* with all ÅSCII letters converted to lowercase, and no wire-level compression.
*
* The function works with two different buffers:
* - Input buffer: 'rdata' is a pointer to the actual wire data, and 'rdlen' is
* the total length till the end of the rdata or DNS packet section. Both
* variables are updated after processing the domain name, so that rdata points
* after it, and rdlen is decreased by the amount of the processed octects.
* - Output buffer: 'out' points to it. In some cases, this buffer can be prefilled
* and used as additional input (see below).
*
* The argument "action" decides what to do with the submitted domain name:
*
* PDN_EXTRACT:
* Extract the domain name from input buffer into the output buffer, possibly uncompressing it.
* Return the length of the domain name in the output buffer in octects, or zero if error.
*
* PDN_COMPARE:
* Compare the domain name in the input buffer and the one in the output buffer (ignoring
* differences in compression). Returns 0 in case of error, a positive number
* if they are equal, or a negative number if they are different. This function always
* consumes the whole name in the input buffer (there is no early exit).
*
* PDN_ORDER:
* Order between the domain name in the input buffer and the domain name in the output buffer.
* Returns 0 if the names are equal, 1 if input > output, or -1 if input < output. This
* function early-exits when it finds a difference, so rdata might not be fully updated.
*
* Notice: because of compression, rdata/rdlen might be updated with a different quantity than
* the returned number of octects. For instance, if we extract a compressed domain name, rdata/rdlen
* might be updated only by 2 bytes (that is, rdata is incresed by 2, and rdlen decreased by 2),
* because it then reuses existing data elsewhere in the DNS packet, while the return value might be
* larger, reflecting the total number of octects composing the domain name.
*
*/
#define PWN_EXTRACT 0
#define PWN_COMPARE 1
#define PWN_ORDER 2
static int process_domain_name(struct dns_header *header, size_t pktlen,
unsigned char** rdata, size_t* rdlen,
unsigned char *out, int action)
{
int hops = 0, total = 0, i;
unsigned char label_type;
unsigned char *end = (unsigned char *)header + pktlen;
unsigned char count; unsigned char *p = *rdata;
int nonequal = 0;
#define PROCESS(ch) \
do { \
if (action == PWN_EXTRACT) \
*out++ = ch; \
else if (action == PWN_COMPARE) \
{ \
if (*out++ != ch) \
nonequal = 1; \
} \
else if (action == PWN_ORDER) \
{ \
char _ch = *out++; \
if (ch < _ch) \
return -1; \
else if (_ch > ch) \
return 1; \
} \
} while (0)
while (1)
{
if (p >= end)
return 0;
if (!(count = *p++))
break;
label_type = count & 0xC0;
if (label_type == 0xC0)
{
int l2;
if (p >= end)
return 0;
l2 = *p++;
if (hops == 0)
{
if (p - *rdata > *rdlen)
return 0;
*rdlen -= p - *rdata;
*rdata = p;
}
if (++hops == 256)
return 0;
p = (unsigned char*)header + (count & 0x3F) * 256 + l2;
}
else if (label_type == 0x00)
{
if (p+count-1 >= end)
return 0;
total += count+1;
if (total >= MAXCDNAME)
return 0;
PROCESS(count);
for (i = 0; i < count; ++i)
{
unsigned char ch = *p++;
if (ch >= 'A' && ch <= 'Z')
ch += 'a' - 'A';
PROCESS(ch);
}
}
else
return 0; /* unsupported label_type */
}
if (hops == 0)
{
if (p - *rdata > *rdlen)
return 0;
*rdlen -= p - *rdata;
*rdata = p;
}
++total;
if (total >= MAXCDNAME)
return 0;
PROCESS(0);
/* If we arrived here without early-exit, they're equal */
if (action == PWN_ORDER)
return 0;
return nonequal ? -total : total;
#undef PROCESS
}
/* RDATA meta-description.
*
* RFC4034 §6.2 introduces the concept of a "canonical form of a RR". This canonical
* form is used in two important points within the DNSSEC protocol/algorithm:
*
* 1) When computing the hash for verifying the RRSIG signature, we need to do it on
* the canonical form.
* 2) When ordering a RRset in canonical order (§6.3), we need to lexicographically sort
* the RRs in canonical form.
*
* The canonical form of a RR is specifically tricky because it also affects the RDATA,
* which is different for each RR type; in fact, RFC4034 says that "domain names in
* RDATA must be canonicalized" (= uncompressed and lower-cased).
*
* To handle this correctly, we then need a way to describe how the RDATA section is
* composed for each RR type; we don't need to describe every field, but just to specify
* where domain names are. The following array contains this description, and it is
* used by rrset_canonical_order() and verifyalg_add_rdata(), to adjust their behaviour
* for each RR type.
*
* The format of the description is very easy, for instance:
*
* { 12, RDESC_DOMAIN, RDESC_DOMAIN, 4, RDESC_DOMAIN, RDESC_END }
*
* This means that this (ficticious) RR type has a RDATA section containing 12 octects
* (we don't care what they contain), followed by 2 domain names, followed by 4 octects,
* followed by 1 domain name, and then followed by an unspecificied number of octects (0
* or more).
*/
#define RDESC_DOMAIN -1
#define RDESC_END 0
static const int rdata_description[][8] =
{
/**/ { RDESC_END },
/* 1: A */ { RDESC_END },
/* 2: NS */ { RDESC_DOMAIN, RDESC_END },
/* 3: .. */ { RDESC_END },
/* 4: .. */ { RDESC_END },
/* 5: CNAME */ { RDESC_DOMAIN, RDESC_END },
/* 6: SOA */ { RDESC_DOMAIN, RDESC_DOMAIN, RDESC_END },
/* 7: */ { RDESC_END },
/* 8: */ { RDESC_END },
/* 9: */ { RDESC_END },
/* 10: */ { RDESC_END },
/* 11: */ { RDESC_END },
/* 12: */ { RDESC_END },
/* 13: */ { RDESC_END },
/* 14: */ { RDESC_END },
/* 15: MX */ { 2, RDESC_DOMAIN, RDESC_END },
};
/* On-the-fly rdata canonicalization
*
* This set of functions allow the user to iterate over the rdata section of a RR
* while canonicalizing it on-the-fly. This is a great memory saving since the user
* doesn't need to allocate memory for a copy of the whole rdata section.
*
* Sample usage:
*
* RDataCFrom cf;
* rdata_cfrom_init(
* &cf,
* header, pktlen, // dns_header
* rdata, // pointer to rdata section
* rrtype, // RR tyep
* tmpbuf); // temporary buf (MAXCDNAME)
*
* while ((p = rdata_cfrom_next(&cf, &len))
* {
* // Process p[0..len]
* }
*
* if (rdata_cfrom_error(&cf))
* // error occurred while parsing
*
*/
typedef struct
{
struct dns_header *header;
size_t pktlen;
unsigned char *rdata;
unsigned char *tmpbuf;
size_t rdlen;
int rrtype;
int cnt;
} RDataCForm;
static void rdata_cform_init(RDataCForm *ctx, struct dns_header *header, size_t pktlen,
unsigned char *rdata, int rrtype, unsigned char *tmpbuf)
{
if (rrtype >= countof(rdata_description))
rrtype = 0;
ctx->header = header;
ctx->pktlen = pktlen;
ctx->rdata = rdata;
ctx->rrtype = rrtype;
ctx->tmpbuf = tmpbuf;
ctx->cnt = -1;
GETSHORT(ctx->rdlen, ctx->rdata);
}
static int rdata_cform_error(RDataCForm *ctx)
{
return ctx->cnt == -2;
}
static unsigned char *rdata_cform_next(RDataCForm *ctx, size_t *len)
{
if (ctx->cnt != -1 && rdata_description[ctx->rrtype][ctx->cnt] == RDESC_END)
return NULL;
int d = rdata_description[ctx->rrtype][++ctx->cnt];
if (d == RDESC_DOMAIN)
{
*len = process_domain_name(ctx->header, ctx->pktlen, &ctx->rdata, &ctx->rdlen, ctx->tmpbuf, PWN_EXTRACT);
if (!*len)
{
ctx->cnt = -2;
return NULL;
}
return ctx->tmpbuf;
}
else if (d == RDESC_END)
{
*len = ctx->rdlen;
return ctx->rdata;
}
else
{
unsigned char *ret = ctx->rdata;
ctx->rdlen -= d;
ctx->rdata += d;
*len = d;
return ret;
}
}
/* Check whether today/now is between date_start and date_end */
static int check_date_range(unsigned long date_start, unsigned long date_end)
{
/* TODO: double-check that time(0) is the correct time we are looking for */
/* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
unsigned long curtime = time(0);
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */
return serial_compare_32(curtime, date_start) == SERIAL_GT
&& serial_compare_32(curtime, date_end) == SERIAL_LT;
}
/* Sort RRs within a RRset in canonical order, according to RFC4034, §6.3
Notice that the RRDATA sections have been already normalized, so a memcpy
is sufficient.
NOTE: r1/r2 point immediately after the owner name. */
struct {
struct dns_header *header;
size_t pktlen;
} rrset_canonical_order_ctx;
static int rrset_canonical_order(const void *r1, const void *r2)
{
size_t r1len, r2len;
int rrtype;
unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
unsigned char tmp1[MAXCDNAME], tmp2[MAXCDNAME]; /* TODO: use part of daemon->namebuff */
GETSHORT(rrtype, pr1);
pr1 += 6; pr2 += 8;
RDataCForm cf1, cf2;
rdata_cform_init(&cf1, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen,
pr1, rrtype, tmp1);
rdata_cform_init(&cf2, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen,
pr2, rrtype, tmp2);
while ((pr1 = rdata_cform_next(&cf1, &r1len)) &&
(pr2 = rdata_cform_next(&cf2, &r2len)))
{
int res = memcmp(pr1, pr2, MIN(r1len,r2len));
if (res != 0)
return res;
if (r1len < r2len)
return -1;
if (r2len > r1len)
return 1;
}
/* If we reached this point, the two RRs are identical (or an error occurred).
RFC2181 says that an RRset is not allowed to contain duplicate
records. If it happens, it is a protocol error and anything goes. */
return 1;
}
typedef struct PendingRRSIGValidation
{
VerifyAlgCtx *alg;
char *signer_name;
int keytag;
} PendingRRSIGValidation;
/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0.
This is an existing C GNU extension, but it's easier to reimplement it,
rather than tweaking with configure. */
static char *strchrnul(char *str, char ch)
{
while (*str && *str != ch)
str++;
return str;
}
/* Convert a domain name to wire format */
static int convert_domain_to_wire(char *name, unsigned char* out)
{
unsigned char len;
unsigned char *start = out;
char *p;
do
{
p = strchrnul(name, '.');
if ((len = p-name))
{
*out++ = len;
while (len--)
{
char ch = *name++;
/* TODO: this will not be required anymore once we
remove all usages of extract_name() from DNSSEC code */
if (ch >= 'A' && ch <= 'Z')
ch = ch - 'A' + 'a';
*out++ = ch;
}
}
name = p+1;
}
while (*p);
*out++ = '\0';
return out-start;
}
/* Pass a resource record's rdata field through the currently-initailized digest algorithm.
We must pass the record in DNS wire format, but if the record contains domain names,
they must be uncompressed. This makes things very tricky, because */
static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pktlen,
unsigned char *rdata)
{
size_t len;
unsigned char *p;
unsigned short total;
unsigned char tmpbuf[MAXDNAME]; /* TODO: reuse part of daemon->namebuff */
RDataCForm cf1, cf2;
/* Initialize two iterations over the canonical form*/
rdata_cform_init(&cf1, header, pktlen, rdata, sigtype, tmpbuf);
cf2 = cf1;
/* Iteration 1: go through the canonical record and count the total octects.
This number might be different from the non-canonical rdata length
because of domain names compression. */
total = 0;
while ((p = rdata_cform_next(&cf1, &len)))
total += len;
if (rdata_cform_error(&cf1))
return 0;
/* Iteration 2: process the canonical record through the hash function */
total = htons(total);
digestalg_add_data(&total, 2);
while ((p = rdata_cform_next(&cf2, &len)))
digestalg_add_data(p, len);
return 1;
}
static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig,
PendingRRSIGValidation *out)
{
int i, res;
int sigtype, sigalg, siglbl;
unsigned char *sigrdata = sig;
unsigned long sigttl, date_end, date_start;
unsigned char* p = reply;
char* signer_name = daemon->namebuff;
int signer_name_rdlen;
int keytag;
void *rrset[16]; /* TODO: max RRset size? */
int rrsetidx = 0;
if (sigrdlen < 18)
return 0;
GETSHORT(sigtype, sig);
sigalg = *sig++;
siglbl = *sig++;
GETLONG(sigttl, sig);
GETLONG(date_end, sig);
GETLONG(date_start, sig);
GETSHORT(keytag, sig);
sigrdlen -= 18;
if (!verifyalg_supported(sigalg))
{
printf("RRSIG algorithm not supported: %d\n", sigalg);
return 0;
}
if (!check_date_range(date_start, date_end))
{
printf("RRSIG outside date range\n");
return 0;
}
/* Iterate within the answer and find the RRsets matching the current RRsig */
for (i = 0; i < count; ++i)
{
int qtype, qclass, rdlen;
if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
return 0;
rrset[rrsetidx] = p;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
p += 4; /* skip ttl */
GETSHORT(rdlen, p);
if (res == 1 && qtype == sigtype && qclass == sigclass)
{
++rrsetidx;
if (rrsetidx == countof(rrset))
{
/* Internal buffer too small */
printf("internal buffer too small for this RRset\n");
return 0;
}
}
p += rdlen;
}
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = pktlen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Skip through the signer name; we don't extract it right now because
we don't want to overwrite the single daemon->namebuff which contains
the owner name. We'll get to this later. */
if (!(p = skip_name(sig, header, pktlen, 0)))
return 0;
signer_name_rdlen = p - sig;
sig = p; sigrdlen -= signer_name_rdlen;
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
if (!alg)
return 0;
alg->sig = sig;
alg->siglen = sigrdlen;
sigtype = htons(sigtype);
sigclass = htons(sigclass);
sigttl = htonl(sigttl);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned char owner_wire[MAXCDNAME];
int owner_wire_len = convert_domain_to_wire(owner, owner_wire);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
{
p = (unsigned char*)(rrset[i]);
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&sigtype, 2);
digestalg_add_data(&sigclass, 2);
digestalg_add_data(&sigttl, 4);
p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
return 0;
}
int digest_len = digestalg_len();
memcpy(alg->digest, digestalg_final(), digest_len);
/* We don't need the owner name anymore; now extract the signer name */
if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name))
return 0;
out->alg = alg;
out->keytag = keytag;
out->signer_name = signer_name;
return 1;
}
static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
{
/* FIXME: keydata is non-contiguous */
return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
}
static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig)
{
PendingRRSIGValidation val;
/* Initiate the RRSIG validation process. The pending state is returned into val. */
if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
return;
printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
/* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
char onekey = 0;
struct crec *crecp = NULL;
while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */
{
onekey = 1;
if (crecp->addr.key.keytag == val.keytag
&& crecp->addr.key.algo == verifyalg_algonum(val.alg))
{
printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
if (end_rrsig_validation(&val, crecp))
printf("Validation OK\n");
else
printf("Validation FAILED\n");
}
}
if (!onekey)
{
printf("DNSKEY not found, need to fetch it\n");
/* TODO: store PendingRRSIGValidation in routing table,
fetch key (and make it go through dnssec_parskey), then complete validation. */
}
}
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen)
{
if (alg == 1)
{
/* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
See RFC4034, Appendix B.1 */
return rdata[rdlen-3] * 256 + rdata[rdlen-2];
}
else
{
unsigned long ac;
int i;
ac = 0;
for (i = 0; i < rdlen; ++i)
ac += (i & 1) ? rdata[i] : rdata[i] << 8;
ac += (ac >> 16) & 0xFFFF;
return ac & 0xFFFF;
}
}
/* Check if the DS record (from cache) points to the DNSKEY record (from cache) */
static int dnskey_ds_match(struct crec *dnskey, struct crec *ds)
{
if (dnskey->addr.key.keytag != ds->addr.key.keytag)
return 0;
if (dnskey->addr.key.algo != ds->addr.key.algo)
return 0;
unsigned char owner[MAXCDNAME]; /* TODO: user part of daemon->namebuff */
int owner_len = convert_domain_to_wire(cache_get_name(ds), owner);
size_t keylen = dnskey->uid;
int dig = ds->uid;
int digsize;
if (!digestalg_begin(dig))
return 0;
digsize = digestalg_len();
digestalg_add_data(owner, owner_len);
digestalg_add_data("\x01\x01\x03", 3);
digestalg_add_data(&ds->addr.key.algo, 1);
digestalg_add_keydata(dnskey->addr.key.keydata, keylen);
return (memcmp(digestalg_final(), ds->addr.key.keydata->key, digsize) == 0);
}
int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
int rdlen, unsigned char *rdata)
{
int flags, proto, alg;
struct keydata *key; struct crec *crecp;
unsigned char *ordata = rdata; int ordlen = rdlen;
CHECKED_GETSHORT(flags, rdata, rdlen);
CHECKED_GETCHAR(proto, rdata, rdlen);
CHECKED_GETCHAR(alg, rdata, rdlen);
if (proto != 3)
return 0;
/* Skip non-signing keys (as specified in RFC4034 */
if (!(flags & 0x100))
return 0;
key = keydata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
if (crecp)
{
/* TODO: improve union not to name "uid" this field */
crecp->uid = rdlen;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = alg;
crecp->addr.key.keytag = dnskey_keytag(alg, ordata, ordlen);
printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
}
else
{
keydata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DNSKEY: cache insertion failure\n");
return 0;
}
return 1;
}
int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
int rdlen, unsigned char *rdata)
{
int keytag, algo, dig;
struct keydata *key; struct crec *crec_ds, *crec_key;
CHECKED_GETSHORT(keytag, rdata, rdlen);
CHECKED_GETCHAR(algo, rdata, rdlen);
CHECKED_GETCHAR(dig, rdata, rdlen);
if (!digestalg_supported(dig))
return 0;
key = keydata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crec_ds = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DS);
if (!crec_ds)
{
keydata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DS: cache insertion failure\n");
return 0;
}
/* TODO: improve union not to name "uid" this field */
crec_ds->uid = dig;
crec_ds->addr.key.keydata = key;
crec_ds->addr.key.algo = algo;
crec_ds->addr.key.keytag = keytag;
printf("DS: storing key for %s (digest: %d)\n", owner, dig);
/* Now try to find a DNSKEY which matches this DS digest. */
printf("Looking for a DNSKEY matching DS %d...\n", keytag);
crec_key = NULL;
while ((crec_key = cache_find_by_name(crec_key, owner, time(0), F_DNSKEY))) /* TODO: time(0) */
{
if (dnskey_ds_match(crec_key, crec_ds))
{
/* TODO: create a link within the cache: ds => dnskey */
printf("MATCH FOUND for keytag %d\n", keytag);
}
}
return 0;
}
int dnssec_validate(struct dns_header *header, size_t pktlen)
{
unsigned char *p, *reply;
char *owner = daemon->namebuff;
int i, s, qtype, qclass, rdlen;
unsigned long ttl;
int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) };
if (slen[0] + slen[1] + slen[2] == 0)
return 0;
if (!(reply = p = skip_questions(header, pktlen)))
return 0;
/* First, process DNSKEY/DS records and add them to the cache. */
cache_start_insert();
for (i = 0; i < slen[0]; i++)
{
if (!extract_name(header, pktlen, &p, owner, 1, 10))
return 0;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qtype == T_DS)
{
printf("DS found\n");
dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
}
else if (qtype == T_DNSKEY)
{
printf("DNSKEY found\n");
dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
}
p += rdlen;
}
cache_end_insert();
/* After we have cached DNSKEY/DS records, start looking for RRSIGs.
We want to do this in a separate step because we want the cache
to be already populated with DNSKEYs before parsing signatures. */
p = reply;
for (s = 0; s < 3; ++s)
{
reply = p;
for (i = 0; i < slen[s]; i++)
{
if (!extract_name(header, pktlen, &p, owner, 1, 10))
return 0;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qtype == T_RRSIG)
{
printf("RRSIG found (owner: %s)\n", owner);
/* TODO: missing logic. We should only validate RRSIGs for which we
have a valid DNSKEY that is referenced by a DS record upstream.
There is a memory vs CPU conflict here; should we validate everything
to save memory and thus waste CPU, or better first acquire all information
(wasting memory) and then doing the minimum CPU computations required? */
dnssec_parserrsig(header, pktlen, reply, slen[s], owner, qclass, rdlen, p);
}
p += rdlen;
}
}
return 1;
}