bind9/lib/dns/message.c
David Lawrence 6d12fdf966 Megacommit of many files.
Mostly, several functions that take pointers as arguments, almost
always char * pointers, had those pointers qualified with "const".
Those that returned pointers to previously const-qualified arguments
had their return values qualified as const.  Some structure members
were qualified as const to retain that attribute from the variables
from which they were assigned.

The macro DE_CONST is used to deal with a handful of very special
places where something is qualified as const but really needs to have
its const qualifier removed.

rdata.c now defines macros for the prototypes of the basic rdata functions,
and all of the lib/dns/rdata/**/*.c files now use them.

Some minor integer-compatibility issues.  (IE, ~0x03 is a signed int,
so assigning it to an unsigned int should use a cast.  The type of an
enum member is int, so there are some conversion issues there, too.)

A pointers-to-function should not be cast to a pointer-to-object.

Variables should not be named for C reserved identifiers.

One or two set-but-not-used variables removed.

Minor other ISC style cleanups.
2000-06-01 18:26:56 +00:00

2893 lines
71 KiB
C

/*
* Copyright (C) 1999, 2000 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*/
/***
*** Imports
***/
#include <config.h>
#include <isc/mem.h>
#include <isc/string.h> /* Required for HP/UX (and others?) */
#include <isc/util.h>
#include <isc/buffer.h>
#include <dns/dnssec.h>
#include <dns/keyvalues.h>
#include <dns/message.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#include <dns/result.h>
#include <dns/tsig.h>
#include <dns/view.h>
#define DNS_MESSAGE_OPCODE_MASK 0x7800U
#define DNS_MESSAGE_OPCODE_SHIFT 11
#define DNS_MESSAGE_RCODE_MASK 0x000fU
#define DNS_MESSAGE_FLAG_MASK 0x8ff0U
#define DNS_MESSAGE_EDNSRCODE_MASK 0xff000000U
#define DNS_MESSAGE_EDNSRCODE_SHIFT 24
#define DNS_MESSAGE_EDNSVERSION_MASK 0x00ff0000U
#define DNS_MESSAGE_EDNSVERSION_SHIFT 16
#define VALID_NAMED_SECTION(s) (((s) > DNS_SECTION_ANY) \
&& ((s) < DNS_SECTION_MAX))
#define VALID_SECTION(s) (((s) >= DNS_SECTION_ANY) \
&& ((s) < DNS_SECTION_MAX))
#define ADD_STRING(b, s) {if (strlen(s) >= \
isc_buffer_availablelength(b)) \
return(ISC_R_NOSPACE); else \
isc_buffer_putstr(b, s);}
#define VALID_PSEUDOSECTION(s) (((s) >= DNS_PSEUDOSECTION_ANY) \
&& ((s) < DNS_PSEUDOSECTION_MAX))
/*
* This is the size of each individual scratchpad buffer, and the numbers
* of various block allocations used within the server.
* XXXMLG These should come from a config setting.
*/
#define SCRATCHPAD_SIZE 512
#define NAME_COUNT 8
#define RDATA_COUNT 8
#define RDATALIST_COUNT 8
#define RDATASET_COUNT RDATALIST_COUNT
/*
* Text representation of the different items, for message_totext
* functions.
*/
static const char *sectiontext[] = {
"QUESTION",
"ANSWER",
"AUTHORITY",
"ADDITIONAL"
};
static const char *opcodetext[] = {
"QUERY",
"IQUERY",
"STATUS",
"RESERVED3",
"NOTIFY",
"UPDATE",
"RESERVED6",
"RESERVED7",
"RESERVED8",
"RESERVED9",
"RESERVED10",
"RESERVED11",
"RESERVED12",
"RESERVED13",
"RESERVED14",
"RESERVED15"
};
static const char *rcodetext[] = {
"NOERROR",
"FORMERR",
"SERVFAIL",
"NXDOMAIN",
"NOTIMPL",
"REFUSED",
"YXDOMAIN",
"YXRRSET",
"NXRRSET",
"NOTAUTH",
"NOTZONE",
"RESERVED11",
"RESERVED12",
"RESERVED13",
"RESERVED14",
"RESERVED15",
"BADVERS"
};
/*
* "helper" type, which consists of a block of some type, and is linkable.
* For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer
* size, or the allocated elements will not be alligned correctly.
*/
struct dns_msgblock {
unsigned int count;
unsigned int remaining;
ISC_LINK(dns_msgblock_t) link;
}; /* dynamically sized */
static inline dns_msgblock_t *
msgblock_allocate(isc_mem_t *, unsigned int, unsigned int);
#define msgblock_get(block, type) \
((type *)msgblock_internalget(block, sizeof(type)))
static inline void *
msgblock_internalget(dns_msgblock_t *, unsigned int);
static inline void
msgblock_reset(dns_msgblock_t *);
static inline void
msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int);
/*
* Allocate a new dns_msgblock_t, and return a pointer to it. If no memory
* is free, return NULL.
*/
static inline dns_msgblock_t *
msgblock_allocate(isc_mem_t *mctx, unsigned int sizeof_type,
unsigned int count)
{
dns_msgblock_t *block;
unsigned int length;
length = sizeof(dns_msgblock_t) + (sizeof_type * count);
block = isc_mem_get(mctx, length);
if (block == NULL)
return (NULL);
block->count = count;
block->remaining = count;
ISC_LINK_INIT(block, link);
return (block);
}
/*
* Return an element from the msgblock. If no more are available, return
* NULL.
*/
static inline void *
msgblock_internalget(dns_msgblock_t *block, unsigned int sizeof_type) {
void *ptr;
if (block == NULL || block->remaining == 0)
return (NULL);
block->remaining--;
ptr = (((unsigned char *)block)
+ sizeof(dns_msgblock_t)
+ (sizeof_type * block->remaining));
return (ptr);
}
static inline void
msgblock_reset(dns_msgblock_t *block) {
block->remaining = block->count;
}
/*
* Release memory associated with a message block.
*/
static inline void
msgblock_free(isc_mem_t *mctx, dns_msgblock_t *block, unsigned int sizeof_type)
{
unsigned int length;
length = sizeof(dns_msgblock_t) + (sizeof_type * block->count);
isc_mem_put(mctx, block, length);
}
/*
* Allocate a new dynamic buffer, and attach it to this message as the
* "current" buffer. (which is always the last on the list, for our
* uses)
*/
static inline isc_result_t
newbuffer(dns_message_t *msg, unsigned int size) {
isc_result_t result;
isc_buffer_t *dynbuf;
dynbuf = NULL;
result = isc_buffer_allocate(msg->mctx, &dynbuf, size);
if (result != ISC_R_SUCCESS)
return (ISC_R_NOMEMORY);
ISC_LIST_APPEND(msg->scratchpad, dynbuf, link);
return (ISC_R_SUCCESS);
}
static inline isc_buffer_t *
currentbuffer(dns_message_t *msg) {
isc_buffer_t *dynbuf;
dynbuf = ISC_LIST_TAIL(msg->scratchpad);
INSIST(dynbuf != NULL);
return (dynbuf);
}
static inline void
releaserdata(dns_message_t *msg, dns_rdata_t *rdata) {
ISC_LIST_PREPEND(msg->freerdata, rdata, link);
}
static inline dns_rdata_t *
newrdata(dns_message_t *msg) {
dns_msgblock_t *msgblock;
dns_rdata_t *rdata;
rdata = ISC_LIST_HEAD(msg->freerdata);
if (rdata != NULL) {
ISC_LIST_UNLINK(msg->freerdata, rdata, link);
return (rdata);
}
msgblock = ISC_LIST_TAIL(msg->rdatas);
rdata = msgblock_get(msgblock, dns_rdata_t);
if (rdata == NULL) {
msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t),
RDATA_COUNT);
if (msgblock == NULL)
return (NULL);
ISC_LIST_APPEND(msg->rdatas, msgblock, link);
rdata = msgblock_get(msgblock, dns_rdata_t);
}
return (rdata);
}
static inline void
releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) {
ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link);
}
static inline dns_rdatalist_t *
newrdatalist(dns_message_t *msg) {
dns_msgblock_t *msgblock;
dns_rdatalist_t *rdatalist;
rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
if (rdatalist != NULL) {
ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
return (rdatalist);
}
msgblock = ISC_LIST_TAIL(msg->rdatalists);
rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
if (rdatalist == NULL) {
msgblock = msgblock_allocate(msg->mctx,
sizeof(dns_rdatalist_t),
RDATALIST_COUNT);
if (msgblock == NULL)
return (NULL);
ISC_LIST_APPEND(msg->rdatalists, msgblock, link);
rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
}
return (rdatalist);
}
static inline void
msginitheader(dns_message_t *m) {
m->id = 0;
m->flags = 0;
m->rcode = 0;
m->opcode = 0;
m->rdclass = 0;
}
static inline void
msginitprivate(dns_message_t *m) {
unsigned int i;
for (i = 0; i < DNS_SECTION_MAX; i++) {
m->cursors[i] = NULL;
m->counts[i] = 0;
}
m->opt = NULL;
m->sig0 = NULL;
m->sig0name = NULL;
m->tsig = NULL;
m->tsigname = NULL;
m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */
m->opt_reserved = 0;
m->sig_reserved = 0;
m->reserved = 0;
m->buffer = NULL;
m->need_cctx_cleanup = 0;
}
static inline void
msginittsig(dns_message_t *m) {
m->tsigstatus = dns_rcode_noerror;
m->querytsigstatus = dns_rcode_noerror;
m->tsigkey = NULL;
m->tsigctx = NULL;
m->sigstart = -1;
m->sig0key = NULL;
m->sig0status = dns_rcode_noerror;
m->query = NULL;
m->saved = NULL;
m->querytsig = NULL;
}
/*
* Init elements to default state. Used both when allocating a new element
* and when resetting one.
*/
static inline void
msginit(dns_message_t *m) {
msginitheader(m);
msginitprivate(m);
msginittsig(m);
m->header_ok = 0;
m->question_ok = 0;
m->tcp_continuation = 0;
m->verified_sig = 0;
m->verify_attempted = 0;
}
static inline void
msgresetnames(dns_message_t *msg, unsigned int first_section) {
unsigned int i;
dns_name_t *name, *next_name;
dns_rdataset_t *rds, *next_rds;
/*
* Clean up name lists by calling the rdataset disassociate function.
*/
for (i = first_section; i < DNS_SECTION_MAX; i++) {
name = ISC_LIST_HEAD(msg->sections[i]);
while (name != NULL) {
next_name = ISC_LIST_NEXT(name, link);
ISC_LIST_UNLINK(msg->sections[i], name, link);
rds = ISC_LIST_HEAD(name->list);
while (rds != NULL) {
next_rds = ISC_LIST_NEXT(rds, link);
ISC_LIST_UNLINK(name->list, rds, link);
INSIST(dns_rdataset_isassociated(rds));
dns_rdataset_disassociate(rds);
isc_mempool_put(msg->rdspool, rds);
rds = next_rds;
}
isc_mempool_put(msg->namepool, name);
name = next_name;
}
}
}
static void
msgresetopt(dns_message_t *msg)
{
if (msg->opt != NULL) {
if (msg->opt_reserved > 0) {
dns_message_renderrelease(msg, msg->opt_reserved);
msg->opt_reserved = 0;
}
INSIST(dns_rdataset_isassociated(msg->opt));
dns_rdataset_disassociate(msg->opt);
isc_mempool_put(msg->rdspool, msg->opt);
msg->opt = NULL;
}
}
static void
msgresetsigs(dns_message_t *msg, isc_boolean_t replying) {
if (msg->sig_reserved > 0) {
dns_message_renderrelease(msg, msg->sig_reserved);
msg->sig_reserved = 0;
}
if (msg->tsig != NULL) {
INSIST(dns_rdataset_isassociated(msg->tsig));
INSIST(msg->namepool != NULL);
if (replying) {
INSIST(msg->querytsig == NULL);
msg->querytsig = msg->tsig;
} else {
dns_rdataset_disassociate(msg->tsig);
isc_mempool_put(msg->rdspool, msg->tsig);
if (msg->querytsig != NULL) {
dns_rdataset_disassociate(msg->querytsig);
isc_mempool_put(msg->rdspool, msg->querytsig);
}
}
isc_mempool_put(msg->namepool, msg->tsigname);
msg->tsig = NULL;
msg->tsigname = NULL;
}
if (msg->sig0 != NULL) {
INSIST(dns_rdataset_isassociated(msg->sig0));
dns_rdataset_disassociate(msg->sig0);
isc_mempool_put(msg->rdspool, msg->sig0);
if (msg->sig0name != NULL)
isc_mempool_put(msg->namepool, msg->sig0name);
msg->sig0 = NULL;
msg->sig0name = NULL;
}
}
/*
* Free all but one (or everything) for this message. This is used by
* both dns_message_reset() and dns_message_parse().
*/
static void
msgreset(dns_message_t *msg, isc_boolean_t everything) {
dns_msgblock_t *msgblock, *next_msgblock;
isc_buffer_t *dynbuf, *next_dynbuf;
dns_rdata_t *rdata;
dns_rdatalist_t *rdatalist;
msgresetnames(msg, 0);
msgresetopt(msg);
msgresetsigs(msg, ISC_FALSE);
/*
* Clean up linked lists.
*/
/*
* Run through the free lists, and just unlink anything found there.
* The memory isn't lost since these are part of message blocks we
* have allocated.
*/
rdata = ISC_LIST_HEAD(msg->freerdata);
while (rdata != NULL) {
ISC_LIST_UNLINK(msg->freerdata, rdata, link);
rdata = ISC_LIST_HEAD(msg->freerdata);
}
rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
while (rdatalist != NULL) {
ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
}
dynbuf = ISC_LIST_HEAD(msg->scratchpad);
INSIST(dynbuf != NULL);
if (!everything) {
isc_buffer_clear(dynbuf);
dynbuf = ISC_LIST_NEXT(dynbuf, link);
}
while (dynbuf != NULL) {
next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link);
isc_buffer_free(&dynbuf);
dynbuf = next_dynbuf;
}
msgblock = ISC_LIST_HEAD(msg->rdatas);
INSIST(msgblock != NULL);
if (!everything) {
msgblock_reset(msgblock);
msgblock = ISC_LIST_NEXT(msgblock, link);
}
while (msgblock != NULL) {
next_msgblock = ISC_LIST_NEXT(msgblock, link);
ISC_LIST_UNLINK(msg->rdatas, msgblock, link);
msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t));
msgblock = next_msgblock;
}
/*
* rdatalists could be empty.
*/
msgblock = ISC_LIST_HEAD(msg->rdatalists);
if (!everything && msgblock != NULL) {
msgblock_reset(msgblock);
msgblock = ISC_LIST_NEXT(msgblock, link);
}
while (msgblock != NULL) {
next_msgblock = ISC_LIST_NEXT(msgblock, link);
ISC_LIST_UNLINK(msg->rdatalists, msgblock, link);
msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t));
msgblock = next_msgblock;
}
if (msg->need_cctx_cleanup == 1)
dns_compress_invalidate(&msg->cctx);
if (msg->tsigkey != NULL) {
dns_tsigkey_detach(&msg->tsigkey);
msg->tsigkey = NULL;
}
if (msg->query != NULL) {
isc_mem_put(msg->mctx, msg->query->base, msg->query->length);
isc_mem_put(msg->mctx, msg->query, sizeof(isc_region_t));
msg->query = NULL;
}
if (msg->saved != NULL) {
isc_mem_put(msg->mctx, msg->saved->base, msg->saved->length);
isc_mem_put(msg->mctx, msg->saved, sizeof(isc_region_t));
msg->saved = NULL;
}
/*
* cleanup the buffer cleanup list
*/
dynbuf = ISC_LIST_HEAD(msg->cleanup);
while (dynbuf != NULL) {
next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
ISC_LIST_UNLINK(msg->cleanup, dynbuf, link);
isc_buffer_free(&dynbuf);
dynbuf = next_dynbuf;
}
/*
* Set other bits to normal default values.
*/
if (!everything)
msginit(msg);
}
static unsigned int
spacefortsig(dns_tsigkey_t *key, int otherlen) {
isc_region_t r1, r2;
unsigned int x;
isc_result_t result;
/*
* The space required for an TSIG record is:
*
* n1 bytes for the name
* 2 bytes for the type
* 2 bytes for the class
* 4 bytes for the ttl
* 6 bytes for the time signed
* 2 bytes for the fudge
* 2 bytes for the MAC size
* x bytes for the MAC
* 2 bytes for the original id
* 2 bytes for the error
* 2 bytes for the other data length
* y bytes for the other data (at most)
* ---------------------------------
* 30 + n1 + n2 + x + y bytes
*/
dns_name_toregion(&key->name, &r1);
dns_name_toregion(&key->algorithm, &r2);
if (key->key == NULL)
x = 0;
else {
result = dst_key_sigsize(key->key, &x);
if (result != ISC_R_SUCCESS)
x = 0;
}
return (24 + r1.length + r2.length + x + otherlen);
}
isc_result_t
dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp)
{
dns_message_t *m;
isc_result_t result;
dns_msgblock_t *msgblock;
isc_buffer_t *dynbuf;
unsigned int i;
REQUIRE(mctx != NULL);
REQUIRE(msgp != NULL);
REQUIRE(*msgp == NULL);
REQUIRE(intent == DNS_MESSAGE_INTENTPARSE
|| intent == DNS_MESSAGE_INTENTRENDER);
m = isc_mem_get(mctx, sizeof(dns_message_t));
if (m == NULL)
return (ISC_R_NOMEMORY);
/*
* No allocations until further notice. Just initialize all lists
* and other members that are freed in the cleanup phase here.
*/
m->magic = DNS_MESSAGE_MAGIC;
m->from_to_wire = intent;
msginit(m);
for (i = 0 ; i < DNS_SECTION_MAX ; i++)
ISC_LIST_INIT(m->sections[i]);
m->mctx = mctx;
ISC_LIST_INIT(m->scratchpad);
ISC_LIST_INIT(m->cleanup);
m->namepool = NULL;
m->rdspool = NULL;
ISC_LIST_INIT(m->rdatas);
ISC_LIST_INIT(m->rdatalists);
ISC_LIST_INIT(m->freerdata);
ISC_LIST_INIT(m->freerdatalist);
/*
* Ok, it is safe to allocate (and then "goto cleanup" if failure)
*/
result = isc_mempool_create(m->mctx, sizeof(dns_name_t), &m->namepool);
if (result != ISC_R_SUCCESS)
goto cleanup;
isc_mempool_setfreemax(m->namepool, NAME_COUNT);
isc_mempool_setfillcount(m->namepool, NAME_COUNT);
isc_mempool_setname(m->namepool, "msg:names");
result = isc_mempool_create(m->mctx, sizeof(dns_rdataset_t),
&m->rdspool);
if (result != ISC_R_SUCCESS)
goto cleanup;
isc_mempool_setfreemax(m->rdspool, NAME_COUNT);
isc_mempool_setfillcount(m->rdspool, NAME_COUNT);
isc_mempool_setname(m->rdspool, "msg:rdataset");
dynbuf = NULL;
result = isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE);
if (result != ISC_R_SUCCESS)
goto cleanup;
ISC_LIST_APPEND(m->scratchpad, dynbuf, link);
msgblock = msgblock_allocate(mctx, sizeof(dns_rdata_t),
RDATA_COUNT);
if (msgblock == NULL)
goto cleanup;
ISC_LIST_APPEND(m->rdatas, msgblock, link);
if (intent == DNS_MESSAGE_INTENTPARSE) {
msgblock = msgblock_allocate(mctx, sizeof(dns_rdatalist_t),
RDATALIST_COUNT);
if (msgblock == NULL)
goto cleanup;
ISC_LIST_APPEND(m->rdatalists, msgblock, link);
}
*msgp = m;
return (ISC_R_SUCCESS);
/*
* Cleanup for error returns.
*/
cleanup:
msgblock = ISC_LIST_HEAD(m->rdatas);
if (msgblock != NULL)
msgblock_free(mctx, msgblock, sizeof(dns_rdata_t));
dynbuf = ISC_LIST_HEAD(m->scratchpad);
if (dynbuf != NULL)
isc_buffer_free(&dynbuf);
if (m->namepool != NULL)
isc_mempool_destroy(&m->namepool);
if (m->rdspool != NULL)
isc_mempool_destroy(&m->rdspool);
m->magic = 0;
isc_mem_put(mctx, m, sizeof(dns_message_t));
return (ISC_R_NOMEMORY);
}
void
dns_message_reset(dns_message_t *msg, unsigned int intent) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(intent == DNS_MESSAGE_INTENTPARSE
|| intent == DNS_MESSAGE_INTENTRENDER);
msgreset(msg, ISC_FALSE);
msg->from_to_wire = intent;
}
void
dns_message_destroy(dns_message_t **msgp) {
dns_message_t *msg;
REQUIRE(msgp != NULL);
REQUIRE(DNS_MESSAGE_VALID(*msgp));
msg = *msgp;
*msgp = NULL;
msgreset(msg, ISC_TRUE);
isc_mempool_destroy(&msg->namepool);
isc_mempool_destroy(&msg->rdspool);
msg->magic = 0;
isc_mem_put(msg->mctx, msg, sizeof(dns_message_t));
}
static isc_result_t
findname(dns_name_t **foundname, dns_name_t *target, unsigned int attributes,
dns_namelist_t *section)
{
dns_name_t *curr;
for (curr = ISC_LIST_TAIL(*section) ;
curr != NULL ;
curr = ISC_LIST_PREV(curr, link)) {
if (dns_name_equal(curr, target) &&
(curr->attributes & attributes) == attributes) {
if (foundname != NULL)
*foundname = curr;
return (ISC_R_SUCCESS);
}
}
return (ISC_R_NOTFOUND);
}
isc_result_t
dns_message_findtype(dns_name_t *name, dns_rdatatype_t type,
dns_rdatatype_t covers, dns_rdataset_t **rdataset)
{
dns_rdataset_t *curr;
if (rdataset != NULL) {
REQUIRE(*rdataset == NULL);
}
for (curr = ISC_LIST_TAIL(name->list) ;
curr != NULL ;
curr = ISC_LIST_PREV(curr, link)) {
if (curr->type == type && curr->covers == covers) {
if (rdataset != NULL)
*rdataset = curr;
return (ISC_R_SUCCESS);
}
}
return (ISC_R_NOTFOUND);
}
/*
* Read a name from buffer "source".
*/
static isc_result_t
getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg,
dns_decompress_t *dctx)
{
isc_buffer_t *scratch;
isc_result_t result;
unsigned int tries;
scratch = currentbuffer(msg);
/*
* First try: use current buffer.
* Second try: allocate a new buffer and use that.
*/
tries = 0;
while (tries < 2) {
dns_name_init(name, NULL);
result = dns_name_fromwire(name, source, dctx, ISC_FALSE,
scratch);
if (result == ISC_R_NOSPACE) {
tries++;
result = newbuffer(msg, SCRATCHPAD_SIZE);
if (result != ISC_R_SUCCESS)
return (result);
scratch = currentbuffer(msg);
} else {
return (result);
}
}
INSIST(0); /* Cannot get here... */
return (ISC_R_UNEXPECTED);
}
static isc_result_t
getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
dns_rdataclass_t rdclass, dns_rdatatype_t rdtype,
unsigned int rdatalen, dns_rdata_t *rdata)
{
isc_buffer_t *scratch;
isc_result_t result;
unsigned int tries;
unsigned int trysize;
/*
* In dynamic update messages, the rdata can be empty.
*/
if (msg->opcode == dns_opcode_update && rdatalen == 0) {
/*
* When the rdata is empty, the data pointer is never
* dereferenced, but it must still be non-NULL. Casting
* 1 rather than "" avoids warnings about discarding
* the const attribute of a string, for compilers that
* would warn about such things.
*/
rdata->data = (unsigned char *)1;
rdata->length = 0;
rdata->rdclass = rdclass;
rdata->type = rdtype;
return (ISC_R_SUCCESS);
}
scratch = currentbuffer(msg);
isc_buffer_setactive(source, rdatalen);
/*
* First try: use current buffer.
* Second try: allocate a new buffer of size
* max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen)
* (the data will fit if it was not more than 50% compressed)
* Subsequent tries: double buffer size on each try.
*/
tries = 0;
trysize = 0;
/* XXX possibly change this to a while (tries < 2) loop */
for (;;) {
result = dns_rdata_fromwire(rdata, rdclass, rdtype,
source, dctx, ISC_FALSE,
scratch);
if (result == ISC_R_NOSPACE) {
if (tries == 0) {
trysize = 2 * rdatalen;
if (trysize < SCRATCHPAD_SIZE)
trysize = SCRATCHPAD_SIZE;
} else {
INSIST(trysize != 0);
if (trysize >= 65535)
return (ISC_R_NOSPACE);
/* XXX DNS_R_RRTOOLONG? */
trysize *= 2;
}
tries++;
result = newbuffer(msg, trysize);
if (result != ISC_R_SUCCESS)
return (result);
scratch = currentbuffer(msg);
} else {
return (result);
}
}
}
static isc_result_t
getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx)
{
isc_region_t r;
unsigned int count;
dns_name_t *name;
dns_name_t *name2;
dns_rdataset_t *rdataset;
dns_rdatalist_t *rdatalist;
isc_result_t result;
dns_rdatatype_t rdtype;
dns_rdataclass_t rdclass;
dns_namelist_t *section;
isc_boolean_t free_name;
section = &msg->sections[DNS_SECTION_QUESTION];
name = NULL;
rdataset = NULL;
rdatalist = NULL;
for (count = 0 ; count < msg->counts[DNS_SECTION_QUESTION] ; count++) {
name = isc_mempool_get(msg->namepool);
if (name == NULL)
return (ISC_R_NOMEMORY);
free_name = ISC_TRUE;
/*
* Parse the name out of this packet.
*/
isc_buffer_remainingregion(source, &r);
isc_buffer_setactive(source, r.length);
result = getname(name, source, msg, dctx);
if (result != ISC_R_SUCCESS)
goto cleanup;
/*
* Run through the section, looking to see if this name
* is already there. If it is found, put back the allocated
* name since we no longer need it, and set our name pointer
* to point to the name we found.
*/
result = findname(&name2, name, 0, section);
/*
* If it is the first name in the section, accept it.
*
* If it is not, but is not the same as the name already
* in the question section, append to the section. Note that
* here in the question section this is illegal, so return
* FORMERR. In the future, check the opcode to see if
* this should be legal or not. In either case we no longer
* need this name pointer.
*/
if (result != ISC_R_SUCCESS) {
if (ISC_LIST_EMPTY(*section)) {
ISC_LIST_APPEND(*section, name, link);
free_name = ISC_FALSE;
} else {
result = DNS_R_FORMERR;
goto cleanup;
}
} else {
isc_mempool_put(msg->namepool, name);
name = name2;
name2 = NULL;
free_name = ISC_FALSE;
}
/*
* Get type and class.
*/
isc_buffer_remainingregion(source, &r);
if (r.length < 4) {
result = ISC_R_UNEXPECTEDEND;
goto cleanup;
}
rdtype = isc_buffer_getuint16(source);
rdclass = isc_buffer_getuint16(source);
/*
* If this class is different than the one we already read,
* this is an error.
*/
if (msg->state == DNS_SECTION_ANY) {
msg->state = DNS_SECTION_QUESTION;
msg->rdclass = rdclass;
} else if (msg->rdclass != rdclass) {
result = DNS_R_FORMERR;
goto cleanup;
}
/*
* If this is a type that cannot occur in a question section,
* return failure.
*/
if (dns_rdatatype_notquestion(rdtype)) {
result = DNS_R_FORMERR;
goto cleanup;
}
/*
* Can't ask the same question twice.
*/
result = dns_message_findtype(name, rdtype, 0, NULL);
if (result == ISC_R_SUCCESS) {
result = DNS_R_FORMERR;
goto cleanup;
}
/*
* Allocate a new rdatalist.
*/
rdatalist = newrdatalist(msg);
if (rdatalist == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
rdataset = isc_mempool_get(msg->rdspool);
if (rdataset == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
/*
* Convert rdatalist to rdataset, and attach the latter to
* the name.
*/
rdatalist->type = rdtype;
rdatalist->covers = 0;
rdatalist->rdclass = rdclass;
rdatalist->ttl = 0;
ISC_LIST_INIT(rdatalist->rdata);
dns_rdataset_init(rdataset);
result = dns_rdatalist_tordataset(rdatalist, rdataset);
if (result != ISC_R_SUCCESS)
goto cleanup;
rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
ISC_LIST_APPEND(name->list, rdataset, link);
rdataset = NULL;
}
return (ISC_R_SUCCESS);
cleanup:
if (rdataset != NULL) {
INSIST(!dns_rdataset_isassociated(rdataset));
isc_mempool_put(msg->rdspool, rdataset);
}
#if 0
if (rdatalist != NULL)
isc_mempool_put(msg->rdlpool, rdatalist);
#endif
if (free_name)
isc_mempool_put(msg->namepool, name);
return (result);
}
static isc_result_t
getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
dns_section_t sectionid, isc_boolean_t preserve_order)
{
isc_region_t r;
unsigned int count, rdatalen, attributes;
dns_name_t *name;
dns_name_t *name2;
dns_rdataset_t *rdataset;
dns_rdatalist_t *rdatalist;
isc_result_t result;
dns_rdatatype_t rdtype, covers;
dns_rdataclass_t rdclass;
dns_rdata_t *rdata;
dns_ttl_t ttl;
dns_namelist_t *section;
isc_boolean_t free_name, free_rdataset;
for (count = 0 ; count < msg->counts[sectionid] ; count++) {
int recstart = source->current;
isc_boolean_t skip_name_search, skip_type_search;
section = &msg->sections[sectionid];
skip_name_search = ISC_FALSE;
skip_type_search = ISC_FALSE;
free_name = ISC_FALSE;
free_rdataset = ISC_FALSE;
name = isc_mempool_get(msg->namepool);
if (name == NULL)
return (ISC_R_NOMEMORY);
free_name = ISC_TRUE;
/*
* Parse the name out of this packet.
*/
isc_buffer_remainingregion(source, &r);
isc_buffer_setactive(source, r.length);
result = getname(name, source, msg, dctx);
if (result != ISC_R_SUCCESS)
goto cleanup;
/*
* Get type, class, ttl, and rdatalen. Verify that at least
* rdatalen bytes remain. (Some of this is deferred to
* later.)
*/
isc_buffer_remainingregion(source, &r);
if (r.length < 2 + 2 + 4 + 2) {
result = ISC_R_UNEXPECTEDEND;
goto cleanup;
}
rdtype = isc_buffer_getuint16(source);
rdclass = isc_buffer_getuint16(source);
/*
* If there was no question section, we may not yet have
* established a class. Do so now.
*/
if (msg->state == DNS_SECTION_ANY) {
if (rdclass == 0 || rdclass == dns_rdataclass_any) {
result = DNS_R_FORMERR;
goto cleanup;
}
msg->rdclass = rdclass;
msg->state = DNS_SECTION_QUESTION;
}
/*
* If this class is different than the one in the question
* section, bail.
*/
if (msg->opcode != dns_opcode_update
&& rdtype != dns_rdatatype_tsig
&& rdtype != dns_rdatatype_opt
&& rdtype != dns_rdatatype_key /* XXX in a TKEY query */
&& rdtype != dns_rdatatype_sig /* XXX SIG(0) */
&& msg->rdclass != rdclass) {
result = DNS_R_FORMERR;
goto cleanup;
}
/*
* Special type handling for TSIG, OPT, and TKEY.
*/
if (rdtype == dns_rdatatype_tsig) {
/*
* If it is a tsig, verify that it is in the
* additional data section, and switch sections for
* the rest of this rdata.
*/
if ((sectionid != DNS_SECTION_ADDITIONAL)
|| (rdclass != dns_rdataclass_any)) {
result = DNS_R_FORMERR;
goto cleanup;
}
if (msg->tsig != NULL) {
result = DNS_R_FORMERR;
goto cleanup;
}
msg->sigstart = recstart;
skip_name_search = ISC_TRUE;
skip_type_search = ISC_TRUE;
} else if (rdtype == dns_rdatatype_opt) {
/*
* The name of an OPT record must be ".", it
* must be in the additional data section, and
* it must be the first OPT we've seen.
*/
if (!dns_name_equal(dns_rootname, name) ||
sectionid != DNS_SECTION_ADDITIONAL ||
msg->opt != NULL) {
result = DNS_R_FORMERR;
goto cleanup;
}
skip_name_search = ISC_TRUE;
skip_type_search = ISC_TRUE;
} else if (rdtype == dns_rdatatype_tkey) {
/*
* A TKEY must be in the additional section if this
* is a query, and the answer section if this is a
* response.
* Its class is ignored.
*/
dns_section_t tkeysection;
if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0)
tkeysection = DNS_SECTION_ADDITIONAL;
else
tkeysection = DNS_SECTION_ANSWER;
if (sectionid != tkeysection) {
result = DNS_R_FORMERR;
goto cleanup;
}
}
/*
* ... now get ttl and rdatalen, and check buffer.
*/
ttl = isc_buffer_getuint32(source);
rdatalen = isc_buffer_getuint16(source);
r.length -= (2 + 2 + 4 + 2);
if (r.length < rdatalen) {
result = ISC_R_UNEXPECTEDEND;
goto cleanup;
}
/*
* Read the rdata from the wire format. Interpret the
* rdata according to its actual class, even if it had a
* DynDNS meta-class in the packet (unless this is a TSIG).
* Then put the meta-class back into the finished rdata.
*/
rdata = newrdata(msg);
if (rdata == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
attributes = 0;
if (rdtype != dns_rdatatype_tsig) {
if (rdtype == dns_rdatatype_cname) {
name->attributes |= DNS_NAMEATTR_CNAME;
attributes = DNS_NAMEATTR_CNAME;
skip_name_search = ISC_TRUE;
} else if (rdtype == dns_rdatatype_dname) {
name->attributes |= DNS_NAMEATTR_DNAME;
attributes = DNS_NAMEATTR_DNAME;
skip_name_search = ISC_TRUE;
}
result = getrdata(source, msg, dctx, msg->rdclass,
rdtype, rdatalen, rdata);
} else
result = getrdata(source, msg, dctx, rdclass,
rdtype, rdatalen, rdata);
if (result != ISC_R_SUCCESS)
goto cleanup;
rdata->rdclass = rdclass;
if (rdtype == dns_rdatatype_sig && rdata->length > 0) {
covers = dns_rdata_covers(rdata);
if (covers == dns_rdatatype_cname)
attributes = DNS_NAMEATTR_CNAME;
else if (covers == dns_rdatatype_dname)
attributes = DNS_NAMEATTR_DNAME;
else if (covers == 0 &&
sectionid == DNS_SECTION_ADDITIONAL)
{
if (msg->sig0 != NULL) {
result = DNS_R_FORMERR;
goto cleanup;
}
msg->sigstart = recstart;
skip_name_search = ISC_TRUE;
skip_type_search = ISC_TRUE;
}
} else
covers = 0;
/*
* If we are doing a dynamic update don't bother searching
* for a name, just append this one to the end of the message.
*/
if (preserve_order || msg->opcode == dns_opcode_update ||
skip_name_search) {
if (rdtype != dns_rdatatype_opt &&
rdtype != dns_rdatatype_tsig &&
!(rdtype == dns_rdatatype_sig && covers == 0 &&
sectionid == DNS_SECTION_ADDITIONAL))
{
ISC_LIST_APPEND(*section, name, link);
free_name = ISC_FALSE;
}
} else {
/*
* Run through the section, looking to see if this name
* is already there. If it is found, put back the
* allocated name since we no longer need it, and set
* our name pointer to point to the name we found.
*/
result = findname(&name2, name, attributes, section);
/*
* If it is a new name, append to the section.
*/
if (result == ISC_R_SUCCESS) {
isc_mempool_put(msg->namepool, name);
name = name2;
} else {
ISC_LIST_APPEND(*section, name, link);
}
free_name = ISC_FALSE;
}
/*
* Search name for the particular type and class.
* Skip this stage if in update mode, or this is a TSIG.
*/
if (preserve_order || msg->opcode == dns_opcode_update ||
skip_type_search)
result = ISC_R_NOTFOUND;
else {
/*
* If this is a type that can only occur in
* the question section, fail.
*/
if (dns_rdatatype_questiononly(rdtype)) {
result = DNS_R_FORMERR;
goto cleanup;
}
rdataset = NULL;
result = dns_message_findtype(name, rdtype, covers,
&rdataset);
}
/*
* If we found an rdataset that matches, we need to
* append this rdata to that set. If we did not, we need
* to create a new rdatalist, store the important bits there,
* convert it to an rdataset, and link the latter to the name.
* Yuck. When appending, make certain that the type isn't
* a singleton type, such as SOA or CNAME.
*
* Note that this check will be bypassed when preserving order,
* the opcode is an update, or the type search is skipped.
*/
if (result == ISC_R_SUCCESS) {
if (dns_rdatatype_issingleton(rdtype)) {
result = DNS_R_FORMERR;
goto cleanup;
}
}
if (result == ISC_R_NOTFOUND) {
rdataset = isc_mempool_get(msg->rdspool);
if (rdataset == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
free_rdataset = ISC_TRUE;
rdatalist = newrdatalist(msg);
if (rdatalist == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
rdatalist->type = rdtype;
rdatalist->covers = covers;
rdatalist->rdclass = rdclass;
rdatalist->ttl = ttl;
ISC_LIST_INIT(rdatalist->rdata);
dns_rdataset_init(rdataset);
dns_rdatalist_tordataset(rdatalist, rdataset);
if (rdtype != dns_rdatatype_opt) {
ISC_LIST_APPEND(name->list, rdataset, link);
free_rdataset = ISC_FALSE;
}
}
/*
* Minimize TTLs.
*
* Section 5.2 of RFC 2181 says we should drop
* nonauthoritative rrsets where the TTLs differ, but we
* currently treat them the as if they were authoritative and
* minimize them.
*/
if (ttl != rdataset->ttl) {
rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED;
if (ttl < rdataset->ttl)
rdataset->ttl = ttl;
}
/*
* XXXMLG Perform a totally ugly hack here to pull
* the rdatalist out of the private field in the rdataset,
* and append this rdata to the rdatalist's linked list
* of rdata.
*/
rdatalist = (dns_rdatalist_t *)(rdataset->private1);
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
/*
* If this is an OPT record, remember it. Also, set
* the extended rcode.
*/
if (rdtype == dns_rdatatype_opt) {
unsigned int ercode;
msg->opt = rdataset;
rdataset = NULL;
free_rdataset = ISC_FALSE;
ercode = (msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK)
>> 20;
msg->rcode |= ercode;
isc_mempool_put(msg->namepool, name);
free_name = ISC_FALSE;
}
/*
* If this is an SIG(0) or TSIG record, remember it.
*/
if (rdtype == dns_rdatatype_sig && covers == 0 &&
sectionid == DNS_SECTION_ADDITIONAL)
{
msg->sig0 = rdataset;
msg->sig0name = name;
rdataset = NULL;
free_rdataset = ISC_FALSE;
free_name = ISC_FALSE;
}
else if (rdtype == dns_rdatatype_tsig) {
msg->tsig = rdataset;
msg->tsigname = name;
rdataset = NULL;
free_rdataset = ISC_FALSE;
free_name = ISC_FALSE;
}
INSIST(free_name == ISC_FALSE);
INSIST(free_rdataset == ISC_FALSE);
}
return (ISC_R_SUCCESS);
cleanup:
if (free_name)
isc_mempool_put(msg->namepool, name);
if (free_rdataset)
isc_mempool_put(msg->rdspool, rdataset);
return (result);
}
isc_result_t
dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
isc_boolean_t preserve_order)
{
isc_region_t r;
dns_decompress_t dctx;
isc_result_t ret;
isc_uint16_t tmpflags;
isc_buffer_t origsource;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(source != NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
origsource = *source;
msg->header_ok = 0;
msg->question_ok = 0;
isc_buffer_remainingregion(source, &r);
if (r.length < DNS_MESSAGE_HEADERLEN)
return (ISC_R_UNEXPECTEDEND);
msg->id = isc_buffer_getuint16(source);
tmpflags = isc_buffer_getuint16(source);
msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK)
>> DNS_MESSAGE_OPCODE_SHIFT);
msg->rcode = (tmpflags & DNS_MESSAGE_RCODE_MASK);
msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK);
msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source);
msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source);
msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source);
msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source);
msg->header_ok = 1;
/*
* -1 means no EDNS.
*/
dns_decompress_init(&dctx, -1, ISC_FALSE);
dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14);
ret = getquestions(source, msg, &dctx);
if (ret != ISC_R_SUCCESS)
return (ret);
msg->question_ok = 1;
ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER,
preserve_order);
if (ret != ISC_R_SUCCESS)
return (ret);
ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY,
preserve_order);
if (ret != ISC_R_SUCCESS)
return (ret);
ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL,
preserve_order);
if (ret != ISC_R_SUCCESS)
return (ret);
isc_buffer_remainingregion(source, &r);
if (r.length != 0)
return (DNS_R_FORMERR);
if (msg->tsig != NULL || msg->tsigkey != NULL || msg->sig0 != NULL) {
msg->saved = isc_mem_get(msg->mctx, sizeof(isc_region_t));
if (msg->saved == NULL)
return (ISC_R_NOMEMORY);
isc_buffer_usedregion(&origsource, &r);
msg->saved->length = r.length;
msg->saved->base = isc_mem_get(msg->mctx, msg->saved->length);
if (msg->saved->base == NULL) {
isc_mem_put(msg->mctx, msg->saved,
sizeof(isc_region_t));
msg->saved = NULL;
return (ISC_R_NOMEMORY);
}
memcpy(msg->saved->base, r.base, msg->saved->length);
}
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_renderbegin(dns_message_t *msg, isc_buffer_t *buffer) {
isc_region_t r;
isc_result_t result;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(buffer != NULL);
REQUIRE(msg->buffer == NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
/*
* Erase the contents of this buffer.
*/
isc_buffer_clear(buffer);
/*
* Make certain there is enough for at least the header in this
* buffer.
*/
isc_buffer_availableregion(buffer, &r);
REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN);
if (r.length < msg->reserved)
return (ISC_R_NOSPACE);
result = dns_compress_init(&msg->cctx, -1, msg->mctx);
if (result != ISC_R_SUCCESS)
return (result);
msg->need_cctx_cleanup = 1;
/*
* Reserve enough space for the header in this buffer.
*/
isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN);
msg->buffer = buffer;
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) {
isc_region_t r, rn;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(buffer != NULL);
REQUIRE(msg->buffer != NULL);
/*
* Ensure that the new buffer is empty, and has enough space to
* hold the current contents.
*/
isc_buffer_clear(buffer);
isc_buffer_availableregion(buffer, &rn);
isc_buffer_usedregion(msg->buffer, &r);
REQUIRE(rn.length > r.length);
/*
* Copy the contents from the old to the new buffer.
*/
isc_buffer_add(buffer, r.length);
memcpy(rn.base, r.base, r.length);
msg->buffer = buffer;
return (ISC_R_SUCCESS);
}
void
dns_message_renderrelease(dns_message_t *msg, unsigned int space) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(space <= msg->reserved);
msg->reserved -= space;
}
isc_result_t
dns_message_renderreserve(dns_message_t *msg, unsigned int space) {
isc_region_t r;
REQUIRE(DNS_MESSAGE_VALID(msg));
if (msg->buffer != NULL) {
isc_buffer_availableregion(msg->buffer, &r);
if (r.length < (space + msg->reserved))
return (ISC_R_NOSPACE);
}
msg->reserved += space;
return (ISC_R_SUCCESS);
}
static inline isc_boolean_t
wrong_priority(dns_rdataset_t *rds, int pass) {
int pass_needed;
/*
* If we are not rendering class IN, this ordering is bogus.
*/
if (rds->rdclass != dns_rdataclass_in)
return (ISC_FALSE);
switch (rds->type) {
case dns_rdatatype_a:
case dns_rdatatype_aaaa:
case dns_rdatatype_a6:
pass_needed = 3;
break;
case dns_rdatatype_sig:
case dns_rdatatype_key:
pass_needed = 2;
break;
default:
pass_needed = 1;
}
if (pass_needed >= pass)
return (ISC_FALSE);
return (ISC_TRUE);
}
isc_result_t
dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
unsigned int options)
{
dns_namelist_t *section;
dns_name_t *name, *next_name;
dns_rdataset_t *rdataset, *next_rdataset;
unsigned int count, total;
isc_result_t result;
isc_buffer_t st; /* for rollbacks */
int pass;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->buffer != NULL);
REQUIRE(VALID_NAMED_SECTION(sectionid));
section = &msg->sections[sectionid];
if ((sectionid == DNS_SECTION_ADDITIONAL)
&& (options & DNS_MESSAGERENDER_ORDERED) == 0)
pass = 3;
else
pass = 1;
/*
* Shrink the space in the buffer by the reserved amount.
*/
msg->buffer->length -= msg->reserved;
total = 0;
do {
name = ISC_LIST_HEAD(*section);
if (name == NULL) {
msg->buffer->length += msg->reserved;
msg->counts[sectionid] += total;
return (ISC_R_SUCCESS);
}
while (name != NULL) {
next_name = ISC_LIST_NEXT(name, link);
rdataset = ISC_LIST_HEAD(name->list);
while (rdataset != NULL) {
next_rdataset = ISC_LIST_NEXT(rdataset, link);
if ((rdataset->attributes &
DNS_RDATASETATTR_RENDERED) != 0)
goto next;
if (((options & DNS_MESSAGERENDER_ORDERED)
== 0)
&& (sectionid == DNS_SECTION_ADDITIONAL)
&& wrong_priority(rdataset, pass))
goto next;
st = *(msg->buffer);
count = 0;
result = dns_rdataset_towire(rdataset, name,
&msg->cctx,
msg->buffer,
&count);
total += count;
/*
* If out of space, record stats on what we
* rendered so far, and return that status.
*
* XXXMLG Need to change this when
* dns_rdataset_towire() can render partial
* sets starting at some arbitary point in the
* set. This will include setting a bit in the
* rdataset to indicate that a partial
* rendering was done, and some state saved
* somewhere (probably in the message struct)
* to indicate where to continue from.
*/
if (result != ISC_R_SUCCESS) {
INSIST(st.used < 65536);
dns_compress_rollback(&msg->cctx,
(isc_uint16_t)st.used);
*(msg->buffer) = st; /* rollback */
msg->buffer->length += msg->reserved;
msg->counts[sectionid] += total;
return (result);
}
/*
* If we have rendered pending data, ensure
* that the AD bit is not set.
*/
if (rdataset->trust == dns_trust_pending &&
(sectionid == DNS_SECTION_ANSWER ||
sectionid == DNS_SECTION_AUTHORITY))
msg->flags &= ~DNS_MESSAGEFLAG_AD;
rdataset->attributes |=
DNS_RDATASETATTR_RENDERED;
next:
rdataset = next_rdataset;
}
name = next_name;
}
} while (--pass != 0);
msg->buffer->length += msg->reserved;
msg->counts[sectionid] += total;
return (ISC_R_SUCCESS);
}
void
dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) {
isc_uint16_t tmp;
isc_region_t r;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
isc_buffer_availableregion(target, &r);
REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN);
isc_buffer_putuint16(target, msg->id);
tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT)
& DNS_MESSAGE_OPCODE_MASK);
tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK);
tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK);
INSIST(msg->counts[DNS_SECTION_QUESTION] < 65536 &&
msg->counts[DNS_SECTION_ANSWER] < 65536 &&
msg->counts[DNS_SECTION_AUTHORITY] < 65536 &&
msg->counts[DNS_SECTION_ADDITIONAL] < 65536);
isc_buffer_putuint16(target, tmp);
isc_buffer_putuint16(target,
(isc_uint16_t)msg->counts[DNS_SECTION_QUESTION]);
isc_buffer_putuint16(target,
(isc_uint16_t)msg->counts[DNS_SECTION_ANSWER]);
isc_buffer_putuint16(target,
(isc_uint16_t)msg->counts[DNS_SECTION_AUTHORITY]);
isc_buffer_putuint16(target,
(isc_uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]);
}
isc_result_t
dns_message_renderend(dns_message_t *msg) {
isc_buffer_t tmpbuf;
isc_region_t r;
int result;
unsigned int count;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->buffer != NULL);
if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) {
/*
* We have an extended rcode but are not using EDNS.
*/
return (DNS_R_FORMERR);
}
/*
* If we've got an OPT record, render it.
*/
if (msg->opt != NULL) {
dns_message_renderrelease(msg, msg->opt_reserved);
msg->opt_reserved = 0;
/*
* Set the extended rcode.
*/
msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK;
msg->opt->ttl |= ((msg->rcode << 20) &
DNS_MESSAGE_EDNSRCODE_MASK);
/*
* Render.
*/
count = 0;
result = dns_rdataset_towire(msg->opt, dns_rootname,
&msg->cctx, msg->buffer, &count);
msg->counts[DNS_SECTION_ADDITIONAL] += count;
if (result != ISC_R_SUCCESS)
return (result);
}
/*
* If we're adding a TSIG or SIG(0) to a truncated message,
* clear all rdatasets from the message except for the question
* before adding the TSIG or SIG(0).
*/
if ((msg->tsigkey != NULL || msg->sig0key != NULL) &&
(msg->flags & DNS_MESSAGEFLAG_TC) != 0)
{
isc_buffer_t *buf;
msgresetnames(msg, DNS_SECTION_ANSWER);
buf = msg->buffer;
dns_message_renderreset(msg);
msg->buffer = buf;
isc_buffer_clear(msg->buffer);
isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN);
dns_compress_rollback(&msg->cctx, 0);
result = dns_message_rendersection(msg, DNS_SECTION_QUESTION,
0);
if (result != ISC_R_SUCCESS)
return (result);
}
/*
* If we're adding a TSIG record, generate and render it.
*/
if (msg->tsigkey != NULL) {
dns_message_renderrelease(msg, msg->sig_reserved);
msg->sig_reserved = 0;
result = dns_tsig_sign(msg);
if (result != ISC_R_SUCCESS)
return (result);
count = 0;
result = dns_rdataset_towire(msg->tsig, msg->tsigname,
&msg->cctx, msg->buffer, &count);
msg->counts[DNS_SECTION_ADDITIONAL] += count;
if (result != ISC_R_SUCCESS)
return (result);
}
/*
* If we're adding a SIG(0) record, generate and render it.
*/
if (msg->sig0key != NULL) {
dns_message_renderrelease(msg, msg->sig_reserved);
msg->sig_reserved = 0;
result = dns_dnssec_signmessage(msg, msg->sig0key);
if (result != ISC_R_SUCCESS)
return (result);
count = 0;
/*
* Note: dns_rootname is used here, not msg->sig0name, since
* the owner name of a SIG(0) is irrelevant, and will not
* be set in a message being rendered.
*/
result = dns_rdataset_towire(msg->sig0, dns_rootname,
&msg->cctx, msg->buffer, &count);
msg->counts[DNS_SECTION_ADDITIONAL] += count;
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_usedregion(msg->buffer, &r);
isc_buffer_init(&tmpbuf, r.base, r.length);
dns_message_renderheader(msg, &tmpbuf);
msg->buffer = NULL; /* forget about this buffer only on success XXX */
dns_compress_invalidate(&msg->cctx);
msg->need_cctx_cleanup = 0;
return (ISC_R_SUCCESS);
}
void
dns_message_renderreset(dns_message_t *msg) {
unsigned int i;
dns_name_t *name;
dns_rdataset_t *rds;
/*
* Reset the message so that it may be rendered again.
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
msg->buffer = NULL;
for (i = 0; i < DNS_SECTION_MAX; i++) {
msg->cursors[i] = NULL;
msg->counts[i] = 0;
for (name = ISC_LIST_HEAD(msg->sections[i]);
name != NULL;
name = ISC_LIST_NEXT(name, link)) {
for (rds = ISC_LIST_HEAD(name->list);
rds != NULL;
rds = ISC_LIST_NEXT(rds, link)) {
rds->attributes &= ~DNS_RDATASETATTR_RENDERED;
}
}
}
}
isc_result_t
dns_message_firstname(dns_message_t *msg, dns_section_t section) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(VALID_NAMED_SECTION(section));
msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]);
if (msg->cursors[section] == NULL)
return (ISC_R_NOMORE);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_nextname(dns_message_t *msg, dns_section_t section) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(VALID_NAMED_SECTION(section));
REQUIRE(msg->cursors[section] != NULL);
msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link);
if (msg->cursors[section] == NULL)
return (ISC_R_NOMORE);
return (ISC_R_SUCCESS);
}
void
dns_message_currentname(dns_message_t *msg, dns_section_t section,
dns_name_t **name)
{
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(VALID_NAMED_SECTION(section));
REQUIRE(name != NULL && *name == NULL);
REQUIRE(msg->cursors[section] != NULL);
*name = msg->cursors[section];
}
isc_result_t
dns_message_findname(dns_message_t *msg, dns_section_t section,
dns_name_t *target, dns_rdatatype_t type,
dns_rdatatype_t covers, dns_name_t **name,
dns_rdataset_t **rdataset)
{
dns_name_t *foundname;
isc_result_t result;
unsigned int attributes;
dns_rdatatype_t atype;
/*
* XXX These requirements are probably too intensive, especially
* where things can be NULL, but as they are they ensure that if
* something is NON-NULL, indicating that the caller expects it
* to be filled in, that we can in fact fill it in.
*/
REQUIRE(msg != NULL);
REQUIRE(VALID_SECTION(section));
REQUIRE(target != NULL);
if (name != NULL)
REQUIRE(*name == NULL);
if (type == dns_rdatatype_any) {
REQUIRE(rdataset == NULL);
} else {
if (rdataset != NULL)
REQUIRE(*rdataset == NULL);
}
/*
* Figure out what attributes we should look for.
*/
if (type == dns_rdatatype_sig)
atype = covers;
else
atype = type;
attributes = 0;
if (atype == dns_rdatatype_cname)
attributes = DNS_NAMEATTR_CNAME;
else if (atype == dns_rdatatype_cname)
attributes = DNS_NAMEATTR_DNAME;
/*
* Search through, looking for the name.
*/
result = findname(&foundname, target, attributes,
&msg->sections[section]);
if (result == ISC_R_NOTFOUND)
return (DNS_R_NXDOMAIN);
else if (result != ISC_R_SUCCESS)
return (result);
if (name != NULL)
*name = foundname;
/*
* And now look for the type.
*/
if (type == dns_rdatatype_any)
return (ISC_R_SUCCESS);
result = dns_message_findtype(foundname, type, covers, rdataset);
if (result == ISC_R_NOTFOUND)
return (DNS_R_NXRRSET);
return (result);
}
void
dns_message_movename(dns_message_t *msg, dns_name_t *name,
dns_section_t fromsection,
dns_section_t tosection)
{
REQUIRE(msg != NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
REQUIRE(name != NULL);
REQUIRE(VALID_NAMED_SECTION(fromsection));
REQUIRE(VALID_NAMED_SECTION(tosection));
/*
* Unlink the name from the old section
*/
ISC_LIST_UNLINK(msg->sections[fromsection], name, link);
ISC_LIST_APPEND(msg->sections[tosection], name, link);
}
void
dns_message_addname(dns_message_t *msg, dns_name_t *name,
dns_section_t section)
{
REQUIRE(msg != NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
REQUIRE(name != NULL);
REQUIRE(VALID_NAMED_SECTION(section));
ISC_LIST_APPEND(msg->sections[section], name, link);
}
isc_result_t
dns_message_gettempname(dns_message_t *msg, dns_name_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item == NULL);
*item = isc_mempool_get(msg->namepool);
if (*item == NULL)
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
void
dns_message_puttempname(dns_message_t *msg, dns_name_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item != NULL);
isc_mempool_put(msg->namepool, *item);
*item = NULL;
}
isc_result_t
dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item == NULL);
*item = newrdata(msg);
if (*item == NULL)
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item == NULL);
*item = isc_mempool_get(msg->rdspool);
if (*item == NULL)
return (ISC_R_NOMEMORY);
dns_rdataset_init(*item);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item == NULL);
*item = newrdatalist(msg);
if (*item == NULL)
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
void
dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item != NULL);
releaserdata(msg, *item);
*item = NULL;
}
void
dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item != NULL);
REQUIRE(!dns_rdataset_isassociated(*item));
isc_mempool_put(msg->rdspool, *item);
*item = NULL;
}
void
dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item != NULL);
releaserdatalist(msg, *item);
*item = NULL;
}
isc_result_t
dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
unsigned int *flagsp)
{
isc_region_t r;
isc_buffer_t buffer;
dns_messageid_t id;
unsigned int flags;
REQUIRE(source != NULL);
buffer = *source;
isc_buffer_remainingregion(&buffer, &r);
if (r.length < DNS_MESSAGE_HEADERLEN)
return (ISC_R_UNEXPECTEDEND);
id = isc_buffer_getuint16(&buffer);
flags = isc_buffer_getuint16(&buffer);
flags &= DNS_MESSAGE_FLAG_MASK;
if (flagsp != NULL)
*flagsp = flags;
if (idp != NULL)
*idp = id;
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_reply(dns_message_t *msg, isc_boolean_t want_question_section) {
unsigned int first_section;
isc_result_t result;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0);
if (!msg->header_ok)
return (DNS_R_FORMERR);
if (msg->opcode != dns_opcode_query &&
msg->opcode != dns_opcode_notify)
want_question_section = ISC_FALSE;
if (want_question_section) {
if (!msg->question_ok)
return (DNS_R_FORMERR);
first_section = DNS_SECTION_ANSWER;
} else
first_section = DNS_SECTION_QUESTION;
msg->from_to_wire = DNS_MESSAGE_INTENTRENDER;
msgresetnames(msg, first_section);
msgresetopt(msg);
msgresetsigs(msg, ISC_TRUE);
msginitprivate(msg);
/*
* We now clear most flags and then set QR, ensuring that the
* reply's flags will be in a reasonable state.
*/
msg->flags &= DNS_MESSAGE_REPLYPRESERVE;
msg->flags |= DNS_MESSAGEFLAG_QR;
/*
* This saves the query TSIG status, if the query was signed, and
* reserves space in the reply for the TSIG.
*/
if (msg->querytsig != NULL) {
unsigned int otherlen = 0;
msg->querytsigstatus = msg->tsigstatus;
msg->tsigstatus = dns_rcode_noerror;
if (msg->querytsigstatus == dns_tsigerror_badtime)
otherlen = 6;
msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen);
result = dns_message_renderreserve(msg, msg->sig_reserved);
if (result != ISC_R_SUCCESS) {
msg->sig_reserved = 0;
return (result);
}
}
if (msg->saved != NULL) {
msg->query = msg->saved;
msg->saved = NULL;
}
return (ISC_R_SUCCESS);
}
dns_rdataset_t *
dns_message_getopt(dns_message_t *msg) {
/*
* Get the OPT record for 'msg'.
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
return (msg->opt);
}
isc_result_t
dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
isc_result_t result;
dns_rdata_t rdata;
/*
* Set the OPT record for 'msg'.
*/
/*
* The space required for an OPT record is:
*
* 1 byte for the name
* 2 bytes for the type
* 2 bytes for the class
* 4 bytes for the ttl
* 2 bytes for the rdata length
* ---------------------------------
* 11 bytes
*
* plus the length of the rdata.
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(opt->type == dns_rdatatype_opt);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
REQUIRE(msg->buffer != NULL);
REQUIRE(msg->state == DNS_SECTION_ANY);
msgresetopt(msg);
result = dns_rdataset_first(opt);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_current(opt, &rdata);
msg->opt_reserved = 11 + rdata.length;
result = dns_message_renderreserve(msg, msg->opt_reserved);
if (result != ISC_R_SUCCESS) {
msg->opt_reserved = 0;
return (result);
}
msg->opt = opt;
return (ISC_R_SUCCESS);
}
dns_rdataset_t *
dns_message_gettsig(dns_message_t *msg, dns_name_t **owner) {
/*
* Get the TSIG record and owner for 'msg'.
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(owner == NULL || *owner == NULL);
if (owner != NULL)
*owner = msg->tsigname;
return (msg->tsig);
}
isc_result_t
dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) {
isc_result_t result;
/*
* Set the TSIG key for 'msg'
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->state == DNS_SECTION_ANY);
REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL);
if (key != NULL) {
dns_tsigkey_attach(key, &msg->tsigkey);
if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) {
msg->sig_reserved = spacefortsig(msg->tsigkey, 0);
result = dns_message_renderreserve(msg,
msg->sig_reserved);
if (result != ISC_R_SUCCESS) {
msg->sig_reserved = 0;
return (result);
}
}
}
return (ISC_R_SUCCESS);
}
dns_tsigkey_t *
dns_message_gettsigkey(dns_message_t *msg) {
/*
* Get the TSIG key for 'msg'
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
return (msg->tsigkey);
}
isc_result_t
dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) {
dns_rdata_t *rdata = NULL;
dns_rdatalist_t *list = NULL;
dns_rdataset_t *set = NULL;
isc_buffer_t *buf = NULL;
isc_region_t r;
isc_result_t result;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->querytsig == NULL);
if (querytsig == NULL)
return (ISC_R_SUCCESS);
result = dns_message_gettemprdata(msg, &rdata);
if (result != ISC_R_SUCCESS)
goto cleanup;
result = dns_message_gettemprdatalist(msg, &list);
if (result != ISC_R_SUCCESS)
goto cleanup;
result = dns_message_gettemprdataset(msg, &set);
if (result != ISC_R_SUCCESS)
goto cleanup;
isc_buffer_usedregion(querytsig, &r);
result = isc_buffer_allocate(msg->mctx, &buf, r.length);
if (result != ISC_R_SUCCESS)
goto cleanup;
isc_buffer_putmem(buf, r.base, r.length);
isc_buffer_usedregion(buf, &r);
dns_rdata_init(rdata);
dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r);
dns_message_takebuffer(msg, &buf);
ISC_LIST_INIT(list->rdata);
ISC_LIST_APPEND(list->rdata, rdata, link);
result = dns_rdatalist_tordataset(list, set);
if (result != ISC_R_SUCCESS)
goto cleanup;
msg->querytsig = set;
return (result);
cleanup:
if (rdata != NULL)
dns_message_puttemprdata(msg, &rdata);
if (list != NULL)
dns_message_puttemprdatalist(msg, &list);
if (set != NULL)
dns_message_puttemprdataset(msg, &set);
return (ISC_R_NOMEMORY);
}
isc_result_t
dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
isc_buffer_t **querytsig) {
isc_result_t result;
dns_rdata_t rdata;
isc_region_t r;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(mctx != NULL);
REQUIRE(querytsig != NULL && *querytsig == NULL);
if (msg->tsig == NULL)
return (ISC_R_SUCCESS);
result = dns_rdataset_first(msg->tsig);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_current(msg->tsig, &rdata);
dns_rdata_toregion(&rdata, &r);
result = isc_buffer_allocate(mctx, querytsig, r.length);
if (result != ISC_R_SUCCESS)
return (result);
isc_buffer_putmem(*querytsig, r.base, r.length);
return (ISC_R_SUCCESS);
}
dns_rdataset_t *
dns_message_getsig0(dns_message_t *msg, dns_name_t **owner) {
/*
* Get the SIG(0) record for 'msg'.
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(owner == NULL || *owner == NULL);
if (msg->sig0 != NULL && owner != NULL) {
/* If dns_message_getsig0 is called on a rendered message
* after the SIG(0) has been applied, we need to return the
* root name, not NULL.
*/
if (msg->sig0name == NULL)
*owner = dns_rootname;
else
*owner = msg->sig0name;
}
return (msg->sig0);
}
isc_result_t
dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) {
isc_region_t r;
unsigned int x;
isc_result_t result;
/*
* Set the SIG(0) key for 'msg'
*/
/*
* The space required for an SIG(0) record is:
*
* 1 byte for the name
* 2 bytes for the type
* 2 bytes for the class
* 4 bytes for the ttl
* 2 bytes for the type covered
* 1 byte for the algorithm
* 1 bytes for the labels
* 4 bytes for the original ttl
* 4 bytes for the signature expiration
* 4 bytes for the signature inception
* 2 bytes for the key tag
* n bytes for the signer's name
* x bytes for the signature
* ---------------------------------
* 27 + n + x bytes
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
REQUIRE(msg->buffer != NULL);
REQUIRE(msg->state == DNS_SECTION_ANY);
REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL);
msg->sig0key = key;
if (key != NULL) {
dns_name_toregion(dst_key_name(key), &r);
result = dst_key_sigsize(key, &x);
if (result != ISC_R_SUCCESS) {
msg->sig_reserved = 0;
return (result);
}
msg->sig_reserved = 27 + r.length + x;
result = dns_message_renderreserve(msg, msg->sig_reserved);
if (result != ISC_R_SUCCESS) {
msg->sig_reserved = 0;
return (result);
}
}
return (ISC_R_SUCCESS);
}
dst_key_t *
dns_message_getsig0key(dns_message_t *msg) {
/*
* Get the SIG(0) key for 'msg'
*/
REQUIRE(DNS_MESSAGE_VALID(msg));
return (msg->sig0key);
}
void
dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(buffer != NULL);
REQUIRE(ISC_BUFFER_VALID(*buffer));
ISC_LIST_APPEND(msg->cleanup, *buffer, link);
*buffer = NULL;
}
isc_result_t
dns_message_signer(dns_message_t *msg, dns_name_t *signer) {
isc_region_t r;
isc_result_t result = ISC_R_SUCCESS;
dns_rdata_t rdata;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(signer != NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
if (msg->tsig == NULL && msg->sig0 == NULL)
return (ISC_R_NOTFOUND);
if (msg->verify_attempted == 0)
return (DNS_R_NOTVERIFIEDYET);
if (!dns_name_hasbuffer(signer)) {
isc_buffer_t *dynbuf = NULL;
result = isc_buffer_allocate(msg->mctx, &dynbuf, 512);
if (result != ISC_R_SUCCESS)
return (result);
dns_name_setbuffer(signer, dynbuf);
dns_message_takebuffer(msg, &dynbuf);
}
if (msg->sig0 != NULL) {
dns_rdata_sig_t sig;
result = dns_rdataset_first(msg->sig0);
INSIST(result == ISC_R_SUCCESS);
dns_rdataset_current(msg->sig0, &rdata);
result = dns_rdata_tostruct(&rdata, &sig, NULL);
if (result != ISC_R_SUCCESS)
return (result);
if (msg->verified_sig && msg->sig0status == dns_rcode_noerror)
result = ISC_R_SUCCESS;
else
result = DNS_R_SIGINVALID;
dns_name_toregion(&sig.signer, &r);
dns_name_fromregion(signer, &r);
dns_rdata_freestruct(&sig);
}
else {
dns_name_t *identity;
dns_rdata_any_tsig_t tsig;
result = dns_rdataset_first(msg->tsig);
INSIST(result == ISC_R_SUCCESS);
dns_rdataset_current(msg->tsig, &rdata);
result = dns_rdata_tostruct(&rdata, &tsig, NULL);
if (msg->tsigstatus != dns_rcode_noerror)
result = DNS_R_TSIGVERIFYFAILURE;
else if (tsig.error != dns_rcode_noerror)
result = DNS_R_TSIGERRORSET;
else
result = ISC_R_SUCCESS;
identity = dns_tsigkey_identity(msg->tsigkey);
if (identity == NULL) {
if (result == ISC_R_SUCCESS)
result = DNS_R_NOIDENTITY;
identity = &msg->tsigkey->name;
}
dns_name_toregion(identity, &r);
dns_name_fromregion(signer, &r);
dns_rdata_freestruct(&tsig);
}
return (result);
}
isc_result_t
dns_message_checksig(dns_message_t *msg, dns_view_t *view) {
isc_buffer_t b, msgb;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(view != NULL);
if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL)
return (ISC_R_SUCCESS);
INSIST(msg->saved != NULL);
isc_buffer_init(&msgb, msg->saved->base, msg->saved->length);
isc_buffer_add(&msgb, msg->saved->length);
if (msg->tsigkey != NULL || msg->tsig != NULL)
return (dns_view_checksig(view, &msgb, msg));
else {
dns_rdata_t rdata;
dns_rdata_sig_t sig;
dns_rdataset_t keyset;
isc_result_t result;
result = dns_rdataset_first(msg->sig0);
INSIST(result == ISC_R_SUCCESS);
dns_rdataset_current(msg->sig0, &rdata);
/*
* This can occur when the message is a dynamic update, since
* the rdata length checking is relaxed. This should not
* happen in a well-formed message, since the SIG(0) is only
* looked for in the additional section, and the dynamic update
* meta-records are in the prerequisite and update sections.
*/
if (rdata.length == 0)
return (ISC_R_UNEXPECTEDEND);
result = dns_rdata_tostruct(&rdata, &sig, msg->mctx);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_init(&keyset);
result = dns_view_simplefind(view, &sig.signer,
dns_rdatatype_key, 0, 0,
ISC_FALSE, &keyset, NULL);
if (result != ISC_R_SUCCESS) {
/* XXXBEW Should possibly create a fetch here */
result = DNS_R_KEYUNAUTHORIZED;
goto freesig;
} else if (keyset.trust < dns_trust_secure) {
/* XXXBEW Should call a validator here */
result = DNS_R_KEYUNAUTHORIZED;
goto freesig;
}
result = dns_rdataset_first(&keyset);
INSIST(result == ISC_R_SUCCESS);
for (;
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&keyset))
{
dst_key_t *key = NULL;
dns_rdataset_current(&keyset, &rdata);
isc_buffer_init(&b, rdata.data, rdata.length);
isc_buffer_add(&b, rdata.length);
result = dst_key_fromdns(&sig.signer, &b, view->mctx,
&key);
if (result != ISC_R_SUCCESS)
continue;
if (dst_key_alg(key) != sig.algorithm ||
dst_key_id(key) != sig.keyid ||
!(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC ||
dst_key_proto(key) == DNS_KEYPROTO_ANY))
{
dst_key_free(&key);
continue;
}
result = dns_dnssec_verifymessage(&msgb, msg, key);
dst_key_free(&key);
if (result == ISC_R_SUCCESS)
break;
}
if (result == ISC_R_NOMORE)
result = DNS_R_KEYUNAUTHORIZED;
freesig:
if (dns_rdataset_isassociated(&keyset))
dns_rdataset_disassociate(&keyset);
dns_rdata_freestruct(&sig);
return (result);
}
}
isc_result_t
dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
dns_messagetextflag_t flags,
isc_buffer_t *target) {
dns_name_t *name, empty_name;
dns_rdataset_t *rdataset;
isc_result_t result;
isc_boolean_t no_rdata;
isc_boolean_t omit_final_dot;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
REQUIRE(VALID_SECTION(section));
omit_final_dot = ISC_TF((flags & DNS_MESSAGETEXTFLAG_OMITDOT) != 0);
if (ISC_LIST_EMPTY(msg->sections[section]))
return ISC_R_SUCCESS;
if (section == DNS_SECTION_QUESTION)
no_rdata = ISC_TRUE;
else
no_rdata = ISC_FALSE;
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
ADD_STRING(target, ";; ");
ADD_STRING(target, sectiontext[section]);
ADD_STRING(target, " SECTION:\n");
}
dns_name_init(&empty_name, NULL);
result = dns_message_firstname(msg, section);
if (result != ISC_R_SUCCESS) {
return (result);
}
do {
name = NULL;
dns_message_currentname(msg, section, &name);
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (no_rdata)
ADD_STRING(target, ";");
result = dns_rdataset_totext(rdataset, name,
omit_final_dot,
no_rdata,
target);
if (result != ISC_R_SUCCESS)
return (result);
}
result = dns_message_nextname(msg, section);
} while (result == ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0)
ADD_STRING(target, "\n");
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
return (result);
}
isc_result_t
dns_message_pseudosectiontotext(dns_message_t *msg,
dns_pseudosection_t section,
dns_messagetextflag_t flags,
isc_buffer_t *target) {
dns_rdataset_t *ps = NULL;
dns_name_t *name = NULL;
isc_result_t result;
char buf[sizeof("1234567890")];
isc_boolean_t omit_final_dot;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
REQUIRE(VALID_PSEUDOSECTION(section));
omit_final_dot = ISC_TF((flags & DNS_MESSAGETEXTFLAG_OMITDOT) != 0);
switch (section) {
case DNS_PSEUDOSECTION_OPT:
ps = dns_message_getopt(msg);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, ";; OPT PSEUDOSECTION:\n");
ADD_STRING(target, "; EDNS: version: ");
sprintf(buf, "%4u",
(unsigned int)((ps->ttl &
0x00ff0000 >> 16)));
ADD_STRING(target, buf);
ADD_STRING(target, ", udp=");
sprintf(buf, "%7u\n",
(unsigned int)ps->rdclass);
ADD_STRING(target, buf);
return (ISC_R_SUCCESS);
case DNS_PSEUDOSECTION_TSIG:
ps = dns_message_gettsig(msg, &name);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n");
result = dns_rdataset_totext(ps, name, omit_final_dot,
ISC_FALSE, target);
ADD_STRING(target, "\n");
return (result);
case DNS_PSEUDOSECTION_SIG0:
ps = dns_message_getsig0(msg, &name);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n");
result = dns_rdataset_totext(ps, name, omit_final_dot,
ISC_FALSE, target);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0)
ADD_STRING(target, "\n");
return (result);
}
return (ISC_R_UNEXPECTED);
}
isc_result_t
dns_message_totext(dns_message_t *msg, dns_messagetextflag_t flags,
isc_buffer_t *target) {
char buf[sizeof "1234567890"];
isc_result_t result;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) {
ADD_STRING(target, ";; ->>HEADER<<- opcode: ");
ADD_STRING(target, opcodetext[msg->opcode]);
ADD_STRING(target, ", status: ");
ADD_STRING(target, rcodetext[msg->rcode]);
ADD_STRING(target, ", id: ");
sprintf(buf, "%6u", msg->id);
ADD_STRING(target, buf);
ADD_STRING(target, "\n;; flags: ");
if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
ADD_STRING(target, "qr ");
if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0)
ADD_STRING(target, "aa ");
if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0)
ADD_STRING(target, "tc ");
if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0)
ADD_STRING(target, "rd ");
if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0)
ADD_STRING(target, "ra ");
if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0)
ADD_STRING(target, "ad ");
if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0)
ADD_STRING(target, "cd ");
ADD_STRING(target, "\n; QUESTION: ");
sprintf(buf, "%3u", msg->counts[DNS_SECTION_QUESTION]);
ADD_STRING(target, buf);
ADD_STRING(target, ", ANSWER: ");
sprintf(buf, "%3u", msg->counts[DNS_SECTION_ANSWER]);
ADD_STRING(target, buf);
ADD_STRING(target, ", AUTHORITY: ");
sprintf(buf, "%3u", msg->counts[DNS_SECTION_AUTHORITY]);
ADD_STRING(target, buf);
ADD_STRING(target, ", ADDITIONAL: ");
sprintf(buf, "%3u", msg->counts[DNS_SECTION_ADDITIONAL]);
ADD_STRING(target, buf);
ADD_STRING(target, "\n");
}
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_OPT,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_QUESTION,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_ANSWER,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_AUTHORITY,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_ADDITIONAL,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_TSIG,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_SIG0,
flags, target);
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
}