#ifndef lint
static char rcsid[] = "$Id: dig.c,v 8.8 1996/05/21 07:32:40 vixie Exp $";
#endif

/*
 * ++Copyright++ 1989
 * -
 * Copyright (c) 1989
 *    The Regents of the University of California.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 THE REGENTS OR CONTRIBUTORS 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.
 * -
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies, and that
 * the name of Digital Equipment Corporation not be used in advertising or
 * publicity pertaining to distribution of the document or software without
 * specific, written prior permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * -
 * --Copyright--
 */

/*********************** Notes for the BIND 4.9 release (Paul Vixie, DEC)
 *	dig 2.0 was written by copying sections of libresolv.a and nslookup
 *	and modifying them to be more useful for a general lookup utility.
 *	as of BIND 4.9, the changes needed to support dig have mostly been
 *	incorporated into libresolv.a and nslookup; dig now links against
 *	some of nslookup's .o files rather than #including them or maintaining
 *	local copies of them.  in some sense, dig belongs in the nslookup
 *	subdirectory rather than up here in "tools", but that's for arc@sgi.com
 *	(owner of nslookup) to decide.
 *
 *	while merging dig back into the BIND release, i made a number of
 *	structural changes.  for one thing, i put all of dig's private
 *	library routines into this file rather than maintaining them in
 *	separate, #included, files.  i don't like to #include ".c" files.
 *	i removed all calls to "bcopy", replacing them with structure
 *	assignments.  i removed all "extern"'s of standard functions,
 *	replacing them with #include's of standard header files.  this
 *	version of dig is probably as portable as the rest of BIND.
 *
 *	i had to remove the query-time and packet-count statistics since
 *	the current libresolv.a is a lot harder to modify to maintain these
 *	than the 4.8 one (used in the original dig) was.  for consolation,
 *	i added a "usage" message with extensive help text.
 *
 *	to save my (limited, albeit) sanity, i ran "indent" over the source.
 *	i also added the standard berkeley/DEC copyrights, since this file now
 *	contains a fair amount of non-USC code.  note that the berkeley and
 *	DEC copyrights do not prohibit redistribution, with or without fee;
 *	we add them only to protect ourselves (you have to claim copyright
 *	in order to disclaim liability and warranty).
 *
 *	Paul Vixie, Palo Alto, CA, April 1993
 ****************************************************************************

 /*******************************************************************
 **      DiG -- Domain Information Groper                          **
 **                                                                **
 **        dig.c - Version 2.1 (7/12/94) ("BIND takeover")         **
 **                                                                **
 **        Developed by: Steve Hotz & Paul Mockapetris             **
 **        USC Information Sciences Institute (USC-ISI)            **
 **        Marina del Rey, California                              **
 **        1989                                                    **
 **                                                                **
 **        dig.c -                                                 **
 **           Version 2.0 (9/1/90)                                 **
 **               o renamed difftime() difftv() to avoid           **
 **                 clash with ANSI C                              **
 **               o fixed incorrect # args to strcmp,gettimeofday  **
 **               o incorrect length specified to strncmp          **
 **               o fixed broken -sticky -envsa -envset functions  **
 **               o print options/flags redefined & modified       **
 **                                                                **
 **           Version 2.0.beta (5/9/90)                            **
 **               o output format - helpful to `doc`               **
 **               o minor cleanup                                  **
 **               o release to beta testers                        **
 **                                                                **
 **           Version 1.1.beta (10/26/89)                          **
 **               o hanging zone transer (when REFUSED) fixed      **
 **               o trailing dot added to domain names in RDATA    **
 **               o ISI internal                                   **
 **                                                                **
 **           Version 1.0.tmp  (8/27/89)                           **
 **               o Error in prnttime() fixed                      **
 **               o no longer dumps core on large pkts             **
 **               o zone transfer (axfr) added                     **
 **               o -x added for inverse queries                   **
 **                               (i.e. "dig -x 128.9.0.32")       **
 **               o give address of default server                 **
 **               o accept broadcast to server @255.255.255.255    **
 **                                                                **
 **           Version 1.0  (3/27/89)                               **
 **               o original release                               **
 **                                                                **
 **     DiG is Public Domain, and may be used for any purpose as   **
 **     long as this notice is not removed.                        **
 ****                                                            ****
 ****   NOTE: Version 2.0.beta is not for public distribution    ****
 ****                                                            ****
 *******************************************************************/


#define VERSION 22
#define VSTRING "2.2"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>

#include <netdb.h>
#include <stdio.h>
#include <resolv.h>
#include <ctype.h> 
#include <errno.h>
#include <string.h>
#include <setjmp.h>
#include <fcntl.h>

#include "nslookup/res.h"
#include "../conf/portability.h"

#define PRF_DEF		0x2ff9
#define PRF_MIN		0xA930
#define PRF_ZONE        0x24f9

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif

int eecode = 0;

FILE *qfp;
int sockFD;

#define SAVEENV "DiG.env"
#define DIG_MAXARGS 30

char *defsrv, *srvmsg;
char defbuf[40] = "default -- ";
char srvbuf[60];

static void Usage();
static int SetOption(), printZone(), printRR();
static struct timeval difftv();
static void prnttime();
static int xstrtonum();
static void res_re_init();

/* stuff for nslookup modules */
FILE		*filePtr;
jmp_buf		env;
HostInfo	*defaultPtr = NULL;
HostInfo	curHostInfo, defaultRec;
int		curHostValid = FALSE;
int		queryType, queryClass;
extern int	StringToClass(), StringToType();	/* subr.c */
#if defined(BSD) && BSD >= 199006 && !defined(RISCOS_BSD)
FILE		*yyin = NULL;
void		yyrestart(f) { }
#endif
char		*pager = NULL;
/* end of nslookup stuff */

 /*
 ** Take arguments appearing in simple string (from file or command line)
 ** place in char**.
 */
void
stackarg(y, l)
	char *l;
	char **y;
{
	int done=0;

	while (!done) {
		switch (*l) {
		case '\t':
		case ' ':
			l++;    break;
		case '\0':
		case '\n':
			done++;
			*y = NULL;
			break;
		default:
			*y++=l;
			while (!isspace(*l))
				l++;
			if (*l == '\n')
				done++;
			*l++ = '\0';
			*y = NULL;
		}
	}
}

char myhostname[MAXHOSTNAMELEN];

int
main(argc, argv)
	int argc;
	char **argv;
{
	struct hostent *hp;
	short port = htons(NAMESERVER_PORT);
	/* Wierd stuff for SPARC alignment, hurts nothing else. */
	union {
		HEADER header_;
		u_char packet_[PACKETSZ];
	} packet_;
#define	packet  (packet_.packet_)
	u_char answer[8*1024];
	int n;
	char doping[90];
	char pingstr[50];
	char *afile;
	char *addrc, *addrend, *addrbegin;

	struct timeval exectime, tv1, tv2, start_time, end_time, query_time;

	char *srv;
	int anyflag = 0;
	int sticky = 0;
	int tmp; 
	int qtypeSet;
	int addrflag = 0;
	int zone = 0;
        int bytes_out, bytes_in;

	char cmd[256];
	char domain[MAXDNAME];
        char msg[120], *msgptr;
	char **vtmp;
	char *args[DIG_MAXARGS];
	char **ax;
	char **ay;
	int once = 1, dofile = 0; /* batch -vs- interactive control */
	char fileq[100];
	char *qptr;
	int  fp;
	int wait=0, delay;
	int envset=0, envsave=0;
	struct __res_state res_x, res_t;
	char *pp;

	res_init();
	_res.pfcode = PRF_DEF;
	qtypeSet = 0;
	bzero(domain, (sizeof domain));
	gethostname(myhostname, (sizeof myhostname));
	defsrv = strcat(defbuf, inet_ntoa(_res.nsaddr.sin_addr));
	res_x = _res;

 /*
 ** If LOCALDEF in environment, should point to file
 ** containing local favourite defaults.  Also look for file
 ** DiG.env (i.e. SAVEENV) in local directory.
 */

	if ((((afile = (char *) getenv("LOCALDEF")) != (char *) NULL) &&
	     ((fp = open(afile, O_RDONLY)) > 0)) ||
	    ((fp = open(SAVEENV, O_RDONLY)) > 0)) {
		read(fp, (char *)&res_x, (sizeof res_x));
		close(fp);
		_res = res_x;
	}
 /*
 **   check for batch-mode DiG; also pre-scan for 'help'
 */
	vtmp = argv;
	ax = args;
	while (*vtmp != NULL) {
		if (strcmp(*vtmp, "-h") == 0 ||
		    strcmp(*vtmp, "-help") == 0 ||
		    strcmp(*vtmp, "-usage") == 0 ||
		    strcmp(*vtmp, "help") == 0) {
			Usage();
			exit(0);
		}

		if (strcmp(*vtmp, "-f") == 0) {
			dofile++; once=0;
			if ((qfp = fopen(*++vtmp, "r")) == NULL) {
				fflush(stdout);
				perror("file open");
				fflush(stderr);
				exit(10);
			}
		} else {
			if (ax - args == DIG_MAXARGS) {
				fprintf(stderr, "dig: too many arguments\n");
				exit(10);
			}
			*ax++ = *vtmp;
		}
		vtmp++;
	}

	_res.id = 1;
	gettimeofday(&tv1, NULL);

 /*
 **  Main section: once if cmd-line query
 **                while !EOF if batch mode
 */
	*fileq = '\0';
	while ((dofile && (fgets(fileq,100,qfp) != NULL)) || 
	       ((!dofile) && (once--))) 
	{
		if ((*fileq=='\n') || (*fileq=='#') || (*fileq==';')) {
			continue; /* ignore blank lines & comments */
		}

/*
 * "sticky" requests that before current parsing args
 * return to current "working" environment (X******)
 */
		if (sticky) {
			printf(";; (using sticky settings)\n");
			_res = res_x;
		}

/* concat cmd-line and file args */
		ay = ax;
		qptr = fileq;
		stackarg(ay, qptr);

		/* defaults */
		queryType = T_NS;
		queryClass = C_IN;
		zone = 0;
		*pingstr = 0;
		srv = NULL;

		sprintf(cmd,"\n; <<>> DiG %s <<>> ",VSTRING);
		argv = args;
		argc = ax - args;
/*
 * More cmd-line options than anyone should ever have to
 * deal with ....
 */
		while (*(++argv) != NULL && **argv != '\0') { 
			strcat(cmd,*argv); strcat(cmd," ");
			if (**argv == '@') {
				srv = (*argv+1);
				continue;
			}
			if (**argv == '%')
				continue;
			if (**argv == '+') {
				SetOption(*argv+1);
				continue;
			}
	 
			if (strncmp(*argv,"-nost",5) == 0) {
				sticky = 0;
				continue;
			} else if (strncmp(*argv,"-st",3) == 0) {
				sticky++;
				continue;
			} else if (strncmp(*argv,"-envsa",6) == 0) {
				envsave++;
				continue;
			} else if (strncmp(*argv,"-envse",6) == 0) {
				envset++;
				continue;
			}

			if (**argv == '-') {
				switch (argv[0][1]) { 
				case 'T': wait = atoi(*++argv);
					break;
				case 'c': 
					if ((tmp = atoi(*++argv))
					    || *argv[0]=='0') {
						queryClass = tmp;
					} else if (tmp = StringToClass(*argv,
								       0, NULL)
						   ) {
						queryClass = tmp;
					} else {
						printf(
						  "; invalid class specified\n"
						       );
					}
					break;
				case 't': 
					if ((tmp = atoi(*++argv))
					    || *argv[0]=='0') {
						queryType = tmp;
						qtypeSet++;
					} else if (tmp = StringToType(*argv,
								      0, NULL)
						   ) {
						queryType = tmp;
						qtypeSet++;
					} else {
						printf(
						   "; invalid type specified\n"
						       );
						}
					break;
				case 'x':
					if (!qtypeSet) {
						queryType = T_ANY;
						qtypeSet++;
					}
					if (!(addrc = *++argv)) {
						printf(
						       "; no arg for -x?\n"
						       );
						break;
					}
					addrend = addrc + strlen(addrc);
					if (*addrend == '.')
						*addrend = '\0';
					*domain = '\0';
					while (addrbegin = strrchr(addrc,'.')) {
						strcat(domain, addrbegin+1);
						strcat(domain, ".");
						*addrbegin = '\0';
					}
					strcat(domain, addrc);
					strcat(domain, ".in-addr.arpa.");
					break;
				case 'p': port = htons(atoi(*++argv)); break;
				case 'P':
					if (argv[0][2] != '\0')
						strcpy(pingstr,&argv[0][2]);
					else
						strcpy(pingstr,"ping -s");
					break;
#if defined(__RES) && (__RES >= 19931104)
				case 'n':
					_res.ndots = atoi(&argv[0][2]);
					break;
#endif /*__RES*/
				} /* switch - */
				continue;
			} /* if '-'   */

			if ((tmp = StringToType(*argv, -1, NULL)) != -1) { 
				if ((T_ANY == tmp) && anyflag++) {  
					queryClass = C_ANY; 	
					continue; 
				}
				if (T_AXFR == tmp) {
					_res.pfcode = PRF_ZONE;
					zone++;
				} else {
					queryType = tmp; 
					qtypeSet++;
				}
			} else if ((tmp = StringToClass(*argv, -1, NULL))
				   != -1) { 
				queryClass = tmp; 
			} else {
				bzero(domain, (sizeof domain));
				sprintf(domain,"%s",*argv);
			}
		} /* while argv remains */

		if (_res.pfcode & 0x80000)
			printf("; pfcode: %08lx, options: %08lx\n",
			       _res.pfcode, _res.options);
	  
/*
 * Current env. (after this parse) is to become the
 * new "working environmnet. Used in conj. with sticky.
 */
		if (envset) {
			res_x = _res;
			envset = 0;
		}

/*
 * Current env. (after this parse) is to become the
 * new default saved environmnet. Save in user specified
 * file if exists else is SAVEENV (== "DiG.env").
 */
		if (envsave) {
			afile = (char *) getenv("LOCALDEF");
			if ((afile &&
			     ((fp = open(afile,
					 O_WRONLY|O_CREAT|O_TRUNC,
					 S_IREAD|S_IWRITE)) > 0))
			    ||
			    ((fp = open(SAVEENV,
					O_WRONLY|O_CREAT|O_TRUNC,
					S_IREAD|S_IWRITE)) > 0)) {
				write(fp, (char *)&_res, (sizeof _res));
				close(fp);
			}
			envsave = 0;
		}

		if (_res.pfcode & RES_PRF_CMD)
			printf("%s\n", cmd);

		addrflag = anyflag = 0;

/*
 * Find address of server to query. If not dot-notation, then
 * try to resolve domain-name (if so, save and turn off print 
 * options, this domain-query is not the one we want. Restore
 * user options when done.
 * Things get a bit wierd since we need to use resolver to be
 * able to "put the resolver to work".
 */

		srvbuf[0] = 0;
		srvmsg = defsrv;
		if (srv != NULL) {
			struct in_addr addr;

			if (inet_aton(srv, &addr)) {
				_res.nscount = 1;
				_res.nsaddr.sin_addr = addr;
				srvmsg = strcat(srvbuf, srv);
			} else {
				res_t = _res;
				_res.pfcode = 0;
				_res.options = RES_DEFAULT;
				res_init();
				hp = gethostbyname(srv);
				_res = res_t;
				if (hp == NULL
				    || hp->h_addr_list == NULL
				    || *hp->h_addr_list == NULL) {
					fflush(stdout);
					fprintf(stderr,
		"; Bad server: %s -- using default server and timer opts\n",
						srv);
					fflush(stderr);
					srvmsg = defsrv;
					srv = NULL;
				} else {
					u_int32_t **addr;

					_res.nscount = 0;
					for (addr = (u_int32_t**)hp->h_addr_list;
					     *addr && (_res.nscount < MAXNS);
					     addr++) {
						_res.nsaddr_list[
							_res.nscount++
						].sin_addr.s_addr = **addr;
					}

					srvmsg = strcat(srvbuf,srv);
					strcat(srvbuf, "  ");
					strcat(srvmsg,
					       inet_ntoa(_res.nsaddr.sin_addr)
					       );
				}
			}
			printf("; (%d server%s found)\n",
			       _res.nscount, (_res.nscount==1)?"":"s");
			_res.id += _res.retry;
		}

		{
			int i;

			for (i = 0;  i < _res.nscount;  i++) {
				_res.nsaddr_list[i].sin_family = AF_INET;
				_res.nsaddr_list[i].sin_port = port;
			}
			_res.id += _res.retry;
		}

		if (zone) {
			int i;

			for (i = 0;  i < _res.nscount;  i++) {
				int x = printZone(domain,
						  &_res.nsaddr_list[i]);
				if (_res.pfcode & RES_PRF_STATS) {
					struct timeval exectime;

					gettimeofday(&exectime,NULL);
					printf(";; FROM: %s to SERVER: %s\n",
					       myhostname,
					       inet_ntoa(_res.nsaddr_list[i]
							 .sin_addr));
					printf(";; WHEN: %s",
					       ctime(&(exectime.tv_sec)));
				}
				if (!x)
					break;	/* success */
			}
			fflush(stdout);
			continue;
		}

		if (*domain && !qtypeSet) {
			queryType = T_A;
			qtypeSet++;
		}
		
		bytes_out = n = res_mkquery(QUERY, domain,
					    queryClass, queryType,
					    NULL, 0, NULL,
					    packet, sizeof(packet));
		if (n < 0) {
			fflush(stderr);
			printf(";; res_mkquery: buffer too small\n\n");
			continue;
		}
		eecode = 0;
		if (_res.pfcode & RES_PRF_HEAD1)
			__fp_resstat(NULL, stdout);
		(void) gettimeofday(&start_time, NULL);
		if ((bytes_in = n = res_send(packet, n,
					     answer, sizeof(answer))) < 0) {
			fflush(stdout);
			n = 0 - n;
			msg[0]=0;
			strcat(msg,";; res_send to server ");
			strcat(msg,srvmsg);
			perror(msg);
			fflush(stderr);

			if (!dofile) {
				if (eecode)
					exit(eecode);
				else
					exit(9);
			}
		}
		(void) gettimeofday(&end_time, NULL);

		if (_res.pfcode & RES_PRF_STATS) {
			query_time = difftv(start_time, end_time);
			printf(";; Total query time: ");
			prnttime(query_time);
			putchar('\n');
			gettimeofday(&exectime,NULL);
			printf(";; FROM: %s to SERVER: %s\n",
			       myhostname, srvmsg);
			printf(";; WHEN: %s",
			       ctime(&(exectime.tv_sec)));
			printf(";; MSG SIZE  sent: %d  rcvd: %d\n",
			       bytes_out, bytes_in);
		}
	  
		fflush(stdout);
/*
 *   Argh ... not particularly elegant. Should put in *real* ping code.
 *   Would necessitate root priviledges for icmp port though!
 */
		if (*pingstr) {
			sprintf(doping,"%s %s 56 3 | tail -3",pingstr,
				(srv==NULL)?(defsrv+10):srv);
			system(doping);
		}
		putchar('\n');

/*
 * Fairly crude method and low overhead method of keeping two
 * batches started at different sites somewhat synchronized.
 */
		gettimeofday(&tv2, NULL);
		delay = (int)(tv2.tv_sec - tv1.tv_sec);
		if (delay < wait) {
			sleep(wait - delay);
		}
	}
	return(eecode);
}


static void
Usage()
{
	fputs("\
usage:  dig [@server] [domain] [q-type] [q-class] {q-opt} {d-opt} [%comment]\n\
where:	server,\n\
	domain	are names in the Domain Name System\n\
	q-class	is one of (in,any,...) [default: in]\n\
	q-type	is one of (a,any,mx,ns,soa,hinfo,axfr,txt,...) [default: a]\n\
", stderr);
	fputs("\
	q-opt	is one of:\n\
		-x dot-notation-address	(shortcut to in-addr.arpa lookups)\n\
		-f file			(batch mode input file name)\n\
		-T time			(batch mode time delay, per query)\n\
		-p port			(nameserver is on this port) [53]\n\
		-Pping-string		(see man page)\n\
		-t query-type		(synonym for q-type)\n\
		-c query-class		(synonym for q-class)\n\
		-envsav,-envset		(see man page)\n\
		-[no]stick		(see man page)\n\
", stderr);
	fputs("\
	d-opt	is of the form ``+keyword=value'' where keyword is one of:\n\
		[no]debug [no]d2 [no]recurse retry=# time=# [no]ko [no]vc\n\
		[no]defname [no]search domain=NAME [no]ignore [no]primary\n\
		[no]aaonly [no]sort [no]cmd [no]stats [no]Header [no]header\n\
		[no]ttlid [no]cl [no]qr [no]reply [no]ques [no]answer\n\
		[no]author [no]addit pfdef pfmin pfset=# pfand=# pfor=#\n\
", stderr);
	fputs("\
notes:	defname and search don't work; use fully-qualified names.\n\
", stderr);
}


static int
SetOption(string)
    char *string;
{
    char 	option[NAME_LEN];
    char 	type[NAME_LEN];
    char 	*ptr;
    int 	i;

    i = sscanf(string, " %s", option);
    if (i != 1) {
	fprintf(stderr, ";*** Invalid option: %s\n",  option);
	return(ERROR);
    } 
   
    if (strncmp(option, "aa", 2) == 0) {	/* aaonly */
	    _res.options |= RES_AAONLY;
	} else if (strncmp(option, "noaa", 4) == 0) {
	    _res.options &= ~RES_AAONLY;
	} else if (strncmp(option, "deb", 3) == 0) {	/* debug */
	    _res.options |= RES_DEBUG;
	} else if (strncmp(option, "nodeb", 5) == 0) {
	    _res.options &= ~(RES_DEBUG | RES_DEBUG2);
	} else if (strncmp(option, "ko", 2) == 0) {	/* keepopen */
	    _res.options |= (RES_STAYOPEN | RES_USEVC);
	} else if (strncmp(option, "noko", 4) == 0) {
	    _res.options &= ~RES_STAYOPEN;
	} else if (strncmp(option, "d2", 2) == 0) {	/* d2 (more debug) */
	    _res.options |= (RES_DEBUG | RES_DEBUG2);
	} else if (strncmp(option, "nod2", 4) == 0) {
	    _res.options &= ~RES_DEBUG2;
	} else if (strncmp(option, "def", 3) == 0) {	/* defname */
	    _res.options |= RES_DEFNAMES;
	} else if (strncmp(option, "nodef", 5) == 0) {
	    _res.options &= ~RES_DEFNAMES;
	} else if (strncmp(option, "sea", 3) == 0) {	/* search list */
	    _res.options |= RES_DNSRCH;
	} else if (strncmp(option, "nosea", 5) == 0) {
	    _res.options &= ~RES_DNSRCH;
	} else if (strncmp(option, "do", 2) == 0) {	/* domain */
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
		sscanf(++ptr, "%s", _res.defdname);
	    }
	  } else if (strncmp(option, "ti", 2) == 0) {      /* timeout */
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
	      sscanf(++ptr, "%d", &_res.retrans);
	    }

	  } else if (strncmp(option, "ret", 3) == 0) {    /* retry */
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
	      sscanf(++ptr, "%d", &_res.retry);
	    }

	} else if (strncmp(option, "i", 1) == 0) {	/* ignore */
	    _res.options |= RES_IGNTC;
	} else if (strncmp(option, "noi", 3) == 0) {
	    _res.options &= ~RES_IGNTC;
	} else if (strncmp(option, "pr", 2) == 0) {	/* primary */
	    _res.options |= RES_PRIMARY;
	} else if (strncmp(option, "nop", 3) == 0) {
	    _res.options &= ~RES_PRIMARY;
	} else if (strncmp(option, "rec", 3) == 0) {	/* recurse */
	    _res.options |= RES_RECURSE;
	} else if (strncmp(option, "norec", 5) == 0) {
	    _res.options &= ~RES_RECURSE;
	} else if (strncmp(option, "v", 1) == 0) {	/* vc */
	    _res.options |= RES_USEVC;
	} else if (strncmp(option, "nov", 3) == 0) {
	    _res.options &= ~RES_USEVC;
	} else if (strncmp(option, "pfset", 5) == 0) {
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
	      _res.pfcode = xstrtonum(++ptr);
	    }
	} else if (strncmp(option, "pfand", 5) == 0) {
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
	      _res.pfcode = _res.pfcode & xstrtonum(++ptr);
	    }
	} else if (strncmp(option, "pfor", 4) == 0) {
	    ptr = strchr(option, '=');
	    if (ptr != NULL) {
	      _res.pfcode |= xstrtonum(++ptr);
	    }
	} else if (strncmp(option, "pfmin", 5) == 0) {
	      _res.pfcode = PRF_MIN;
	} else if (strncmp(option, "pfdef", 5) == 0) {
	      _res.pfcode = PRF_DEF;
	} else if (strncmp(option, "an", 2) == 0) {  /* answer section */
	      _res.pfcode |= RES_PRF_ANS;
	} else if (strncmp(option, "noan", 4) == 0) {
	      _res.pfcode &= ~RES_PRF_ANS;
	} else if (strncmp(option, "qu", 2) == 0) {  /* question section */
	      _res.pfcode |= RES_PRF_QUES;
	} else if (strncmp(option, "noqu", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_QUES;
	} else if (strncmp(option, "au", 2) == 0) {  /* authority section */
	      _res.pfcode |= RES_PRF_AUTH;
	} else if (strncmp(option, "noau", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_AUTH;
	} else if (strncmp(option, "ad", 2) == 0) {  /* addition section */
	      _res.pfcode |= RES_PRF_ADD;
	} else if (strncmp(option, "noad", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_ADD;
	} else if (strncmp(option, "tt", 2) == 0) {  /* TTL & ID */
	      _res.pfcode |= RES_PRF_TTLID;
	} else if (strncmp(option, "nott", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_TTLID;
	} else if (strncmp(option, "he", 2) == 0) {  /* head flags stats */
	      _res.pfcode |= RES_PRF_HEAD2;
	} else if (strncmp(option, "nohe", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_HEAD2;
	} else if (strncmp(option, "H", 1) == 0) {  /* header all */
	      _res.pfcode |= RES_PRF_HEADX;
	} else if (strncmp(option, "noH", 3) == 0) {  
	      _res.pfcode &= ~(RES_PRF_HEADX);
	} else if (strncmp(option, "qr", 2) == 0) {  /* query */
	      _res.pfcode |= RES_PRF_QUERY;
	} else if (strncmp(option, "noqr", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_QUERY;
	} else if (strncmp(option, "rep", 3) == 0) {  /* reply */
	      _res.pfcode |= RES_PRF_REPLY;
	} else if (strncmp(option, "norep", 5) == 0) {  
	      _res.pfcode &= ~RES_PRF_REPLY;
	} else if (strncmp(option, "cm", 2) == 0) {  /* command line */
	      _res.pfcode |= RES_PRF_CMD;
	} else if (strncmp(option, "nocm", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_CMD;
	} else if (strncmp(option, "cl", 2) == 0) {  /* class mnemonic */
	      _res.pfcode |= RES_PRF_CLASS;
	} else if (strncmp(option, "nocl", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_CLASS;
	} else if (strncmp(option, "st", 2) == 0) {  /* stats*/
	      _res.pfcode |= RES_PRF_STATS;
	} else if (strncmp(option, "nost", 4) == 0) {  
	      _res.pfcode &= ~RES_PRF_STATS;
	} else {
	    fprintf(stderr, "; *** Invalid option: %s\n",  option);
	    return(ERROR);
	}
	res_re_init();
	return(SUCCESS);
}



/*
 * Force a reinitialization when the domain is changed.
 */
static void
res_re_init()
{
	static char localdomain[] = "LOCALDOMAIN";
	char *buf;
	long pfcode = _res.pfcode;
#if defined(__RES) && (__RES >= 19931104)
	long ndots = _res.ndots;
#endif

	/* this is ugly but putenv() is more portable than setenv() */
	buf = malloc((sizeof localdomain) +strlen(_res.defdname) +10/*fuzz*/);
	sprintf(buf, "%s=%s", localdomain, _res.defdname);
	putenv(buf);	/* keeps the argument, so we won't free it */
	res_init();
	_res.pfcode = pfcode;
#if defined(__RES) && (__RES >= 19931104)
	_res.ndots = ndots;
#endif
}


/*
 * convert char string (decimal, octal, or hex) to integer
 */
static int
xstrtonum(p)
	char *p;
{
	int v = 0;
	int i;
	int b = 10;
	int flag = 0;
	while (*p != 0) {
		if (!flag++)
			if (*p == '0') {
				b = 8; p++;
				continue;
			}
		if (isupper(*p))
			*p=tolower(*p);
		if (*p == 'x') {
			b = 16; p++;
			continue;
		}
		if (isdigit(*p)) {
			i = *p - '0';
		} else if (isxdigit(*p)) {
			i = *p - 'a' + 10;
		} else {
			fprintf(stderr,
				"; *** Bad char in numeric string..ignored\n");
			i = -1;
		}
		if (i >= b) {
			fprintf(stderr,
				"; *** Bad char in numeric string..ignored\n");
			i = -1;
		}
		if (i >= 0)
			v = v * b + i;
		p++;
	}
	return(v);
}

/* this code was cloned from nslookup/list.c */

extern char *_res_resultcodes[];	/* res_debug.c */

typedef union {
    HEADER qb1;
    u_char qb2[PACKETSZ];
} querybuf;

static int
printZone(zone, sin)
	char *zone;
	struct sockaddr_in *sin;
{
	querybuf		buf;
	HEADER			*headerPtr;
	int			msglen;
	int			amtToRead;
	int			numRead;
	int			numAnswers = 0;
	int			numRecords = 0;
	int			result;
	int			soacnt = 0;
	int			sockFD;
	int			count, type, class, rlen, done, n;
	u_short			len;
	u_char			*cp;
	char			dname[2][NAME_LEN];
	char			file[NAME_LEN];
	static u_char		*answer = NULL;
	static int		answerLen = 0;
	enum {
	    NO_ERRORS,
	    ERR_READING_LEN,
	    ERR_READING_MSG,
	    ERR_PRINTING
	} error = NO_ERRORS;

	/*
	 *  Create a query packet for the requested zone name.
	 */
	msglen = res_mkquery(QUERY, zone, queryClass, T_AXFR, NULL,
			     0, 0, buf.qb2, sizeof(buf));
	if (msglen < 0) {
	    if (_res.options & RES_DEBUG) {
		fprintf(stderr, ";; res_mkquery failed\n");
	    }
	    return (ERROR);
	}

	/*
	 *  Set up a virtual circuit to the server.
	 */
	if ((sockFD = socket(sin->sin_family, SOCK_STREAM, 0)) < 0) {
	    int e = errno;
	    perror(";; socket");
	    return(e);
	}
	if (connect(sockFD, (struct sockaddr *)sin, sizeof(*sin)) < 0) {
	    int e = errno;
	    perror(";; connect");
	    (void) close(sockFD);
	    sockFD = -1;
	    return e;
	}

	/*
	 * Send length & message for zone transfer
	 */

	__putshort(msglen, (u_char *)&len);

        if (write(sockFD, (char *)&len, INT16SZ) != INT16SZ ||
            write(sockFD, (char *)&buf, msglen) != msglen) {
		int e = errno;
		perror(";; write");
		(void) close(sockFD);
		sockFD = -1;
		return(e);
	}

	dname[0][0] = '\0';
	for (done = 0; !done; NULL) {
	    u_int16_t tmp;

	    /*
	     * Read the length of the response.
	     */

	    cp = (u_char *) &tmp;
	    amtToRead = INT16SZ;
	    while (amtToRead > 0 && (numRead=read(sockFD, cp, amtToRead)) > 0){
		cp	  += numRead;
		amtToRead -= numRead;
	    }
	    if (numRead <= 0) {
		error = ERR_READING_LEN;
		break;
	    }

	    if ((len = _getshort((u_char*)&tmp)) == 0) {
		break;	/* nothing left to read */
	    }

	    /*
	     * The server sent too much data to fit the existing buffer --
	     * allocate a new one.
	     */
	    if (len > (u_int)answerLen) {
		if (answerLen != 0) {
		    free(answer);
		}
		answerLen = len;
		answer = (u_char *)Malloc(answerLen);
	    }

	    /*
	     * Read the response.
	     */

	    amtToRead = len;
	    cp = answer;
	    while (amtToRead > 0 && (numRead=read(sockFD, cp, amtToRead)) > 0) {
		cp += numRead;
		amtToRead -= numRead;
	    }
	    if (numRead <= 0) {
		error = ERR_READING_MSG;
		break;
	    }

	    result = printRR(stdout, answer, cp);
	    if (result != 0) {
		error = ERR_PRINTING;
		break;
	    }
	    numRecords += htons(((HEADER *)answer)->ancount);
	    numAnswers++;

	    /* Header. */
	    cp = answer + HFIXEDSZ;
	    /* Question. */
	    for (count = ntohs(((HEADER *)answer)->qdcount);	
		 count > 0;
		 count--)
		cp += dn_skipname(cp, answer + len) + QFIXEDSZ;
	    /* Answer. */
	    for (count = ntohs(((HEADER *)answer)->ancount);
		 count > 0;
		 count--) {
		n = dn_expand(answer, answer + len, cp,
			      dname[soacnt], sizeof dname[0]);
		if (n < 0) {
		    error = ERR_PRINTING;
		    done++;
		    break;
		}
		cp += n;
		GETSHORT(type, cp);
		GETSHORT(class, cp);
		cp += INT32SZ;	/* ttl */
		GETSHORT(rlen, cp);
		cp += rlen;
		if (type == T_SOA && soacnt++ &&
		    !strcasecmp(dname[0], dname[1])) {
		    done++;
		    break;
		}
	    }
	}

	printf(";; Received %d answer%s (%d record%s).\n",
	       numAnswers, (numAnswers != 1) ? "s" : "",
	       numRecords, (numRecords != 1) ? "s" : "");

	(void) close(sockFD);
	sockFD = -1;

	switch (error) {
	    case NO_ERRORS:
		return (0);

	    case ERR_READING_LEN:
		return(EMSGSIZE);

	    case ERR_PRINTING:
		return(result);

	    case ERR_READING_MSG:
		return(EMSGSIZE);

	    default:
		return(EFAULT);
	}
}

static int
printRR(file, msg, eom)
    FILE	*file;
    u_char	*msg, *eom;
{
    register u_char	*cp;
    HEADER		*headerPtr;
    int			type, class, dlen, nameLen;
    u_int32_t		ttl;
    int			n, pref;
    struct in_addr	inaddr;
    char		name[NAME_LEN];
    char		name2[NAME_LEN];
    Boolean		stripped;

    /*
     * Read the header fields.
     */
    headerPtr = (HEADER *)msg;
    cp = msg + HFIXEDSZ;
    if (headerPtr->rcode != NOERROR) {
	return(headerPtr->rcode);
    }

    /*
     *  We are looking for info from answer resource records.
     *  If there aren't any, return with an error. We assume
     *  there aren't any question records.
     */

    if (ntohs(headerPtr->ancount) == 0) {
	return(NO_INFO);
    }
    for (n = ntohs(headerPtr->qdcount); n > 0; n--) {
	nameLen = dn_skipname(cp, eom);
	if (nameLen < 0)
	    return (ERROR);
	cp += nameLen + QFIXEDSZ;
    }
#ifdef PROTOCOLDEBUG
    printf(";;; (message of %d octets has %d answers)\n",
	   eom - msg, ntohs(headerPtr->ancount));
#endif
    for (n = ntohs(headerPtr->ancount); n > 0; n--)
	cp = (u_char*) p_rr(cp, msg, stdout);
    return(SUCCESS);
}

static struct timeval
difftv(a, b)
	struct timeval a, b;
{
	static struct timeval diff;

	diff.tv_sec = b.tv_sec - a.tv_sec;
	if ((diff.tv_usec = b.tv_usec - a.tv_usec) < 0) {
		diff.tv_sec--;
		diff.tv_usec += 1000000;
	}
	return(diff);
}

static void
prnttime(t)
	struct timeval t;
{
	printf("%lu msec", (u_long)(t.tv_sec * 1000 + (t.tv_usec / 1000)));
}
