root/usr.bin/dig/lib/dns/message.c
/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: message.c,v 1.22 2024/12/27 09:04:48 florian Exp $ */

/*! \file */

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

#include <sys/socket.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include <isc/buffer.h>
#include <isc/util.h>

#include <dns/log.h>
#include <dns/masterdump.h>
#include <dns/message.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/tsig.h>
#include <dns/ttl.h>

#define DNS_MESSAGE_OPCODE_MASK         0x7800U
#define DNS_MESSAGE_OPCODE_SHIFT        11
#define DNS_MESSAGE_RCODE_MASK          0x000fU
#define DNS_MESSAGE_FLAG_MASK           0x8ff0U
#define DNS_MESSAGE_EDNSRCODE_MASK      0xff000000U

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

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

/*%
 * 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"
};

/*%
 * "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(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 *);

/*
 * 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(unsigned int sizeof_type,
                  unsigned int count)
{
        dns_msgblock_t *block;
        unsigned int length;

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

        block = malloc(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;
}

/*
 * 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(&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(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(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(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->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->query.base = NULL;
        m->query.length = 0;
        m->free_query = 0;
        m->saved.base = NULL;
        m->saved.length = 0;
        m->free_saved = 0;
        m->sitok = 0;
        m->sitbad = 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);
                                free(rds);
                                rds = next_rds;
                        }
                        if (dns_name_dynamic(name))
                                dns_name_free(name);
                        free(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);
                free(msg->opt);
                msg->opt = NULL;
                msg->sitok = 0;
                msg->sitbad = 0;
        }
}

static void
msgresetsigs(dns_message_t *msg, int 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));
                if (replying) {
                        INSIST(msg->querytsig == NULL);
                        msg->querytsig = msg->tsig;
                } else {
                        dns_rdataset_disassociate(msg->tsig);
                        free(msg->tsig);
                        if (msg->querytsig != NULL) {
                                dns_rdataset_disassociate(msg->querytsig);
                                free(msg->querytsig);
                        }
                }
                if (dns_name_dynamic(msg->tsigname))
                        dns_name_free(msg->tsigname);
                free(msg->tsigname);
                msg->tsig = NULL;
                msg->tsigname = NULL;
        } else if (msg->querytsig != NULL && !replying) {
                dns_rdataset_disassociate(msg->querytsig);
                free(msg->querytsig);
                msg->querytsig = NULL;
        }
        if (msg->sig0 != NULL) {
                INSIST(dns_rdataset_isassociated(msg->sig0));
                dns_rdataset_disassociate(msg->sig0);
                free(msg->sig0);
                if (msg->sig0name != NULL) {
                        if (dns_name_dynamic(msg->sig0name))
                                dns_name_free(msg->sig0name);
                        free(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, int 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, 0);

        /*
         * 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);
                free(msgblock);
                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);
                free(msgblock);
                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);
                free(msgblock);
                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)
                        free(msg->query.base);
                msg->query.base = NULL;
                msg->query.length = 0;
        }

        if (msg->saved.base != NULL) {
                if (msg->free_saved != 0)
                        free(msg->saved.base);
                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);
}

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(unsigned int intent, dns_message_t **msgp)
{
        dns_message_t *m;
        isc_result_t result;
        isc_buffer_t *dynbuf;
        unsigned int i;

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

        m = malloc(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->from_to_wire = intent;
        msginit(m);

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

        ISC_LIST_INIT(m->scratchpad);
        ISC_LIST_INIT(m->cleanup);
        ISC_LIST_INIT(m->rdatas);
        ISC_LIST_INIT(m->rdatalists);
        ISC_LIST_INIT(m->offsets);
        ISC_LIST_INIT(m->freerdata);
        ISC_LIST_INIT(m->freerdatalist);

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

        dynbuf = NULL;
        result = isc_buffer_allocate(&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);
        }
        free(m);

        return (ISC_R_NOMEMORY);
}

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

        REQUIRE(msgp != NULL);

        msg = *msgp;
        *msgp = NULL;

        msgreset(msg, 1);
        free(msg);
}

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 (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);  /* Cannot get here... */
        return (ISC_R_UNEXPECTED);
}

static isc_result_t
getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
         dns_rdataclass_t rdclass, dns_rdatatype_t rdtype,
         unsigned int rdatalen, dns_rdata_t *rdata)
{
        isc_buffer_t *scratch;
        isc_result_t result;
        unsigned int tries;
        unsigned int trysize;

        scratch = currentbuffer(msg);

        isc_buffer_setactive(source, rdatalen);

        /*
         * First try:  use current buffer.
         * Second try:  allocate a new buffer of size
         *     max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen)
         *     (the data will fit if it was not more than 50% compressed)
         * Subsequent tries: double buffer size on each try.
         */
        tries = 0;
        trysize = 0;
        /* XXX possibly change this to a while (tries < 2) loop */
        for (;;) {
                result = dns_rdata_fromwire(rdata, rdclass, rdtype,
                                            source, dctx, 0,
                                            scratch);

                if (result == ISC_R_NOSPACE) {
                        if (tries == 0) {
                                trysize = 2 * rdatalen;
                                if (trysize < SCRATCHPAD_SIZE)
                                        trysize = SCRATCHPAD_SIZE;
                        } else {
                                INSIST(trysize != 0);
                                if (trysize >= 65535)
                                        return (ISC_R_NOSPACE);
                                        /* XXX DNS_R_RRTOOLONG? */
                                trysize *= 2;
                        }
                        tries++;
                        result = newbuffer(msg, trysize);
                        if (result != ISC_R_SUCCESS)
                                return (result);

                        scratch = currentbuffer(msg);
                } else {
                        return (result);
                }
        }
}

#define DO_FORMERR                                      \
        do {                                            \
                if (best_effort)                        \
                        seen_problem = 1;       \
                else {                                  \
                        result = DNS_R_FORMERR;         \
                        goto cleanup;                   \
                }                                       \
        } while (0)

static isc_result_t
getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
             unsigned int options)
{
        isc_region_t r;
        unsigned int count;
        dns_name_t *name;
        dns_name_t *name2;
        dns_offsets_t *offsets;
        dns_rdataset_t *rdataset;
        dns_rdatalist_t *rdatalist;
        isc_result_t result;
        dns_rdatatype_t rdtype;
        dns_rdataclass_t rdclass;
        dns_namelist_t *section;
        int free_name;
        int best_effort;
        int seen_problem;

        section = &msg->sections[DNS_SECTION_QUESTION];

        best_effort = options & DNS_MESSAGEPARSE_BESTEFFORT;
        seen_problem = 0;

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

        for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
                name = malloc(sizeof(dns_name_t));
                if (name == NULL)
                        return (ISC_R_NOMEMORY);
                free_name = 1;

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

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

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

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

                /*
                 * 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_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_FORMERR;

                /*
                 * Allocate a new rdatalist.
                 */
                rdatalist = newrdatalist(msg);
                if (rdatalist == NULL) {
                        result = ISC_R_NOMEMORY;
                        goto cleanup;
                }
                rdataset =  malloc(sizeof(dns_rdataset_t));
                if (rdataset == NULL) {
                        result = ISC_R_NOMEMORY;
                        goto cleanup;
                }

                /*
                 * Convert rdatalist to rdataset, and attach the latter to
                 * the name.
                 */
                rdatalist->type = rdtype;
                rdatalist->covers = 0;
                rdatalist->rdclass = rdclass;
                rdatalist->ttl = 0;
                ISC_LIST_INIT(rdatalist->rdata);

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

                rdataset->attributes |= DNS_RDATASETATTR_QUESTION;

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

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

 cleanup:
        if (rdataset != NULL) {
                INSIST(!dns_rdataset_isassociated(rdataset));
                free(rdataset);
        }
        if (free_name)
                free(name);

        return (result);
}

static int
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 (0);
}

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_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;
        int free_name = 0, free_rdataset = 0;
        int best_effort, seen_problem;
        int issigzero;

        best_effort = options & DNS_MESSAGEPARSE_BESTEFFORT;
        seen_problem = 0;

        section = &msg->sections[sectionid];

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

                name = malloc(sizeof(dns_name_t));
                if (name == NULL)
                        return (ISC_R_NOMEMORY);
                free_name = 1;

                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_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_FORMERR;

                /*
                 * Special type handling for TSIG, OPT, and TKEY.
                 */
                if (rdtype == dns_rdatatype_tsig) {
                        /*
                         * If it is a tsig, verify that it is in the
                         * additional data section.
                         */
                        if (sectionid != DNS_SECTION_ADDITIONAL ||
                            rdclass != dns_rdataclass_any ||
                            count != msg->counts[sectionid]  - 1)
                                DO_FORMERR;
                        msg->sigstart = recstart;
                } 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_FORMERR;
                } else if (rdtype == dns_rdatatype_tkey) {
                        /*
                         * A TKEY must be in the additional section if this
                         * is a query, and the answer section if this is a
                         * response.  Unless it's a Win2000 client.
                         *
                         * Its class is ignored.
                         */
                        dns_section_t tkeysection;

                        if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0)
                                tkeysection = DNS_SECTION_ADDITIONAL;
                        else
                                tkeysection = DNS_SECTION_ANSWER;
                        if (sectionid != tkeysection &&
                            sectionid != DNS_SECTION_ANSWER)
                                DO_FORMERR;
                }

                /*
                 * ... now get ttl and rdatalen, and check buffer.
                 */
                ttl = isc_buffer_getuint32(source);
                rdatalen = isc_buffer_getuint16(source);
                r.length -= (2 + 2 + 4 + 2);
                if (r.length < rdatalen) {
                        result = ISC_R_UNEXPECTEDEND;
                        goto cleanup;
                }

                /*
                 * Read the rdata from the wire format.  Interpret the
                 * rdata according to its actual class, even if it had a
                 * DynDNS meta-class in the packet (unless this is a TSIG).
                 * Then put the meta-class back into the finished rdata.
                 */
                rdata = newrdata(msg);
                if (rdata == NULL) {
                        result = ISC_R_NOMEMORY;
                        goto cleanup;
                }
                if (msg->opcode == dns_opcode_update &&
                    update(sectionid, rdclass)) {
                        if (rdatalen != 0) {
                                result = DNS_R_FORMERR;
                                goto cleanup;
                        }
                        /*
                         * When the rdata is empty, the data pointer is
                         * never dereferenced, but it must still be non-NULL.
                         * Casting 1 rather than "" avoids warnings about
                         * discarding the const attribute of a string,
                         * for compilers that would warn about such things.
                         */
                        rdata->data = (unsigned char *)1;
                        rdata->length = 0;
                        rdata->rdclass = rdclass;
                        rdata->type = rdtype;
                        rdata->flags = DNS_RDATA_UPDATE;
                        result = ISC_R_SUCCESS;
                } else if (rdclass == dns_rdataclass_none &&
                           msg->opcode == dns_opcode_update &&
                           sectionid == DNS_SECTION_UPDATE) {
                        result = getrdata(source, msg, dctx, msg->rdclass,
                                          rdtype, rdatalen, rdata);
                } else
                        result = getrdata(source, msg, dctx, rdclass,
                                          rdtype, rdatalen, rdata);
                if (result != ISC_R_SUCCESS)
                        goto cleanup;
                rdata->rdclass = rdclass;
                issigzero = 0;
                if (rdtype == dns_rdatatype_rrsig &&
                    rdata->flags == 0) {
                        covers = dns_rdata_covers(rdata);
                        if (covers == 0)
                                DO_FORMERR;
                } else if (rdtype == dns_rdatatype_sig /* SIG(0) */ &&
                           rdata->flags == 0) {
                        covers = dns_rdata_covers(rdata);
                        if (covers == 0) {
                                if (sectionid != DNS_SECTION_ADDITIONAL ||
                                    count != msg->counts[sectionid]  - 1)
                                        DO_FORMERR;
                                msg->sigstart = recstart;
                                issigzero = 1;
                        } else {
                                if (msg->rdclass != dns_rdataclass_any &&
                                    msg->rdclass != rdclass)
                                        DO_FORMERR;
                        }
                } else
                        covers = 0;

                /*
                 * Check the ownername of NSEC3 records
                 */
                if (rdtype == dns_rdatatype_nsec3 &&
                    !dns_rdata_checkowner_nsec3(name, msg->rdclass, rdtype,
                                          0)) {
                        result = DNS_R_BADOWNERNAME;
                        goto cleanup;
                }

                if (rdtype != dns_rdatatype_opt &&
                    rdtype != dns_rdatatype_tsig && !issigzero) {
                        ISC_LIST_APPEND(*section, name, link);
                        free_name = 0;
                }

                rdataset = malloc(sizeof(dns_rdataset_t));
                if (rdataset == NULL) {
                        result = ISC_R_NOMEMORY;
                        goto cleanup;
                }
                free_rdataset = 1;

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

                rdatalist->type = rdtype;
                rdatalist->covers = covers;
                rdatalist->rdclass = rdclass;
                rdatalist->ttl = ttl;
                ISC_LIST_INIT(rdatalist->rdata);

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

                if (rdtype != dns_rdatatype_opt &&
                    rdtype != dns_rdatatype_tsig &&
                    !issigzero)
                {
                        ISC_LIST_APPEND(name->list, rdataset, link);
                        free_rdataset = 0;
                }

                /*
                 * 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->ttl = ttl;

                /* Append this rdata to the rdataset. */
                dns_rdatalist_fromrdataset(rdataset, &rdatalist);
                ISC_LIST_APPEND(rdatalist->rdata, rdata, link);

                /*
                 * If this is an OPT, SIG(0) or TSIG record, remember it.
                 * Also, set the extended rcode for TSIG.
                 *
                 * Note msg->opt, msg->sig0 and msg->tsig will only be
                 * already set if best-effort parsing is enabled otherwise
                 * there will only be at most one of each.
                 */
                if (rdtype == dns_rdatatype_opt && msg->opt == NULL) {
                        dns_rcode_t ercode;

                        msg->opt = rdataset;
                        rdataset = NULL;
                        free_rdataset = 0;
                        ercode = (dns_rcode_t)
                                ((msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK)
                                 >> 20);
                        msg->rcode |= ercode;
                        free(name);
                        free_name = 0;
                } else if (issigzero && msg->sig0 == NULL) {
                        msg->sig0 = rdataset;
                        msg->sig0name = name;
                        rdataset = NULL;
                        free_rdataset = 0;
                        free_name = 0;
                } else if (rdtype == dns_rdatatype_tsig && msg->tsig == NULL) {
                        msg->tsig = rdataset;
                        msg->tsigname = name;
                        /* Windows doesn't like TSIG names to be compressed. */
                        msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
                        rdataset = NULL;
                        free_rdataset = 0;
                        free_name = 0;
                }

                if (seen_problem) {
                        if (free_name)
                                free(name);
                        if (free_rdataset)
                                free(rdataset);
                        free_name = free_rdataset = 0;
                }
                INSIST(!free_name);
                INSIST(!free_rdataset);
        }

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

 cleanup:
        if (free_name)
                free(name);
        if (free_rdataset)
                free(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;
        int seen_problem;
        int ignore_tc;

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

        seen_problem = 0;
        ignore_tc = options & DNS_MESSAGEPARSE_IGNORETRUNCATION;

        origsource = *source;

        msg->header_ok = 0;
        msg->question_ok = 0;

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

        msg->id = isc_buffer_getuint16(source);
        tmpflags = isc_buffer_getuint16(source);
        msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK)
                       >> DNS_MESSAGE_OPCODE_SHIFT);
        msg->rcode = (dns_rcode_t)(tmpflags & DNS_MESSAGE_RCODE_MASK);
        msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK);
        msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source);
        msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source);
        msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source);
        msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source);

        msg->header_ok = 1;
        msg->state = DNS_SECTION_QUESTION;

        /*
         * -1 means no EDNS.
         */
        dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);

        dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14);

        ret = getquestions(source, msg, &dctx, options);
        if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
                goto truncated;
        if (ret == DNS_R_RECOVERABLE) {
                seen_problem = 1;
                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 = 1;
                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 = 1;
                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 = 1;
                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:
        isc_buffer_usedregion(&origsource, &msg->saved);

        if (ret == ISC_R_UNEXPECTEDEND && ignore_tc)
                return (DNS_R_RECOVERABLE);
        if (seen_problem)
                return (DNS_R_RECOVERABLE);
        return (ISC_R_SUCCESS);
}

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

        REQUIRE(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);
}

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

        msg->reserved -= space;
}

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

        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 int
wrong_priority(dns_rdataset_t *rds, int pass) {
        int pass_needed;

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

        switch (rds->type) {
        case dns_rdatatype_a:
        case dns_rdatatype_aaaa:
                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 (0);

        return (1);
}

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 *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, 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)
{
        dns_namelist_t *section;
        dns_name_t *name, *next_name;
        dns_rdataset_t *rdataset, *next_rdataset;
        unsigned int count, total;
        isc_result_t result;
        isc_buffer_t st; /* for rollbacks */
        int pass;

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

        section = &msg->sections[sectionid];

        if (sectionid == DNS_SECTION_ADDITIONAL)
                pass = 3;
        else
                pass = 1;

        /*
         * 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;

        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 ((sectionid == DNS_SECTION_ADDITIONAL)
                                    && wrong_priority(rdataset, pass))
                                        goto next;

                                st = *(msg->buffer);

                                count = 0;
                                result = dns_rdataset_towiresorted(
                                                  rdataset,
                                                  name,
                                                  msg->cctx,
                                                  msg->buffer,
                                                  &count);

                                total += count;

                                /*
                                 * If out of space, record stats on what we
                                 * rendered so far, and return that status.
                                 *
                                 */
                                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 ((sectionid == DNS_SECTION_ANSWER ||
                                     sectionid == DNS_SECTION_AUTHORITY))
                                        msg->flags &= ~DNS_MESSAGEFLAG_AD;

                                rdataset->attributes |=
                                        DNS_RDATASETATTR_RENDERED;

                        next:
                                rdataset = next_rdataset;
                        }

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

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

        return (ISC_R_SUCCESS);
}

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

        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(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->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);
                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, &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, &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(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(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(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(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 (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_addname(dns_message_t *msg, dns_name_t *name,
                    dns_section_t section)
{
        REQUIRE(msg != NULL);
        REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
        REQUIRE(name != NULL);
        REQUIRE(VALID_NAMED_SECTION(section));

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

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

        UNUSED(msg);

        *item = malloc(sizeof(dns_name_t));
        if (*item == NULL)
                return (ISC_R_NOMEMORY);
        dns_name_init(*item, NULL);

        return (ISC_R_SUCCESS);
}

isc_result_t
dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) {
        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(item != NULL && *item == NULL);

        UNUSED(msg);

        *item = malloc(sizeof(dns_rdataset_t));
        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(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(item != NULL && *item != NULL);

        UNUSED(msg);

        if (dns_name_dynamic(*item))
                dns_name_free(*item);
        free(*item);
        *item = NULL;
}

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

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

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

        UNUSED(msg);

        free(*item);
        *item = NULL;
}

void
dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) {
        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);
}

dns_rdataset_t *
dns_message_getopt(dns_message_t *msg) {

        /*
         * Get the OPT record for '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(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(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(msg->state == DNS_SECTION_ANY);

        if (key == NULL && msg->tsigkey != NULL) {
                if (msg->sig_reserved != 0) {
                        dns_message_renderrelease(msg, msg->sig_reserved);
                        msg->sig_reserved = 0;
                }
                dns_tsigkey_detach(&msg->tsigkey);
        }
        if (key != NULL) {
                REQUIRE(msg->tsigkey == NULL);
                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'
         */
        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(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(&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_buffer_t **querytsig) {
        isc_result_t result;
        dns_rdata_t rdata = DNS_RDATA_INIT;
        isc_region_t r;

        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(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(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);
}

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

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

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;
        int seensoa = 0;

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

        if (ISC_LIST_EMPTY(msg->sections[section]))
                return (ISC_R_SUCCESS);

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

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

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;

        /*
         * 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);
        return (ISC_R_SUCCESS);
}

static const char *
ede_info_code2str(uint16_t info_code)
{
        if (info_code > 49151)
                return "Private Use";

        switch (info_code) {
        case 0:
                return "Other Error";
        case 1:
                return "Unsupported DNSKEY Algorithm";
        case 2:
                return "Unsupported DS Digest Type";
        case 3:
                return "Stale Answer";
        case 4:
                return "Forged Answer";
        case 5:
                return "DNSSEC Indeterminate";
        case 6:
                return "DNSSEC Bogus";
        case 7:
                return "Signature Expired";
        case 8:
                return "Signature Not Yet Valid";
        case 9:
                return "DNSKEY Missing";
        case 10:
                return "RRSIGs Missing";
        case 11:
                return "No Zone Key Bit Set";
        case 12:
                return "NSEC Missing";
        case 13:
                return "Cached Error";
        case 14:
                return "Not Ready";
        case 15:
                return "Blocked";
        case 16:
                return "Censored";
        case 17:
                return "Filtered";
        case 18:
                return "Prohibited";
        case 19:
                return "Stale NXDomain Answer";
        case 20:
                return "Not Authoritative";
        case 21:
                return "Not Supported";
        case 22:
                return "No Reachable Authority";
        case 23:
                return "Network Error";
        case 24:
                return "Invalid Data";
        default:
                return "Unassigned";
        }
}

static const char *
zoneversion_zone(const char *zone, int labelcount)
{
        size_t pos;

        if (zone == NULL || labelcount == 0)
                return ".";

        pos = strlen(zone);
        if (pos == 0)
                return ".";

        pos--; /* go to last char in string */
        if (zone[pos] == '.')
                pos--; /* the labelcount does not count the empty root label */

        for (; pos > 0; pos--) {
                if (zone[pos] == '.') {
                        labelcount--;

                        if (labelcount == 0) {
                                pos++;
                                break;
                        }
                }
        }

        return (zone + pos);
}

isc_result_t
dns_message_pseudosectiontotext(dns_message_t *msg,
                                dns_pseudosection_t section,
                                const dns_master_style_t *style,
                                dns_messagetextflag_t flags,
                                const char *textname,
                                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(target != NULL);
        REQUIRE(VALID_PSEUDOSECTION(section));

        switch (section) {
        case DNS_PSEUDOSECTION_OPT:
                ps = dns_message_getopt(msg);
                if (ps == NULL)
                        return (ISC_R_SUCCESS);
                if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
                        ADD_STRING(target, ";; OPT PSEUDOSECTION:\n");
                ADD_STRING(target, "; EDNS: version: ");
                snprintf(buf, sizeof(buf), "%u",
                         (unsigned int)((ps->ttl & 0x00ff0000) >> 16));
                ADD_STRING(target, buf);
                ADD_STRING(target, ", flags:");
                if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0)
                        ADD_STRING(target, " do");
                mbz = ps->ttl & 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);

                        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,
                                                                1,
                                                                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_EDE) {
                                uint16_t info_code;
                                ADD_STRING(target, "; EDE");
                                if (optlen >= 2) {
                                        info_code =
                                            isc_buffer_getuint16(&optbuf);
                                        optlen -= 2;
                                        snprintf(buf, sizeof(buf), ": %u (",
                                            info_code);
                                        ADD_STRING(target, buf);
                                        ADD_STRING(target,
                                            ede_info_code2str(info_code));
                                        ADD_STRING(target, ")");
                                }
                        } else if (optcode == DNS_OPT_ZONEVERSION) {
                                int i;

                                ADD_STRING(target, "; ZONEVERSION: ");
                                optdata = isc_buffer_current(&optbuf);
                                for (i = 0; i < optlen; i++) {
                                        snprintf(buf, sizeof(buf), "%02x ",
                                                 optdata[i]);
                                        ADD_STRING(target, buf);
                                }

                                if (optlen >= 2) {
                                        uint8_t labelcount, type;
                                        const char *zone;

                                        labelcount =
                                            isc_buffer_getuint8(&optbuf);
                                        optlen -= 1;
                                        type = isc_buffer_getuint8(&optbuf);
                                        optlen -= 1;
                                        zone = zoneversion_zone(textname,
                                            labelcount);

                                        if (type == 0 && optlen == 4) {
                                                uint32_t serial;

                                                serial = isc_buffer_getuint32(
                                                    &optbuf);
                                                optlen -= 4;
                                                ADD_STRING(target,
                                                    "(\"SOA-SERIAL: ");
                                                snprintf(buf, sizeof(buf), "%u",
                                                    serial);
                                                ADD_STRING(target, buf);
                                                ADD_STRING(target, " (");
                                                ADD_STRING(target, zone);
                                                ADD_STRING(target, ")");
                                                ADD_STRING(target, "\")");
                                        }
                                }
                        } 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) {
                                        if (msg->sitok)
                                                ADD_STRING(target, " (good)");
                                        if (msg->sitbad)
                                                ADD_STRING(target, " (bad)");
                                        ADD_STRING(target, "\n");
                                        continue;
                                }

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

                                /*
                                 * For non-SIT 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);
                if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
                        ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n");
                result = dns_master_rdatasettotext(name, ps, style, target);
                if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
                    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
                        ADD_STRING(target, "\n");
                return (result);
        case DNS_PSEUDOSECTION_SIG0:
                ps = dns_message_getsig0(msg, &name);
                if (ps == NULL)
                        return (ISC_R_SUCCESS);
                if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
                        ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n");
                result = dns_master_rdatasettotext(name, ps, style, target);
                if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 &&
                    (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
                        ADD_STRING(target, "\n");
                return (result);
        }
        return (ISC_R_UNEXPECTED);
}

isc_result_t
dns_message_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(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(&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);
}