diff options
Diffstat (limited to 'lib/dns/message.c')
-rw-r--r-- | lib/dns/message.c | 4748 |
1 files changed, 4748 insertions, 0 deletions
diff --git a/lib/dns/message.c b/lib/dns/message.c new file mode 100644 index 0000000..09645c2 --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,4748 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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 https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#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/utf8.h> +#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 /* ifdef SKAN_MSG_DEBUG */ + +#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 1232 +#define NAME_FILLCOUNT 4 +#define NAME_FREEMAX 8 * NAME_FILLCOUNT +#define OFFSET_COUNT 4 +#define RDATA_COUNT 8 +#define RDATALIST_COUNT 8 +#define RDATASET_FILLCOUNT 4 +#define RDATASET_FREEMAX 8 * RDATASET_FILLCOUNT + +/*% + * Text representation of the different items, for message_totext + * functions. + */ +static const char *sectiontext[] = { "QUESTION", "ANSWER", "AUTHORITY", + "ADDITIONAL" }; + +static const char *updsectiontext[] = { "ZONE", "PREREQUISITE", "UPDATE", + "ADDITIONAL" }; + +static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS", + "RESERVED3", "NOTIFY", "UPDATE", + "RESERVED6", "RESERVED7", "RESERVED8", + "RESERVED9", "RESERVED10", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", + "RESERVED15" }; + +static const char *edetext[] = { "Other", + "Unsupported DNSKEY Algorithm", + "Unsupported DS Digest Type", + "Stale Answer", + "Forged Answer", + "DNSSEC Indeterminate", + "DNSSEC Bogus", + "Signature Expired", + "Signature Not Yet Valid", + "DNSKEY Missing", + "RRSIGs Missing", + "No Zone Key Bit Set", + "NSEC Missing", + "Cached Error", + "Not Ready", + "Blocked", + "Censored", + "Filtered", + "Prohibited", + "Stale NXDOMAIN Answer", + "Not Authoritative", + "Not Supported", + "No Reachable Authority", + "Network Error", + "Invalid Data" }; + +/*% + * "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 dns_msgblock_t * +msgblock_allocate(isc_mem_t *, unsigned int, unsigned int); + +#define msgblock_get(block, type) \ + ((type *)msgblock_internalget(block, sizeof(type))) + +static void * +msgblock_internalget(dns_msgblock_t *, unsigned int); + +static void +msgblock_reset(dns_msgblock_t *); + +static void +msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int); + +static void +logfmtpacket(dns_message_t *message, const char *description, + const 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 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); + + 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 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 void +msgblock_reset(dns_msgblock_t *block) { + block->remaining = block->count; +} + +/* + * Release memory associated with a message block. + */ +static 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 isc_result_t +newbuffer(dns_message_t *msg, unsigned int size) { + isc_buffer_t *dynbuf; + + dynbuf = NULL; + isc_buffer_allocate(msg->mctx, &dynbuf, size); + + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + return (ISC_R_SUCCESS); +} + +static isc_buffer_t * +currentbuffer(dns_message_t *msg) { + isc_buffer_t *dynbuf; + + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + + return (dynbuf); +} + +static void +releaserdata(dns_message_t *msg, dns_rdata_t *rdata) { + ISC_LIST_PREPEND(msg->freerdata, rdata, link); +} + +static 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 void +releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) { + ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link); +} + +static 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 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 void +msginitheader(dns_message_t *m) { + m->id = 0; + m->flags = 0; + m->rcode = 0; + m->opcode = 0; + m->rdclass = 0; +} + +static 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->padding = 0; + m->padding_off = 0; + m->buffer = NULL; +} + +static 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 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.env = NULL; + m->order_arg.acl = NULL; + m->order_arg.element = 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; + m->indent.string = "\t"; + m->indent.count = 0; +} + +static 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; + } + dns_message_puttempname(msg, &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); + } + } + dns_message_puttempname(msg, &msg->tsigname); + msg->tsig = 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) { + dns_message_puttempname(msg, &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); +} + +void +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) { + dns_message_t *m = NULL; + isc_buffer_t *dynbuf = NULL; + 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)); + *m = (dns_message_t){ .from_to_wire = intent }; + isc_mem_attach(mctx, &m->mctx); + msginit(m); + + for (i = 0; i < DNS_SECTION_MAX; i++) { + ISC_LIST_INIT(m->sections[i]); + } + + ISC_LIST_INIT(m->scratchpad); + ISC_LIST_INIT(m->cleanup); + 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); + + isc_mempool_create(m->mctx, sizeof(dns_fixedname_t), &m->namepool); + isc_mempool_setfillcount(m->namepool, NAME_FILLCOUNT); + isc_mempool_setfreemax(m->namepool, NAME_FREEMAX); + isc_mempool_setname(m->namepool, "msg:names"); + + isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), &m->rdspool); + isc_mempool_setfillcount(m->rdspool, RDATASET_FILLCOUNT); + isc_mempool_setfreemax(m->rdspool, RDATASET_FREEMAX); + isc_mempool_setname(m->rdspool, "msg:rdataset"); + + isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE); + ISC_LIST_APPEND(m->scratchpad, dynbuf, link); + + isc_refcount_init(&m->refcount, 1); + m->magic = DNS_MESSAGE_MAGIC; + + *msgp = m; +} + +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; +} + +static void +dns__message_destroy(dns_message_t *msg) { + REQUIRE(msg != NULL); + REQUIRE(DNS_MESSAGE_VALID(msg)); + + msgreset(msg, true); + isc_mempool_destroy(&msg->namepool); + isc_mempool_destroy(&msg->rdspool); + isc_refcount_destroy(&msg->refcount); + msg->magic = 0; + isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t)); +} + +void +dns_message_attach(dns_message_t *source, dns_message_t **target) { + REQUIRE(DNS_MESSAGE_VALID(source)); + + isc_refcount_increment(&source->refcount); + *target = source; +} + +void +dns_message_detach(dns_message_t **messagep) { + REQUIRE(messagep != NULL && DNS_MESSAGE_VALID(*messagep)); + dns_message_t *msg = *messagep; + *messagep = NULL; + + if (isc_refcount_decrement(&msg->refcount) == 1) { + dns__message_destroy(msg); + } +} + +static isc_result_t +findname(dns_name_t **foundname, const 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(const 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(const 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, 0, 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); + } + } + + UNREACHABLE(); +} + +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 = NULL; + dns_name_t *name2 = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section = &msg->sections[DNS_SECTION_QUESTION]; + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool seen_problem = false; + bool free_name = false; + + for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) { + name = NULL; + result = dns_message_gettempname(msg, &name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + name->offsets = (unsigned char *)newoffsets(msg); + free_name = true; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the allocated + * name since we no longer need it, and set our name pointer + * to point to the name we found. + */ + result = findname(&name2, name, 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 { + dns_message_puttempname(msg, &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 (free_name) { + dns_message_puttempname(msg, &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_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + isc_result_t result; + dns_rdatatype_t rdtype, covers; + dns_rdataclass_t rdclass; + dns_rdata_t *rdata = NULL; + dns_ttl_t ttl; + dns_namelist_t *section = &msg->sections[sectionid]; + bool free_name = false, free_rdataset = false, seen_problem = false; + bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0); + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool isedns, issigzero, istsig; + + 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; + isedns = false; + issigzero = false; + istsig = false; + + name = NULL; + result = dns_message_gettempname(msg, &name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + name->offsets = (unsigned char *)newoffsets(msg); + free_name = true; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Get type, class, ttl, and rdatalen. Verify that at least + * rdatalen bytes remain. (Some of this is deferred to + * later.) + */ + isc_buffer_remainingregion(source, &r); + if (r.length < 2 + 2 + 4 + 2) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If there was no question section, we may not yet have + * established a class. Do so now. + */ + if (msg->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); + } else { + skip_name_search = true; + skip_type_search = true; + istsig = 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); + } else { + skip_name_search = true; + skip_type_search = true; + isedns = 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; + 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); + } else { + 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 (!isedns && !istsig && !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) { + dns_message_puttempname(msg, &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 (!isedns && !istsig && !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 (isedns) { + 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; + dns_message_puttempname(msg, &name); + free_name = false; + } else if (issigzero) { + msg->sig0 = rdataset; + msg->sig0name = name; + msg->sigstart = recstart; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } else if (istsig) { + msg->tsig = rdataset; + msg->tsigname = name; + msg->sigstart = recstart; + /* + * 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) { + dns_message_puttempname(msg, &name); + } + if (free_rdataset) { + isc_mempool_put(msg->rdspool, rdataset); + } + free_name = free_rdataset = false; + } + INSIST(!free_name); + INSIST(!free_rdataset); + } + + /* + * 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) { + dns_message_puttempname(msg, &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) != 0); + + origsource = *source; + + msg->header_ok = 0; + msg->question_ok = 0; + + 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); + memmove(msg->saved.base, isc_buffer_base(&origsource), + msg->saved.length); + msg->free_saved = 1; + } + + 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 (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + return (DNS_R_RECOVERABLE); + } + if (seen_problem) { + 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 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); +} + +static isc_result_t +renderset(dns_rdataset_t *rdataset, const 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; + } + + 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. Cast msg->rcode to dns_ttl_t + * so that we do a unsigned shift. + */ + msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK; + msg->opt->ttl |= (((dns_ttl_t)(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); + } + } + + /* + * Deal with EDNS padding. + * + * padding_off is the length of the OPT with the 0-length PAD + * at the end. + */ + if (msg->padding_off > 0) { + unsigned char *cp = isc_buffer_used(msg->buffer); + unsigned int used, remaining; + uint16_t len, padsize = 0; + + /* Check PAD */ + if ((cp[-4] != 0) || (cp[-3] != DNS_OPT_PAD) || (cp[-2] != 0) || + (cp[-1] != 0)) + { + return (ISC_R_UNEXPECTED); + } + + /* + * Zero-fill the PAD to the computed size; + * patch PAD length and OPT rdlength + */ + + /* Aligned used length + reserved to padding block */ + used = isc_buffer_usedlength(msg->buffer); + if (msg->padding != 0) { + padsize = ((uint16_t)used + msg->reserved) % + msg->padding; + } + if (padsize != 0) { + padsize = msg->padding - padsize; + } + /* Stay below the available length */ + remaining = isc_buffer_availablelength(msg->buffer); + if (padsize > remaining) { + padsize = remaining; + } + + isc_buffer_add(msg->buffer, padsize); + memset(cp, 0, padsize); + cp[-2] = (unsigned char)((padsize & 0xff00U) >> 8); + cp[-1] = (unsigned char)(padsize & 0x00ffU); + cp -= msg->padding_off; + len = ((uint16_t)(cp[-2])) << 8; + len |= ((uint16_t)(cp[-1])); + len += padsize; + cp[-2] = (unsigned char)((len & 0xff00U) >> 8); + cp[-1] = (unsigned char)(len & 0x00ffU); + } + + /* + * 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, + const 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) { + dns_fixedname_t *fn = NULL; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + fn = isc_mempool_get(msg->namepool); + if (fn == NULL) { + return (ISC_R_NOMEMORY); + } + *item = dns_fixedname_initname(fn); + + 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 **itemp) { + dns_name_t *item = NULL; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(itemp != NULL && *itemp != NULL); + + item = *itemp; + *itemp = NULL; + + REQUIRE(!ISC_LINK_LINKED(item, link)); + REQUIRE(ISC_LIST_HEAD(item->list) == NULL); + + /* + * we need to check this in case dns_name_dup() was used. + */ + if (dns_name_dynamic(item)) { + dns_name_free(item, msg->mctx); + } + + /* + * 'name' is the first field in dns_fixedname_t, so putting + * back the address of name is the same as putting back + * the fixedname. + */ + isc_mempool_put(msg->namepool, item); +} + +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, const 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)); + + 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); + isc_buffer_allocate(msg->mctx, &buf, r.length); + 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); + + isc_buffer_allocate(mctx, querytsig, r.length); + isc_buffer_putmem(*querytsig, r.base, r.length); + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getsig0(dns_message_t *msg, const 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; + isc_buffer_allocate(msg->mctx, &dynbuf, 512); + 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 { + const 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 /* ifdef SKAN_MSG_DEBUG */ + +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 /* ifdef SKAN_MSG_DEBUG */ + 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, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&keyset); + if (view == NULL) { + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } + 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 < msg->indent.count; __i++) { \ + ADD_STRING(target, msg->indent.string); \ + } \ + } 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; + size_t saved_count; + dns_masterstyle_flags_t sflags; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_SECTION(section)); + + saved_count = msg->indent.count; + + if (ISC_LIST_EMPTY(msg->sections[section])) { + goto cleanup; + } + + sflags = dns_master_styleflags(style); + + 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) { + msg->indent.count++; + } + 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, &msg->indent, + 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) { + msg->indent.count--; + } + 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: + msg->indent.count = saved_count; + 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 +render_llq(isc_buffer_t *optbuf, isc_buffer_t *target) { + char buf[sizeof("18446744073709551615")]; /* 2^64-1 */ + isc_result_t result = ISC_R_SUCCESS; + uint32_t u; + uint64_t q; + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, " Version: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, ", Opcode: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, ", Error: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + q = isc_buffer_getuint32(optbuf); + q <<= 32; + q |= isc_buffer_getuint32(optbuf); + ADD_STRING(target, ", Identifier: "); + snprintf(buf, sizeof(buf), "%" PRIu64, q); + ADD_STRING(target, buf); + + u = isc_buffer_getuint32(optbuf); + ADD_STRING(target, ", Lifetime: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); +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; + const 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; + size_t saved_count; + unsigned char *optdata; + unsigned int indent; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_PSEUDOSECTION(section)); + + saved_count = msg->indent.count; + + switch (section) { + case DNS_PSEUDOSECTION_OPT: + ps = dns_message_getopt(msg); + if (ps == NULL) { + goto cleanup; + } + + INDENT(style); + ADD_STRING(target, "OPT_PSEUDOSECTION:\n"); + msg->indent.count++; + + INDENT(style); + ADD_STRING(target, "EDNS:\n"); + indent = ++msg->indent.count; + + 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 perform 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) { + bool extra_text = false; + msg->indent.count = indent; + 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_LLQ) { + INDENT(style); + ADD_STRING(target, "LLQ:"); + if (optlen == 18U) { + result = render_llq(&optbuf, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ADD_STRING(target, "\n"); + continue; + } + } else 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) { + INDENT(style); + ADD_STRING(target, "EXPIRE:"); + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ADD_STRING(target, ")\n"); + continue; + } + } else if (optcode == DNS_OPT_TCP_KEEPALIVE) { + if (optlen == 2) { + unsigned int dsecs; + dsecs = isc_buffer_getuint16(&optbuf); + INDENT(style); + ADD_STRING(target, "TCP-KEEPALIVE: "); + snprintf(buf, sizeof(buf), "%u.%u", + dsecs / 10U, dsecs % 10U); + ADD_STRING(target, buf); + ADD_STRING(target, " secs\n"); + continue; + } + INDENT(style); + ADD_STRING(target, "TCP-KEEPALIVE:"); + } 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 if (optcode == DNS_OPT_EDE) { + INDENT(style); + ADD_STRING(target, "EDE:"); + if (optlen >= 2U) { + uint16_t ede; + ADD_STRING(target, "\n"); + msg->indent.count++; + INDENT(style); + ADD_STRING(target, "INFO-CODE:"); + ede = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u", ede); + ADD_STRING(target, buf); + if (ede < ARRAY_SIZE(edetext)) { + ADD_STRING(target, " ("); + ADD_STRING(target, + edetext[ede]); + ADD_STRING(target, ")"); + } + ADD_STRING(target, "\n"); + optlen -= 2; + if (optlen != 0) { + INDENT(style); + ADD_STRING(target, + "EXTRA-TEXT:"); + extra_text = true; + } + } + } else if (optcode == DNS_OPT_CLIENT_TAG) { + uint16_t id; + INDENT(style); + ADD_STRING(target, "CLIENT-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else if (optcode == DNS_OPT_SERVER_TAG) { + uint16_t id; + INDENT(style); + ADD_STRING(target, "SERVER-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else { + INDENT(style); + ADD_STRING(target, "OPT="); + snprintf(buf, sizeof(buf), "%u:", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + bool utf8ok = false; + + ADD_STRING(target, " "); + + optdata = isc_buffer_current(&optbuf); + if (extra_text) { + utf8ok = isc_utf8_valid(optdata, + optlen); + } + if (!utf8ok) { + 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 + */ + if (!extra_text) { + ADD_STRING(target, "(\""); + } else { + 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]) || + (utf8ok && optdata[i] > 127)) + { + isc_buffer_putmem( + target, &optdata[i], 1); + } else { + isc_buffer_putstr(target, "."); + } + } + if (!extra_text) { + ADD_STRING(target, "\")"); + } else { + ADD_STRING(target, "\""); + } + } + ADD_STRING(target, "\n"); + } + msg->indent.count = indent; + 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, + &msg->indent, 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, + &msg->indent, target); + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) + { + ADD_STRING(target, "\n"); + } + goto cleanup; + } + + result = ISC_R_UNEXPECTED; + +cleanup: + msg->indent.count = saved_count; + 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; + const dns_name_t *name = NULL; + isc_result_t result; + char buf[sizeof(" (65000 bytes)")]; + 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 no 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_LLQ) { + ADD_STRING(target, "; LLQ:"); + if (optlen == 18U) { + result = render_llq(&optbuf, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, "\n"); + continue; + } + } else 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) { + ADD_STRING(target, "; EXPIRE:"); + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, ")\n"); + continue; + } + } else if (optcode == DNS_OPT_TCP_KEEPALIVE) { + ADD_STRING(target, "; TCP KEEPALIVE:"); + if (optlen == 2) { + unsigned int dsecs; + dsecs = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u.%u", + dsecs / 10U, dsecs % 10U); + ADD_STRING(target, buf); + ADD_STRING(target, " secs\n"); + continue; + } + } else if (optcode == DNS_OPT_PAD) { + ADD_STRING(target, "; PAD:"); + if (optlen > 0U) { + snprintf(buf, sizeof(buf), + " (%u bytes)", optlen); + ADD_STRING(target, buf); + isc_buffer_forward(&optbuf, optlen); + } + ADD_STRING(target, "\n"); + continue; + } 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 if (optcode == DNS_OPT_EDE) { + ADD_STRING(target, "; EDE:"); + if (optlen >= 2U) { + uint16_t ede; + ede = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u", ede); + ADD_STRING(target, buf); + if (ede < ARRAY_SIZE(edetext)) { + ADD_STRING(target, " ("); + ADD_STRING(target, + edetext[ede]); + ADD_STRING(target, ")"); + } + optlen -= 2; + if (optlen != 0) { + ADD_STRING(target, ":"); + } + } else if (optlen == 1U) { + /* Malformed */ + optdata = isc_buffer_current(&optbuf); + snprintf(buf, sizeof(buf), + " %02x (\"%c\")\n", optdata[0], + isprint(optdata[0]) + ? optdata[0] + : '.'); + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, buf); + continue; + } + } else if (optcode == DNS_OPT_CLIENT_TAG) { + uint16_t id; + ADD_STRING(target, "; CLIENT-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else if (optcode == DNS_OPT_SERVER_TAG) { + uint16_t id; + ADD_STRING(target, "; SERVER-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else { + ADD_STRING(target, "; OPT="); + snprintf(buf, sizeof(buf), "%u:", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + bool utf8ok = false; + + ADD_STRING(target, " "); + + optdata = isc_buffer_current(&optbuf); + if (optcode == DNS_OPT_EDE) { + utf8ok = isc_utf8_valid(optdata, + optlen); + } + if (!utf8ok) { + 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. + */ + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "(\""); + } else { + ADD_STRING(target, "("); + } + if (isc_buffer_availablelength(target) < optlen) + { + return (ISC_R_NOSPACE); + } + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i]) || + (utf8ok && optdata[i] > 127)) + { + isc_buffer_putmem( + target, &optdata[i], 1); + } else { + isc_buffer_putstr(target, "."); + } + } + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "\")"); + } else { + 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, + &msg->indent, 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, + &msg->indent, 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_headertotext(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) { + return (ISC_R_SUCCESS); + } + + if (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), "%u", 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 { + INDENT(style); + 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 { + 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"); + } + +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) { + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + + result = dns_message_headertotext(msg, style, flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + 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); + 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, + dns_aclenv_t *env, const dns_acl_t *acl, + const dns_aclelement_t *elem) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE((order == NULL) == (env == NULL)); + REQUIRE(env == NULL || (acl != NULL || elem != NULL)); + + msg->order = order; + msg->order_arg.env = env; + msg->order_arg.acl = acl; + msg->order_arg.element = elem; +} + +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, + const 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, + const 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, + const 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); + 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; + bool seenpad = false; + for (i = 0; i < count; i++) { + len += ednsopts[i].length + 4; + } + + if (len > 0xffffU) { + result = ISC_R_NOSPACE; + goto cleanup; + } + + isc_buffer_allocate(message->mctx, &buf, len); + + for (i = 0; i < count; i++) { + if (ednsopts[i].code == DNS_OPT_PAD && + ednsopts[i].length == 0U && !seenpad) + { + seenpad = true; + continue; + } + 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); + } + } + + /* Padding must be the final option */ + if (seenpad) { + isc_buffer_putuint16(buf, DNS_OPT_PAD); + isc_buffer_putuint16(buf, 0); + } + rdata->data = isc_buffer_base(buf); + rdata->length = len; + dns_message_takebuffer(message, &buf); + if (seenpad) { + message->padding_off = len; + } + } 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; +} + +void +dns_message_setpadding(dns_message_t *msg, uint16_t padding) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + + /* Avoid silly large padding */ + if (padding > 512) { + padding = 512; + } + msg->padding = padding; +} + +void +dns_message_clonebuffer(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->free_saved == 0 && msg->saved.base != NULL) { + msg->saved.base = + memmove(isc_mem_get(msg->mctx, msg->saved.length), + msg->saved.base, msg->saved.length); + msg->free_saved = 1; + } + if (msg->free_query == 0 && msg->query.base != NULL) { + msg->query.base = + memmove(isc_mem_get(msg->mctx, msg->query.length), + msg->query.base, msg->query.length); + msg->free_query = 1; + } +} |