/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file */

/***
 *** Imports
 ***/

#include <config.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>

#include <isc/buffer.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/string.h>		/* Required for HP/UX (and others?) */
#include <isc/util.h>

#include <dns/dnssec.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dns/masterdump.h>
#include <dns/message.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#include <dns/result.h>
#include <dns/tsig.h>
#include <dns/ttl.h>
#include <dns/view.h>

#ifdef SKAN_MSG_DEBUG
static void
hexdump(const char *msg, const char *msg2, void *base, size_t len) {
	unsigned char *p;
	unsigned int cnt;

	p = base;
	cnt = 0;

	printf("*** %s [%s] (%u bytes @ %p)\n", msg, msg2, (unsigned)len, base);

	while (cnt < len) {
		if (cnt % 16 == 0)
			printf("%p: ", p);
		else if (cnt % 8 == 0)
			printf(" |");
		printf(" %02x %c", *p, (isprint(*p) ? *p : ' '));
		p++;
		cnt++;

		if (cnt % 16 == 0)
			printf("\n");
	}

	if (cnt % 16 != 0)
		printf("\n");
}
#endif

#define DNS_MESSAGE_OPCODE_MASK		0x7800U
#define DNS_MESSAGE_OPCODE_SHIFT	11
#define DNS_MESSAGE_RCODE_MASK		0x000fU
#define DNS_MESSAGE_FLAG_MASK		0x8ff0U
#define DNS_MESSAGE_EDNSRCODE_MASK	0xff000000U
#define DNS_MESSAGE_EDNSRCODE_SHIFT	24
#define DNS_MESSAGE_EDNSVERSION_MASK	0x00ff0000U
#define DNS_MESSAGE_EDNSVERSION_SHIFT	16

#define VALID_NAMED_SECTION(s)  (((s) > DNS_SECTION_ANY) \
				 && ((s) < DNS_SECTION_MAX))
#define VALID_SECTION(s)	(((s) >= DNS_SECTION_ANY) \
				 && ((s) < DNS_SECTION_MAX))
#define ADD_STRING(b, s)	{if (strlen(s) >= \
				   isc_buffer_availablelength(b)) { \
				       result = ISC_R_NOSPACE; \
				       goto cleanup; \
				 } else \
				       isc_buffer_putstr(b, s);}
#define VALID_PSEUDOSECTION(s)	(((s) >= DNS_PSEUDOSECTION_ANY) \
				 && ((s) < DNS_PSEUDOSECTION_MAX))

#define OPTOUT(x) (((x)->attributes & DNS_RDATASETATTR_OPTOUT) != 0)

/*%
 * This is the size of each individual scratchpad buffer, and the numbers
 * of various block allocations used within the server.
 * XXXMLG These should come from a config setting.
 */
#define SCRATCHPAD_SIZE		512
#define NAME_COUNT		 64
#define OFFSET_COUNT		  4
#define RDATA_COUNT		  8
#define RDATALIST_COUNT		  8
#define RDATASET_COUNT	         64

/*%
 * Text representation of the different items, for message_totext
 * functions.
 */
static const char *sectiontext[] = {
	"QUESTION",
	"ANSWER",
	"AUTHORITY",
	"ADDITIONAL"
};

static const char *updsectiontext[] = {
	"ZONE",
	"PREREQUISITE",
	"UPDATE",
	"ADDITIONAL"
};

static const char *opcodetext[] = {
	"QUERY",
	"IQUERY",
	"STATUS",
	"RESERVED3",
	"NOTIFY",
	"UPDATE",
	"RESERVED6",
	"RESERVED7",
	"RESERVED8",
	"RESERVED9",
	"RESERVED10",
	"RESERVED11",
	"RESERVED12",
	"RESERVED13",
	"RESERVED14",
	"RESERVED15"
};

/*%
 * "helper" type, which consists of a block of some type, and is linkable.
 * For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer
 * size, or the allocated elements will not be aligned correctly.
 */
struct dns_msgblock {
	unsigned int			count;
	unsigned int			remaining;
	ISC_LINK(dns_msgblock_t)	link;
}; /* dynamically sized */

static inline dns_msgblock_t *
msgblock_allocate(isc_mem_t *, unsigned int, unsigned int);

#define msgblock_get(block, type) \
	((type *)msgblock_internalget(block, sizeof(type)))

static inline void *
msgblock_internalget(dns_msgblock_t *, unsigned int);

static inline void
msgblock_reset(dns_msgblock_t *);

static inline void
msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int);

static void
logfmtpacket(dns_message_t *message, const char *description,
	     isc_sockaddr_t *address, isc_logcategory_t *category,
	     isc_logmodule_t *module, const dns_master_style_t *style,
	     int level, isc_mem_t *mctx);

/*
 * Allocate a new dns_msgblock_t, and return a pointer to it.  If no memory
 * is free, return NULL.
 */
static inline dns_msgblock_t *
msgblock_allocate(isc_mem_t *mctx, unsigned int sizeof_type,
		  unsigned int count)
{
	dns_msgblock_t *block;
	unsigned int length;

	length = sizeof(dns_msgblock_t) + (sizeof_type * count);

	block = isc_mem_get(mctx, length);
	if (block == NULL)
		return (NULL);

	block->count = count;
	block->remaining = count;

	ISC_LINK_INIT(block, link);

	return (block);
}

/*
 * Return an element from the msgblock.  If no more are available, return
 * NULL.
 */
static inline void *
msgblock_internalget(dns_msgblock_t *block, unsigned int sizeof_type) {
	void *ptr;

	if (block == NULL || block->remaining == 0)
		return (NULL);

	block->remaining--;

	ptr = (((unsigned char *)block)
	       + sizeof(dns_msgblock_t)
	       + (sizeof_type * block->remaining));

	return (ptr);
}

static inline void
msgblock_reset(dns_msgblock_t *block) {
	block->remaining = block->count;
}

/*
 * Release memory associated with a message block.
 */
static inline void
msgblock_free(isc_mem_t *mctx, dns_msgblock_t *block, unsigned int sizeof_type)
{
	unsigned int length;

	length = sizeof(dns_msgblock_t) + (sizeof_type * block->count);

	isc_mem_put(mctx, block, length);
}

/*
 * Allocate a new dynamic buffer, and attach it to this message as the
 * "current" buffer.  (which is always the last on the list, for our
 * uses)
 */
static inline isc_result_t
newbuffer(dns_message_t *msg, unsigned int size) {
	isc_result_t result;
	isc_buffer_t *dynbuf;

	dynbuf = NULL;
	result = isc_buffer_allocate(msg->mctx, &dynbuf, size);
	if (result != ISC_R_SUCCESS)
		return (ISC_R_NOMEMORY);

	ISC_LIST_APPEND(msg->scratchpad, dynbuf, link);
	return (ISC_R_SUCCESS);
}

static inline isc_buffer_t *
currentbuffer(dns_message_t *msg) {
	isc_buffer_t *dynbuf;

	dynbuf = ISC_LIST_TAIL(msg->scratchpad);
	INSIST(dynbuf != NULL);

	return (dynbuf);
}

static inline void
releaserdata(dns_message_t *msg, dns_rdata_t *rdata) {
	ISC_LIST_PREPEND(msg->freerdata, rdata, link);
}

static inline dns_rdata_t *
newrdata(dns_message_t *msg) {
	dns_msgblock_t *msgblock;
	dns_rdata_t *rdata;

	rdata = ISC_LIST_HEAD(msg->freerdata);
	if (rdata != NULL) {
		ISC_LIST_UNLINK(msg->freerdata, rdata, link);
		return (rdata);
	}

	msgblock = ISC_LIST_TAIL(msg->rdatas);
	rdata = msgblock_get(msgblock, dns_rdata_t);
	if (rdata == NULL) {
		msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t),
					     RDATA_COUNT);
		if (msgblock == NULL)
			return (NULL);

		ISC_LIST_APPEND(msg->rdatas, msgblock, link);

		rdata = msgblock_get(msgblock, dns_rdata_t);
	}

	dns_rdata_init(rdata);
	return (rdata);
}

static inline void
releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) {
	ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link);
}

static inline dns_rdatalist_t *
newrdatalist(dns_message_t *msg) {
	dns_msgblock_t *msgblock;
	dns_rdatalist_t *rdatalist;

	rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
	if (rdatalist != NULL) {
		ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
		goto out;
	}

	msgblock = ISC_LIST_TAIL(msg->rdatalists);
	rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
	if (rdatalist == NULL) {
		msgblock = msgblock_allocate(msg->mctx,
					     sizeof(dns_rdatalist_t),
					     RDATALIST_COUNT);
		if (msgblock == NULL)
			return (NULL);

		ISC_LIST_APPEND(msg->rdatalists, msgblock, link);

		rdatalist = msgblock_get(msgblock, dns_rdatalist_t);
	}
 out:
	if (rdatalist != NULL)
		dns_rdatalist_init(rdatalist);

	return (rdatalist);
}

static inline dns_offsets_t *
newoffsets(dns_message_t *msg) {
	dns_msgblock_t *msgblock;
	dns_offsets_t *offsets;

	msgblock = ISC_LIST_TAIL(msg->offsets);
	offsets = msgblock_get(msgblock, dns_offsets_t);
	if (offsets == NULL) {
		msgblock = msgblock_allocate(msg->mctx,
					     sizeof(dns_offsets_t),
					     OFFSET_COUNT);
		if (msgblock == NULL)
			return (NULL);

		ISC_LIST_APPEND(msg->offsets, msgblock, link);

		offsets = msgblock_get(msgblock, dns_offsets_t);
	}

	return (offsets);
}

static inline void
msginitheader(dns_message_t *m) {
	m->id = 0;
	m->flags = 0;
	m->rcode = 0;
	m->opcode = 0;
	m->rdclass = 0;
}

static inline void
msginitprivate(dns_message_t *m) {
	unsigned int i;

	for (i = 0; i < DNS_SECTION_MAX; i++) {
		m->cursors[i] = NULL;
		m->counts[i] = 0;
	}
	m->opt = NULL;
	m->sig0 = NULL;
	m->sig0name = NULL;
	m->tsig = NULL;
	m->tsigname = NULL;
	m->state = DNS_SECTION_ANY;  /* indicate nothing parsed or rendered */
	m->opt_reserved = 0;
	m->sig_reserved = 0;
	m->reserved = 0;
	m->buffer = NULL;
}

static inline void
msginittsig(dns_message_t *m) {
	m->tsigstatus = dns_rcode_noerror;
	m->querytsigstatus = dns_rcode_noerror;
	m->tsigkey = NULL;
	m->tsigctx = NULL;
	m->sigstart = -1;
	m->sig0key = NULL;
	m->sig0status = dns_rcode_noerror;
	m->timeadjust = 0;
}

/*
 * Init elements to default state.  Used both when allocating a new element
 * and when resetting one.
 */
static inline void
msginit(dns_message_t *m) {
	msginitheader(m);
	msginitprivate(m);
	msginittsig(m);
	m->header_ok = 0;
	m->question_ok = 0;
	m->tcp_continuation = 0;
	m->verified_sig = 0;
	m->verify_attempted = 0;
	m->order = NULL;
	m->order_arg = NULL;
	m->query.base = NULL;
	m->query.length = 0;
	m->free_query = 0;
	m->saved.base = NULL;
	m->saved.length = 0;
	m->free_saved = 0;
	m->cc_ok = 0;
	m->cc_bad = 0;
	m->tkey = 0;
	m->rdclass_set = 0;
	m->querytsig = NULL;
}

static inline void
msgresetnames(dns_message_t *msg, unsigned int first_section) {
	unsigned int i;
	dns_name_t *name, *next_name;
	dns_rdataset_t *rds, *next_rds;

	/*
	 * Clean up name lists by calling the rdataset disassociate function.
	 */
	for (i = first_section; i < DNS_SECTION_MAX; i++) {
		name = ISC_LIST_HEAD(msg->sections[i]);
		while (name != NULL) {
			next_name = ISC_LIST_NEXT(name, link);
			ISC_LIST_UNLINK(msg->sections[i], name, link);

			rds = ISC_LIST_HEAD(name->list);
			while (rds != NULL) {
				next_rds = ISC_LIST_NEXT(rds, link);
				ISC_LIST_UNLINK(name->list, rds, link);

				INSIST(dns_rdataset_isassociated(rds));
				dns_rdataset_disassociate(rds);
				isc_mempool_put(msg->rdspool, rds);
				rds = next_rds;
			}
			if (dns_name_dynamic(name))
				dns_name_free(name, msg->mctx);
			isc_mempool_put(msg->namepool, name);
			name = next_name;
		}
	}
}

static void
msgresetopt(dns_message_t *msg)
{
	if (msg->opt != NULL) {
		if (msg->opt_reserved > 0) {
			dns_message_renderrelease(msg, msg->opt_reserved);
			msg->opt_reserved = 0;
		}
		INSIST(dns_rdataset_isassociated(msg->opt));
		dns_rdataset_disassociate(msg->opt);
		isc_mempool_put(msg->rdspool, msg->opt);
		msg->opt = NULL;
		msg->cc_ok = 0;
		msg->cc_bad = 0;
	}
}

static void
msgresetsigs(dns_message_t *msg, bool replying) {
	if (msg->sig_reserved > 0) {
		dns_message_renderrelease(msg, msg->sig_reserved);
		msg->sig_reserved = 0;
	}
	if (msg->tsig != NULL) {
		INSIST(dns_rdataset_isassociated(msg->tsig));
		INSIST(msg->namepool != NULL);
		if (replying) {
			INSIST(msg->querytsig == NULL);
			msg->querytsig = msg->tsig;
		} else {
			dns_rdataset_disassociate(msg->tsig);
			isc_mempool_put(msg->rdspool, msg->tsig);
			if (msg->querytsig != NULL) {
				dns_rdataset_disassociate(msg->querytsig);
				isc_mempool_put(msg->rdspool, msg->querytsig);
			}
		}
		if (dns_name_dynamic(msg->tsigname))
			dns_name_free(msg->tsigname, msg->mctx);
		isc_mempool_put(msg->namepool, msg->tsigname);
		msg->tsig = NULL;
		msg->tsigname = NULL;
	} else if (msg->querytsig != NULL && !replying) {
		dns_rdataset_disassociate(msg->querytsig);
		isc_mempool_put(msg->rdspool, msg->querytsig);
		msg->querytsig = NULL;
	}
	if (msg->sig0 != NULL) {
		INSIST(dns_rdataset_isassociated(msg->sig0));
		dns_rdataset_disassociate(msg->sig0);
		isc_mempool_put(msg->rdspool, msg->sig0);
		if (msg->sig0name != NULL) {
			if (dns_name_dynamic(msg->sig0name))
				dns_name_free(msg->sig0name, msg->mctx);
			isc_mempool_put(msg->namepool, msg->sig0name);
		}
		msg->sig0 = NULL;
		msg->sig0name = NULL;
	}
}

/*
 * Free all but one (or everything) for this message.  This is used by
 * both dns_message_reset() and dns_message_destroy().
 */
static void
msgreset(dns_message_t *msg, bool everything) {
	dns_msgblock_t *msgblock, *next_msgblock;
	isc_buffer_t *dynbuf, *next_dynbuf;
	dns_rdata_t *rdata;
	dns_rdatalist_t *rdatalist;

	msgresetnames(msg, 0);
	msgresetopt(msg);
	msgresetsigs(msg, false);

	/*
	 * Clean up linked lists.
	 */

	/*
	 * Run through the free lists, and just unlink anything found there.
	 * The memory isn't lost since these are part of message blocks we
	 * have allocated.
	 */
	rdata = ISC_LIST_HEAD(msg->freerdata);
	while (rdata != NULL) {
		ISC_LIST_UNLINK(msg->freerdata, rdata, link);
		rdata = ISC_LIST_HEAD(msg->freerdata);
	}
	rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
	while (rdatalist != NULL) {
		ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link);
		rdatalist = ISC_LIST_HEAD(msg->freerdatalist);
	}

	dynbuf = ISC_LIST_HEAD(msg->scratchpad);
	INSIST(dynbuf != NULL);
	if (!everything) {
		isc_buffer_clear(dynbuf);
		dynbuf = ISC_LIST_NEXT(dynbuf, link);
	}
	while (dynbuf != NULL) {
		next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
		ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link);
		isc_buffer_free(&dynbuf);
		dynbuf = next_dynbuf;
	}

	msgblock = ISC_LIST_HEAD(msg->rdatas);
	if (!everything && msgblock != NULL) {
		msgblock_reset(msgblock);
		msgblock = ISC_LIST_NEXT(msgblock, link);
	}
	while (msgblock != NULL) {
		next_msgblock = ISC_LIST_NEXT(msgblock, link);
		ISC_LIST_UNLINK(msg->rdatas, msgblock, link);
		msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t));
		msgblock = next_msgblock;
	}

	/*
	 * rdatalists could be empty.
	 */

	msgblock = ISC_LIST_HEAD(msg->rdatalists);
	if (!everything && msgblock != NULL) {
		msgblock_reset(msgblock);
		msgblock = ISC_LIST_NEXT(msgblock, link);
	}
	while (msgblock != NULL) {
		next_msgblock = ISC_LIST_NEXT(msgblock, link);
		ISC_LIST_UNLINK(msg->rdatalists, msgblock, link);
		msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t));
		msgblock = next_msgblock;
	}

	msgblock = ISC_LIST_HEAD(msg->offsets);
	if (!everything && msgblock != NULL) {
		msgblock_reset(msgblock);
		msgblock = ISC_LIST_NEXT(msgblock, link);
	}
	while (msgblock != NULL) {
		next_msgblock = ISC_LIST_NEXT(msgblock, link);
		ISC_LIST_UNLINK(msg->offsets, msgblock, link);
		msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t));
		msgblock = next_msgblock;
	}

	if (msg->tsigkey != NULL) {
		dns_tsigkey_detach(&msg->tsigkey);
		msg->tsigkey = NULL;
	}

	if (msg->tsigctx != NULL)
		dst_context_destroy(&msg->tsigctx);

	if (msg->query.base != NULL) {
		if (msg->free_query != 0)
			isc_mem_put(msg->mctx, msg->query.base,
				    msg->query.length);
		msg->query.base = NULL;
		msg->query.length = 0;
	}

	if (msg->saved.base != NULL) {
		if (msg->free_saved != 0)
			isc_mem_put(msg->mctx, msg->saved.base,
				    msg->saved.length);
		msg->saved.base = NULL;
		msg->saved.length = 0;
	}

	/*
	 * cleanup the buffer cleanup list
	 */
	dynbuf = ISC_LIST_HEAD(msg->cleanup);
	while (dynbuf != NULL) {
		next_dynbuf = ISC_LIST_NEXT(dynbuf, link);
		ISC_LIST_UNLINK(msg->cleanup, dynbuf, link);
		isc_buffer_free(&dynbuf);
		dynbuf = next_dynbuf;
	}

	/*
	 * Set other bits to normal default values.
	 */
	if (!everything)
		msginit(msg);

	ENSURE(isc_mempool_getallocated(msg->namepool) == 0);
	ENSURE(isc_mempool_getallocated(msg->rdspool) == 0);
}

static unsigned int
spacefortsig(dns_tsigkey_t *key, int otherlen) {
	isc_region_t r1, r2;
	unsigned int x;
	isc_result_t result;

	/*
	 * The space required for an TSIG record is:
	 *
	 *	n1 bytes for the name
	 *	2 bytes for the type
	 *	2 bytes for the class
	 *	4 bytes for the ttl
	 *	2 bytes for the rdlength
	 *	n2 bytes for the algorithm name
	 *	6 bytes for the time signed
	 *	2 bytes for the fudge
	 *	2 bytes for the MAC size
	 *	x bytes for the MAC
	 *	2 bytes for the original id
	 *	2 bytes for the error
	 *	2 bytes for the other data length
	 *	y bytes for the other data (at most)
	 * ---------------------------------
	 *     26 + n1 + n2 + x + y bytes
	 */

	dns_name_toregion(&key->name, &r1);
	dns_name_toregion(key->algorithm, &r2);
	if (key->key == NULL)
		x = 0;
	else {
		result = dst_key_sigsize(key->key, &x);
		if (result != ISC_R_SUCCESS)
			x = 0;
	}
	return (26 + r1.length + r2.length + x + otherlen);
}

isc_result_t
dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp)
{
	dns_message_t *m;
	isc_result_t result;
	isc_buffer_t *dynbuf;
	unsigned int i;

	REQUIRE(mctx != NULL);
	REQUIRE(msgp != NULL);
	REQUIRE(*msgp == NULL);
	REQUIRE(intent == DNS_MESSAGE_INTENTPARSE
		|| intent == DNS_MESSAGE_INTENTRENDER);

	m = isc_mem_get(mctx, sizeof(dns_message_t));
	if (m == NULL)
		return (ISC_R_NOMEMORY);

	/*
	 * No allocations until further notice.  Just initialize all lists
	 * and other members that are freed in the cleanup phase here.
	 */

	m->magic = DNS_MESSAGE_MAGIC;
	m->from_to_wire = intent;
	msginit(m);

	for (i = 0; i < DNS_SECTION_MAX; i++)
		ISC_LIST_INIT(m->sections[i]);

	m->mctx = NULL;
	isc_mem_attach(mctx, &m->mctx);

	ISC_LIST_INIT(m->scratchpad);
	ISC_LIST_INIT(m->cleanup);
	m->namepool = NULL;
	m->rdspool = NULL;
	ISC_LIST_INIT(m->rdatas);
	ISC_LIST_INIT(m->rdatalists);
	ISC_LIST_INIT(m->offsets);
	ISC_LIST_INIT(m->freerdata);
	ISC_LIST_INIT(m->freerdatalist);

	/*
	 * Ok, it is safe to allocate (and then "goto cleanup" if failure)
	 */

	result = isc_mempool_create(m->mctx, sizeof(dns_name_t), &m->namepool);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	isc_mempool_setfillcount(m->namepool, NAME_COUNT);
	isc_mempool_setfreemax(m->namepool, NAME_COUNT);
	isc_mempool_setname(m->namepool, "msg:names");

	result = isc_mempool_create(m->mctx, sizeof(dns_rdataset_t),
				    &m->rdspool);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	isc_mempool_setfillcount(m->rdspool, RDATASET_COUNT);
	isc_mempool_setfreemax(m->rdspool, RDATASET_COUNT);
	isc_mempool_setname(m->rdspool, "msg:rdataset");

	dynbuf = NULL;
	result = isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	ISC_LIST_APPEND(m->scratchpad, dynbuf, link);

	m->cctx = NULL;

	*msgp = m;
	return (ISC_R_SUCCESS);

	/*
	 * Cleanup for error returns.
	 */
 cleanup:
	dynbuf = ISC_LIST_HEAD(m->scratchpad);
	if (dynbuf != NULL) {
		ISC_LIST_UNLINK(m->scratchpad, dynbuf, link);
		isc_buffer_free(&dynbuf);
	}
	if (m->namepool != NULL)
		isc_mempool_destroy(&m->namepool);
	if (m->rdspool != NULL)
		isc_mempool_destroy(&m->rdspool);
	m->magic = 0;
	isc_mem_putanddetach(&mctx, m, sizeof(dns_message_t));

	return (ISC_R_NOMEMORY);
}

void
dns_message_reset(dns_message_t *msg, unsigned int intent) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(intent == DNS_MESSAGE_INTENTPARSE
		|| intent == DNS_MESSAGE_INTENTRENDER);

	msgreset(msg, false);
	msg->from_to_wire = intent;
}

void
dns_message_destroy(dns_message_t **msgp) {
	dns_message_t *msg;

	REQUIRE(msgp != NULL);
	REQUIRE(DNS_MESSAGE_VALID(*msgp));

	msg = *msgp;
	*msgp = NULL;

	msgreset(msg, true);
	isc_mempool_destroy(&msg->namepool);
	isc_mempool_destroy(&msg->rdspool);
	msg->magic = 0;
	isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t));
}

static isc_result_t
findname(dns_name_t **foundname, dns_name_t *target,
	 dns_namelist_t *section)
{
	dns_name_t *curr;

	for (curr = ISC_LIST_TAIL(*section);
	     curr != NULL;
	     curr = ISC_LIST_PREV(curr, link)) {
		if (dns_name_equal(curr, target)) {
			if (foundname != NULL)
				*foundname = curr;
			return (ISC_R_SUCCESS);
		}
	}

	return (ISC_R_NOTFOUND);
}

isc_result_t
dns_message_find(dns_name_t *name, dns_rdataclass_t rdclass,
		 dns_rdatatype_t type, dns_rdatatype_t covers,
		 dns_rdataset_t **rdataset)
{
	dns_rdataset_t *curr;

	REQUIRE(name != NULL);
	REQUIRE(rdataset == NULL || *rdataset == NULL);

	for (curr = ISC_LIST_TAIL(name->list);
	     curr != NULL;
	     curr = ISC_LIST_PREV(curr, link)) {
		if (curr->rdclass == rdclass &&
		    curr->type == type && curr->covers == covers) {
			if (rdataset != NULL)
				*rdataset = curr;
			return (ISC_R_SUCCESS);
		}
	}

	return (ISC_R_NOTFOUND);
}

isc_result_t
dns_message_findtype(dns_name_t *name, dns_rdatatype_t type,
		     dns_rdatatype_t covers, dns_rdataset_t **rdataset)
{
	dns_rdataset_t *curr;

	REQUIRE(name != NULL);
	REQUIRE(rdataset == NULL || *rdataset == NULL);

	for (curr = ISC_LIST_TAIL(name->list);
	     curr != NULL;
	     curr = ISC_LIST_PREV(curr, link)) {
		if (curr->type == type && curr->covers == covers) {
			if (ISC_UNLIKELY(rdataset != NULL))
				*rdataset = curr;
			return (ISC_R_SUCCESS);
		}
	}

	return (ISC_R_NOTFOUND);
}

/*
 * Read a name from buffer "source".
 */
static isc_result_t
getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg,
	dns_decompress_t *dctx)
{
	isc_buffer_t *scratch;
	isc_result_t result;
	unsigned int tries;

	scratch = currentbuffer(msg);

	/*
	 * First try:  use current buffer.
	 * Second try:  allocate a new buffer and use that.
	 */
	tries = 0;
	while (tries < 2) {
		result = dns_name_fromwire(name, source, dctx, 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);
		}
	}

	INSIST(0);
	ISC_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;
	dns_name_t *name2;
	dns_offsets_t *offsets;
	dns_rdataset_t *rdataset;
	dns_rdatalist_t *rdatalist;
	isc_result_t result;
	dns_rdatatype_t rdtype;
	dns_rdataclass_t rdclass;
	dns_namelist_t *section;
	bool free_name;
	bool best_effort;
	bool seen_problem;

	section = &msg->sections[DNS_SECTION_QUESTION];

	best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
	seen_problem = false;

	name = NULL;
	rdataset = NULL;
	rdatalist = NULL;

	for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
		name = isc_mempool_get(msg->namepool);
		if (name == NULL)
			return (ISC_R_NOMEMORY);
		free_name = true;

		offsets = newoffsets(msg);
		if (offsets == NULL) {
			result = ISC_R_NOMEMORY;
			goto cleanup;
		}
		dns_name_init(name, *offsets);

		/*
		 * Parse the name out of this packet.
		 */
		isc_buffer_remainingregion(source, &r);
		isc_buffer_setactive(source, r.length);
		result = getname(name, source, msg, dctx);
		if (result != ISC_R_SUCCESS)
			goto cleanup;

		/*
		 * Run through the section, looking to see if this name
		 * is already there.  If it is found, put back the allocated
		 * name since we no longer need it, and set our name pointer
		 * to point to the name we found.
		 */
		result = findname(&name2, name, section);

		/*
		 * If it is the first name in the section, accept it.
		 *
		 * If it is not, but is not the same as the name already
		 * in the question section, append to the section.  Note that
		 * here in the question section this is illegal, so return
		 * FORMERR.  In the future, check the opcode to see if
		 * this should be legal or not.  In either case we no longer
		 * need this name pointer.
		 */
		if (result != ISC_R_SUCCESS) {
			if (!ISC_LIST_EMPTY(*section))
				DO_ERROR(DNS_R_FORMERR);
			ISC_LIST_APPEND(*section, name, link);
			free_name = false;
		} else {
			isc_mempool_put(msg->namepool, name);
			name = name2;
			name2 = NULL;
			free_name = false;
		}

		/*
		 * Get type and class.
		 */
		isc_buffer_remainingregion(source, &r);
		if (r.length < 4) {
			result = ISC_R_UNEXPECTEDEND;
			goto cleanup;
		}
		rdtype = isc_buffer_getuint16(source);
		rdclass = isc_buffer_getuint16(source);

		/*
		 * If this class is different than the one we already read,
		 * this is an error.
		 */
		if (msg->rdclass_set == 0) {
			msg->rdclass = rdclass;
			msg->rdclass_set = 1;
		} else if (msg->rdclass != rdclass)
			DO_ERROR(DNS_R_FORMERR);

		/*
		 * Is this a TKEY query?
		 */
		if (rdtype == dns_rdatatype_tkey)
			msg->tkey = 1;

		/*
		 * Can't ask the same question twice.
		 */
		result = dns_message_find(name, rdclass, rdtype, 0, NULL);
		if (result == ISC_R_SUCCESS)
			DO_ERROR(DNS_R_FORMERR);

		/*
		 * Allocate a new rdatalist.
		 */
		rdatalist = newrdatalist(msg);
		if (rdatalist == NULL) {
			result = ISC_R_NOMEMORY;
			goto cleanup;
		}
		rdataset =  isc_mempool_get(msg->rdspool);
		if (rdataset == NULL) {
			result = ISC_R_NOMEMORY;
			goto cleanup;
		}

		/*
		 * Convert rdatalist to rdataset, and attach the latter to
		 * the name.
		 */
		rdatalist->type = rdtype;
		rdatalist->rdclass = rdclass;

		dns_rdataset_init(rdataset);
		result = dns_rdatalist_tordataset(rdatalist, rdataset);
		if (result != ISC_R_SUCCESS)
			goto cleanup;

		rdataset->attributes |= DNS_RDATASETATTR_QUESTION;

		ISC_LIST_APPEND(name->list, rdataset, link);
		rdataset = NULL;
	}

	if (seen_problem)
		return (DNS_R_RECOVERABLE);
	return (ISC_R_SUCCESS);

 cleanup:
	if (rdataset != NULL) {
		INSIST(!dns_rdataset_isassociated(rdataset));
		isc_mempool_put(msg->rdspool, rdataset);
	}
#if 0
	if (rdatalist != NULL)
		isc_mempool_put(msg->rdlpool, rdatalist);
#endif
	if (free_name)
		isc_mempool_put(msg->namepool, name);

	return (result);
}

static bool
update(dns_section_t section, dns_rdataclass_t rdclass) {
	if (section == DNS_SECTION_PREREQUISITE)
		return (rdclass == dns_rdataclass_any ||
			 rdclass == dns_rdataclass_none);
	if (section == DNS_SECTION_UPDATE)
		return (rdclass == dns_rdataclass_any);
	return (false);
}

/*
 * Check to confirm that all DNSSEC records (DS, NSEC, NSEC3) have
 * covering RRSIGs.
 */
static bool
auth_signed(dns_namelist_t *section) {
	dns_name_t *name;

	for (name = ISC_LIST_HEAD(*section);
	     name != NULL;
	     name = ISC_LIST_NEXT(name, link))
	{
		int auth_dnssec = 0, auth_rrsig = 0;
		dns_rdataset_t *rds;

		for (rds = ISC_LIST_HEAD(name->list);
		     rds != NULL;
		     rds = ISC_LIST_NEXT(rds, link))
		{
			switch (rds->type) {
			case dns_rdatatype_ds:
				auth_dnssec |= 0x1;
				break;
			case dns_rdatatype_nsec:
				auth_dnssec |= 0x2;
				break;
			case dns_rdatatype_nsec3:
				auth_dnssec |= 0x4;
				break;
			case dns_rdatatype_rrsig:
				break;
			default:
				continue;
			}

			switch (rds->covers) {
			case dns_rdatatype_ds:
				auth_rrsig |= 0x1;
				break;
			case dns_rdatatype_nsec:
				auth_rrsig |= 0x2;
				break;
			case dns_rdatatype_nsec3:
				auth_rrsig |= 0x4;
				break;
			default:
				break;
			}
		}

		if (auth_dnssec != auth_rrsig)
			return (false);
	}

	return (true);
}

static isc_result_t
getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
	   dns_section_t sectionid, unsigned int options)
{
	isc_region_t r;
	unsigned int count, rdatalen;
	dns_name_t *name = NULL;
	dns_name_t *name2 = NULL;
	dns_offsets_t *offsets;
	dns_rdataset_t *rdataset;
	dns_rdatalist_t *rdatalist;
	isc_result_t result;
	dns_rdatatype_t rdtype, covers;
	dns_rdataclass_t rdclass;
	dns_rdata_t *rdata;
	dns_ttl_t ttl;
	dns_namelist_t *section;
	bool free_name = false, free_rdataset = false;
	bool preserve_order, best_effort, seen_problem;
	bool isedns, issigzero, istsig;

	preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
	best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
	seen_problem = false;

	section = &msg->sections[sectionid];

	for (count = 0; count < msg->counts[sectionid]; count++) {
		int recstart = source->current;
		bool skip_name_search, skip_type_search;

		skip_name_search = false;
		skip_type_search = false;
		free_rdataset = false;
		isedns = false;
		issigzero = false;
		istsig = false;

		name = isc_mempool_get(msg->namepool);
		if (name == NULL)
			return (ISC_R_NOMEMORY);
		free_name = true;

		offsets = newoffsets(msg);
		if (offsets == NULL) {
			result = ISC_R_NOMEMORY;
			goto cleanup;
		}
		dns_name_init(name, *offsets);

		/*
		 * Parse the name out of this packet.
		 */
		isc_buffer_remainingregion(source, &r);
		isc_buffer_setactive(source, r.length);
		result = getname(name, source, msg, dctx);
		if (result != ISC_R_SUCCESS)
			goto cleanup;

		/*
		 * Get type, class, ttl, and rdatalen.  Verify that at least
		 * rdatalen bytes remain.  (Some of this is deferred to
		 * later.)
		 */
		isc_buffer_remainingregion(source, &r);
		if (r.length < 2 + 2 + 4 + 2) {
			result = ISC_R_UNEXPECTEDEND;
			goto cleanup;
		}
		rdtype = isc_buffer_getuint16(source);
		rdclass = isc_buffer_getuint16(source);

		/*
		 * If there was no question section, we may not yet have
		 * established a class.  Do so now.
		 */
		if (msg->rdclass_set == 0 &&
		    rdtype != dns_rdatatype_opt &&	/* class is UDP SIZE */
		    rdtype != dns_rdatatype_tsig &&	/* class is ANY */
		    rdtype != dns_rdatatype_tkey) {	/* class is undefined */
			msg->rdclass = rdclass;
			msg->rdclass_set = 1;
		}

		/*
		 * If this class is different than the one in the question
		 * section, bail.
		 */
		if (msg->opcode != dns_opcode_update
		    && rdtype != dns_rdatatype_tsig
		    && rdtype != dns_rdatatype_opt
		    && rdtype != dns_rdatatype_key /* in a TKEY query */
		    && rdtype != dns_rdatatype_sig /* SIG(0) */
		    && rdtype != dns_rdatatype_tkey /* Win2000 TKEY */
		    && msg->rdclass != dns_rdataclass_any
		    && msg->rdclass != rdclass)
			DO_ERROR(DNS_R_FORMERR);

		/*
		 * If this is not a TKEY query/response then the KEY
		 * record's class needs to match.
		 */
		if (msg->opcode != dns_opcode_update && !msg->tkey &&
		    rdtype == dns_rdatatype_key &&
		    msg->rdclass != dns_rdataclass_any &&
		    msg->rdclass != rdclass)
			DO_ERROR(DNS_R_FORMERR);

		/*
		 * Special type handling for TSIG, OPT, and TKEY.
		 */
		if (rdtype == dns_rdatatype_tsig) {
			/*
			 * If it is a tsig, verify that it is in the
			 * additional data section.
			 */
			if (sectionid != DNS_SECTION_ADDITIONAL ||
			    rdclass != dns_rdataclass_any ||
			    count != msg->counts[sectionid]  - 1) {
				DO_ERROR(DNS_R_BADTSIG);
			} 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) {
				isc_mempool_put(msg->namepool, name);
				name = name2;
			} else {
				ISC_LIST_APPEND(*section, name, link);
			}
			free_name = false;
		}

		/*
		 * Search name for the particular type and class.
		 * Skip this stage if in update mode or this is a meta-type.
		 */
		if (preserve_order || msg->opcode == dns_opcode_update ||
		    skip_type_search)
			result = ISC_R_NOTFOUND;
		else {
			/*
			 * If this is a type that can only occur in
			 * the question section, fail.
			 */
			if (dns_rdatatype_questiononly(rdtype))
				DO_ERROR(DNS_R_FORMERR);

			rdataset = NULL;
			result = dns_message_find(name, rdclass, rdtype,
						   covers, &rdataset);
		}

		/*
		 * If we found an rdataset that matches, we need to
		 * append this rdata to that set.  If we did not, we need
		 * to create a new rdatalist, store the important bits there,
		 * convert it to an rdataset, and link the latter to the name.
		 * Yuck.  When appending, make certain that the type isn't
		 * a singleton type, such as SOA or CNAME.
		 *
		 * Note that this check will be bypassed when preserving order,
		 * the opcode is an update, or the type search is skipped.
		 */
		if (result == ISC_R_SUCCESS) {
			if (dns_rdatatype_issingleton(rdtype)) {
				dns_rdata_t *first;
				dns_rdatalist_fromrdataset(rdataset,
							   &rdatalist);
				first = ISC_LIST_HEAD(rdatalist->rdata);
				INSIST(first != NULL);
				if (dns_rdata_compare(rdata, first) != 0)
					DO_ERROR(DNS_R_FORMERR);
			}
		}

		if (result == ISC_R_NOTFOUND) {
			rdataset = isc_mempool_get(msg->rdspool);
			if (rdataset == NULL) {
				result = ISC_R_NOMEMORY;
				goto cleanup;
			}
			free_rdataset = true;

			rdatalist = newrdatalist(msg);
			if (rdatalist == NULL) {
				result = ISC_R_NOMEMORY;
				goto cleanup;
			}

			rdatalist->type = rdtype;
			rdatalist->covers = covers;
			rdatalist->rdclass = rdclass;
			rdatalist->ttl = ttl;

			dns_rdataset_init(rdataset);
			RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist,
							       rdataset)
				      == ISC_R_SUCCESS);
			dns_rdataset_setownercase(rdataset, name);

			if (!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;
			isc_mempool_put(msg->namepool, 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)
				isc_mempool_put(msg->namepool, name);
			if (free_rdataset)
				isc_mempool_put(msg->rdspool, rdataset);
			free_name = free_rdataset = false;
		}
		INSIST(free_name == false);
		INSIST(free_rdataset == false);
	}

	/*
	 * If any of DS, NSEC or NSEC3 appeared in the
	 * authority section of a query response without
	 * a covering RRSIG, FORMERR
	 */
	if (sectionid == DNS_SECTION_AUTHORITY &&
	    msg->opcode == dns_opcode_query &&
	    ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) &&
	    ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) &&
	    !preserve_order &&
	    !auth_signed(section))
		DO_ERROR(DNS_R_FORMERR);

	if (seen_problem)
		return (DNS_R_RECOVERABLE);
	return (ISC_R_SUCCESS);

 cleanup:
	if (free_name)
		isc_mempool_put(msg->namepool, name);
	if (free_rdataset)
		isc_mempool_put(msg->rdspool, rdataset);

	return (result);
}

isc_result_t
dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
		  unsigned int options)
{
	isc_region_t r;
	dns_decompress_t dctx;
	isc_result_t ret;
	uint16_t tmpflags;
	isc_buffer_t origsource;
	bool seen_problem;
	bool ignore_tc;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(source != NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);

	seen_problem = false;
	ignore_tc = ((options & DNS_MESSAGEPARSE_IGNORETRUNCATION) != 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);
		if (msg->saved.base == NULL) {
			return (ISC_R_NOMEMORY);
		}
		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 == true)
		return (DNS_R_RECOVERABLE);
	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx,
			isc_buffer_t *buffer)
{
	isc_region_t r;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(buffer != NULL);
	REQUIRE(msg->buffer == NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);

	msg->cctx = cctx;

	/*
	 * Erase the contents of this buffer.
	 */
	isc_buffer_clear(buffer);

	/*
	 * Make certain there is enough for at least the header in this
	 * buffer.
	 */
	isc_buffer_availableregion(buffer, &r);
	if (r.length < DNS_MESSAGE_HEADERLEN)
		return (ISC_R_NOSPACE);

	if (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved)
		return (ISC_R_NOSPACE);

	/*
	 * Reserve enough space for the header in this buffer.
	 */
	isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN);

	msg->buffer = buffer;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) {
	isc_region_t r, rn;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(buffer != NULL);
	REQUIRE(msg->buffer != NULL);

	/*
	 * Ensure that the new buffer is empty, and has enough space to
	 * hold the current contents.
	 */
	isc_buffer_clear(buffer);

	isc_buffer_availableregion(buffer, &rn);
	isc_buffer_usedregion(msg->buffer, &r);
	REQUIRE(rn.length > r.length);

	/*
	 * Copy the contents from the old to the new buffer.
	 */
	isc_buffer_add(buffer, r.length);
	memmove(rn.base, r.base, r.length);

	msg->buffer = buffer;

	return (ISC_R_SUCCESS);
}

void
dns_message_renderrelease(dns_message_t *msg, unsigned int space) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(space <= msg->reserved);

	msg->reserved -= space;
}

isc_result_t
dns_message_renderreserve(dns_message_t *msg, unsigned int space) {
	isc_region_t r;

	REQUIRE(DNS_MESSAGE_VALID(msg));

	if (msg->buffer != NULL) {
		isc_buffer_availableregion(msg->buffer, &r);
		if (r.length < (space + msg->reserved))
			return (ISC_R_NOSPACE);
	}

	msg->reserved += space;

	return (ISC_R_SUCCESS);
}

static inline bool
wrong_priority(dns_rdataset_t *rds, int pass, dns_rdatatype_t preferred_glue) {
	int pass_needed;

	/*
	 * If we are not rendering class IN, this ordering is bogus.
	 */
	if (rds->rdclass != dns_rdataclass_in)
		return (false);

	switch (rds->type) {
	case dns_rdatatype_a:
	case dns_rdatatype_aaaa:
		if (preferred_glue == rds->type)
			pass_needed = 4;
		else
			pass_needed = 3;
		break;
	case dns_rdatatype_rrsig:
	case dns_rdatatype_dnskey:
		pass_needed = 2;
		break;
	default:
		pass_needed = 1;
	}

	if (pass_needed >= pass)
		return (false);

	return (true);
}

#ifdef ALLOW_FILTER_AAAA
/*
 * Decide whether to not answer with an AAAA record and its RRSIG
 */
static inline bool
norender_rdataset(const dns_rdataset_t *rdataset, unsigned int options,
		  dns_section_t sectionid)
{
	if (sectionid == DNS_SECTION_QUESTION)
		return (false);

	switch (rdataset->type) {
	case dns_rdatatype_ns:
		if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 ||
		    sectionid != DNS_SECTION_AUTHORITY)
			return (false);
		break;

	case dns_rdatatype_aaaa:
		if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0)
			return (false);
		break;

	case dns_rdatatype_rrsig:
		if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 ||
		    (rdataset->covers != dns_rdatatype_ns &&
		     rdataset->covers != dns_rdatatype_aaaa))
			return (false);
		if ((rdataset->covers == dns_rdatatype_ns) &&
		    (sectionid != DNS_SECTION_AUTHORITY))
			return (false);
		break;

	default:
		return (false);
	}

	if (rdataset->rdclass != dns_rdataclass_in)
		return (false);

	return (true);
}
#endif

static isc_result_t
renderset(dns_rdataset_t *rdataset, dns_name_t *owner_name,
	  dns_compress_t *cctx, isc_buffer_t *target,
	  unsigned int reserved, unsigned int options, unsigned int *countp)
{
	isc_result_t result;

	/*
	 * Shrink the space in the buffer by the reserved amount.
	 */
	if (target->length - target->used < reserved)
		return (ISC_R_NOSPACE);

	target->length -= reserved;
	result = dns_rdataset_towire(rdataset, owner_name,
				     cctx, target, options, countp);
	target->length += reserved;

	return (result);
}

static void
maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) {
	if (msg->counts[sectionid] == 0 &&
	    (sectionid == DNS_SECTION_ANSWER ||
	     (sectionid == DNS_SECTION_AUTHORITY &&
	      msg->counts[DNS_SECTION_ANSWER] == 0)))
		msg->flags &= ~DNS_MESSAGEFLAG_AD;
}

isc_result_t
dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
			  unsigned int options)
{
	dns_namelist_t *section;
	dns_name_t *name, *next_name;
	dns_rdataset_t *rdataset, *next_rdataset;
	unsigned int count, total;
	isc_result_t result;
	isc_buffer_t st; /* for rollbacks */
	int pass;
	bool partial = false;
	unsigned int rd_options;
	dns_rdatatype_t preferred_glue = 0;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->buffer != NULL);
	REQUIRE(VALID_NAMED_SECTION(sectionid));

	section = &msg->sections[sectionid];

	if ((sectionid == DNS_SECTION_ADDITIONAL)
	    && (options & DNS_MESSAGERENDER_ORDERED) == 0) {
		if ((options & DNS_MESSAGERENDER_PREFER_A) != 0) {
			preferred_glue = dns_rdatatype_a;
			pass = 4;
		} else if ((options & DNS_MESSAGERENDER_PREFER_AAAA) != 0) {
			preferred_glue = dns_rdatatype_aaaa;
			pass = 4;
		} else
			pass = 3;
	} else
		pass = 1;

	if ((options & DNS_MESSAGERENDER_OMITDNSSEC) == 0)
		rd_options = 0;
	else
		rd_options = DNS_RDATASETTOWIRE_OMITDNSSEC;

	/*
	 * Shrink the space in the buffer by the reserved amount.
	 */
	if (msg->buffer->length - msg->buffer->used < msg->reserved)
		return (ISC_R_NOSPACE);
	msg->buffer->length -= msg->reserved;

	total = 0;
	if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0)
		partial = true;

	/*
	 * Render required glue first.  Set TC if it won't fit.
	 */
	name = ISC_LIST_HEAD(*section);
	if (name != NULL) {
		rdataset = ISC_LIST_HEAD(name->list);
		if (rdataset != NULL &&
		    (rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) != 0 &&
		    (rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0) {
			const void *order_arg = msg->order_arg;
			st = *(msg->buffer);
			count = 0;
			if (partial)
				result = dns_rdataset_towirepartial(rdataset,
								    name,
								    msg->cctx,
								    msg->buffer,
								    msg->order,
								    order_arg,
								    rd_options,
								    &count,
								    NULL);
			else
				result = dns_rdataset_towiresorted(rdataset,
								   name,
								   msg->cctx,
								   msg->buffer,
								   msg->order,
								   order_arg,
								   rd_options,
								   &count);
			total += count;
			if (partial && result == ISC_R_NOSPACE) {
				msg->flags |= DNS_MESSAGEFLAG_TC;
				msg->buffer->length += msg->reserved;
				msg->counts[sectionid] += total;
				return (result);
			}
			if (result == ISC_R_NOSPACE)
				msg->flags |= DNS_MESSAGEFLAG_TC;
			if (result != ISC_R_SUCCESS) {
				INSIST(st.used < 65536);
				dns_compress_rollback(msg->cctx,
						      (uint16_t)st.used);
				*(msg->buffer) = st;  /* rollback */
				msg->buffer->length += msg->reserved;
				msg->counts[sectionid] += total;
				return (result);
			}
			rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
		}
	}

	do {
		name = ISC_LIST_HEAD(*section);
		if (name == NULL) {
			msg->buffer->length += msg->reserved;
			msg->counts[sectionid] += total;
			return (ISC_R_SUCCESS);
		}

		while (name != NULL) {
			next_name = ISC_LIST_NEXT(name, link);

			rdataset = ISC_LIST_HEAD(name->list);
			while (rdataset != NULL) {
				next_rdataset = ISC_LIST_NEXT(rdataset, link);

				if ((rdataset->attributes &
				     DNS_RDATASETATTR_RENDERED) != 0)
					goto next;

				if (((options & DNS_MESSAGERENDER_ORDERED)
				     == 0)
				    && (sectionid == DNS_SECTION_ADDITIONAL)
				    && wrong_priority(rdataset, pass,
						      preferred_glue))
					goto next;

#ifdef ALLOW_FILTER_AAAA
				/*
				 * Suppress AAAAs if asked and we are
				 * not doing DNSSEC or are breaking DNSSEC.
				 * Say so in the AD bit if we break DNSSEC.
				 */
				if (norender_rdataset(rdataset, options, sectionid)) {
					if (sectionid == DNS_SECTION_ANSWER ||
					    sectionid == DNS_SECTION_AUTHORITY)
					    msg->flags &= ~DNS_MESSAGEFLAG_AD;
					if (OPTOUT(rdataset))
					    msg->flags &= ~DNS_MESSAGEFLAG_AD;
					goto next;
				}

#endif
				st = *(msg->buffer);

				count = 0;
				if (partial)
					result = dns_rdataset_towirepartial(
							  rdataset,
							  name,
							  msg->cctx,
							  msg->buffer,
							  msg->order,
							  msg->order_arg,
							  rd_options,
							  &count,
							  NULL);
				else
					result = dns_rdataset_towiresorted(
							  rdataset,
							  name,
							  msg->cctx,
							  msg->buffer,
							  msg->order,
							  msg->order_arg,
							  rd_options,
							  &count);

				total += count;

				/*
				 * If out of space, record stats on what we
				 * rendered so far, and return that status.
				 *
				 * XXXMLG Need to change this when
				 * dns_rdataset_towire() can render partial
				 * sets starting at some arbitrary point in the
				 * set.  This will include setting a bit in the
				 * rdataset to indicate that a partial
				 * rendering was done, and some state saved
				 * somewhere (probably in the message struct)
				 * to indicate where to continue from.
				 */
				if (partial && result == ISC_R_NOSPACE) {
					msg->buffer->length += msg->reserved;
					msg->counts[sectionid] += total;
					return (result);
				}
				if (result != ISC_R_SUCCESS) {
					INSIST(st.used < 65536);
					dns_compress_rollback(msg->cctx,
							(uint16_t)st.used);
					*(msg->buffer) = st;  /* rollback */
					msg->buffer->length += msg->reserved;
					msg->counts[sectionid] += total;
					maybe_clear_ad(msg, sectionid);
					return (result);
				}

				/*
				 * If we have rendered non-validated data,
				 * ensure that the AD bit is not set.
				 */
				if (rdataset->trust != dns_trust_secure &&
				    (sectionid == DNS_SECTION_ANSWER ||
				     sectionid == DNS_SECTION_AUTHORITY))
					msg->flags &= ~DNS_MESSAGEFLAG_AD;
				if (OPTOUT(rdataset))
					msg->flags &= ~DNS_MESSAGEFLAG_AD;

				rdataset->attributes |=
					DNS_RDATASETATTR_RENDERED;

			next:
				rdataset = next_rdataset;
			}

			name = next_name;
		}
	} while (--pass != 0);

	msg->buffer->length += msg->reserved;
	msg->counts[sectionid] += total;

	return (ISC_R_SUCCESS);
}

void
dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) {
	uint16_t tmp;
	isc_region_t r;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(target != NULL);

	isc_buffer_availableregion(target, &r);
	REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN);

	isc_buffer_putuint16(target, msg->id);

	tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT)
	       & DNS_MESSAGE_OPCODE_MASK);
	tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK);
	tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK);

	INSIST(msg->counts[DNS_SECTION_QUESTION]  < 65536 &&
	       msg->counts[DNS_SECTION_ANSWER]    < 65536 &&
	       msg->counts[DNS_SECTION_AUTHORITY] < 65536 &&
	       msg->counts[DNS_SECTION_ADDITIONAL] < 65536);

	isc_buffer_putuint16(target, tmp);
	isc_buffer_putuint16(target,
			    (uint16_t)msg->counts[DNS_SECTION_QUESTION]);
	isc_buffer_putuint16(target,
			    (uint16_t)msg->counts[DNS_SECTION_ANSWER]);
	isc_buffer_putuint16(target,
			    (uint16_t)msg->counts[DNS_SECTION_AUTHORITY]);
	isc_buffer_putuint16(target,
			    (uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]);
}

isc_result_t
dns_message_renderend(dns_message_t *msg) {
	isc_buffer_t tmpbuf;
	isc_region_t r;
	int result;
	unsigned int count;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->buffer != NULL);

	if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) {
		/*
		 * We have an extended rcode but are not using EDNS.
		 */
		return (DNS_R_FORMERR);
	}

	/*
	 * If we're adding a OPT, TSIG or SIG(0) to a truncated message,
	 * clear all rdatasets from the message except for the question
	 * before adding the OPT, TSIG or SIG(0).  If the question doesn't
	 * fit, don't include it.
	 */
	if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) &&
	    (msg->flags & DNS_MESSAGEFLAG_TC) != 0)
	{
		isc_buffer_t *buf;

		msgresetnames(msg, DNS_SECTION_ANSWER);
		buf = msg->buffer;
		dns_message_renderreset(msg);
		msg->buffer = buf;
		isc_buffer_clear(msg->buffer);
		isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN);
		dns_compress_rollback(msg->cctx, 0);
		result = dns_message_rendersection(msg, DNS_SECTION_QUESTION,
						   0);
		if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE)
			return (result);
	}

	/*
	 * If we've got an OPT record, render it.
	 */
	if (msg->opt != NULL) {
		dns_message_renderrelease(msg, msg->opt_reserved);
		msg->opt_reserved = 0;
		/*
		 * Set the extended rcode.
		 */
		msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK;
		msg->opt->ttl |= ((msg->rcode << 20) &
				  DNS_MESSAGE_EDNSRCODE_MASK);
		/*
		 * Render.
		 */
		count = 0;
		result = renderset(msg->opt, dns_rootname, msg->cctx,
				   msg->buffer, msg->reserved, 0, &count);
		msg->counts[DNS_SECTION_ADDITIONAL] += count;
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	/*
	 * If we're adding a TSIG record, generate and render it.
	 */
	if (msg->tsigkey != NULL) {
		dns_message_renderrelease(msg, msg->sig_reserved);
		msg->sig_reserved = 0;
		result = dns_tsig_sign(msg);
		if (result != ISC_R_SUCCESS)
			return (result);
		count = 0;
		result = renderset(msg->tsig, msg->tsigname, msg->cctx,
				   msg->buffer, msg->reserved, 0, &count);
		msg->counts[DNS_SECTION_ADDITIONAL] += count;
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	/*
	 * If we're adding a SIG(0) record, generate and render it.
	 */
	if (msg->sig0key != NULL) {
		dns_message_renderrelease(msg, msg->sig_reserved);
		msg->sig_reserved = 0;
		result = dns_dnssec_signmessage(msg, msg->sig0key);
		if (result != ISC_R_SUCCESS)
			return (result);
		count = 0;
		/*
		 * Note: dns_rootname is used here, not msg->sig0name, since
		 * the owner name of a SIG(0) is irrelevant, and will not
		 * be set in a message being rendered.
		 */
		result = renderset(msg->sig0, dns_rootname, msg->cctx,
				   msg->buffer, msg->reserved, 0, &count);
		msg->counts[DNS_SECTION_ADDITIONAL] += count;
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	isc_buffer_usedregion(msg->buffer, &r);
	isc_buffer_init(&tmpbuf, r.base, r.length);

	dns_message_renderheader(msg, &tmpbuf);

	msg->buffer = NULL;  /* forget about this buffer only on success XXX */

	return (ISC_R_SUCCESS);
}

void
dns_message_renderreset(dns_message_t *msg) {
	unsigned int i;
	dns_name_t *name;
	dns_rdataset_t *rds;

	/*
	 * Reset the message so that it may be rendered again.
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);

	msg->buffer = NULL;

	for (i = 0; i < DNS_SECTION_MAX; i++) {
		msg->cursors[i] = NULL;
		msg->counts[i] = 0;
		for (name = ISC_LIST_HEAD(msg->sections[i]);
		     name != NULL;
		     name = ISC_LIST_NEXT(name, link)) {
			for (rds = ISC_LIST_HEAD(name->list);
			     rds != NULL;
			     rds = ISC_LIST_NEXT(rds, link)) {
				rds->attributes &= ~DNS_RDATASETATTR_RENDERED;
			}
		}
	}
	if (msg->tsigname != NULL)
		dns_message_puttempname(msg, &msg->tsigname);
	if (msg->tsig != NULL) {
		dns_rdataset_disassociate(msg->tsig);
		dns_message_puttemprdataset(msg, &msg->tsig);
	}
	if (msg->sig0 != NULL) {
		dns_rdataset_disassociate(msg->sig0);
		dns_message_puttemprdataset(msg, &msg->sig0);
	}
}

isc_result_t
dns_message_firstname(dns_message_t *msg, dns_section_t section) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(VALID_NAMED_SECTION(section));

	msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]);

	if (msg->cursors[section] == NULL)
		return (ISC_R_NOMORE);

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_nextname(dns_message_t *msg, dns_section_t section) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(VALID_NAMED_SECTION(section));
	REQUIRE(msg->cursors[section] != NULL);

	msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link);

	if (msg->cursors[section] == NULL)
		return (ISC_R_NOMORE);

	return (ISC_R_SUCCESS);
}

void
dns_message_currentname(dns_message_t *msg, dns_section_t section,
			dns_name_t **name)
{
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(VALID_NAMED_SECTION(section));
	REQUIRE(name != NULL && *name == NULL);
	REQUIRE(msg->cursors[section] != NULL);

	*name = msg->cursors[section];
}

isc_result_t
dns_message_findname(dns_message_t *msg, dns_section_t section,
		     dns_name_t *target, dns_rdatatype_t type,
		     dns_rdatatype_t covers, dns_name_t **name,
		     dns_rdataset_t **rdataset)
{
	dns_name_t *foundname;
	isc_result_t result;

	/*
	 * XXX These requirements are probably too intensive, especially
	 * where things can be NULL, but as they are they ensure that if
	 * something is NON-NULL, indicating that the caller expects it
	 * to be filled in, that we can in fact fill it in.
	 */
	REQUIRE(msg != NULL);
	REQUIRE(VALID_SECTION(section));
	REQUIRE(target != NULL);
	REQUIRE(name == NULL || *name == NULL);

	if (type == dns_rdatatype_any) {
		REQUIRE(rdataset == NULL);
	} else {
		REQUIRE(rdataset == NULL || *rdataset == NULL);
	}

	result = findname(&foundname, target,
			  &msg->sections[section]);

	if (result == ISC_R_NOTFOUND)
		return (DNS_R_NXDOMAIN);
	else if (result != ISC_R_SUCCESS)
		return (result);

	if (name != NULL)
		*name = foundname;

	/*
	 * And now look for the type.
	 */
	if (ISC_UNLIKELY(type == dns_rdatatype_any))
		return (ISC_R_SUCCESS);

	result = dns_message_findtype(foundname, type, covers, rdataset);
	if (result == ISC_R_NOTFOUND)
		return (DNS_R_NXRRSET);

	return (result);
}

void
dns_message_movename(dns_message_t *msg, dns_name_t *name,
		     dns_section_t fromsection,
		     dns_section_t tosection)
{
	REQUIRE(msg != NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
	REQUIRE(name != NULL);
	REQUIRE(VALID_NAMED_SECTION(fromsection));
	REQUIRE(VALID_NAMED_SECTION(tosection));

	/*
	 * Unlink the name from the old section
	 */
	ISC_LIST_UNLINK(msg->sections[fromsection], name, link);
	ISC_LIST_APPEND(msg->sections[tosection], name, link);
}

void
dns_message_addname(dns_message_t *msg, dns_name_t *name,
		    dns_section_t section)
{
	REQUIRE(msg != NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
	REQUIRE(name != NULL);
	REQUIRE(VALID_NAMED_SECTION(section));

	ISC_LIST_APPEND(msg->sections[section], name, link);
}

void
dns_message_removename(dns_message_t *msg, dns_name_t *name,
		       dns_section_t section)
{
	REQUIRE(msg != NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
	REQUIRE(name != NULL);
	REQUIRE(VALID_NAMED_SECTION(section));

	ISC_LIST_UNLINK(msg->sections[section], name, link);
}

isc_result_t
dns_message_gettempname(dns_message_t *msg, dns_name_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item == NULL);

	*item = isc_mempool_get(msg->namepool);
	if (*item == NULL)
		return (ISC_R_NOMEMORY);
	dns_name_init(*item, NULL);

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_gettempoffsets(dns_message_t *msg, dns_offsets_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item == NULL);

	*item = newoffsets(msg);
	if (*item == NULL)
		return (ISC_R_NOMEMORY);

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item == NULL);

	*item = newrdata(msg);
	if (*item == NULL)
		return (ISC_R_NOMEMORY);

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item == NULL);

	*item = isc_mempool_get(msg->rdspool);
	if (*item == NULL)
		return (ISC_R_NOMEMORY);

	dns_rdataset_init(*item);

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item == NULL);

	*item = newrdatalist(msg);
	if (*item == NULL)
		return (ISC_R_NOMEMORY);

	return (ISC_R_SUCCESS);
}

void
dns_message_puttempname(dns_message_t *msg, dns_name_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item != NULL);

	if (dns_name_dynamic(*item))
		dns_name_free(*item, msg->mctx);
	isc_mempool_put(msg->namepool, *item);
	*item = NULL;
}

void
dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item != NULL);

	releaserdata(msg, *item);
	*item = NULL;
}

void
dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item != NULL);

	REQUIRE(!dns_rdataset_isassociated(*item));
	isc_mempool_put(msg->rdspool, *item);
	*item = NULL;
}

void
dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(item != NULL && *item != NULL);

	releaserdatalist(msg, *item);
	*item = NULL;
}

isc_result_t
dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp,
		       unsigned int *flagsp)
{
	isc_region_t r;
	isc_buffer_t buffer;
	dns_messageid_t id;
	unsigned int flags;

	REQUIRE(source != NULL);

	buffer = *source;

	isc_buffer_remainingregion(&buffer, &r);
	if (r.length < DNS_MESSAGE_HEADERLEN)
		return (ISC_R_UNEXPECTEDEND);

	id = isc_buffer_getuint16(&buffer);
	flags = isc_buffer_getuint16(&buffer);
	flags &= DNS_MESSAGE_FLAG_MASK;

	if (flagsp != NULL)
		*flagsp = flags;
	if (idp != NULL)
		*idp = id;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_reply(dns_message_t *msg, bool want_question_section) {
	unsigned int clear_from;
	isc_result_t result;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0);

	if (!msg->header_ok)
		return (DNS_R_FORMERR);
	if (msg->opcode != dns_opcode_query &&
	    msg->opcode != dns_opcode_notify)
		want_question_section = false;
	if (msg->opcode == dns_opcode_update)
		clear_from = DNS_SECTION_PREREQUISITE;
	else if (want_question_section) {
		if (!msg->question_ok)
			return (DNS_R_FORMERR);
		clear_from = DNS_SECTION_ANSWER;
	} else
		clear_from = DNS_SECTION_QUESTION;
	msg->from_to_wire = DNS_MESSAGE_INTENTRENDER;
	msgresetnames(msg, clear_from);
	msgresetopt(msg);
	msgresetsigs(msg, true);
	msginitprivate(msg);
	/*
	 * We now clear most flags and then set QR, ensuring that the
	 * reply's flags will be in a reasonable state.
	 */
	if (msg->opcode == dns_opcode_query)
		msg->flags &= DNS_MESSAGE_REPLYPRESERVE;
	else
		msg->flags = 0;
	msg->flags |= DNS_MESSAGEFLAG_QR;

	/*
	 * This saves the query TSIG status, if the query was signed, and
	 * reserves space in the reply for the TSIG.
	 */
	if (msg->tsigkey != NULL) {
		unsigned int otherlen = 0;
		msg->querytsigstatus = msg->tsigstatus;
		msg->tsigstatus = dns_rcode_noerror;
		if (msg->querytsigstatus == dns_tsigerror_badtime)
			otherlen = 6;
		msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen);
		result = dns_message_renderreserve(msg, msg->sig_reserved);
		if (result != ISC_R_SUCCESS) {
			msg->sig_reserved = 0;
			return (result);
		}
	}
	if (msg->saved.base != NULL) {
		msg->query.base = msg->saved.base;
		msg->query.length = msg->saved.length;
		msg->free_query = msg->free_saved;
		msg->saved.base = NULL;
		msg->saved.length = 0;
		msg->free_saved = 0;
	}

	return (ISC_R_SUCCESS);
}

dns_rdataset_t *
dns_message_getopt(dns_message_t *msg) {

	/*
	 * Get the OPT record for 'msg'.
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));

	return (msg->opt);
}

isc_result_t
dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
	isc_result_t result;
	dns_rdata_t rdata = DNS_RDATA_INIT;

	/*
	 * Set the OPT record for 'msg'.
	 */

	/*
	 * The space required for an OPT record is:
	 *
	 *	1 byte for the name
	 *	2 bytes for the type
	 *	2 bytes for the class
	 *	4 bytes for the ttl
	 *	2 bytes for the rdata length
	 * ---------------------------------
	 *     11 bytes
	 *
	 * plus the length of the rdata.
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(opt->type == dns_rdatatype_opt);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
	REQUIRE(msg->state == DNS_SECTION_ANY);

	msgresetopt(msg);

	result = dns_rdataset_first(opt);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	dns_rdataset_current(opt, &rdata);
	msg->opt_reserved = 11 + rdata.length;
	result = dns_message_renderreserve(msg, msg->opt_reserved);
	if (result != ISC_R_SUCCESS) {
		msg->opt_reserved = 0;
		goto cleanup;
	}

	msg->opt = opt;

	return (ISC_R_SUCCESS);

 cleanup:
	dns_rdataset_disassociate(opt);
	dns_message_puttemprdataset(msg, &opt);
	return (result);
}

dns_rdataset_t *
dns_message_gettsig(dns_message_t *msg, dns_name_t **owner) {

	/*
	 * Get the TSIG record and owner for 'msg'.
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(owner == NULL || *owner == NULL);

	if (owner != NULL)
		*owner = msg->tsigname;
	return (msg->tsig);
}

isc_result_t
dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) {
	isc_result_t result;

	/*
	 * Set the TSIG key for 'msg'
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));

	if (key == NULL && msg->tsigkey != NULL) {
		if (msg->sig_reserved != 0) {
			dns_message_renderrelease(msg, msg->sig_reserved);
			msg->sig_reserved = 0;
		}
		dns_tsigkey_detach(&msg->tsigkey);
	}
	if (key != NULL) {
		REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL);
		dns_tsigkey_attach(key, &msg->tsigkey);
		if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) {
			msg->sig_reserved = spacefortsig(msg->tsigkey, 0);
			result = dns_message_renderreserve(msg,
							   msg->sig_reserved);
			if (result != ISC_R_SUCCESS) {
				dns_tsigkey_detach(&msg->tsigkey);
				msg->sig_reserved = 0;
				return (result);
			}
		}
	}
	return (ISC_R_SUCCESS);
}

dns_tsigkey_t *
dns_message_gettsigkey(dns_message_t *msg) {

	/*
	 * Get the TSIG key for 'msg'
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));

	return (msg->tsigkey);
}

isc_result_t
dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) {
	dns_rdata_t *rdata = NULL;
	dns_rdatalist_t *list = NULL;
	dns_rdataset_t *set = NULL;
	isc_buffer_t *buf = NULL;
	isc_region_t r;
	isc_result_t result;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->querytsig == NULL);

	if (querytsig == NULL)
		return (ISC_R_SUCCESS);

	result = dns_message_gettemprdata(msg, &rdata);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	result = dns_message_gettemprdatalist(msg, &list);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	result = dns_message_gettemprdataset(msg, &set);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	isc_buffer_usedregion(querytsig, &r);
	result = isc_buffer_allocate(msg->mctx, &buf, r.length);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	isc_buffer_putmem(buf, r.base, r.length);
	isc_buffer_usedregion(buf, &r);
	dns_rdata_init(rdata);
	dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r);
	dns_message_takebuffer(msg, &buf);
	ISC_LIST_APPEND(list->rdata, rdata, link);
	result = dns_rdatalist_tordataset(list, set);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	msg->querytsig = set;

	return (result);

 cleanup:
	if (rdata != NULL)
		dns_message_puttemprdata(msg, &rdata);
	if (list != NULL)
		dns_message_puttemprdatalist(msg, &list);
	if (set != NULL)
		dns_message_puttemprdataset(msg, &set);
	return (ISC_R_NOMEMORY);
}

isc_result_t
dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx,
			 isc_buffer_t **querytsig) {
	isc_result_t result;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	isc_region_t r;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(mctx != NULL);
	REQUIRE(querytsig != NULL && *querytsig == NULL);

	if (msg->tsig == NULL)
		return (ISC_R_SUCCESS);

	result = dns_rdataset_first(msg->tsig);
	if (result != ISC_R_SUCCESS)
		return (result);
	dns_rdataset_current(msg->tsig, &rdata);
	dns_rdata_toregion(&rdata, &r);

	result = isc_buffer_allocate(mctx, querytsig, r.length);
	if (result != ISC_R_SUCCESS)
		return (result);
	isc_buffer_putmem(*querytsig, r.base, r.length);
	return (ISC_R_SUCCESS);
}

dns_rdataset_t *
dns_message_getsig0(dns_message_t *msg, dns_name_t **owner) {

	/*
	 * Get the SIG(0) record for 'msg'.
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(owner == NULL || *owner == NULL);

	if (msg->sig0 != NULL && owner != NULL) {
		/* If dns_message_getsig0 is called on a rendered message
		 * after the SIG(0) has been applied, we need to return the
		 * root name, not NULL.
		 */
		if (msg->sig0name == NULL)
			*owner = dns_rootname;
		else
			*owner = msg->sig0name;
	}
	return (msg->sig0);
}

isc_result_t
dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) {
	isc_region_t r;
	unsigned int x;
	isc_result_t result;

	/*
	 * Set the SIG(0) key for 'msg'
	 */

	/*
	 * The space required for an SIG(0) record is:
	 *
	 *	1 byte for the name
	 *	2 bytes for the type
	 *	2 bytes for the class
	 *	4 bytes for the ttl
	 *	2 bytes for the type covered
	 *	1 byte for the algorithm
	 *	1 bytes for the labels
	 *	4 bytes for the original ttl
	 *	4 bytes for the signature expiration
	 *	4 bytes for the signature inception
	 *	2 bytes for the key tag
	 *	n bytes for the signer's name
	 *	x bytes for the signature
	 * ---------------------------------
	 *     27 + n + x bytes
	 */
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
	REQUIRE(msg->state == DNS_SECTION_ANY);

	if (key != NULL) {
		REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL);
		dns_name_toregion(dst_key_name(key), &r);
		result = dst_key_sigsize(key, &x);
		if (result != ISC_R_SUCCESS) {
			msg->sig_reserved = 0;
			return (result);
		}
		msg->sig_reserved = 27 + r.length + x;
		result = dns_message_renderreserve(msg, msg->sig_reserved);
		if (result != ISC_R_SUCCESS) {
			msg->sig_reserved = 0;
			return (result);
		}
		msg->sig0key = key;
	}
	return (ISC_R_SUCCESS);
}

dst_key_t *
dns_message_getsig0key(dns_message_t *msg) {

	/*
	 * Get the SIG(0) key for 'msg'
	 */

	REQUIRE(DNS_MESSAGE_VALID(msg));

	return (msg->sig0key);
}

void
dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(buffer != NULL);
	REQUIRE(ISC_BUFFER_VALID(*buffer));

	ISC_LIST_APPEND(msg->cleanup, *buffer, link);
	*buffer = NULL;
}

isc_result_t
dns_message_signer(dns_message_t *msg, dns_name_t *signer) {
	isc_result_t result = ISC_R_SUCCESS;
	dns_rdata_t rdata = DNS_RDATA_INIT;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(signer != NULL);
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);

	if (msg->tsig == NULL && msg->sig0 == NULL)
		return (ISC_R_NOTFOUND);

	if (msg->verify_attempted == 0)
		return (DNS_R_NOTVERIFIEDYET);

	if (!dns_name_hasbuffer(signer)) {
		isc_buffer_t *dynbuf = NULL;
		result = isc_buffer_allocate(msg->mctx, &dynbuf, 512);
		if (result != ISC_R_SUCCESS)
			return (result);
		dns_name_setbuffer(signer, dynbuf);
		dns_message_takebuffer(msg, &dynbuf);
	}

	if (msg->sig0 != NULL) {
		dns_rdata_sig_t sig;

		result = dns_rdataset_first(msg->sig0);
		INSIST(result == ISC_R_SUCCESS);
		dns_rdataset_current(msg->sig0, &rdata);

		result = dns_rdata_tostruct(&rdata, &sig, NULL);
		if (result != ISC_R_SUCCESS)
			return (result);

		if (msg->verified_sig && msg->sig0status == dns_rcode_noerror)
			result = ISC_R_SUCCESS;
		else
			result = DNS_R_SIGINVALID;
		dns_name_clone(&sig.signer, signer);
		dns_rdata_freestruct(&sig);
	} else {
		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

isc_result_t
dns_message_checksig(dns_message_t *msg, dns_view_t *view) {
	isc_buffer_t b, msgb;

	REQUIRE(DNS_MESSAGE_VALID(msg));

	if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL)
		return (ISC_R_SUCCESS);

	INSIST(msg->saved.base != NULL);
	isc_buffer_init(&msgb, msg->saved.base, msg->saved.length);
	isc_buffer_add(&msgb, msg->saved.length);
	if (msg->tsigkey != NULL || msg->tsig != NULL) {
#ifdef SKAN_MSG_DEBUG
		dns_message_dumpsig(msg, "dns_message_checksig#1");
#endif
		if (view != NULL)
			return (dns_view_checksig(view, &msgb, msg));
		else
			return (dns_tsig_verify(&msgb, msg, NULL, NULL));
	} else {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdata_sig_t sig;
		dns_rdataset_t keyset;
		isc_result_t result;

		result = dns_rdataset_first(msg->sig0);
		INSIST(result == ISC_R_SUCCESS);
		dns_rdataset_current(msg->sig0, &rdata);

		/*
		 * This can occur when the message is a dynamic update, since
		 * the rdata length checking is relaxed.  This should not
		 * happen in a well-formed message, since the SIG(0) is only
		 * looked for in the additional section, and the dynamic update
		 * meta-records are in the prerequisite and update sections.
		 */
		if (rdata.length == 0)
			return (ISC_R_UNEXPECTEDEND);

		result = dns_rdata_tostruct(&rdata, &sig, msg->mctx);
		if (result != ISC_R_SUCCESS)
			return (result);

		dns_rdataset_init(&keyset);
		if (view == NULL)
			return (DNS_R_KEYUNAUTHORIZED);
		result = dns_view_simplefind(view, &sig.signer,
					     dns_rdatatype_key /* SIG(0) */,
					     0, 0, false, &keyset, NULL);

		if (result != ISC_R_SUCCESS) {
			/* XXXBEW Should possibly create a fetch here */
			result = DNS_R_KEYUNAUTHORIZED;
			goto freesig;
		} else if (keyset.trust < dns_trust_secure) {
			/* XXXBEW Should call a validator here */
			result = DNS_R_KEYUNAUTHORIZED;
			goto freesig;
		}
		result = dns_rdataset_first(&keyset);
		INSIST(result == ISC_R_SUCCESS);
		for (;
		     result == ISC_R_SUCCESS;
		     result = dns_rdataset_next(&keyset))
		{
			dst_key_t *key = NULL;

			dns_rdata_reset(&rdata);
			dns_rdataset_current(&keyset, &rdata);
			isc_buffer_init(&b, rdata.data, rdata.length);
			isc_buffer_add(&b, rdata.length);

			result = dst_key_fromdns(&sig.signer, rdata.rdclass,
						 &b, view->mctx, &key);
			if (result != ISC_R_SUCCESS)
				continue;
			if (dst_key_alg(key) != sig.algorithm ||
			    dst_key_id(key) != sig.keyid ||
			    !(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC ||
			      dst_key_proto(key) == DNS_KEYPROTO_ANY))
			{
				dst_key_free(&key);
				continue;
			}
			result = dns_dnssec_verifymessage(&msgb, msg, key);
			dst_key_free(&key);
			if (result == ISC_R_SUCCESS)
				break;
		}
		if (result == ISC_R_NOMORE)
			result = DNS_R_KEYUNAUTHORIZED;

 freesig:
		if (dns_rdataset_isassociated(&keyset))
			dns_rdataset_disassociate(&keyset);
		dns_rdata_freestruct(&sig);
		return (result);
	}
}

#define INDENT(sp) \
	do { \
		unsigned int __i; \
		dns_masterstyle_flags_t  __flags = dns_master_styleflags(sp); \
		if ((__flags & DNS_STYLEFLAG_INDENT) == 0ULL && \
		    (__flags & DNS_STYLEFLAG_YAML) == 0ULL) \
			break; \
		for (__i = 0; __i < dns_master_indent; __i++) { \
			ADD_STRING(target, dns_master_indentstr); \
		} \
	} while (0)

isc_result_t
dns_message_sectiontotext(dns_message_t *msg, dns_section_t section,
			  const dns_master_style_t *style,
			  dns_messagetextflag_t flags,
			  isc_buffer_t *target) {
	dns_name_t *name, empty_name;
	dns_rdataset_t *rdataset;
	isc_result_t result = ISC_R_SUCCESS;
	bool seensoa = false;
	unsigned int saveindent;
	dns_masterstyle_flags_t sflags;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(target != NULL);
	REQUIRE(VALID_SECTION(section));

	saveindent = dns_master_indent;
	sflags = dns_master_styleflags(style);
	if (ISC_LIST_EMPTY(msg->sections[section]))
		goto cleanup;


	INDENT(style);
	if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
		if (msg->opcode != dns_opcode_update) {
			ADD_STRING(target, sectiontext[section]);
		} else {
			ADD_STRING(target, updsectiontext[section]);
		}
		ADD_STRING(target, "_SECTION:\n");
	} else if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
		ADD_STRING(target, ";; ");
		if (msg->opcode != dns_opcode_update) {
			ADD_STRING(target, sectiontext[section]);
		} else {
			ADD_STRING(target, updsectiontext[section]);
		}
		ADD_STRING(target, " SECTION:\n");
	}

	dns_name_init(&empty_name, NULL);
	result = dns_message_firstname(msg, section);
	if (result != ISC_R_SUCCESS) {
		goto cleanup;
	}
	if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
		dns_master_indent++;
	}
	do {
		name = NULL;
		dns_message_currentname(msg, section, &name);
		for (rdataset = ISC_LIST_HEAD(name->list);
		     rdataset != NULL;
		     rdataset = ISC_LIST_NEXT(rdataset, link)) {
			if (section == DNS_SECTION_ANSWER &&
			    rdataset->type == dns_rdatatype_soa) {
				if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0)
					continue;
				if (seensoa &&
				    (flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0)
					continue;
				seensoa = true;
			}
			if (section == DNS_SECTION_QUESTION) {
				INDENT(style);
				if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
					ADD_STRING(target, "- ");
				} else {
					ADD_STRING(target, ";");
				}
				result = dns_master_questiontotext(name,
								   rdataset,
								   style,
								   target);
			} else {
				result = dns_master_rdatasettotext(name,
								   rdataset,
								   style,
								   target);
			}
			if (result != ISC_R_SUCCESS)
				goto cleanup;
		}
		result = dns_message_nextname(msg, section);
	} while (result == ISC_R_SUCCESS);
	if ((sflags & DNS_STYLEFLAG_YAML) != 0) {
		dns_master_indent--;
	}
	if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
	    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0 &&
	    (sflags & DNS_STYLEFLAG_YAML) == 0)
	{
		INDENT(style);
		ADD_STRING(target, "\n");
	}
	if (result == ISC_R_NOMORE)
		result = ISC_R_SUCCESS;

 cleanup:
	dns_master_indent = saveindent;
	return (result);
}

static isc_result_t
render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) {
	int i;
	char addr[16], addr_text[64];
	uint16_t family;
	uint8_t addrlen, addrbytes, scopelen;
	isc_result_t result;

	/*
	 * Note: This routine needs to handle malformed ECS options.
	 */

	if (isc_buffer_remaininglength(ecsbuf) < 4)
		return (DNS_R_OPTERR);
	family = isc_buffer_getuint16(ecsbuf);
	addrlen = isc_buffer_getuint8(ecsbuf);
	scopelen = isc_buffer_getuint8(ecsbuf);

	addrbytes = (addrlen + 7) / 8;
	if (isc_buffer_remaininglength(ecsbuf) < addrbytes)
		return (DNS_R_OPTERR);

	if (addrbytes > sizeof(addr))
		return (DNS_R_OPTERR);

	memset(addr, 0, sizeof(addr));
	for (i = 0; i < addrbytes; i ++)
		addr[i] = isc_buffer_getuint8(ecsbuf);

	switch (family) {
	case 0:
		if (addrlen != 0U || scopelen != 0U)
			return (DNS_R_OPTERR);
		strlcpy(addr_text, "0", sizeof(addr_text));
		break;
	case 1:
		if (addrlen > 32 || scopelen > 32)
			return (DNS_R_OPTERR);
		inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text));
		break;
	case 2:
		if (addrlen > 128 || scopelen > 128)
			return (DNS_R_OPTERR);
		inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text));
		break;
	default:
		return (DNS_R_OPTERR);
	}

	ADD_STRING(target, ": ");
	ADD_STRING(target, addr_text);
	snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen);
	ADD_STRING(target, addr_text);

	result = ISC_R_SUCCESS;

 cleanup:
	return (result);
}

static isc_result_t
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;
	dns_name_t *name = NULL;
	isc_result_t result = ISC_R_SUCCESS;
	char buf[sizeof("1234567890")];
	uint32_t mbz;
	dns_rdata_t rdata;
	isc_buffer_t optbuf;
	uint16_t optcode, optlen;
	unsigned char *optdata;
	unsigned int saveindent = dns_master_indent;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(target != NULL);
	REQUIRE(VALID_PSEUDOSECTION(section));

	switch (section) {
	case DNS_PSEUDOSECTION_OPT:
		ps = dns_message_getopt(msg);
		if (ps == NULL) {
			goto cleanup;
		}

		INDENT(style);
		ADD_STRING(target, "OPT_PSEUDOSECTION:\n");
		dns_master_indent++;

		INDENT(style);
		ADD_STRING(target, "EDNS:\n");
		dns_master_indent++;

		INDENT(style);
		ADD_STRING(target, "version: ");
		snprintf(buf, sizeof(buf), "%u",
			 (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, "flags:");
		if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0)
			ADD_STRING(target, " do");
		ADD_STRING(target, "\n");
		mbz = ps->ttl & 0xffff;
		mbz &= ~DNS_MESSAGEEXTFLAG_DO;		/* Known Flags. */
		if (mbz != 0) {
			INDENT(style);
			ADD_STRING(target, "MBZ: ");
			snprintf(buf, sizeof(buf), "0x%.4x", mbz);
			ADD_STRING(target, buf);
			ADD_STRING(target, "\n");
		}
		INDENT(style);
		ADD_STRING(target, "udp: ");
		snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
		ADD_STRING(target, buf);
		result = dns_rdataset_first(ps);
		if (result != ISC_R_SUCCESS) {
			result = ISC_R_SUCCESS;
			goto cleanup;
		}

		/*
		 * Print EDNS info, if any.
		 *
		 * WARNING: The option contents may be malformed as
		 * dig +ednsopt=value:<content> does not validity
		 * checking.
		 */
		dns_rdata_init(&rdata);
		dns_rdataset_current(ps, &rdata);

		isc_buffer_init(&optbuf, rdata.data, rdata.length);
		isc_buffer_add(&optbuf, rdata.length);
		while (isc_buffer_remaininglength(&optbuf) != 0) {
			INSIST(isc_buffer_remaininglength(&optbuf) >= 4U);
			optcode = isc_buffer_getuint16(&optbuf);
			optlen = isc_buffer_getuint16(&optbuf);
			INSIST(isc_buffer_remaininglength(&optbuf) >= optlen);

			if (optcode == DNS_OPT_LLQ) {
				INDENT(style);
				if (optlen == 18U) {
					ADD_STRING(target, "LLQ: ");
					result = render_llq(&optbuf, target);
					if (result != ISC_R_SUCCESS) {
						goto cleanup;
					}
					ADD_STRING(target, "\n");
					continue;
				}
				ADD_STRING(target, "LLQ");
			} 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) {
				if (optlen == 4) {
					uint32_t secs;
					secs = isc_buffer_getuint32(&optbuf);
					INDENT(style);
					ADD_STRING(target, "EXPIRE: ");
					snprintf(buf, sizeof(buf), "%u", secs);
					ADD_STRING(target, buf);
					ADD_STRING(target, " (");
					result = dns_ttl_totext(secs,
								true,
								target);
					if (result != ISC_R_SUCCESS)
						goto cleanup;
					ADD_STRING(target, ")\n");
					continue;
				}
				INDENT(style);
				ADD_STRING(target, "EXPIRE");
			} else if (optcode == DNS_OPT_PAD) {
				INDENT(style);
				ADD_STRING(target, "PAD");
			} else if (optcode == DNS_OPT_KEY_TAG) {
				INDENT(style);
				ADD_STRING(target, "KEY-TAG");
				if (optlen > 0U && (optlen % 2U) == 0U) {
					const char *sep = ": ";
					uint16_t id;
					while (optlen > 0U) {
					    id = isc_buffer_getuint16(&optbuf);
					    snprintf(buf, sizeof(buf), "%s%u",
						     sep, id);
					    ADD_STRING(target, buf);
					    sep = ", ";
					    optlen -= 2;
					}
					ADD_STRING(target, "\n");
					continue;
				}
			} else 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);
				ADD_STRING(target, "\n");
			}

			if (optlen != 0) {
				int i;
				ADD_STRING(target, ": ");

				optdata = isc_buffer_current(&optbuf);
				for (i = 0; i < optlen; i++) {
					const char *sep;
					switch (optcode) {
					case DNS_OPT_COOKIE:
						sep = "";
						break;
					default:
						sep = " ";
						break;
					}
					snprintf(buf, sizeof(buf), "%02x%s",
						 optdata[i], sep);
					ADD_STRING(target, buf);
				}

				isc_buffer_forward(&optbuf, optlen);

				if (optcode == DNS_OPT_COOKIE) {
					/*
					 * Valid server cookie?
					 */
					if (msg->cc_ok && optlen >= 16)
						ADD_STRING(target, " (good)");
					/*
					 * Server cookie is not valid but
					 * we had our cookie echoed back.
					 */
					if (msg->cc_ok && optlen < 16)
						ADD_STRING(target, " (echoed)");
					/*
					 * We didn't get our cookie echoed
					 * back.
					 */
					if (msg->cc_bad)
						ADD_STRING(target, " (bad)");
					ADD_STRING(target, "\n");
					continue;
				}

				if (optcode == DNS_OPT_CLIENT_SUBNET) {
					ADD_STRING(target, "\n");
					continue;
				}

				/*
				 * For non-COOKIE options, add a printable
				 * version
				 */
				ADD_STRING(target, "(\"");
				if (isc_buffer_availablelength(target) < optlen)
				{
					result = ISC_R_NOSPACE;
					goto cleanup;
				}
				for (i = 0; i < optlen; i++) {
					if (isprint(optdata[i]))
						isc_buffer_putmem(target,
								  &optdata[i],
								  1);
					else
						isc_buffer_putstr(target, ".");
				}
				ADD_STRING(target, "\")");
			}
			ADD_STRING(target, "\n");
		}
		result = ISC_R_SUCCESS;
		goto cleanup;
	case DNS_PSEUDOSECTION_TSIG:
		ps = dns_message_gettsig(msg, &name);
		if (ps == NULL) {
			result = ISC_R_SUCCESS;
			goto cleanup;
		}
		INDENT(style);
		ADD_STRING(target, "TSIG_PSEUDOSECTION:\n");
		result = dns_master_rdatasettotext(name, ps, style, target);
		ADD_STRING(target, "\n");
		goto cleanup;
	case DNS_PSEUDOSECTION_SIG0:
		ps = dns_message_getsig0(msg, &name);
		if (ps == NULL) {
			result = ISC_R_SUCCESS;
			goto cleanup;
		}
		INDENT(style);
		ADD_STRING(target, "SIG0_PSEUDOSECTION:\n");
		result = dns_master_rdatasettotext(name, ps, style, target);
		if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
		    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
			ADD_STRING(target, "\n");
		goto cleanup;
	}

	result = ISC_R_UNEXPECTED;

 cleanup:
	dns_master_indent = saveindent;
	return (result);
}

isc_result_t
dns_message_pseudosectiontotext(dns_message_t *msg,
				dns_pseudosection_t section,
				const dns_master_style_t *style,
				dns_messagetextflag_t flags,
				isc_buffer_t *target)
{
	dns_rdataset_t *ps = NULL;
	dns_name_t *name = NULL;
	isc_result_t result;
	char buf[sizeof("1234567890")];
	uint32_t mbz;
	dns_rdata_t rdata;
	isc_buffer_t optbuf;
	uint16_t optcode, optlen;
	unsigned char *optdata;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(target != NULL);
	REQUIRE(VALID_PSEUDOSECTION(section));

	if ((dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) != 0)
		return (dns_message_pseudosectiontoyaml(msg, section, style,
							flags, target));
	switch (section) {
	case DNS_PSEUDOSECTION_OPT:
		ps = dns_message_getopt(msg);
		if (ps == NULL)
			return (ISC_R_SUCCESS);
		if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) {
			INDENT(style);
			ADD_STRING(target, ";; OPT PSEUDOSECTION:\n");
		}

		INDENT(style);
		ADD_STRING(target, "; EDNS: version: ");
		snprintf(buf, sizeof(buf), "%u",
			 (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
		ADD_STRING(target, buf);
		ADD_STRING(target, ", flags:");
		if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0)
			ADD_STRING(target, " do");
		mbz = ps->ttl & 0xffff;
		mbz &= ~DNS_MESSAGEEXTFLAG_DO;		/* Known Flags. */
		if (mbz != 0) {
			ADD_STRING(target, "; MBZ: ");
			snprintf(buf, sizeof(buf), "0x%.4x", mbz);
			ADD_STRING(target, buf);
			ADD_STRING(target, ", udp: ");
		} else
			ADD_STRING(target, "; udp: ");
		snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass);
		ADD_STRING(target, buf);

		result = dns_rdataset_first(ps);
		if (result != ISC_R_SUCCESS)
			return (ISC_R_SUCCESS);

		/*
		 * Print EDNS info, if any.
		 *
		 * WARNING: The option contents may be malformed as
		 * dig +ednsopt=value:<content> does not validity
		 * checking.
		 */
		dns_rdata_init(&rdata);
		dns_rdataset_current(ps, &rdata);

		isc_buffer_init(&optbuf, rdata.data, rdata.length);
		isc_buffer_add(&optbuf, rdata.length);
		while (isc_buffer_remaininglength(&optbuf) != 0) {
			INSIST(isc_buffer_remaininglength(&optbuf) >= 4U);
			optcode = isc_buffer_getuint16(&optbuf);
			optlen = isc_buffer_getuint16(&optbuf);
			INSIST(isc_buffer_remaininglength(&optbuf) >= optlen);

			INDENT(style);

			if (optcode == DNS_OPT_LLQ) {
				if (optlen == 18U) {
					ADD_STRING(target, "; LLQ:");
					result = render_llq(&optbuf, target);
					if (result != ISC_R_SUCCESS) {
						return (result);
					}
					ADD_STRING(target, "\n");
					continue;
				}
				ADD_STRING(target, "; LLQ");
			} 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) {
				if (optlen == 4) {
					uint32_t secs;
					secs = isc_buffer_getuint32(&optbuf);
					ADD_STRING(target, "; EXPIRE: ");
					snprintf(buf, sizeof(buf), "%u", secs);
					ADD_STRING(target, buf);
					ADD_STRING(target, " (");
					result = dns_ttl_totext(secs,
								true,
								target);
					if (result != ISC_R_SUCCESS)
						return (result);
					ADD_STRING(target, ")\n");
					continue;
				}
				ADD_STRING(target, "; EXPIRE");
			} else if (optcode == DNS_OPT_PAD) {
				ADD_STRING(target, "; PAD");
			} else if (optcode == DNS_OPT_KEY_TAG) {
				ADD_STRING(target, "; KEY-TAG");
				if (optlen > 0U && (optlen % 2U) == 0U) {
					const char *sep = ": ";
					uint16_t id;
					while (optlen > 0U) {
					    id = isc_buffer_getuint16(&optbuf);
					    snprintf(buf, sizeof(buf), "%s%u",
						     sep, id);
					    ADD_STRING(target, buf);
					    sep = ", ";
					    optlen -= 2;
					}
					ADD_STRING(target, "\n");
					continue;
				}
			} else 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;
				ADD_STRING(target, ": ");

				optdata = isc_buffer_current(&optbuf);
				for (i = 0; i < optlen; i++) {
					const char *sep;
					switch (optcode) {
					case DNS_OPT_COOKIE:
						sep = "";
						break;
					default:
						sep = " ";
						break;
					}
					snprintf(buf, sizeof(buf), "%02x%s",
						 optdata[i], sep);
					ADD_STRING(target, buf);
				}

				isc_buffer_forward(&optbuf, optlen);

				if (optcode == DNS_OPT_COOKIE) {
					/*
					 * Valid server cookie?
					 */
					if (msg->cc_ok && optlen >= 16)
						ADD_STRING(target, " (good)");
					/*
					 * Server cookie is not valid but
					 * we had our cookie echoed back.
					 */
					if (msg->cc_ok && optlen < 16)
						ADD_STRING(target, " (echoed)");
					/*
					 * We didn't get our cookie echoed
					 * back.
					 */
					if (msg->cc_bad)
						ADD_STRING(target, " (bad)");
					ADD_STRING(target, "\n");
					continue;
				}

				if (optcode == DNS_OPT_CLIENT_SUBNET) {
					ADD_STRING(target, "\n");
					continue;
				}

				/*
				 * For non-COOKIE options, add a printable
				 * version
				 */
				ADD_STRING(target, "(\"");
				if (isc_buffer_availablelength(target) < optlen)
					return (ISC_R_NOSPACE);
				for (i = 0; i < optlen; i++) {
					if (isprint(optdata[i]))
						isc_buffer_putmem(target,
								  &optdata[i],
								  1);
					else
						isc_buffer_putstr(target, ".");
				}
				ADD_STRING(target, "\")");
			}
			ADD_STRING(target, "\n");
		}
		return (ISC_R_SUCCESS);
	case DNS_PSEUDOSECTION_TSIG:
		ps = dns_message_gettsig(msg, &name);
		if (ps == NULL)
			return (ISC_R_SUCCESS);
		INDENT(style);
		if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
			ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n");
		result = dns_master_rdatasettotext(name, ps, style, target);
		if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
		    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
			ADD_STRING(target, "\n");
		return (result);
	case DNS_PSEUDOSECTION_SIG0:
		ps = dns_message_getsig0(msg, &name);
		if (ps == NULL)
			return (ISC_R_SUCCESS);
		INDENT(style);
		if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
			ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n");
		result = dns_master_rdatasettotext(name, ps, style, target);
		if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
		    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
			ADD_STRING(target, "\n");
		return (result);
	}
	result = ISC_R_UNEXPECTED;
 cleanup:
	return (result);
}

isc_result_t
dns_message_totext(dns_message_t *msg, const dns_master_style_t *style,
		   dns_messagetextflag_t flags, isc_buffer_t *target)
{
	char buf[sizeof("1234567890")];
	isc_result_t result;

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(target != NULL);

	if (((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) &&
	     (dns_master_styleflags(style) & DNS_STYLEFLAG_YAML))
	{
		INDENT(style);
		ADD_STRING(target, "opcode: ");
		ADD_STRING(target, opcodetext[msg->opcode]);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, "status: ");
		result = dns_rcode_totext(msg->rcode, target);
		if (result != ISC_R_SUCCESS)
			return (result);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, "id: ");
		snprintf(buf, sizeof(buf), "%6u", msg->id);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, "flags:");
		if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
			ADD_STRING(target, " qr");
		if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0)
			ADD_STRING(target, " aa");
		if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0)
			ADD_STRING(target, " tc");
		if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0)
			ADD_STRING(target, " rd");
		if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0)
			ADD_STRING(target, " ra");
		if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0)
			ADD_STRING(target, " ad");
		if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0)
			ADD_STRING(target, " cd");
		ADD_STRING(target, "\n");
		/*
		 * The final unnamed flag must be zero.
		 */
		if ((msg->flags & 0x0040U) != 0) {
			INDENT(style);
			ADD_STRING(target, "MBZ: 0x4");
			ADD_STRING(target, "\n");
		}
		if (msg->opcode != dns_opcode_update) {
			INDENT(style);
			ADD_STRING(target, "QUESTION: ");
		} else {
			ADD_STRING(target, "ZONE: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			 msg->counts[DNS_SECTION_QUESTION]);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		if (msg->opcode != dns_opcode_update) {
			INDENT(style);
			ADD_STRING(target, "ANSWER: ");
		} else {
			INDENT(style);
			ADD_STRING(target, "PREREQ: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			 msg->counts[DNS_SECTION_ANSWER]);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		if (msg->opcode != dns_opcode_update) {
			INDENT(style);
			ADD_STRING(target, "AUTHORITY: ");
		} else {
			INDENT(style);
			ADD_STRING(target, "UPDATE: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			msg->counts[DNS_SECTION_AUTHORITY]);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, "ADDITIONAL: ");
		snprintf(buf, sizeof(buf), "%1u",
			msg->counts[DNS_SECTION_ADDITIONAL]);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
	} else if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) {
		INDENT(style);
		ADD_STRING(target, ";; ->>HEADER<<- opcode: ");
		ADD_STRING(target, opcodetext[msg->opcode]);
		ADD_STRING(target, ", status: ");
		result = dns_rcode_totext(msg->rcode, target);
		if (result != ISC_R_SUCCESS)
			return (result);
		ADD_STRING(target, ", id: ");
		snprintf(buf, sizeof(buf), "%6u", msg->id);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
		INDENT(style);
		ADD_STRING(target, ";; flags:");
		if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0)
			ADD_STRING(target, " qr");
		if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0)
			ADD_STRING(target, " aa");
		if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0)
			ADD_STRING(target, " tc");
		if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0)
			ADD_STRING(target, " rd");
		if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0)
			ADD_STRING(target, " ra");
		if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0)
			ADD_STRING(target, " ad");
		if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0)
			ADD_STRING(target, " cd");
		/*
		 * The final unnamed flag must be zero.
		 */
		if ((msg->flags & 0x0040U) != 0) {
			INDENT(style);
			ADD_STRING(target, "; MBZ: 0x4");
		}
		if (msg->opcode != dns_opcode_update) {
			INDENT(style);
			ADD_STRING(target, "; QUESTION: ");
		} else {
			INDENT(style);
			ADD_STRING(target, "; ZONE: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			 msg->counts[DNS_SECTION_QUESTION]);
		ADD_STRING(target, buf);
		if (msg->opcode != dns_opcode_update) {
			ADD_STRING(target, ", ANSWER: ");
		} else {
			ADD_STRING(target, ", PREREQ: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			 msg->counts[DNS_SECTION_ANSWER]);
		ADD_STRING(target, buf);
		if (msg->opcode != dns_opcode_update) {
			ADD_STRING(target, ", AUTHORITY: ");
		} else {
			ADD_STRING(target, ", UPDATE: ");
		}
		snprintf(buf, sizeof(buf), "%1u",
			msg->counts[DNS_SECTION_AUTHORITY]);
		ADD_STRING(target, buf);
		ADD_STRING(target, ", ADDITIONAL: ");
		snprintf(buf, sizeof(buf), "%1u",
			msg->counts[DNS_SECTION_ADDITIONAL]);
		ADD_STRING(target, buf);
		ADD_STRING(target, "\n");
	}
	result = dns_message_pseudosectiontotext(msg,
						 DNS_PSEUDOSECTION_OPT,
						 style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);

	result = dns_message_sectiontotext(msg, DNS_SECTION_QUESTION,
					   style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);
	result = dns_message_sectiontotext(msg, DNS_SECTION_ANSWER,
					   style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);
	result = dns_message_sectiontotext(msg, DNS_SECTION_AUTHORITY,
					   style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);
	result = dns_message_sectiontotext(msg, DNS_SECTION_ADDITIONAL,
					   style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);

	result = dns_message_pseudosectiontotext(msg,
						 DNS_PSEUDOSECTION_TSIG,
						 style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);

	result = dns_message_pseudosectiontotext(msg,
						 DNS_PSEUDOSECTION_SIG0,
						 style, flags, target);
	if (result != ISC_R_SUCCESS)
		return (result);

 cleanup:
	return (result);
}

isc_region_t *
dns_message_getrawmessage(dns_message_t *msg) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	return (&msg->saved);
}

void
dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order,
			 const void *order_arg)
{
	REQUIRE(DNS_MESSAGE_VALID(msg));
	msg->order = order;
	msg->order_arg = order_arg;
}

void
dns_message_settimeadjust(dns_message_t *msg, int timeadjust) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	msg->timeadjust = timeadjust;
}

int
dns_message_gettimeadjust(dns_message_t *msg) {
	REQUIRE(DNS_MESSAGE_VALID(msg));
	return (msg->timeadjust);
}

isc_result_t
dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) {

	REQUIRE(opcode < 16);

	if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode]))
		return (ISC_R_NOSPACE);
	isc_buffer_putstr(target, opcodetext[opcode]);
	return (ISC_R_SUCCESS);
}

void
dns_message_logpacket(dns_message_t *message, const char *description,
		      isc_logcategory_t *category, isc_logmodule_t *module,
		      int level, isc_mem_t *mctx)
{
	logfmtpacket(message, description, NULL, category, module,
		     &dns_master_style_debug, level, mctx);
}

void
dns_message_logpacket2(dns_message_t *message,
		       const char *description, isc_sockaddr_t *address,
		       isc_logcategory_t *category, isc_logmodule_t *module,
		       int level, isc_mem_t *mctx)
{
	REQUIRE(address != NULL);

	logfmtpacket(message, description, address, category, module,
		     &dns_master_style_debug, level, mctx);
}

void
dns_message_logfmtpacket(dns_message_t *message, const char *description,
			 isc_logcategory_t *category, isc_logmodule_t *module,
			 const dns_master_style_t *style, int level,
			 isc_mem_t *mctx)
{
	logfmtpacket(message, description, NULL, category, module, style,
		     level, mctx);
}

void
dns_message_logfmtpacket2(dns_message_t *message,
			  const char *description, isc_sockaddr_t *address,
			  isc_logcategory_t *category, isc_logmodule_t *module,
			  const dns_master_style_t *style, int level,
			  isc_mem_t *mctx)
{
	REQUIRE(address != NULL);

	logfmtpacket(message, description, address, category, module, style,
		     level, mctx);
}

static void
logfmtpacket(dns_message_t *message, const char *description,
	     isc_sockaddr_t *address, isc_logcategory_t *category,
	     isc_logmodule_t *module, const dns_master_style_t *style,
	     int level, isc_mem_t *mctx)
{
	char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
	const char *newline = "\n";
	const char *space = " ";
	isc_buffer_t buffer;
	char *buf = NULL;
	int len = 1024;
	isc_result_t result;

	if (! isc_log_wouldlog(dns_lctx, level))
		return;

	/*
	 * Note that these are multiline debug messages.  We want a newline
	 * to appear in the log after each message.
	 */

	if (address != NULL)
		isc_sockaddr_format(address, addrbuf, sizeof(addrbuf));
	else
		newline = space = "";

	do {
		buf = isc_mem_get(mctx, len);
		if (buf == NULL)
			break;
		isc_buffer_init(&buffer, buf, len);
		result = dns_message_totext(message, style, 0, &buffer);
		if (result == ISC_R_NOSPACE) {
			isc_mem_put(mctx, buf, len);
			len += 1024;
		} else if (result == ISC_R_SUCCESS)
			isc_log_write(dns_lctx, category, module, level,
				      "%s%s%s%s%.*s", description, space,
				      addrbuf, newline,
				      (int)isc_buffer_usedlength(&buffer),
				      buf);
	} while (result == ISC_R_NOSPACE);

	if (buf != NULL)
		isc_mem_put(mctx, buf, len);
}

isc_result_t
dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp,
		     unsigned int version, uint16_t udpsize,
		     unsigned int flags, dns_ednsopt_t *ednsopts, size_t count)
{
	dns_rdataset_t *rdataset = NULL;
	dns_rdatalist_t *rdatalist = NULL;
	dns_rdata_t *rdata = NULL;
	isc_result_t result;
	unsigned int len = 0, i;

	REQUIRE(DNS_MESSAGE_VALID(message));
	REQUIRE(rdatasetp != NULL && *rdatasetp == NULL);

	result = dns_message_gettemprdatalist(message, &rdatalist);
	if (result != ISC_R_SUCCESS)
		return (result);
	result = dns_message_gettemprdata(message, &rdata);
	if (result != ISC_R_SUCCESS)
		goto cleanup;
	result = dns_message_gettemprdataset(message, &rdataset);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	rdatalist->type = dns_rdatatype_opt;

	/*
	 * Set Maximum UDP buffer size.
	 */
	rdatalist->rdclass = udpsize;

	/*
	 * Set EXTENDED-RCODE and Z to 0.
	 */
	rdatalist->ttl = (version << 16);
	rdatalist->ttl |= (flags & 0xffff);

	/*
	 * Set EDNS options if applicable
	 */
	if (count != 0U) {
		isc_buffer_t *buf = NULL;
		for (i = 0; i < count; i++)
			len += ednsopts[i].length + 4;

		if (len > 0xffffU) {
			result = ISC_R_NOSPACE;
			goto cleanup;
		}

		result = isc_buffer_allocate(message->mctx, &buf, len);
		if (result != ISC_R_SUCCESS)
			goto cleanup;

		for (i = 0; i < count; i++)  {
			isc_buffer_putuint16(buf, ednsopts[i].code);
			isc_buffer_putuint16(buf, ednsopts[i].length);
			if (ednsopts[i].length != 0) {
				isc_buffer_putmem(buf, ednsopts[i].value,
						  ednsopts[i].length);
			}
		}
		rdata->data = isc_buffer_base(buf);
		rdata->length = len;
		dns_message_takebuffer(message, &buf);
	} else {
		rdata->data = NULL;
		rdata->length = 0;
	}

	rdata->rdclass = rdatalist->rdclass;
	rdata->type = rdatalist->type;
	rdata->flags = 0;

	ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
	result = dns_rdatalist_tordataset(rdatalist, rdataset);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

	*rdatasetp = rdataset;
	return (ISC_R_SUCCESS);

 cleanup:
	if (rdata != NULL)
		dns_message_puttemprdata(message, &rdata);
	if (rdataset != NULL)
		dns_message_puttemprdataset(message, &rdataset);
	if (rdatalist != NULL)
		dns_message_puttemprdatalist(message, &rdatalist);
	return (result);
}

void
dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {

	REQUIRE(DNS_MESSAGE_VALID(msg));
	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE);
	REQUIRE(msg->state == DNS_SECTION_ANY);
	REQUIRE(msg->rdclass_set == 0);

	msg->rdclass = rdclass;
	msg->rdclass_set = 1;
}
