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

#include <limits.h>
#include <netdb.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <isc/app.h>
#include <isc/buffer.h>
#include <isc/event.h>
#include <isc/util.h>
#include <isc/task.h>

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

#include "dig.h"

static int short_form = 1,
        tcpmode = 0,
        identify = 0, stats = 1,
        comments = 1, section_question = 1,
        section_answer = 1, section_authority = 1,
        section_additional = 1, recurse = 1,
        aaonly = 0, nofail = 1;

static int interactive;

static int in_use = 0;
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_soa(rdata, &soa);
        check_result(result, "dns_rdata_tostruct_soa");

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

static void
printa(dns_rdata_t *rdata) {
        isc_result_t result;
        char text[sizeof("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;
        int done = 0;

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

        while (!done) {
                result = isc_buffer_allocate(&b, size);
                if (result != ISC_R_SUCCESS)
                        check_result(result, "isc_buffer_allocate");
                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 = 1;
                } 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, int 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:
                                        if (section != DNS_SECTION_ANSWER)
                                                goto def_short_section;
                                        dns_name_format(name, namebuf,
                                                        sizeof(namebuf));
                                        printf("Name:\t%s\n", namebuf);
                                        printa(&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, int 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, struct sockaddr_storage *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 isc_result_t
printmessage(dig_query_t *query, dns_message_t *msg, int headers) {
        char servtext[ISC_SOCKADDR_FORMATSIZE];

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

        debug("printmessage()");

        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, 1, DNS_SECTION_QUESTION);
                detailsection(query, msg, 1, DNS_SECTION_ANSWER);
                detailsection(query, msg, 1, DNS_SECTION_AUTHORITY);
                detailsection(query, msg, 1, 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 ((msg->flags & DNS_MESSAGEFLAG_AA) == 0)
                puts("Non-authoritative answer:");
        if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_ANSWER]))
                printsection(query, msg, headers, DNS_SECTION_ANSWER);
        else
                printf("*** Can't find %s: No answer\n",
                       query->lookup->textname);

        if (((msg->flags & DNS_MESSAGEFLAG_AA) == 0) &&
            (query->lookup->rdtype != dns_rdatatype_a)) {
                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(int full, int serv_only) {
        dig_server_t *srv;
        struct sockaddr_storage 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 int
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 (1);
        else {
                printf("unknown query type: %s\n", typetext);
                return (0);
        }
}

static int
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 (1);
        else {
                printf("unknown query class: %s\n", typetext);
                return (0);
        }
}

static void
set_port(const char *value) {
        uint32_t n;
        const char *errstr;

        n = strtonum(value, 0, 65535, &errstr);
        if (errstr == NULL)
                port = (uint16_t) n;
        else
                printf("port is %s: '%s'\n", errstr, value);
}

static void
set_timeout(const char *value) {
        uint32_t n;
        const char *errstr;

        n = strtonum(value, 0, UINT_MAX, &errstr);
        if (errstr == NULL)
                timeout = n;
        else
                printf("timeout is %s: '%s'\n", errstr, value);
}

static void
set_tries(const char *value) {
        uint32_t n;
        const char *errstr;

        n = strtonum(value, 0, INT_MAX, &errstr);
        if (errstr == NULL)
                tries = n;
        else
                printf("tries is %s: '%s'\n", errstr, value);
}

static void
set_ndots(const char *value) {
        uint32_t n;
        const char *errstr;

        n = strtonum(value, 0, 128, &errstr);
        if (errstr == NULL)
                ndots = n;
        else
                printf("ndots is %s: '%s'\n", errstr, value);
}

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(1, 0);
        } 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));
        } else if (strncasecmp(opt, "ty=", 3) == 0) {
                if (testtype(&opt[3]))
                        strlcpy(deftype, &opt[3], sizeof(deftype));
        } else if (strncasecmp(opt, "querytype=", 10) == 0) {
                if (testtype(&opt[10]))
                        strlcpy(deftype, &opt[10], sizeof(deftype));
        } else if (strncasecmp(opt, "query=", 6) == 0) {
                if (testtype(&opt[6]))
                        strlcpy(deftype, &opt[6], sizeof(deftype));
        } else if (strncasecmp(opt, "qu=", 3) == 0) {
                if (testtype(&opt[3]))
                        strlcpy(deftype, &opt[3], sizeof(deftype));
        } else if (strncasecmp(opt, "q=", 2) == 0) {
                if (testtype(&opt[2]))
                        strlcpy(deftype, &opt[2], sizeof(deftype));
        } else if (strncasecmp(opt, "domain=", 7) == 0) {
                strlcpy(domainopt, &opt[7], sizeof(domainopt));
                set_search_domain(domainopt);
                usesearch = 1;
        } else if (strncasecmp(opt, "do=", 3) == 0) {
                strlcpy(domainopt, &opt[3], sizeof(domainopt));
                set_search_domain(domainopt);
                usesearch = 1;
        } 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 = 1;
        } else if (CHECKOPT("norecurse", 5)) {
                recurse = 0;
        } 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 = 1;
        } else if (CHECKOPT("nodefname", 5)) {
                usesearch = 0;
        } else if (CHECKOPT("vc", 2) == 0) {
                tcpmode = 1;
        } else if (CHECKOPT("novc", 4) == 0) {
                tcpmode = 0;
        } else if (CHECKOPT("debug", 3) == 0) {
                short_form = 0;
                showsearch = 1;
        } else if (CHECKOPT("nodebug", 5) == 0) {
                short_form = 1;
                showsearch = 0;
        } else if (CHECKOPT("d2", 2) == 0) {
                debugging = 1;
        } else if (CHECKOPT("nod2", 4) == 0) {
                debugging = 0;
        } else if (CHECKOPT("search", 3) == 0) {
                usesearch = 1;
        } else if (CHECKOPT("nosearch", 5) == 0) {
                usesearch = 0;
        } else if (CHECKOPT("sil", 3) == 0) {
                /* deprecation_msg = 0; */
        } else if (CHECKOPT("fail", 3) == 0) {
                nofail=0;
        } else if (CHECKOPT("nofail", 5) == 0) {
                nofail=1;
        } 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()");
        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, lookup->ip6_int, 1)
            == ISC_R_SUCCESS) {
                strlcpy(lookup->textname, store, sizeof(lookup->textname));
                lookup->rdtype = dns_rdatatype_ptr;
                lookup->rdtypeset = 1;
        } else {
                strlcpy(lookup->textname, opt, sizeof(lookup->textname));
                lookup->rdtype = rdtype;
                lookup->rdtypeset = 1;
        }
        lookup->rdclass = rdclass;
        lookup->rdclassset = 1;
        lookup->trace = 0;
        lookup->trace_root = lookup->trace;
        lookup->ns_search_only = 0;
        lookup->identify = identify;
        lookup->recurse = recurse;
        lookup->aaonly = aaonly;
        lookup->retries = tries;
        lookup->udpsize = 0;
        lookup->comments = comments;
        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 = 1;
        if (nofail)
                lookup->servfail_stops = 0;
        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;

        ptr = next_token(&input, " \t\r\n");
        if (ptr == NULL)
                return;
        arg = next_token(&input, " \t\r\n");
        if (strcasecmp(ptr, "set") == 0) {
                if (arg == NULL)
                        printf("Usage: set keyword=value, or set all\n");
                else
                        setoption(arg);
        } else if ((strcasecmp(ptr, "server") == 0) ||
                 (strcasecmp(ptr, "lserver") == 0)) {
                isc_result_t res;

                if (arg == NULL)
                        printf("usage: server hostname\n");
                else if ((res = set_nameserver(arg))) {
                        printf("couldn't get address for '%s': %s\n",
                            arg, isc_result_totext(res));
                } else {
                        check_ra = 0;
                        show_settings(1, 1);
                }
        } else if (strcasecmp(ptr, "exit") == 0) {
                in_use = 0;
        } 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 = malloc(COMMSIZE);
        if (buf == NULL)
                fatal("memory allocation failure");
        if (interactive) {
                fputs("> ", stderr);
                fflush(stderr);
                ptr = fgets(buf, COMMSIZE, stdin);
        } else
                ptr = fgets(buf, COMMSIZE, stdin);
        if (ptr == NULL) {
                in_use = 0;
        } else
                do_next_command(ptr);
        free(buf);
}

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

        usesearch = 1;
        for (argc--, argv++; argc > 0; 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 = 1;
                } else {
                        if (!have_lookup) {
                                have_lookup = 1;
                                in_use = 1;
                                addlookup(argv[0]);
                        } else {
                                isc_result_t res;

                                if ((res = set_nameserver(argv[0])))
                                        fatal("couldn't get address for '%s': %s",
                                            argv[0], isc_result_totext(res));
                                check_ra = 0;
                        }
                }
        }
}

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);
                        }
                        if (ISC_LINK_LINKED(&q->recvbuf, link))
                                ISC_LIST_DEQUEUE(q->recvlist, &q->recvbuf,
                                                 link);
                        if (ISC_LINK_LINKED(&q->lengthbuf, link))
                                ISC_LIST_DEQUEUE(q->lengthlist, &q->lengthbuf,
                                                 link);
                        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);
                        free(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);
                        free(sp);

                }
                if (l->sendmsg != NULL)
                        dns_message_destroy(&l->sendmsg);
                lp = l;
                l = ISC_LIST_NEXT(l, link);
                ISC_LIST_DEQUEUE(lookup_list, lp, link);
                free(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
nslookup_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(root_hints_server_list);
        ISC_LIST_INIT(search_list);

        check_ra = 1;

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

        if (unveil(_PATH_RESCONF, "r") == -1) {
                perror("unveil " _PATH_RESCONF);
                exit(1);
        }

        if (pledge("stdio rpath inet dns", NULL) == -1) {
                perror("pledge");
                exit(1);
        }

        setup_libs();
        progname = argv[0];

        parse_args(argc, argv);

        setup_system(0, 0);
        if (domainopt[0] != '\0')
                set_search_domain(domainopt);
        if (in_use)
                result = isc_app_onrun(global_task, onrun_callback,
                                       NULL);
        else
                result = isc_app_onrun(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();

        return (query_error | print_error);
}