/*
 * 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 - 2006 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.
 */

/* Common code for dnsperf and resperf */

#define DEFINE_GLOBALS
#include "common.h"

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

/*
 * show_usage_common:
 *   Print out usage/syntax information common to both programs
 */
void
show_usage_common() {
	fprintf(stderr,
"\nUsage: %s [-d datafile] [-s server_addr] [-p port]\n"
"               [-b bufsize] [-f family] [-e] [-D]\n"
"               [-y name:secret] [-v] [-A] [-h]\n",
		progname);
}

void show_options_common() {
	fprintf(stderr,
"  -d specifies the input data file (default: stdin)\n"
"  -s sets the server to query (default: %s)\n"
"  -p sets the port on which to query the server (default: %d)\n"
"  -b set socket send/receive buffer size in kilobytes (default: %d k)\n"
"  -f specify address family of DNS transport, inet or inet6 (default: any)\n"
"  -e enable EDNS 0\n"
"  -D set the DNSSEC OK bit (implies EDNS)\n"
"  -y specifies the TSIG name and secret (no default)\n"
"  -A report command-line arguments\n"
"  -h print this usage\n",
	        DEF_SERVER_TO_QUERY, DEF_SERVER_PORT,
		DEF_BUFFER_SIZE);
}

void
show_usage(void) {
	show_usage_common();
	show_usage_prog();
	show_options_common();
	show_options_prog();
	fprintf(stderr, "\n");
}

/*
 * parse_udouble:
 * Converts a string to an unsigned double
 */
isc_result_t
parse_udouble(double *dp, const char *string) {
	char c;
	int index = 0;
	isc_boolean_t seen_dot = ISC_FALSE;

	while (string[index] != 0) {
		c = string[index];
		if (c == '.') {
			if (seen_dot) {
				return (ISC_R_BADNUMBER);
			} else {
				seen_dot = ISC_TRUE;
			}
		} else if (c < '0' || c > '9') {
			return (ISC_R_BADNUMBER);
		}
		index++;
	}

	*dp = atof(string);
	return (ISC_R_SUCCESS);
}

/*
 * get_input_line:
 *   Get the next non-comment line from the input file
 *
 *   Put text in line, up to max of n chars, removing trailing newlines.
 *   Skip comment lines and empty lines.
 */
isc_result_t
get_input_line(char *line, int n) {
	char *str;
	unsigned int len;

	while (ISC_TRUE) {
		str = fgets(line, n, datafile);
		if (str == NULL)
			return (ISC_R_EOF);
		if (str[0] != COMMENT_CHAR && str[0] != '\n')
			break;
	}

	/*
	 * We should really test if we saw the whole line, and either
	 * concatenate or flush and warn until the next complete line.
	 */
	len = strlen(str);
	if (str[len - 1] == '\n')
		str[len - 1] = '\0';
	return (ISC_R_SUCCESS);
}

static void
buffer_fromstring(isc_buffer_t *buffer, char *s)
{
	int len;

	len = strlen(s);
	isc_buffer_init(buffer, s, len);
	isc_buffer_add(buffer, len);
}

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

	if (isc_buffer_availablelength(packet) < EDNSLEN) {
		fprintf(stderr, "Failed to add OPT to query packet\n");
		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);
}

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

	isc_hmacmd5_init(&hmac, isc_buffer_base(&tsigsecret),
			 isc_buffer_usedlength(&tsigsecret));
	now = time(NULL);
	dns_name_toregion(tsigname, &name_r);
	digest_len = ISC_MD5_DIGESTLENGTH;

	/* Make sure everything will fit */
	rdlen = tsigalg.length + 16 + digest_len;
	totallen = name_r.length + 10 + rdlen;
	if (totallen > isc_buffer_availablelength(packet)) {
		fprintf(stderr, "adding TSIG: out of space\n");
		return (ISC_R_NOSPACE);
	}

	base = isc_buffer_base(packet);

	/* Digest the message */
	isc_hmacmd5_update(&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, &tsigalg);		/* 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 */
	isc_hmacmd5_update(&hmac, isc_buffer_base(&tmp),
			   isc_buffer_usedlength(&tmp));
	isc_hmacmd5_sign(&hmac, digest);

	/* 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, &tsigalg);		/* 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, digest_len);		/* digest len */
	isc_buffer_putmem(packet, digest, digest_len);		/* 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
send_datagram(isc_buffer_t *msg) {
	unsigned char *base;
	int len;
	int bytes_sent;

	base = isc_buffer_base(msg);
	len = isc_buffer_usedlength(msg);
	bytes_sent = sendto(query_socket, base, len, 0,
			    &server_addr.type.sa, server_addr.length);
	if (bytes_sent == -1) {
		fprintf(stderr, "Failed to send packet to %s\n",
			server_addr_str);
		return (ISC_R_FAILURE);
	}

	if (bytes_sent != len)
		fprintf(stderr, "Warning: incomplete packet sent\n");

	total_request_size += len;

	return (ISC_R_SUCCESS);
}

/*
 * open_datafile:
 *   Open the data file ready for reading
 */
static void
open_datafile(void) {
	if (datafile_name == NULL)
		datafile = stdin;
	else {
		datafile = fopen(datafile_name, "r");
		if (datafile == NULL) {
			fprintf(stderr, "Error: unable to open datafile: %s\n",
			        datafile_name);
			exit(1);
		}
	}
}

static void
set_server_sa(void) {
	isc_sockaddr_t addrs[8];
	int count, i;
	isc_netaddr_t na;
	isc_result_t result;

	count = 0;
	result = bind9_getaddresses(server_name, server_port, addrs, 8,
				    &count);
	if (result == ISC_R_SUCCESS) {
		for (i = 0; i < count; i++) {
			if (isc_sockaddr_pf(&addrs[i]) == family ||
			    family == AF_UNSPEC)
			{
				server_addr = addrs[i];
				isc_netaddr_fromsockaddr(&na, &server_addr);
				isc_netaddr_format(&na, server_addr_str,
						   sizeof(server_addr_str));
				return;
			}
		}
	}

	fprintf(stderr, "Error: unknown server name: %s\n", server_name);
	exit(1);
}

/*
 * open_socket:
 *   Open a socket for the queries.
 */
static void
open_socket(void) {
	isc_sockaddr_t sockaddr;
	int sock;
	int ret;
	int bufsize;
	int flags;

	isc_sockaddr_anyofpf(&sockaddr, isc_sockaddr_pf(&server_addr));

	sock = socket(isc_sockaddr_pf(&sockaddr), SOCK_DGRAM, 0);
	if (sock == -1) {
		fprintf(stderr, "Error: socket: %s", strerror(errno));
		exit(1);
	}

#if defined(AF_INET6) && defined(IPV6_V6ONLY)
	if (isc_sockaddr_pf(&sockaddr) == AF_INET6) {
		int on = 1;

		if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
			       &on, sizeof(on)) == -1) {
			fprintf(stderr,
				"Warning: setsockopt(IPV6_V6ONLY) failed\n");
		}
	}
#endif

	if (bind(sock, &sockaddr.type.sa, sockaddr.length) == -1)
		fprintf(stderr, "Error: bind call failed");

	bufsize = 1024 * socket_bufsize;

	ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
			 (char *) &bufsize, sizeof(bufsize));
	if (ret < 0)
		fprintf(stderr, "Warning: setsockbuf(SO_RCVBUF) failed\n");

	ret = setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
			 (char *) &bufsize, sizeof(bufsize));
	if (ret < 0)
		fprintf(stderr, "Warning: setsockbuf(SO_SNDBUF) failed\n");


	flags = fcntl(sock, F_GETFL, 0);
	if (flags < 0) {
		fprintf(stderr, "Error: fcntl(F_GETFL)");
		exit(1);
	}
	ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
	if (ret < 0) {
		fprintf(stderr, "Error: fcntl(F_SETFL)");
		exit(1);
	}

	query_socket = sock;
}

/*
 * setup:
 *   Set configuration options from command line arguments
 *   Open datafile for reading
 */
isc_result_t
setup(int argc, char **argv, const char *progopts) {
	int i;
	isc_result_t result;

	progname = isc_file_basename(argv[0]);

	dns_result_register();

	ISC_LIST_INIT(outstanding_list);
	ISC_LIST_INIT(instanding_list);
	for (i = 0; i < NQIDS; i++) {
		ISC_LINK_INIT(&status[i], link);
		ISC_LIST_APPEND(instanding_list, &status[i], link);
		status[i].list = &instanding_list;
	}

	result = parse_args(argc, argv, progopts);
	if (result != ISC_R_SUCCESS) {
		show_usage();
		return (result);
	}

	result = isc_mem_create(0, 0, &mctx);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "creating memory context: %s\n",
			isc_result_totext(result));
		return (result);
	}

	if (update_mode) {
		result = isc_lex_create(mctx, 1024, &lexer);
		if (result != ISC_R_SUCCESS) {
			fprintf(stderr, "creating lexer: %s\n",
				isc_result_totext(result));
			return (result);
		}
	}

	if (tsigkeyarg != NULL) {
		char *colon, *secret;
		isc_buffer_t source;

		DE_CONST(TSIG_HMACMD5_NAME, tsigalg.base);
		tsigalg.length = strlen(TSIG_HMACMD5_NAME) + 1;

		colon = strchr(tsigkeyarg, ':');
		if (colon == NULL)
			fprintf(stderr, "Invalid TSIG name:secret\n");
		secret = colon + 1;
		isc_buffer_init(&source, tsigkeyarg, colon - tsigkeyarg);
		isc_buffer_add(&source, colon - tsigkeyarg);
		dns_fixedname_init(&tsigfname);
		tsigname = dns_fixedname_name(&tsigfname);
		result = dns_name_fromtext(tsigname, &source, dns_rootname,
					   DNS_NAME_DOWNCASE, NULL);
		if (result != ISC_R_SUCCESS) {
			fprintf(stderr, "Invalid TSIG key name '%.*s'\n",
				(int)(colon - tsigkeyarg), tsigkeyarg);
			return (result);
		}
		isc_buffer_init(&tsigsecret, tsigdata, sizeof(tsigdata));
		result = isc_base64_decodestring(colon + 1, &tsigsecret);
		if (result != ISC_R_SUCCESS) {
			fprintf(stderr, "Invalid TSIG secret '%s'\n", secret);
			return (result);
		}
	}

	result = dns_compress_init(&compress, 0, mctx);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Error creating compression context: %s\n",
			isc_result_totext(result));
		return (result);
	}
	dns_compress_setmethods(&compress, DNS_COMPRESS_GLOBAL14);

	open_datafile();
	set_server_sa();
	open_socket();

	return (ISC_R_SUCCESS);
}

void
cleanup(void) {
	dns_compress_invalidate(&compress);
	if (lexer != NULL)
		isc_lex_destroy(&lexer);
	if (mctx != NULL)
		isc_mem_destroy(&mctx);

	(void) close(query_socket);

	if (datafile != stdin && datafile != NULL)
		(void) fclose(datafile);
}

/*
 * Update the time cache.
 */
void
update_current_time(void) {
	if (gettimeofday(&time_now, NULL) == -1) {
		fprintf(stderr, "gettimeofday(): %s\n", strerror(errno));
		exit(1);
	}
}

/*
 * difftv:
 *   Find the difference in seconds between two timeval structs.
 *
 *   Return the difference between tv1 and tv2 in seconds as a double.
 */
double
difftv(const struct timeval *tv1, const struct timeval *tv2) {
	long diff_sec, diff_usec;
	diff_sec = tv1->tv_sec - tv2->tv_sec;
	diff_usec = tv1->tv_usec - tv2->tv_usec;

	return (double)diff_sec + ((double)diff_usec / 1000000.0);
}

/*
 * Reads a line of input containing a query and sends it.
 */
static isc_result_t
do_query(isc_buffer_t *msg, char *input, int qid) {
	isc_buffer_t buffer;
	char *domain_str, *type_str;
	int domain_len;
	dns_fixedname_t fname;
	dns_name_t *name;
	isc_region_t name_r;
	isc_consttextregion_t qtype_r;
	dns_rdatatype_t qtype;
	isc_result_t result;

	domain_str = input;
	domain_len = strcspn(input, WHITESPACE);

	type_str = input + domain_len;
	while (isspace(*type_str & 0xff))
		type_str++;

	if (*type_str == 0) {
		fprintf(stderr, "Invalid query input format: %s\n", input);
		return (ISC_R_FAILURE);
	}

	dns_fixedname_init(&fname);
	name = dns_fixedname_name(&fname);
	isc_buffer_init(&buffer, domain_str, domain_len);
	isc_buffer_add(&buffer, domain_len);
	result = dns_name_fromtext(name, &buffer, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Invalid domain name: %.*s\n",
			domain_len, domain_str);
		return (result);
	}

	qtype_r.base = type_str;
	qtype_r.length = strlen(type_str);
	result = dns_rdatatype_fromtext(&qtype, (isc_textregion_t *) &qtype_r);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Invalid query type: %s\n", type_str);
		return (ISC_R_FAILURE);
	}

	/* Create the DNS packet header */
	isc_buffer_putuint16(msg, qid);
	isc_buffer_putuint16(msg, DNS_MESSAGEFLAG_RD);	/* 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 */

	/* Create the question section */
	dns_name_toregion(name, &name_r);
	isc_buffer_copyregion(msg, &name_r);
	isc_buffer_putuint16(msg, qtype);
	isc_buffer_putuint16(msg, dns_rdataclass_in);

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

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

	return send_datagram(msg);
}

/*
 * Reads one line containing an individual update for a dynamic update message.
 */
static isc_result_t
read_update_line(char *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 *token;
	isc_buffer_t buffer;
	isc_textregion_t src;
	dns_rdatacallbacks_t callbacks;
	isc_result_t result;

	/* Read the owner name */
	token = strsep(&str, WHITESPACE);
	if (token == NULL || *token == 0) {
		fprintf(stderr, "Invalid update command: %s\n", line);
		return (ISC_R_FAILURE);
	}

	buffer_fromstring(&buffer, token);
	result = dns_name_fromtext(name, &buffer, zname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Invalid owner name: %s\n", token);
		return (result);
	}

	/* Read the ttl */
	if (want_ttl) {
		token = strsep(&str, WHITESPACE);
		if (token == NULL || *token == 0) {
			fprintf(stderr, "Invalid update command: %s\n",
				line);
			return (ISC_R_FAILURE);
		}
		src.base = token;
		src.length = strlen(token);
		result = dns_ttl_fromtext(&src, ttlp);
		if (result != ISC_R_SUCCESS) {
			fprintf(stderr, "Invalid ttl: %s\n", token);
			return (result);
		}
	}

	/* Read the type */
	token = strsep(&str, WHITESPACE);
	if (token == NULL || *token == 0) {
		if (!need_type)
			return (ISC_R_SUCCESS);
		fprintf(stderr, "Invalid update command: %s\n", line);
		return (ISC_R_SUCCESS);
	}

	src.base = token;
	src.length = strlen(token);
	result = dns_rdatatype_fromtext(typep, &src);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Invalid type: %s\n", token);
		return (result);
	}

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

	if (str != NULL) {
		while (isspace(*str & 0xff))
			str++;
	}
	if (str == NULL || *str == 0) {
		if (!need_rdata)
			return (ISC_R_SUCCESS);
		fprintf(stderr, "Invalid update command: %s\n", line);
		return (ISC_R_FAILURE);
	}

	buffer_fromstring(&buffer, str);
	result = isc_lex_openbuffer(lexer, &buffer);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Setting up lexer: %s\n",
			isc_result_totext(result));
		return (result);
	}
	dns_rdatacallbacks_init_stdio(&callbacks);
	result = dns_rdata_fromtext(rdata, dns_rdataclass_in, *typep, lexer,
				    zname, 0, mctx, rdatabuf, &callbacks);
	(void)isc_lex_close(lexer);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Parsing rdata: %s\n", str);
		return (result);
	}

	return (ISC_R_SUCCESS);
}

/*
 * Skips all lines until a line containing "send" is seen or there are no lines
 * left.
 */
static void
skip_update(char *line, int len) {
	while (get_input_line(line, len) == ISC_R_SUCCESS) {
		if (strcmp(line, "send") == 0)
			break;
	}
}

/*
 * Reads a complete dynamic update message and sends it.
 */
static isc_result_t
do_update(isc_buffer_t *msg, char *input, int qid) {
	char *msgbase;
	isc_buffer_t buffer, rdlenbuf, rdatabuf;
	char line[MAX_INPUT_LEN + 1];
	char copy[MAX_INPUT_LEN + 1];
	unsigned char rdataarray[MAX_INPUT_LEN + 1];
	char *str, *token;
	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(&compress, 0);

	msgbase = isc_buffer_base(msg);

	/* Create the DNS packet header */
	isc_buffer_putuint16(msg, qid);
	isc_buffer_putuint16(msg, dns_opcode_update << 11);	/* 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 */

	/* Initialize */
	dns_fixedname_init(&fzname);
	zname = dns_fixedname_name(&fzname);

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

	/* Parse zone name */
	buffer_fromstring(&buffer, input);
	result = dns_name_fromtext(zname, &buffer, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Invalid zone name: %s\n", input);
		skip_update(line, sizeof(line));
		return (result);
	}

	/* Render zone section */
	result = dns_name_towire(zname, &compress, msg);
	if (result != ISC_R_SUCCESS) {
		fprintf(stderr, "Error rendering zone name: %s\n",
			isc_result_totext(result));
		skip_update(line, sizeof(line));
		return (result);
	}
	isc_buffer_putuint16(msg, dns_rdatatype_soa);
	isc_buffer_putuint16(msg, dns_rdataclass_in);

	while (ISC_TRUE) {
		if (get_input_line(line, sizeof(line)) != ISC_R_SUCCESS) {
			fprintf(stderr, "Warning: incomplete update\n");
			return (ISC_R_EOF);
		}

		strcpy(copy, line);
		str = copy;
		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 = strsep(&str, WHITESPACE);
		if (strcasecmp(token, "send") == 0) {
			break;
		} else if (strcasecmp(token, "add") == 0) {
			result = read_update_line(line, 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 (strcasecmp(token, "delete") == 0) {
			result = read_update_line(line, 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 (strcasecmp(token, "require") == 0) {
			result = read_update_line(line, 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 (strcasecmp(token, "prohibit") == 0) {
			result = read_update_line(line, str, zname, ISC_FALSE,
						  ISC_FALSE, ISC_FALSE,
						  ISC_FALSE, oname, &ttl,
						  &rdtype, &rdata, &rdatabuf);
			rdclass = dns_rdataclass_none;
			is_update = ISC_FALSE;
		} else {
			fprintf(stderr, "Invalid update command: %s\n", line);
			result = ISC_R_FAILURE;
		}

		if (result != ISC_R_SUCCESS) {
			skip_update(line, sizeof(line));
			return (result);
		}

		if (!is_update && updates > 0) {
			fprintf(stderr,
				"Error: prereqs must precede updates\n");
			skip_update(line, sizeof(line));
			return (ISC_R_FAILURE);
		}

		/* Render record */
		result = dns_name_towire(oname, &compress, msg);
		if (result != ISC_R_SUCCESS) {
			fprintf(stderr, "Error rendering record name: %s\n",
				isc_result_totext(result));
			skip_update(line, sizeof(line));
			return (result);
		}
		if (isc_buffer_availablelength(msg) < 10) {
			fprintf(stderr,
				"Error: out of space in message buffer\n");
			skip_update(line, sizeof(line));
			return (ISC_R_NOSPACE);
		}

		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, &compress, msg);
			if (result != ISC_R_SUCCESS) {
				fprintf(stderr, "Error rendering rdata: %s\n",
					isc_result_totext(result));
				skip_update(line, sizeof(line));
				return (result);
			}
			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 */

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

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

	return send_datagram(msg);
}

/*
 * process_line:
 *   Send a query or update based on a line of input
 *   Return ISC_R_NOMORE if we ran out of query IDs.
 */
isc_result_t
process_line(char *query_desc) {
	struct query_status *s;
	isc_buffer_t msg;
	isc_result_t result;
	int qid;

	/* Find an unused query ID. */
	s = ISC_LIST_HEAD(instanding_list);
	if (! s)
		return ISC_R_NOMORE;
	qid = s - status;

	/* Create message buffer */
	if (edns)
		isc_buffer_init(&msg, outpacket_buffer, MAX_EDNS_PACKET);
	else
		isc_buffer_init(&msg, outpacket_buffer, MAX_UDP_PACKET);

	if (update_mode)
		result = do_update(&msg, query_desc, qid);
	else
		result = do_query(&msg, query_desc, qid);

	if (result != ISC_R_SUCCESS)
		return result;

	/* Register the query in status[] */
	if (verbose) {
		s->desc = strdup(query_desc);
		if (s->desc == NULL) {
			fprintf(stderr, "Out of memory\n");
			exit(1);
		}
	}
	s->sent_timestamp = time_now;

	/* Link it into the queue of outstanding queries */
	ISC_LIST_UNLINK(instanding_list, s, link);
	ISC_LIST_PREPEND(outstanding_list, s, link);
	s->list = &outstanding_list;

	num_queries_sent++;
	num_queries_outstanding++;

	return ISC_R_SUCCESS;
}

/*
 * try_process_response:
 *
 *   Receive from the given socket & process an individual response packet.
 *   Remove it from the list of open queries (status[]) and decrement the
 *   number of outstanding queries if it matches an open query.
 *
 *   Returns whether a response was available.
 */
isc_boolean_t
try_process_response(int sockfd) {
	int numbytes, resp_id, flags;
	isc_buffer_t buffer;

	numbytes = recvfrom(sockfd, inpacket_buffer, sizeof(inpacket_buffer),
			    0, NULL, NULL);
	if (numbytes == -1) {
		if (errno == EAGAIN || errno == EINTR) {
			return (ISC_FALSE);
		} else {
			fprintf(stderr, "Error receiving datagram: %s\n",
				strerror(errno));
			exit(1);
		}
	} else if (numbytes < 4) {
		fprintf(stderr, "Warning: received short response\n");
		return (ISC_TRUE);
	}

	isc_buffer_init(&buffer, inpacket_buffer, numbytes);
	isc_buffer_add(&buffer, numbytes);

	resp_id = isc_buffer_getuint16(&buffer);
	flags = isc_buffer_getuint16(&buffer);

	if (register_response(resp_id, flags & 0xF))
		total_response_size += numbytes;

	return (ISC_TRUE);
}

/*
 * parse_args:
 *   Parse program arguments and set configuration options
 */
isc_result_t
parse_args(int argc, char **argv, const char *progopts) {
	int c;
	isc_result_t result;
	char opts[100];
	snprintf(opts, sizeof opts, "f:Ad:s:p:b:eDhy:%s", progopts);

	while ((c = getopt(argc, argv, opts)) !=
	       -1)
	{
		switch (c) {
		case 'f':
			if (strcmp(optarg, "inet") == 0)
				family = AF_INET;
#ifdef AF_INET6
			else if (strcmp(optarg, "inet6") == 0)
				family = AF_INET6;
#endif
			else if (strcmp(optarg, "any") == 0)
				family = AF_UNSPEC;
			else {
				fprintf(stderr, "Invalid address family: %s\n",
					optarg);
				return (ISC_R_FAILURE);
			}
			break;
		case 'A':
			print_arguments = ISC_TRUE;
			break;
		case 'd':
			datafile_name = optarg;
			break;
		case 's':
			server_name = optarg;
			break;
		case 'p':
			result = isc_parse_uint16(&server_port, optarg, 10);
			if (result != ISC_R_SUCCESS) {
				fprintf(stderr, "Invalid port: %s\n", optarg);
				return (ISC_R_FAILURE);
			}
			break;
		case 'b':
			result = isc_parse_uint32(&socket_bufsize, optarg, 10);
			if (result != ISC_R_SUCCESS) {
				fprintf(stderr, "Invalid socket buffer size: "
					"%s\n", optarg);
				return (ISC_R_FAILURE);
			}
			break;
		case 'e':
			edns = ISC_TRUE;
			break;
		case 'D':
			dnssec = ISC_TRUE;
			edns = ISC_TRUE;
			break;
		case 'h':
			show_usage();
			exit(0);
		case 'y':
			tsigkeyarg = optarg;
			break;
		default:
			result = handle_prog_opt(c);
			if (result == ISC_R_NOTFOUND)
				return result;
			break;
		}
	}

	if (optind != argc) {
		fprintf(stderr, "unexpected argument %s\n", argv[optind]);
		return ISC_R_FAILURE;
	}

	return (ISC_R_SUCCESS);
}

void
maybe_print_args(int argc, char **argv) {
	if (print_arguments) {
		int i;
		printf("[Status] Arguments: ");
		for (i = 1; i < argc; i++) {
			printf("%s ", argv[i]);
		}
		printf("\n");
	}
}


syntax highlighted by Code2HTML, v. 0.9.1