blob: 712696fd18ecd9ca04773e44bb088ac7ff462a75 [file] [log] [blame]
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h"
#define SERIAL_UNDEF -100
#define SERIAL_EQ 0
#define SERIAL_LT -1
#define SERIAL_GT 1
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
then calling to_wire() removes compression and maps case,
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
*/
static int to_wire(char *name)
{
unsigned char *l, *p, term;
int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
for (p = l; *p != '.' && *p != 0; p++)
if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
term = *p;
if ((len = p - l) != 0)
memmove(l+1, l, len);
*l = len;
p++;
if (term == 0)
*p = 0;
}
return l + 1 - (unsigned char *)name;
}
/* Note: no compression allowed in input. */
static void from_wire(char *name)
{
unsigned char *l;
int len;
for (l = (unsigned char *)name; *l != 0; l += len+1)
{
len = *l;
memmove(l, l+1, len);
l[len] = '.';
}
*(l-1) = 0;
}
/* Input in presentation format */
static int count_labels(char *name)
{
int i;
if (*name == 0)
return 0;
for (i = 0; *name; name++)
if (*name == '.')
i++;
return i+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;
}
/* Check whether today/now is between date_start and date_end */
static int check_date_range(unsigned long date_start, unsigned long date_end)
{
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;
}
static u16 *get_desc(int type)
{
/* List of RRtypes which include domains in the data.
0 -> domain
integer -> no of plain bytes
-1 -> end
zero is not a valid RRtype, so the final entry is returned for
anything which needs no mangling.
*/
static u16 rr_desc[] =
{
T_NS, 0, -1,
T_MD, 0, -1,
T_MF, 0, -1,
T_CNAME, 0, -1,
T_SOA, 0, 0, -1,
T_MB, 0, -1,
T_MG, 0, -1,
T_MR, 0, -1,
T_PTR, 0, -1,
T_MINFO, 0, 0, -1,
T_MX, 2, 0, -1,
T_RP, 0, 0, -1,
T_AFSDB, 2, 0, -1,
T_RT, 2, 0, -1,
T_SIG, 18, 0, -1,
T_PX, 2, 0, 0, -1,
T_NXT, 0, -1,
T_KX, 2, 0, -1,
T_SRV, 6, 0, -1,
T_DNAME, 0, -1,
T_RRSIG, 18, 0, -1,
T_NSEC, 0, -1,
0, -1 /* wildcard/catchall */
};
u16 *p = rr_desc;
while (*p != type && *p != 0)
while (*p++ != (u16)-1);
return p+1;
}
/* Return bytes of canonicalised rdata, when the return value is zero, the remaining
data, pointed to by *p, should be used raw. */
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff,
unsigned char **p, u16 **desc)
{
int d = **desc;
(*desc)++;
/* No more data needs mangling */
if (d == (u16)-1)
return 0;
if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
/* domain-name, canonicalise */
return to_wire(buff);
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((end - *p) < d)
d = end - *p;
if (d != 0)
{
memcpy(buff, *p, d);
*p += d;
}
return d;
}
}
/* Bubble sort the RRset into the canonical order.
Note that the byte-streams from two RRs may get unsynced: consider
RRs which have two domain-names at the start and then other data.
The domain-names may have different lengths in each RR, but sort equal
------------
|abcde|fghi|
------------
|abcd|efghi|
------------
leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
*/
static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
unsigned char **rrset, char *buff1, char *buff2)
{
int swap, quit, i;
do
{
for (swap = 0, i = 0; i < rrsetidx-1; i++)
{
int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
u16 *dp1, *dp2;
unsigned char *end1, *end2;
unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
p1 += 8; /* skip class, type, ttl */
GETSHORT(rdlen1, p1);
end1 = p1 + rdlen1;
p2 += 8; /* skip class, type, ttl */
GETSHORT(rdlen2, p2);
end2 = p2 + rdlen2;
dp1 = dp2 = rr_desc;
for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
{
if (left1 != 0)
memmove(buff1, buff1 + len1 - left1, left1);
if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0)
{
quit = 1;
len1 = end1 - p1;
memcpy(buff1 + left1, p1, len1);
}
len1 += left1;
if (left2 != 0)
memmove(buff2, buff2 + len2 - left2, left2);
if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0)
{
quit = 1;
len2 = end2 - p2;
memcpy(buff2 + left2, p2, len2);
}
len2 += left2;
if (len1 > len2)
left1 = len1 - len2, left2 = 0, len = len2;
else
left2 = len2 - len1, left1 = 0, len = len1;
rc = memcmp(buff1, buff2, len);
if (rc == 1 || (rc == 0 && quit && len1 > len2))
{
unsigned char *tmp = rrset[i+1];
rrset[i+1] = rrset[i];
rrset[i] = tmp;
swap = quit = 1;
}
}
}
} while (swap);
}
/* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code:
STAT_SECURE if it validates.
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
otherwise find the key in the cache.
*/
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
{
static unsigned char **rrset = NULL, **sigs = NULL;
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 (!(p = skip_questions(header, plen)))
return STAT_INSECURE;
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;
pstart = p;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(stype, p);
GETSHORT(sclass, p);
p += 4; /* TTL */
pdata = p;
GETSHORT(rdlen, p);
if (res == 1 && sclass == class)
{
if (stype == type)
{
if (rrsetidx == rrset_sz)
{
unsigned char **new;
/* expand */
if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
return STAT_INSECURE;
if (rrset)
{
memcpy(new, rrset, rrset_sz * sizeof(unsigned char **));
free(rrset);
}
rrset = new;
rrset_sz += 5;
}
rrset[rrsetidx++] = pstart;
}
if (stype == T_RRSIG)
{
if (rdlen < 18)
return STAT_INSECURE; /* bad packet */
GETSHORT(type_covered, p);
algo = *p++;
labels = *p++;
p += 4; /* orig_ttl */
GETLONG(sig_expiration, p);
GETLONG(sig_inception, p);
p = pdata + 2; /* restore for ADD_RDLEN */
if (type_covered == type &&
check_date_range(sig_inception, sig_expiration) &&
verifyalg_supported(algo) &&
labels <= name_labels)
{
if (sigidx == sig_sz)
{
unsigned char **new;
/* expand */
if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **))))
return STAT_INSECURE;
if (sigs)
{
memcpy(new, sigs, sig_sz * sizeof(unsigned char **));
free(sigs);
}
sigs = new;
sig_sz += 5;
}
sigs[sigidx++] = pdata;
}
}
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
/* RRset empty, no RRSIGs */
if (rrsetidx == 0 || sigidx == 0)
return STAT_INSECURE;
/* Sort RRset records into canonical order.
Note that at this point keyname and name buffs are
unused, and used as workspace by the sort. */
sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname);
/* Now try all the sigs to try and find one which validates */
for (j = 0; j <sigidx; j++)
{
unsigned char *psav;
int i, wire_len;
VerifyAlgCtx *alg;
u32 nsigttl;
p = sigs[j];
GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
psav = p;
p += 2; /* type_covered - already checked */
algo = *p++;
labels = *p++;
GETLONG(orig_ttl, p);
p += 8; /* sig_expiration and sig_inception */
GETSHORT(key_tag, p);
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_INSECURE;
/* 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;
alg = verifyalg_alloc(algo);
alg->sig = p;
alg->siglen = rdlen - (p - psav);
nsigttl = htonl(orig_ttl);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(psav, 18);
wire_len = to_wire(keyname);
digestalg_add_data(keyname, wire_len);
from_wire(keyname);
for (i = 0; i < rrsetidx; ++i)
{
int seg;
unsigned char *end, *cp;
char *name_start = name;
u16 len, *dp;
p = rrset[i];
if (!extract_name(header, plen, &p, name, 1, 10))
return STAT_INSECURE;
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
if (labels < name_labels)
{
int k;
for (k = name_labels - labels; k != 0; k--)
while (*name_start != '.' && *name_start != 0)
name_start++;
name_start--;
*name_start = '*';
}
wire_len = to_wire(name_start);
digestalg_add_data(name_start, wire_len);
digestalg_add_data(p, 4); /* class and type */
digestalg_add_data(&nsigttl, 4);
p += 8; /* skip class, type, ttl */
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE;
end = p + rdlen;
/* canonicalise rdata and calculate length of same, use name buffer as workspace */
cp = p;
dp = rr_desc;
for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg);
len += end - cp;
len = htons(len);
digestalg_add_data(&len, 2);
/* Now canonicalise again and digest. */
cp = p;
dp = rr_desc;
while ((seg = get_rdata(header, plen, end, name, &cp, &dp)))
digestalg_add_data(name, seg);
if (cp != end)
digestalg_add_data(cp, end - cp);
}
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
extract_name(header, plen, &p, name, 1, 0);
memcpy(alg->digest, digestalg_final(), digestalg_len());
if (key)
{
if (algo_in == algo && keytag_in == key_tag &&
alg->vtbl->verify(alg, key, keylen))
return STAT_SECURE;
}
else
{
/* iterate through all possible keys 4035 5.3.1 */
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag &&
alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid))
return STAT_SECURE;
}
}
return STAT_BOGUS;
}
/* The DNS packet is expected to contain the answer to a DNSKEY query.
Leave name of query in name.
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
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.
STAT_NEED_DS DS 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)
{
unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1;
int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
struct blockdata *key;
if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, 1, 4))
{
strcpy(name, "<none>");
return STAT_INSECURE;
}
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE;
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
{
strcpy(keyname, name);
return STAT_NEED_DS;
}
cache_start_insert();
/* NOTE, we need to find ONE DNSKEY which matches the DS */
for (valid = 0, j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qclass != class || qtype != T_DNSKEY || rc == 2)
{
if (ADD_RDLEN(header, p, plen, rdlen))
continue;
return STAT_INSECURE; /* bad packet */
}
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
return STAT_INSECURE; /* bad packet */
psave = p;
/* length at least covers flags, protocol and algo now. */
GETSHORT(flags, p);
if (*p++ != 3)
return STAT_INSECURE;
algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
/* Put the key into the cache. Note that if the validation fails, we won't
call cache_end_insert() and this will never be committed. */
if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
(recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
{
recp1->uid = rdlen - 4;
recp1->addr.key.keydata = key;
recp1->addr.key.algo = algo;
recp1->addr.key.keytag = keytag;
}
p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
/* Already determined that message is OK. Just loop stuffing cache */
if (valid || !key)
continue;
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
if (recp1->addr.key.algo == algo &&
recp1->addr.key.keytag == keytag &&
(flags & 0x100) && /* zone key flag */
digestalg_supported(recp1->addr.key.digest))
{
int wire_len = to_wire(name);
digestalg_begin(recp1->addr.key.digest);
digestalg_add_data(name, wire_len);
digestalg_add_data((char *)psave, rdlen);
from_wire(name);
if (recp1->uid == digestalg_len() &&
blockdata_retrieve(recp1->addr.key.keydata, recp1->uid, digestalg_final()) &&
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag))
{
struct all_addr a;
valid = 1;
a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
break;
}
}
}
if (valid)
{
/* commit cache insert. */
cache_end_insert();
return STAT_SECURE;
}
log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
return STAT_BOGUS;
}
/* The DNS packet is expected to contain the answer to a DS query
Leave name of DS query in name.
Put all DSs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DS in reply.
STAT_SECURE At least one valid DS found and in cache.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
*/
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp;
int qtype, qclass, val, j, gotone;
struct blockdata *key;
if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, 1, 4))
{
strcpy(name, "<none>");
return STAT_INSECURE;
}
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE;
val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0);
if (val == STAT_BOGUS)
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
/* failed to validate or missing key. */
if (val != STAT_SECURE)
return val;
cache_start_insert();
for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
{
int ttl, rdlen, rc, algo, digest, keytag;
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
/* check type, class and name, skip if not in DS rrset */
if (qclass == class && qtype == T_DS && rc == 1)
{
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
return STAT_INSECURE; /* bad packet */
psave = p;
GETSHORT(keytag, p);
algo = *p++;
digest = *p++;
/* We've proved that the DS is OK, store it in the cache */
if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
(crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
{
struct all_addr a;
a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
crecp->addr.key.digest = digest;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = algo;
crecp->addr.key.keytag = keytag;
crecp->uid = rdlen - 4;
}
else
return STAT_INSECURE; /* cache problem */
p = psave;
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
}
cache_end_insert();
return STAT_SECURE;
}
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
{
unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1, type2, class2, rdlen2;
int i, j, rc;
if (!(ans_start = skip_questions(header, plen)))
return STAT_INSECURE;
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
if (!extract_name(header, plen, &p1, name, 1, 10))
return STAT_INSECURE; /* bad packet */
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
/* Don't try and validate RRSIGs! */
if (type1 != T_RRSIG)
{
/* Check if we've done this RRset already */
for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(type2, p2);
GETSHORT(class2, p2);
p2 += 4; /* TTL */
GETSHORT(rdlen2, p2);
if (type2 == type1 && class2 == class1 && rc == 1)
break; /* Done it before: name, type, class all match. */
if (!ADD_RDLEN(header, p2, plen, rdlen2))
return STAT_INSECURE;
}
/* Not done, validate now */
if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
{
*class = class1; /* Class for DS or DNSKEY */
return rc;
}
}
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_INSECURE;
}
return STAT_SECURE;
}
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
{
if (alg == 1)
{
/* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
See RFC4034, Appendix B.1 */
return key[keylen-4] * 256 + key[keylen-3];
}
else
{
unsigned long ac;
int i;
ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
for (i = 0; i < keylen; ++i)
ac += (i & 1) ? key[i] : key[i] << 8;
ac += (ac >> 16) & 0xffff;
return ac & 0xffff;
}
}
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
{
unsigned char *p;
char types[20];
querystr("dnssec", types, type);
if (addr->sa.sa_family == AF_INET)
log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
#ifdef HAVE_IPV6
else
log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);
#endif
header->qdcount = htons(1);
header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
header->hb3 = HB3_RD;
SET_OPCODE(header, QUERY);
header->hb4 = HB4_CD;
/* ID filled in later */
p = (unsigned char *)(header+1);
p = do_rfc1035_name(p, name);
*p++ = 0;
PUTSHORT(type, p);
PUTSHORT(class, p);
return add_do_bit(header, p - (unsigned char *)header, end);
}
#endif /* HAVE_DNSSEC */