diff options
Diffstat (limited to 'contrib/bind9/lib/dns/message.c')
-rw-r--r-- | contrib/bind9/lib/dns/message.c | 3160 |
1 files changed, 3160 insertions, 0 deletions
diff --git a/contrib/bind9/lib/dns/message.c b/contrib/bind9/lib/dns/message.c new file mode 100644 index 0000000..badde6e --- /dev/null +++ b/contrib/bind9/lib/dns/message.c @@ -0,0 +1,3160 @@ +/* + * Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2003 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* $Id: message.c,v 1.194.2.10.2.17 2004/05/05 01:32:16 marka Exp $ */ + +/*** + *** Imports + ***/ + +#include <config.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/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 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" +}; + +static const char *rcodetext[] = { + "NOERROR", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "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); + } + + 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); + 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 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->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; + } +} + +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->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 = 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_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, + 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_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) { + 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_FORMERR \ + do { \ + if (best_effort) \ + seen_problem = ISC_TRUE; \ + else { \ + result = DNS_R_FORMERR; \ + 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_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->state == DNS_SECTION_ANY) { + msg->state = DNS_SECTION_QUESTION; + msg->rdclass = rdclass; + } else if (msg->rdclass != rdclass) + DO_FORMERR; + + /* + * Can't ask the same question twice. + */ + result = dns_message_findtype(name, rdtype, 0, NULL); + if (result == ISC_R_SUCCESS) + DO_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->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; + } + + 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_name = 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->state == DNS_SECTION_ANY && + 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->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_dnskey /* in a TKEY query */ + && rdtype != dns_rdatatype_sig /* SIG(0) */ + && rdtype != dns_rdatatype_tkey /* Win2000 TKEY */ + && msg->rdclass != rdclass) + DO_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_FORMERR; + 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) || + msg->opt != NULL) + DO_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_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 (rdtype == dns_rdatatype_tsig) + result = getrdata(source, msg, dctx, rdclass, + rdtype, rdatalen, rdata); + else + result = getrdata(source, msg, dctx, msg->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_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_FORMERR; + msg->sigstart = recstart; + skip_name_search = ISC_TRUE; + skip_type_search = ISC_TRUE; + issigzero = ISC_TRUE; + } + } else + covers = 0; + + /* + * 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_FORMERR; + + 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)) + DO_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; + ISC_LIST_INIT(rdatalist->rdata); + + dns_rdataset_init(rdataset); + RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, + rdataset) + == ISC_R_SUCCESS); + + 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 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. Note that msg->opt will only be set + * if best-effort parsing is enabled. + */ + 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; + } + + /* + * If this is an SIG(0) or TSIG record, remember it. Note + * that msg->sig0 or msg->tsig will only be set if best-effort + * parsing is enabled. + */ + 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; + rdataset = NULL; + free_rdataset = ISC_FALSE; + free_name = 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; + + /* + * -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); + memcpy(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); + 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, 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); +} + +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; + + 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; + + 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 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 (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; + + 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, 0, + &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 the question doesn't fit, + * don't include it. + */ + 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 && result != ISC_R_NOSPACE) + 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); + if (name != NULL) + REQUIRE(*name == NULL); + if (type == dns_rdatatype_any) { + REQUIRE(rdataset == NULL); + } else { + if (rdataset != NULL) + REQUIRE(*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 (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); + 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 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->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_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_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 = 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); + 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)); +} + +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) { + 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_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; + + 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) { + 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_QUESTION) { + 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) + 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, + 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; + + 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) + ADD_STRING(target, ";; OPT PSEUDOSECTION:\n"); + 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 & ~DNS_MESSAGEEXTFLAG_DO & 0xffff; + if (mbz != 0) { + ADD_STRING(target, "; MBZ: "); + snprintf(buf, sizeof(buf), "%.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); + 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_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 ((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) { + 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: "); + snprintf(buf, sizeof(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 "); + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, "; QUESTION: "); + } else { + 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, + 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); +} |