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

/*
 * Copyright (C) 2004 - 2015 Nominum, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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.
 */

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

#define ISC_BUFFER_USEINLINE

#include <isc/base64.h>
#include <isc/buffer.h>
#include <isc/hmacmd5.h>
#include <isc/hmacsha.h>
#include <isc/lex.h>
#include <isc/mem.h>
#include <isc/region.h>
#include <isc/result.h>
#include <isc/util.h>

#include <dns/callbacks.h>
#include <dns/fixedname.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdatatype.h>
#include <dns/ttl.h>

#include "dns.h"
#include "log.h"
#include "opt.h"

#define WHITESPACE			" \t\n"

#define MAX_RDATA_LENGTH		65535
#define EDNSLEN				11

const char *perf_dns_rcode_strings[] = {
	"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
	"NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET",
	"NXRRSET", "NOTAUTH", "NOTZONE", "rcode11",
	"rcode12", "rcode13", "rcode14", "rcode15"
};

#define TSIG_HMACMD5_NAME		"\010hmac-md5\007sig-alg\003reg\003int"
#define TSIG_HMACSHA1_NAME		"\011hmac-sha1"
#define TSIG_HMACSHA224_NAME		"\013hmac-sha224"
#define TSIG_HMACSHA256_NAME		"\013hmac-sha256"
#define TSIG_HMACSHA384_NAME		"\013hmac-sha384"
#define TSIG_HMACSHA512_NAME		"\013hmac-sha512"

typedef enum {
	TSIG_HMACMD5,
	TSIG_HMACSHA1,
	TSIG_HMACSHA224,
	TSIG_HMACSHA256,
	TSIG_HMACSHA384,
	TSIG_HMACSHA512
} hmac_type_t;

typedef union {
	isc_hmacmd5_t hmacmd5;
	isc_hmacsha1_t hmacsha1;
	isc_hmacsha224_t hmacsha224;
	isc_hmacsha256_t hmacsha256;
	isc_hmacsha384_t hmacsha384;
	isc_hmacsha512_t hmacsha512;
} hmac_ctx_t;

struct perf_dnstsigkey {
	isc_mem_t *mctx;
	isc_constregion_t alg;
	hmac_type_t hmactype;
	unsigned int digestlen;
	dns_fixedname_t fname;
	dns_name_t *name;
	unsigned char secretdata[256];
	isc_buffer_t secret;
};

struct perf_dnsctx {
	isc_mem_t *mctx;
	dns_compress_t compress;
	isc_lex_t *lexer;
};

perf_dnsctx_t *
perf_dns_createctx(isc_boolean_t updates)
{
	isc_mem_t *mctx;
	perf_dnsctx_t *ctx;
	isc_result_t result;

	if (!updates)
		return NULL;

	mctx = NULL;
	result = isc_mem_create(0, 0, &mctx);
	if (result != ISC_R_SUCCESS)
		perf_log_fatal("creating memory context: %s",
			       isc_result_totext(result));

	ctx = isc_mem_get(mctx, sizeof(*ctx));
	if (ctx == NULL)
		perf_log_fatal("out of memory");

	memset(ctx, 0, sizeof(*ctx));
	ctx->mctx = mctx;

	result = dns_compress_init(&ctx->compress, 0, ctx->mctx);
	if (result != ISC_R_SUCCESS) {
		perf_log_fatal("creating compression context: %s",
			       isc_result_totext(result));
	}
	dns_compress_setmethods(&ctx->compress, DNS_COMPRESS_GLOBAL14);

	result = isc_lex_create(ctx->mctx, 1024, &ctx->lexer);
	if (result != ISC_R_SUCCESS) {
		perf_log_fatal("creating lexer: %s",
			       isc_result_totext(result));
	}

	return (ctx);
}

void
perf_dns_destroyctx(perf_dnsctx_t **ctxp)
{
	perf_dnsctx_t *ctx;
	isc_mem_t *mctx;

	INSIST(ctxp != NULL);
	ctx = *ctxp;
	*ctxp = NULL;

	if (ctx == NULL)
		return;

	mctx = ctx->mctx;
	isc_lex_destroy(&ctx->lexer);
	dns_compress_invalidate(&ctx->compress);
	isc_mem_put(mctx, ctx, sizeof(*ctx));
	isc_mem_destroy(&mctx);
}

static isc_result_t
name_fromstring(dns_name_t *name, dns_name_t *origin,
		const char *str, unsigned int len,
		isc_buffer_t *target, const char *type)
{
	isc_buffer_t buffer;
	isc_result_t result;

	isc_buffer_init(&buffer, str, len);
	isc_buffer_add(&buffer, len);
	result = dns_name_fromtext(name, &buffer, origin, 0, target);
	if (result != ISC_R_SUCCESS)
		perf_log_warning("invalid %s name: %.*s", type, (int)len, str);
	return result;
}

#define SET_KEY(key, type) do {					\
	(key)->alg.base = TSIG_HMAC ## type ## _NAME;		\
	(key)->alg.length = sizeof(TSIG_HMAC ## type ## _NAME);	\
	(key)->hmactype = TSIG_HMAC ## type;			\
	(key)->digestlen = ISC_ ## type ## _DIGESTLENGTH;	\
	} while (0)

perf_dnstsigkey_t *
perf_dns_parsetsigkey(const char *arg, isc_mem_t *mctx)
{
	perf_dnstsigkey_t *tsigkey;
	const char *sep1, *sep2, *alg, *name, *secret;
	int alglen, namelen;
	isc_result_t result;

	tsigkey = isc_mem_get(mctx, sizeof (*tsigkey));
	if (tsigkey == NULL)
		perf_log_fatal("out of memory");
	memset(tsigkey, 0, sizeof (*tsigkey));
	tsigkey->mctx = mctx;

	sep1 = strchr(arg, ':');
	if (sep1 == NULL) {
		perf_log_warning("invalid TSIG [alg:]name:secret");
		perf_opt_usage();
		exit(1);
	}

	sep2 = strchr(sep1 + 1, ':');
	if (sep2 == NULL) {
		/* name:key */
		alg = NULL;
		alglen = 0;
		name = arg;
		namelen = sep1 - arg;
		secret = sep1 + 1;
	} else {
		/* [alg:]name:secret */
		alg = arg;
		alglen = sep1 - arg;
		name = sep1 + 1;
		namelen = sep2 - sep1 - 1;
		secret = sep2 + 1;
	}

	/* Algorithm */

	if (alg == NULL || strncasecmp(alg, "hmac-md5:", 9) == 0) {
		SET_KEY(tsigkey, MD5);
	} else if (strncasecmp(alg, "hmac-sha1:", 10) == 0) {
		SET_KEY(tsigkey, SHA1);
	} else if (strncasecmp(alg, "hmac-sha224:", 12) == 0) {
		SET_KEY(tsigkey, SHA224);
	} else if (strncasecmp(alg, "hmac-sha256:", 12) == 0) {
		SET_KEY(tsigkey, SHA256);
	} else if (strncasecmp(alg, "hmac-sha384:", 12) == 0) {
		SET_KEY(tsigkey, SHA384);
	} else if (strncasecmp(alg, "hmac-sha512:", 12) == 0) {
		SET_KEY(tsigkey, SHA512);
	} else {
		perf_log_warning("invalid TSIG algorithm %.*s", alglen, alg);
		perf_opt_usage();
		exit(1);
	}

	/* Name */

	dns_fixedname_init(&tsigkey->fname);
	tsigkey->name = dns_fixedname_name(&tsigkey->fname);
	result = name_fromstring(tsigkey->name, dns_rootname, name, namelen,
				 NULL, "TSIG key");
	if (result != ISC_R_SUCCESS) {
		perf_opt_usage();
		exit(1);
	}
	(void)dns_name_downcase(tsigkey->name, tsigkey->name, NULL);

	/* Secret */

	isc_buffer_init(&tsigkey->secret, tsigkey->secretdata,
			sizeof(tsigkey->secretdata));
	result = isc_base64_decodestring(secret, &tsigkey->secret);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("invalid TSIG secret '%s'", secret);
		perf_opt_usage();
		exit(1);
	}

	return tsigkey;
}

void
perf_dns_destroytsigkey(perf_dnstsigkey_t **tsigkeyp)
{
	perf_dnstsigkey_t *tsigkey;

	INSIST(tsigkeyp != NULL && *tsigkeyp != NULL);

	tsigkey = *tsigkeyp;
	*tsigkeyp = NULL;

	isc_mem_put(tsigkey->mctx, tsigkey, sizeof(*tsigkey));
}

/*
 * Appends an OPT record to the packet.
 */
static isc_result_t
add_edns(isc_buffer_t *packet, isc_boolean_t dnssec) {
	unsigned char *base;

	if (isc_buffer_availablelength(packet) < EDNSLEN) {
		perf_log_warning("failed to add OPT to query packet");
		return (ISC_R_NOSPACE);
	}

	base = isc_buffer_base(packet);

	isc_buffer_putuint8(packet, 0);			/* root name */
	isc_buffer_putuint16(packet, dns_rdatatype_opt);/* type */
	isc_buffer_putuint16(packet, MAX_EDNS_PACKET);	/* class */
	isc_buffer_putuint8(packet, 0);			/* xrcode */
	isc_buffer_putuint8(packet, 0);			/* version */
	if (dnssec)					/* flags */
		isc_buffer_putuint16(packet, 0x8000);
	else
		isc_buffer_putuint16(packet, 0);
	isc_buffer_putuint16(packet, 0);		/* rdlen */

	base[11]++;				/* increment record count */

	return (ISC_R_SUCCESS);
}

static void
hmac_init(perf_dnstsigkey_t *tsigkey, hmac_ctx_t *ctx)
{
	unsigned char *secret;
	unsigned int length;

	secret = isc_buffer_base(&tsigkey->secret);
	length = isc_buffer_usedlength(&tsigkey->secret);

	switch (tsigkey->hmactype) {
	case TSIG_HMACMD5:
		isc_hmacmd5_init(&ctx->hmacmd5, secret, length);
		break;
	case TSIG_HMACSHA1:
		isc_hmacsha1_init(&ctx->hmacsha1, secret, length);
		break;
	case TSIG_HMACSHA224:
		isc_hmacsha224_init(&ctx->hmacsha224, secret, length);
		break;
	case TSIG_HMACSHA256:
		isc_hmacsha256_init(&ctx->hmacsha256, secret, length);
		break;
	case TSIG_HMACSHA384:
		isc_hmacsha384_init(&ctx->hmacsha384, secret, length);
		break;
	case TSIG_HMACSHA512:
		isc_hmacsha512_init(&ctx->hmacsha512, secret, length);
		break;
	}
}

static void
hmac_update(perf_dnstsigkey_t *tsigkey, hmac_ctx_t *ctx,
	    unsigned char *data, unsigned int length)
{
	switch (tsigkey->hmactype) {
	case TSIG_HMACMD5:
		isc_hmacmd5_update(&ctx->hmacmd5, data, length);
		break;
	case TSIG_HMACSHA1:
		isc_hmacsha1_update(&ctx->hmacsha1, data, length);
		break;
	case TSIG_HMACSHA224:
		isc_hmacsha224_update(&ctx->hmacsha224, data, length);
		break;
	case TSIG_HMACSHA256:
		isc_hmacsha256_update(&ctx->hmacsha256, data, length);
		break;
	case TSIG_HMACSHA384:
		isc_hmacsha384_update(&ctx->hmacsha384, data, length);
		break;
	case TSIG_HMACSHA512:
		isc_hmacsha512_update(&ctx->hmacsha512, data, length);
		break;
	}
}

static void
hmac_sign(perf_dnstsigkey_t *tsigkey, hmac_ctx_t *ctx, unsigned char *digest,
	  unsigned int digestlen)
{
	switch (tsigkey->hmactype) {
	case TSIG_HMACMD5:
		isc_hmacmd5_sign(&ctx->hmacmd5, digest);
		break;
	case TSIG_HMACSHA1:
		isc_hmacsha1_sign(&ctx->hmacsha1, digest, digestlen);
		break;
	case TSIG_HMACSHA224:
		isc_hmacsha224_sign(&ctx->hmacsha224, digest, digestlen);
		break;
	case TSIG_HMACSHA256:
		isc_hmacsha256_sign(&ctx->hmacsha256, digest, digestlen);
		break;
	case TSIG_HMACSHA384:
		isc_hmacsha384_sign(&ctx->hmacsha384, digest, digestlen);
		break;
	case TSIG_HMACSHA512:
		isc_hmacsha512_sign(&ctx->hmacsha512, digest, digestlen);
		break;
	}
}

/*
 * Appends a TSIG record to the packet.
 */
static isc_result_t
add_tsig(isc_buffer_t *packet, perf_dnstsigkey_t *tsigkey)
{
	unsigned char *base;
	hmac_ctx_t hmac;
	isc_region_t name_r;
	isc_region_t *alg_r;
	unsigned int rdlen, totallen;
	unsigned char tmpdata[512];
	isc_buffer_t tmp;
	isc_uint32_t now;
	unsigned char digest[ISC_SHA256_DIGESTLENGTH];

	hmac_init(tsigkey, &hmac);
	now = time(NULL);
	dns_name_toregion(tsigkey->name, &name_r);
	alg_r = (isc_region_t *) &tsigkey->alg;

	/* Make sure everything will fit */
	rdlen = alg_r->length + 16 + tsigkey->digestlen;
	totallen = name_r.length + 10 + rdlen;
	if (totallen > isc_buffer_availablelength(packet)) {
		perf_log_warning("adding TSIG: out of space");
		return (ISC_R_NOSPACE);
	}

	base = isc_buffer_base(packet);

	/* Digest the message */
	hmac_update(tsigkey, &hmac, isc_buffer_base(packet),
		    isc_buffer_usedlength(packet));

	/* Digest the TSIG record */
	isc_buffer_init(&tmp, tmpdata, sizeof tmpdata);
	isc_buffer_copyregion(&tmp, &name_r);		/* name */
	isc_buffer_putuint16(&tmp, dns_rdataclass_any);	/* class */
	isc_buffer_putuint32(&tmp, 0);			/* ttl */
	isc_buffer_copyregion(&tmp, alg_r);		/* alg */
	isc_buffer_putuint16(&tmp, 0);			/* time high */
	isc_buffer_putuint32(&tmp, now);		/* time low */
	isc_buffer_putuint16(&tmp, 300);		/* fudge */
	isc_buffer_putuint16(&tmp, 0);			/* error */
	isc_buffer_putuint16(&tmp, 0);			/* other length */
	hmac_update(tsigkey, &hmac, isc_buffer_base(&tmp),
		    isc_buffer_usedlength(&tmp));
	hmac_sign(tsigkey, &hmac, digest, tsigkey->digestlen);

	/* Add the TSIG record. */
	isc_buffer_copyregion(packet, &name_r);			/* name */
	isc_buffer_putuint16(packet, dns_rdatatype_tsig);	/* type */
	isc_buffer_putuint16(packet, dns_rdataclass_any);	/* class */
	isc_buffer_putuint32(packet, 0);			/* ttl */
	isc_buffer_putuint16(packet, rdlen);			/* rdlen */
	isc_buffer_copyregion(packet, alg_r);			/* alg */
	isc_buffer_putuint16(packet, 0);			/* time high */
	isc_buffer_putuint32(packet, now);			/* time low */
	isc_buffer_putuint16(packet, 300);			/* fudge */
	isc_buffer_putuint16(packet, tsigkey->digestlen);	/* digest len */
	isc_buffer_putmem(packet, digest, tsigkey->digestlen);	/* digest */
	isc_buffer_putmem(packet, base, 2);			/* orig ID */
	isc_buffer_putuint16(packet, 0);			/* error */
	isc_buffer_putuint16(packet, 0);			/* other len */

	base[11]++;				/* increment record count */

	return (ISC_R_SUCCESS);
}

static isc_result_t
build_query(const isc_textregion_t *line, isc_buffer_t *msg)
{
	char *domain_str;
	int domain_len;
	dns_name_t name;
	dns_offsets_t offsets;
	isc_textregion_t qtype_r;
	dns_rdatatype_t qtype;
	isc_result_t result;

	domain_str = line->base;
	domain_len = strcspn(line->base, WHITESPACE);

	qtype_r.base = line->base + domain_len;
	while (isspace(*qtype_r.base & 0xff))
		qtype_r.base++;
	qtype_r.length = strcspn(qtype_r.base, WHITESPACE);

	/* Create the question section */
	DNS_NAME_INIT(&name, offsets);
	result = name_fromstring(&name, dns_rootname, domain_str, domain_len,
				 msg, "domain");
	if (result != ISC_R_SUCCESS)
		return (result);

	if (qtype_r.length == 0) {
		perf_log_warning("invalid query input format: %s", line->base);
		return (ISC_R_FAILURE);
	}
	result = dns_rdatatype_fromtext(&qtype, &qtype_r);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("invalid query type: %.*s",
				 (int) qtype_r.length, qtype_r.base);
		return (ISC_R_FAILURE);
	}

	isc_buffer_putuint16(msg, qtype);
	isc_buffer_putuint16(msg, dns_rdataclass_in);

	return ISC_R_SUCCESS;
}

static isc_boolean_t
token_equals(const isc_textregion_t *token, const char *str)
{
	return (strlen(str) == token->length &&
		strncasecmp(str, token->base, token->length) == 0);
}

/*
 * Reads one line containing an individual update for a dynamic update message.
 */
static isc_result_t
read_update_line(perf_dnsctx_t *ctx, const isc_textregion_t *line, char *str,
		 dns_name_t *zname, int want_ttl, int need_type,
		 int want_rdata, int need_rdata, dns_name_t *name,
		 isc_uint32_t *ttlp, dns_rdatatype_t *typep,
		 dns_rdata_t *rdata, isc_buffer_t *rdatabuf)
{
	char *curr_str;
	unsigned int curr_len;
	isc_buffer_t buffer;
	isc_textregion_t src;
	dns_rdatacallbacks_t callbacks;
	isc_result_t result;

	while (isspace(*str & 0xff))
		str++;

	/* Read the owner name */
	curr_str = str;
	curr_len = strcspn(curr_str, WHITESPACE);
	result = name_fromstring(name, zname, curr_str, curr_len, NULL,
				 "owner");
	if (result != ISC_R_SUCCESS)
		return (result);
	str += curr_len;
	while (isspace(*str & 0xff))
		str++;

	/* Read the ttl */
	if (want_ttl) {
		curr_str = str;
		curr_len = strcspn(curr_str, WHITESPACE);
		src.base = curr_str;
		src.length = curr_len;
		result = dns_ttl_fromtext(&src, ttlp);
		if (result != ISC_R_SUCCESS) {
			perf_log_warning("invalid ttl: %.*s",
					 curr_len, curr_str);
			return (result);
		}
		str += curr_len;
		while (isspace(*str & 0xff))
			str++;
	}

	/* Read the type */
	curr_str = str;
	curr_len = strcspn(curr_str, WHITESPACE);
	if (curr_len == 0) {
		if (!need_type)
			return (ISC_R_SUCCESS);
		perf_log_warning("invalid update command: %s", line->base);
		return (ISC_R_SUCCESS);
	}
	src.base = curr_str;
	src.length = curr_len;
	result = dns_rdatatype_fromtext(typep, &src);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("invalid type: %.*s", curr_len, curr_str);
		return (result);
	}
	str += curr_len;
	while (isspace(*str & 0xff))
		str++;

	/* Read the rdata */
	if (!want_rdata)
		return (ISC_R_SUCCESS);

	if (*str == 0) {
		if (!need_rdata)
			return (ISC_R_SUCCESS);
		perf_log_warning("invalid update command: %s\n", line->base);
		return (ISC_R_FAILURE);
	}

	isc_buffer_init(&buffer, str, strlen(str));
	isc_buffer_add(&buffer, strlen(str));
	result = isc_lex_openbuffer(ctx->lexer, &buffer);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("setting up lexer: %s",
				 isc_result_totext(result));
		return (result);
	}
	dns_rdatacallbacks_init_stdio(&callbacks);
	result = dns_rdata_fromtext(rdata, dns_rdataclass_in, *typep,
				    ctx->lexer, zname, 0, ctx->mctx, rdatabuf,
				    &callbacks);
	(void)isc_lex_close(ctx->lexer);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("parsing rdata: %s", str);
		return (result);
	}

	return (ISC_R_SUCCESS);
}

/*
 * Reads a complete dynamic update message and sends it.
 */
static isc_result_t
build_update(perf_dnsctx_t *ctx, const isc_textregion_t *record,
	     isc_buffer_t *msg)
{
	isc_textregion_t input;
	char *msgbase;
	isc_buffer_t rdlenbuf, rdatabuf;
	unsigned char rdataarray[MAX_RDATA_LENGTH];
	isc_textregion_t token;
	char *str;
	isc_boolean_t is_update;
	int updates = 0;
	int prereqs = 0;
	dns_fixedname_t fzname, foname;
	dns_name_t *zname, *oname;
	isc_uint32_t ttl;
	dns_rdatatype_t rdtype;
	dns_rdataclass_t rdclass;
	dns_rdata_t rdata;
	isc_uint16_t rdlen;
	isc_result_t result;

	/* Reset compression context */
	dns_compress_rollback(&ctx->compress, 0);

	input = *record;
	msgbase = isc_buffer_base(msg);

	/* Initialize */

	dns_fixedname_init(&foname);
	oname = dns_fixedname_name(&foname);

	/* Parse zone name */
	dns_fixedname_init(&fzname);
	zname = dns_fixedname_name(&fzname);
	result = name_fromstring(zname, dns_rootname,
				 input.base, strlen(input.base),
				 NULL, "zone");
	if (result != ISC_R_SUCCESS)
		goto done;

	/* Render zone section */
	result = dns_name_towire(zname, &ctx->compress, msg);
	if (result != ISC_R_SUCCESS) {
		perf_log_warning("error rendering zone name: %s",
				 isc_result_totext(result));
		goto done;
	}
	isc_buffer_putuint16(msg, dns_rdatatype_soa);
	isc_buffer_putuint16(msg, dns_rdataclass_in);

	while (ISC_TRUE) {
		input.base += strlen(input.base) + 1;
		if (input.base >= record->base + record->length) {
			perf_log_warning("warning: incomplete update");
			goto done;
		}

		ttl = 0;
		rdtype = dns_rdatatype_any;
		isc_buffer_init(&rdatabuf, rdataarray, sizeof(rdataarray));
		dns_rdata_init(&rdata);
		rdlen = 0;
		rdclass = dns_rdataclass_in;
		is_update = ISC_FALSE;

		token.base = input.base;
		token.length = strcspn(token.base, WHITESPACE);
		str = input.base + token.length;
		if (token_equals(&token, "send")) {
			break;
		} else if (token_equals(&token, "add")) {
			result = read_update_line(ctx, &input, str, zname,
						  ISC_TRUE,
						  ISC_TRUE, ISC_TRUE, ISC_TRUE,
						  oname, &ttl, &rdtype,
						  &rdata, &rdatabuf);
			rdclass = dns_rdataclass_in;
			is_update = ISC_TRUE;
		} else if (token_equals(&token, "delete")) {
			result = read_update_line(ctx, &input, str, zname,
						  ISC_FALSE,
						  ISC_FALSE, ISC_TRUE,
						  ISC_FALSE, oname, &ttl,
						  &rdtype, &rdata, &rdatabuf);
			if (isc_buffer_usedlength(&rdatabuf) > 0)
				rdclass = dns_rdataclass_none;
			else
				rdclass = dns_rdataclass_any;
			is_update = ISC_TRUE;
		} else if (token_equals(&token, "require")) {
			result = read_update_line(ctx, &input, str, zname,
						  ISC_FALSE,
						  ISC_FALSE, ISC_TRUE,
						  ISC_FALSE, oname, &ttl,
						  &rdtype, &rdata, &rdatabuf);
			if (isc_buffer_usedlength(&rdatabuf) > 0)
				rdclass = dns_rdataclass_in;
			else
				rdclass = dns_rdataclass_any;
			is_update = ISC_FALSE;
		} else if (token_equals(&token, "prohibit")) {
			result = read_update_line(ctx, &input, str, zname,
						  ISC_FALSE,
						  ISC_FALSE, ISC_FALSE,
						  ISC_FALSE, oname, &ttl,
						  &rdtype, &rdata, &rdatabuf);
			rdclass = dns_rdataclass_none;
			is_update = ISC_FALSE;
		} else {
			perf_log_warning("invalid update command: %s",
					 input.base);
			result = ISC_R_FAILURE;
		}

		if (result != ISC_R_SUCCESS)
			goto done;

		if (!is_update && updates > 0) {
			perf_log_warning("prereqs must precede updates");
			result = ISC_R_FAILURE;
			goto done;
		}

		/* Render record */
		result = dns_name_towire(oname, &ctx->compress, msg);
		if (result != ISC_R_SUCCESS) {
			perf_log_warning("rendering record name: %s",
					 isc_result_totext(result));
			goto done;
		}
		if (isc_buffer_availablelength(msg) < 10) {
			perf_log_warning("out of space in message buffer");
			result = ISC_R_NOSPACE;
			goto done;
		}

		isc_buffer_putuint16(msg, rdtype);
		isc_buffer_putuint16(msg, rdclass);
		isc_buffer_putuint32(msg, ttl);
		rdlenbuf = *msg;
		isc_buffer_putuint16(msg, 0); /* rdlen */
		rdlen = isc_buffer_usedlength(&rdatabuf);
		if (rdlen > 0) {
			result = dns_rdata_towire(&rdata, &ctx->compress, msg);
			if (result != ISC_R_SUCCESS) {
				perf_log_warning("rendering rdata: %s",
						 isc_result_totext(result));
				goto done;
			}
			rdlen = msg->used - rdlenbuf.used - 2;
			isc_buffer_putuint16(&rdlenbuf, rdlen);
		}
		if (is_update)
			updates++;
		else
			prereqs++;
	}

	msgbase[7] = prereqs; /* ANCOUNT = number of prereqs */
	msgbase[9] = updates; /* AUCOUNT = number of updates */

	result = ISC_R_SUCCESS;

 done:
	return result;
}

isc_result_t
perf_dns_buildrequest(perf_dnsctx_t *ctx, const isc_textregion_t *record,
		      isc_uint16_t qid,
		      isc_boolean_t edns, isc_boolean_t dnssec,
		      perf_dnstsigkey_t *tsigkey, isc_buffer_t *msg)
{
	unsigned int flags;
	isc_result_t result;

	if (ctx != NULL)
		flags = dns_opcode_update << 11;
	else
		flags = DNS_MESSAGEFLAG_RD;

	/* Create the DNS packet header */
	isc_buffer_putuint16(msg, qid);
	isc_buffer_putuint16(msg, flags);	/* flags */
	isc_buffer_putuint16(msg, 1);		/* qdcount */
	isc_buffer_putuint16(msg, 0);		/* ancount */
	isc_buffer_putuint16(msg, 0);		/* aucount */
	isc_buffer_putuint16(msg, 0);		/* arcount */

	if (ctx != NULL) {
		result = build_update(ctx, record, msg);
	} else {
		result = build_query(record, msg);
	}
	if (result != ISC_R_SUCCESS)
		return (result);

	if (edns) {
		result = add_edns(msg, dnssec);
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	if (tsigkey != NULL) {
		result = add_tsig(msg, tsigkey);
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	return (ISC_R_SUCCESS);
}
