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

#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>

#include <isc/app.h>
#include <isc/buffer.h>
#include <isc/commandline.h>
#include <isc/event.h>
#include <isc/netaddr.h>
#include <isc/parseint.h>
#include <isc/print.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/util.h>

#include <dns/byaddr.h>
#include <dns/fixedname.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>

#include <dig/dig.h>

#if defined(HAVE_READLINE)
#if defined(HAVE_EDIT_READLINE_READLINE_H)
#include <edit/readline/readline.h>
#if defined(HAVE_EDIT_READLINE_HISTORY_H)
#include <edit/readline/history.h>
#endif /* if defined(HAVE_EDIT_READLINE_HISTORY_H) */
#elif defined(HAVE_EDITLINE_READLINE_H)
#include <editline/readline.h>
#elif defined(HAVE_READLINE_READLINE_H)
/* Prevent deprecated functions being declared. */
#define _FUNCTION_DEF 1
/* Ensure rl_message() gets prototype. */
#define USE_VARARGS   1
#define PREFER_STDARG 1
#include <readline/readline.h>
#if defined(HAVE_READLINE_HISTORY_H)
#include <readline/history.h>
#endif /* if defined(HAVE_READLINE_HISTORY_H) */
#endif /* if defined(HAVE_EDIT_READLINE_READLINE_H) */
#endif /* if defined(HAVE_READLINE) */

static bool short_form = true, tcpmode = false, tcpmode_set = false,
	    identify = false, stats = true, comments = true,
	    section_question = true, section_answer = true,
	    section_authority = true, section_additional = true, recurse = true,
	    aaonly = false, nofail = true, default_lookups = true,
	    a_noanswer = false;

static bool interactive;

static bool in_use = false;
static char defclass[MXRD] = "IN";
static char deftype[MXRD] = "A";
static isc_event_t *global_event = NULL;
static int query_error = 1, print_error = 0;

static char domainopt[DNS_NAME_MAXTEXT];

static const char *rcodetext[] = { "NOERROR",	 "FORMERR",    "SERVFAIL",
				   "NXDOMAIN",	 "NOTIMP",     "REFUSED",
				   "YXDOMAIN",	 "YXRRSET",    "NXRRSET",
				   "NOTAUTH",	 "NOTZONE",    "RESERVED11",
				   "RESERVED12", "RESERVED13", "RESERVED14",
				   "RESERVED15", "BADVERS" };

static const char *rtypetext[] = {
	"rtype_0 = ",	       /* 0 */
	"internet address = ", /* 1 */
	"nameserver = ",       /* 2 */
	"md = ",	       /* 3 */
	"mf = ",	       /* 4 */
	"canonical name = ",   /* 5 */
	"soa = ",	       /* 6 */
	"mb = ",	       /* 7 */
	"mg = ",	       /* 8 */
	"mr = ",	       /* 9 */
	"rtype_10 = ",	       /* 10 */
	"protocol = ",	       /* 11 */
	"name = ",	       /* 12 */
	"hinfo = ",	       /* 13 */
	"minfo = ",	       /* 14 */
	"mail exchanger = ",   /* 15 */
	"text = ",	       /* 16 */
	"rp = ",	       /* 17 */
	"afsdb = ",	       /* 18 */
	"x25 address = ",      /* 19 */
	"isdn address = ",     /* 20 */
	"rt = ",	       /* 21 */
	"nsap = ",	       /* 22 */
	"nsap_ptr = ",	       /* 23 */
	"signature = ",	       /* 24 */
	"key = ",	       /* 25 */
	"px = ",	       /* 26 */
	"gpos = ",	       /* 27 */
	"has AAAA address ",   /* 28 */
	"loc = ",	       /* 29 */
	"next = ",	       /* 30 */
	"rtype_31 = ",	       /* 31 */
	"rtype_32 = ",	       /* 32 */
	"service = ",	       /* 33 */
	"rtype_34 = ",	       /* 34 */
	"naptr = ",	       /* 35 */
	"kx = ",	       /* 36 */
	"cert = ",	       /* 37 */
	"v6 address = ",       /* 38 */
	"dname = ",	       /* 39 */
	"rtype_40 = ",	       /* 40 */
	"optional = "	       /* 41 */
};

#define N_KNOWN_RRTYPES (sizeof(rtypetext) / sizeof(rtypetext[0]))

static void
flush_lookup_list(void);
static void
getinput(isc_task_t *task, isc_event_t *event);

static char *
rcode_totext(dns_rcode_t rcode) {
	static char buf[sizeof("?65535")];
	union {
		const char *consttext;
		char *deconsttext;
	} totext;

	if (rcode >= (sizeof(rcodetext) / sizeof(rcodetext[0]))) {
		snprintf(buf, sizeof(buf), "?%u", rcode);
		totext.deconsttext = buf;
	} else {
		totext.consttext = rcodetext[rcode];
	}
	return (totext.deconsttext);
}

static void
query_finished(void) {
	isc_event_t *event = global_event;

	flush_lookup_list();
	debug("dighost_shutdown()");

	if (!in_use) {
		isc_app_shutdown();
		return;
	}

	isc_task_send(global_task, &event);
}

static void
printsoa(dns_rdata_t *rdata) {
	dns_rdata_soa_t soa;
	isc_result_t result;
	char namebuf[DNS_NAME_FORMATSIZE];

	result = dns_rdata_tostruct(rdata, &soa, NULL);
	check_result(result, "dns_rdata_tostruct");

	dns_name_format(&soa.origin, namebuf, sizeof(namebuf));
	printf("\torigin = %s\n", namebuf);
	dns_name_format(&soa.contact, namebuf, sizeof(namebuf));
	printf("\tmail addr = %s\n", namebuf);
	printf("\tserial = %u\n", soa.serial);
	printf("\trefresh = %u\n", soa.refresh);
	printf("\tretry = %u\n", soa.retry);
	printf("\texpire = %u\n", soa.expire);
	printf("\tminimum = %u\n", soa.minimum);
	dns_rdata_freestruct(&soa);
}

static void
printaddr(dns_rdata_t *rdata) {
	isc_result_t result;
	char text[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
	isc_buffer_t b;

	isc_buffer_init(&b, text, sizeof(text));
	result = dns_rdata_totext(rdata, NULL, &b);
	check_result(result, "dns_rdata_totext");
	printf("Address: %.*s\n", (int)isc_buffer_usedlength(&b),
	       (char *)isc_buffer_base(&b));
}

static void
printrdata(dns_rdata_t *rdata) {
	isc_result_t result;
	isc_buffer_t *b = NULL;
	unsigned int size = 1024;
	bool done = false;

	if (rdata->type < N_KNOWN_RRTYPES) {
		printf("%s", rtypetext[rdata->type]);
	} else {
		printf("rdata_%d = ", rdata->type);
	}

	while (!done) {
		isc_buffer_allocate(mctx, &b, size);
		result = dns_rdata_totext(rdata, NULL, b);
		if (result == ISC_R_SUCCESS) {
			printf("%.*s\n", (int)isc_buffer_usedlength(b),
			       (char *)isc_buffer_base(b));
			done = true;
		} else if (result != ISC_R_NOSPACE) {
			check_result(result, "dns_rdata_totext");
		}
		isc_buffer_free(&b);
		size *= 2;
	}
}

static isc_result_t
printsection(dig_query_t *query, dns_message_t *msg, bool headers,
	     dns_section_t section) {
	isc_result_t result, loopresult;
	dns_name_t *name;
	dns_rdataset_t *rdataset = NULL;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	char namebuf[DNS_NAME_FORMATSIZE];

	UNUSED(query);
	UNUSED(headers);

	debug("printsection()");

	result = dns_message_firstname(msg, section);
	if (result == ISC_R_NOMORE) {
		return (ISC_R_SUCCESS);
	} else if (result != ISC_R_SUCCESS) {
		return (result);
	}
	for (;;) {
		name = NULL;
		dns_message_currentname(msg, section, &name);
		for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
		     rdataset = ISC_LIST_NEXT(rdataset, link))
		{
			loopresult = dns_rdataset_first(rdataset);
			while (loopresult == ISC_R_SUCCESS) {
				dns_rdataset_current(rdataset, &rdata);
				switch (rdata.type) {
				case dns_rdatatype_a:
				case dns_rdatatype_aaaa:
					if (section != DNS_SECTION_ANSWER) {
						goto def_short_section;
					}
					dns_name_format(name, namebuf,
							sizeof(namebuf));
					printf("Name:\t%s\n", namebuf);
					printaddr(&rdata);
					break;
				case dns_rdatatype_soa:
					dns_name_format(name, namebuf,
							sizeof(namebuf));
					printf("%s\n", namebuf);
					printsoa(&rdata);
					break;
				default:
				def_short_section:
					dns_name_format(name, namebuf,
							sizeof(namebuf));
					printf("%s\t", namebuf);
					printrdata(&rdata);
					break;
				}
				dns_rdata_reset(&rdata);
				loopresult = dns_rdataset_next(rdataset);
			}
		}
		result = dns_message_nextname(msg, section);
		if (result == ISC_R_NOMORE) {
			break;
		} else if (result != ISC_R_SUCCESS) {
			return (result);
		}
	}
	return (ISC_R_SUCCESS);
}

static isc_result_t
detailsection(dig_query_t *query, dns_message_t *msg, bool headers,
	      dns_section_t section) {
	isc_result_t result, loopresult;
	dns_name_t *name;
	dns_rdataset_t *rdataset = NULL;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	char namebuf[DNS_NAME_FORMATSIZE];

	UNUSED(query);

	debug("detailsection()");

	if (headers) {
		switch (section) {
		case DNS_SECTION_QUESTION:
			puts("    QUESTIONS:");
			break;
		case DNS_SECTION_ANSWER:
			puts("    ANSWERS:");
			break;
		case DNS_SECTION_AUTHORITY:
			puts("    AUTHORITY RECORDS:");
			break;
		case DNS_SECTION_ADDITIONAL:
			puts("    ADDITIONAL RECORDS:");
			break;
		}
	}

	result = dns_message_firstname(msg, section);
	if (result == ISC_R_NOMORE) {
		return (ISC_R_SUCCESS);
	} else if (result != ISC_R_SUCCESS) {
		return (result);
	}
	for (;;) {
		name = NULL;
		dns_message_currentname(msg, section, &name);
		for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
		     rdataset = ISC_LIST_NEXT(rdataset, link))
		{
			if (section == DNS_SECTION_QUESTION) {
				dns_name_format(name, namebuf, sizeof(namebuf));
				printf("\t%s, ", namebuf);
				dns_rdatatype_format(rdataset->type, namebuf,
						     sizeof(namebuf));
				printf("type = %s, ", namebuf);
				dns_rdataclass_format(rdataset->rdclass,
						      namebuf, sizeof(namebuf));
				printf("class = %s\n", namebuf);
			}
			loopresult = dns_rdataset_first(rdataset);
			while (loopresult == ISC_R_SUCCESS) {
				dns_rdataset_current(rdataset, &rdata);

				dns_name_format(name, namebuf, sizeof(namebuf));
				printf("    ->  %s\n", namebuf);

				switch (rdata.type) {
				case dns_rdatatype_soa:
					printsoa(&rdata);
					break;
				default:
					printf("\t");
					printrdata(&rdata);
				}
				dns_rdata_reset(&rdata);
				printf("\tttl = %u\n", rdataset->ttl);
				loopresult = dns_rdataset_next(rdataset);
			}
		}
		result = dns_message_nextname(msg, section);
		if (result == ISC_R_NOMORE) {
			break;
		} else if (result != ISC_R_SUCCESS) {
			return (result);
		}
	}
	return (ISC_R_SUCCESS);
}

static void
received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) {
	UNUSED(bytes);
	UNUSED(from);
	UNUSED(query);
}

static void
trying(char *frm, dig_lookup_t *lookup) {
	UNUSED(frm);
	UNUSED(lookup);
}

static void
chase_cnamechain(dns_message_t *msg, dns_name_t *qname) {
	isc_result_t result;
	dns_rdataset_t *rdataset;
	dns_rdata_cname_t cname;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	unsigned int i = msg->counts[DNS_SECTION_ANSWER];

	while (i-- > 0) {
		rdataset = NULL;
		result = dns_message_findname(msg, DNS_SECTION_ANSWER, qname,
					      dns_rdatatype_cname, 0, NULL,
					      &rdataset);
		if (result != ISC_R_SUCCESS) {
			return;
		}
		result = dns_rdataset_first(rdataset);
		check_result(result, "dns_rdataset_first");
		dns_rdata_reset(&rdata);
		dns_rdataset_current(rdataset, &rdata);
		result = dns_rdata_tostruct(&rdata, &cname, NULL);
		check_result(result, "dns_rdata_tostruct");
		dns_name_copynf(&cname.cname, qname);
		dns_rdata_freestruct(&cname);
	}
}

static isc_result_t
printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg,
	     bool headers) {
	char servtext[ISC_SOCKADDR_FORMATSIZE];

	UNUSED(msgbuf);

	/* I've we've gotten this far, we've reached a server. */
	query_error = 0;

	debug("printmessage()");

	if (!default_lookups || query->lookup->rdtype == dns_rdatatype_a) {
		isc_sockaddr_format(&query->sockaddr, servtext,
				    sizeof(servtext));
		printf("Server:\t\t%s\n", query->userarg);
		printf("Address:\t%s\n", servtext);

		puts("");
	}

	if (!short_form) {
		puts("------------");
		/*		detailheader(query, msg);*/
		detailsection(query, msg, true, DNS_SECTION_QUESTION);
		detailsection(query, msg, true, DNS_SECTION_ANSWER);
		detailsection(query, msg, true, DNS_SECTION_AUTHORITY);
		detailsection(query, msg, true, DNS_SECTION_ADDITIONAL);
		puts("------------");
	}

	if (msg->rcode != 0) {
		char nametext[DNS_NAME_FORMATSIZE];
		dns_name_format(query->lookup->name, nametext,
				sizeof(nametext));
		printf("** server can't find %s: %s\n", nametext,
		       rcode_totext(msg->rcode));
		debug("returning with rcode == 0");

		/* the lookup failed */
		print_error |= 1;
		return (ISC_R_SUCCESS);
	}

	if (default_lookups && query->lookup->rdtype == dns_rdatatype_a) {
		char namestr[DNS_NAME_FORMATSIZE];
		dig_lookup_t *lookup;
		dns_fixedname_t fixed;
		dns_name_t *name;

		/* Add AAAA lookup. */
		name = dns_fixedname_initname(&fixed);
		dns_name_copynf(query->lookup->name, name);
		chase_cnamechain(msg, name);
		dns_name_format(name, namestr, sizeof(namestr));
		lookup = clone_lookup(query->lookup, false);
		if (lookup != NULL) {
			strlcpy(lookup->textname, namestr,
				sizeof(lookup->textname));
			lookup->rdtype = dns_rdatatype_aaaa;
			lookup->rdtypeset = true;
			lookup->origin = NULL;
			lookup->retries = tries;
			ISC_LIST_APPEND(lookup_list, lookup, link);
		}
	}

	if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0 &&
	    (!default_lookups || query->lookup->rdtype == dns_rdatatype_a))
	{
		puts("Non-authoritative answer:");
	}
	if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ANSWER])) {
		printsection(query, msg, headers, DNS_SECTION_ANSWER);
	} else {
		if (default_lookups && query->lookup->rdtype == dns_rdatatype_a)
		{
			a_noanswer = true;
		} else if (!default_lookups ||
			   (query->lookup->rdtype == dns_rdatatype_aaaa &&
			    a_noanswer))
		{
			printf("*** Can't find %s: No answer\n",
			       query->lookup->textname);
		}
	}

	if (((msg->flags & DNS_MESSAGEFLAG_AA) == 0) &&
	    (query->lookup->rdtype != dns_rdatatype_a) &&
	    (query->lookup->rdtype != dns_rdatatype_aaaa))
	{
		puts("\nAuthoritative answers can be found from:");
		printsection(query, msg, headers, DNS_SECTION_AUTHORITY);
		printsection(query, msg, headers, DNS_SECTION_ADDITIONAL);
	}
	return (ISC_R_SUCCESS);
}

static void
show_settings(bool full, bool serv_only) {
	dig_server_t *srv;
	isc_sockaddr_t sockaddr;
	dig_searchlist_t *listent;
	isc_result_t result;

	srv = ISC_LIST_HEAD(server_list);

	while (srv != NULL) {
		char sockstr[ISC_SOCKADDR_FORMATSIZE];

		result = get_address(srv->servername, port, &sockaddr);
		check_result(result, "get_address");

		isc_sockaddr_format(&sockaddr, sockstr, sizeof(sockstr));
		printf("Default server: %s\nAddress: %s\n", srv->userarg,
		       sockstr);
		if (!full) {
			return;
		}
		srv = ISC_LIST_NEXT(srv, link);
	}
	if (serv_only) {
		return;
	}
	printf("\nSet options:\n");
	printf("  %s\t\t\t%s\t\t%s\n", tcpmode ? "vc" : "novc",
	       short_form ? "nodebug" : "debug", debugging ? "d2" : "nod2");
	printf("  %s\t\t%s\n", usesearch ? "search" : "nosearch",
	       recurse ? "recurse" : "norecurse");
	printf("  timeout = %u\t\tretry = %d\tport = %u\tndots = %d\n", timeout,
	       tries, port, ndots);
	printf("  querytype = %-8s\tclass = %s\n", deftype, defclass);
	printf("  srchlist = ");
	for (listent = ISC_LIST_HEAD(search_list); listent != NULL;
	     listent = ISC_LIST_NEXT(listent, link))
	{
		printf("%s", listent->origin);
		if (ISC_LIST_NEXT(listent, link) != NULL) {
			printf("/");
		}
	}
	printf("\n");
}

static bool
testtype(char *typetext) {
	isc_result_t result;
	isc_textregion_t tr;
	dns_rdatatype_t rdtype;

	tr.base = typetext;
	tr.length = strlen(typetext);
	result = dns_rdatatype_fromtext(&rdtype, &tr);
	if (result == ISC_R_SUCCESS) {
		return (true);
	} else {
		printf("unknown query type: %s\n", typetext);
		return (false);
	}
}

static bool
testclass(char *typetext) {
	isc_result_t result;
	isc_textregion_t tr;
	dns_rdataclass_t rdclass;

	tr.base = typetext;
	tr.length = strlen(typetext);
	result = dns_rdataclass_fromtext(&rdclass, &tr);
	if (result == ISC_R_SUCCESS) {
		return (true);
	} else {
		printf("unknown query class: %s\n", typetext);
		return (false);
	}
}

static void
set_port(const char *value) {
	uint32_t n;
	isc_result_t result = parse_uint(&n, value, 65535, "port");
	if (result == ISC_R_SUCCESS) {
		port = (uint16_t)n;
	}
}

static void
set_timeout(const char *value) {
	uint32_t n;
	isc_result_t result = parse_uint(&n, value, UINT_MAX, "timeout");
	if (result == ISC_R_SUCCESS) {
		timeout = n;
	}
}

static void
set_tries(const char *value) {
	uint32_t n;
	isc_result_t result = parse_uint(&n, value, INT_MAX, "tries");
	if (result == ISC_R_SUCCESS) {
		tries = n;
	}
}

static void
set_ndots(const char *value) {
	uint32_t n;
	isc_result_t result = parse_uint(&n, value, 128, "ndots");
	if (result == ISC_R_SUCCESS) {
		ndots = n;
	}
}

static void
version(void) {
	fputs("nslookup " VERSION "\n", stderr);
}

static void
setoption(char *opt) {
	size_t l = strlen(opt);

#define CHECKOPT(A, N) \
	((l >= N) && (l < sizeof(A)) && (strncasecmp(opt, A, l) == 0))

	if (CHECKOPT("all", 3)) {
		show_settings(true, false);
	} else if (strncasecmp(opt, "class=", 6) == 0) {
		if (testclass(&opt[6])) {
			strlcpy(defclass, &opt[6], sizeof(defclass));
		}
	} else if (strncasecmp(opt, "cl=", 3) == 0) {
		if (testclass(&opt[3])) {
			strlcpy(defclass, &opt[3], sizeof(defclass));
		}
	} else if (strncasecmp(opt, "type=", 5) == 0) {
		if (testtype(&opt[5])) {
			strlcpy(deftype, &opt[5], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "ty=", 3) == 0) {
		if (testtype(&opt[3])) {
			strlcpy(deftype, &opt[3], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "querytype=", 10) == 0) {
		if (testtype(&opt[10])) {
			strlcpy(deftype, &opt[10], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "query=", 6) == 0) {
		if (testtype(&opt[6])) {
			strlcpy(deftype, &opt[6], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "qu=", 3) == 0) {
		if (testtype(&opt[3])) {
			strlcpy(deftype, &opt[3], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "q=", 2) == 0) {
		if (testtype(&opt[2])) {
			strlcpy(deftype, &opt[2], sizeof(deftype));
			default_lookups = false;
		}
	} else if (strncasecmp(opt, "domain=", 7) == 0) {
		strlcpy(domainopt, &opt[7], sizeof(domainopt));
		set_search_domain(domainopt);
		usesearch = true;
	} else if (strncasecmp(opt, "do=", 3) == 0) {
		strlcpy(domainopt, &opt[3], sizeof(domainopt));
		set_search_domain(domainopt);
		usesearch = true;
	} else if (strncasecmp(opt, "port=", 5) == 0) {
		set_port(&opt[5]);
	} else if (strncasecmp(opt, "po=", 3) == 0) {
		set_port(&opt[3]);
	} else if (strncasecmp(opt, "timeout=", 8) == 0) {
		set_timeout(&opt[8]);
	} else if (strncasecmp(opt, "t=", 2) == 0) {
		set_timeout(&opt[2]);
	} else if (CHECKOPT("recurse", 3)) {
		recurse = true;
	} else if (CHECKOPT("norecurse", 5)) {
		recurse = false;
	} else if (strncasecmp(opt, "retry=", 6) == 0) {
		set_tries(&opt[6]);
	} else if (strncasecmp(opt, "ret=", 4) == 0) {
		set_tries(&opt[4]);
	} else if (CHECKOPT("defname", 3)) {
		usesearch = true;
	} else if (CHECKOPT("nodefname", 5)) {
		usesearch = false;
	} else if (CHECKOPT("vc", 2)) {
		tcpmode = true;
		tcpmode_set = true;
	} else if (CHECKOPT("novc", 4)) {
		tcpmode = false;
		tcpmode_set = true;
	} else if (CHECKOPT("debug", 3)) {
		short_form = false;
		showsearch = true;
	} else if (CHECKOPT("nodebug", 5)) {
		short_form = true;
		showsearch = false;
	} else if (CHECKOPT("d2", 2)) {
		debugging = true;
	} else if (CHECKOPT("nod2", 4)) {
		debugging = false;
	} else if (CHECKOPT("search", 3)) {
		usesearch = true;
	} else if (CHECKOPT("nosearch", 5)) {
		usesearch = false;
	} else if (CHECKOPT("sil", 3)) {
		/* deprecation_msg = false; */
	} else if (CHECKOPT("fail", 3)) {
		nofail = false;
	} else if (CHECKOPT("nofail", 5)) {
		nofail = true;
	} else if (strncasecmp(opt, "ndots=", 6) == 0) {
		set_ndots(&opt[6]);
	} else {
		printf("*** Invalid option: %s\n", opt);
	}
}

static void
addlookup(char *opt) {
	dig_lookup_t *lookup;
	isc_result_t result;
	isc_textregion_t tr;
	dns_rdatatype_t rdtype;
	dns_rdataclass_t rdclass;
	char store[MXNAME];

	debug("addlookup()");

	a_noanswer = false;

	tr.base = deftype;
	tr.length = strlen(deftype);
	result = dns_rdatatype_fromtext(&rdtype, &tr);
	if (result != ISC_R_SUCCESS) {
		printf("unknown query type: %s\n", deftype);
		rdclass = dns_rdatatype_a;
	}
	tr.base = defclass;
	tr.length = strlen(defclass);
	result = dns_rdataclass_fromtext(&rdclass, &tr);
	if (result != ISC_R_SUCCESS) {
		printf("unknown query class: %s\n", defclass);
		rdclass = dns_rdataclass_in;
	}
	lookup = make_empty_lookup();
	if (get_reverse(store, sizeof(store), opt, true) == ISC_R_SUCCESS) {
		strlcpy(lookup->textname, store, sizeof(lookup->textname));
		lookup->rdtype = dns_rdatatype_ptr;
		lookup->rdtypeset = true;
	} else {
		strlcpy(lookup->textname, opt, sizeof(lookup->textname));
		lookup->rdtype = rdtype;
		lookup->rdtypeset = true;
	}
	lookup->rdclass = rdclass;
	lookup->rdclassset = true;
	lookup->trace = false;
	lookup->trace_root = lookup->trace;
	lookup->ns_search_only = false;
	lookup->identify = identify;
	lookup->recurse = recurse;
	lookup->aaonly = aaonly;
	lookup->retries = tries;
	lookup->comments = comments;
	if (lookup->rdtype == dns_rdatatype_any && !tcpmode_set) {
		lookup->tcp_mode = true;
	} else {
		lookup->tcp_mode = tcpmode;
	}
	lookup->stats = stats;
	lookup->section_question = section_question;
	lookup->section_answer = section_answer;
	lookup->section_authority = section_authority;
	lookup->section_additional = section_additional;
	lookup->new_search = true;
	lookup->besteffort = false;
	if (nofail) {
		lookup->servfail_stops = false;
	}
	ISC_LIST_INIT(lookup->q);
	ISC_LINK_INIT(lookup, link);
	ISC_LIST_APPEND(lookup_list, lookup, link);
	lookup->origin = NULL;
	ISC_LIST_INIT(lookup->my_server_list);
	debug("looking up %s", lookup->textname);
}

static void
do_next_command(char *input) {
	char *ptr, *arg, *last;

	if ((ptr = strtok_r(input, " \t\r\n", &last)) == NULL) {
		return;
	}
	arg = strtok_r(NULL, " \t\r\n", &last);
	if ((strcasecmp(ptr, "set") == 0) && (arg != NULL)) {
		setoption(arg);
	} else if ((strcasecmp(ptr, "server") == 0) ||
		   (strcasecmp(ptr, "lserver") == 0))
	{
		isc_app_block();
		set_nameserver(arg);
		check_ra = false;
		isc_app_unblock();
		show_settings(true, true);
	} else if (strcasecmp(ptr, "exit") == 0) {
		in_use = false;
	} else if (strcasecmp(ptr, "help") == 0 || strcasecmp(ptr, "?") == 0) {
		printf("The '%s' command is not yet implemented.\n", ptr);
	} else if (strcasecmp(ptr, "finger") == 0 ||
		   strcasecmp(ptr, "root") == 0 || strcasecmp(ptr, "ls") == 0 ||
		   strcasecmp(ptr, "view") == 0)
	{
		printf("The '%s' command is not implemented.\n", ptr);
	} else {
		addlookup(ptr);
	}
}

static void
get_next_command(void) {
	char *buf;
	char *ptr;

	fflush(stdout);
	buf = isc_mem_allocate(mctx, COMMSIZE);
	isc_app_block();
	if (interactive) {
#ifdef HAVE_READLINE
		ptr = readline("> ");
		if (ptr != NULL) {
			add_history(ptr);
		}
#else  /* ifdef HAVE_READLINE */
		fputs("> ", stderr);
		fflush(stderr);
		ptr = fgets(buf, COMMSIZE, stdin);
#endif /* ifdef HAVE_READLINE */
	} else {
		ptr = fgets(buf, COMMSIZE, stdin);
	}
	isc_app_unblock();
	if (ptr == NULL) {
		in_use = false;
	} else {
		do_next_command(ptr);
	}
#ifdef HAVE_READLINE
	if (interactive) {
		free(ptr);
	}
#endif /* ifdef HAVE_READLINE */
	isc_mem_free(mctx, buf);
}

ISC_PLATFORM_NORETURN_PRE static void
usage(void) ISC_PLATFORM_NORETURN_POST;

static void
usage(void) {
	fprintf(stderr, "Usage:\n");
	fprintf(stderr, "   nslookup [-opt ...]             # interactive mode "
			"using default server\n");
	fprintf(stderr, "   nslookup [-opt ...] - server    # interactive mode "
			"using 'server'\n");
	fprintf(stderr, "   nslookup [-opt ...] host        # just look up "
			"'host' using default server\n");
	fprintf(stderr, "   nslookup [-opt ...] host server # just look up "
			"'host' using 'server'\n");
	exit(1);
}

static void
parse_args(int argc, char **argv) {
	bool have_lookup = false;

	usesearch = true;
	for (argc--, argv++; argc > 0 && argv[0] != NULL; argc--, argv++) {
		debug("main parsing %s", argv[0]);
		if (argv[0][0] == '-') {
			if (strncasecmp(argv[0], "-ver", 4) == 0) {
				version();
				exit(0);
			} else if (argv[0][1] != 0) {
				setoption(&argv[0][1]);
			} else {
				have_lookup = true;
			}
		} else {
			if (!have_lookup) {
				have_lookup = true;
				in_use = true;
				addlookup(argv[0]);
			} else {
				if (argv[1] != NULL) {
					usage();
				}
				set_nameserver(argv[0]);
				check_ra = false;
			}
		}
	}
}

static void
flush_lookup_list(void) {
	dig_lookup_t *l, *lp;
	dig_query_t *q, *qp;
	dig_server_t *s, *sp;

	lookup_counter = 0;
	l = ISC_LIST_HEAD(lookup_list);
	while (l != NULL) {
		q = ISC_LIST_HEAD(l->q);
		while (q != NULL) {
			if (q->sock != NULL) {
				isc_socket_cancel(q->sock, NULL,
						  ISC_SOCKCANCEL_ALL);
				isc_socket_detach(&q->sock);
			}
			isc_buffer_invalidate(&q->recvbuf);
			isc_buffer_invalidate(&q->lengthbuf);
			qp = q;
			q = ISC_LIST_NEXT(q, link);
			ISC_LIST_DEQUEUE(l->q, qp, link);
			isc_mem_free(mctx, qp);
		}
		s = ISC_LIST_HEAD(l->my_server_list);
		while (s != NULL) {
			sp = s;
			s = ISC_LIST_NEXT(s, link);
			ISC_LIST_DEQUEUE(l->my_server_list, sp, link);
			isc_mem_free(mctx, sp);
		}
		if (l->sendmsg != NULL) {
			dns_message_detach(&l->sendmsg);
		}
		lp = l;
		l = ISC_LIST_NEXT(l, link);
		ISC_LIST_DEQUEUE(lookup_list, lp, link);
		isc_mem_free(mctx, lp);
	}
}

static void
getinput(isc_task_t *task, isc_event_t *event) {
	UNUSED(task);
	if (global_event == NULL) {
		global_event = event;
	}
	while (in_use) {
		get_next_command();
		if (ISC_LIST_HEAD(lookup_list) != NULL) {
			start_lookup();
			return;
		}
	}
	isc_app_shutdown();
}

int
main(int argc, char **argv) {
	isc_result_t result;

	interactive = isatty(0);

	ISC_LIST_INIT(lookup_list);
	ISC_LIST_INIT(server_list);
	ISC_LIST_INIT(search_list);

	check_ra = true;

	/* setup dighost callbacks */
	dighost_printmessage = printmessage;
	dighost_received = received;
	dighost_trying = trying;
	dighost_shutdown = query_finished;

	result = isc_app_start();
	check_result(result, "isc_app_start");

	setup_libs();
	progname = argv[0];

	setup_system(false, false);
	parse_args(argc, argv);
	if (keyfile[0] != 0) {
		setup_file_key();
	} else if (keysecret[0] != 0) {
		setup_text_key();
	}
	if (domainopt[0] != '\0') {
		set_search_domain(domainopt);
	}
	if (in_use) {
		result = isc_app_onrun(mctx, global_task, onrun_callback, NULL);
	} else {
		result = isc_app_onrun(mctx, global_task, getinput, NULL);
	}
	check_result(result, "isc_app_onrun");
	in_use = !in_use;

	(void)isc_app_run();

	puts("");
	debug("done, and starting to shut down");
	if (global_event != NULL) {
		isc_event_free(&global_event);
	}
	cancel_all();
	destroy_libs();
	isc_app_finish();

	return (query_error | print_error);
}
