diff options
Diffstat (limited to '')
-rw-r--r-- | lib/dns/message.c | 4362 |
1 files changed, 4362 insertions, 0 deletions
diff --git a/lib/dns/message.c b/lib/dns/message.c new file mode 100644 index 0000000..ac637a2 --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,4362 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include <config.h> +#include <ctype.h> +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/string.h> /* Required for HP/UX (and others?) */ +#include <isc/util.h> + +#include <dns/dnssec.h> +#include <dns/keyvalues.h> +#include <dns/log.h> +#include <dns/masterdump.h> +#include <dns/message.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/rdata.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/result.h> +#include <dns/tsig.h> +#include <dns/ttl.h> +#include <dns/view.h> + +#ifdef SKAN_MSG_DEBUG +static void +hexdump(const char *msg, const char *msg2, void *base, size_t len) { + unsigned char *p; + unsigned int cnt; + + p = base; + cnt = 0; + + printf("*** %s [%s] (%u bytes @ %p)\n", msg, msg2, (unsigned)len, base); + + while (cnt < len) { + if (cnt % 16 == 0) + printf("%p: ", p); + else if (cnt % 8 == 0) + printf(" |"); + printf(" %02x %c", *p, (isprint(*p) ? *p : ' ')); + p++; + cnt++; + + if (cnt % 16 == 0) + printf("\n"); + } + + if (cnt % 16 != 0) + printf("\n"); +} +#endif + +#define DNS_MESSAGE_OPCODE_MASK 0x7800U +#define DNS_MESSAGE_OPCODE_SHIFT 11 +#define DNS_MESSAGE_RCODE_MASK 0x000fU +#define DNS_MESSAGE_FLAG_MASK 0x8ff0U +#define DNS_MESSAGE_EDNSRCODE_MASK 0xff000000U +#define DNS_MESSAGE_EDNSRCODE_SHIFT 24 +#define DNS_MESSAGE_EDNSVERSION_MASK 0x00ff0000U +#define DNS_MESSAGE_EDNSVERSION_SHIFT 16 + +#define VALID_NAMED_SECTION(s) (((s) > DNS_SECTION_ANY) \ + && ((s) < DNS_SECTION_MAX)) +#define VALID_SECTION(s) (((s) >= DNS_SECTION_ANY) \ + && ((s) < DNS_SECTION_MAX)) +#define ADD_STRING(b, s) {if (strlen(s) >= \ + isc_buffer_availablelength(b)) { \ + result = ISC_R_NOSPACE; \ + goto cleanup; \ + } else \ + isc_buffer_putstr(b, s);} +#define VALID_PSEUDOSECTION(s) (((s) >= DNS_PSEUDOSECTION_ANY) \ + && ((s) < DNS_PSEUDOSECTION_MAX)) + +#define OPTOUT(x) (((x)->attributes & DNS_RDATASETATTR_OPTOUT) != 0) + +/*% + * This is the size of each individual scratchpad buffer, and the numbers + * of various block allocations used within the server. + * XXXMLG These should come from a config setting. + */ +#define SCRATCHPAD_SIZE 512 +#define NAME_COUNT 64 +#define OFFSET_COUNT 4 +#define RDATA_COUNT 8 +#define RDATALIST_COUNT 8 +#define RDATASET_COUNT 64 + +/*% + * Text representation of the different items, for message_totext + * functions. + */ +static const char *sectiontext[] = { + "QUESTION", + "ANSWER", + "AUTHORITY", + "ADDITIONAL" +}; + +static const char *updsectiontext[] = { + "ZONE", + "PREREQUISITE", + "UPDATE", + "ADDITIONAL" +}; + +static const char *opcodetext[] = { + "QUERY", + "IQUERY", + "STATUS", + "RESERVED3", + "NOTIFY", + "UPDATE", + "RESERVED6", + "RESERVED7", + "RESERVED8", + "RESERVED9", + "RESERVED10", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15" +}; + +/*% + * "helper" type, which consists of a block of some type, and is linkable. + * For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer + * size, or the allocated elements will not be aligned correctly. + */ +struct dns_msgblock { + unsigned int count; + unsigned int remaining; + ISC_LINK(dns_msgblock_t) link; +}; /* dynamically sized */ + +static inline dns_msgblock_t * +msgblock_allocate(isc_mem_t *, unsigned int, unsigned int); + +#define msgblock_get(block, type) \ + ((type *)msgblock_internalget(block, sizeof(type))) + +static inline void * +msgblock_internalget(dns_msgblock_t *, unsigned int); + +static inline void +msgblock_reset(dns_msgblock_t *); + +static inline void +msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int); + +static void +logfmtpacket(dns_message_t *message, const char *description, + isc_sockaddr_t *address, isc_logcategory_t *category, + isc_logmodule_t *module, const dns_master_style_t *style, + int level, isc_mem_t *mctx); + +/* + * Allocate a new dns_msgblock_t, and return a pointer to it. If no memory + * is free, return NULL. + */ +static inline dns_msgblock_t * +msgblock_allocate(isc_mem_t *mctx, unsigned int sizeof_type, + unsigned int count) +{ + dns_msgblock_t *block; + unsigned int length; + + length = sizeof(dns_msgblock_t) + (sizeof_type * count); + + block = isc_mem_get(mctx, length); + if (block == NULL) + return (NULL); + + block->count = count; + block->remaining = count; + + ISC_LINK_INIT(block, link); + + return (block); +} + +/* + * Return an element from the msgblock. If no more are available, return + * NULL. + */ +static inline void * +msgblock_internalget(dns_msgblock_t *block, unsigned int sizeof_type) { + void *ptr; + + if (block == NULL || block->remaining == 0) + return (NULL); + + block->remaining--; + + ptr = (((unsigned char *)block) + + sizeof(dns_msgblock_t) + + (sizeof_type * block->remaining)); + + return (ptr); +} + +static inline void +msgblock_reset(dns_msgblock_t *block) { + block->remaining = block->count; +} + +/* + * Release memory associated with a message block. + */ +static inline void +msgblock_free(isc_mem_t *mctx, dns_msgblock_t *block, unsigned int sizeof_type) +{ + unsigned int length; + + length = sizeof(dns_msgblock_t) + (sizeof_type * block->count); + + isc_mem_put(mctx, block, length); +} + +/* + * Allocate a new dynamic buffer, and attach it to this message as the + * "current" buffer. (which is always the last on the list, for our + * uses) + */ +static inline isc_result_t +newbuffer(dns_message_t *msg, unsigned int size) { + isc_result_t result; + isc_buffer_t *dynbuf; + + dynbuf = NULL; + result = isc_buffer_allocate(msg->mctx, &dynbuf, size); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOMEMORY); + + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + return (ISC_R_SUCCESS); +} + +static inline isc_buffer_t * +currentbuffer(dns_message_t *msg) { + isc_buffer_t *dynbuf; + + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + + return (dynbuf); +} + +static inline void +releaserdata(dns_message_t *msg, dns_rdata_t *rdata) { + ISC_LIST_PREPEND(msg->freerdata, rdata, link); +} + +static inline dns_rdata_t * +newrdata(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_rdata_t *rdata; + + rdata = ISC_LIST_HEAD(msg->freerdata); + if (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + return (rdata); + } + + msgblock = ISC_LIST_TAIL(msg->rdatas); + rdata = msgblock_get(msgblock, dns_rdata_t); + if (rdata == NULL) { + msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t), + RDATA_COUNT); + if (msgblock == NULL) + return (NULL); + + ISC_LIST_APPEND(msg->rdatas, msgblock, link); + + rdata = msgblock_get(msgblock, dns_rdata_t); + } + + dns_rdata_init(rdata); + return (rdata); +} + +static inline void +releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) { + ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link); +} + +static inline dns_rdatalist_t * +newrdatalist(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_rdatalist_t *rdatalist; + + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + if (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + goto out; + } + + msgblock = ISC_LIST_TAIL(msg->rdatalists); + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + if (rdatalist == NULL) { + msgblock = msgblock_allocate(msg->mctx, + sizeof(dns_rdatalist_t), + RDATALIST_COUNT); + if (msgblock == NULL) + return (NULL); + + ISC_LIST_APPEND(msg->rdatalists, msgblock, link); + + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + } + out: + if (rdatalist != NULL) + dns_rdatalist_init(rdatalist); + + return (rdatalist); +} + +static inline dns_offsets_t * +newoffsets(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_offsets_t *offsets; + + msgblock = ISC_LIST_TAIL(msg->offsets); + offsets = msgblock_get(msgblock, dns_offsets_t); + if (offsets == NULL) { + msgblock = msgblock_allocate(msg->mctx, + sizeof(dns_offsets_t), + OFFSET_COUNT); + if (msgblock == NULL) + return (NULL); + + ISC_LIST_APPEND(msg->offsets, msgblock, link); + + offsets = msgblock_get(msgblock, dns_offsets_t); + } + + return (offsets); +} + +static inline void +msginitheader(dns_message_t *m) { + m->id = 0; + m->flags = 0; + m->rcode = 0; + m->opcode = 0; + m->rdclass = 0; +} + +static inline void +msginitprivate(dns_message_t *m) { + unsigned int i; + + for (i = 0; i < DNS_SECTION_MAX; i++) { + m->cursors[i] = NULL; + m->counts[i] = 0; + } + m->opt = NULL; + m->sig0 = NULL; + m->sig0name = NULL; + m->tsig = NULL; + m->tsigname = NULL; + m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */ + m->opt_reserved = 0; + m->sig_reserved = 0; + m->reserved = 0; + m->buffer = NULL; +} + +static inline void +msginittsig(dns_message_t *m) { + m->tsigstatus = dns_rcode_noerror; + m->querytsigstatus = dns_rcode_noerror; + m->tsigkey = NULL; + m->tsigctx = NULL; + m->sigstart = -1; + m->sig0key = NULL; + m->sig0status = dns_rcode_noerror; + m->timeadjust = 0; +} + +/* + * Init elements to default state. Used both when allocating a new element + * and when resetting one. + */ +static inline void +msginit(dns_message_t *m) { + msginitheader(m); + msginitprivate(m); + msginittsig(m); + m->header_ok = 0; + m->question_ok = 0; + m->tcp_continuation = 0; + m->verified_sig = 0; + m->verify_attempted = 0; + m->order = NULL; + m->order_arg = NULL; + m->query.base = NULL; + m->query.length = 0; + m->free_query = 0; + m->saved.base = NULL; + m->saved.length = 0; + m->free_saved = 0; + m->cc_ok = 0; + m->cc_bad = 0; + m->tkey = 0; + m->rdclass_set = 0; + m->querytsig = NULL; +} + +static inline void +msgresetnames(dns_message_t *msg, unsigned int first_section) { + unsigned int i; + dns_name_t *name, *next_name; + dns_rdataset_t *rds, *next_rds; + + /* + * Clean up name lists by calling the rdataset disassociate function. + */ + for (i = first_section; i < DNS_SECTION_MAX; i++) { + name = ISC_LIST_HEAD(msg->sections[i]); + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(msg->sections[i], name, link); + + rds = ISC_LIST_HEAD(name->list); + while (rds != NULL) { + next_rds = ISC_LIST_NEXT(rds, link); + ISC_LIST_UNLINK(name->list, rds, link); + + INSIST(dns_rdataset_isassociated(rds)); + dns_rdataset_disassociate(rds); + isc_mempool_put(msg->rdspool, rds); + rds = next_rds; + } + if (dns_name_dynamic(name)) + dns_name_free(name, msg->mctx); + isc_mempool_put(msg->namepool, name); + name = next_name; + } + } +} + +static void +msgresetopt(dns_message_t *msg) +{ + if (msg->opt != NULL) { + if (msg->opt_reserved > 0) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + } + INSIST(dns_rdataset_isassociated(msg->opt)); + dns_rdataset_disassociate(msg->opt); + isc_mempool_put(msg->rdspool, msg->opt); + msg->opt = NULL; + msg->cc_ok = 0; + msg->cc_bad = 0; + } +} + +static void +msgresetsigs(dns_message_t *msg, bool 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, bool 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, false); + + /* + * Clean up linked lists. + */ + + /* + * Run through the free lists, and just unlink anything found there. + * The memory isn't lost since these are part of message blocks we + * have allocated. + */ + rdata = ISC_LIST_HEAD(msg->freerdata); + while (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + rdata = ISC_LIST_HEAD(msg->freerdata); + } + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + while (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + } + + dynbuf = ISC_LIST_HEAD(msg->scratchpad); + INSIST(dynbuf != NULL); + if (!everything) { + isc_buffer_clear(dynbuf); + dynbuf = ISC_LIST_NEXT(dynbuf, link); + } + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + msgblock = ISC_LIST_HEAD(msg->rdatas); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatas, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t)); + msgblock = next_msgblock; + } + + /* + * rdatalists could be empty. + */ + + msgblock = ISC_LIST_HEAD(msg->rdatalists); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatalists, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t)); + msgblock = next_msgblock; + } + + msgblock = ISC_LIST_HEAD(msg->offsets); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->offsets, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t)); + msgblock = next_msgblock; + } + + if (msg->tsigkey != NULL) { + dns_tsigkey_detach(&msg->tsigkey); + msg->tsigkey = NULL; + } + + if (msg->tsigctx != NULL) + dst_context_destroy(&msg->tsigctx); + + if (msg->query.base != NULL) { + if (msg->free_query != 0) + isc_mem_put(msg->mctx, msg->query.base, + msg->query.length); + msg->query.base = NULL; + msg->query.length = 0; + } + + if (msg->saved.base != NULL) { + if (msg->free_saved != 0) + isc_mem_put(msg->mctx, msg->saved.base, + msg->saved.length); + msg->saved.base = NULL; + msg->saved.length = 0; + } + + /* + * cleanup the buffer cleanup list + */ + dynbuf = ISC_LIST_HEAD(msg->cleanup); + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->cleanup, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + /* + * Set other bits to normal default values. + */ + if (!everything) + msginit(msg); + + ENSURE(isc_mempool_getallocated(msg->namepool) == 0); + ENSURE(isc_mempool_getallocated(msg->rdspool) == 0); +} + +static unsigned int +spacefortsig(dns_tsigkey_t *key, int otherlen) { + isc_region_t r1, r2; + unsigned int x; + isc_result_t result; + + /* + * The space required for an TSIG record is: + * + * n1 bytes for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the rdlength + * n2 bytes for the algorithm name + * 6 bytes for the time signed + * 2 bytes for the fudge + * 2 bytes for the MAC size + * x bytes for the MAC + * 2 bytes for the original id + * 2 bytes for the error + * 2 bytes for the other data length + * y bytes for the other data (at most) + * --------------------------------- + * 26 + n1 + n2 + x + y bytes + */ + + dns_name_toregion(&key->name, &r1); + dns_name_toregion(key->algorithm, &r2); + if (key->key == NULL) + x = 0; + else { + result = dst_key_sigsize(key->key, &x); + if (result != ISC_R_SUCCESS) + x = 0; + } + return (26 + r1.length + r2.length + x + otherlen); +} + +isc_result_t +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) +{ + dns_message_t *m; + isc_result_t result; + isc_buffer_t *dynbuf; + unsigned int i; + + REQUIRE(mctx != NULL); + REQUIRE(msgp != NULL); + REQUIRE(*msgp == NULL); + REQUIRE(intent == DNS_MESSAGE_INTENTPARSE + || intent == DNS_MESSAGE_INTENTRENDER); + + m = isc_mem_get(mctx, sizeof(dns_message_t)); + if (m == NULL) + return (ISC_R_NOMEMORY); + + /* + * No allocations until further notice. Just initialize all lists + * and other members that are freed in the cleanup phase here. + */ + + m->magic = DNS_MESSAGE_MAGIC; + m->from_to_wire = intent; + msginit(m); + + for (i = 0; i < DNS_SECTION_MAX; i++) + ISC_LIST_INIT(m->sections[i]); + + m->mctx = NULL; + isc_mem_attach(mctx, &m->mctx); + + ISC_LIST_INIT(m->scratchpad); + ISC_LIST_INIT(m->cleanup); + m->namepool = NULL; + m->rdspool = NULL; + ISC_LIST_INIT(m->rdatas); + ISC_LIST_INIT(m->rdatalists); + ISC_LIST_INIT(m->offsets); + ISC_LIST_INIT(m->freerdata); + ISC_LIST_INIT(m->freerdatalist); + + /* + * Ok, it is safe to allocate (and then "goto cleanup" if failure) + */ + + result = isc_mempool_create(m->mctx, sizeof(dns_name_t), &m->namepool); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_mempool_setfillcount(m->namepool, NAME_COUNT); + 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_setfillcount(m->rdspool, RDATASET_COUNT); + isc_mempool_setfreemax(m->rdspool, RDATASET_COUNT); + isc_mempool_setname(m->rdspool, "msg:rdataset"); + + dynbuf = NULL; + result = isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_APPEND(m->scratchpad, dynbuf, link); + + m->cctx = NULL; + + *msgp = m; + return (ISC_R_SUCCESS); + + /* + * Cleanup for error returns. + */ + cleanup: + dynbuf = ISC_LIST_HEAD(m->scratchpad); + if (dynbuf != NULL) { + ISC_LIST_UNLINK(m->scratchpad, dynbuf, link); + isc_buffer_free(&dynbuf); + } + if (m->namepool != NULL) + isc_mempool_destroy(&m->namepool); + if (m->rdspool != NULL) + isc_mempool_destroy(&m->rdspool); + m->magic = 0; + isc_mem_putanddetach(&mctx, m, sizeof(dns_message_t)); + + return (ISC_R_NOMEMORY); +} + +void +dns_message_reset(dns_message_t *msg, unsigned int intent) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(intent == DNS_MESSAGE_INTENTPARSE + || intent == DNS_MESSAGE_INTENTRENDER); + + msgreset(msg, 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, true); + isc_mempool_destroy(&msg->namepool); + isc_mempool_destroy(&msg->rdspool); + msg->magic = 0; + isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t)); +} + +static isc_result_t +findname(dns_name_t **foundname, dns_name_t *target, + dns_namelist_t *section) +{ + dns_name_t *curr; + + for (curr = ISC_LIST_TAIL(*section); + curr != NULL; + curr = ISC_LIST_PREV(curr, link)) { + if (dns_name_equal(curr, target)) { + if (foundname != NULL) + *foundname = curr; + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_message_find(dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t type, dns_rdatatype_t covers, + dns_rdataset_t **rdataset) +{ + dns_rdataset_t *curr; + + REQUIRE(name != NULL); + REQUIRE(rdataset == NULL || *rdataset == NULL); + + for (curr = ISC_LIST_TAIL(name->list); + curr != NULL; + curr = ISC_LIST_PREV(curr, link)) { + if (curr->rdclass == rdclass && + curr->type == type && curr->covers == covers) { + if (rdataset != NULL) + *rdataset = curr; + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_message_findtype(dns_name_t *name, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_rdataset_t **rdataset) +{ + dns_rdataset_t *curr; + + REQUIRE(name != NULL); + REQUIRE(rdataset == NULL || *rdataset == NULL); + + for (curr = ISC_LIST_TAIL(name->list); + curr != NULL; + curr = ISC_LIST_PREV(curr, link)) { + if (curr->type == type && curr->covers == covers) { + if (ISC_UNLIKELY(rdataset != NULL)) + *rdataset = curr; + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +/* + * Read a name from buffer "source". + */ +static isc_result_t +getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg, + dns_decompress_t *dctx) +{ + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + + scratch = currentbuffer(msg); + + /* + * First try: use current buffer. + * Second try: allocate a new buffer and use that. + */ + tries = 0; + while (tries < 2) { + result = dns_name_fromwire(name, source, dctx, false, + scratch); + + if (result == ISC_R_NOSPACE) { + tries++; + + result = newbuffer(msg, SCRATCHPAD_SIZE); + if (result != ISC_R_SUCCESS) + return (result); + + scratch = currentbuffer(msg); + dns_name_reset(name); + } else { + return (result); + } + } + + INSIST(0); /* Cannot get here... */ + return (ISC_R_UNEXPECTED); +} + +static isc_result_t +getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, + unsigned int rdatalen, dns_rdata_t *rdata) +{ + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + unsigned int trysize; + + scratch = currentbuffer(msg); + + isc_buffer_setactive(source, rdatalen); + + /* + * First try: use current buffer. + * Second try: allocate a new buffer of size + * max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen) + * (the data will fit if it was not more than 50% compressed) + * Subsequent tries: double buffer size on each try. + */ + tries = 0; + trysize = 0; + /* XXX possibly change this to a while (tries < 2) loop */ + for (;;) { + result = dns_rdata_fromwire(rdata, rdclass, rdtype, + source, dctx, 0, + scratch); + + if (result == ISC_R_NOSPACE) { + if (tries == 0) { + trysize = 2 * rdatalen; + if (trysize < SCRATCHPAD_SIZE) + trysize = SCRATCHPAD_SIZE; + } else { + INSIST(trysize != 0); + if (trysize >= 65535) + return (ISC_R_NOSPACE); + /* XXX DNS_R_RRTOOLONG? */ + trysize *= 2; + } + tries++; + result = newbuffer(msg, trysize); + if (result != ISC_R_SUCCESS) + return (result); + + scratch = currentbuffer(msg); + } else { + return (result); + } + } +} + +#define DO_ERROR(r) \ + do { \ + if (best_effort) \ + seen_problem = true; \ + else { \ + result = r; \ + goto cleanup; \ + } \ + } while (0) + +static isc_result_t +getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + unsigned int options) +{ + isc_region_t r; + unsigned int count; + dns_name_t *name; + dns_name_t *name2; + dns_offsets_t *offsets; + dns_rdataset_t *rdataset; + dns_rdatalist_t *rdatalist; + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section; + bool free_name; + bool best_effort; + bool seen_problem; + + section = &msg->sections[DNS_SECTION_QUESTION]; + + best_effort = (options & DNS_MESSAGEPARSE_BESTEFFORT); + seen_problem = 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 = true; + + offsets = newoffsets(msg); + if (offsets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(name, *offsets); + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the allocated + * name since we no longer need it, and set our name pointer + * to point to the name we found. + */ + result = findname(&name2, name, section); + + /* + * If it is the first name in the section, accept it. + * + * If it is not, but is not the same as the name already + * in the question section, append to the section. Note that + * here in the question section this is illegal, so return + * FORMERR. In the future, check the opcode to see if + * this should be legal or not. In either case we no longer + * need this name pointer. + */ + if (result != ISC_R_SUCCESS) { + if (!ISC_LIST_EMPTY(*section)) + DO_ERROR(DNS_R_FORMERR); + ISC_LIST_APPEND(*section, name, link); + free_name = false; + } else { + isc_mempool_put(msg->namepool, name); + name = name2; + name2 = NULL; + free_name = false; + } + + /* + * Get type and class. + */ + isc_buffer_remainingregion(source, &r); + if (r.length < 4) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If this class is different than the one we already read, + * this is an error. + */ + if (msg->rdclass_set == 0) { + msg->rdclass = rdclass; + msg->rdclass_set = 1; + } else if (msg->rdclass != rdclass) + DO_ERROR(DNS_R_FORMERR); + + /* + * Is this a TKEY query? + */ + if (rdtype == dns_rdatatype_tkey) + msg->tkey = 1; + + /* + * Can't ask the same question twice. + */ + result = dns_message_find(name, rdclass, rdtype, 0, NULL); + if (result == ISC_R_SUCCESS) + DO_ERROR(DNS_R_FORMERR); + + /* + * Allocate a new rdatalist. + */ + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + rdataset = isc_mempool_get(msg->rdspool); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * Convert rdatalist to rdataset, and attach the latter to + * the name. + */ + rdatalist->type = rdtype; + rdatalist->rdclass = rdclass; + + dns_rdataset_init(rdataset); + result = dns_rdatalist_tordataset(rdatalist, rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + rdataset->attributes |= DNS_RDATASETATTR_QUESTION; + + ISC_LIST_APPEND(name->list, rdataset, link); + rdataset = NULL; + } + + if (seen_problem) + return (DNS_R_RECOVERABLE); + return (ISC_R_SUCCESS); + + cleanup: + if (rdataset != NULL) { + INSIST(!dns_rdataset_isassociated(rdataset)); + isc_mempool_put(msg->rdspool, rdataset); + } +#if 0 + if (rdatalist != NULL) + isc_mempool_put(msg->rdlpool, rdatalist); +#endif + if (free_name) + isc_mempool_put(msg->namepool, name); + + return (result); +} + +static bool +update(dns_section_t section, dns_rdataclass_t rdclass) { + if (section == DNS_SECTION_PREREQUISITE) + return (rdclass == dns_rdataclass_any || + rdclass == dns_rdataclass_none); + if (section == DNS_SECTION_UPDATE) + return (rdclass == dns_rdataclass_any); + return (false); +} + +/* + * Check to confirm that all DNSSEC records (DS, NSEC, NSEC3) have + * covering RRSIGs. + */ +static bool +auth_signed(dns_namelist_t *section) { + dns_name_t *name; + + for (name = ISC_LIST_HEAD(*section); + name != NULL; + name = ISC_LIST_NEXT(name, link)) + { + int auth_dnssec = 0, auth_rrsig = 0; + dns_rdataset_t *rds; + + for (rds = ISC_LIST_HEAD(name->list); + rds != NULL; + rds = ISC_LIST_NEXT(rds, link)) + { + switch (rds->type) { + case dns_rdatatype_ds: + auth_dnssec |= 0x1; + break; + case dns_rdatatype_nsec: + auth_dnssec |= 0x2; + break; + case dns_rdatatype_nsec3: + auth_dnssec |= 0x4; + break; + case dns_rdatatype_rrsig: + break; + default: + continue; + } + + switch (rds->covers) { + case dns_rdatatype_ds: + auth_rrsig |= 0x1; + break; + case dns_rdatatype_nsec: + auth_rrsig |= 0x2; + break; + case dns_rdatatype_nsec3: + auth_rrsig |= 0x4; + break; + default: + break; + } + } + + if (auth_dnssec != auth_rrsig) + return (false); + } + + return (true); +} + +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 = NULL; + dns_name_t *name2 = NULL; + 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; + bool free_name = false, free_rdataset = false; + bool preserve_order, best_effort, seen_problem; + bool issigzero; + + preserve_order = (options & DNS_MESSAGEPARSE_PRESERVEORDER); + best_effort = (options & DNS_MESSAGEPARSE_BESTEFFORT); + seen_problem = false; + + section = &msg->sections[sectionid]; + + for (count = 0; count < msg->counts[sectionid]; count++) { + int recstart = source->current; + bool skip_name_search, skip_type_search; + + skip_name_search = false; + skip_type_search = false; + free_rdataset = false; + + name = isc_mempool_get(msg->namepool); + if (name == NULL) + return (ISC_R_NOMEMORY); + free_name = true; + + offsets = newoffsets(msg); + if (offsets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(name, *offsets); + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * Get type, class, ttl, and rdatalen. Verify that at least + * rdatalen bytes remain. (Some of this is deferred to + * later.) + */ + isc_buffer_remainingregion(source, &r); + if (r.length < 2 + 2 + 4 + 2) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If there was no question section, we may not yet have + * established a class. Do so now. + */ + if (msg->rdclass_set == 0 && + rdtype != dns_rdatatype_opt && /* class is UDP SIZE */ + rdtype != dns_rdatatype_tsig && /* class is ANY */ + rdtype != dns_rdatatype_tkey) { /* class is undefined */ + msg->rdclass = rdclass; + msg->rdclass_set = 1; + } + + /* + * If this class is different than the one in the question + * section, bail. + */ + if (msg->opcode != dns_opcode_update + && rdtype != dns_rdatatype_tsig + && rdtype != dns_rdatatype_opt + && rdtype != dns_rdatatype_key /* in a TKEY query */ + && rdtype != dns_rdatatype_sig /* SIG(0) */ + && rdtype != dns_rdatatype_tkey /* Win2000 TKEY */ + && msg->rdclass != dns_rdataclass_any + && msg->rdclass != rdclass) + DO_ERROR(DNS_R_FORMERR); + + /* + * If this is not a TKEY query/response then the KEY + * record's class needs to match. + */ + if (msg->opcode != dns_opcode_update && !msg->tkey && + rdtype == dns_rdatatype_key && + msg->rdclass != dns_rdataclass_any && + msg->rdclass != rdclass) + DO_ERROR(DNS_R_FORMERR); + + /* + * Special type handling for TSIG, OPT, and TKEY. + */ + if (rdtype == dns_rdatatype_tsig) { + /* + * If it is a tsig, verify that it is in the + * additional data section. + */ + if (sectionid != DNS_SECTION_ADDITIONAL || + rdclass != dns_rdataclass_any || + count != msg->counts[sectionid] - 1) + DO_ERROR(DNS_R_BADTSIG); + msg->sigstart = recstart; + skip_name_search = true; + skip_type_search = true; + } else if (rdtype == dns_rdatatype_opt) { + /* + * The name of an OPT record must be ".", it + * must be in the additional data section, and + * it must be the first OPT we've seen. + */ + if (!dns_name_equal(dns_rootname, name) || + sectionid != DNS_SECTION_ADDITIONAL || + msg->opt != NULL) + DO_ERROR(DNS_R_FORMERR); + skip_name_search = true; + skip_type_search = true; + } else if (rdtype == dns_rdatatype_tkey) { + /* + * A TKEY must be in the additional section if this + * is a query, and the answer section if this is a + * response. Unless it's a Win2000 client. + * + * Its class is ignored. + */ + dns_section_t tkeysection; + + if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0) + tkeysection = DNS_SECTION_ADDITIONAL; + else + tkeysection = DNS_SECTION_ANSWER; + if (sectionid != tkeysection && + sectionid != DNS_SECTION_ANSWER) + DO_ERROR(DNS_R_FORMERR); + } + + /* + * ... now get ttl and rdatalen, and check buffer. + */ + ttl = isc_buffer_getuint32(source); + rdatalen = isc_buffer_getuint16(source); + r.length -= (2 + 2 + 4 + 2); + if (r.length < rdatalen) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + /* + * Read the rdata from the wire format. Interpret the + * rdata according to its actual class, even if it had a + * DynDNS meta-class in the packet (unless this is a TSIG). + * Then put the meta-class back into the finished rdata. + */ + rdata = newrdata(msg); + if (rdata == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + if (msg->opcode == dns_opcode_update && + update(sectionid, rdclass)) { + if (rdatalen != 0) { + result = DNS_R_FORMERR; + goto cleanup; + } + /* + * When the rdata is empty, the data pointer is + * never dereferenced, but it must still be non-NULL. + * Casting 1 rather than "" avoids warnings about + * discarding the const attribute of a string, + * for compilers that would warn about such things. + */ + rdata->data = (unsigned char *)1; + rdata->length = 0; + rdata->rdclass = rdclass; + rdata->type = rdtype; + rdata->flags = DNS_RDATA_UPDATE; + result = ISC_R_SUCCESS; + } else if (rdclass == dns_rdataclass_none && + msg->opcode == dns_opcode_update && + sectionid == DNS_SECTION_UPDATE) { + result = getrdata(source, msg, dctx, msg->rdclass, + rdtype, rdatalen, rdata); + } else + result = getrdata(source, msg, dctx, rdclass, + rdtype, rdatalen, rdata); + if (result != ISC_R_SUCCESS) + goto cleanup; + rdata->rdclass = rdclass; + issigzero = false; + if (rdtype == dns_rdatatype_rrsig && + rdata->flags == 0) { + covers = dns_rdata_covers(rdata); + if (covers == 0) + DO_ERROR(DNS_R_FORMERR); + } else if (rdtype == dns_rdatatype_sig /* SIG(0) */ && + rdata->flags == 0) { + covers = dns_rdata_covers(rdata); + if (covers == 0) { + if (sectionid != DNS_SECTION_ADDITIONAL || + count != msg->counts[sectionid] - 1) + DO_ERROR(DNS_R_BADSIG0); + msg->sigstart = recstart; + skip_name_search = true; + skip_type_search = true; + issigzero = true; + } else { + if (msg->rdclass != dns_rdataclass_any && + msg->rdclass != rdclass) + DO_ERROR(DNS_R_FORMERR); + } + } else + covers = 0; + + /* + * Check the ownername of NSEC3 records + */ + if (rdtype == dns_rdatatype_nsec3 && + !dns_rdata_checkowner(name, msg->rdclass, rdtype, + false)) { + result = DNS_R_BADOWNERNAME; + goto cleanup; + } + + /* + * If we are doing a dynamic update or this is a meta-type, + * don't bother searching for a name, just append this one + * to the end of the message. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_name_search) { + if (rdtype != dns_rdatatype_opt && + rdtype != dns_rdatatype_tsig && + !issigzero) + { + ISC_LIST_APPEND(*section, name, link); + free_name = 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 = false; + } + + /* + * Search name for the particular type and class. + * Skip this stage if in update mode or this is a meta-type. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_type_search) + result = ISC_R_NOTFOUND; + else { + /* + * If this is a type that can only occur in + * the question section, fail. + */ + if (dns_rdatatype_questiononly(rdtype)) + DO_ERROR(DNS_R_FORMERR); + + rdataset = NULL; + result = dns_message_find(name, rdclass, rdtype, + covers, &rdataset); + } + + /* + * If we found an rdataset that matches, we need to + * append this rdata to that set. If we did not, we need + * to create a new rdatalist, store the important bits there, + * convert it to an rdataset, and link the latter to the name. + * Yuck. When appending, make certain that the type isn't + * a singleton type, such as SOA or CNAME. + * + * Note that this check will be bypassed when preserving order, + * the opcode is an update, or the type search is skipped. + */ + if (result == ISC_R_SUCCESS) { + if (dns_rdatatype_issingleton(rdtype)) { + dns_rdata_t *first; + dns_rdatalist_fromrdataset(rdataset, + &rdatalist); + first = ISC_LIST_HEAD(rdatalist->rdata); + INSIST(first != NULL); + if (dns_rdata_compare(rdata, first) != 0) + DO_ERROR(DNS_R_FORMERR); + } + } + + if (result == ISC_R_NOTFOUND) { + rdataset = isc_mempool_get(msg->rdspool); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + free_rdataset = true; + + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + rdatalist->type = rdtype; + rdatalist->covers = covers; + rdatalist->rdclass = rdclass; + rdatalist->ttl = ttl; + + dns_rdataset_init(rdataset); + RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, + rdataset) + == ISC_R_SUCCESS); + dns_rdataset_setownercase(rdataset, name); + + if (rdtype != dns_rdatatype_opt && + rdtype != dns_rdatatype_tsig && + !issigzero) + { + ISC_LIST_APPEND(name->list, rdataset, link); + free_rdataset = false; + } + } + + /* + * Minimize TTLs. + * + * Section 5.2 of RFC2181 says we should drop + * nonauthoritative rrsets where the TTLs differ, but we + * currently treat them the as if they were authoritative and + * minimize them. + */ + if (ttl != rdataset->ttl) { + rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED; + if (ttl < rdataset->ttl) + rdataset->ttl = ttl; + } + + /* Append this rdata to the rdataset. */ + dns_rdatalist_fromrdataset(rdataset, &rdatalist); + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + + /* + * If this is an OPT, SIG(0) or TSIG record, remember it. + * Also, set the extended rcode for TSIG. + * + * Note msg->opt, msg->sig0 and msg->tsig will only be + * already set if best-effort parsing is enabled otherwise + * there will only be at most one of each. + */ + if (rdtype == dns_rdatatype_opt && msg->opt == NULL) { + dns_rcode_t ercode; + + msg->opt = rdataset; + rdataset = NULL; + free_rdataset = false; + ercode = (dns_rcode_t) + ((msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK) + >> 20); + msg->rcode |= ercode; + isc_mempool_put(msg->namepool, name); + free_name = false; + } else if (issigzero && msg->sig0 == NULL) { + msg->sig0 = rdataset; + msg->sig0name = name; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } else if (rdtype == dns_rdatatype_tsig && msg->tsig == NULL) { + msg->tsig = rdataset; + msg->tsigname = name; + /* Windows doesn't like TSIG names to be compressed. */ + msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } + + if (seen_problem) { + if (free_name) + isc_mempool_put(msg->namepool, name); + if (free_rdataset) + isc_mempool_put(msg->rdspool, rdataset); + free_name = free_rdataset = false; + } + INSIST(free_name == false); + INSIST(free_rdataset == false); + } + + /* + * If any of DS, NSEC or NSEC3 appeared in the + * authority section of a query response without + * a covering RRSIG, FORMERR + */ + if (sectionid == DNS_SECTION_AUTHORITY && + msg->opcode == dns_opcode_query && + ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) && + ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) && + !preserve_order && + !auth_signed(section)) + DO_ERROR(DNS_R_FORMERR); + + 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; + uint16_t tmpflags; + isc_buffer_t origsource; + bool seen_problem; + bool ignore_tc; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(source != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + seen_problem = false; + ignore_tc = (options & DNS_MESSAGEPARSE_IGNORETRUNCATION); + + origsource = *source; + + msg->header_ok = 0; + msg->question_ok = 0; + + isc_buffer_remainingregion(source, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) + return (ISC_R_UNEXPECTEDEND); + + msg->id = isc_buffer_getuint16(source); + tmpflags = isc_buffer_getuint16(source); + msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK) + >> DNS_MESSAGE_OPCODE_SHIFT); + msg->rcode = (dns_rcode_t)(tmpflags & DNS_MESSAGE_RCODE_MASK); + msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK); + msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source); + + msg->header_ok = 1; + msg->state = DNS_SECTION_QUESTION; + + /* + * -1 means no EDNS. + */ + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY); + + dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14); + + ret = getquestions(source, msg, &dctx, options); + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) + goto truncated; + if (ret == DNS_R_RECOVERABLE) { + seen_problem = 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 = 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 = 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 = true; + ret = ISC_R_SUCCESS; + } + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_remainingregion(source, &r); + if (r.length != 0) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MESSAGE, ISC_LOG_DEBUG(3), + "message has %u byte(s) of trailing garbage", + r.length); + } + + truncated: + if ((options & DNS_MESSAGEPARSE_CLONEBUFFER) == 0) + isc_buffer_usedregion(&origsource, &msg->saved); + else { + msg->saved.length = isc_buffer_usedlength(&origsource); + msg->saved.base = isc_mem_get(msg->mctx, msg->saved.length); + if (msg->saved.base == NULL) + return (ISC_R_NOMEMORY); + memmove(msg->saved.base, isc_buffer_base(&origsource), + msg->saved.length); + msg->free_saved = 1; + } + + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) + return (DNS_R_RECOVERABLE); + if (seen_problem == 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 - DNS_MESSAGE_HEADERLEN < msg->reserved) + return (ISC_R_NOSPACE); + + /* + * Reserve enough space for the header in this buffer. + */ + isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN); + + msg->buffer = buffer; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) { + isc_region_t r, rn; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(msg->buffer != NULL); + + /* + * Ensure that the new buffer is empty, and has enough space to + * hold the current contents. + */ + isc_buffer_clear(buffer); + + isc_buffer_availableregion(buffer, &rn); + isc_buffer_usedregion(msg->buffer, &r); + REQUIRE(rn.length > r.length); + + /* + * Copy the contents from the old to the new buffer. + */ + isc_buffer_add(buffer, r.length); + memmove(rn.base, r.base, r.length); + + msg->buffer = buffer; + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderrelease(dns_message_t *msg, unsigned int space) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(space <= msg->reserved); + + msg->reserved -= space; +} + +isc_result_t +dns_message_renderreserve(dns_message_t *msg, unsigned int space) { + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->buffer != NULL) { + isc_buffer_availableregion(msg->buffer, &r); + if (r.length < (space + msg->reserved)) + return (ISC_R_NOSPACE); + } + + msg->reserved += space; + + return (ISC_R_SUCCESS); +} + +static inline bool +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 (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 (false); + + return (true); +} + +#ifdef ALLOW_FILTER_AAAA +/* + * Decide whether to not answer with an AAAA record and its RRSIG + */ +static inline bool +norender_rdataset(const dns_rdataset_t *rdataset, unsigned int options, + dns_section_t sectionid) +{ + if (sectionid == DNS_SECTION_QUESTION) + return (false); + + switch (rdataset->type) { + case dns_rdatatype_ns: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 || + sectionid != DNS_SECTION_AUTHORITY) + return (false); + break; + + case dns_rdatatype_aaaa: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0) + return (false); + break; + + case dns_rdatatype_rrsig: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 || + (rdataset->covers != dns_rdatatype_ns && + rdataset->covers != dns_rdatatype_aaaa)) + return (false); + if ((rdataset->covers == dns_rdatatype_ns) && + (sectionid != DNS_SECTION_AUTHORITY)) + return (false); + break; + + default: + return (false); + } + + if (rdataset->rdclass != dns_rdataclass_in) + return (false); + + return (true); +} +#endif + +static isc_result_t +renderset(dns_rdataset_t *rdataset, dns_name_t *owner_name, + dns_compress_t *cctx, isc_buffer_t *target, + unsigned int reserved, unsigned int options, unsigned int *countp) +{ + isc_result_t result; + + /* + * Shrink the space in the buffer by the reserved amount. + */ + if (target->length - target->used < reserved) + return (ISC_R_NOSPACE); + + target->length -= reserved; + result = dns_rdataset_towire(rdataset, owner_name, + cctx, target, options, countp); + target->length += reserved; + + return (result); +} + +static void +maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) { + if (msg->counts[sectionid] == 0 && + (sectionid == DNS_SECTION_ANSWER || + (sectionid == DNS_SECTION_AUTHORITY && + msg->counts[DNS_SECTION_ANSWER] == 0))) + msg->flags &= ~DNS_MESSAGEFLAG_AD; +} + +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; + bool partial = 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. + */ + if (msg->buffer->length - msg->buffer->used < msg->reserved) + return (ISC_R_NOSPACE); + msg->buffer->length -= msg->reserved; + + total = 0; + if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0) + partial = true; + + /* + * Render required glue first. Set TC if it won't fit. + */ + name = ISC_LIST_HEAD(*section); + if (name != NULL) { + rdataset = ISC_LIST_HEAD(name->list); + if (rdataset != NULL && + (rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) != 0 && + (rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0) { + const void *order_arg = msg->order_arg; + st = *(msg->buffer); + count = 0; + if (partial) + result = dns_rdataset_towirepartial(rdataset, + name, + msg->cctx, + msg->buffer, + msg->order, + order_arg, + rd_options, + &count, + NULL); + else + result = dns_rdataset_towiresorted(rdataset, + name, + msg->cctx, + msg->buffer, + msg->order, + order_arg, + rd_options, + &count); + total += count; + if (partial && result == ISC_R_NOSPACE) { + msg->flags |= DNS_MESSAGEFLAG_TC; + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + if (result == ISC_R_NOSPACE) + msg->flags |= DNS_MESSAGEFLAG_TC; + if (result != ISC_R_SUCCESS) { + INSIST(st.used < 65536); + dns_compress_rollback(msg->cctx, + (uint16_t)st.used); + *(msg->buffer) = st; /* rollback */ + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + rdataset->attributes |= DNS_RDATASETATTR_RENDERED; + } + } + + do { + name = ISC_LIST_HEAD(*section); + if (name == NULL) { + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (ISC_R_SUCCESS); + } + + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, link); + + rdataset = ISC_LIST_HEAD(name->list); + while (rdataset != NULL) { + next_rdataset = ISC_LIST_NEXT(rdataset, link); + + if ((rdataset->attributes & + DNS_RDATASETATTR_RENDERED) != 0) + goto next; + + if (((options & DNS_MESSAGERENDER_ORDERED) + == 0) + && (sectionid == DNS_SECTION_ADDITIONAL) + && wrong_priority(rdataset, pass, + preferred_glue)) + goto next; + +#ifdef ALLOW_FILTER_AAAA + /* + * Suppress AAAAs if asked and we are + * not doing DNSSEC or are breaking DNSSEC. + * Say so in the AD bit if we break DNSSEC. + */ + if (norender_rdataset(rdataset, options, sectionid)) { + if (sectionid == DNS_SECTION_ANSWER || + sectionid == DNS_SECTION_AUTHORITY) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + if (OPTOUT(rdataset)) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + goto next; + } + +#endif + st = *(msg->buffer); + + count = 0; + if (partial) + result = dns_rdataset_towirepartial( + rdataset, + name, + msg->cctx, + msg->buffer, + msg->order, + msg->order_arg, + rd_options, + &count, + NULL); + else + result = dns_rdataset_towiresorted( + rdataset, + name, + msg->cctx, + msg->buffer, + msg->order, + msg->order_arg, + rd_options, + &count); + + total += count; + + /* + * If out of space, record stats on what we + * rendered so far, and return that status. + * + * XXXMLG Need to change this when + * dns_rdataset_towire() can render partial + * sets starting at some arbitrary point in the + * set. This will include setting a bit in the + * rdataset to indicate that a partial + * rendering was done, and some state saved + * somewhere (probably in the message struct) + * to indicate where to continue from. + */ + if (partial && result == ISC_R_NOSPACE) { + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + if (result != ISC_R_SUCCESS) { + INSIST(st.used < 65536); + dns_compress_rollback(msg->cctx, + (uint16_t)st.used); + *(msg->buffer) = st; /* rollback */ + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + maybe_clear_ad(msg, sectionid); + return (result); + } + + /* + * If we have rendered non-validated data, + * ensure that the AD bit is not set. + */ + if (rdataset->trust != dns_trust_secure && + (sectionid == DNS_SECTION_ANSWER || + sectionid == DNS_SECTION_AUTHORITY)) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + if (OPTOUT(rdataset)) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + + rdataset->attributes |= + DNS_RDATASETATTR_RENDERED; + + next: + rdataset = next_rdataset; + } + + name = next_name; + } + } while (--pass != 0); + + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) { + 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, + (uint16_t)msg->counts[DNS_SECTION_QUESTION]); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_ANSWER]); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_AUTHORITY]); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]); +} + +isc_result_t +dns_message_renderend(dns_message_t *msg) { + isc_buffer_t tmpbuf; + isc_region_t r; + int result; + unsigned int count; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + + if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) { + /* + * We have an extended rcode but are not using EDNS. + */ + return (DNS_R_FORMERR); + } + + /* + * If we're adding a OPT, TSIG or SIG(0) to a truncated message, + * clear all rdatasets from the message except for the question + * before adding the OPT, TSIG or SIG(0). If the question doesn't + * fit, don't include it. + */ + if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) && + (msg->flags & DNS_MESSAGEFLAG_TC) != 0) + { + isc_buffer_t *buf; + + msgresetnames(msg, DNS_SECTION_ANSWER); + buf = msg->buffer; + dns_message_renderreset(msg); + msg->buffer = buf; + isc_buffer_clear(msg->buffer); + isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN); + dns_compress_rollback(msg->cctx, 0); + result = dns_message_rendersection(msg, DNS_SECTION_QUESTION, + 0); + if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE) + return (result); + } + + /* + * If we've got an OPT record, render it. + */ + if (msg->opt != NULL) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + /* + * Set the extended rcode. + */ + msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK; + msg->opt->ttl |= ((msg->rcode << 20) & + DNS_MESSAGE_EDNSRCODE_MASK); + /* + * Render. + */ + count = 0; + result = renderset(msg->opt, dns_rootname, msg->cctx, + msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); + } + + /* + * If we're adding a TSIG record, generate and render it. + */ + if (msg->tsigkey != NULL) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + result = dns_tsig_sign(msg); + if (result != ISC_R_SUCCESS) + return (result); + count = 0; + result = renderset(msg->tsig, msg->tsigname, msg->cctx, + msg->buffer, msg->reserved, 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 = renderset(msg->sig0, dns_rootname, msg->cctx, + msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); + } + + isc_buffer_usedregion(msg->buffer, &r); + isc_buffer_init(&tmpbuf, r.base, r.length); + + dns_message_renderheader(msg, &tmpbuf); + + msg->buffer = NULL; /* forget about this buffer only on success XXX */ + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderreset(dns_message_t *msg) { + unsigned int i; + dns_name_t *name; + dns_rdataset_t *rds; + + /* + * Reset the message so that it may be rendered again. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + + msg->buffer = NULL; + + for (i = 0; i < DNS_SECTION_MAX; i++) { + msg->cursors[i] = NULL; + msg->counts[i] = 0; + for (name = ISC_LIST_HEAD(msg->sections[i]); + name != NULL; + name = ISC_LIST_NEXT(name, link)) { + for (rds = ISC_LIST_HEAD(name->list); + rds != NULL; + rds = ISC_LIST_NEXT(rds, link)) { + rds->attributes &= ~DNS_RDATASETATTR_RENDERED; + } + } + } + if (msg->tsigname != NULL) + dns_message_puttempname(msg, &msg->tsigname); + if (msg->tsig != NULL) { + dns_rdataset_disassociate(msg->tsig); + dns_message_puttemprdataset(msg, &msg->tsig); + } + if (msg->sig0 != NULL) { + dns_rdataset_disassociate(msg->sig0); + dns_message_puttemprdataset(msg, &msg->sig0); + } +} + +isc_result_t +dns_message_firstname(dns_message_t *msg, dns_section_t section) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + + msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]); + + if (msg->cursors[section] == NULL) + return (ISC_R_NOMORE); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_nextname(dns_message_t *msg, dns_section_t section) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + REQUIRE(msg->cursors[section] != NULL); + + msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link); + + if (msg->cursors[section] == NULL) + return (ISC_R_NOMORE); + + return (ISC_R_SUCCESS); +} + +void +dns_message_currentname(dns_message_t *msg, dns_section_t section, + dns_name_t **name) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + REQUIRE(name != NULL && *name == NULL); + REQUIRE(msg->cursors[section] != NULL); + + *name = msg->cursors[section]; +} + +isc_result_t +dns_message_findname(dns_message_t *msg, dns_section_t section, + dns_name_t *target, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_name_t **name, + dns_rdataset_t **rdataset) +{ + dns_name_t *foundname; + isc_result_t result; + + /* + * XXX These requirements are probably too intensive, especially + * where things can be NULL, but as they are they ensure that if + * something is NON-NULL, indicating that the caller expects it + * to be filled in, that we can in fact fill it in. + */ + REQUIRE(msg != NULL); + REQUIRE(VALID_SECTION(section)); + REQUIRE(target != NULL); + REQUIRE(name == NULL || *name == NULL); + + if (type == dns_rdatatype_any) { + REQUIRE(rdataset == NULL); + } else { + REQUIRE(rdataset == NULL || *rdataset == NULL); + } + + result = findname(&foundname, target, + &msg->sections[section]); + + if (result == ISC_R_NOTFOUND) + return (DNS_R_NXDOMAIN); + else if (result != ISC_R_SUCCESS) + return (result); + + if (name != NULL) + *name = foundname; + + /* + * And now look for the type. + */ + if (ISC_UNLIKELY(type == dns_rdatatype_any)) + return (ISC_R_SUCCESS); + + result = dns_message_findtype(foundname, type, covers, rdataset); + if (result == ISC_R_NOTFOUND) + return (DNS_R_NXRRSET); + + return (result); +} + +void +dns_message_movename(dns_message_t *msg, dns_name_t *name, + dns_section_t fromsection, + dns_section_t tosection) +{ + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(fromsection)); + REQUIRE(VALID_NAMED_SECTION(tosection)); + + /* + * Unlink the name from the old section + */ + ISC_LIST_UNLINK(msg->sections[fromsection], name, link); + ISC_LIST_APPEND(msg->sections[tosection], name, link); +} + +void +dns_message_addname(dns_message_t *msg, dns_name_t *name, + dns_section_t section) +{ + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(section)); + + ISC_LIST_APPEND(msg->sections[section], name, link); +} + +void +dns_message_removename(dns_message_t *msg, dns_name_t *name, + dns_section_t section) +{ + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(section)); + + ISC_LIST_UNLINK(msg->sections[section], name, link); +} + +isc_result_t +dns_message_gettempname(dns_message_t *msg, dns_name_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = isc_mempool_get(msg->namepool); + if (*item == NULL) + return (ISC_R_NOMEMORY); + dns_name_init(*item, NULL); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettempoffsets(dns_message_t *msg, dns_offsets_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newoffsets(msg); + if (*item == NULL) + return (ISC_R_NOMEMORY); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newrdata(msg); + if (*item == NULL) + return (ISC_R_NOMEMORY); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = isc_mempool_get(msg->rdspool); + if (*item == NULL) + return (ISC_R_NOMEMORY); + + dns_rdataset_init(*item); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newrdatalist(msg); + if (*item == NULL) + return (ISC_R_NOMEMORY); + + return (ISC_R_SUCCESS); +} + +void +dns_message_puttempname(dns_message_t *msg, dns_name_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + if (dns_name_dynamic(*item)) + dns_name_free(*item, msg->mctx); + isc_mempool_put(msg->namepool, *item); + *item = NULL; +} + +void +dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdata(msg, *item); + *item = NULL; +} + +void +dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + REQUIRE(!dns_rdataset_isassociated(*item)); + isc_mempool_put(msg->rdspool, *item); + *item = NULL; +} + +void +dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdatalist(msg, *item); + *item = NULL; +} + +isc_result_t +dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp, + unsigned int *flagsp) +{ + isc_region_t r; + isc_buffer_t buffer; + dns_messageid_t id; + unsigned int flags; + + REQUIRE(source != NULL); + + buffer = *source; + + isc_buffer_remainingregion(&buffer, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) + return (ISC_R_UNEXPECTEDEND); + + id = isc_buffer_getuint16(&buffer); + flags = isc_buffer_getuint16(&buffer); + flags &= DNS_MESSAGE_FLAG_MASK; + + if (flagsp != NULL) + *flagsp = flags; + if (idp != NULL) + *idp = id; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_reply(dns_message_t *msg, bool want_question_section) { + unsigned int clear_from; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0); + + if (!msg->header_ok) + return (DNS_R_FORMERR); + if (msg->opcode != dns_opcode_query && + msg->opcode != dns_opcode_notify) + want_question_section = false; + if (msg->opcode == dns_opcode_update) + clear_from = DNS_SECTION_PREREQUISITE; + else if (want_question_section) { + if (!msg->question_ok) + return (DNS_R_FORMERR); + clear_from = DNS_SECTION_ANSWER; + } else + clear_from = DNS_SECTION_QUESTION; + msg->from_to_wire = DNS_MESSAGE_INTENTRENDER; + msgresetnames(msg, clear_from); + msgresetopt(msg); + msgresetsigs(msg, true); + msginitprivate(msg); + /* + * We now clear most flags and then set QR, ensuring that the + * reply's flags will be in a reasonable state. + */ + if (msg->opcode == dns_opcode_query) + msg->flags &= DNS_MESSAGE_REPLYPRESERVE; + else + msg->flags = 0; + msg->flags |= DNS_MESSAGEFLAG_QR; + + /* + * This saves the query TSIG status, if the query was signed, and + * reserves space in the reply for the TSIG. + */ + if (msg->tsigkey != NULL) { + unsigned int otherlen = 0; + msg->querytsigstatus = msg->tsigstatus; + msg->tsigstatus = dns_rcode_noerror; + if (msg->querytsigstatus == dns_tsigerror_badtime) + otherlen = 6; + msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen); + result = dns_message_renderreserve(msg, msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + } + if (msg->saved.base != NULL) { + msg->query.base = msg->saved.base; + msg->query.length = msg->saved.length; + msg->free_query = msg->free_saved; + msg->saved.base = NULL; + msg->saved.length = 0; + msg->free_saved = 0; + } + + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getopt(dns_message_t *msg) { + + /* + * Get the OPT record for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->opt); +} + +isc_result_t +dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * Set the OPT record for 'msg'. + */ + + /* + * The space required for an OPT record is: + * + * 1 byte for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the rdata length + * --------------------------------- + * 11 bytes + * + * plus the length of the rdata. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(opt->type == dns_rdatatype_opt); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(msg->state == DNS_SECTION_ANY); + + msgresetopt(msg); + + result = dns_rdataset_first(opt); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdataset_current(opt, &rdata); + msg->opt_reserved = 11 + rdata.length; + result = dns_message_renderreserve(msg, msg->opt_reserved); + if (result != ISC_R_SUCCESS) { + msg->opt_reserved = 0; + goto cleanup; + } + + msg->opt = opt; + + return (ISC_R_SUCCESS); + + cleanup: + dns_rdataset_disassociate(opt); + dns_message_puttemprdataset(msg, &opt); + return (result); +} + +dns_rdataset_t * +dns_message_gettsig(dns_message_t *msg, dns_name_t **owner) { + + /* + * Get the TSIG record and owner for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(owner == NULL || *owner == NULL); + + if (owner != NULL) + *owner = msg->tsigname; + return (msg->tsig); +} + +isc_result_t +dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) { + isc_result_t result; + + /* + * Set the TSIG key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->state == DNS_SECTION_ANY); + + if (key == NULL && msg->tsigkey != NULL) { + if (msg->sig_reserved != 0) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + } + dns_tsigkey_detach(&msg->tsigkey); + } + if (key != NULL) { + REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL); + dns_tsigkey_attach(key, &msg->tsigkey); + if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) { + msg->sig_reserved = spacefortsig(msg->tsigkey, 0); + result = dns_message_renderreserve(msg, + msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + dns_tsigkey_detach(&msg->tsigkey); + msg->sig_reserved = 0; + return (result); + } + } + } + return (ISC_R_SUCCESS); +} + +dns_tsigkey_t * +dns_message_gettsigkey(dns_message_t *msg) { + + /* + * Get the TSIG key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->tsigkey); +} + +isc_result_t +dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) { + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *list = NULL; + dns_rdataset_t *set = NULL; + isc_buffer_t *buf = NULL; + isc_region_t r; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->querytsig == NULL); + + if (querytsig == NULL) + return (ISC_R_SUCCESS); + + result = dns_message_gettemprdata(msg, &rdata); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_message_gettemprdatalist(msg, &list); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdataset(msg, &set); + if (result != ISC_R_SUCCESS) + goto cleanup; + + isc_buffer_usedregion(querytsig, &r); + result = isc_buffer_allocate(msg->mctx, &buf, r.length); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_buffer_putmem(buf, r.base, r.length); + isc_buffer_usedregion(buf, &r); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r); + dns_message_takebuffer(msg, &buf); + ISC_LIST_APPEND(list->rdata, rdata, link); + result = dns_rdatalist_tordataset(list, set); + if (result != ISC_R_SUCCESS) + goto cleanup; + + msg->querytsig = set; + + return (result); + + cleanup: + if (rdata != NULL) + dns_message_puttemprdata(msg, &rdata); + if (list != NULL) + dns_message_puttemprdatalist(msg, &list); + if (set != NULL) + dns_message_puttemprdataset(msg, &set); + return (ISC_R_NOMEMORY); +} + +isc_result_t +dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx, + isc_buffer_t **querytsig) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(mctx != NULL); + REQUIRE(querytsig != NULL && *querytsig == NULL); + + if (msg->tsig == NULL) + return (ISC_R_SUCCESS); + + result = dns_rdataset_first(msg->tsig); + if (result != ISC_R_SUCCESS) + return (result); + dns_rdataset_current(msg->tsig, &rdata); + dns_rdata_toregion(&rdata, &r); + + result = isc_buffer_allocate(mctx, querytsig, r.length); + if (result != ISC_R_SUCCESS) + return (result); + isc_buffer_putmem(*querytsig, r.base, r.length); + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getsig0(dns_message_t *msg, dns_name_t **owner) { + + /* + * Get the SIG(0) record for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(owner == NULL || *owner == NULL); + + if (msg->sig0 != NULL && owner != NULL) { + /* If dns_message_getsig0 is called on a rendered message + * after the SIG(0) has been applied, we need to return the + * root name, not NULL. + */ + if (msg->sig0name == NULL) + *owner = dns_rootname; + else + *owner = msg->sig0name; + } + return (msg->sig0); +} + +isc_result_t +dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) { + isc_region_t r; + unsigned int x; + isc_result_t result; + + /* + * Set the SIG(0) key for 'msg' + */ + + /* + * The space required for an SIG(0) record is: + * + * 1 byte for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the type covered + * 1 byte for the algorithm + * 1 bytes for the labels + * 4 bytes for the original ttl + * 4 bytes for the signature expiration + * 4 bytes for the signature inception + * 2 bytes for the key tag + * n bytes for the signer's name + * x bytes for the signature + * --------------------------------- + * 27 + n + x bytes + */ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(msg->state == DNS_SECTION_ANY); + + if (key != NULL) { + REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL); + dns_name_toregion(dst_key_name(key), &r); + result = dst_key_sigsize(key, &x); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + msg->sig_reserved = 27 + r.length + x; + result = dns_message_renderreserve(msg, msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + msg->sig0key = key; + } + return (ISC_R_SUCCESS); +} + +dst_key_t * +dns_message_getsig0key(dns_message_t *msg) { + + /* + * Get the SIG(0) key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->sig0key); +} + +void +dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(ISC_BUFFER_VALID(*buffer)); + + ISC_LIST_APPEND(msg->cleanup, *buffer, link); + *buffer = NULL; +} + +isc_result_t +dns_message_signer(dns_message_t *msg, dns_name_t *signer) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata = DNS_RDATA_INIT; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(signer != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + if (msg->tsig == NULL && msg->sig0 == NULL) + return (ISC_R_NOTFOUND); + + if (msg->verify_attempted == 0) + return (DNS_R_NOTVERIFIEDYET); + + if (!dns_name_hasbuffer(signer)) { + isc_buffer_t *dynbuf = NULL; + result = isc_buffer_allocate(msg->mctx, &dynbuf, 512); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_setbuffer(signer, dynbuf); + dns_message_takebuffer(msg, &dynbuf); + } + + if (msg->sig0 != NULL) { + dns_rdata_sig_t sig; + + result = dns_rdataset_first(msg->sig0); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->sig0, &rdata); + + result = dns_rdata_tostruct(&rdata, &sig, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + if (msg->verified_sig && msg->sig0status == dns_rcode_noerror) + result = ISC_R_SUCCESS; + else + result = DNS_R_SIGINVALID; + dns_name_clone(&sig.signer, signer); + dns_rdata_freestruct(&sig); + } else { + dns_name_t *identity; + dns_rdata_any_tsig_t tsig; + + result = dns_rdataset_first(msg->tsig); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->tsig, &rdata); + + result = dns_rdata_tostruct(&rdata, &tsig, NULL); + INSIST(result == ISC_R_SUCCESS); + if (msg->verified_sig && + msg->tsigstatus == dns_rcode_noerror && + tsig.error == dns_rcode_noerror) + { + result = ISC_R_SUCCESS; + } else if ((!msg->verified_sig) || + (msg->tsigstatus != dns_rcode_noerror)) + { + result = DNS_R_TSIGVERIFYFAILURE; + } else { + INSIST(tsig.error != dns_rcode_noerror); + result = DNS_R_TSIGERRORSET; + } + dns_rdata_freestruct(&tsig); + + if (msg->tsigkey == NULL) { + /* + * If msg->tsigstatus & tsig.error are both + * dns_rcode_noerror, the message must have been + * verified, which means msg->tsigkey will be + * non-NULL. + */ + INSIST(result != ISC_R_SUCCESS); + } else { + identity = dns_tsigkey_identity(msg->tsigkey); + if (identity == NULL) { + if (result == ISC_R_SUCCESS) + result = DNS_R_NOIDENTITY; + identity = &msg->tsigkey->name; + } + dns_name_clone(identity, signer); + } + } + + return (result); +} + +void +dns_message_resetsig(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->verified_sig = 0; + msg->verify_attempted = 0; + msg->tsigstatus = dns_rcode_noerror; + msg->sig0status = dns_rcode_noerror; + msg->timeadjust = 0; + if (msg->tsigkey != NULL) { + dns_tsigkey_detach(&msg->tsigkey); + msg->tsigkey = NULL; + } +} + +isc_result_t +dns_message_rechecksig(dns_message_t *msg, dns_view_t *view) { + dns_message_resetsig(msg); + return (dns_message_checksig(msg, view)); +} + +#ifdef SKAN_MSG_DEBUG +void +dns_message_dumpsig(dns_message_t *msg, char *txt1) { + dns_rdata_t querytsigrdata = DNS_RDATA_INIT; + dns_rdata_any_tsig_t querytsig; + isc_result_t result; + + if (msg->tsig != NULL) { + result = dns_rdataset_first(msg->tsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->tsig, &querytsigrdata); + result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + hexdump(txt1, "TSIG", querytsig.signature, + querytsig.siglen); + } + + if (msg->querytsig != NULL) { + result = dns_rdataset_first(msg->querytsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->querytsig, &querytsigrdata); + result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + hexdump(txt1, "QUERYTSIG", querytsig.signature, + querytsig.siglen); + } +} +#endif + +isc_result_t +dns_message_checksig(dns_message_t *msg, dns_view_t *view) { + isc_buffer_t b, msgb; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL) + return (ISC_R_SUCCESS); + + INSIST(msg->saved.base != NULL); + isc_buffer_init(&msgb, msg->saved.base, msg->saved.length); + isc_buffer_add(&msgb, msg->saved.length); + if (msg->tsigkey != NULL || msg->tsig != NULL) { +#ifdef SKAN_MSG_DEBUG + dns_message_dumpsig(msg, "dns_message_checksig#1"); +#endif + if (view != NULL) + return (dns_view_checksig(view, &msgb, msg)); + else + return (dns_tsig_verify(&msgb, msg, NULL, NULL)); + } else { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_sig_t sig; + dns_rdataset_t keyset; + isc_result_t result; + + result = dns_rdataset_first(msg->sig0); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->sig0, &rdata); + + /* + * This can occur when the message is a dynamic update, since + * the rdata length checking is relaxed. This should not + * happen in a well-formed message, since the SIG(0) is only + * looked for in the additional section, and the dynamic update + * meta-records are in the prerequisite and update sections. + */ + if (rdata.length == 0) + return (ISC_R_UNEXPECTEDEND); + + result = dns_rdata_tostruct(&rdata, &sig, msg->mctx); + if (result != ISC_R_SUCCESS) + return (result); + + dns_rdataset_init(&keyset); + if (view == NULL) + return (DNS_R_KEYUNAUTHORIZED); + result = dns_view_simplefind(view, &sig.signer, + dns_rdatatype_key /* SIG(0) */, + 0, 0, false, &keyset, NULL); + + if (result != ISC_R_SUCCESS) { + /* XXXBEW Should possibly create a fetch here */ + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } else if (keyset.trust < dns_trust_secure) { + /* XXXBEW Should call a validator here */ + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } + result = dns_rdataset_first(&keyset); + INSIST(result == ISC_R_SUCCESS); + for (; + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&keyset)) + { + dst_key_t *key = NULL; + + dns_rdata_reset(&rdata); + dns_rdataset_current(&keyset, &rdata); + isc_buffer_init(&b, rdata.data, rdata.length); + isc_buffer_add(&b, rdata.length); + + result = dst_key_fromdns(&sig.signer, rdata.rdclass, + &b, view->mctx, &key); + if (result != ISC_R_SUCCESS) + continue; + if (dst_key_alg(key) != sig.algorithm || + dst_key_id(key) != sig.keyid || + !(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC || + dst_key_proto(key) == DNS_KEYPROTO_ANY)) + { + dst_key_free(&key); + continue; + } + result = dns_dnssec_verifymessage(&msgb, msg, key); + dst_key_free(&key); + if (result == ISC_R_SUCCESS) + break; + } + if (result == ISC_R_NOMORE) + result = DNS_R_KEYUNAUTHORIZED; + + freesig: + if (dns_rdataset_isassociated(&keyset)) + dns_rdataset_disassociate(&keyset); + dns_rdata_freestruct(&sig); + return (result); + } +} + +#define INDENT(sp) \ + do { \ + unsigned int __i; \ + dns_masterstyle_flags_t __flags = dns_master_styleflags(sp); \ + if ((__flags & DNS_STYLEFLAG_INDENT) == 0ULL && \ + (__flags & DNS_STYLEFLAG_YAML) == 0ULL) \ + break; \ + for (__i = 0; __i < dns_master_indent; __i++) { \ + ADD_STRING(target, dns_master_indentstr); \ + } \ + } while (0) + +isc_result_t +dns_message_sectiontotext(dns_message_t *msg, dns_section_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, + isc_buffer_t *target) { + dns_name_t *name, empty_name; + dns_rdataset_t *rdataset; + isc_result_t result = ISC_R_SUCCESS; + bool seensoa = false; + unsigned int saveindent; + dns_masterstyle_flags_t sflags; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_SECTION(section)); + + saveindent = dns_master_indent; + sflags = dns_master_styleflags(style); + if (ISC_LIST_EMPTY(msg->sections[section])) + goto cleanup; + + + INDENT(style); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, sectiontext[section]); + } else { + ADD_STRING(target, updsectiontext[section]); + } + ADD_STRING(target, "_SECTION:\n"); + } else 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) { + goto cleanup; + } + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + dns_master_indent++; + } + do { + name = NULL; + dns_message_currentname(msg, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + if (section == DNS_SECTION_ANSWER && + rdataset->type == dns_rdatatype_soa) { + if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0) + continue; + if (seensoa && + (flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0) + continue; + seensoa = true; + } + if (section == DNS_SECTION_QUESTION) { + INDENT(style); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + ADD_STRING(target, "- "); + } else { + 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) + goto cleanup; + } + result = dns_message_nextname(msg, section); + } while (result == ISC_R_SUCCESS); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + dns_master_indent--; + } + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0 && + (sflags & DNS_STYLEFLAG_YAML) == 0) + { + INDENT(style); + ADD_STRING(target, "\n"); + } + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + cleanup: + dns_master_indent = saveindent; + return (result); +} + +static isc_result_t +render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) { + int i; + char addr[16], addr_text[64]; + uint16_t family; + uint8_t addrlen, addrbytes, scopelen; + isc_result_t result; + + /* + * Note: This routine needs to handle malformed ECS options. + */ + + if (isc_buffer_remaininglength(ecsbuf) < 4) + return (DNS_R_OPTERR); + family = isc_buffer_getuint16(ecsbuf); + addrlen = isc_buffer_getuint8(ecsbuf); + scopelen = isc_buffer_getuint8(ecsbuf); + + addrbytes = (addrlen + 7) / 8; + if (isc_buffer_remaininglength(ecsbuf) < addrbytes) + return (DNS_R_OPTERR); + + if (addrbytes > sizeof(addr)) + return (DNS_R_OPTERR); + + memset(addr, 0, sizeof(addr)); + for (i = 0; i < addrbytes; i ++) + addr[i] = isc_buffer_getuint8(ecsbuf); + + switch (family) { + case 0: + if (addrlen != 0U || scopelen != 0U) + return (DNS_R_OPTERR); + strlcpy(addr_text, "0", sizeof(addr_text)); + break; + case 1: + if (addrlen > 32 || scopelen > 32) + return (DNS_R_OPTERR); + inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text)); + break; + case 2: + if (addrlen > 128 || scopelen > 128) + return (DNS_R_OPTERR); + inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text)); + break; + default: + return (DNS_R_OPTERR); + } + + ADD_STRING(target, ": "); + ADD_STRING(target, addr_text); + snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen); + ADD_STRING(target, addr_text); + + result = ISC_R_SUCCESS; + + cleanup: + return (result); +} + + +static isc_result_t +dns_message_pseudosectiontoyaml(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 = ISC_R_SUCCESS; + char buf[sizeof("1234567890")]; + uint32_t mbz; + dns_rdata_t rdata; + isc_buffer_t optbuf; + uint16_t optcode, optlen; + unsigned char *optdata; + unsigned int saveindent = dns_master_indent; + + 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) { + goto cleanup; + } + + INDENT(style); + ADD_STRING(target, "OPT_PSEUDOSECTION:\n"); + dns_master_indent++; + + INDENT(style); + ADD_STRING(target, "EDNS:\n"); + dns_master_indent++; + + INDENT(style); + ADD_STRING(target, "version: "); + snprintf(buf, sizeof(buf), "%u", + (unsigned int)((ps->ttl & 0x00ff0000) >> 16)); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "flags:"); + if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) + ADD_STRING(target, " do"); + ADD_STRING(target, "\n"); + mbz = ps->ttl & 0xffff; + mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */ + if (mbz != 0) { + INDENT(style); + ADD_STRING(target, "MBZ: "); + snprintf(buf, sizeof(buf), "0x%.4x", mbz); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } + INDENT(style); + ADD_STRING(target, "udp: "); + snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass); + ADD_STRING(target, buf); + result = dns_rdataset_first(ps); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* + * Print EDNS info, if any. + * + * WARNING: The option contents may be malformed as + * dig +ednsopt=value:<content> does not validity + * checking. + */ + dns_rdata_init(&rdata); + dns_rdataset_current(ps, &rdata); + + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) != 0) { + INSIST(isc_buffer_remaininglength(&optbuf) >= 4U); + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(isc_buffer_remaininglength(&optbuf) >= optlen); + + if (optcode == DNS_OPT_NSID) { + INDENT(style); + ADD_STRING(target, "NSID"); + } else if (optcode == DNS_OPT_COOKIE) { + INDENT(style); + ADD_STRING(target, "COOKIE"); + } else if (optcode == DNS_OPT_CLIENT_SUBNET) { + isc_buffer_t ecsbuf; + INDENT(style); + ADD_STRING(target, "CLIENT-SUBNET"); + isc_buffer_init(&ecsbuf, + isc_buffer_current(&optbuf), + optlen); + isc_buffer_add(&ecsbuf, optlen); + result = render_ecs(&ecsbuf, target); + if (result == ISC_R_NOSPACE) + goto cleanup; + if (result == ISC_R_SUCCESS) { + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, "\n"); + continue; + } + ADD_STRING(target, "\n"); + } else if (optcode == DNS_OPT_EXPIRE) { + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + INDENT(style); + ADD_STRING(target, "EXPIRE: "); + snprintf(buf, sizeof(buf), "%u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, + true, + target); + if (result != ISC_R_SUCCESS) + goto cleanup; + ADD_STRING(target, ")\n"); + continue; + } + INDENT(style); + ADD_STRING(target, "EXPIRE"); + } else if (optcode == DNS_OPT_PAD) { + INDENT(style); + ADD_STRING(target, "PAD"); + } else if (optcode == DNS_OPT_KEY_TAG) { + INDENT(style); + ADD_STRING(target, "KEY-TAG"); + if (optlen > 0U && (optlen % 2U) == 0U) { + const char *sep = ": "; + uint16_t id; + while (optlen > 0U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), "%s%u", + sep, id); + ADD_STRING(target, buf); + sep = ", "; + optlen -= 2; + } + ADD_STRING(target, "\n"); + continue; + } + } else { + INDENT(style); + ADD_STRING(target, "OPT: "); + snprintf(buf, sizeof(buf), "%u", optcode); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } + + if (optlen != 0) { + int i; + ADD_STRING(target, ": "); + + optdata = isc_buffer_current(&optbuf); + for (i = 0; i < optlen; i++) { + const char *sep; + switch (optcode) { + case DNS_OPT_COOKIE: + sep = ""; + break; + default: + sep = " "; + break; + } + snprintf(buf, sizeof(buf), "%02x%s", + optdata[i], sep); + ADD_STRING(target, buf); + } + + isc_buffer_forward(&optbuf, optlen); + + if (optcode == DNS_OPT_COOKIE) { + /* + * Valid server cookie? + */ + if (msg->cc_ok && optlen >= 16) + ADD_STRING(target, " (good)"); + /* + * Server cookie is not valid but + * we had our cookie echoed back. + */ + if (msg->cc_ok && optlen < 16) + ADD_STRING(target, " (echoed)"); + /* + * We didn't get our cookie echoed + * back. + */ + if (msg->cc_bad) + ADD_STRING(target, " (bad)"); + ADD_STRING(target, "\n"); + continue; + } + + if (optcode == DNS_OPT_CLIENT_SUBNET) { + ADD_STRING(target, "\n"); + continue; + } + + /* + * For non-COOKIE options, add a printable + * version + */ + ADD_STRING(target, "(\""); + if (isc_buffer_availablelength(target) < optlen) + { + result = ISC_R_NOSPACE; + goto cleanup; + } + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i])) + isc_buffer_putmem(target, + &optdata[i], + 1); + else + isc_buffer_putstr(target, "."); + } + ADD_STRING(target, "\")"); + } + ADD_STRING(target, "\n"); + } + result = ISC_R_SUCCESS; + goto cleanup; + case DNS_PSEUDOSECTION_TSIG: + ps = dns_message_gettsig(msg, &name); + if (ps == NULL) { + result = ISC_R_SUCCESS; + goto cleanup; + } + INDENT(style); + ADD_STRING(target, "TSIG_PSEUDOSECTION:\n"); + result = dns_master_rdatasettotext(name, ps, style, target); + ADD_STRING(target, "\n"); + goto cleanup; + case DNS_PSEUDOSECTION_SIG0: + ps = dns_message_getsig0(msg, &name); + if (ps == NULL) { + result = ISC_R_SUCCESS; + goto cleanup; + } + INDENT(style); + 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"); + goto cleanup; + } + + result = ISC_R_UNEXPECTED; + + cleanup: + dns_master_indent = saveindent; + 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")]; + uint32_t mbz; + dns_rdata_t rdata; + isc_buffer_t optbuf; + uint16_t optcode, optlen; + unsigned char *optdata; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_PSEUDOSECTION(section)); + + if ((dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) != 0) + return (dns_message_pseudosectiontoyaml(msg, section, style, + flags, target)); + switch (section) { + case DNS_PSEUDOSECTION_OPT: + ps = dns_message_getopt(msg); + if (ps == NULL) + return (ISC_R_SUCCESS); + if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) { + INDENT(style); + ADD_STRING(target, ";; OPT PSEUDOSECTION:\n"); + } + + INDENT(style); + ADD_STRING(target, "; EDNS: version: "); + snprintf(buf, sizeof(buf), "%u", + (unsigned int)((ps->ttl & 0x00ff0000) >> 16)); + ADD_STRING(target, buf); + ADD_STRING(target, ", flags:"); + if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) + ADD_STRING(target, " do"); + mbz = ps->ttl & 0xffff; + mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */ + if (mbz != 0) { + ADD_STRING(target, "; MBZ: "); + snprintf(buf, sizeof(buf), "0x%.4x", mbz); + ADD_STRING(target, buf); + ADD_STRING(target, ", udp: "); + } else + ADD_STRING(target, "; udp: "); + snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass); + ADD_STRING(target, buf); + + result = dns_rdataset_first(ps); + if (result != ISC_R_SUCCESS) + return (ISC_R_SUCCESS); + + /* + * Print EDNS info, if any. + * + * WARNING: The option contents may be malformed as + * dig +ednsopt=value:<content> does not validity + * checking. + */ + dns_rdata_init(&rdata); + dns_rdataset_current(ps, &rdata); + + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) != 0) { + INSIST(isc_buffer_remaininglength(&optbuf) >= 4U); + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(isc_buffer_remaininglength(&optbuf) >= optlen); + + INDENT(style); + + if (optcode == DNS_OPT_NSID) { + ADD_STRING(target, "; NSID"); + } else if (optcode == DNS_OPT_COOKIE) { + ADD_STRING(target, "; COOKIE"); + } else if (optcode == DNS_OPT_CLIENT_SUBNET) { + isc_buffer_t ecsbuf; + + ADD_STRING(target, "; CLIENT-SUBNET"); + isc_buffer_init(&ecsbuf, + isc_buffer_current(&optbuf), + optlen); + isc_buffer_add(&ecsbuf, optlen); + result = render_ecs(&ecsbuf, target); + if (result == ISC_R_NOSPACE) + return (result); + if (result == ISC_R_SUCCESS) { + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_EXPIRE) { + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + ADD_STRING(target, "; EXPIRE: "); + snprintf(buf, sizeof(buf), "%u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, + true, + target); + if (result != ISC_R_SUCCESS) + return (result); + ADD_STRING(target, ")\n"); + continue; + } + ADD_STRING(target, "; EXPIRE"); + } else if (optcode == DNS_OPT_PAD) { + ADD_STRING(target, "; PAD"); + } else if (optcode == DNS_OPT_KEY_TAG) { + ADD_STRING(target, "; KEY-TAG"); + if (optlen > 0U && (optlen % 2U) == 0U) { + const char *sep = ": "; + uint16_t id; + while (optlen > 0U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), "%s%u", + sep, id); + ADD_STRING(target, buf); + sep = ", "; + optlen -= 2; + } + ADD_STRING(target, "\n"); + continue; + } + } else { + ADD_STRING(target, "; OPT="); + snprintf(buf, sizeof(buf), "%u", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + ADD_STRING(target, ": "); + + optdata = isc_buffer_current(&optbuf); + for (i = 0; i < optlen; i++) { + const char *sep; + switch (optcode) { + case DNS_OPT_COOKIE: + sep = ""; + break; + default: + sep = " "; + break; + } + snprintf(buf, sizeof(buf), "%02x%s", + optdata[i], sep); + ADD_STRING(target, buf); + } + + isc_buffer_forward(&optbuf, optlen); + + if (optcode == DNS_OPT_COOKIE) { + /* + * Valid server cookie? + */ + if (msg->cc_ok && optlen >= 16) + ADD_STRING(target, " (good)"); + /* + * Server cookie is not valid but + * we had our cookie echoed back. + */ + if (msg->cc_ok && optlen < 16) + ADD_STRING(target, " (echoed)"); + /* + * We didn't get our cookie echoed + * back. + */ + if (msg->cc_bad) + ADD_STRING(target, " (bad)"); + ADD_STRING(target, "\n"); + continue; + } + + if (optcode == DNS_OPT_CLIENT_SUBNET) { + ADD_STRING(target, "\n"); + continue; + } + + /* + * For non-COOKIE options, add a printable + * version + */ + ADD_STRING(target, "(\""); + if (isc_buffer_availablelength(target) < optlen) + return (ISC_R_NOSPACE); + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i])) + isc_buffer_putmem(target, + &optdata[i], + 1); + else + isc_buffer_putstr(target, "."); + } + ADD_STRING(target, "\")"); + } + ADD_STRING(target, "\n"); + } + return (ISC_R_SUCCESS); + case DNS_PSEUDOSECTION_TSIG: + ps = dns_message_gettsig(msg, &name); + if (ps == NULL) + return (ISC_R_SUCCESS); + INDENT(style); + 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); + INDENT(style); + 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); + } + result = ISC_R_UNEXPECTED; + cleanup: + return (result); +} + +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) && + (dns_master_styleflags(style) & DNS_STYLEFLAG_YAML)) + { + INDENT(style); + ADD_STRING(target, "opcode: "); + ADD_STRING(target, opcodetext[msg->opcode]); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "status: "); + result = dns_rcode_totext(msg->rcode, target); + if (result != ISC_R_SUCCESS) + return (result); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "id: "); + snprintf(buf, sizeof(buf), "%6u", msg->id); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "flags:"); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) + ADD_STRING(target, " qr"); + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) + ADD_STRING(target, " aa"); + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) + ADD_STRING(target, " tc"); + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) + ADD_STRING(target, " rd"); + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) + ADD_STRING(target, " ra"); + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) + ADD_STRING(target, " ad"); + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) + ADD_STRING(target, " cd"); + ADD_STRING(target, "\n"); + /* + * The final unnamed flag must be zero. + */ + if ((msg->flags & 0x0040U) != 0) { + INDENT(style); + ADD_STRING(target, "MBZ: 0x4"); + ADD_STRING(target, "\n"); + } + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "QUESTION: "); + } else { + ADD_STRING(target, "ZONE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_QUESTION]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "ANSWER: "); + } else { + INDENT(style); + ADD_STRING(target, "PREREQ: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ANSWER]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "AUTHORITY: "); + } else { + INDENT(style); + ADD_STRING(target, "UPDATE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_AUTHORITY]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "ADDITIONAL: "); + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ADDITIONAL]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } else if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) { + INDENT(style); + ADD_STRING(target, ";; ->>HEADER<<- opcode: "); + ADD_STRING(target, opcodetext[msg->opcode]); + ADD_STRING(target, ", status: "); + result = dns_rcode_totext(msg->rcode, target); + if (result != ISC_R_SUCCESS) + return (result); + ADD_STRING(target, ", id: "); + snprintf(buf, sizeof(buf), "%6u", msg->id); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, ";; flags:"); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) + ADD_STRING(target, " qr"); + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) + ADD_STRING(target, " aa"); + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) + ADD_STRING(target, " tc"); + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) + ADD_STRING(target, " rd"); + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) + ADD_STRING(target, " ra"); + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) + ADD_STRING(target, " ad"); + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) + ADD_STRING(target, " cd"); + /* + * The final unnamed flag must be zero. + */ + if ((msg->flags & 0x0040U) != 0) { + INDENT(style); + ADD_STRING(target, "; MBZ: 0x4"); + } + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "; QUESTION: "); + } else { + INDENT(style); + 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); + + cleanup: + return (result); +} + +isc_region_t * +dns_message_getrawmessage(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + return (&msg->saved); +} + +void +dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order, + const void *order_arg) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->order = order; + msg->order_arg = order_arg; +} + +void +dns_message_settimeadjust(dns_message_t *msg, int timeadjust) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->timeadjust = timeadjust; +} + +int +dns_message_gettimeadjust(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + return (msg->timeadjust); +} + +isc_result_t +dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) { + + REQUIRE(opcode < 16); + + if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode])) + return (ISC_R_NOSPACE); + isc_buffer_putstr(target, opcodetext[opcode]); + return (ISC_R_SUCCESS); +} + +void +dns_message_logpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx) +{ + logfmtpacket(message, description, NULL, category, module, + &dns_master_style_debug, level, mctx); +} + +void +dns_message_logpacket2(dns_message_t *message, + const char *description, isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx) +{ + REQUIRE(address != NULL); + + logfmtpacket(message, description, address, category, module, + &dns_master_style_debug, level, mctx); +} + +void +dns_message_logfmtpacket(dns_message_t *message, const char *description, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx) +{ + logfmtpacket(message, description, NULL, category, module, style, + level, mctx); +} + +void +dns_message_logfmtpacket2(dns_message_t *message, + const char *description, isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx) +{ + REQUIRE(address != NULL); + + logfmtpacket(message, description, address, category, module, style, + level, mctx); +} + +static void +logfmtpacket(dns_message_t *message, const char *description, + isc_sockaddr_t *address, isc_logcategory_t *category, + isc_logmodule_t *module, const dns_master_style_t *style, + int level, isc_mem_t *mctx) +{ + char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + const char *newline = "\n"; + const char *space = " "; + isc_buffer_t buffer; + char *buf = NULL; + int len = 1024; + isc_result_t result; + + if (! isc_log_wouldlog(dns_lctx, level)) + return; + + /* + * Note that these are multiline debug messages. We want a newline + * to appear in the log after each message. + */ + + if (address != NULL) + isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); + else + newline = space = ""; + + do { + buf = isc_mem_get(mctx, len); + if (buf == NULL) + break; + isc_buffer_init(&buffer, buf, len); + result = dns_message_totext(message, style, 0, &buffer); + if (result == ISC_R_NOSPACE) { + isc_mem_put(mctx, buf, len); + len += 1024; + } else if (result == ISC_R_SUCCESS) + isc_log_write(dns_lctx, category, module, level, + "%s%s%s%s%.*s", description, space, + addrbuf, newline, + (int)isc_buffer_usedlength(&buffer), + buf); + } while (result == ISC_R_NOSPACE); + + if (buf != NULL) + isc_mem_put(mctx, buf, len); +} + +isc_result_t +dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp, + unsigned int version, uint16_t udpsize, + unsigned int flags, dns_ednsopt_t *ednsopts, size_t count) +{ + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdata_t *rdata = NULL; + isc_result_t result; + unsigned int len = 0, i; + + REQUIRE(DNS_MESSAGE_VALID(message)); + REQUIRE(rdatasetp != NULL && *rdatasetp == NULL); + + result = dns_message_gettemprdatalist(message, &rdatalist); + if (result != ISC_R_SUCCESS) + return (result); + result = dns_message_gettemprdata(message, &rdata); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_message_gettemprdataset(message, &rdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + rdatalist->type = dns_rdatatype_opt; + + /* + * Set Maximum UDP buffer size. + */ + rdatalist->rdclass = udpsize; + + /* + * Set EXTENDED-RCODE and Z to 0. + */ + rdatalist->ttl = (version << 16); + rdatalist->ttl |= (flags & 0xffff); + + /* + * Set EDNS options if applicable + */ + if (count != 0U) { + isc_buffer_t *buf = NULL; + for (i = 0; i < count; i++) + len += ednsopts[i].length + 4; + + if (len > 0xffffU) { + result = ISC_R_NOSPACE; + goto cleanup; + } + + result = isc_buffer_allocate(message->mctx, &buf, len); + if (result != ISC_R_SUCCESS) + goto cleanup; + + for (i = 0; i < count; i++) { + isc_buffer_putuint16(buf, ednsopts[i].code); + isc_buffer_putuint16(buf, ednsopts[i].length); + if (ednsopts[i].length != 0) { + isc_buffer_putmem(buf, ednsopts[i].value, + ednsopts[i].length); + } + } + rdata->data = isc_buffer_base(buf); + rdata->length = len; + dns_message_takebuffer(message, &buf); + } else { + rdata->data = NULL; + rdata->length = 0; + } + + rdata->rdclass = rdatalist->rdclass; + rdata->type = rdatalist->type; + rdata->flags = 0; + + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + result = dns_rdatalist_tordataset(rdatalist, rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + *rdatasetp = rdataset; + return (ISC_R_SUCCESS); + + cleanup: + if (rdata != NULL) + dns_message_puttemprdata(message, &rdata); + if (rdataset != NULL) + dns_message_puttemprdataset(message, &rdataset); + if (rdatalist != NULL) + dns_message_puttemprdatalist(message, &rdatalist); + return (result); +} + +void +dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) { + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + REQUIRE(msg->state == DNS_SECTION_ANY); + REQUIRE(msg->rdclass_set == 0); + + msg->rdclass = rdclass; + msg->rdclass_set = 1; +} |