message.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 1999-2016 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*! \file */
/***
*** Imports
***/
#include <config.h>
#include <ctype.h>
#include <isc/buffer.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/string.h> /* Required for HP/UX (and others?) */
#include <isc/util.h>
#include <dns/dnssec.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dns/masterdump.h>
#include <dns/message.h>
#include <dns/opcode.h>
#include <dns/rcode.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/ttl.h>
#include <dns/view.h>
#ifdef SKAN_MSG_DEBUG
static void
hexdump(const char *msg, const char *msg2, void *base, size_t len) {
unsigned char *p;
unsigned int cnt;
p = base;
cnt = 0;
printf("*** %s [%s] (%u bytes @ %p)\n", msg, msg2, len, base);
while (cnt < len) {
if (cnt % 16 == 0)
printf("%p: ", p);
else if (cnt % 8 == 0)
printf(" |");
printf(" %02x %c", *p, (isprint(*p) ? *p : ' '));
p++;
cnt++;
if (cnt % 16 == 0)
printf("\n");
}
if (cnt % 16 != 0)
printf("\n");
}
#endif
#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))
#define OPTOUT(x) (((x)->attributes & DNS_RDATASETATTR_OPTOUT) != 0)
/*%
* 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 OFFSET_COUNT 4
#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 *updsectiontext[] = {
"ZONE",
"PREREQUISITE",
"UPDATE",
"ADDITIONAL"
};
static const char *opcodetext[] = {
"QUERY",
"IQUERY",
"STATUS",
"RESERVED3",
"NOTIFY",
"UPDATE",
"RESERVED6",
"RESERVED7",
"RESERVED8",
"RESERVED9",
"RESERVED10",
"RESERVED11",
"RESERVED12",
"RESERVED13",
"RESERVED14",
"RESERVED15"
};
/*%
* "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 aligned 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);
static void
logfmtpacket(dns_message_t *message, const char *description,
isc_sockaddr_t *address, isc_logcategory_t *category,
isc_logmodule_t *module, const dns_master_style_t *style,
int level, isc_mem_t *mctx);
/*
* 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);
}
dns_rdata_init(rdata);
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);
goto out;
}
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);
}
out:
if (rdatalist != NULL)
dns_rdatalist_init(rdatalist);
return (rdatalist);
}
static inline dns_offsets_t *
newoffsets(dns_message_t *msg) {
dns_msgblock_t *msgblock;
dns_offsets_t *offsets;
msgblock = ISC_LIST_TAIL(msg->offsets);
offsets = msgblock_get(msgblock, dns_offsets_t);
if (offsets == NULL) {
msgblock = msgblock_allocate(msg->mctx,
sizeof(dns_offsets_t),
OFFSET_COUNT);
if (msgblock == NULL)
return (NULL);
ISC_LIST_APPEND(msg->offsets, msgblock, link);
offsets = msgblock_get(msgblock, dns_offsets_t);
}
return (offsets);
}
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;
}
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->timeadjust = 0;
}
/*
* 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;
m->order = NULL;
m->order_arg = NULL;
m->query.base = NULL;
m->query.length = 0;
m->free_query = 0;
m->saved.base = NULL;
m->saved.length = 0;
m->free_saved = 0;
m->cc_ok = 0;
m->cc_bad = 0;
m->tkey = 0;
m->rdclass_set = 0;
m->querytsig = NULL;
}
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;
}
if (dns_name_dynamic(name))
dns_name_free(name, msg->mctx);
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;
msg->cc_ok = 0;
msg->cc_bad = 0;
}
}
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);
}
}
if (dns_name_dynamic(msg->tsigname))
dns_name_free(msg->tsigname, msg->mctx);
isc_mempool_put(msg->namepool, msg->tsigname);
msg->tsig = NULL;
msg->tsigname = NULL;
} else if (msg->querytsig != NULL && !replying) {
dns_rdataset_disassociate(msg->querytsig);
isc_mempool_put(msg->rdspool, msg->querytsig);
msg->querytsig = 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) {
if (dns_name_dynamic(msg->sig0name))
dns_name_free(msg->sig0name, msg->mctx);
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_destroy().
*/
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);
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->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;
}
msgblock = ISC_LIST_HEAD(msg->offsets);
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->offsets, msgblock, link);
msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t));
msgblock = next_msgblock;
}
if (msg->tsigkey != NULL) {
dns_tsigkey_detach(&msg->tsigkey);
msg->tsigkey = NULL;
}
if (msg->tsigctx != NULL)
dst_context_destroy(&msg->tsigctx);
if (msg->query.base != NULL) {
if (msg->free_query != 0)
isc_mem_put(msg->mctx, msg->query.base,
msg->query.length);
msg->query.base = NULL;
msg->query.length = 0;
}
if (msg->saved.base != NULL) {
if (msg->free_saved != 0)
isc_mem_put(msg->mctx, msg->saved.base,
msg->saved.length);
msg->saved.base = NULL;
msg->saved.length = 0;
}
/*
* 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);
ENSURE(isc_mempool_getallocated(msg->namepool) == 0);
ENSURE(isc_mempool_getallocated(msg->rdspool) == 0);
}
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
* 2 bytes for the rdlength
* n2 bytes for the algorithm name
* 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)
* ---------------------------------
* 26 + 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 (26 + 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;
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 = NULL;
isc_mem_attach(mctx, &m->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->offsets);
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_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_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);
m->cctx = NULL;
*msgp = m;
return (ISC_R_SUCCESS);
/*
* Cleanup for error returns.
*/
cleanup:
dynbuf = ISC_LIST_HEAD(m->scratchpad);
if (dynbuf != NULL) {
ISC_LIST_UNLINK(m->scratchpad, dynbuf, link);
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_putanddetach(&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_putanddetach(&msg->mctx, msg, sizeof(dns_message_t));
}
static isc_result_t
findname(dns_name_t **foundname, dns_name_t *target,
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)) {
if (foundname != NULL)
*foundname = curr;
return (ISC_R_SUCCESS);
}
}
return (ISC_R_NOTFOUND);
}
isc_result_t
dns_message_find(dns_name_t *name, dns_rdataclass_t rdclass,
dns_rdatatype_t type, dns_rdatatype_t covers,
dns_rdataset_t **rdataset)
{
dns_rdataset_t *curr;
REQUIRE(name != NULL);
REQUIRE(rdataset == NULL || *rdataset == NULL);
for (curr = ISC_LIST_TAIL(name->list);
curr != NULL;
curr = ISC_LIST_PREV(curr, link)) {
if (curr->rdclass == rdclass &&
curr->type == type && curr->covers == covers) {
if (rdataset != NULL)
*rdataset = 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;
REQUIRE(name != NULL);
REQUIRE(rdataset == NULL || *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 (ISC_UNLIKELY(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) {
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);
dns_name_reset(name);
} 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;
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, 0,
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);
}
}
}
#define DO_ERROR(r) \
do { \
if (best_effort) \
seen_problem = ISC_TRUE; \
else { \
result = r; \
goto cleanup; \
} \
} while (0)
static isc_result_t
getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
unsigned int options)
{
isc_region_t r;
unsigned int count;
dns_name_t *name;
dns_name_t *name2;
dns_offsets_t *offsets;
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;
isc_boolean_t best_effort;
isc_boolean_t seen_problem;
section = &msg->sections[DNS_SECTION_QUESTION];
best_effort = ISC_TF(options & DNS_MESSAGEPARSE_BESTEFFORT);
seen_problem = ISC_FALSE;
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;
offsets = newoffsets(msg);
if (offsets == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
dns_name_init(name, *offsets);
/*
* 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, 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))
DO_ERROR(DNS_R_FORMERR);
ISC_LIST_APPEND(*section, name, link);
free_name = ISC_FALSE;
} 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->rdclass_set == 0) {
msg->rdclass = rdclass;
msg->rdclass_set = 1;
} else if (msg->rdclass != rdclass)
DO_ERROR(DNS_R_FORMERR);
/*
* Is this a TKEY query?
*/
if (rdtype == dns_rdatatype_tkey)
msg->tkey = 1;
/*
* Can't ask the same question twice.
*/
result = dns_message_find(name, rdclass, rdtype, 0, NULL);
if (result == ISC_R_SUCCESS)
DO_ERROR(DNS_R_FORMERR);
/*
* 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->rdclass = rdclass;
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;
}
if (seen_problem)
return (DNS_R_RECOVERABLE);
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_boolean_t
update(dns_section_t section, dns_rdataclass_t rdclass) {
if (section == DNS_SECTION_PREREQUISITE)
return (ISC_TF(rdclass == dns_rdataclass_any ||
rdclass == dns_rdataclass_none));
if (section == DNS_SECTION_UPDATE)
return (ISC_TF(rdclass == dns_rdataclass_any));
return (ISC_FALSE);
}
static isc_result_t
getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
dns_section_t sectionid, unsigned int options)
{
isc_region_t r;
unsigned int count, rdatalen;
dns_name_t *name;
dns_name_t *name2;
dns_offsets_t *offsets;
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;
isc_boolean_t preserve_order, best_effort, seen_problem;
isc_boolean_t issigzero;
preserve_order = ISC_TF(options & DNS_MESSAGEPARSE_PRESERVEORDER);
best_effort = ISC_TF(options & DNS_MESSAGEPARSE_BESTEFFORT);
seen_problem = ISC_FALSE;
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_rdataset = ISC_FALSE;
name = isc_mempool_get(msg->namepool);
if (name == NULL)
return (ISC_R_NOMEMORY);
free_name = ISC_TRUE;
offsets = newoffsets(msg);
if (offsets == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
dns_name_init(name, *offsets);
/*
* 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->rdclass_set == 0 &&
rdtype != dns_rdatatype_opt && /* class is UDP SIZE */
rdtype != dns_rdatatype_tsig && /* class is ANY */
rdtype != dns_rdatatype_tkey) { /* class is undefined */
msg->rdclass = rdclass;
msg->rdclass_set = 1;
}
/*
* 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 /* in a TKEY query */
&& rdtype != dns_rdatatype_sig /* SIG(0) */
&& rdtype != dns_rdatatype_tkey /* Win2000 TKEY */
&& msg->rdclass != dns_rdataclass_any
&& msg->rdclass != rdclass)
DO_ERROR(DNS_R_FORMERR);
/*
* If this is not a TKEY query/response then the KEY
* record's class needs to match.
*/
if (msg->opcode != dns_opcode_update && !msg->tkey &&
rdtype == dns_rdatatype_key &&
msg->rdclass != dns_rdataclass_any &&
msg->rdclass != rdclass)
DO_ERROR(DNS_R_FORMERR);
/*
* 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.
*/
if (sectionid != DNS_SECTION_ADDITIONAL ||
rdclass != dns_rdataclass_any ||
count != msg->counts[sectionid] - 1)
DO_ERROR(DNS_R_BADTSIG);
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)
DO_ERROR(DNS_R_FORMERR);
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. Unless it's a Win2000 client.
*
* 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 &&
sectionid != DNS_SECTION_ANSWER)
DO_ERROR(DNS_R_FORMERR);
}
/*
* ... 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;
}
if (msg->opcode == dns_opcode_update &&
update(sectionid, rdclass)) {
if (rdatalen != 0) {
result = DNS_R_FORMERR;
goto cleanup;
}
/*
* 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;
rdata->flags = DNS_RDATA_UPDATE;
result = ISC_R_SUCCESS;
} else if (rdclass == dns_rdataclass_none &&
msg->opcode == dns_opcode_update &&
sectionid == DNS_SECTION_UPDATE) {
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;
issigzero = ISC_FALSE;
if (rdtype == dns_rdatatype_rrsig &&
rdata->flags == 0) {
covers = dns_rdata_covers(rdata);
if (covers == 0)
DO_ERROR(DNS_R_FORMERR);
} else if (rdtype == dns_rdatatype_sig /* SIG(0) */ &&
rdata->flags == 0) {
covers = dns_rdata_covers(rdata);
if (covers == 0) {
if (sectionid != DNS_SECTION_ADDITIONAL ||
count != msg->counts[sectionid] - 1)
DO_ERROR(DNS_R_BADSIG0);
msg->sigstart = recstart;
skip_name_search = ISC_TRUE;
skip_type_search = ISC_TRUE;
issigzero = ISC_TRUE;
} else {
if (msg->rdclass != dns_rdataclass_any &&
msg->rdclass != rdclass)
DO_ERROR(DNS_R_FORMERR);
}
} else
covers = 0;
/*
* Check the ownername of NSEC3 records
*/
if (rdtype == dns_rdatatype_nsec3 &&
!dns_rdata_checkowner(name, msg->rdclass, rdtype,
ISC_FALSE)) {
result = DNS_R_BADOWNERNAME;
goto cleanup;
}
/*
* If we are doing a dynamic update or this is a meta-type,
* 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 &&
!issigzero)
{
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, 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 meta-type.
*/
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))
DO_ERROR(DNS_R_FORMERR);
rdataset = NULL;
result = dns_message_find(name, rdclass, 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)) {
dns_rdata_t *first;
dns_rdatalist_fromrdataset(rdataset,
&rdatalist);
first = ISC_LIST_HEAD(rdatalist->rdata);
INSIST(first != NULL);
if (dns_rdata_compare(rdata, first) != 0)
DO_ERROR(DNS_R_FORMERR);
}
}
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;
dns_rdataset_init(rdataset);
RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist,
rdataset)
== ISC_R_SUCCESS);
dns_rdataset_setownercase(rdataset, name);
if (rdtype != dns_rdatatype_opt &&
rdtype != dns_rdatatype_tsig &&
!issigzero)
{
ISC_LIST_APPEND(name->list, rdataset, link);
free_rdataset = ISC_FALSE;
}
}
/*
* Minimize TTLs.
*
* Section 5.2 of RFC2181 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;
}
/* Append this rdata to the rdataset. */
dns_rdatalist_fromrdataset(rdataset, &rdatalist);
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
/*
* If this is an OPT, SIG(0) or TSIG record, remember it.
* Also, set the extended rcode for TSIG.
*
* Note msg->opt, msg->sig0 and msg->tsig will only be
* already set if best-effort parsing is enabled otherwise
* there will only be at most one of each.
*/
if (rdtype == dns_rdatatype_opt && msg->opt == NULL) {
dns_rcode_t ercode;
msg->opt = rdataset;
rdataset = NULL;
free_rdataset = ISC_FALSE;
ercode = (dns_rcode_t)
((msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK)
>> 20);
msg->rcode |= ercode;
isc_mempool_put(msg->namepool, name);
free_name = ISC_FALSE;
} else if (issigzero && msg->sig0 == NULL) {
msg->sig0 = rdataset;
msg->sig0name = name;
rdataset = NULL;
free_rdataset = ISC_FALSE;
free_name = ISC_FALSE;
} else if (rdtype == dns_rdatatype_tsig && msg->tsig == NULL) {
msg->tsig = rdataset;
msg->tsigname = name;
/* Windows doesn't like TSIG names to be compressed. */
msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
rdataset = NULL;
free_rdataset = ISC_FALSE;
free_name = ISC_FALSE;
}
if (seen_problem) {
if (free_name)
isc_mempool_put(msg->namepool, name);
if (free_rdataset)
isc_mempool_put(msg->rdspool, rdataset);
free_name = free_rdataset = ISC_FALSE;
}
INSIST(free_name == ISC_FALSE);
INSIST(free_rdataset == ISC_FALSE);
}
if (seen_problem)
return (DNS_R_RECOVERABLE);
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,
unsigned int options)
{
isc_region_t r;
dns_decompress_t dctx;
isc_result_t ret;
isc_uint16_t tmpflags;
isc_buffer_t origsource;
isc_boolean_t seen_problem;
isc_boolean_t ignore_tc;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(source != NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
seen_problem = ISC_FALSE;
ignore_tc = ISC_TF(options & DNS_MESSAGEPARSE_IGNORETRUNCATION);
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 = (dns_rcode_t)(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;
msg->state = DNS_SECTION_QUESTION;
/*
* -1 means no EDNS.
*/
dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14);
ret = getquestions(source, msg, &dctx, options);
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
goto truncated;
if (ret == DNS_R_RECOVERABLE) {
seen_problem = ISC_TRUE;
ret = ISC_R_SUCCESS;
}
if (ret != ISC_R_SUCCESS)
return (ret);
msg->question_ok = 1;
ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER, options);
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
goto truncated;
if (ret == DNS_R_RECOVERABLE) {
seen_problem = ISC_TRUE;
ret = ISC_R_SUCCESS;
}
if (ret != ISC_R_SUCCESS)
return (ret);
ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY, options);
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
goto truncated;
if (ret == DNS_R_RECOVERABLE) {
seen_problem = ISC_TRUE;
ret = ISC_R_SUCCESS;
}
if (ret != ISC_R_SUCCESS)
return (ret);
ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL, options);
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
goto truncated;
if (ret == DNS_R_RECOVERABLE) {
seen_problem = ISC_TRUE;
ret = ISC_R_SUCCESS;
}
if (ret != ISC_R_SUCCESS)
return (ret);
isc_buffer_remainingregion(source, &r);
if (r.length != 0) {
isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_MESSAGE, ISC_LOG_DEBUG(3),
"message has %u byte(s) of trailing garbage",
r.length);
}
truncated:
if ((options & DNS_MESSAGEPARSE_CLONEBUFFER) == 0)
isc_buffer_usedregion(&origsource, &msg->saved);
else {
msg->saved.length = isc_buffer_usedlength(&origsource);
msg->saved.base = isc_mem_get(msg->mctx, msg->saved.length);
if (msg->saved.base == NULL)
return (ISC_R_NOMEMORY);
memmove(msg->saved.base, isc_buffer_base(&origsource),
msg->saved.length);
msg->free_saved = 1;
}
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
return (DNS_R_RECOVERABLE);
if (seen_problem == ISC_TRUE)
return (DNS_R_RECOVERABLE);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
isc_buffer_t *buffer)
{
isc_region_t r;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(buffer != NULL);
REQUIRE(msg->buffer == NULL);
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
msg->cctx = cctx;
/*
* 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);
if (r.length < DNS_MESSAGE_HEADERLEN)
return (ISC_R_NOSPACE);
if (r.length < msg->reserved)
return (ISC_R_NOSPACE);
/*
* 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);
memmove(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, dns_rdatatype_t preferred_glue) {
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:
if (preferred_glue == rds->type)
pass_needed = 4;
else
pass_needed = 3;
break;
case dns_rdatatype_rrsig:
case dns_rdatatype_dnskey:
pass_needed = 2;
break;
default:
pass_needed = 1;
}
if (pass_needed >= pass)
return (ISC_FALSE);
return (ISC_TRUE);
}
#ifdef ALLOW_FILTER_AAAA
/*
* Decide whether to not answer with an AAAA record and its RRSIG
*/
static inline isc_boolean_t
norender_rdataset(const dns_rdataset_t *rdataset, unsigned int options,
dns_section_t sectionid)
{
if (sectionid == DNS_SECTION_QUESTION)
return (ISC_FALSE);
switch (rdataset->type) {
case dns_rdatatype_ns:
if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 ||
sectionid != DNS_SECTION_AUTHORITY)
return (ISC_FALSE);
break;
case dns_rdatatype_aaaa:
if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0)
return (ISC_FALSE);
break;
case dns_rdatatype_rrsig:
if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 ||
(rdataset->covers != dns_rdatatype_ns &&
rdataset->covers != dns_rdatatype_aaaa))
return (ISC_FALSE);
if ((rdataset->covers == dns_rdatatype_ns) &&
(sectionid != DNS_SECTION_AUTHORITY))
return (ISC_FALSE);
break;
default:
return (ISC_FALSE);
}
if (rdataset->rdclass != dns_rdataclass_in)
return (ISC_FALSE);
return (ISC_TRUE);
}
#endif
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;
isc_boolean_t partial = ISC_FALSE;
unsigned int rd_options;
dns_rdatatype_t preferred_glue = 0;
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) {
if ((options & DNS_MESSAGERENDER_PREFER_A) != 0) {
preferred_glue = dns_rdatatype_a;
pass = 4;
} else if ((options & DNS_MESSAGERENDER_PREFER_AAAA) != 0) {
preferred_glue = dns_rdatatype_aaaa;
pass = 4;
} else
pass = 3;
} else
pass = 1;
if ((options & DNS_MESSAGERENDER_OMITDNSSEC) == 0)
rd_options = 0;
else
rd_options = DNS_RDATASETTOWIRE_OMITDNSSEC;
/*
* Shrink the space in the buffer by the reserved amount.
*/
msg->buffer->length -= msg->reserved;
total = 0;
if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0)
partial = ISC_TRUE;
/*
* Render required glue first. Set TC if it won't fit.
*/
name = ISC_LIST_HEAD(*section);
if (name != NULL) {
rdataset = ISC_LIST_HEAD(name->list);
if (rdataset != NULL &&
(rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) != 0 &&
(rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0) {
const void *order_arg = msg->order_arg;
st = *(msg->buffer);
count = 0;
if (partial)
result = dns_rdataset_towirepartial(rdataset,
name,
msg->cctx,
msg->buffer,
msg->order,
order_arg,
rd_options,
&count,
NULL);
else
result = dns_rdataset_towiresorted(rdataset,
name,
msg->cctx,
msg->buffer,
msg->order,
order_arg,
rd_options,
&count);
total += count;
if (partial && result == ISC_R_NOSPACE) {
msg->flags |= DNS_MESSAGEFLAG_TC;
msg->buffer->length += msg->reserved;
msg->counts[sectionid] += total;
return (result);
}
if (result == ISC_R_NOSPACE)
msg->flags |= DNS_MESSAGEFLAG_TC;
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);
}
rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
}
}
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,
preferred_glue))
goto next;
#ifdef ALLOW_FILTER_AAAA
/*
* Suppress AAAAs if asked and we are
* not doing DNSSEC or are breaking DNSSEC.
* Say so in the AD bit if we break DNSSEC.
*/
if (norender_rdataset(rdataset, options, sectionid)) {
if (sectionid == DNS_SECTION_ANSWER ||
sectionid == DNS_SECTION_AUTHORITY)
msg->flags &= ~DNS_MESSAGEFLAG_AD;
if (OPTOUT(rdataset))
msg->flags &= ~DNS_MESSAGEFLAG_AD;
goto next;
}
#endif
st = *(msg->buffer);
count = 0;
if (partial)
result = dns_rdataset_towirepartial(
rdataset,
name,
msg->cctx,
msg->buffer,
msg->order,
msg->order_arg,
rd_options,
&count,
NULL);
else
result = dns_rdataset_towiresorted(
rdataset,
name,
msg->cctx,
msg->buffer,
msg->order,
msg->order_arg,
rd_options,
&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 arbitrary 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 (partial && result == ISC_R_NOSPACE) {
msg->buffer->length += msg->reserved;
msg->counts[sectionid] += total;
return (result);
}
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 non-validated data,
* ensure that the AD bit is not set.
*/
if (rdataset->trust != dns_trust_secure &&
(sectionid == DNS_SECTION_ANSWER ||
sectionid == DNS_SECTION_AUTHORITY))
msg->flags &= ~DNS_MESSAGEFLAG_AD;
if (OPTOUT(rdataset))
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're adding a OPT, TSIG or SIG(0) to a truncated message,
* clear all rdatasets from the message except for the question
* before adding the OPT, TSIG or SIG(0). If the question doesn't
* fit, don't include it.
*/
if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) &&
(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 && result != ISC_R_NOSPACE)
return (result);
}
/*
* 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, 0,
&count);
msg->counts[DNS_SECTION_ADDITIONAL] += count;
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, 0,
&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, 0,
&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 */
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;
}
}
}
if (msg->tsigname != NULL)
dns_message_puttempname(msg, &msg->tsigname);
if (msg->tsig != NULL) {
dns_rdataset_disassociate(msg->tsig);
dns_message_puttemprdataset(msg, &msg->tsig);
}
if (msg->sig0 != NULL) {
dns_rdataset_disassociate(msg->sig0);
dns_message_puttemprdataset(msg, &msg->sig0);
}
}
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;
/*
* 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);
REQUIRE(name == NULL || *name == NULL);
if (type == dns_rdatatype_any) {
REQUIRE(rdataset == NULL);
} else {
REQUIRE(rdataset == NULL || *rdataset == NULL);
}
result = findname(&foundname, target,
&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 (ISC_UNLIKELY(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);
}
void
dns_message_removename(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_UNLINK(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);
dns_name_init(*item, NULL);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_gettempoffsets(dns_message_t *msg, dns_offsets_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item == NULL);
*item = newoffsets(msg);
if (*item == NULL)
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
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_puttempname(dns_message_t *msg, dns_name_t **item) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(item != NULL && *item != NULL);
if (dns_name_dynamic(*item))
dns_name_free(*item, msg->mctx);
isc_mempool_put(msg->namepool, *item);
*item = NULL;
}
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 clear_from;
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 (msg->opcode == dns_opcode_update)
clear_from = DNS_SECTION_PREREQUISITE;
else if (want_question_section) {
if (!msg->question_ok)
return (DNS_R_FORMERR);
clear_from = DNS_SECTION_ANSWER;
} else
clear_from = DNS_SECTION_QUESTION;
msg->from_to_wire = DNS_MESSAGE_INTENTRENDER;
msgresetnames(msg, clear_from);
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->tsigkey != 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.base != NULL) {
msg->query.base = msg->saved.base;
msg->query.length = msg->saved.length;
msg->free_query = msg->free_saved;
msg->saved.base = NULL;
msg->saved.length = 0;
msg->free_saved = 0;
}
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 = DNS_RDATA_INIT;
/*
* 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->state == DNS_SECTION_ANY);
msgresetopt(msg);
result = dns_rdataset_first(opt);
if (result != ISC_R_SUCCESS)
goto cleanup;
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;
goto cleanup;
}
msg->opt = opt;
return (ISC_R_SUCCESS);
cleanup:
dns_rdataset_disassociate(opt);
dns_message_puttemprdataset(msg, &opt);
return (result);
}
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);
if (key == NULL && msg->tsigkey != NULL) {
if (msg->sig_reserved != 0) {
dns_message_renderrelease(msg, msg->sig_reserved);
msg->sig_reserved = 0;
}
dns_tsigkey_detach(&msg->tsigkey);
}
if (key != NULL) {
REQUIRE(msg->tsigkey == NULL && msg->sig0key == 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) {
dns_tsigkey_detach(&msg->tsigkey);
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_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 = DNS_RDATA_INIT;
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->state == DNS_SECTION_ANY);
if (key != NULL) {
REQUIRE(msg->sig0key == NULL && msg->tsigkey == 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);
}
msg->sig0key = key;
}
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_result_t result = ISC_R_SUCCESS;
dns_rdata_t rdata = DNS_RDATA_INIT;
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_clone(&sig.signer, signer);
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);
INSIST(result == ISC_R_SUCCESS);
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;
dns_rdata_freestruct(&tsig);
if (msg->tsigkey == NULL) {
/*
* If msg->tsigstatus & tsig.error are both
* dns_rcode_noerror, the message must have been
* verified, which means msg->tsigkey will be
* non-NULL.
*/
INSIST(result != ISC_R_SUCCESS);
} else {
identity = dns_tsigkey_identity(msg->tsigkey);
if (identity == NULL) {
if (result == ISC_R_SUCCESS)
result = DNS_R_NOIDENTITY;
identity = &msg->tsigkey->name;
}
dns_name_clone(identity, signer);
}
}
return (result);
}
void
dns_message_resetsig(dns_message_t *msg) {
REQUIRE(DNS_MESSAGE_VALID(msg));
msg->verified_sig = 0;
msg->verify_attempted = 0;
msg->tsigstatus = dns_rcode_noerror;
msg->sig0status = dns_rcode_noerror;
msg->timeadjust = 0;
if (msg->tsigkey != NULL) {
dns_tsigkey_detach(&msg->tsigkey);
msg->tsigkey = NULL;
}
}
isc_result_t
dns_message_rechecksig(dns_message_t *msg, dns_view_t *view) {
dns_message_resetsig(msg);
return (dns_message_checksig(msg, view));
}
#ifdef SKAN_MSG_DEBUG
void
dns_message_dumpsig(dns_message_t *msg, char *txt1) {
dns_rdata_t querytsigrdata = DNS_RDATA_INIT;
dns_rdata_any_tsig_t querytsig;
isc_result_t result;
if (msg->tsig != NULL) {
result = dns_rdataset_first(msg->tsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
dns_rdataset_current(msg->tsig, &querytsigrdata);
result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
hexdump(txt1, "TSIG", querytsig.signature,
querytsig.siglen);
}
if (msg->querytsig != NULL) {
result = dns_rdataset_first(msg->querytsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
dns_rdataset_current(msg->querytsig, &querytsigrdata);
result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
hexdump(txt1, "QUERYTSIG", querytsig.signature,
querytsig.siglen);
}
}
#endif
isc_result_t
dns_message_checksig(dns_message_t *msg, dns_view_t *view) {
isc_buffer_t b, msgb;
REQUIRE(DNS_MESSAGE_VALID(msg));
if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL)
return (ISC_R_SUCCESS);
INSIST(msg->saved.base != 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) {
#ifdef SKAN_MSG_DEBUG
dns_message_dumpsig(msg, "dns_message_checksig#1");
#endif
if (view != NULL)
return (dns_view_checksig(view, &msgb, msg));
else
return (dns_tsig_verify(&msgb, msg, NULL, NULL));
} else {
dns_rdata_t rdata = DNS_RDATA_INIT;
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);
if (view == NULL)
return (DNS_R_KEYUNAUTHORIZED);
result = dns_view_simplefind(view, &sig.signer,
dns_rdatatype_key /* SIG(0) */,
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_rdata_reset(&rdata);
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, rdata.rdclass,
&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,
const dns_master_style_t *style,
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 seensoa = ISC_FALSE;
unsigned int sflags = dns_master_styleflags(style);
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
REQUIRE(VALID_SECTION(section));
if (ISC_LIST_EMPTY(msg->sections[section]))
return (ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, ";; ");
if (msg->opcode != dns_opcode_update) {
ADD_STRING(target, sectiontext[section]);
} else {
ADD_STRING(target, updsectiontext[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 (section == DNS_SECTION_ANSWER &&
rdataset->type == dns_rdatatype_soa) {
if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0)
continue;
if (seensoa &&
(flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0)
continue;
seensoa = ISC_TRUE;
}
if (section == DNS_SECTION_QUESTION) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target,
dns_master_indentstr);
ADD_STRING(target, ";");
result = dns_master_questiontotext(name,
rdataset,
style,
target);
} else {
result = dns_master_rdatasettotext(name,
rdataset,
style,
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 &&
(flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, "\n");
}
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
return (result);
}
static isc_result_t
render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) {
int i;
char addr[16], addr_text[64];
isc_uint16_t family;
isc_uint8_t addrlen, addrbytes, scopelen;
/*
* Note: This routine needs to handle malformed ECS options.
*/
if (isc_buffer_remaininglength(ecsbuf) < 4)
return (DNS_R_OPTERR);
family = isc_buffer_getuint16(ecsbuf);
addrlen = isc_buffer_getuint8(ecsbuf);
scopelen = isc_buffer_getuint8(ecsbuf);
if (addrlen == 0 && family != 0)
return (DNS_R_OPTERR);
addrbytes = (addrlen + 7) / 8;
if (isc_buffer_remaininglength(ecsbuf) < addrbytes)
return (DNS_R_OPTERR);
if (addrbytes > sizeof(addr))
return (DNS_R_OPTERR);
ADD_STRING(target, ": ");
memset(addr, 0, sizeof(addr));
for (i = 0; i < addrbytes; i ++)
addr[i] = isc_buffer_getuint8(ecsbuf);
switch (family) {
case 0:
if (addrlen != 0U || scopelen != 0U)
return (DNS_R_OPTERR);
strlcpy(addr_text, "0", sizeof(addr_text));
break;
case 1:
if (addrlen > 32 || scopelen > 32)
return (DNS_R_OPTERR);
inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text));
break;
case 2:
if (addrlen > 128 || scopelen > 128)
return (DNS_R_OPTERR);
inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text));
break;
default:
snprintf(addr_text, sizeof(addr_text),
"Unsupported family %u", family);
ADD_STRING(target, addr_text);
return (ISC_R_SUCCESS);
}
ADD_STRING(target, addr_text);
snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen);
ADD_STRING(target, addr_text);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_message_pseudosectiontotext(dns_message_t *msg,
dns_pseudosection_t section,
const dns_master_style_t *style,
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_uint32_t mbz;
dns_rdata_t rdata;
isc_buffer_t optbuf;
isc_uint16_t optcode, optlen;
unsigned char *optdata;
unsigned int sflags = dns_master_styleflags(style);
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
REQUIRE(VALID_PSEUDOSECTION(section));
switch (section) {
case DNS_PSEUDOSECTION_OPT:
ps = dns_message_getopt(msg);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, ";; OPT PSEUDOSECTION:\n");
}
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, "; EDNS: version: ");
snprintf(buf, sizeof(buf), "%u",
(unsigned int)((ps->ttl & 0x00ff0000) >> 16));
ADD_STRING(target, buf);
ADD_STRING(target, ", flags:");
if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0)
ADD_STRING(target, " do");
mbz = ps->ttl & 0xffff;
mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */
if (mbz != 0) {
ADD_STRING(target, "; MBZ: ");
snprintf(buf, sizeof(buf), "0x%.4x", mbz);
ADD_STRING(target, buf);
ADD_STRING(target, ", udp: ");
} else
ADD_STRING(target, "; udp: ");
snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
ADD_STRING(target, buf);
result = dns_rdataset_first(ps);
if (result != ISC_R_SUCCESS)
return (ISC_R_SUCCESS);
/*
* Print EDNS info, if any.
*
* WARNING: The option contents may be malformed as
* dig +ednsopt=value:<content> does not validity
* checking.
*/
dns_rdata_init(&rdata);
dns_rdataset_current(ps, &rdata);
isc_buffer_init(&optbuf, rdata.data, rdata.length);
isc_buffer_add(&optbuf, rdata.length);
while (isc_buffer_remaininglength(&optbuf) != 0) {
INSIST(isc_buffer_remaininglength(&optbuf) >= 4U);
optcode = isc_buffer_getuint16(&optbuf);
optlen = isc_buffer_getuint16(&optbuf);
INSIST(isc_buffer_remaininglength(&optbuf) >= optlen);
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
if (optcode == DNS_OPT_NSID) {
ADD_STRING(target, "; NSID");
} else if (optcode == DNS_OPT_COOKIE) {
ADD_STRING(target, "; COOKIE");
} else if (optcode == DNS_OPT_CLIENT_SUBNET) {
isc_buffer_t ecsbuf;
ADD_STRING(target, "; CLIENT-SUBNET");
isc_buffer_init(&ecsbuf,
isc_buffer_current(&optbuf),
optlen);
isc_buffer_add(&ecsbuf, optlen);
result = render_ecs(&ecsbuf, target);
if (result == ISC_R_NOSPACE)
return (result);
if (result == ISC_R_SUCCESS) {
isc_buffer_forward(&optbuf, optlen);
ADD_STRING(target, "\n");
continue;
}
} else if (optcode == DNS_OPT_EXPIRE) {
if (optlen == 4) {
isc_uint32_t secs;
secs = isc_buffer_getuint32(&optbuf);
ADD_STRING(target, "; EXPIRE: ");
snprintf(buf, sizeof(buf), "%u", secs);
ADD_STRING(target, buf);
ADD_STRING(target, " (");
result = dns_ttl_totext(secs,
ISC_TRUE,
target);
if (result != ISC_R_SUCCESS)
return (result);
ADD_STRING(target, ")\n");
continue;
}
ADD_STRING(target, "; EXPIRE");
} else if (optcode == DNS_OPT_PAD) {
ADD_STRING(target, "; PAD");
} else {
ADD_STRING(target, "; OPT=");
snprintf(buf, sizeof(buf), "%u", optcode);
ADD_STRING(target, buf);
}
if (optlen != 0) {
int i;
ADD_STRING(target, ": ");
optdata = isc_buffer_current(&optbuf);
for (i = 0; i < optlen; i++) {
const char *sep;
switch (optcode) {
case DNS_OPT_COOKIE:
sep = "";
break;
default:
sep = " ";
break;
}
snprintf(buf, sizeof(buf), "%02x%s",
optdata[i], sep);
ADD_STRING(target, buf);
}
isc_buffer_forward(&optbuf, optlen);
if (optcode == DNS_OPT_COOKIE) {
if (msg->cc_ok)
ADD_STRING(target, " (good)");
if (msg->cc_bad)
ADD_STRING(target, " (bad)");
ADD_STRING(target, "\n");
continue;
}
if (optcode == DNS_OPT_CLIENT_SUBNET) {
ADD_STRING(target, "\n");
continue;
}
/*
* For non-COOKIE options, add a printable
* version
*/
ADD_STRING(target, "(\"");
if (isc_buffer_availablelength(target) < optlen)
return (ISC_R_NOSPACE);
for (i = 0; i < optlen; i++) {
if (isprint(optdata[i]))
isc_buffer_putmem(target,
&optdata[i],
1);
else
isc_buffer_putstr(target, ".");
}
ADD_STRING(target, "\")");
}
ADD_STRING(target, "\n");
}
return (ISC_R_SUCCESS);
case DNS_PSEUDOSECTION_TSIG:
ps = dns_message_gettsig(msg, &name);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n");
result = dns_master_rdatasettotext(name, ps, style, target);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
(flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, "\n");
return (result);
case DNS_PSEUDOSECTION_SIG0:
ps = dns_message_getsig0(msg, &name);
if (ps == NULL)
return (ISC_R_SUCCESS);
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n");
result = dns_master_rdatasettotext(name, ps, style, target);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
(flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
ADD_STRING(target, "\n");
return (result);
}
return (ISC_R_UNEXPECTED);
}
isc_result_t
dns_message_totext(dns_message_t *msg, const dns_master_style_t *style,
dns_messagetextflag_t flags, isc_buffer_t *target)
{
unsigned int sflags = dns_master_styleflags(style);
char buf[sizeof("1234567890")];
isc_result_t result;
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(target != NULL);
if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, ";; ->>HEADER<<- opcode: ");
ADD_STRING(target, opcodetext[msg->opcode]);
ADD_STRING(target, ", status: ");
result = dns_rcode_totext(msg->rcode, target);
if (result != ISC_R_SUCCESS)
return (result);
ADD_STRING(target, ", id: ");
snprintf(buf, sizeof(buf), "%6u", msg->id);
ADD_STRING(target, buf);
ADD_STRING(target, "\n");
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, ";; 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");
/*
* The final unnamed flag must be zero.
*/
if ((msg->flags & 0x0040U) != 0) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, "; MBZ: 0x4");
}
if (msg->opcode != dns_opcode_update) {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, "; QUESTION: ");
} else {
if ((sflags & DNS_STYLEFLAG_INDENT) != 0)
ADD_STRING(target, dns_master_indentstr);
ADD_STRING(target, "; ZONE: ");
}
snprintf(buf, sizeof(buf), "%1u",
msg->counts[DNS_SECTION_QUESTION]);
ADD_STRING(target, buf);
if (msg->opcode != dns_opcode_update) {
ADD_STRING(target, ", ANSWER: ");
} else {
ADD_STRING(target, ", PREREQ: ");
}
snprintf(buf, sizeof(buf), "%1u",
msg->counts[DNS_SECTION_ANSWER]);
ADD_STRING(target, buf);
if (msg->opcode != dns_opcode_update) {
ADD_STRING(target, ", AUTHORITY: ");
} else {
ADD_STRING(target, ", UPDATE: ");
}
snprintf(buf, sizeof(buf), "%1u",
msg->counts[DNS_SECTION_AUTHORITY]);
ADD_STRING(target, buf);
ADD_STRING(target, ", ADDITIONAL: ");
snprintf(buf, sizeof(buf), "%1u",
msg->counts[DNS_SECTION_ADDITIONAL]);
ADD_STRING(target, buf);
ADD_STRING(target, "\n");
}
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_OPT,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_QUESTION,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_ANSWER,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_AUTHORITY,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_sectiontotext(msg, DNS_SECTION_ADDITIONAL,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_TSIG,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_pseudosectiontotext(msg,
DNS_PSEUDOSECTION_SIG0,
style, flags, target);
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
}
isc_region_t *
dns_message_getrawmessage(dns_message_t *msg) {
REQUIRE(DNS_MESSAGE_VALID(msg));
return (&msg->saved);
}
void
dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order,
const void *order_arg)
{
REQUIRE(DNS_MESSAGE_VALID(msg));
msg->order = order;
msg->order_arg = order_arg;
}
void
dns_message_settimeadjust(dns_message_t *msg, int timeadjust) {
REQUIRE(DNS_MESSAGE_VALID(msg));
msg->timeadjust = timeadjust;
}
int
dns_message_gettimeadjust(dns_message_t *msg) {
REQUIRE(DNS_MESSAGE_VALID(msg));
return (msg->timeadjust);
}
isc_result_t
dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) {
REQUIRE(opcode < 16);
if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode]))
return (ISC_R_NOSPACE);
isc_buffer_putstr(target, opcodetext[opcode]);
return (ISC_R_SUCCESS);
}
void
dns_message_logpacket(dns_message_t *message, const char *description,
isc_logcategory_t *category, isc_logmodule_t *module,
int level, isc_mem_t *mctx)
{
logfmtpacket(message, description, NULL, category, module,
&dns_master_style_debug, level, mctx);
}
void
dns_message_logpacket2(dns_message_t *message,
const char *description, isc_sockaddr_t *address,
isc_logcategory_t *category, isc_logmodule_t *module,
int level, isc_mem_t *mctx)
{
REQUIRE(address != NULL);
logfmtpacket(message, description, address, category, module,
&dns_master_style_debug, level, mctx);
}
void
dns_message_logfmtpacket(dns_message_t *message, const char *description,
isc_logcategory_t *category, isc_logmodule_t *module,
const dns_master_style_t *style, int level,
isc_mem_t *mctx)
{
logfmtpacket(message, description, NULL, category, module, style,
level, mctx);
}
void
dns_message_logfmtpacket2(dns_message_t *message,
const char *description, isc_sockaddr_t *address,
isc_logcategory_t *category, isc_logmodule_t *module,
const dns_master_style_t *style, int level,
isc_mem_t *mctx)
{
REQUIRE(address != NULL);
logfmtpacket(message, description, address, category, module, style,
level, mctx);
}
static void
logfmtpacket(dns_message_t *message, const char *description,
isc_sockaddr_t *address, isc_logcategory_t *category,
isc_logmodule_t *module, const dns_master_style_t *style,
int level, isc_mem_t *mctx)
{
char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
const char *newline = "\n";
const char *space = " ";
isc_buffer_t buffer;
char *buf = NULL;
int len = 1024;
isc_result_t result;
if (! isc_log_wouldlog(dns_lctx, level))
return;
/*
* Note that these are multiline debug messages. We want a newline
* to appear in the log after each message.
*/
if (address != NULL)
isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
else
newline = space = "";
do {
buf = isc_mem_get(mctx, len);
if (buf == NULL)
break;
isc_buffer_init(&buffer, buf, len);
result = dns_message_totext(message, style, 0, &buffer);
if (result == ISC_R_NOSPACE) {
isc_mem_put(mctx, buf, len);
len += 1024;
} else if (result == ISC_R_SUCCESS)
isc_log_write(dns_lctx, category, module, level,
"%s%s%s%s%.*s", description, space,
addrbuf, newline,
(int)isc_buffer_usedlength(&buffer),
buf);
} while (result == ISC_R_NOSPACE);
if (buf != NULL)
isc_mem_put(mctx, buf, len);
}
isc_result_t
dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp,
unsigned int version, isc_uint16_t udpsize,
unsigned int flags, dns_ednsopt_t *ednsopts, size_t count)
{
dns_rdataset_t *rdataset = NULL;
dns_rdatalist_t *rdatalist = NULL;
dns_rdata_t *rdata = NULL;
isc_result_t result;
unsigned int len = 0, i;
REQUIRE(DNS_MESSAGE_VALID(message));
REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);
result = dns_message_gettemprdatalist(message, &rdatalist);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_message_gettemprdata(message, &rdata);
if (result != ISC_R_SUCCESS)
goto cleanup;
result = dns_message_gettemprdataset(message, &rdataset);
if (result != ISC_R_SUCCESS)
goto cleanup;
rdatalist->type = dns_rdatatype_opt;
/*
* Set Maximum UDP buffer size.
*/
rdatalist->rdclass = udpsize;
/*
* Set EXTENDED-RCODE and Z to 0.
*/
rdatalist->ttl = (version << 16);
rdatalist->ttl |= (flags & 0xffff);
/*
* Set EDNS options if applicable
*/
if (count != 0U) {
isc_buffer_t *buf = NULL;
for (i = 0; i < count; i++)
len += ednsopts[i].length + 4;
if (len > 0xffffU) {
result = ISC_R_NOSPACE;
goto cleanup;
}
result = isc_buffer_allocate(message->mctx, &buf, len);
if (result != ISC_R_SUCCESS)
goto cleanup;
for (i = 0; i < count; i++) {
isc_buffer_putuint16(buf, ednsopts[i].code);
isc_buffer_putuint16(buf, ednsopts[i].length);
isc_buffer_putmem(buf, ednsopts[i].value,
ednsopts[i].length);
}
rdata->data = isc_buffer_base(buf);
rdata->length = len;
dns_message_takebuffer(message, &buf);
} else {
rdata->data = NULL;
rdata->length = 0;
}
rdata->rdclass = rdatalist->rdclass;
rdata->type = rdatalist->type;
rdata->flags = 0;
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
result = dns_rdatalist_tordataset(rdatalist, rdataset);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
*rdatasetp = rdataset;
return (ISC_R_SUCCESS);
cleanup:
if (rdata != NULL)
dns_message_puttemprdata(message, &rdata);
if (rdataset != NULL)
dns_message_puttemprdataset(message, &rdataset);
if (rdatalist != NULL)
dns_message_puttemprdatalist(message, &rdatalist);
return (result);
}
void
dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {
REQUIRE(DNS_MESSAGE_VALID(msg));
REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
REQUIRE(msg->state == DNS_SECTION_ANY);
REQUIRE(msg->rdclass_set == 0);
msg->rdclass = rdclass;
msg->rdclass_set = 1;
}