diff options
Diffstat (limited to 'lib/dns/message.c')
-rw-r--r-- | lib/dns/message.c | 2161 |
1 files changed, 2161 insertions, 0 deletions
diff --git a/lib/dns/message.c b/lib/dns/message.c new file mode 100644 index 00000000..febca5ab --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,2161 @@ +/* + * Copyright (C) 1999, 2000 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/*** + *** Imports + ***/ + +#include <config.h> + +#include <stddef.h> +#include <string.h> + +#include <isc/assertions.h> +#include <isc/boolean.h> +#include <isc/region.h> +#include <isc/types.h> + +#include <dns/message.h> +#include <dns/rdataset.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rdatatype.h> +#include <dns/rdatalist.h> +#include <dns/compress.h> +#include <dns/tsig.h> +#include <dns/dnssec.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)) + +/* + * This is the size of each individual scratchpad buffer, and the numbers + * of various block allocations used within the server. + * XXXMLG These should come from a config setting. + */ +#define SCRATCHPAD_SIZE 512 +#define NAME_COUNT 8 +#define RDATA_COUNT 8 +#define RDATALIST_COUNT 8 +#define RDATASET_COUNT RDATALIST_COUNT + +/* + * "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, + ISC_BUFFERTYPE_BINARY); + if (result != ISC_R_SUCCESS) + return (DNS_R_NOMEMORY); + + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + return (DNS_R_SUCCESS); +} + +static inline isc_buffer_t * +currentbuffer(dns_message_t *msg) +{ + isc_buffer_t *dynbuf; + + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + + return (dynbuf); +} + +static inline void +releaserdata(dns_message_t *msg, dns_rdata_t *rdata) +{ + ISC_LIST_PREPEND(msg->freerdata, rdata, link); +} + +static inline dns_rdata_t * +newrdata(dns_message_t *msg) +{ + dns_msgblock_t *msgblock; + dns_rdata_t *rdata; + + rdata = ISC_LIST_HEAD(msg->freerdata); + if (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + return (rdata); + } + + msgblock = ISC_LIST_TAIL(msg->rdatas); + rdata = msgblock_get(msgblock, dns_rdata_t); + if (rdata == NULL) { + msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t), + RDATA_COUNT); + if (msgblock == NULL) + return (NULL); + + ISC_LIST_APPEND(msg->rdatas, msgblock, link); + + rdata = msgblock_get(msgblock, dns_rdata_t); + } + + return (rdata); +} + +static inline void +releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) +{ + ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link); +} + +static inline dns_rdatalist_t * +newrdatalist(dns_message_t *msg) +{ + dns_msgblock_t *msgblock; + dns_rdatalist_t *rdatalist; + + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + if (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + return (rdatalist); + } + + msgblock = ISC_LIST_TAIL(msg->rdatalists); + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + if (rdatalist == NULL) { + msgblock = msgblock_allocate(msg->mctx, + sizeof(dns_rdatalist_t), + RDATALIST_COUNT); + if (msgblock == NULL) + return (NULL); + + ISC_LIST_APPEND(msg->rdatalists, msgblock, link); + + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + } + + return (rdatalist); +} + +static inline void +msginitheader(dns_message_t *m) +{ + m->id = 0; + m->flags = 0; + m->rcode = 0; + m->opcode = 0; + m->rdclass = 0; +} + +static inline void +msginitprivate(dns_message_t *m) +{ + unsigned int i; + + for (i = 0; i < DNS_SECTION_MAX; i++) { + m->cursors[i] = NULL; + m->counts[i] = 0; + } + m->opt = NULL; + m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */ + m->opt_reserved = 0; + m->reserved = 0; + m->buffer = NULL; + m->need_cctx_cleanup = 0; +} + +static inline void +msginittsig(dns_message_t *m) +{ + m->tsigstatus = m->querytsigstatus = dns_rcode_noerror; + m->tsig = m->querytsig = NULL; + m->tsigkey = NULL; + m->tsigctx = NULL; + m->sigstart = -1; + m->sig0key = NULL; + m->sig0status = dns_rcode_noerror; + m->query = NULL; + m->saved = NULL; +} + +/* + * Init elements to default state. Used both when allocating a new element + * and when resetting one. + */ +static inline void +msginit(dns_message_t *m) +{ + msginitheader(m); + msginitprivate(m); + msginittsig(m); + m->header_ok = 0; + m->question_ok = 0; + m->tcp_continuation = 0; + m->verified_sig0 = 0; +} + +static inline void +msgresetnames(dns_message_t *msg, unsigned int first_section) { + unsigned int i; + dns_name_t *name, *next_name; + dns_rdataset_t *rds, *next_rds; + + /* + * Clean up name lists by calling the rdataset disassociate function. + */ + for (i = first_section; i < DNS_SECTION_MAX; i++) { + name = ISC_LIST_HEAD(msg->sections[i]); + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(msg->sections[i], name, link); + + rds = ISC_LIST_HEAD(name->list); + while (rds != NULL) { + next_rds = ISC_LIST_NEXT(rds, link); + ISC_LIST_UNLINK(name->list, rds, link); + + INSIST(dns_rdataset_isassociated(rds)); + dns_rdataset_disassociate(rds); + isc_mempool_put(msg->rdspool, rds); + rds = next_rds; + } + isc_mempool_put(msg->namepool, name); + name = next_name; + } + } +} + +static void +msgresetopt(dns_message_t *msg) +{ + if (msg->opt != NULL) { + if (msg->opt_reserved > 0) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + } + INSIST(dns_rdataset_isassociated(msg->opt)); + dns_rdataset_disassociate(msg->opt); + isc_mempool_put(msg->rdspool, msg->opt); + msg->opt = NULL; + } +} + +/* + * Free all but one (or everything) for this message. This is used by + * both dns_message_reset() and dns_message_parse(). + */ +static void +msgreset(dns_message_t *msg, isc_boolean_t everything) +{ + dns_msgblock_t *msgblock, *next_msgblock; + isc_buffer_t *dynbuf, *next_dynbuf; + dns_rdata_t *rdata; + dns_rdatalist_t *rdatalist; + + msgresetnames(msg, 0); + msgresetopt(msg); + + /* + * Clean up linked lists. + */ + + /* + * Run through the free lists, and just unlink anything found there. + * The memory isn't lost since these are part of message blocks we + * have allocated. + */ + rdata = ISC_LIST_HEAD(msg->freerdata); + while (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + rdata = ISC_LIST_HEAD(msg->freerdata); + } + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + while (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + } + + dynbuf = ISC_LIST_HEAD(msg->scratchpad); + INSIST(dynbuf != NULL); + if (!everything) { + isc_buffer_clear(dynbuf); + dynbuf = ISC_LIST_NEXT(dynbuf, link); + } + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + msgblock = ISC_LIST_HEAD(msg->rdatas); + INSIST(msgblock != NULL); + if (!everything) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatas, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t)); + msgblock = next_msgblock; + } + + /* + * rdatalists could be empty. + */ + + msgblock = ISC_LIST_HEAD(msg->rdatalists); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatalists, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t)); + msgblock = next_msgblock; + } + + if (msg->need_cctx_cleanup == 1) + dns_compress_invalidate(&msg->cctx); + + if (msg->tsig != NULL) { + dns_rdata_freestruct(msg->tsig); + isc_mem_put(msg->mctx, msg->tsig, + sizeof(dns_rdata_any_tsig_t)); + msg->tsig = NULL; + } + + if (msg->querytsig != NULL) { + dns_rdata_freestruct(msg->querytsig); + isc_mem_put(msg->mctx, msg->querytsig, + sizeof(dns_rdata_any_tsig_t)); + msg->querytsig = NULL; + } + + if (msg->tsigkey != NULL) { + dns_tsigkey_free(&msg->tsigkey); + msg->tsigkey = NULL; + } + + if (msg->query != NULL) { + isc_mem_put(msg->mctx, msg->query->base, msg->query->length); + isc_mem_put(msg->mctx, msg->query, sizeof(isc_region_t)); + msg->query = NULL; + } + + if (msg->saved != NULL) { + isc_mem_put(msg->mctx, msg->saved->base, msg->saved->length); + isc_mem_put(msg->mctx, msg->saved, sizeof(isc_region_t)); + msg->saved = NULL; + } + + /* + * cleanup the buffer cleanup list + */ + dynbuf = ISC_LIST_HEAD(msg->cleanup); + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->cleanup, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + /* + * Set other bits to normal default values. + */ + if (!everything) + msginit(msg); +} + +isc_result_t +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) +{ + dns_message_t *m; + isc_result_t result; + dns_msgblock_t *msgblock; + isc_buffer_t *dynbuf; + unsigned int i; + + REQUIRE(mctx != NULL); + REQUIRE(msgp != NULL); + REQUIRE(*msgp == NULL); + REQUIRE(intent == DNS_MESSAGE_INTENTPARSE + || intent == DNS_MESSAGE_INTENTRENDER); + + m = isc_mem_get(mctx, sizeof(dns_message_t)); + if (m == NULL) + return(DNS_R_NOMEMORY); + + /* + * No allocations until further notice. Just initialize all lists + * and other members that are freed in the cleanup phase here. + */ + + m->magic = DNS_MESSAGE_MAGIC; + m->from_to_wire = intent; + msginit(m); + + for (i = 0 ; i < DNS_SECTION_MAX ; i++) + ISC_LIST_INIT(m->sections[i]); + m->mctx = mctx; + + ISC_LIST_INIT(m->scratchpad); + ISC_LIST_INIT(m->cleanup); + m->namepool = NULL; + m->rdspool = NULL; + ISC_LIST_INIT(m->rdatas); + ISC_LIST_INIT(m->rdatalists); + ISC_LIST_INIT(m->freerdata); + ISC_LIST_INIT(m->freerdatalist); + + /* + * Ok, it is safe to allocate (and then "goto cleanup" if failure) + */ + + result = isc_mempool_create(m->mctx, sizeof(dns_name_t), &m->namepool); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_mempool_setfreemax(m->namepool, NAME_COUNT); + isc_mempool_setfillcount(m->namepool, NAME_COUNT); + isc_mempool_setname(m->namepool, "msg:names"); + + result = isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), + &m->rdspool); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_mempool_setfreemax(m->rdspool, NAME_COUNT); + isc_mempool_setfillcount(m->rdspool, NAME_COUNT); + isc_mempool_setname(m->rdspool, "msg:rdataset"); + + dynbuf = NULL; + result = isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE, + ISC_BUFFERTYPE_BINARY); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_APPEND(m->scratchpad, dynbuf, link); + + msgblock = msgblock_allocate(mctx, sizeof(dns_rdata_t), + RDATA_COUNT); + if (msgblock == NULL) + goto cleanup; + ISC_LIST_APPEND(m->rdatas, msgblock, link); + + if (intent == DNS_MESSAGE_INTENTPARSE) { + msgblock = msgblock_allocate(mctx, sizeof(dns_rdatalist_t), + RDATALIST_COUNT); + if (msgblock == NULL) + goto cleanup; + ISC_LIST_APPEND(m->rdatalists, msgblock, link); + } + + *msgp = m; + return (DNS_R_SUCCESS); + + /* + * Cleanup for error returns. + */ + cleanup: + msgblock = ISC_LIST_HEAD(m->rdatas); + if (msgblock != NULL) + msgblock_free(mctx, msgblock, sizeof(dns_rdata_t)); + dynbuf = ISC_LIST_HEAD(m->scratchpad); + if (dynbuf != NULL) + isc_buffer_free(&dynbuf); + if (m->namepool != NULL) + isc_mempool_destroy(&m->namepool); + if (m->rdspool != NULL) + isc_mempool_destroy(&m->rdspool); + m->magic = 0; + isc_mem_put(mctx, m, sizeof(dns_message_t)); + + return (DNS_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); + + msg->from_to_wire = intent; + msgreset(msg, ISC_FALSE); +} + +void +dns_message_destroy(dns_message_t **msgp) +{ + dns_message_t *msg; + + REQUIRE(msgp != NULL); + REQUIRE(DNS_MESSAGE_VALID(*msgp)); + + msg = *msgp; + *msgp = NULL; + + msgreset(msg, ISC_TRUE); + isc_mempool_destroy(&msg->namepool); + isc_mempool_destroy(&msg->rdspool); + msg->magic = 0; + isc_mem_put(msg->mctx, msg, sizeof(dns_message_t)); +} + +static isc_result_t +findname(dns_name_t **foundname, dns_name_t *target, unsigned int attributes, + dns_namelist_t *section) +{ + dns_name_t *curr; + + for (curr = ISC_LIST_TAIL(*section) ; + curr != NULL ; + curr = ISC_LIST_PREV(curr, link)) { + if (dns_name_equal(curr, target) && + (curr->attributes & attributes) == attributes) { + if (foundname != NULL) + *foundname = curr; + return (DNS_R_SUCCESS); + } + } + + return (DNS_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 (DNS_R_SUCCESS); + } + } + + return (DNS_R_NOTFOUND); +} + +/* + * Read a name from buffer "source". + */ +static isc_result_t +getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg, + dns_decompress_t *dctx) +{ + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + + scratch = currentbuffer(msg); + + /* + * First try: use current buffer. + * Second try: allocate a new buffer and use that. + */ + tries = 0; + while (tries < 2) { + dns_name_init(name, NULL); + result = dns_name_fromwire(name, source, dctx, ISC_FALSE, + scratch); + + if (result == DNS_R_NOSPACE) { + tries++; + + result = newbuffer(msg, SCRATCHPAD_SIZE); + if (result != DNS_R_SUCCESS) + return (result); + + scratch = currentbuffer(msg); + } else { + return (result); + } + } + + INSIST(0); /* Cannot get here... */ + return (DNS_R_UNEXPECTED); +} + +static isc_result_t +getrdata(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg, + dns_decompress_t *dctx, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, unsigned int rdatalen, dns_rdata_t *rdata) +{ + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + unsigned int trysize; + + /* + * In dynamic update messages, the rdata can be empty. + */ + if (msg->opcode == dns_opcode_update && rdatalen == 0) { + /* + * When the rdata is empty, the data pointer is never + * dereferenced, but it must still be non-NULL. + */ + rdata->data = (unsigned char *)""; + rdata->length = 0; + rdata->rdclass = rdclass; + rdata->type = rdtype; + return DNS_R_SUCCESS; + } + + scratch = currentbuffer(msg); + + isc_buffer_setactive(source, rdatalen); + dns_decompress_localinit(dctx, name, source); + + /* + * 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; + for (;;) { + result = dns_rdata_fromwire(rdata, rdclass, rdtype, + source, dctx, ISC_FALSE, + scratch); + + if (result == DNS_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 != DNS_R_SUCCESS) + return (result); + + scratch = currentbuffer(msg); + } else { + return (result); + } + } +} + +static isc_result_t +getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx) +{ + isc_region_t r; + unsigned int count; + dns_name_t *name; + dns_name_t *name2; + dns_rdataset_t *rdataset; + dns_rdatalist_t *rdatalist; + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section; + isc_boolean_t free_name; + + section = &msg->sections[DNS_SECTION_QUESTION]; + + name = NULL; + rdataset = NULL; + rdatalist = NULL; + + for (count = 0 ; count < msg->counts[DNS_SECTION_QUESTION] ; count++) { + name = isc_mempool_get(msg->namepool); + if (name == NULL) + return (DNS_R_NOMEMORY); + free_name = ISC_TRUE; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remaining(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != DNS_R_SUCCESS) + goto cleanup; + + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the allocated + * name since we no longer need it, and set our name pointer + * to point to the name we found. + */ + result = findname(&name2, name, 0, section); + + /* + * If it is the first name in the section, accept it. + * + * If it is not, but is not the same as the name already + * in the question section, append to the section. Note that + * here in the question section this is illegal, so return + * FORMERR. In the future, check the opcode to see if + * this should be legal or not. In either case we no longer + * need this name pointer. + */ + if (result != DNS_R_SUCCESS) { + if (ISC_LIST_EMPTY(*section)) { + ISC_LIST_APPEND(*section, name, link); + free_name = ISC_FALSE; + } else { + result = DNS_R_FORMERR; + goto cleanup; + } + } else { + isc_mempool_put(msg->namepool, name); + name = name2; + name2 = NULL; + free_name = ISC_FALSE; + } + + /* + * Get type and class. + */ + isc_buffer_remaining(source, &r); + if (r.length < 4) { + result = DNS_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; + msg->state = DNS_SECTION_QUESTION; + } else if (msg->rdclass != rdclass) { + result = DNS_R_FORMERR; + goto cleanup; + } + + /* + * Can't ask the same question twice. + */ + result = dns_message_findtype(name, rdtype, 0, NULL); + if (result == DNS_R_SUCCESS) { + result = DNS_R_FORMERR; + goto cleanup; + } + + /* + * Allocate a new rdatalist. + */ + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = DNS_R_NOMEMORY; + goto cleanup; + } + rdataset = isc_mempool_get(msg->rdspool); + if (rdataset == NULL) { + result = DNS_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 != DNS_R_SUCCESS) + goto cleanup; + + rdataset->attributes |= DNS_RDATASETATTR_QUESTION; + + ISC_LIST_APPEND(name->list, rdataset, link); + } + + return (DNS_R_SUCCESS); + + cleanup: + if (rdataset != NULL) { + INSIST(!dns_rdataset_isassociated(rdataset)); + isc_mempool_put(msg->rdspool, rdataset); + } +#if 0 + if (rdatalist != NULL) + isc_mempool_put(msg->rdlpool, rdatalist); +#endif + if (free_name) + isc_mempool_put(msg->namepool, name); + + return (result); +} + +static isc_result_t +getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_section_t sectionid, isc_boolean_t preserve_order) +{ + isc_region_t r; + unsigned int count, rdatalen, attributes; + dns_name_t *name; + dns_name_t *name2; + dns_rdataset_t *rdataset; + dns_rdatalist_t *rdatalist; + isc_result_t result; + dns_rdatatype_t rdtype, covers; + dns_rdataclass_t rdclass; + dns_rdata_t *rdata; + dns_ttl_t ttl; + dns_namelist_t *section; + isc_boolean_t free_name, free_rdataset; + + for (count = 0 ; count < msg->counts[sectionid] ; count++) { + int recstart = source->current; + isc_boolean_t skip_name_search, skip_type_search; + + section = &msg->sections[sectionid]; + + skip_name_search = ISC_FALSE; + skip_type_search = ISC_FALSE; + free_name = ISC_FALSE; + free_rdataset = ISC_FALSE; + + name = isc_mempool_get(msg->namepool); + if (name == NULL) + return (DNS_R_NOMEMORY); + free_name = ISC_TRUE; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remaining(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != DNS_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_remaining(source, &r); + if (r.length < 2 + 2 + 4 + 2) { + result = DNS_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If there was no question section, we may not yet have + * established a class. Do so now. + */ + if (msg->state == DNS_SECTION_ANY) { + if (rdclass == 0 || rdclass == dns_rdataclass_any) { + result = DNS_R_FORMERR; + goto cleanup; + } + msg->rdclass = rdclass; + msg->state = DNS_SECTION_QUESTION; + } + + /* + * If this class is different than the one in the question + * section, bail. + */ + if (msg->opcode != dns_opcode_update + && rdtype != dns_rdatatype_tsig + && rdtype != dns_rdatatype_opt + && rdtype != dns_rdatatype_key /* XXX in a TKEY query */ + && rdtype != dns_rdatatype_sig /* XXX SIG(0) */ + && msg->rdclass != rdclass) { + result = DNS_R_FORMERR; + goto cleanup; + } + + /* + * Special type handling for TSIG, OPT, and TKEY. + */ + if (rdtype == dns_rdatatype_tsig) { + /* + * If it is a tsig, verify that it is in the + * additional data section, and switch sections for + * the rest of this rdata. + */ + if ((sectionid != DNS_SECTION_ADDITIONAL) + || (rdclass != dns_rdataclass_any)) { + result = DNS_R_FORMERR; + goto cleanup; + } + section = &msg->sections[DNS_SECTION_TSIG]; + msg->sigstart = recstart; + skip_name_search = ISC_TRUE; + skip_type_search = ISC_TRUE; + } else if (rdtype == dns_rdatatype_opt) { + /* + * The name of an OPT record must be ".", it + * must be in the additional data section, and + * it must be the first OPT we've seen. + */ + if (!dns_name_equal(dns_rootname, name) || + sectionid != DNS_SECTION_ADDITIONAL || + msg->opt != NULL) { + result = DNS_R_FORMERR; + goto cleanup; + } + skip_name_search = ISC_TRUE; + skip_type_search = ISC_TRUE; + } else if (rdtype == dns_rdatatype_tkey) { + /* + * A TKEY must be in the additional section. + * Its class is ignored. + */ + if (sectionid != DNS_SECTION_ADDITIONAL) { + result = DNS_R_FORMERR; + goto cleanup; + } + } + + /* + * ... now get ttl and rdatalen, and check buffer. + */ + ttl = isc_buffer_getuint32(source); + rdatalen = isc_buffer_getuint16(source); + r.length -= (2 + 2 + 4 + 2); + if (r.length < rdatalen) { + result = DNS_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 = DNS_R_NOMEMORY; + goto cleanup; + } + attributes = 0; + if (rdtype != dns_rdatatype_tsig) { + if (rdtype == dns_rdatatype_cname) { + name->attributes |= DNS_NAMEATTR_CNAME; + attributes = DNS_NAMEATTR_CNAME; + skip_name_search = ISC_TRUE; + } else if (rdtype == dns_rdatatype_dname) { + name->attributes |= DNS_NAMEATTR_DNAME; + attributes = DNS_NAMEATTR_DNAME; + skip_name_search = ISC_TRUE; + } + result = getrdata(name, source, msg, dctx, + msg->rdclass, rdtype, + rdatalen, rdata); + } else + result = getrdata(name, source, msg, dctx, + rdclass, rdtype, rdatalen, rdata); + if (result != DNS_R_SUCCESS) + goto cleanup; + rdata->rdclass = rdclass; + if (rdtype == dns_rdatatype_sig && rdata->length > 0) { + covers = dns_rdata_covers(rdata); + if (covers == dns_rdatatype_cname) + attributes = DNS_NAMEATTR_CNAME; + else if (covers == dns_rdatatype_dname) + attributes = DNS_NAMEATTR_DNAME; + else if (covers == 0) { + msg->sigstart = recstart; + section = &msg->sections[DNS_SECTION_SIG0]; + } + } else + covers = 0; + + /* + * If we are doing a dynamic update don't bother searching + * for a name, just append this one to the end of the message. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_name_search) { + if (rdtype != dns_rdatatype_opt) { + ISC_LIST_APPEND(*section, name, link); + free_name = ISC_FALSE; + } + } else { + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the + * allocated name since we no longer need it, and set + * our name pointer to point to the name we found. + */ + result = findname(&name2, name, attributes, section); + + /* + * If it is a new name, append to the section. + */ + if (result == DNS_R_SUCCESS) { + isc_mempool_put(msg->namepool, name); + name = name2; + } else { + ISC_LIST_APPEND(*section, name, link); + } + free_name = ISC_FALSE; + } + + /* + * Search name for the particular type and class. + * Skip this stage if in update mode, or this is a TSIG. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_type_search) + result = DNS_R_NOTFOUND; + else { + 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. + * + * XXXRTH Check for attempts to create multi-record RRsets + * for singleton RR types. + */ + if (result == DNS_R_NOTFOUND) { + rdataset = isc_mempool_get(msg->rdspool); + if (rdataset == NULL) { + result = DNS_R_NOMEMORY; + goto cleanup; + } + free_rdataset = ISC_TRUE; + + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = DNS_R_NOMEMORY; + goto cleanup; + } + + rdatalist->type = rdtype; + rdatalist->covers = covers; + rdatalist->rdclass = rdclass; + rdatalist->ttl = ttl; + ISC_LIST_INIT(rdatalist->rdata); + + dns_rdataset_init(rdataset); + dns_rdatalist_tordataset(rdatalist, rdataset); + + if (rdtype != dns_rdatatype_opt) { + ISC_LIST_APPEND(name->list, rdataset, link); + free_rdataset = ISC_FALSE; + } + } + + /* + * Minimize TTLs. + * + * Section 5.2 of RFC 2181 says we should drop + * nonauthoritative rrsets where the TTLs differ, but we + * currently treat them the as if they were authoritative and + * minimize them. + */ + if (ttl != rdataset->ttl) { + rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED; + if (ttl < rdataset->ttl) + rdataset->ttl = ttl; + } + + /* + * XXXMLG Perform a totally ugly hack here to pull + * the rdatalist out of the private field in the rdataset, + * and append this rdata to the rdatalist's linked list + * of rdata. + */ + rdatalist = (dns_rdatalist_t *)(rdataset->private1); + + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + + /* + * If this is an OPT record, remember it. Also, set + * the extended rcode. + */ + if (rdtype == dns_rdatatype_opt) { + unsigned int ercode; + + msg->opt = rdataset; + rdataset = NULL; + free_rdataset = ISC_FALSE; + ercode = (msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK) + >> 20; + msg->rcode |= ercode; + isc_mempool_put(msg->namepool, name); + free_name = ISC_FALSE; + } + + INSIST(free_name == ISC_FALSE); + INSIST(free_rdataset == ISC_FALSE); + } + + return (DNS_R_SUCCESS); + + cleanup: + if (free_name) + isc_mempool_put(msg->namepool, name); + if (free_rdataset) + isc_mempool_put(msg->rdspool, rdataset); + + return (result); +} + +isc_result_t +dns_message_parse(dns_message_t *msg, isc_buffer_t *source, + isc_boolean_t preserve_order) +{ + isc_region_t r; + dns_decompress_t dctx; + isc_result_t ret; + isc_uint16_t tmpflags; + isc_buffer_t origsource; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(source != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + origsource = *source; + + msg->header_ok = 0; + msg->question_ok = 0; + + isc_buffer_remaining(source, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) + return (DNS_R_UNEXPECTEDEND); + + msg->id = isc_buffer_getuint16(source); + tmpflags = isc_buffer_getuint16(source); + msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK) + >> DNS_MESSAGE_OPCODE_SHIFT); + msg->rcode = (tmpflags & DNS_MESSAGE_RCODE_MASK); + msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK); + msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source); + + msg->header_ok = 1; + + /* + * -1 means no EDNS. + */ + dns_decompress_init(&dctx, -1, ISC_FALSE); + + if (dns_decompress_edns(&dctx) > 1 || !dns_decompress_strict(&dctx)) + dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL); + else + dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14); + + + ret = getquestions(source, msg, &dctx); + if (ret != DNS_R_SUCCESS) + return (ret); + msg->question_ok = 1; + + ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER, + preserve_order); + if (ret != DNS_R_SUCCESS) + return (ret); + + ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY, + preserve_order); + if (ret != DNS_R_SUCCESS) + return (ret); + + ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL, + preserve_order); + if (ret != DNS_R_SUCCESS) + return (ret); + + isc_buffer_remaining(source, &r); + if (r.length != 0) + return (DNS_R_FORMERR); + + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_TSIG]) || + !ISC_LIST_EMPTY(msg->sections[DNS_SECTION_SIG0])) + { + msg->saved = isc_mem_get(msg->mctx, sizeof(isc_region_t)); + if (msg->saved == NULL) + return (ISC_R_NOMEMORY); + isc_buffer_used(&origsource, &r); + msg->saved->length = r.length; + msg->saved->base = isc_mem_get(msg->mctx, msg->saved->length); + if (msg->saved->base == NULL) { + isc_mem_put(msg->mctx, msg->saved, + sizeof(isc_region_t)); + msg->saved = NULL; + return (ISC_R_NOMEMORY); + } + memcpy(msg->saved->base, r.base, msg->saved->length); + } + + return (DNS_R_SUCCESS); +} + +isc_result_t +dns_message_renderbegin(dns_message_t *msg, isc_buffer_t *buffer) +{ + isc_region_t r; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(msg->buffer == NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + + /* + * Erase the contents of this buffer. + */ + isc_buffer_clear(buffer); + + /* + * Make certain there is enough for at least the header in this + * buffer. + */ + isc_buffer_available(buffer, &r); + REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN); + + result = dns_compress_init(&msg->cctx, -1, msg->mctx); + if (result != DNS_R_SUCCESS) + return (result); + msg->need_cctx_cleanup = 1; + + /* + * Reserve enough space for the header in this buffer. + */ + isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN); + + msg->buffer = buffer; + + return (DNS_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_available(buffer, &rn); + isc_buffer_used(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 (DNS_R_SUCCESS); +} + +void +dns_message_renderrelease(dns_message_t *msg, unsigned int space) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + 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)); + REQUIRE(msg->buffer != NULL); + + isc_buffer_available(msg->buffer, &r); + if (r.length < (space + msg->reserved)) + return (DNS_R_NOSPACE); + + msg->reserved += space; + + return (DNS_R_SUCCESS); +} + +static inline isc_boolean_t +wrong_priority(dns_rdataset_t *rds, int pass) +{ + int pass_needed; + + /* + * If we are not rendering class IN, this ordering is bogus. + */ + if (rds->rdclass != dns_rdataclass_in) + return (ISC_FALSE); + + switch (rds->type) { + case dns_rdatatype_a: + case dns_rdatatype_aaaa: + case dns_rdatatype_a6: + pass_needed = 3; + break; + case dns_rdatatype_sig: + case dns_rdatatype_key: + pass_needed = 2; + break; + default: + pass_needed = 1; + } + + if (pass_needed >= pass) + return (ISC_FALSE); + + return (ISC_TRUE); +} + +isc_result_t +dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid, + unsigned int options) +{ + dns_namelist_t *section; + dns_name_t *name, *next_name; + dns_rdataset_t *rdataset, *next_rdataset; + unsigned int count, total; + isc_result_t result; + isc_buffer_t st; /* for rollbacks */ + int pass; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + REQUIRE(VALID_NAMED_SECTION(sectionid)); + + section = &msg->sections[sectionid]; + + if ((sectionid == DNS_SECTION_ADDITIONAL) + && (options & DNS_MESSAGERENDER_ORDERED) == 0) + pass = 3; + else + pass = 1; + + /* + * Shrink the space in the buffer by the reserved amount. + */ + msg->buffer->length -= msg->reserved; + + total = 0; + + do { + name = ISC_LIST_HEAD(*section); + if (name == NULL) + return (DNS_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) + goto next; + + if (((options & DNS_MESSAGERENDER_ORDERED) == 0) + && (sectionid == DNS_SECTION_ADDITIONAL) + && wrong_priority(rdataset, pass)) + goto next; + + st = *(msg->buffer); + + count = 0; + result = dns_rdataset_towire(rdataset, name, + &msg->cctx, + msg->buffer, &count); + + total += count; + + /* + * If out of space, record stats on what we rendered + * so far, and return that status. + * + * XXXMLG Need to change this when + * dns_rdataset_towire() can render partial + * sets starting at some arbitary point in the set. + * This will include setting a bit in the + * rdataset to indicate that a partial rendering + * was done, and some state saved somewhere + * (probably in the message struct) + * to indicate where to continue from. + */ + if (result != DNS_R_SUCCESS) { + INSIST(st.used < 65536); + dns_compress_rollback(&msg->cctx, + (isc_uint16_t)st.used); + *(msg->buffer) = st; /* rollback */ + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + + /* + * If we have rendered pending data, ensure that the + * AD bit is not set. + */ + if (rdataset->trust == dns_trust_pending && + (sectionid == DNS_SECTION_ANSWER || + sectionid == DNS_SECTION_AUTHORITY)) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + + rdataset->attributes |= DNS_RDATASETATTR_RENDERED; + + next: + rdataset = next_rdataset; + } + + name = next_name; + } + } while (--pass != 0); + + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + + return (DNS_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_available(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] + + msg->counts[DNS_SECTION_TSIG] + + msg->counts[DNS_SECTION_SIG0]) < 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]); + tmp = msg->counts[DNS_SECTION_ADDITIONAL] + + msg->counts[DNS_SECTION_TSIG] + + msg->counts[DNS_SECTION_SIG0]; + isc_buffer_putuint16(target, tmp); +} + +isc_result_t +dns_message_renderend(dns_message_t *msg) +{ + isc_buffer_t tmpbuf; + isc_region_t r; + int result; + unsigned int count; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + + if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) { + /* + * We have an extended rcode but are not using EDNS. + */ + return (DNS_R_FORMERR); + } + + /* + * If we've got an OPT record, render it. + */ + if (msg->opt != NULL) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + /* + * Set the extended rcode. + */ + msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK; + msg->opt->ttl |= ((msg->rcode << 20) & + DNS_MESSAGE_EDNSRCODE_MASK); + /* + * Render. + */ + count = 0; + result = dns_rdataset_towire(msg->opt, dns_rootname, + &msg->cctx, msg->buffer, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); + } + + if (msg->tsigkey != NULL) { + result = dns_tsig_sign(msg); + if (result != DNS_R_SUCCESS) + return (result); + result = dns_message_rendersection(msg, DNS_SECTION_TSIG, 0); + if (result != DNS_R_SUCCESS) + return (result); + } + + else if (msg->sig0key != NULL) { + result = dns_dnssec_signmessage(msg, msg->sig0key); + if (result != DNS_R_SUCCESS) + return (result); + result = dns_message_rendersection(msg, DNS_SECTION_SIG0, 0); + if (result != DNS_R_SUCCESS) + return (result); + } + + isc_buffer_used(msg->buffer, &r); + isc_buffer_init(&tmpbuf, r.base, r.length, ISC_BUFFERTYPE_BINARY); + + dns_message_renderheader(msg, &tmpbuf); + + msg->buffer = NULL; /* forget about this buffer only on success XXX */ + + dns_compress_invalidate(&msg->cctx); + msg->need_cctx_cleanup = 0; + + return (DNS_R_SUCCESS); +} + +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 (DNS_R_NOMORE); + + return (DNS_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 (DNS_R_NOMORE); + + return (DNS_R_SUCCESS); +} + +void +dns_message_currentname(dns_message_t *msg, dns_section_t section, + dns_name_t **name) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + REQUIRE(name != NULL && *name == NULL); + REQUIRE(msg->cursors[section] != NULL); + + *name = msg->cursors[section]; +} + +isc_result_t +dns_message_findname(dns_message_t *msg, dns_section_t section, + dns_name_t *target, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_name_t **name, + dns_rdataset_t **rdataset) +{ + dns_name_t *foundname; + isc_result_t result; + unsigned int attributes; + dns_rdatatype_t atype; + + /* + * XXX These requirements are probably too intensive, especially + * where things can be NULL, but as they are they ensure that if + * something is NON-NULL, indicating that the caller expects it + * to be filled in, that we can in fact fill it in. + */ + REQUIRE(msg != NULL); + REQUIRE(VALID_SECTION(section)); + REQUIRE(target != NULL); + if (name != NULL) + REQUIRE(*name == NULL); + if (type == dns_rdatatype_any) { + REQUIRE(rdataset == NULL); + } else { + if (rdataset != NULL) + REQUIRE(*rdataset == NULL); + } + + /* + * Figure out what attributes we should look for. + */ + if (type == dns_rdatatype_sig) + atype = covers; + else + atype = type; + attributes = 0; + if (atype == dns_rdatatype_cname) + attributes = DNS_NAMEATTR_CNAME; + else if (atype == dns_rdatatype_cname) + attributes = DNS_NAMEATTR_DNAME; + + /* + * Search through, looking for the name. + */ + result = findname(&foundname, target, attributes, + &msg->sections[section]); + if (result == DNS_R_NOTFOUND) + return (DNS_R_NXDOMAIN); + else if (result != DNS_R_SUCCESS) + return (result); + + if (name != NULL) + *name = foundname; + + /* + * And now look for the type. + */ + if (type == dns_rdatatype_any) + return (DNS_R_SUCCESS); + + result = dns_message_findtype(foundname, type, covers, rdataset); + if (result == DNS_R_NOTFOUND) + return (DNS_R_NXRDATASET); + + 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 (DNS_R_NOMEMORY); + + return (DNS_R_SUCCESS); +} + +void +dns_message_puttempname(dns_message_t *msg, dns_name_t **item) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + isc_mempool_put(msg->namepool, *item); + *item = NULL; +} + +isc_result_t +dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newrdata(msg); + if (*item == NULL) + return (DNS_R_NOMEMORY); + + return (DNS_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 (DNS_R_NOMEMORY); + + dns_rdataset_init(*item); + + return (DNS_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 (DNS_R_NOMEMORY); + + return (DNS_R_SUCCESS); +} + +void +dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdata(msg, *item); + *item = NULL; +} + +void +dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + REQUIRE(!dns_rdataset_isassociated(*item)); + isc_mempool_put(msg->rdspool, *item); + *item = NULL; +} + +void +dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdatalist(msg, *item); + *item = NULL; +} + +isc_result_t +dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp, + unsigned int *flagsp) +{ + isc_region_t r; + isc_buffer_t buffer; + dns_messageid_t id; + unsigned int flags; + + REQUIRE(source != NULL); + + buffer = *source; + + isc_buffer_remaining(&buffer, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) + return (DNS_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 (DNS_R_SUCCESS); +} + +isc_result_t +dns_message_reply(dns_message_t *msg, isc_boolean_t want_question_section) { + unsigned int first_section; + + 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); + 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 information for later use, if there is + * any. This only happens once - that is, if dns_message_reply + * has already moved the variables, this has no effect. + */ + if (msg->tsig != NULL) { + msg->querytsig = msg->tsig; + msg->tsig = NULL; + msg->querytsigstatus = msg->tsigstatus; + msg->tsigstatus = dns_rcode_noerror; + } + if (msg->saved != NULL) { + msg->query = msg->saved; + msg->saved = NULL; + } + + return (DNS_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getopt(dns_message_t *msg) { + + /* + * Get the OPT record for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->opt); +} + +isc_result_t +dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) { + isc_result_t result; + dns_rdata_t rdata; + + /* + * Set the OPT record for 'msg'. + */ + + /* + * The space required for an OPT record is: + * + * 1 byte for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the rdata length + * --------------------------------- + * 11 bytes + * + * plus the length of the rdata. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(opt->type == dns_rdatatype_opt); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(msg->buffer != NULL); + REQUIRE(msg->state == DNS_SECTION_ANY); + + msgresetopt(msg); + + result = dns_rdataset_first(opt); + if (result != ISC_R_SUCCESS) + return (result); + dns_rdataset_current(opt, &rdata); + msg->opt_reserved = 11 + rdata.length; + result = dns_message_renderreserve(msg, msg->opt_reserved); + if (result != ISC_R_SUCCESS) { + msg->opt_reserved = 0; + return (result); + } + + msg->opt = opt; + + return (DNS_R_SUCCESS); +} + +void +dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(ISC_BUFFER_VALID(*buffer)); + + ISC_LIST_APPEND(msg->cleanup, *buffer, link); + *buffer = NULL; +} + +isc_result_t +dns_message_signer(dns_message_t *msg, dns_name_t *signer) { + isc_region_t r; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(signer != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + if ((msg->tsig == NULL || msg->tsigkey == NULL) && + ISC_LIST_EMPTY(msg->sections[DNS_SECTION_SIG0])) + return (ISC_R_NOTFOUND); + + if (!dns_name_hasbuffer(signer)) { + isc_buffer_t *dynbuf = NULL; + result = isc_buffer_allocate(msg->mctx, &dynbuf, 512, + ISC_BUFFERTYPE_BINARY); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_setbuffer(signer, dynbuf); + dns_message_takebuffer(msg, &dynbuf); + } + + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_SIG0])) { + dns_rdataset_t *dataset; + dns_rdata_t rdata; + dns_name_t *sig0name; + dns_rdata_generic_sig_t sig; + + result = dns_message_firstname(msg, DNS_SECTION_SIG0); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + sig0name = NULL; + dns_message_currentname(msg, DNS_SECTION_SIG0, &sig0name); + dataset = NULL; + result = dns_message_findtype(sig0name, dns_rdatatype_sig, 0, + &dataset); + if (result != ISC_R_SUCCESS) + return (result); + result = dns_rdataset_first(dataset); + dns_rdataset_current(dataset, &rdata); + + result = dns_rdata_tostruct(&rdata, &sig, msg->mctx); + if (result != ISC_R_SUCCESS) + return (result); + + if (msg->sig0status != dns_rcode_noerror) + result = DNS_R_SIGINVALID; + else if (msg->verified_sig0 == 0) + result = DNS_R_NOTVERIFIEDYET; + else + result = ISC_R_SUCCESS; + dns_name_toregion(&sig.signer, &r); + dns_name_fromregion(signer, &r); + dns_rdata_freestruct(&sig); + } + else { + dns_name_t *identity; + if (msg->tsigstatus != dns_rcode_noerror) + result = DNS_R_TSIGVERIFYFAILURE; + else if (msg->tsig->error != dns_rcode_noerror) + result = DNS_R_TSIGERRORSET; + else + result = ISC_R_SUCCESS; + identity = dns_tsigkey_identity(msg->tsigkey); + if (identity == NULL) { + if (result == ISC_R_SUCCESS) + result = DNS_R_NOIDENTITY; + identity = &msg->tsigkey->name; + } + dns_name_toregion(identity, &r); + dns_name_fromregion(signer, &r); + } + + return (result); +} + +isc_result_t +dns_message_checksig(dns_message_t *msg, dns_view_t *view) { + isc_buffer_t b; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(view != NULL); + + if (msg->tsigkey == NULL && + ISC_LIST_EMPTY(msg->sections[DNS_SECTION_TSIG])) + return (ISC_R_SUCCESS); + if (msg->saved == NULL) + return (DNS_R_EXPECTEDTSIG); + isc_buffer_init(&b, msg->saved->base, msg->saved->length, + ISC_BUFFERTYPE_BINARY); + isc_buffer_add(&b, msg->saved->length); + return dns_view_checksig(view, &b, msg); +} |