/*
  Based on getdns/src/test/getdns_query.c (commit 60be402).
  For license information, see end of this file.

  Example usage:
  $ dns-net2wire -q +dnssec_return_validation_chain +dnssec_return_status +dnssec_return_only_secure dfri.se
 */

#define _POSIX_SOURCE

//#include "config.h"
//#include "debug.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <inttypes.h>
#include <errno.h>
#include <assert.h>
#include <arpa/inet.h>
#include <getdns/getdns.h>
#include <getdns/getdns_extra.h>
#include "common.h"

#define DEBUG_SCHED(...)

#define MAX_TIMEOUTS FD_SETSIZE

#define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""

/* Eventloop based on select */
typedef struct my_eventloop {
	getdns_eventloop        base;
	getdns_eventloop_event *fd_events[FD_SETSIZE];
	uint64_t                fd_timeout_times[FD_SETSIZE];
	getdns_eventloop_event *timeout_events[MAX_TIMEOUTS];
	uint64_t                timeout_times[MAX_TIMEOUTS];
} my_eventloop;

static uint64_t get_now_plus(uint64_t amount)
{
	struct timeval tv;
	uint64_t       now;

	if (gettimeofday(&tv, NULL)) {
		perror("gettimeofday() failed");
		exit(EXIT_FAILURE);
	}
	now = tv.tv_sec * 1000000 + tv.tv_usec;

	return (now + amount * 1000) >= now ? now + amount * 1000 : -1;
}

getdns_return_t
my_eventloop_schedule(getdns_eventloop *loop,
    int fd, uint64_t timeout, getdns_eventloop_event *event)
{
	my_eventloop *my_loop  = (my_eventloop *)loop;
	size_t        i;

	assert(loop);
	assert(event);
	assert(fd < FD_SETSIZE);

	DEBUG_SCHED( "%s(loop: %p, fd: %d, timeout: %"PRIu64", event: %p)\n"
	        , __FUNCTION__, loop, fd, timeout, event);
	if (fd >= 0 && (event->read_cb || event->write_cb)) {
		assert(my_loop->fd_events[fd] == NULL);

		my_loop->fd_events[fd] = event;
		my_loop->fd_timeout_times[fd] = get_now_plus(timeout);
		event->ev = (void *) (intptr_t) fd + 1;

		DEBUG_SCHED( "scheduled read/write at %d\n", fd);
		return GETDNS_RETURN_GOOD;
	}

	assert(event->timeout_cb && !event->read_cb && !event->write_cb);

	for (i = 0; i < MAX_TIMEOUTS; i++) {
		if (my_loop->timeout_events[i] == NULL) {
			my_loop->timeout_events[i] = event;
			my_loop->timeout_times[i] = get_now_plus(timeout);
			event->ev = (void *) (intptr_t) i + 1;

			DEBUG_SCHED( "scheduled timeout at %d\n", (int)i);
			return GETDNS_RETURN_GOOD;
		}
	}
	return GETDNS_RETURN_GENERIC_ERROR;
}

getdns_return_t
my_eventloop_clear(getdns_eventloop *loop, getdns_eventloop_event *event)
{
	my_eventloop *my_loop = (my_eventloop *)loop;
	size_t i;

	assert(loop);
	assert(event);

	DEBUG_SCHED( "%s(loop: %p, event: %p)\n", __FUNCTION__, loop, event);

	i = (intptr_t)event->ev - 1;
	assert(i >= 0 && i < FD_SETSIZE);

	if (event->timeout_cb && !event->read_cb && !event->write_cb) {
		assert(my_loop->timeout_events[i] == event);
		my_loop->timeout_events[i] = NULL;
	} else {
		assert(my_loop->fd_events[i] == event);
		my_loop->fd_events[i] = NULL;
	}
	event->ev = NULL;
	return GETDNS_RETURN_GOOD;
}

void my_eventloop_cleanup(getdns_eventloop *loop)
{
}

void my_read_cb(int fd, getdns_eventloop_event *event)
{
	DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event);
	event->read_cb(event->userarg);
}

void my_write_cb(int fd, getdns_eventloop_event *event)
{
	DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event);
	event->write_cb(event->userarg);
}

void my_timeout_cb(int fd, getdns_eventloop_event *event)
{
	DEBUG_SCHED( "%s(fd: %d, event: %p)\n", __FUNCTION__, fd, event);
	event->timeout_cb(event->userarg);
}

void my_eventloop_run_once(getdns_eventloop *loop, int blocking)
{
	my_eventloop *my_loop = (my_eventloop *)loop;

	fd_set   readfds, writefds;
	int      fd, max_fd = -1;
	uint64_t now, timeout = (uint64_t)-1;
	size_t   i;
	struct timeval tv;

	assert(loop);

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	now = get_now_plus(0);

	for (i = 0; i < MAX_TIMEOUTS; i++) {
		if (!my_loop->timeout_events[i])
			continue;
		if (now > my_loop->timeout_times[i])
			my_timeout_cb(-1, my_loop->timeout_events[i]);
		else if (my_loop->timeout_times[i] < timeout)
			timeout = my_loop->timeout_times[i];
	}
	for (fd = 0; fd < FD_SETSIZE; fd++) {
		if (!my_loop->fd_events[fd])
			continue;
		if (my_loop->fd_events[fd]->read_cb)
			FD_SET(fd, &readfds);
		if (my_loop->fd_events[fd]->write_cb)
			FD_SET(fd, &writefds);
		if (fd > max_fd)
			max_fd = fd;
		if (my_loop->fd_timeout_times[fd] < timeout)
			timeout = my_loop->fd_timeout_times[fd];
	}
	if (max_fd == -1 && timeout == (uint64_t)-1)
		return;

	if (! blocking || now > timeout) {
		tv.tv_sec = 0;
		tv.tv_usec = 0;
	} else {
		tv.tv_sec  = (timeout - now) / 1000000;
		tv.tv_usec = (timeout - now) % 1000000;
	}
	if (select(max_fd + 1, &readfds, &writefds, NULL, &tv) < 0) {
		perror("select() failed");
		exit(EXIT_FAILURE);
	}
	now = get_now_plus(0);
	for (fd = 0; fd < FD_SETSIZE; fd++) {
		if (my_loop->fd_events[fd] &&
		    my_loop->fd_events[fd]->read_cb &&
		    FD_ISSET(fd, &readfds))
			my_read_cb(fd, my_loop->fd_events[fd]);

		if (my_loop->fd_events[fd] &&
		    my_loop->fd_events[fd]->write_cb &&
		    FD_ISSET(fd, &writefds))
			my_write_cb(fd, my_loop->fd_events[fd]);

		if (my_loop->fd_events[fd] &&
		    my_loop->fd_events[fd]->timeout_cb &&
		    now > my_loop->fd_timeout_times[fd])
			my_timeout_cb(fd, my_loop->fd_events[fd]);

		i = fd;
		if (my_loop->timeout_events[i] &&
		    my_loop->timeout_events[i]->timeout_cb &&
		    now > my_loop->timeout_times[i])
			my_timeout_cb(-1, my_loop->timeout_events[i]);
	}
}

void my_eventloop_run(getdns_eventloop *loop)
{
	my_eventloop *my_loop = (my_eventloop *)loop;
	size_t        i;

	assert(loop);

	i = 0;
	while (i < MAX_TIMEOUTS) {
		if (my_loop->fd_events[i] || my_loop->timeout_events[i]) {
			my_eventloop_run_once(loop, 1);
			i = 0;
		} else {
			i++;
		}
	}
}

void my_eventloop_init(my_eventloop *loop)
{
	static getdns_eventloop_vmt my_eventloop_vmt = {
		my_eventloop_cleanup,
		my_eventloop_schedule,
		my_eventloop_clear,
		my_eventloop_run,
		my_eventloop_run_once
	};

	(void) memset(loop, 0, sizeof(my_eventloop));
	loop->base.vmt = &my_eventloop_vmt;
}

static int quiet = 0;
static int batch_mode = 0;
static char *query_file = NULL;
static int json = 0;
static char *the_root = ".";
static char *name;
static getdns_context *context;
static getdns_dict *extensions;
static getdns_list *pubkey_pinset = NULL;
static size_t pincount = 0;
static uint16_t request_type = GETDNS_RRTYPE_NS;
static int timeout, edns0_size, padding_blocksize;
static int async = 0, interactive = 0;
static enum { GENERAL, ADDRESS, HOSTNAME, SERVICE } calltype = GENERAL;

int get_rrtype(const char *t);

int gqldns_b64_pton(char const *src, uint8_t *target, size_t targsize)
{
	const uint8_t pad64 = 64; /* is 64th in the b64 array */
	const char* s = src;
	uint8_t in[4];
	size_t o = 0, incount = 0;

	while(*s) {
		/* skip any character that is not base64 */
		/* conceptually we do:
		const char* b64 =      pad'=' is appended to array
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
		const char* d = strchr(b64, *s++);
		and use d-b64;
		*/
		char d = *s++;
		if(d <= 'Z' && d >= 'A')
			d -= 'A';
		else if(d <= 'z' && d >= 'a')
			d = d - 'a' + 26;
		else if(d <= '9' && d >= '0')
			d = d - '0' + 52;
		else if(d == '+')
			d = 62;
		else if(d == '/')
			d = 63;
		else if(d == '=')
			d = 64;
		else	continue;
		in[incount++] = (uint8_t)d;
		if(incount != 4)
			continue;
		/* process whole block of 4 characters into 3 output bytes */
		if(in[3] == pad64 && in[2] == pad64) { /* A B = = */
			if(o+1 > targsize)
				return -1;
			target[o] = (in[0]<<2) | ((in[1]&0x30)>>4);
			o += 1;
			break; /* we are done */
		} else if(in[3] == pad64) { /* A B C = */
			if(o+2 > targsize)
				return -1;
			target[o] = (in[0]<<2) | ((in[1]&0x30)>>4);
			target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2);
			o += 2;
			break; /* we are done */
		} else {
			if(o+3 > targsize)
				return -1;
			/* write xxxxxxyy yyyyzzzz zzwwwwww */
			target[o] = (in[0]<<2) | ((in[1]&0x30)>>4);
			target[o+1]= ((in[1]&0x0f)<<4) | ((in[2]&0x3c)>>2);
			target[o+2]= ((in[2]&0x03)<<6) | in[3];
			o += 3;
		}
		incount = 0;
	}
	return (int)o;
}

getdns_dict *
ipaddr_dict(getdns_context *context, char *ipstr)
{
	getdns_dict *r = getdns_dict_create_with_context(context);
	char *s = strchr(ipstr, '%'), *scope_id_str = "";
	char *p = strchr(ipstr, '@'), *portstr = "";
	char *t = strchr(ipstr, '#'), *tls_portstr = "";
	char *n = strchr(ipstr, '~'), *tls_namestr = "";
	/* ^[alg:]name:key */
	char *T = strchr(ipstr, '^'), *tsig_name_str = ""
	                            , *tsig_secret_str = ""
	                            , *tsig_algorithm_str = "";
	int            tsig_secret_size;
	uint8_t        tsig_secret_buf[256]; /* 4 times SHA512 */
	getdns_bindata tsig_secret;
	uint8_t buf[sizeof(struct in6_addr)];
	getdns_bindata addr;

	addr.data = buf;

	if (!r) return NULL;
	if (s) {
		*s = 0;
		scope_id_str = s + 1;
	}
	if (p) {
		*p = 0;
		portstr = p + 1;
	}
	if (t) {
		*t = 0;
		tls_portstr = t + 1;
	}
	if (n) {
		*n = 0;
		tls_namestr = n + 1;
	}
	if (T) {
		*T = 0;
		tsig_name_str = T + 1;
		if ((T = strchr(tsig_name_str, ':'))) {
			*T = 0;
			tsig_secret_str = T + 1;
			if ((T = strchr(tsig_secret_str, ':'))) {
				*T = 0;
				tsig_algorithm_str  = tsig_name_str;
				tsig_name_str = tsig_secret_str;
				tsig_secret_str  = T + 1;
			}
		} else {
			tsig_name_str = "";
		}
	}
	if (strchr(ipstr, ':')) {
		getdns_dict_util_set_string(r, "address_type", "IPv6");
		addr.size = 16;
		if (inet_pton(AF_INET6, ipstr, buf) <= 0) {
			getdns_dict_destroy(r);
			return NULL;
		}
	} else {
		getdns_dict_util_set_string(r, "address_type", "IPv4");
		addr.size = 4;
		if (inet_pton(AF_INET, ipstr, buf) <= 0) {
			getdns_dict_destroy(r);
			return NULL;
		}
	}
	getdns_dict_set_bindata(r, "address_data", &addr);
	if (*portstr)
		getdns_dict_set_int(r, "port", (int32_t)atoi(portstr));
	if (*tls_portstr)
		getdns_dict_set_int(r, "tls_port", (int32_t)atoi(tls_portstr));
	if (*tls_namestr) {
		getdns_dict_util_set_string(r, "tls_auth_name", tls_namestr);
	}
	if (*scope_id_str)
		getdns_dict_util_set_string(r, "scope_id", scope_id_str);
	if (*tsig_name_str)
		getdns_dict_util_set_string(r, "tsig_name", tsig_name_str);
	if (*tsig_algorithm_str)
		getdns_dict_util_set_string(r, "tsig_algorithm", tsig_algorithm_str);
	if (*tsig_secret_str) {
		tsig_secret_size = gqldns_b64_pton(
		    tsig_secret_str, tsig_secret_buf, sizeof(tsig_secret_buf));
		if (tsig_secret_size > 0) {
			tsig_secret.size = tsig_secret_size;
			tsig_secret.data = tsig_secret_buf;
			getdns_dict_set_bindata(r, "tsig_secret", &tsig_secret);
		}
	}
	return r;
}

static getdns_return_t
fill_transport_list(getdns_context *context, char *transport_list_str, 
                    getdns_transport_list_t *transports, size_t *transport_count)
{
	size_t max_transports = *transport_count;
	*transport_count = 0;
	for ( size_t i = 0
	    ; i < max_transports && i < strlen(transport_list_str)
	    ; i++, (*transport_count)++) {
		switch(*(transport_list_str + i)) {
			case 'U': 
				transports[i] = GETDNS_TRANSPORT_UDP;
				break;
			case 'T': 
				transports[i] = GETDNS_TRANSPORT_TCP;
				break;
			case 'L': 
				transports[i] = GETDNS_TRANSPORT_TLS;
				break;
			default:
				fprintf(stderr, "Unrecognised transport '%c' in string %s\n", 
				       *(transport_list_str + i), transport_list_str);
				return GETDNS_RETURN_GENERIC_ERROR;
		}
	}
	return GETDNS_RETURN_GOOD;
}

void
print_usage(FILE *out, const char *progname)
{
	fprintf(out, "usage: %s [<option> ...] \\\n"
	    "\t\t[@<upstream> ...] [+<extension> ...] [<name>] [<type>]\n", progname);
	fprintf(out, "\ndefault mode: "
#ifdef HAVE_LIBUNBOUND
	    "recursive"
#else
	    "stub"
#endif
	    ", synchronous resolution of NS record using UDP with TCP fallback\n");
	fprintf(out, "\nupstreams: @<ip>[%%<scope_id>][@<port>][#<tls port>][~<tls name>][^<tsig spec>]\n");
	fprintf(out, "\ntsig spec: [<algorithm>:]<name>:<secret in Base64>\n");
	fprintf(out, "\nextensions:\n");
	fprintf(out, "\t+add_warning_for_bad_dns\n");
	fprintf(out, "\t+dnssec_return_status\n");
	fprintf(out, "\t+dnssec_return_only_secure\n");
	fprintf(out, "\t+dnssec_return_validation_chain\n");
#ifdef DNSSEC_ROADBLOCK_AVOIDANCE
	fprintf(out, "\t+dnssec_roadblock_avoidance\n");
#endif
#ifdef EDNS_COOKIES
	fprintf(out, "\t+edns_cookies\n");
#endif
	fprintf(out, "\t+return_both_v4_and_v6\n");
	fprintf(out, "\t+return_call_reporting\n");
	fprintf(out, "\t+sit=<cookie>\t\tSend along cookie OPT with value <cookie>\n");
	fprintf(out, "\t+specify_class=<class>\n");
	fprintf(out, "\t+0\t\t\tClear all extensions\n");
	fprintf(out, "\noptions:\n");
	fprintf(out, "\t-a\tPerform asynchronous resolution "
	    "(default = synchronous)\n");
	fprintf(out, "\t-A\taddress lookup (<type> is ignored)\n");
	fprintf(out, "\t-B\tBatch mode. Schedule all messages before processing responses.\n");
	fprintf(out, "\t-b <bufsize>\tSet edns0 max_udp_payload size\n");
	fprintf(out, "\t-c\tSend Client Subnet privacy request\n");
	fprintf(out, "\t-D\tSet edns0 do bit\n");
	fprintf(out, "\t-d\tclear edns0 do bit\n");
	fprintf(out, "\t-e <idle_timeout>\tSet idle timeout in miliseconds\n");
	fprintf(out, "\t-F <filename>\tread the queries from the specified file\n");
	fprintf(out, "\t-f <filename>\tRead DNSSEC trust anchors from <filename>\n");
	fprintf(out, "\t-G\tgeneral lookup\n");
	fprintf(out, "\t-H\thostname lookup. (<name> must be an IP address; <type> is ignored)\n");
	fprintf(out, "\t-h\tPrint this help\n");
	fprintf(out, "\t-i\tPrint api information\n");
	fprintf(out, "\t-I\tInteractive mode (> 1 queries on same context)\n");
	fprintf(out, "\t-j\tOutput json response dict\n");
	fprintf(out, "\t-J\tPretty print json response dict\n");
	fprintf(out, "\t-k\tPrint root trust anchors\n");
	fprintf(out, "\t-K <pin>\tPin a public key for TLS connections (can repeat)\n");
	fprintf(out, "\t\t(should look like '" EXAMPLE_PIN "')\n");
	fprintf(out, "\t-n\tSet TLS authentication mode to NONE (default)\n");
	fprintf(out, "\t-m\tSet TLS authentication mode to REQUIRED\n");
	fprintf(out, "\t-p\tPretty print response dict\n");
	fprintf(out, "\t-P <blocksize>\tPad TLS queries to a multiple of blocksize\n");
	fprintf(out, "\t-r\tSet recursing resolution type\n");
	fprintf(out, "\t-R <filename>\tRead root hints from <filename>\n");
	fprintf(out, "\t-q\tQuiet mode - don't print response\n");
	fprintf(out, "\t-s\tSet stub resolution type (default = recursing)\n");
	fprintf(out, "\t-S\tservice lookup (<type> is ignored)\n");
	fprintf(out, "\t-t <timeout>\tSet timeout in miliseconds\n");

	fprintf(out, "\t-W\tAppend suffix always (default)\n");
	fprintf(out, "\t-1\tAppend suffix only to single label after failure\n");
	fprintf(out, "\t-M\tAppend suffix only to multi label name after failure\n");
	fprintf(out, "\t-N\tNever append a suffix\n");
	fprintf(out, "\t-Z <suffixes>\tSet suffixes with the given comma separed list\n");

	fprintf(out, "\t-T\tSet transport to TCP only\n");
	fprintf(out, "\t-O\tSet transport to TCP only keep connections open\n");
	fprintf(out, "\t-L\tSet transport to TLS only keep connections open\n");
	fprintf(out, "\t-E\tSet transport to TLS with TCP fallback only keep connections open\n");
	fprintf(out, "\t-u\tSet transport to UDP with TCP fallback (default)\n");
	fprintf(out, "\t-U\tSet transport to UDP only\n");
	fprintf(out, "\t-l <transports>\tSet transport list. List can contain 1 of each of the characters\n");
	fprintf(out, "\t\t\t U T L S for UDP, TCP or TLS e.g 'UT' or 'LTU' \n");

}

static getdns_return_t validate_chain(getdns_dict *response)
{
	getdns_return_t r;
	getdns_list *validation_chain;
	getdns_list *replies_tree;
	getdns_dict *reply;
	getdns_list *to_validate;
	getdns_list *trust_anchor;
	size_t i;
	int s;
	
	if (!(to_validate = getdns_list_create()))
		return GETDNS_RETURN_MEMORY_ERROR;

	if (getdns_context_get_dnssec_trust_anchors(context, &trust_anchor))
		trust_anchor = getdns_root_trust_anchor(NULL);

	if ((r = getdns_dict_get_list(
	    response, "validation_chain", &validation_chain)))
		goto error;

	if ((r = getdns_dict_get_list(
	    response, "replies_tree", &replies_tree)))
		goto error;

	fprintf(stdout, "replies_tree dnssec_status: ");
	switch ((s = getdns_validate_dnssec(
	    replies_tree, validation_chain, trust_anchor))) {

	case GETDNS_DNSSEC_SECURE:
		fprintf(stdout, "GETDNS_DNSSEC_SECURE\n");
		break;
	case GETDNS_DNSSEC_BOGUS:
		fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n");
		break;
	case GETDNS_DNSSEC_INDETERMINATE:
		fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n");
		break;
	case GETDNS_DNSSEC_INSECURE:
		fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n");
		break;
	case GETDNS_DNSSEC_NOT_PERFORMED:
		fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n");
		break;
	default:
		fprintf(stdout, "%d\n", (int)s);
	}

	i = 0;
	while (!(r = getdns_list_get_dict(replies_tree, i++, &reply))) {

		if ((r = getdns_list_set_dict(to_validate, 0, reply)))
			goto error;

		printf("reply %u, dnssec_status: ", (unsigned)i);
		switch ((s = getdns_validate_dnssec(
		    to_validate, validation_chain, trust_anchor))) {

		case GETDNS_DNSSEC_SECURE:
			fprintf(stdout, "GETDNS_DNSSEC_SECURE\n");
			break;
		case GETDNS_DNSSEC_BOGUS:
			fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n");
			break;
		case GETDNS_DNSSEC_INDETERMINATE:
			fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n");
			break;
		case GETDNS_DNSSEC_INSECURE:
			fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n");
			break;
		case GETDNS_DNSSEC_NOT_PERFORMED:
			fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n");
			break;
		default:
			fprintf(stdout, "%d\n", (int)s);
		}
	}
	if (r == GETDNS_RETURN_NO_SUCH_LIST_ITEM)
		r = GETDNS_RETURN_GOOD;
error:
	getdns_list_destroy(trust_anchor);
	getdns_list_destroy(to_validate);

	return GETDNS_RETURN_GOOD;
}

void callback(getdns_context *context, getdns_callback_type_t callback_type,
    getdns_dict *response, void *userarg, getdns_transaction_t trans_id)
{
	char *response_str;

	/* This is a callback with data */;
	if (response && !quiet && (response_str = json ?
	    getdns_print_json_dict(response, json == 1)
	  : getdns_pretty_print_dict(response))) {

		fprintf(stdout, "ASYNC response:\n%s\n", response_str);
		validate_chain(response);
		free(response_str);
	}

	if (callback_type == GETDNS_CALLBACK_COMPLETE) {
		printf("Response code was: GOOD. Status was: Callback with ID %"PRIu64"  was successfull.\n",
			trans_id);

	} else if (callback_type == GETDNS_CALLBACK_CANCEL)
		fprintf(stderr,
			"An error occurred: The callback with ID %"PRIu64" was cancelled. Exiting.\n",
			trans_id);
	else {
		fprintf(stderr,
			"An error occurred: The callback got a callback_type of %d. Exiting.\n",
			callback_type);
		fprintf(stderr,
			"Error :      '%s'\n",
			getdns_get_errorstr_by_id(callback_type));
	}
	getdns_dict_destroy(response);
	response = NULL;
}

#define CONTINUE ((getdns_return_t)-2)
#define CONTINUE_ERROR ((getdns_return_t)-3)

static getdns_return_t set_cookie(getdns_dict *exts, char *cookie)
{
	uint8_t data[40];
	size_t i;
	getdns_return_t r = GETDNS_RETURN_GENERIC_ERROR;
	getdns_bindata bindata;

	getdns_dict *opt_parameters = getdns_dict_create();
	getdns_list *options = getdns_list_create();
	getdns_dict *option = getdns_dict_create();

	if (*cookie == '=')
		cookie++;

	for (i = 0; i < 40 && *cookie; i++) {
		if (*cookie >= '0' && *cookie <= '9')
			data[i] = (uint8_t)(*cookie - '0') << 4;
		else if (*cookie >= 'a' && *cookie <= 'f')
			data[i] = (uint8_t)(*cookie - 'a' + 10) << 4;
		else if (*cookie >= 'A' && *cookie <= 'F')
			data[i] = (uint8_t)(*cookie - 'A' + 10) << 4;
		else
			goto done;
		cookie++;
		if (*cookie >= '0' && *cookie <= '9')
			data[i] |= (uint8_t)(*cookie - '0');
		else if (*cookie >= 'a' && *cookie <= 'f')
			data[i] |= (uint8_t)(*cookie - 'a' + 10);
		else if (*cookie >= 'A' && *cookie <= 'F')
			data[i] |= (uint8_t)(*cookie - 'A' + 10);
		else
			goto done;
		cookie++;;
	}
	bindata.data = data;
	bindata.size = i;
	if ((r = getdns_dict_set_int(option, "option_code", 10)))
		goto done;
	if ((r = getdns_dict_set_bindata(option, "option_data", &bindata)))
		goto done;
	if ((r = getdns_list_set_dict(options, 0, option)))
		goto done;
	if ((r = getdns_dict_set_list(opt_parameters, "options", options)))
		goto done;
	r = getdns_dict_set_dict(exts, "add_opt_parameters", opt_parameters);
done:
	getdns_dict_destroy(option);
	getdns_list_destroy(options);
	getdns_dict_destroy(opt_parameters);
	return r;
}

getdns_return_t parse_args(int argc, char **argv)
{
	getdns_return_t r = GETDNS_RETURN_GOOD;
	size_t i, j;
	char *arg, *c, *endptr;
	int t, print_api_info = 0, print_trust_anchors = 0;
	getdns_list *upstream_list = NULL;
	getdns_list *tas = NULL, *hints = NULL;
	getdns_dict *pubkey_pin = NULL;
	getdns_list *suffixes;
	char *suffix;
	getdns_bindata bindata;
	size_t upstream_count = 0;
	FILE *fh;
	uint32_t klass;

	for (i = 1; i < argc; i++) {
		arg = argv[i];
		if ((t = get_rrtype(arg)) >= 0) {
			request_type = t;
			continue;

		} else if (arg[0] == '+') {
			if (arg[1] == 's' && arg[2] == 'i' && arg[3] == 't' &&
			   (arg[4] == '=' || arg[4] == '\0')) {
				if ((r = set_cookie(extensions, arg+4))) {
					fprintf(stderr, "Could not set cookie:"
					    " %d", r);
					break;
				}
			} else if (strncmp(arg+1, "specify_class=", 14) == 0) {
				if (strncasecmp(arg+15, "IN", 3) == 0)
					r = getdns_dict_set_int(extensions,
					    "specify_class", GETDNS_RRCLASS_IN);
				else if (strncasecmp(arg+15, "CH", 3) == 0)
					r = getdns_dict_set_int(extensions,
					    "specify_class", GETDNS_RRCLASS_CH);
				else if (strncasecmp(arg+15, "HS", 3) == 0)
					r = getdns_dict_set_int(extensions,
					    "specify_class", GETDNS_RRCLASS_HS);
				else if (strncasecmp(arg+15, "NONE", 5) == 0)
					r = getdns_dict_set_int(extensions,
					    "specify_class", GETDNS_RRCLASS_NONE);
				else if (strncasecmp(arg+15, "ANY", 4) == 0)
					r = getdns_dict_set_int(extensions,
					    "specify_class", GETDNS_RRCLASS_ANY);
				else if (strncasecmp(arg+15, "CLASS", 5) == 0) {
					klass = strtol(arg + 20, &endptr, 10);
					if (*endptr || klass > 255)
						fprintf(stderr,
						    "Unknown class: %s\n",
						    arg+15);
					else
						r = getdns_dict_set_int(extensions,
						    "specify_class", klass);

				} else
					fprintf(stderr,
					    "Unknown class: %s\n", arg+15);
			} else if (arg[1] == '0') {
			    /* Unset all existing extensions*/
				getdns_dict_destroy(extensions);
				extensions = getdns_dict_create();
				break;
			} else if ((r = getdns_dict_set_int(extensions, arg+1,
			    GETDNS_EXTENSION_TRUE))) {
				fprintf(stderr, "Could not set extension "
				    "\"%s\": %d\n", argv[i], r);
				break;
			}
			continue;

		} else if (arg[0] == '@') {
			getdns_dict *upstream = ipaddr_dict(context, arg + 1);
			if (upstream) {
				if (!upstream_list &&
				    !(upstream_list =
				    getdns_list_create_with_context(context))){
					fprintf(stderr, "Could not create upstream list\n");
					return GETDNS_RETURN_MEMORY_ERROR;
				}
				getdns_list_set_dict(upstream_list,
				    upstream_count++, upstream);
			}
			continue;
		} else if (arg[0] != '-') {
			name = arg;
			continue;
		}
		for (c = arg+1; *c; c++) {
			switch (*c) {
			case 'a':
				async = 1;
				break;
			case 'A':
				calltype = ADDRESS;
				break;
			case 'b':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "max_udp_payload_size "
					    "expected after -b\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				edns0_size = strtol(argv[i], &endptr, 10);
				if (*endptr || edns0_size < 0) {
					fprintf(stderr, "positive "
					    "numeric max_udp_payload_size "
					    "expected after -b\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_context_set_edns_maximum_udp_payload_size(
				    context, (uint16_t) edns0_size);
				goto next;
			case 'c':
				if (getdns_context_set_edns_client_subnet_private(context, 1))
					return GETDNS_RETURN_GENERIC_ERROR;
				break;
			case 'D':
				(void) getdns_context_set_edns_do_bit(context, 1);
				break;
			case 'd':
				(void) getdns_context_set_edns_do_bit(context, 0);
				break;
			case 'f':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "file name expected "
					    "after -f\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (!(fh = fopen(argv[i], "r"))) {
					fprintf(stderr, "Could not open \"%s\""
					    ": %s\n",argv[i], strerror(errno));
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (getdns_fp2rr_list(fh, &tas, NULL, 3600)) {
					fprintf(stderr,"Could not parse "
					    "\"%s\"\n", argv[i]);
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				fclose(fh);
				if (getdns_context_set_dnssec_trust_anchors(
				    context, tas)) {
					fprintf(stderr,"Could not set "
					    "trust anchors from \"%s\"\n",
					    argv[i]);
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_list_destroy(tas);
				tas = NULL;
				break;
			case 'F':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "file name expected "
					    "after -F\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				query_file = argv[i];
				interactive = 1;
				break;
			case 'G':
				calltype = GENERAL;
				break;
			case 'H':
				calltype = HOSTNAME;
				break;
			case 'h':
				print_usage(stdout, argv[0]);
				return CONTINUE;
			case 'i':
				print_api_info = 1;
				break;
			case 'I':
				interactive = 1;
				break;
			case 'j':
				json = 2;
				break;
			case 'J':
				json = 1;
				break;
			case 'K':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "pin string of the form "
						EXAMPLE_PIN
						"expected after -K\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				pubkey_pin = getdns_pubkey_pin_create_from_string(context,
										 argv[i]);
				if (pubkey_pin == NULL) {
					fprintf(stderr, "could not convert '%s' into a "
						"public key pin.\n"
						"Good pins look like: " EXAMPLE_PIN "\n"
						"Please see RFC 7469 for details about "
						"the format\n", argv[i]);
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (pubkey_pinset == NULL)
					pubkey_pinset = getdns_list_create_with_context(context);
				if (r = getdns_list_set_dict(pubkey_pinset, pincount++,
							     pubkey_pin), r) {
					fprintf(stderr, "Failed to add pin to pinset (error %d: %s)\n",
						r, getdns_get_errorstr_by_id(r));
					getdns_dict_destroy(pubkey_pin);
					pubkey_pin = NULL;
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_dict_destroy(pubkey_pin);
				pubkey_pin = NULL;
				break;
			case 'k':
				print_trust_anchors = 1;
				break;
			case 'n':
				getdns_context_set_tls_authentication(context,
				                 GETDNS_AUTHENTICATION_NONE);
				break;
			case 'm':
				getdns_context_set_tls_authentication(context,
				                 GETDNS_AUTHENTICATION_REQUIRED);
				break;
			case 'P':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "tls_query_padding_blocksize "
					    "expected after -P\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				padding_blocksize = strtol(argv[i], &endptr, 10);
				if (*endptr || padding_blocksize < 0) {
					fprintf(stderr, "non-negative "
					    "numeric padding blocksize expected "
					    "after -P\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (getdns_context_set_tls_query_padding_blocksize(
					    context, padding_blocksize))
					return GETDNS_RETURN_GENERIC_ERROR;
				goto next;
			case 'p':
				json = 0;
			case 'q':
				quiet = 1;
				break;
			case 'r':
				getdns_context_set_resolution_type(
				    context,
				    GETDNS_RESOLUTION_RECURSING);
				break;
			case 'R':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "file name expected "
					    "after -f\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (!(fh = fopen(argv[i], "r"))) {
					fprintf(stderr, "Could not open \"%s\""
					    ": %s\n",argv[i], strerror(errno));
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (getdns_fp2rr_list(fh, &hints, NULL, 3600)) {
					fprintf(stderr,"Could not parse "
					    "\"%s\"\n", argv[i]);
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				fclose(fh);
				if (getdns_context_set_dns_root_servers(
				    context, hints)) {
					fprintf(stderr,"Could not set "
					    "root servers from \"%s\"\n",
					    argv[i]);
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_list_destroy(hints);
				hints = NULL;
				break;
			case 's':
				getdns_context_set_resolution_type(
				    context, GETDNS_RESOLUTION_STUB);
				break;
			case 'S':
				calltype = SERVICE;
				break;
			case 't':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "timeout expected "
					    "after -t\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				timeout = strtol(argv[i], &endptr, 10);
				if (*endptr || timeout < 0) {
					fprintf(stderr, "positive "
					    "numeric timeout expected "
					    "after -t\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_context_set_timeout(
					context, timeout);
				goto next;
			case 'e':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "idle timeout expected "
					    "after -t\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				timeout = strtol(argv[i], &endptr, 10);
				if (*endptr || timeout < 0) {
					fprintf(stderr, "positive "
					    "numeric idle timeout expected "
					    "after -t\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_context_set_idle_timeout(
					context, timeout);
				goto next;
			case 'W':
				(void) getdns_context_set_append_name(context,
				    GETDNS_APPEND_NAME_ALWAYS);
				break;
			case '1':
				(void) getdns_context_set_append_name(context,
			GETDNS_APPEND_NAME_ONLY_TO_SINGLE_LABEL_AFTER_FAILURE);
				break;
			case 'M':
				(void) getdns_context_set_append_name(context,
		GETDNS_APPEND_NAME_ONLY_TO_MULTIPLE_LABEL_NAME_AFTER_FAILURE);
				break;
			case 'N':
				(void) getdns_context_set_append_name(context,
				    GETDNS_APPEND_NAME_NEVER);
				break;
			case 'Z':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "suffixes expected"
					    "after -Z\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				if (!(suffixes = getdns_list_create()))
					return GETDNS_RETURN_MEMORY_ERROR;
				suffix = strtok(argv[i], ",");
				j = 0;
				while (suffix) {
					bindata.size = strlen(suffix);
					bindata.data = (void *)suffix;
					(void) getdns_list_set_bindata(
					    suffixes, j++, &bindata);
					suffix = strtok(NULL, ",");
				}
				(void) getdns_context_set_suffix(context,
				    suffixes);
				getdns_list_destroy(suffixes);
				goto next;
			case 'T':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_TCP_ONLY);
				break;
			case 'O':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN);
				break;
			case 'L':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_TLS_ONLY_KEEP_CONNECTIONS_OPEN);
				break;
			case 'E':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_TLS_FIRST_AND_FALL_BACK_TO_TCP_KEEP_CONNECTIONS_OPEN);
				break;
			case 'u':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP);
				break;
			case 'U':
				getdns_context_set_dns_transport(context,
				    GETDNS_TRANSPORT_UDP_ONLY);
				break;
			case 'l':
				if (c[1] != 0 || ++i >= argc || !*argv[i]) {
					fprintf(stderr, "transport list expected "
					    "after -l\n");
					return GETDNS_RETURN_GENERIC_ERROR;
				}
				getdns_transport_list_t transports[10];
				size_t transport_count = sizeof(transports);
				if ((r = fill_transport_list(context, argv[i], transports, &transport_count)) ||
				    (r = getdns_context_set_dns_transport_list(context, 
				                                               transport_count, transports))){
						fprintf(stderr, "Could not set transports\n");
						return r;
				}
				break;
			case 'B':
				batch_mode = 1;
				break;


			default:
				fprintf(stderr, "Unknown option "
				    "\"%c\"\n", *c);
				for (i = 0; i < argc; i++)
					fprintf(stderr, "%d: \"%s\"\n", (int)i, argv[i]);
				return GETDNS_RETURN_GENERIC_ERROR;
			}
		}
next:		;
	}
	if (r)
		return r;
	if (pubkey_pinset && upstream_count) {
		getdns_dict *upstream;
		/* apply the accumulated pubkey pinset to all upstreams: */
		for (i = 0; i < upstream_count; i++) {
			if (r = getdns_list_get_dict(upstream_list, i, &upstream), r) {
				fprintf(stderr, "Failed to get upstream %lu when adding pinset\n", (long unsigned int)i);
				return r;
			}
			if (r = getdns_dict_set_list(upstream, "tls_pubkey_pinset", pubkey_pinset), r) {
				fprintf(stderr, "Failed to set pubkey pinset on upstream %lu\n", (long unsigned int)i);
				return r;
			}
		}
	}
	if (upstream_count &&
	    (r = getdns_context_set_upstream_recursive_servers(
	    context, upstream_list))) {
		fprintf(stderr, "Error setting upstream recursive servers\n");
	}
	if (print_api_info) {
		fprintf(stdout, "%s\n", getdns_pretty_print_dict(
		    getdns_context_get_api_information(context)));
		return CONTINUE;
	}
	if (print_trust_anchors) {
		if (!getdns_context_get_dnssec_trust_anchors(context, &tas)) {
		/* if ((tas = getdns_root_trust_anchor(NULL))) { */
			fprintf(stdout, "%s\n", getdns_pretty_print_list(tas));
			return CONTINUE;
		} else
			return CONTINUE_ERROR;
	}
	return r;
}

static void
dump_ta(FILE *fp)
{
        getdns_list *trust_anchors = NULL;
        getdns_return_t r = 0;
        if ((r = getdns_context_get_dnssec_trust_anchors(
                     context, &trust_anchors))
            || trust_anchors == NULL) {
                fprintf(stderr, "Unable to get trust anchors: %s\n",
                        getdns_get_errorstr_by_id(r));
                return;
        }

        size_t list_len = 0;
	r = getdns_list_get_length(trust_anchors, &list_len);
	if (r) {
		fprintf(stderr, "unable to get length of trust_anchors\n");
		return;
	}

        for (size_t i = 0; i < list_len; i++) {
		getdns_dict *rr = NULL;
                uint8_t *res = NULL;
                size_t res_len;
		r = getdns_list_get_dict(trust_anchors, i , &rr);
		if (r) {
			fprintf(stderr, "unable to get rr from entry "
				"%d: %d\n", i, r);
			return;
		}

		r = getdns_rr_dict2wire(rr, &res, &res_len);
		if (r) {
			fprintf(stderr,
				"unable to convert entry %d "
				"to wire format: %d\n", i, r);
			return;
		}

                if (fwrite(res, 1, res_len, fp) != res_len)
                        fprintf(stderr, "Could not write trust anchor to file\n");
        }
}


FILE *support_out_fp = NULL;

getdns_return_t do_the_call(void)
{
	getdns_return_t r;
	getdns_dict *address = NULL;
	getdns_dict *response = NULL;
	char *response_str;
	uint32_t status;

	if (calltype == HOSTNAME &&
	    !(address = ipaddr_dict(context, name))) {
		fprintf(stderr, "Could not convert \"%s\" "
				"to an IP address", name);
		return GETDNS_RETURN_GOOD;
	}
	if (async) {
		switch (calltype) {
		case GENERAL:
			r = getdns_general(context, name, request_type,
			    extensions, &response, NULL, callback);
			break;
		case ADDRESS:
			r = getdns_address(context, name,
			    extensions, &response, NULL, callback);
			break;
		case HOSTNAME:
			r = getdns_hostname(context, address,
			    extensions, &response, NULL, callback);
			break;
		case SERVICE:
			r = getdns_service(context, name,
			    extensions, &response, NULL, callback);
			break;
		default:
			r = GETDNS_RETURN_GENERIC_ERROR;
			break;
		}
		if (r == GETDNS_RETURN_GOOD && !batch_mode) 
			getdns_context_run(context);
		if (r != GETDNS_RETURN_GOOD)
			fprintf(stderr, "An error occurred: %d '%s'\n", r,
				 getdns_get_errorstr_by_id(r));
	} else {
		switch (calltype) {
		case GENERAL:
			r = getdns_general_sync(context, name,
			    request_type, extensions, &response);
			break;
		case ADDRESS:
			r = getdns_address_sync(context, name,
			    extensions, &response);
			break;
		case HOSTNAME:
			r = getdns_hostname_sync(context, address,
			    extensions, &response);
			break;
		case SERVICE:
			r = getdns_service_sync(context, name,
			    extensions, &response);
			break;
		default:
			r = GETDNS_RETURN_GENERIC_ERROR;
			break;
		}
		if (r != GETDNS_RETURN_GOOD) {
			fprintf(stderr, "An error occurred: %d '%s'\n", r,
				 getdns_get_errorstr_by_id(r));
			return r;
		}
		if (response && !quiet) {
			if ((response_str = json ?
			    getdns_print_json_dict(response, json == 1)
			  : getdns_pretty_print_dict(response))) {

				fprintf( stdout, "SYNC response:\n%s\n"
				       , response_str);
				validate_chain(response);
				free(response_str);
			} else {
				r = GETDNS_RETURN_MEMORY_ERROR;
				fprintf( stderr
				       , "Could not print response\n");
			}
		}
                getdns_list *validation_chain = NULL;
                if ((r = getdns_dict_get_list(
                             response, "validation_chain", &validation_chain))) {
                        fprintf(stderr, "get_list validation_chain: %d (%s)\n",
                                r, getdns_get_errorstr_by_id(r));
                        exit(1);
                }

                if (dump_tree(support_out_fp, response, "validation_chain", NULL))
                        fprintf(stderr, "Could not dump %s to file\n", "validation_chain");

                FILE *tree_out_fp = fopen("treeout", "w");
                assert(tree_out_fp);
                if (dump_tree(tree_out_fp, response, "replies_tree", "answer"))
                        fprintf(stderr, "Could not dump %s to file\n", "replies_tree");
                if (fclose(tree_out_fp)) assert(0);

                FILE *tree_out_ta = fopen("treeout_ta", "w");
                assert(tree_out_ta);
                dump_ta(tree_out_ta);
                if (fclose(tree_out_ta)) assert(0);

		getdns_dict_get_int(response, "status", &status);
		fprintf(stdout, "Response code was: GOOD. Status was: %s\n", 
			 getdns_get_errorstr_by_id(status));
		if (response)
			getdns_dict_destroy(response);
	}
	return r;
}

my_eventloop my_loop;
FILE *fp;

void read_line_cb(void *userarg)
{
	getdns_eventloop_event *read_line_ev = userarg;
	getdns_return_t r;

	char line[1024], *token, *linev[256];
	int linec;

	if (!fgets(line, 1024, fp) || !*line) {
		if (query_file)
			fprintf(stdout,"End of file.");
		my_eventloop_clear(&my_loop.base, read_line_ev);
		return;
	}
	if (query_file)
		fprintf(stdout,"Found query: %s", line);

	linev[0] = __FILE__;
	linec = 1;
	if (!(token = strtok(line, " \t\f\n\r"))) {
		if (! query_file) {
			printf("> ");
			fflush(stdout);
		}
		return;
	}
	if (*token == '#') {
		fprintf(stdout,"Result:      Skipping comment\n");
		if (! query_file) {
			printf("> ");
			fflush(stdout);
		}
		return;
	}
	do linev[linec++] = token;
	while (linec < 256 && (token = strtok(NULL, " \t\f\n\r")));

	if (((r = parse_args(linec, linev)) || (r = do_the_call())) &&
	    (r != CONTINUE && r != CONTINUE_ERROR))
		my_eventloop_clear(&my_loop.base, read_line_ev);

	else if (! query_file) {
		printf("> ");
		fflush(stdout);
	}
}

int
main(int argc, char **argv)
{
	getdns_return_t r;

	name = the_root;
	if ((r = getdns_context_create(&context, 1))) {
		fprintf(stderr, "Create context failed: %d\n", r);
		return r;
	}
	my_eventloop_init(&my_loop);
	if ((r = getdns_context_set_eventloop(context, &my_loop.base)))
		goto done_destroy_context;
	if ((r = getdns_context_set_use_threads(context, 1)))
		goto done_destroy_context;
	extensions = getdns_dict_create();
	if (! extensions) {
		fprintf(stderr, "Could not create extensions dict\n");
		r = GETDNS_RETURN_MEMORY_ERROR;
		goto done_destroy_context;
	}
	if ((r = parse_args(argc, argv)))
		goto done_destroy_context;

	if (query_file) {
		fp = fopen(query_file, "rt");
		if (fp == NULL) {
			fprintf(stderr, "Could not open query file: %s\n", query_file);
			goto done_destroy_context;
		}
	} else
		fp = stdin;

        support_out_fp = fopen("treeout_support", "w");
        assert(support_out_fp);

	/* Make the call */
	if (interactive) {
		getdns_eventloop_event read_line_ev = {
		    &read_line_ev, read_line_cb, NULL, NULL, NULL };
		(void) my_eventloop_schedule(
		    &my_loop.base, fileno(fp), -1, &read_line_ev);
		if (!query_file) {
			printf("> ");
			fflush(stdout);
		}
		my_eventloop_run(&my_loop.base);
	}
	else
		r = do_the_call();

	if ((r == GETDNS_RETURN_GOOD && batch_mode))
		getdns_context_run(context);

	/* Clean up */
        fclose(support_out_fp);
	getdns_dict_destroy(extensions);
done_destroy_context:
	getdns_context_destroy(context);

	if (fp)
		fclose(fp);

	if (r == CONTINUE)
		return 0;
	else if (r == CONTINUE_ERROR)
		return 1;
	fprintf(stdout, "\nAll done.\n");
	return r;
}

int get_rrtype(const char *t) {
	char *endptr;
	int r;

	switch (t[0]) {
	case 'A':
	case 'a': switch (t[1]) {
	          case '\0': return GETDNS_RRTYPE_A;
	          case '6': if (t[2] == '\0') return GETDNS_RRTYPE_A6;
                            return -1;
	          case 'A':
	          case 'a': /* before "AA", final "AA" (GETDNS_RRTYPE_AAAA) */
	                    if ((t[2]|0x20) == 'a' && (t[3]|0x20) == 'a' && t[4] == '\0')
	                              return GETDNS_RRTYPE_AAAA;
	                    return -1;
	          case 'F':
	          case 'f': /* before "AF", final "SDB" (GETDNS_RRTYPE_AFSDB) */
	                    if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'd' && (t[4]|0x20) == 'b' && t[5] == '\0')
	                              return GETDNS_RRTYPE_AFSDB;
	                    return -1;
	          case 'P':
	          case 'p': /* before "AP", final "L" (GETDNS_RRTYPE_APL) */
	                    if ((t[2]|0x20) == 'l' && t[3] == '\0')
	                              return GETDNS_RRTYPE_APL;
	                    return -1;
	          case 'T':
	          case 't': /* before "AT", final "MA" (GETDNS_RRTYPE_ATMA) */
	                    if ((t[2]|0x20) == 'm' && (t[3]|0x20) == 'a' && t[4] == '\0')
	                              return GETDNS_RRTYPE_ATMA;
	                    return -1;
	          case 'X':
	          case 'x': /* before "AX", final "FR" (GETDNS_RRTYPE_AXFR) */
	                    if ((t[2]|0x20) == 'f' && (t[3]|0x20) == 'r' && t[4] == '\0')
	                              return GETDNS_RRTYPE_AXFR;
	                    return -1;
	          default : return -1;
	          };
	case 'C':
	case 'c': switch (t[1]) {
	          case 'A':
	          case 'a': /* before "CA", final "A" (GETDNS_RRTYPE_CAA) */
	                    if ((t[2]|0x20) == 'a' && t[3] == '\0')
	                              return GETDNS_RRTYPE_CAA;
	                    return -1;
	          case 'D':
	          case 'd': switch (t[2]) {
	                    case 'N':
	                    case 'n': /* before "CDN", final "SKEY" (GETDNS_RRTYPE_CDNSKEY) */
	                              if ((t[3]|0x20) == 's' && (t[4]|0x20) == 'k' && (t[5]|0x20) == 'e' && (t[6]|0x20) == 'y' && t[7] == '\0')
	                                        return GETDNS_RRTYPE_CDNSKEY;
	                              return -1;
	                    case 'S':
	                    case 's': if (t[3] == '\0') return GETDNS_RRTYPE_CDS;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'E':
	          case 'e': /* before "CE", final "RT" (GETDNS_RRTYPE_CERT) */
	                    if ((t[2]|0x20) == 'r' && (t[3]|0x20) == 't' && t[4] == '\0')
	                              return GETDNS_RRTYPE_CERT;
	                    return -1;
	          case 'N':
	          case 'n': /* before "CN", final "AME" (GETDNS_RRTYPE_CNAME) */
	                    if ((t[2]|0x20) == 'a' && (t[3]|0x20) == 'm' && (t[4]|0x20) == 'e' && t[5] == '\0')
	                              return GETDNS_RRTYPE_CNAME;
	                    return -1;
	          case 'S':
	          case 's': /* before "CS", final "YNC" (GETDNS_RRTYPE_CSYNC) */
	                    if ((t[2]|0x20) == 'y' && (t[3]|0x20) == 'n' && (t[4]|0x20) == 'c' && t[5] == '\0')
	                              return GETDNS_RRTYPE_CSYNC;
	                    return -1;

	          default : return -1;
	          };
	case 'D':
	case 'd': switch (t[1]) {
	          case 'H':
	          case 'h': /* before "DH", final "CID" (GETDNS_RRTYPE_DHCID) */
	                    if ((t[2]|0x20) == 'c' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'd' && t[5] == '\0')
	                              return GETDNS_RRTYPE_DHCID;
	                    return -1;
	          case 'L':
	          case 'l': /* before "DL", final "V" (GETDNS_RRTYPE_DLV) */
	                    if ((t[2]|0x20) == 'v' && t[3] == '\0')
	                              return GETDNS_RRTYPE_DLV;
	                    return -1;
	          case 'N':
	          case 'n': switch (t[2]) {
	                    case 'A':
	                    case 'a': /* before "DNA", final "ME" (GETDNS_RRTYPE_DNAME) */
	                              if ((t[3]|0x20) == 'm' && (t[4]|0x20) == 'e' && t[5] == '\0')
	                                        return GETDNS_RRTYPE_DNAME;
	                              return -1;
	                    case 'S':
	                    case 's': /* before "DNS", final "KEY" (GETDNS_RRTYPE_DNSKEY) */
	                              if ((t[3]|0x20) == 'k' && (t[4]|0x20) == 'e' && (t[5]|0x20) == 'y' && t[6] == '\0')
	                                        return GETDNS_RRTYPE_DNSKEY;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'S':
	          case 's': if (t[2] == '\0') return GETDNS_RRTYPE_DS;
	                    return -1;
	          default : return -1;
	          };
	case 'E':
	case 'e': switch (t[1]) {
	          case 'I':
	          case 'i': /* before "EI", final "D" (GETDNS_RRTYPE_EID) */
	                    if ((t[2]|0x20) == 'd' && t[3] == '\0')
	                              return GETDNS_RRTYPE_EID;
	                    return -1;
	          case 'U':
	          case 'u': /* before "EU", next "I" */
	                    if ((t[2]|0x20) != 'i')
	                              return -1;
	                    switch (t[3]) {
	                    case '4': /* before "EUI4", final "8" (GETDNS_RRTYPE_EUI48) */
	                              if (t[4] == '8' && t[5] == '\0')
	                                        return GETDNS_RRTYPE_EUI48;
	                              return -1;
	                    case '6': /* before "EUI6", final "4" (GETDNS_RRTYPE_EUI64) */
	                              if (t[4] == '4' && t[5] == '\0')
	                                        return GETDNS_RRTYPE_EUI64;
	                              return -1;
	                    default : return -1;
	                    };
	          default : return -1;
	          };
	case 'G':
	case 'g': switch (t[1]) {
	          case 'I':
	          case 'i': /* before "GI", final "D" (GETDNS_RRTYPE_GID) */
	                    if ((t[2]|0x20) == 'd' && t[3] == '\0')
	                              return GETDNS_RRTYPE_GID;
	                    return -1;
	          case 'P':
	          case 'p': /* before "GP", final "OS" (GETDNS_RRTYPE_GPOS) */
	                    if ((t[2]|0x20) == 'o' && (t[3]|0x20) == 's' && t[4] == '\0')
	                              return GETDNS_RRTYPE_GPOS;
	                    return -1;
	          default : return -1;
	          };
	case 'H':
	case 'h': /* before "H", next "I" */
	          if ((t[1]|0x20) != 'i')
	                    return -1;
	          switch (t[2]) {
	          case 'N':
	          case 'n': /* before "HIN", final "FO" (GETDNS_RRTYPE_HINFO) */
	                    if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0')
	                              return GETDNS_RRTYPE_HINFO;
	                    return -1;
	          case 'P':
	          case 'p': if (t[3] == '\0') return GETDNS_RRTYPE_HIP;
	                    return -1;
	          default : return -1;
	          };
	case 'I':
	case 'i': switch (t[1]) {
	          case 'P':
	          case 'p': /* before "IP", final "SECKEY" (GETDNS_RRTYPE_IPSECKEY) */
	                    if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'e' && (t[4]|0x20) == 'c' && (t[5]|0x20) == 'k' && (t[6]|0x20) == 'e' && (t[7]|0x20) == 'y' && t[8] == '\0')
	                              return GETDNS_RRTYPE_IPSECKEY;
	                    return -1;
	          case 'S':
	          case 's': /* before "IS", final "DN" (GETDNS_RRTYPE_ISDN) */
	                    if ((t[2]|0x20) == 'd' && (t[3]|0x20) == 'n' && t[4] == '\0')
	                              return GETDNS_RRTYPE_ISDN;
	                    return -1;
	          case 'X':
	          case 'x': /* before "IX", final "FR" (GETDNS_RRTYPE_IXFR) */
	                    if ((t[2]|0x20) == 'f' && (t[3]|0x20) == 'r' && t[4] == '\0')
	                              return GETDNS_RRTYPE_IXFR;
	                    return -1;
	          default : return -1;
	          };
	case 'K':
	case 'k': switch (t[1]) {
	          case 'E':
	          case 'e': /* before "KE", final "Y" (GETDNS_RRTYPE_KEY) */
	                    if ((t[2]|0x20) == 'y' && t[3] == '\0')
	                              return GETDNS_RRTYPE_KEY;
	                    return -1;
	          case 'X':
	          case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_KX;
	                    return -1;
	          default : return -1;
	          };
	case 'L':
	case 'l': switch (t[1]) {
	          case '3': /* before "L3", final "2" (GETDNS_RRTYPE_L32) */
	                    if (t[2] == '2' && t[3] == '\0')
	                              return GETDNS_RRTYPE_L32;
	                    return -1;
	          case '6': /* before "L6", final "4" (GETDNS_RRTYPE_L64) */
	                    if (t[2] == '4' && t[3] == '\0')
	                              return GETDNS_RRTYPE_L64;
	                    return -1;
	          case 'O':
	          case 'o': /* before "LO", final "C" (GETDNS_RRTYPE_LOC) */
	                    if ((t[2]|0x20) == 'c' && t[3] == '\0')
	                              return GETDNS_RRTYPE_LOC;
	                    return -1;
	          case 'P':
	          case 'p': if (t[2] == '\0') return GETDNS_RRTYPE_LP;
	                    return -1;
	          default : return -1;
	          };
	case 'M':
	case 'm': switch (t[1]) {
	          case 'A':
	          case 'a': /* before "MA", next "IL" */
	                    if ((t[2]|0x20) != 'i' && (t[3]|0x20) != 'l')
	                              return -1;
	                    switch (t[4]) {
	                    case 'A':
	                    case 'a': if (t[5] == '\0') return GETDNS_RRTYPE_MAILA;
	                              return -1;
	                    case 'B':
	                    case 'b': if (t[5] == '\0') return GETDNS_RRTYPE_MAILB;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'B':
	          case 'b': if (t[2] == '\0') return GETDNS_RRTYPE_MB;
                            return -1;
	          case 'D':
	          case 'd': if (t[2] == '\0') return GETDNS_RRTYPE_MD;
                            return -1;
	          case 'F':
	          case 'f': if (t[2] == '\0') return GETDNS_RRTYPE_MF;
                            return -1;
	          case 'G':
	          case 'g': if (t[2] == '\0') return GETDNS_RRTYPE_MG;
                            return -1;
	          case 'I':
	          case 'i': /* before "MI", final "NFO" (GETDNS_RRTYPE_MINFO) */
	                    if ((t[2]|0x20) == 'n' && (t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0')
	                              return GETDNS_RRTYPE_MINFO;
	                    return -1;
	          case 'R':
	          case 'r': if (t[2] == '\0') return GETDNS_RRTYPE_MR;
	                    return -1;
	          case 'X':
	          case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_MX;
	                    return -1;
	          default : return -1;
	          };
	case 'N':
	case 'n': switch (t[1]) {
	          case 'A':
	          case 'a': /* before "NA", final "PTR" (GETDNS_RRTYPE_NAPTR) */
	                    if ((t[2]|0x20) == 'p' && (t[3]|0x20) == 't' && (t[4]|0x20) == 'r' && t[5] == '\0')
	                              return GETDNS_RRTYPE_NAPTR;
	                    return -1;
	          case 'I':
	          case 'i': switch (t[2]) {
	                    case 'D':
	                    case 'd': if (t[3] == '\0') return GETDNS_RRTYPE_NID;
	                              return -1;
	                    case 'M':
	                    case 'm': /* before "NIM", final "LOC" (GETDNS_RRTYPE_NIMLOC) */
	                              if ((t[3]|0x20) == 'l' && (t[4]|0x20) == 'o' && (t[5]|0x20) == 'c' && t[6] == '\0')
	                                        return GETDNS_RRTYPE_NIMLOC;
	                              return -1;
	                    case 'N':
	                    case 'n': /* before "NIN", final "FO" (GETDNS_RRTYPE_NINFO) */
	                              if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0')
	                                        return GETDNS_RRTYPE_NINFO;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'S':
	          case 's': switch (t[2]) {
	                    case '\0': return GETDNS_RRTYPE_NS;
	                    case 'A':
	                    case 'a': /* before "NSA", final "P" (GETDNS_RRTYPE_NSAP) */
	                              if ((t[3]|0x20) == 'p' && t[4] == '\0')
	                                        return GETDNS_RRTYPE_NSAP;
	                              return -1;
	                    case 'E':
	                    case 'e': /* before "NSE", final "C3PARAM" (GETDNS_RRTYPE_NSEC3PARAM) */
	                              if ((t[3]|0x20) == 'c' && t[4] == '3' && (t[5]|0x20) == 'p' && (t[6]|0x20) == 'a' && (t[7]|0x20) == 'r' && (t[8]|0x20) == 'a' && (t[9]|0x20) == 'm' && t[10] == '\0')
	                                        return GETDNS_RRTYPE_NSEC3PARAM;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'U':
	          case 'u': /* before "NU", final "LL" (GETDNS_RRTYPE_NULL) */
	                    if ((t[2]|0x20) == 'l' && (t[3]|0x20) == 'l' && t[4] == '\0')
	                              return GETDNS_RRTYPE_NULL;
	                    return -1;
	          case 'X':
	          case 'x': /* before "NX", final "T" (GETDNS_RRTYPE_NXT) */
	                    if ((t[2]|0x20) == 't' && t[3] == '\0')
	                              return GETDNS_RRTYPE_NXT;
	                    return -1;
	          default : return -1;
	          };
	case 'O':
	case 'o': /* before "O", next "P" */
	          if ((t[1]|0x20) != 'p')
	                    return -1;
	          switch (t[2]) {
	          case 'E':
	          case 'e': /* before "OPE", final "NPGPKEY" (GETDNS_RRTYPE_OPENPGPKEY) */
	                    if ((t[3]|0x20) == 'n' && (t[4]|0x20) == 'p' && (t[5]|0x20) == 'g' && (t[6]|0x20) == 'p' && (t[7]|0x20) == 'k' && (t[8]|0x20) == 'e' && (t[9]|0x20) == 'y' && t[10] == '\0')
	                              return GETDNS_RRTYPE_OPENPGPKEY;
	                    return -1;
	          case 'T':
	          case 't': if (t[3] == '\0') return GETDNS_RRTYPE_OPT;
	                    return -1;
	          default : return -1;
	          };
	case 'P':
	case 'p': switch (t[1]) {
	          case 'T':
	          case 't': /* before "PT", final "R" (GETDNS_RRTYPE_PTR) */
	                    if ((t[2]|0x20) == 'r' && t[3] == '\0')
	                              return GETDNS_RRTYPE_PTR;
	                    return -1;
	          case 'X':
	          case 'x': if (t[2] == '\0') return GETDNS_RRTYPE_PX;
	                    return -1;
	          default : return -1;
	          };
	case 'R':
	case 'r': switch (t[1]) {
	          case 'K':
	          case 'k': /* before "RK", final "EY" (GETDNS_RRTYPE_RKEY) */
	                    if ((t[2]|0x20) == 'e' && (t[3]|0x20) == 'y' && t[4] == '\0')
	                              return GETDNS_RRTYPE_RKEY;
	                    return -1;
	          case 'P':
	          case 'p': if (t[2] == '\0') return GETDNS_RRTYPE_RP;
	                    return -1;
	          case 'R':
	          case 'r': /* before "RR", final "SIG" (GETDNS_RRTYPE_RRSIG) */
	                    if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'g' && t[5] == '\0')
	                              return GETDNS_RRTYPE_RRSIG;
	                    return -1;
	          case 'T':
	          case 't': if (t[2] == '\0') return GETDNS_RRTYPE_RT;
	                    return -1;
	          default : return -1;
	          };
	case 'S':
	case 's': switch (t[1]) {
	          case 'I':
	          case 'i': switch (t[2]) {
	                    case 'G':
	                    case 'g': if (t[3] == '\0') return GETDNS_RRTYPE_SIG;
	                              return -1;
	                    case 'N':
	                    case 'n': /* before "SIN", final "K" (GETDNS_RRTYPE_SINK) */
	                              if ((t[3]|0x20) == 'k' && t[4] == '\0')
	                                        return GETDNS_RRTYPE_SINK;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'O':
	          case 'o': /* before "SO", final "A" (GETDNS_RRTYPE_SOA) */
	                    if ((t[2]|0x20) == 'a' && t[3] == '\0')
	                              return GETDNS_RRTYPE_SOA;
	                    return -1;
	          case 'P':
	          case 'p': /* before "SP", final "F" (GETDNS_RRTYPE_SPF) */
	                    if ((t[2]|0x20) == 'f' && t[3] == '\0')
	                              return GETDNS_RRTYPE_SPF;
	                    return -1;
	          case 'R':
	          case 'r': /* before "SR", final "V" (GETDNS_RRTYPE_SRV) */
	                    if ((t[2]|0x20) == 'v' && t[3] == '\0')
	                              return GETDNS_RRTYPE_SRV;
	                    return -1;
	          case 'S':
	          case 's': /* before "SS", final "HFP" (GETDNS_RRTYPE_SSHFP) */
	                    if ((t[2]|0x20) == 'h' && (t[3]|0x20) == 'f' && (t[4]|0x20) == 'p' && t[5] == '\0')
	                              return GETDNS_RRTYPE_SSHFP;
	                    return -1;
	          default : return -1;
	          };
	case 'T':
	case 't': switch (t[1]) {
	          case 'A':
	          case 'a': /* before "TA", final "LINK" (GETDNS_RRTYPE_TALINK) */
	                    if ((t[2]|0x20) == 'l' && (t[3]|0x20) == 'i' && (t[4]|0x20) == 'n' && (t[5]|0x20) == 'k' && t[6] == '\0')
	                              return GETDNS_RRTYPE_TALINK;
	                    return -1;
	          case 'K':
	          case 'k': /* before "TK", final "EY" (GETDNS_RRTYPE_TKEY) */
	                    if ((t[2]|0x20) == 'e' && (t[3]|0x20) == 'y' && t[4] == '\0')
	                              return GETDNS_RRTYPE_TKEY;
	                    return -1;
	          case 'L':
	          case 'l': /* before "TL", final "SA" (GETDNS_RRTYPE_TLSA) */
	                    if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'a' && t[4] == '\0')
	                              return GETDNS_RRTYPE_TLSA;
	                    return -1;
	          case 'S':
	          case 's': /* before "TS", final "IG" (GETDNS_RRTYPE_TSIG) */
	                    if ((t[2]|0x20) == 'i' && (t[3]|0x20) == 'g' && t[4] == '\0')
	                              return GETDNS_RRTYPE_TSIG;
	                    return -1;
	          case 'X':
	          case 'x': /* before "TX", final "T" (GETDNS_RRTYPE_TXT) */
	                    if ((t[2]|0x20) == 't' && t[3] == '\0')
	                              return GETDNS_RRTYPE_TXT;
	                    return -1;
	          case 'Y':
		  case 'y': /* before "TY", then "PE" followed by a number */
	                    if ((t[2]|0x20) == 'p' && (t[3]|0x20) == 'e' && t[4] != '\0') {
	                            r = (int) strtol(t + 4, &endptr, 10);
	                            if (*endptr == '\0') return r;
	                    }
	                    return -1;
	          default : return -1;
	          };
	case 'U':
	case 'u': switch (t[1]) {
	          case 'I':
	          case 'i': switch (t[2]) {
	                    case 'D':
	                    case 'd': if (t[3] == '\0') return GETDNS_RRTYPE_UID;
	                              return -1;
	                    case 'N':
	                    case 'n': /* before "UIN", final "FO" (GETDNS_RRTYPE_UINFO) */
	                              if ((t[3]|0x20) == 'f' && (t[4]|0x20) == 'o' && t[5] == '\0')
	                                        return GETDNS_RRTYPE_UINFO;
	                              return -1;
	                    default : return -1;
	                    };
	          case 'N':
	          case 'n': /* before "UN", final "SPEC" (GETDNS_RRTYPE_UNSPEC) */
	                    if ((t[2]|0x20) == 's' && (t[3]|0x20) == 'p' && (t[4]|0x20) == 'e' && (t[5]|0x20) == 'c' && t[6] == '\0')
	                              return GETDNS_RRTYPE_UNSPEC;
	                    return -1;
	          case 'R':
	          case 'r': /* before "UR", final "I" (GETDNS_RRTYPE_URI) */
	                    if ((t[2]|0x20) == 'i' && t[3] == '\0')
	                              return GETDNS_RRTYPE_URI;
	                    return -1;
	          default : return -1;
	          };
	case 'W':
	case 'w': /* before "W", final "KS" (GETDNS_RRTYPE_WKS) */
	          if ((t[1]|0x20) == 'k' && (t[2]|0x20) == 's' && t[3] == '\0')
	                    return GETDNS_RRTYPE_WKS;
	          return -1;
	case 'X':
	case 'x': /* before "X", final "25" (GETDNS_RRTYPE_X25) */
	          if (t[1] == '2' && t[2] == '5' && t[3] == '\0')
	                    return GETDNS_RRTYPE_X25;
	          return -1;
	default : return -1;
	};
}

/*
 * Copyright (c) 2013, NLNet Labs, Verisign, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the names of the copyright holders nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Local Variables:  */
/* c-file-style: "linux" */
/* End:              */