/*
 * Copyright (c) 1985, 1989 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/*
 * Originally, this program came from Rutgers University, however it
 * is based on nslookup and other pieces of named tools, so it needs
 * that copyright notice.
 */

/*
 * Rewritten by Eric Wassenaar, Nikhef-H, <e07@nikhef.nl>
 *
 * The officially maintained source of this program is available
 * via anonymous ftp from machine 'ftp.nikhef.nl' [192.16.199.1]
 * in the directory '/pub/network' as 'host.tar.Z'
 *
 * You are kindly requested to report bugs and make suggestions
 * for improvements to the author at the given email address,
 * and to not re-distribute your own modifications to others.
 */

#ifndef lint
static char Version[] = "@(#)host.c	e07@nikhef.nl (Eric Wassenaar) 961013";
#endif

#if defined(apollo) && defined(lint)
#define __attribute(x)
#endif

#define justfun			/* this is only for fun */
#undef  obsolete		/* old code left as a reminder */
#undef  notyet			/* new code for possible future use */

/*
 *			New features
 *
 * - Major overhaul of the entire code.
 * - Very rigid error checking, with more verbose error messages.
 * - Zone listing section completely rewritten.
 * - It is now possible to do recursive listings into delegated zones.
 * - Maintain resource record statistics during zone listings.
 * - Maintain count of hosts during zone listings.
 * - Check for various extraneous conditions during zone listings.
 * - Check for illegal domain names containing invalid characters.
 * - Verify that certain domain names represent canonical host names.
 * - Perform ttl consistency checking during zone listings.
 * - Exploit multiple server addresses if available.
 * - Option to exploit only primary server for zone transfers.
 * - Option to exclude info from names that do not reside in a zone.
 * - Implement timeout handling during connect and read.
 * - Write resource record output to optional log file.
 * - Special MB tracing by recursively expanding MR and MG records.
 * - Special mode to check SOA records at each nameserver for a zone.
 * - Special mode to check reverse mappings of host addresses.
 * - Extended syntax allows multiple arguments on command line or stdin.
 * - Configurable default options in HOST_DEFAULTS environment variable.
 * - Implement new resource record types from RFC 1183 and 1348.
 * - Basic experimental NSAP support as defined in RFC 1637.
 * - Implement new resource record types from RFC 1664 and 1712.
 * - Implement new resource record types from RFC 1876 and 1886.
 * - Code is extensively documented.
 */

/*
 *			Publication history
 *
 * This information has been moved to the RELEASE_NOTES file.
 */

/*
 *			Compilation options
 *
 * This program usually compiles without special compilation options,
 * but for some platforms you may have to define special settings.
 * See the Makefile and the header file port.h for details.
 */

/*
 *			Miscellaneous notes
 *
 * This program should be linked explicitly with the BIND resolver library
 * in case the default gethostbyname() or gethostbyaddr() routines use a
 * non-standard strategy for retrieving information. These functions in the
 * resolver library call on the nameserver, and fall back on the hosts file
 * only if no nameserver is running (ECONNREFUSED).
 *
 * You may also want to link this program with the BIND resolver library if
 * your default library has not been compiled with DEBUG printout enabled.
 *
 * The version of the resolver should be BIND 4.8.2 or later. The crucial
 * include files are <netdb.h>, (resolv.h>, <arpa/nameser.h>. These files
 * are assumed to be present in the /usr/include directory.
 *
 * The resolver code depends on the definition of the BSD pre-processor
 * variable. This variable is usually defined in the file <sys/param.h>.
 *
 * The definition of this variable determines the method how to handle
 * datagram connections. This may not work properly on all platforms.
 *
 * The hostent struct defined in <netdb.h> is assumed to handle multiple
 * addresses in h_addr_list[]. Usually this is true if BSD >= 43.
 *
 * Your nameserver may not handle queries about top-level zones properly
 * if the "domain" directive is present in the named.boot file. It will
 * append the default domain to single names for which no data is cached.
 *
 * The treatment of TXT records has changed from 4.8.2 to 4.8.3. Formerly,
 * the data consisted simply of the text string. Now, the text string is
 * preceded by the character count with a maximum of 255, and multiple
 * strings are embedded if the total character count exceeds 255.
 * We handle only the new situation in this program, assuming that nobody
 * uses TXT records before 4.8.3 (unfortunately this is not always true:
 * current vendor supplied software may sometimes be even pre-BIND 4.8.2).
 *
 * Note that in 4.8.3 PACKETSZ from nameser.h is still at 512, which is
 * the maximum possible packet size for datagrams, whereas MAXDATA from
 * db.h has increased from 256 to 2048. The resolver defines MAXPACKET
 * as 1024. The nameserver reads queries in a buffer of size BUFSIZ.
 *
 * The gethostbyname() routine in 4.8.3 interprets dotted quads (if not
 * terminated with a dot) and simulates a gethostbyaddr(), but we will
 * not rely on it, and handle dotted quads ourselves.
 *
 * On some systems a bug in the _doprnt() routine exists which prevents
 * printf("%.*s", n, string) to be printed correctly if n == 0.
 *
 * This program has not been optimized for speed. Especially the memory
 * management is simple and straightforward.
 */

/*
 *			Terminology used
 *
 * Gateway hosts.
 * These are hosts that have more than one address registered under
 * the same name. Obviously we cannot recognize a gateway host if it
 * has different names associated with its different addresses.
 *
 * Duplicate hosts.
 * These are non-gateway hosts of which the address was found earlier
 * but with a different name, possibly in a totally different zone.
 * Such hosts should not be counted again in the overall host count.
 * This situation notably occurs in e.g. the "ac.uk" domain which has
 * many names registered in both the long and the abbreviated form,
 * such as 'host.department.university.ac.uk' and 'host.dept.un.ac.uk'.
 * This is probably not an error per se. It is an error if some domain
 * has registered a foreign address under a name within its own domain.
 * To recognize duplicate hosts when traversing many zones, we have to
 * maintain a global list of host addresses. To simplify things, only
 * single-address hosts are handled as such.
 *
 * Extrazone hosts.
 * These are hosts which belong to a zone but which are not residing
 * directly within the zone under consideration and which are not
 * glue records for a delegated zone of the given zone. E.g. if we are
 * processing the zone 'bar' and find 'host.foo.bar' but 'foo.bar' is not
 * an NS registered delegated zone of 'bar' then it is considered to be
 * an extrazone host. This is not necessarily an error, but it could be.
 *
 * Lame delegations.
 * If we query the SOA record of a zone at a supposedly authoritative
 * nameserver for that zone (listed in the NS records for the zone),
 * the SOA record should be present and the answer authoritative.
 * If not, we flag a lame delegation of the zone to that nameserver.
 * This may need refinement in some special cases.
 * A lame delegation is also flagged if we discover that a nameserver
 * mentioned in an NS record does not exist when looking up its address.
 *
 * Primary nameserver.
 * This utility assumes that the first domain name in the RHS of the
 * SOA record for a zone contains the name of the primary nameserver
 * (or one of the primary nameservers) for that zone. Unfortunately,
 * this field has not been unambiguously defined. Nevertheless, many
 * hostmasters interpret the definitions given in RFC 1033 and 1035
 * as such, and therefore host will continue doing so. Interpretation
 * as the machine that holds the zone data disk file is pretty useless.
 */

/*
 *		Usage: host [options] name [server]
 *		Usage: host [options] -x [name ...]
 *		Usage: host [options] -X server [name ...]
 *
 * Regular command line options:
 * ----------------------------
 *
 * -t type	specify query type; default is T_A for normal mode
 * -a		specify query type T_ANY
 * -v		print verbose messages (-vv is very verbose)
 * -d		print debugging output (-dd prints even more)
 *
 * Special mode options.
 * --------------------
 *
 * -l		special mode to generate zone listing for a zone
 * -L level	do recursive zone listing/checking this level deep
 * -p		use primary nameserver of zone for zone transfers
 * -P server	give priority to preferred servers for zone transfers
 * -N zone	do not perform zone transfer for these explicit zones
 * -S		print zone resource record statistics
 * -H		special mode to count hosts residing in a zone
 * -G		same as -H but lists gateway hosts in addition
 * -E		same as -H but lists extrazone hosts in addition
 * -D		same as -H but lists duplicate hosts in addition
 * -C		special mode to check SOA records for a zone
 * -A		special mode to check reverse mappings of host addresses
 *
 * Miscellaneous options.
 * ---------------------
 *
 * -f filename	log resource record output also in given file
 * -F filename	same as -f, but exchange role of stdout and log file
 * -I chars	chars are not considered illegal in domain names
 * -i		generate reverse in-addr.arpa query for dotted quad
 * -n		generate reverse nsap.int query for dotted nsap address
 * -q		be quiet about some non-fatal errors
 * -T		print ttl value during non-verbose output
 * -Z		print selected RR output in full zone file format
 *
 * Seldom used options.
 * -------------------
 *
 * -c class	specify query class; default is C_IN
 * -e		exclude info from names that do not reside in the zone
 * -m		specify query type T_MAILB and trace MB records
 * -o		suppress resource record output to stdout
 * -r		do not use recursion when querying nameserver
 * -R		repeatedly add search domains to qualify queryname
 * -s secs	specify timeout value in seconds; default is 2 * 5
 * -u		use virtual circuit instead of datagram for queries
 * -w		wait until nameserver becomes available
 *
 * Undocumented options. (Experimental, subject to change)
 * --------------------
 *
 * -g length	only select names that are at least this long
 * -B		enforce full BIND behavior during DNSRCH
 * -M		special mode to list mailable delegated zones of zone
 * -W		special mode to list wildcard records in a zone
 * -z		special mode to list delegated zones in a zone
 */

static char Usage[] =
"\
Usage:      host [-v] [-a] [-t querytype] [options]  name  [server]\n\
Listing:    host [-v] [-a] [-t querytype] [options]  -l zone  [server]\n\
Hostcount:  host [-v] [options] -H [-D] [-E] [-G] zone\n\
Check soa:  host [-v] [options] -C zone\n\
Addrcheck:  host [-v] [options] -A host\n\
Listing options: [-L level] [-S] [-A] [-p] [-P prefserver] [-N skipzone]\n\
Common options:  [-d] [-f|-F filename] [-I chars] [-i|-n] [-q] [-T] [-Z]\n\
Other options:   [-c class] [-e] [-m] [-o] [-r] [-R] [-s secs] [-u] [-w]\n\
Extended usage:  [-x [name ...]] [-X server [name ...]]\
";

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>

#include <sys/types.h>		/* not always automatically included */
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>

#undef NOERROR			/* in <sys/streams.h> on solaris 2.x */
#include <arpa/nameser.h>
#include <resolv.h>

#include "port.h"		/* various portability definitions */
#include "conf.h"		/* various configuration definitions */
#include "type.h"		/* types should be in <arpa/nameser.h> */
#include "exit.h"		/* exit codes come from <sysexits.h> */

typedef int	bool;		/* boolean type */
#define TRUE	1
#define FALSE	0

#ifndef NO_DATA
#define NO_DATA	NO_ADDRESS	/* used here only in case authoritative */
#endif

#define NO_RREC	(NO_DATA + 1)	/* used for non-authoritative NO_DATA */
#define NO_HOST	(NO_DATA + 2)	/* used for non-authoritative HOST_NOT_FOUND */

#define QUERY_REFUSED  (NO_DATA + 3)	/* query was refused by server */
#define SERVER_FAILURE (NO_DATA + 4)	/* instead of TRY_AGAIN upon SERVFAIL */
#define HOST_NOT_CANON (NO_DATA + 5)	/* host name is not canonical */

#define T_NONE	0		/* yet unspecified resource record type */
#define T_FIRST	T_A		/* first possible type in resource record */
#define T_LAST	(T_IXFR - 1)	/* last  possible type in resource record */

#ifndef NOCHANGE
#define NOCHANGE 0xf		/* compatibility with older BIND versions */
#endif

#define NOT_DOTTED_QUAD	((ipaddr_t)-1)
#define BROADCAST_ADDR	((ipaddr_t)0xffffffff)
#define LOCALHOST_ADDR	((ipaddr_t)0x7f000001)

#if PACKETSZ > 8192
#define MAXPACKET PACKETSZ	/* PACKETSZ should be the max udp size (512) */
#else
#define MAXPACKET 8192		/* but tcp packets can be considerably larger */
#endif

typedef union {
	HEADER header;
	u_char packet[MAXPACKET];
} querybuf;

#ifndef HFIXEDSZ
#define HFIXEDSZ 12		/* actually sizeof(HEADER) */
#endif

#define MAXDLEN (MAXPACKET - HFIXEDSZ)	/* upper bound for dlen */

#include "rrec.h"		/* resource record structures */

#define input			/* read-only input parameter */
#define output			/* modified output parameter */

#define STDIN	0
#define STDOUT	1
#define STDERR	2

#ifdef lint
#define EXTERN
#else
#define EXTERN extern
#endif

EXTERN int errno;
EXTERN int h_errno;		/* defined in the resolver library */
EXTERN res_state_t _res;	/* defined in res_init.c */
extern char *dbprefix;		/* prefix for debug messages (send.c) */
extern char *version;		/* program version number (vers.c) */

char **optargv = NULL;		/* argument list including default options */
int optargc = 0;		/* number of arguments in new argument list */

int errorcount = 0;		/* global error count */

int record_stats[T_ANY+1];	/* count of resource records per type */

char cnamebuf[MAXDNAME+1];
char *cname = NULL;		/* RHS name to which CNAME is aliased */
char mnamebuf[MAXDNAME+1];
char *mname = NULL;		/* RHS name to which MR or MG is aliased */
char soanamebuf[MAXDNAME+1];
char *soaname = NULL;		/* LHS domain name of SOA record */
char subnamebuf[MAXDNAME+1];
char *subname = NULL;		/* LHS domain name of NS record */
char adrnamebuf[MAXDNAME+1];
char *adrname = NULL;		/* LHS domain name of A record */

ipaddr_t address;		/* internet address of A record */

char *listhost = NULL;		/* actual host queried during zone listing */

char serverbuf[MAXDNAME+1];
char *server = NULL;		/* name of explicit server to query */

char realnamebuf[2*MAXDNAME+2];
char *realname = NULL;		/* the actual name that was queried */

FILE *logfile = NULL;		/* default is stdout only */
bool logexchange = FALSE;	/* exchange role of log file and stdout */

char *illegal = NULL;		/* give warning about illegal domain names */
char *skipzone = NULL;		/* zone(s) for which to skip zone transfer */
char *prefserver = NULL;	/* preferred server(s) for zone listing */

char *queryname = NULL;		/* the name about which to query */
int querytype = T_NONE;		/* the type of the query */
int queryclass = C_IN;		/* the class of the query */
ipaddr_t queryaddr;		/* set if name to query is dotted quad */

int debug = 0;			/* print resolver debugging output */
int verbose = 0;		/* verbose mode for extra output */

#ifdef justfun
int namelen = 0;		/* select records exceeding this length */
#endif

int recursive = 0;		/* recursive listmode maximum level */
int recursion_level = 0;	/* current recursion level */
int skip_level = 0;		/* level beyond which to skip checks */
int print_level = 0;		/* level below which to skip verbose output */

bool quiet = FALSE;		/* suppress non-fatal warning messages */
bool reverse = FALSE;		/* generate reverse in-addr.arpa queries */
bool revnsap = FALSE;		/* generate reverse nsap.int queries */
bool primary = FALSE;		/* use primary server for zone transfers */
bool suppress = FALSE;		/* suppress resource record output */
bool dotprint = FALSE;		/* print trailing dot in non-listing mode */
bool ttlprint = FALSE;		/* print ttl value in non-verbose mode */
bool waitmode = FALSE;		/* wait until server becomes available */
bool mailmode = FALSE;		/* trace MG and MR into MB records */
bool addrmode = FALSE;		/* check reverse mappings of addresses */
bool listmode = FALSE;		/* generate zone listing of a zone */
bool hostmode = FALSE;		/* count real hosts residing within zone */
bool duplmode = FALSE;		/* list duplicate hosts within zone */
bool extrmode = FALSE;		/* list extrazone hosts within zone */
bool gatemode = FALSE;		/* list gateway hosts within zone */
bool checkmode = FALSE;		/* check SOA records at each nameserver */
bool mxdomains = FALSE;		/* list MX records for each delegated zone */
bool wildcards = FALSE;		/* list only wildcard records in a zone */
bool listzones = FALSE;		/* list only delegated zones in a zone */
bool exclusive = FALSE;		/* exclude records that are not in zone */
bool recurskip = FALSE;		/* skip certain checks during recursion */
bool statistics = FALSE;	/* print resource record statistics */
bool bindcompat = FALSE;	/* enforce full BIND DNSRCH compatibility */
bool classprint = FALSE;	/* print class value in non-verbose mode */

#include "defs.h"		/* declaration of functions */

#define is_xdigit(c)	(isascii(c) && isxdigit(c))
#define is_space(c)	(isascii(c) && isspace(c))
#define is_alnum(c)	(isascii(c) && isalnum(c))
#define is_upper(c)	(isascii(c) && isupper(c))

#define lowercase(c)	(is_upper(c) ? tolower(c) : (c))
#define lower(c)	(((c) >= 'A' && (c) <= 'Z') ? (c) + 'a' - 'A' : (c))
#define hexdigit(n)	(((n) < 10) ? '0' + (n) : 'A' + (n) - 10);

#define bitset(a,b)	(((a) & (b)) != 0)
#define sameword(a,b)	(strcasecmp(a,b) == 0)
#define samepart(a,b)	(strncasecmp(a,b,strlen(b)) == 0)
#define samehead(a,b)	(strncasecmp(a,b,sizeof(b)-1) == 0)

#define fakename(a)	(samehead(a,"localhost.") || samehead(a,"loopback."))
#define nulladdr(a)	(((a) == 0) || ((a) == BROADCAST_ADDR))
#define fakeaddr(a)	(nulladdr(a) || ((a) == htonl(LOCALHOST_ADDR)))
#define incopy(a)	*((struct in_addr *)(a))
#define querysize(n)	(((n) > sizeof(querybuf)) ? sizeof(querybuf) : (n))

#define newlist(a,n,t)	(t *)xalloc((ptr_t *)(a), (siz_t)((n)*sizeof(t)))
#define newstruct(t)	(t *)xalloc((ptr_t *)NULL, (siz_t)(sizeof(t)))
#define newstring(s)	(char *)xalloc((ptr_t *)NULL, (siz_t)(strlen(s)+1))
#define newstr(s)	strcpy(newstring(s), s)
#define xfree(a)	(void) free((ptr_t *)(a))

#define strlength(s)	(int)strlen(s)
#define in_string(s,c)	(index(s,c) != NULL)
#define is_quoted(a,b)	(((a) > (b)) && ((a)[-1] == '\\'))

#define plural(n)	(((n) == 1) ? "" : "s")
#define plurale(n)	(((n) == 1) ? "" : "es")

#ifdef DEBUG
#define assert(condition)\
{\
	if (!(condition))\
	{\
		(void) fprintf(stderr, "assertion botch: ");\
		(void) fprintf(stderr, "%s(%d): ", __FILE__, __LINE__);\
		(void) fprintf(stderr, "%s\n", "condition");\
		exit(EX_SOFTWARE);\
	}\
}
#else
#define assert(condition)
#endif

/*
** MAIN -- Start of program host
** -----------------------------
**
**	Exits:
**		EX_SUCCESS	Operation successfully completed
**		EX_UNAVAILABLE	Could not obtain requested information
**		EX_CANTCREAT	Could not create specified log file
**		EX_NOINPUT	No input arguments were found
**		EX_NOHOST	Could not lookup explicit server
**		EX_OSERR	Could not obtain resources
**		EX_USAGE	Improper parameter/option specified
**		EX_SOFTWARE	Assertion botch in DEBUG mode
*/

int
main(argc, argv)
input int argc;
input char *argv[];
{
	register char *option;
	res_state_t new_res;		/* new resolver database */
	int result;			/* result status of action taken */
	char *program;			/* name that host was called with */
	char *servername = NULL;	/* name of explicit server */
	char *logfilename = NULL;	/* name of log file */
	bool extended = FALSE;		/* accept extended argument syntax */

	assert(sizeof(int) >= 4);	/* probably paranoid */
#ifdef obsolete
	assert(sizeof(u_short) == 2);	/* perhaps less paranoid */
	assert(sizeof(ipaddr_t) == 4);	/* but this is critical */
#endif

/*
 * Synchronize stdout and stderr in case output is redirected.
 */
	linebufmode(stdout);

/*
 * Initialize resolver, set new defaults. See show_res() for details.
 * The old defaults are (RES_RECURSE | RES_DEFNAMES | RES_DNSRCH)
 */
	(void) res_init();

	_res.options |=  RES_DEFNAMES;	/* qualify single names */
	_res.options &= ~RES_DNSRCH;	/* dotted names are qualified */

	_res.options |=  RES_RECURSE;	/* request nameserver recursion */
	_res.options &= ~RES_DEBUG;	/* turn off debug printout */
	_res.options &= ~RES_USEVC;	/* do not use virtual circuit */

	_res.retry = DEF_RETRIES;	/* number of datagram retries */
	_res.retrans = DEF_RETRANS;	/* timeout in secs between retries */

	/* initialize packet id */
	_res.id = getpid() & 0x7fff;

	/* save new defaults */
	new_res = _res;

/*
 * Check whether host was called with a different name.
 * Interpolate default options and parameters.
 */
	if (argc < 1 || argv[0] == NULL)
		fatal(Usage);

	option = getenv("HOST_DEFAULTS");
	if (option != NULL)
	{
		set_defaults(option, argc, argv);
		argc = optargc; argv = optargv;
	}

	program = rindex(argv[0], '/');
	if (program++ == NULL)
		program = argv[0];

	/* check for resource record names */
	querytype = parse_type(program);
	if (querytype < 0)
		querytype = T_NONE;

	/* check for zone listing abbreviation */
	if (sameword(program, "zone"))
		listmode = TRUE;

/*
 * Scan command line options and flags.
 */
	while (argc > 1 && argv[1] != NULL && argv[1][0] == '-')
	{
	    for (option = &argv[1][1]; *option != '\0'; option++)
	    {
		switch (*option)
		{
		    case 'w' :
			waitmode = TRUE;
			new_res.retry = DEF_RETRIES;
			new_res.retrans = DEF_RETRANS;
			break;

		    case 's' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing timeout value");
			new_res.retry = DEF_RETRIES;
			new_res.retrans = atoi(argv[2]);
			if (new_res.retrans <= 0)
				fatal("Invalid timeout value %s", argv[2]);
			argv++; argc--;
			break;

		    case 'r' :
			new_res.options &= ~RES_RECURSE;
			break;

		    case 'B' :
			bindcompat = TRUE;
			/*FALLTHROUGH*/

		    case 'R' :
			new_res.options |= RES_DNSRCH;
			break;

		    case 'u' :
			new_res.options |= RES_USEVC;
			break;

		    case 'd' :
			new_res.options |= RES_DEBUG;
			debug++;		/* increment debugging level */
			break;

		    case 'v' :
			verbose++;		/* increment verbosity level */
			break;

		    case 'q' :
			quiet = TRUE;
			break;

		    case 'i' :
			reverse = TRUE;
			break;

		    case 'n' :
			revnsap = TRUE;
			break;

		    case 'p' :
			primary = TRUE;
			break;

		    case 'o' :
			suppress = TRUE;
			break;

		    case 'e' :
			exclusive = TRUE;
			break;

		    case 'S' :
			statistics = TRUE;
			break;

		    case 'T' :
			ttlprint = TRUE;
			break;

		    case 'Z' :
			dotprint = TRUE;
			ttlprint = TRUE;
			classprint = TRUE;
			break;

		    case 'A' :
			addrmode = TRUE;
			break;

		    case 'D' :
		    case 'E' :
		    case 'G' :
		    case 'H' :
			if (*option == 'D')
				duplmode = TRUE;
			if (*option == 'E')
				extrmode = TRUE;
			if (*option == 'G')
				gatemode = TRUE;
			hostmode = TRUE;
			listmode = TRUE;
			if (querytype == T_NONE)
				querytype = -1;	/* suppress zone data output */
			break;

		    case 'C' :
			checkmode = TRUE;
			listmode = TRUE;
			if (querytype == T_NONE)
				querytype = -1;	/* suppress zone data output */
			break;

		    case 'z' :
			listzones = TRUE;
			listmode = TRUE;
			if (querytype == T_NONE)
				querytype = -1;	/* suppress zone data output */
			break;

		    case 'M' :
			mxdomains = TRUE;
			listmode = TRUE;
			if (querytype == T_NONE)
				querytype = -1;	/* suppress zone data output */
			break;

		    case 'W' :
			wildcards = TRUE;
			listmode = TRUE;
			if (querytype == T_NONE)
				querytype = T_MX;
			break;

		    case 'L' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing recursion level");
			recursive = atoi(argv[2]);
			if (recursive <= 0)
				fatal("Invalid recursion level %s", argv[2]);
			argv++; argc--;
			/*FALLTHROUGH*/

		    case 'l' :
			listmode = TRUE;
			break;

		    case 'c' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing query class");
			queryclass = parse_class(argv[2]);
			if (queryclass < 0)
				fatal("Invalid query class %s", argv[2]);
			argv++; argc--;
			break;

		    case 't' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing query type");
			querytype = parse_type(argv[2]);
			if (querytype < 0)
				fatal("Invalid query type %s", argv[2]);
			argv++; argc--;
			break;

		    case 'a' :
			querytype = T_ANY;	/* filter anything available */
			break;

		    case 'm' :
			mailmode = TRUE;
			querytype = T_MAILB;	/* filter MINFO/MG/MR/MB data */
			break;

		    case 'I' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing allowed chars");
			illegal = argv[2];
			argv++; argc--;
			break;

		    case 'P' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing preferred server");
			prefserver = argv[2];
			argv++; argc--;
			break;

		    case 'N' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing zone to be skipped");
			skipzone = argv[2];
			argv++; argc--;
			break;

		    case 'F' :
			logexchange = TRUE;
			/*FALLTHROUGH*/

		    case 'f' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing log file name");
			logfilename = argv[2];
			argv++; argc--;
			break;

		    case 'X' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing server name");
			servername = argv[2];
			argv++; argc--;
			/*FALLTHROUGH*/

		    case 'x' :
			extended = TRUE;
			break;
#ifdef justfun
		    case 'g' :
			if (argv[2] == NULL || argv[2][0] == '-')
				fatal("Missing minimum length");
			namelen = atoi(argv[2]);
			if (namelen <= 0)
				fatal("Invalid minimum length %s", argv[2]);
			argv++; argc--;
			break;
#endif
		    case 'V' :
			printf("Version %s\n", version);
			exit(EX_SUCCESS);

		    default:
			fatal(Usage);
		}
	    }

	    argv++; argc--;
	}

/*
 * Check the remaining arguments.
 */
	/* old syntax must have at least one argument */
	if (!extended && (argc < 2 || argv[1] == NULL || argc > 3))
		fatal(Usage);

	/* old syntax has explicit server as second argument */
	if (!extended && (argc > 2 && argv[2] != NULL))
		servername = argv[2];

/*
 * Open log file if requested.
 */
	if (logfilename != NULL)
		set_logfile(logfilename);

/*
 * Set default preferred server for zone listings, if not specified.
 */
	if (listmode && (prefserver == NULL))
		prefserver = myhostname();

/*
 * Check for possible alternative server. Use new resolver defaults.
 */
	if (servername != NULL)
		set_server(servername);

/*
 * Do final resolver initialization.
 * Show resolver parameters and special environment options.
 */
	/* set new resolver values changed by command options */
	_res.retry = new_res.retry;
	_res.retrans = new_res.retrans;
	_res.options = new_res.options;

	/* show the new resolver database */
	if (verbose > 1 || debug > 1)
		show_res();

	/* show customized default domain */
	option = getenv("LOCALDOMAIN");
	if (option != NULL && verbose > 1)
		printf("Explicit local domain %s\n\n", option);

/*
 * Process command line argument(s) depending on syntax.
 */
	if (!extended) /* only one argument */
		result = process_name(argv[1]);

	else if (argc < 2) /* no arguments */
		result = process_file(stdin);

	else /* multiple command line arguments */
		result = process_argv(argc, argv);

/*
 * Report result status of action taken.
 */
	exit(result);
	/*NOTREACHED*/
}

/*
** SET_DEFAULTS -- Interpolate default options and parameters in argv
** ------------------------------------------------------------------
**
**	The HOST_DEFAULTS environment variable gives customized options.
**
**	Returns:
**		None.
**
**	Outputs:
**		Creates ``optargv'' vector with ``optargc'' arguments.
*/

void
set_defaults(option, argc, argv)
input char *option;			/* option string */
input int argc;				/* original command line arg count */
input char *argv[];			/* original command line arguments */
{
	register char *p, *q;
	register int i;

/*
 * Allocate new argument vector.
 */
	optargv = newlist(NULL, 2, char *);
	optargv[0] = argv[0];
	optargc = 1;

/*
 * Construct argument list from option string.
 */
	for (q = "", p = newstr(option); *p != '\0'; p = q)
	{
		while (is_space(*p))
			p++;

		if (*p == '\0')
			break;

		for (q = p; *q != '\0' && !is_space(*q); q++)
			continue;

		if (*q != '\0')
			*q++ = '\0';

		optargv = newlist(optargv, optargc+2, char *);
		optargv[optargc] = p;
		optargc++;
	}

/*
 * Append command line arguments.
 */
	for (i = 1; i < argc && argv[i] != NULL; i++)
	{
		optargv = newlist(optargv, optargc+2, char *);
		optargv[optargc] = argv[i];
		optargc++;
	}

	/* and terminate */
	optargv[optargc] = NULL;
}

/*
** PROCESS_ARGV -- Process command line arguments
** ----------------------------------------------
**
**	Returns:
**		EX_SUCCESS if information was obtained successfully.
**		Appropriate exit code otherwise.
*/

int
process_argv(argc, argv)
input int argc;
input char *argv[];
{
	register int i;
	int result;			/* result status of action taken */
	int excode = EX_NOINPUT;	/* overall result status */

	for (i = 1; i < argc && argv[i] != NULL; i++)
	{
		/* process a single argument */
		result = process_name(argv[i]);

		/* maintain overall result */
		if (result != EX_SUCCESS || excode == EX_NOINPUT)
			excode = result;
	}

	/* return overall result */
	return(excode);
}

/*
** PROCESS_FILE -- Process arguments from input file
** -------------------------------------------------
**
**	Returns:
**		EX_SUCCESS if information was obtained successfully.
**		Appropriate exit code otherwise.
*/

int
process_file(fp)
input FILE *fp;				/* input file with query names */
{
	register char *p, *q;
	char buf[BUFSIZ];
	int result;			/* result status of action taken */
	int excode = EX_NOINPUT;	/* overall result status */

	while (fgets(buf, sizeof(buf), fp) != NULL)
	{
		p = index(buf, '\n');
		if (p != NULL)
			*p = '\0';

		/* extract names separated by whitespace */
		for (q = "", p = buf; *p != '\0'; p = q)
		{
			while (is_space(*p))
				p++;

			/* ignore comment lines */
			if (*p == '\0' || *p == '#' || *p == ';')
				break;

			for (q = p; *q != '\0' && !is_space(*q); q++)
				continue;

			if (*q != '\0')
				*q++ = '\0';

			/* process a single argument */
			result = process_name(p);

			/* maintain overall result */
			if (result != EX_SUCCESS || excode == EX_NOINPUT)
				excode = result;
		}
	}

	/* return overall result */
	return(excode);
}

/*
** PROCESS_NAME -- Process a single command line argument
** ------------------------------------------------------
**
**	Returns:
**		EX_SUCCESS if information was obtained successfully.
**		Appropriate exit code otherwise.
**
**	Wrapper for execute_name() to hide administrative tasks.
*/

int
process_name(name)
input char *name;			/* command line argument */
{
	int result;			/* result status of action taken */
	static int save_querytype;
	static bool save_reverse;
	static bool firstname = TRUE;

	/* separate subsequent pieces of output */
	if (!firstname && (verbose || debug || checkmode))
		printf("\n");

/*
 * Some global variables are redefined further on. Save their initial
 * values in the first pass, and restore them during subsequent passes.
 */
	if (firstname)
	{
		save_querytype = querytype;
		save_reverse = reverse;
		firstname = FALSE;
	}
	else
	{
		querytype = save_querytype;
		reverse = save_reverse;
	}

/*
 * Do the real work.
 */
	result = execute_name(name);
	return(result);
}

/*
** EXECUTE_NAME -- Process a single command line argument
** ------------------------------------------------------
**
**	Returns:
**		EX_SUCCESS if information was obtained successfully.
**		Appropriate exit code otherwise.
**
**	Outputs:
**		Defines ``queryname'' and ``queryaddr'' appropriately.
**
**	Side effects:
**		May redefine ``querytype'' and ``reverse'' if necessary.
*/

int
execute_name(name)
input char *name;			/* command line argument */
{
	bool result;			/* result status of action taken */

	/* check for nonsense input name */
	if (strlength(name) > MAXDNAME)
	{
		errmsg("Query name %s too long", name);
		return(EX_USAGE);
	}

/*
 * Analyze the name and type to be queried about.
 * The name can be an ordinary domain name, or an internet address
 * in dotted quad notation. If the -n option is given, the name is
 * supposed to be a dotted nsap address.
 * Furthermore, an empty input name is treated as the root domain.
 */
	queryname = name;
	if (queryname[0] == '\0')
		queryname = ".";

	if (sameword(queryname, "."))
		queryaddr = NOT_DOTTED_QUAD;
	else
		queryaddr = inet_addr(queryname);

/*
 * Generate reverse in-addr.arpa query if so requested.
 * The input name must be a dotted quad, and be convertible.
 */
	if (reverse)
	{
		if (queryaddr == NOT_DOTTED_QUAD)
			name = NULL;
		else
			name = in_addr_arpa(queryname);

		if (name == NULL)
		{
			errmsg("Invalid dotted quad %s", queryname);
			return(EX_USAGE);
		}

		/* redefine appropriately */
		queryname = name;
		queryaddr = NOT_DOTTED_QUAD;
	}

/*
 * Generate reverse nsap.int query if so requested.
 * The input name must be a dotted nsap, and be convertible.
 */
	if (revnsap)
	{
		if (reverse)
			name = NULL;
		else
			name = nsap_int(queryname);

		if (name == NULL)
		{
			errmsg("Invalid nsap address %s", queryname);
			return(EX_USAGE);
		}

		/* redefine appropriately */
		queryname = name;
		queryaddr = NOT_DOTTED_QUAD;

		/* this is also a reversed mapping domain */
		reverse = TRUE;
	}

/*
 * In regular mode, the querytype is used to formulate the nameserver
 * query, and any response is filtered out when processing the answer.
 * In listmode, the querytype is used to filter out the proper records.
 */
	/* set querytype for regular mode if unspecified */
	if ((querytype == T_NONE) && !listmode)
	{
		if ((queryaddr != NOT_DOTTED_QUAD) || reverse)
			querytype = T_PTR;
		else
			querytype = T_A;
	}

/*
 * Check for incompatible options.
 */
	/* cannot have dotted quad in listmode */
	if (listmode && (queryaddr != NOT_DOTTED_QUAD))
	{
		errmsg("Invalid query name %s", queryname);
		return(EX_USAGE);
	}

	/* must have regular name or dotted quad in addrmode */
	if (!listmode && addrmode && reverse)
	{
		errmsg("Invalid query name %s", queryname);
		return(EX_USAGE);
	}

	/* show what we are going to query about */
	if (verbose)
		show_types(queryname, querytype, queryclass);

/*
 * All set. Perform requested function.
 */
	result = execute(queryname, queryaddr);
	return(result ? EX_SUCCESS : EX_UNAVAILABLE);
}

/*
** EXECUTE -- Perform the requested function
** -----------------------------------------
**
**	Returns:
**		TRUE if information was obtained successfully.
**		FALSE otherwise.
**
**	The whole environment has been set up and checked.
*/

bool
execute(name, addr)
input char *name;			/* name to query about */
input ipaddr_t addr;			/* explicit address of query */
{
	bool result;			/* result status of action taken */

/*
 * Special mode to list contents of specified zone.
 */
	if (listmode)
	{
		result = list_zone(name);
		return(result);
	}

/*
 * Special mode to check reverse mappings of host addresses.
 */
	if (addrmode)
	{
		if (addr == NOT_DOTTED_QUAD)
			result = check_addr(name);
		else
			result = check_name(addr);
		return(result);
	}

/*
 * Regular mode to query about specified host.
 */
	result = host_query(name, addr);
	return(result);
}

/*
** HOST_QUERY -- Regular mode to query about specified host
** --------------------------------------------------------
**
**	Returns:
**		TRUE if information was obtained successfully.
**		FALSE otherwise.
*/

bool
host_query(name, addr)
input char *name;			/* name to query about */
input ipaddr_t addr;			/* explicit address of query */
{
	struct hostent *hp;
	struct in_addr inaddr;
	char newnamebuf[MAXDNAME+1];
	char *newname = NULL;		/* name to which CNAME is aliased */
	int ncnames = 0;		/* count of CNAMEs in chain */
	bool result;			/* result status of action taken */

	inaddr.s_addr = addr;

	result = FALSE;
	h_errno = TRY_AGAIN;

	/* retry until positive result or permanent failure */
	while (result == FALSE && h_errno == TRY_AGAIN)
	{
		/* reset before each query to avoid stale data */
		errno = 0;
		realname = NULL;

		if (addr == NOT_DOTTED_QUAD)
		{
			/* reset CNAME indicator */
			cname = NULL;

			/* lookup the name in question */
			if (newname == NULL)
				result = get_hostinfo(name, FALSE);
			else
				result = get_hostinfo(newname, TRUE);

			/* recurse on CNAMEs, but not too deep */
			if (cname && (querytype != T_CNAME))
			{
				newname = strcpy(newnamebuf, cname);

				if (ncnames++ > MAXCHAIN)
				{
					errmsg("Possible CNAME loop");
					return(FALSE);
				}

				result = FALSE;
				h_errno = TRY_AGAIN;
				continue;
			}
		}
		else
		{
			hp = geth_byaddr((char *)&inaddr, INADDRSZ, AF_INET);
			if (hp != NULL)
			{
				print_host("Name", hp);
				result = TRUE;
			}
		}

		/* only retry if so requested */
		if (!waitmode)
			break;
	}

	/* use actual name if available */
	if (realname != NULL)
		name = realname;

	/* explain the reason of a failure */
	if (result == FALSE)
		ns_error(name, querytype, queryclass, server);

	return(result);
}

/*
** MYHOSTNAME -- Determine our own fully qualified host name
** ---------------------------------------------------------
**
**	Returns:
**		Pointer to own host name.
**		Aborts if host name could not be determined.
*/

char *
myhostname()
{
	struct hostent *hp;
	static char mynamebuf[MAXDNAME+1];
	static char *myname = NULL;

	if (myname == NULL)
	{
		if (gethostname(mynamebuf, MAXDNAME) < 0)
		{
			perror("gethostname");
			exit(EX_OSERR);
		}
		mynamebuf[MAXDNAME] = '\0';

		hp = gethostbyname(mynamebuf);
		if (hp == NULL)
		{
			ns_error(mynamebuf, T_A, C_IN, server);
			errmsg("Error in looking up own name");
			exit(EX_NOHOST);
		}

		/* cache the result */
		myname = strncpy(mynamebuf, hp->h_name, MAXDNAME);
		myname[MAXDNAME] = '\0';
	}

	return(myname);
}

/*
** SET_SERVER -- Override default nameserver with explicit server
** --------------------------------------------------------------
**
**	Returns:
**		None.
**		Aborts the program if an unknown host was given.
**
**	Side effects:
**		The global variable ``server'' is set to indicate
**		that an explicit server is being used.
**
**	The default nameserver addresses in the resolver database
**	which are initialized by res_init() from /etc/resolv.conf
**	are replaced with the (possibly multiple) addresses of an
**	explicitly named server host. If a dotted quad is given,
**	only that single address will be used.
**
**	The answers from such server must be interpreted with some
**	care if we don't know beforehand whether it can be trusted.
*/

void
set_server(name)
input char *name;			/* name of server to be queried */
{
	register int i;
	struct hostent *hp;
	struct in_addr inaddr;
	ipaddr_t addr;			/* explicit address of server */

	/* check for nonsense input name */
	if (strlength(name) > MAXDNAME)
	{
		errmsg("Server name %s too long", name);
		exit(EX_USAGE);
	}

/*
 * Overrule the default nameserver addresses.
 */
	addr = inet_addr(name);
	inaddr.s_addr = addr;

	if (addr == NOT_DOTTED_QUAD)
	{
		/* lookup all of its addresses; this must not fail */
		hp = gethostbyname(name);
		if (hp == NULL)
		{
			ns_error(name, T_A, C_IN, server);
			errmsg("Error in looking up server name");
			exit(EX_NOHOST);
		}

		for (i = 0; i < MAXNS && hp->h_addr_list[i]; i++)
		{
			nslist(i).sin_family = AF_INET;
			nslist(i).sin_port = htons(NAMESERVER_PORT);
			nslist(i).sin_addr = incopy(hp->h_addr_list[i]);
		}
		_res.nscount = i;
	}
	else
	{
		/* lookup the name, but use only the given address */
		hp = gethostbyaddr((char *)&inaddr, INADDRSZ, AF_INET);

		nslist(0).sin_family = AF_INET;
		nslist(0).sin_port = htons(NAMESERVER_PORT);
		nslist(0).sin_addr = inaddr;
		_res.nscount = 1;
	}

/*
 * Indicate the use of an explicit server.
 */
	if (hp != NULL)
	{
		server = strncpy(serverbuf, hp->h_name, MAXDNAME);
		server[MAXDNAME] = '\0';

		if (verbose)
			print_host("Server", hp);
	}
	else
	{
		server = strcpy(serverbuf, inet_ntoa(inaddr));

		if (verbose)
			printf("Server: %s\n\n", server);
	}
}

/*
** SET_LOGFILE -- Initialize optional log file
** -------------------------------------------
**
**	Returns:
**		None.
**		Aborts the program if the file could not be created.
**
**	Side effects:
**		The global variable ``logfile'' is set to indicate
**		that resource record output is to be written to it.
**
**	Swap ordinary stdout and log file output if so requested.
*/

void
set_logfile(filename)
input char *filename;			/* name of log file */
{
	if (logexchange)
	{
		logfile = fdopen(dup(STDOUT), "w");
		if (logfile == NULL)
		{
			perror("fdopen");
			exit(EX_OSERR);
		}

		if (freopen(filename, "w", stdout) == NULL)
		{
			perror(filename);
			exit(EX_CANTCREAT);
		}
	}
	else
	{
		logfile = fopen(filename, "w");
		if (logfile == NULL)
		{
			perror(filename);
			exit(EX_CANTCREAT);
		}
	}
}

/*
** FATAL -- Abort program when illegal option encountered
** ------------------------------------------------------
**
**	Returns:
**		Aborts after issuing error message.
*/

void /*VARARGS1*/
fatal(fmt, a, b, c, d)
input char *fmt;			/* format of message */
input char *a, *b, *c, *d;		/* optional arguments */
{
	(void) fprintf(stderr, fmt, a, b, c, d);
	(void) fprintf(stderr, "\n");
	exit(EX_USAGE);
}


/*
** ERRMSG -- Issue error message to error output
** ---------------------------------------------
**
**	Returns:
**		None.
**
**	Side effects:
**		Increments the global error count.
*/

void /*VARARGS1*/
errmsg(fmt, a, b, c, d)
input char *fmt;			/* format of message */
input char *a, *b, *c, *d;		/* optional arguments */
{
	(void) fprintf(stderr, fmt, a, b, c, d);
	(void) fprintf(stderr, "\n");

	/* flag an error */
	errorcount++;
}

/*
** GET_HOSTINFO -- Principal routine to query about given name
** -----------------------------------------------------------
**
**	Returns:
**		TRUE if requested info was obtained successfully.
**		FALSE otherwise.
**
**	This is the equivalent of the resolver module res_search().
**
**	In this program RES_DEFNAMES is always on, and RES_DNSRCH
**	is off by default. This means that single names without dot
**	are always, and only, tried within the own default domain,
**	and compound names are assumed to be already fully qualified.
**
**	The default BIND behavior can be simulated by turning on
**	RES_DNSRCH with -R. The given name, whether or not compound,
**	is then	first tried within the possible search domains.
**
**	Note. In the latter case, the search terminates in case the
**	specified name exists but does not have the desired type.
**	The BIND behavior is to continue the search. This can be
**	simulated with the undocumented option -B.
*/

bool
get_hostinfo(name, qualified)
input char *name;			/* name to query about */
input bool qualified;			/* assume fully qualified if set */
{
	register char **domain;
	register char *cp;
	int dot;			/* number of dots in query name */
	bool result;			/* result status of action taken */
	char oldnamebuf[2*MAXDNAME+2];
	char *oldname;			/* saved actual name when NO_DATA */
	int nodata = 0;			/* NO_DATA status during DNSRCH */
	int nquery = 0;			/* number of extra search queries */

/*
 * Single dot means root zone.
 */
	if (sameword(name, "."))
		qualified = TRUE;

/*
 * Names known to be fully qualified are just tried ``as is''.
 */
	if (qualified)
	{
		result = get_domaininfo(name, (char *)NULL);
		return(result);
	}

/*
 * Count number of dots. Move to the end of the name.
 */
	for (dot = 0, cp = name; *cp != '\0'; cp++)
		if (*cp == '.')
			dot++;

/*
 * Check for aliases of single name.
 * Note that the alias is supposed to be fully qualified.
 */
	if (dot == 0 && (cp = hostalias(name)) != NULL)
	{
		if (verbose)
			printf("Aliased %s to %s\n", name, cp);

		result = get_domaininfo(cp, (char *)NULL);
		return(result);
	}

/*
 * Trailing dot means absolute (fully qualified) address.
 */
	if (dot != 0 && cp[-1] == '.')
	{
		cp[-1] = '\0';
		result = get_domaininfo(name, (char *)NULL);
		cp[-1] = '.';
		return(result);
	}

/*
 * Append own default domain and other search domains if appropriate.
 */
	if ((dot == 0 && bitset(RES_DEFNAMES, _res.options)) ||
	    (dot != 0 && bitset(RES_DNSRCH, _res.options)))
	{
		for (domain = _res.dnsrch; *domain; domain++)
		{
			result = get_domaininfo(name, *domain);
			if (result)
				return(result);

			/* keep count of extra search queries */
			nquery++;

			/* in case nameserver not present */
			if (errno == ECONNREFUSED)
				return(FALSE);

			/* if no further search desired (single name) */
	    		if (!bitset(RES_DNSRCH, _res.options))
				break;

			/* if name exists but has not requested type */
			if (h_errno == NO_DATA || h_errno == NO_RREC)
			{
				if (bindcompat)
				{
					/* remember status and search up */
					oldname = strcpy(oldnamebuf, realname);
					nodata = h_errno;
					continue;
				}

				return(FALSE);
			}

			/* retry only if name does not exist at all */
			if (h_errno != HOST_NOT_FOUND && h_errno != NO_HOST)
				break;
		}
	}

/*
 * Single name lookup failed.
 */
	if (dot == 0)
	{
		/* unclear what actual name should be */
		if (nquery != 1)
			realname = NULL;

		/* restore nodata status from search */
		if (bindcompat && nodata)
		{
			realname = strcpy(realnamebuf, oldname);
			h_errno = nodata;
		}

		/* set status in case we never queried */
		if (!bitset(RES_DEFNAMES, _res.options))
			h_errno = HOST_NOT_FOUND;

		return(FALSE);
	}

/*
 * Rest means fully qualified.
 */
	result = get_domaininfo(name, (char *)NULL);

	/* restore nodata status from search */
	if (!result && bindcompat && nodata)
	{
		realname = strcpy(realnamebuf, oldname);
		h_errno = nodata;
	}

	return(result);
}

/*
** GET_DOMAININFO -- Fetch and print desired info about name in domain
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if requested info was obtained successfully.
**		FALSE otherwise.
**
**	Side effects:
**		Sets global variable ``realname'' to actual name queried.
**
**	This is the equivalent of the resolver module res_querydomain().
**
**	Things get a little complicated in case RES_DNSRCH is on.
**	If we get an answer but the data is corrupted, an error will be
**	returned and NO_RECOVERY will be set. This will terminate the
**	extra search loop, but a compound name will still be tried as-is.
**	The same holds if the query times out or we have a server failure,
**	in which case an error will be returned and TRY_AGAIN will be set.
**	For now we take this for granted. Normally RES_DNSRCH is disabled.
**	In this default case we do only one query and we have no problem.
*/

bool
get_domaininfo(name, domain)
input char *name;			/* name to query about */
input char *domain;			/* domain to which name is relative */
{
	char namebuf[2*MAXDNAME+2];	/* buffer to store full domain name */
	querybuf answer;
	register int n;
	bool result;			/* result status of action taken */

/*
 * Show what we are about to query.
 */
	if (verbose)
	{
		if (domain == NULL || domain[0] == '\0')
			printf("Trying %s", name);
		else
			printf("Trying %s within %s", name, domain);

		if (server && (verbose > 1))
			printf(" at server %s", server);

		printf(" ...\n");
	}

/*
 * Construct the actual domain name.
 * A null domain means the given name is already fully qualified.
 * If the composite name is too long, res_mkquery() will fail.
 */
	if (domain == NULL || domain[0] == '\0')
		(void) sprintf(namebuf, "%.*s", MAXDNAME, name);
	else
		(void) sprintf(namebuf, "%.*s.%.*s",
				MAXDNAME, name, MAXDNAME, domain);
	name = namebuf;

/*
 * Fetch the desired info.
 */
	n = get_info(&answer, name, querytype, queryclass);
	result = (n < 0) ? FALSE : TRUE;

/*
 * Print the relevant data.
 * If we got a positive answer, the data may still be corrupted.
 */
	if (result)
	    result = print_info(&answer, n, name, querytype, queryclass, TRUE);

/*
 * Remember the actual name that was queried.
 * Must be at the end to avoid clobbering during recursive calls.
 */
	realname = strcpy(realnamebuf, name);

	return(result);
}

/*
** GET_INFO -- Basic routine to issue a nameserver query
** -----------------------------------------------------
**
**	Returns:
**		Length of nameserver answer buffer, if obtained.
**		-1 if an error occurred (h_errno is set appropriately).
**
**	This is the equivalent of the resolver module res_query().
*/

int
get_info(answerbuf, name, type, class)
output querybuf *answerbuf;		/* location of buffer to store answer */
input char *name;			/* full name to query about */
input int type;				/* specific resource record type */
input int class;			/* specific resource record class */
{
	querybuf query;
	HEADER *bp;
	int ancount;
	register int n;

/*
 * Construct query, and send it to the nameserver.
 * res_send() will fail if no nameserver responded. In the BIND version the
 * possible values for errno are ECONNREFUSED and ETIMEDOUT. If we did get
 * an answer, errno should be reset, since res_send() may have left an errno
 * in case it has used datagrams. Our private version of res_send() will leave
 * also other error statuses, and will clear errno if an answer was obtained.
 */
	errno = 0;	/* reset before querying nameserver */

	n = res_mkquery(QUERY, name, class, type, (qbuf_t *)NULL, 0,
			(rrec_t *)NULL, (qbuf_t *)&query, sizeof(querybuf));
	if (n < 0)
	{
		if (debug)
			printf("%sres_mkquery failed\n", dbprefix);
		h_errno = NO_RECOVERY;
		return(-1);
	}

	n = res_send((qbuf_t *)&query, n, (qbuf_t *)answerbuf, sizeof(querybuf));
	if (n < 0)
	{
		if (debug)
			printf("%sres_send failed\n", dbprefix);
		h_errno = TRY_AGAIN;
		return(-1);
	}

	errno = 0;	/* reset after we got an answer */

	if (n < HFIXEDSZ)
	{
		pr_error("answer length %s too short after %s query for %s",
			itoa(n), pr_type(type), name);
		h_errno = NO_RECOVERY;
		return(-1);
	}

/*
 * Analyze the status of the answer from the nameserver.
 */
	if ((verbose > print_level) || debug)
		print_status(answerbuf, n);

	bp = (HEADER *)answerbuf;
	ancount = ntohs(bp->ancount);

	if (bp->rcode != NOERROR || ancount == 0)
	{
		switch (bp->rcode)
		{
		    case NXDOMAIN:
			/* distinguish between authoritative or not */
			h_errno = bp->aa ? HOST_NOT_FOUND : NO_HOST;
			break;

		    case NOERROR:
			/* distinguish between authoritative or not */
			h_errno = bp->aa ? NO_DATA : NO_RREC;
			break;

		    case SERVFAIL:
			h_errno = SERVER_FAILURE; /* instead of TRY_AGAIN */
			break;

		    case REFUSED:
			h_errno = QUERY_REFUSED; /* instead of NO_RECOVERY */
			break;

		    default:
			h_errno = NO_RECOVERY; /* FORMERR NOTIMP NOCHANGE */
			break;
		}
		return(-1);
	}

	/* valid answer received, avoid buffer overrun */
	h_errno = 0;
	return(querysize(n));
}

/*
** PRINT_INFO -- Check resource records in answer and print relevant data
** ----------------------------------------------------------------------
**
**	Returns:
**		TRUE if answer buffer was processed successfully.
**		FALSE otherwise.
**
**	Side effects:
**		Will recurse on MAILB records if appropriate.
**		See also side effects of the print_rrec() routine.
*/

bool
print_info(answerbuf, answerlen, name, type, class, regular)
input querybuf *answerbuf;		/* location of answer buffer */
input int answerlen;			/* length of answer buffer */
input char *name;			/* full name we are querying about */
input int type;				/* record type we are querying about */
input int class;			/* record class we are querying about */
input bool regular;			/* set if this is a regular lookup */
{
	HEADER *bp;
	int qdcount, ancount, nscount, arcount;
	u_char *msg, *eom;
	register u_char *cp;

	bp = (HEADER *)answerbuf;
	qdcount = ntohs(bp->qdcount);
	ancount = ntohs(bp->ancount);
	nscount = ntohs(bp->nscount);
	arcount = ntohs(bp->arcount);

	msg = (u_char *)answerbuf;
	eom = (u_char *)answerbuf + answerlen;
	cp  = (u_char *)answerbuf + HFIXEDSZ;

/*
 * Skip the query section in the response (present only in normal queries).
 */
	if (qdcount)
	{
		while (qdcount > 0 && cp < eom)	/* process all records */
		{
			cp = skip_qrec(name, type, class, cp, msg, eom);
			if (cp == NULL)
				return(FALSE);
			qdcount--;
		}

		if (qdcount)
		{
			pr_error("invalid qdcount after %s query for %s",
				pr_type(type), name);
			h_errno = NO_RECOVERY;
			return(FALSE);
		}
	}

/*
 * Process the actual answer section in the response.
 * During zone transfers, this is the only section available.
 */
	if (ancount)
	{
		if ((type != T_AXFR) && verbose && !bp->aa)
			printf("The following answer is not authoritative:\n");

		while (ancount > 0 && cp < eom)
		{
			/* reset for each record during zone listings */
			soaname = NULL, subname = NULL, adrname = NULL, address = 0;

			print_level++;
			cp = print_rrec(name, type, class, cp, msg, eom, regular);
			print_level--;
			if (cp == NULL)
				return(FALSE);
			ancount--;

			/* update zone information during zone listings */
			if (type == T_AXFR)
				update_zone(name);

			/* we trace down CNAME chains ourselves */
			if (regular && !verbose && cname)
				return(TRUE);

			/* recursively expand MR/MG records into MB records */
			if (regular && mailmode && mname)
				(void) get_recursive(&mname);
		}

		if (ancount)
		{
			pr_error("invalid ancount after %s query for %s",
				pr_type(type), name);
			h_errno = NO_RECOVERY;
			return(FALSE);
		}
	}

/*
 * The nameserver and additional info section are normally not processed.
 * Both sections shouldn't exist in zone transfers.
 */
	if (!verbose || exclusive)
		return(TRUE);

	if (nscount)
	{
		printf("Authoritative nameservers:\n");

		while (nscount > 0 && cp < eom)
		{
			print_level++;
			cp = print_rrec(name, type, class, cp, msg, eom, FALSE);
			print_level--;
			if (cp == NULL)
				return(FALSE);
			nscount--;
		}

		if (nscount)
		{
			pr_error("invalid nscount after %s query for %s",
				pr_type(type), name);
			h_errno = NO_RECOVERY;
			return(FALSE);
		}
	}

	if (arcount)
	{
		printf("Additional information:\n");

		while (arcount > 0 && cp < eom)
		{
			print_level++;
			cp = print_rrec(name, type, class, cp, msg, eom, FALSE);
			print_level--;
			if (cp == NULL)
				return(FALSE);
			arcount--;
		}

		if (arcount)
		{
			pr_error("invalid arcount after %s query for %s",
				pr_type(type), name);
			h_errno = NO_RECOVERY;
			return(FALSE);
		}
	}

	/* all sections were processed successfully */
	return(TRUE);
}

/*
** PRINT_DATA -- Output resource record data if this record is wanted
** ------------------------------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		The global variable ``doprint'' is set by print_rrec()
**		if we need to print the data.
*/

static bool doprint;		/* indicates whether or not to print */

void /*VARARGS1*/
print_data(fmt, a, b, c, d)
input char *fmt;			/* format of message */
input char *a, *b, *c, *d;		/* optional arguments */
{
	/* if (doprint) */
	{
		if (!suppress)
			printf(fmt, a, b, c, d);

		if (logfile != NULL)
			(void) fprintf(logfile, fmt, a, b, c, d);
	}
}

#define doprintf(x)\
{\
	if (doprint)\
	{\
		print_data x ;\
	}\
}

/*
** PRINT_RREC -- Decode single resource record and output relevant data
** --------------------------------------------------------------------
**
**	Returns:
**		Pointer to position in answer buffer after current record.
**		NULL if there was a format error in the current record.
**
**	Outputs:
**		The global variable ``doprint'' is set appropriately
**		for use by print_data().
**
**	Side effects:
**		Updates resource record statistics in record_stats[].
**		Sets ``soaname'' if this is an SOA record.
**		Sets ``subname'' if this is an NS record.
**		Sets ``adrname'' if this is an A record.
**		Sets ``address'' if this is an A record.
**		Sets ``cname'' if this is a valid CNAME record.
**		Sets ``mname'' if this is a valid MAILB record.
**		These variables must have been cleared before calling
**		print_info() and may be checked afterwards.
*/

/* print domain names after certain conversions */
#define pr_name(x)	pr_domain(x, listing)

/* check the LHS record name of these records for invalid characters */
#define test_valid(t)	(((t == T_A) && !reverse) || t == T_MX || t == T_AAAA)

/* check the RHS domain name of these records for canonical host names */
#define test_canon(t)	(t == T_NS || t == T_MX)

u_char *
print_rrec(name, qtype, qclass, cp, msg, eom, regular)
input char *name;			/* full name we are querying about */
input int qtype;			/* record type we are querying about */
input int qclass;			/* record class we are querying about */
register u_char *cp;			/* current position in answer buf */
input u_char *msg, *eom;		/* begin and end of answer buf */
input bool regular;			/* set if this is a regular lookup */
{
	char rname[MAXDNAME+1];		/* record name in LHS */
	char dname[MAXDNAME+1];		/* domain name in RHS */
	int type, class, ttl, dlen;	/* fixed values in every record */
	u_char *eor;			/* predicted position of next record */
	bool classmatch;		/* set if we want to see this class */
	bool listing;			/* set if this is a zone listing */
	char *host = listhost;		/* contacted host for zone listings */
	register int n, c;
	struct in_addr inaddr;
	struct protoent *protocol;
	struct servent *service;

/*
 * Pickup the standard values present in each resource record.
 */
	n = expand_name(name, T_NONE, cp, msg, eom, rname);
	if (n < 0)
		return(NULL);
	cp += n;

	n = 3*INT16SZ + INT32SZ;
	if (check_size(rname, T_NONE, cp, msg, eom, n) < 0)
		return(NULL);

	type = _getshort(cp);
	cp += INT16SZ;

	class = _getshort(cp);
	cp += INT16SZ;

	ttl = _getlong(cp);
	cp += INT32SZ;

	dlen = _getshort(cp);
	cp += INT16SZ;

	eor = cp + dlen;

/*
 * Decide whether or not to print this resource record.
 */
	listing = (qtype == T_AXFR || qtype == T_IXFR) ? TRUE : FALSE;

	if (listing)
	{
		classmatch = want_class(class, queryclass);
		doprint = classmatch && want_type(type, querytype);
	}
	else
	{
		classmatch = want_class(class, C_ANY);
		doprint = classmatch && want_type(type, T_ANY);
	}

	if (doprint && exclusive && !indomain(rname, name, TRUE))
		doprint = FALSE;

	if (doprint && exclusive && fakename(rname))
		doprint = FALSE;

	if (doprint && wildcards && !in_string(rname, '*'))
		doprint = FALSE;
#ifdef justfun
	if (namelen && (strlength(rname) < namelen))
		doprint = FALSE;
#endif

/*
 * Print name and common values, if appropriate.
 */
	doprintf(("%-20s", pr_name(rname)))

	if (verbose || ttlprint)
		doprintf(("\t%s", itoa(ttl)))

	if (verbose || classprint || (class != qclass))
		doprintf(("\t%s", pr_class(class)))

	doprintf(("\t%s", pr_type(type)))

/*
 * Update resource record statistics for zone listing.
 */
	if (listing && classmatch)
	{
		if (type >= T_FIRST && type <= T_LAST)
			record_stats[type]++;
	}

/*
 * Save the domain name of an SOA or NS or A record for zone listing.
 */
	if (listing && classmatch)
	{
		if (type == T_A)
			adrname = strcpy(adrnamebuf, rname);

		else if (type == T_NS)
			subname = strcpy(subnamebuf, rname);

		else if (type == T_SOA)
			soaname = strcpy(soanamebuf, rname);
	}

/*
 * Print type specific data, if appropriate.
 */
	switch (type)
	{
	    case T_A:
		if (class == C_IN || class == C_HS)
		{
			if (dlen == INADDRSZ)
			{
				bcopy((char *)cp, (char *)&inaddr, INADDRSZ);
				address = inaddr.s_addr;
				doprintf(("\t%s", inet_ntoa(inaddr)))
				cp += INADDRSZ;
				break;
			}
#ifdef obsolete
			if (dlen == INADDRSZ + 1 + INT16SZ)
			{
				bcopy((char *)cp, (char *)&inaddr, INADDRSZ);
				address = inaddr.s_addr;
				doprintf(("\t%s", inet_ntoa(inaddr)))
				cp += INADDRSZ;

				n = *cp++;
				doprintf((" ; proto = %s", itoa(n)))

				n = _getshort(cp);
				doprintf((", port = %s", itoa(n)))
				cp += INT16SZ;
				break;
			}
#endif
			address = 0;
			break;
		}
		address = 0;
		cp += dlen;
		break;

	    case T_MX:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_NS:
	    case T_PTR:
	    case T_CNAME:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;
		break;

	    case T_HINFO:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t\"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t\"%s\"", stoa(cp, n, TRUE)))
		cp += n;
		break;

	    case T_SOA:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;

		n = 5*INT32SZ;
		if (check_size(rname, type, cp, msg, eor, n) < 0)
			break;
		doprintf((" ("))

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", utoa(n)))
		doprintf(("\t;serial (version)"))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t;refresh period (%s)", pr_time(n, FALSE)))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t;retry interval (%s)", pr_time(n, FALSE)))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t;expire time (%s)", pr_time(n, FALSE)))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t;default ttl (%s)", pr_time(n, FALSE)))
		cp += INT32SZ;

		doprintf(("\n\t\t\t)"))
		break;

	    case T_WKS:
		if (check_size(rname, type, cp, msg, eor, INADDRSZ) < 0)
			break;
		bcopy((char *)cp, (char *)&inaddr, INADDRSZ);
		doprintf(("\t%s", inet_ntoa(inaddr)))
		cp += INADDRSZ;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;

		protocol = getprotobynumber(n);
		if (protocol != NULL)
			doprintf((" %s", protocol->p_name))
		else
			doprintf((" %s", itoa(n)))

		doprintf((" ("))
		n = 0;
		while (cp < eor)
		{
		    c = *cp++;
		    do
		    {
 			if (c & 0200)
			{
			    int port;

			    port = htons(n);
			    if (protocol != NULL)
				    service = getservbyport(port, protocol->p_name);
			    else
				    service = NULL;

			    if (service != NULL)
				    doprintf((" %s", service->s_name))
			    else
				    doprintf((" %s", itoa(n)))
			}
 			c <<= 1;
		    } while (++n & 07);
		}
		doprintf((" )"))
		break;

#ifdef obsolete
	    case T_TXT:
		/* if (dlen > 0) */
		{
			doprintf(("\t\"%s\"", stoa(cp, dlen, TRUE)))
			cp += dlen;
		}
		break;
#endif

	    case T_TXT:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t\"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		while (cp < eor)
		{
			if (check_size(rname, type, cp, msg, eor, 1) < 0)
				break;
			n = *cp++;
			doprintf((" \"%s\"", stoa(cp, n, TRUE)))
			cp += n;
		}
		break;

	    case T_MINFO:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_MB:
	    case T_MG:
	    case T_MR:
	    case T_MD:
	    case T_MF:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;
		break;

	    case T_UID:
	    case T_GID:
		if (dlen == INT32SZ)
		{
			n = _getlong(cp);
			doprintf(("\t%s", itoa(n)))
			cp += INT32SZ;
		}
		break;

	    case T_UINFO:
		doprintf(("\t\"%s\"", stoa(cp, dlen, TRUE)))
		cp += dlen;
		break;

	    case T_RP:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_RT:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_AFSDB:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_X25:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t%s", stoa(cp, n, FALSE)))
		cp += n;
		break;

	    case T_ISDN:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t%s", stoa(cp, n, FALSE)))
		cp += n;

		if (cp < eor)
		{
			if (check_size(rname, type, cp, msg, eor, 1) < 0)
				break;
			n = *cp++;
			doprintf((" %s", stoa(cp, n, FALSE)))
			cp += n;
		}
		break;

	    case T_NSAP:
		doprintf(("\t0x%s", nsap_ntoa(cp, dlen)))
		cp += dlen;
		break;

	    case T_NSAPPTR:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;
		break;

	    case T_PX:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_GPOS:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t%s", stoa(cp, n, FALSE)))
		cp += n;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t%s", stoa(cp, n, FALSE)))
		cp += n;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t%s", stoa(cp, n, FALSE)))
		cp += n;
		break;

	    case T_LOC:
		if ((n = *cp) != T_LOC_VERSION)
		{
			pr_error("invalid version %s in %s record for %s",
				itoa(n), pr_type(type), rname);
			cp += dlen;
			break;
		}

		n = INT32SZ + 3*INT32SZ;
		if (check_size(rname, type, cp, msg, eor, n) < 0)
			break;
		c = _getlong(cp);
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\t%s ", pr_spherical(n, "N", "S")))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf((" %s ", pr_spherical(n, "E", "W")))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf((" %sm ", pr_vertical(n, "", "-")))
		cp += INT32SZ;

		doprintf((" %sm", pr_precision((c >> 16) & 0xff)))
		doprintf((" %sm", pr_precision((c >>  8) & 0xff)))
		doprintf((" %sm", pr_precision((c >>  0) & 0xff)))
		break;

	    case T_UNSPEC:
	    case T_NULL:
		cp += dlen;
		break;

	    case T_AAAA:
		if (dlen == IPNGSIZE)
		{
			doprintf(("\t%s", ipng_ntoa(cp)))
			cp += IPNGSIZE;
		}
		break;

	    case T_SIG:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", pr_type(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" %s", itoa(n)))

		n = 1 + 3*INT32SZ + INT16SZ;
		if (check_size(rname, type, cp, msg, eor, n) < 0)
			break;
		doprintf((" ("))

		n = *cp++;
		doprintf(("\n\t\t\t; %s", itoa(n)))
		doprintf(("\t\t;labels"))

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t\t;original ttl"))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", pr_date(n)))
		doprintf(("\t;signature expiration"))
		cp += INT32SZ;

		n = _getlong(cp);
		doprintf(("\n\t\t\t%s", pr_date(n)))
		doprintf(("\t;signature signed time"))
		cp += INT32SZ;

		n = _getshort(cp);
		doprintf(("\n\t\t\t%s", itoa(n)))
		doprintf(("\t\t;key footprint"))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\n\t\t\t%s", pr_name(dname)))
		cp += n;

		if (cp < eor)
		{
			register char *buf;
			register int size;

			n = eor - cp;
			buf = base_ntoa(cp, n);
			size = strlength(buf);
			cp += n;

			while ((n = (size > 64) ? 64 : size) > 0)
			{
				doprintf(("\n\t%s", stoa((u_char *)buf, n, FALSE)))
				buf += n; size -= n;
			}
		}

		doprintf(("\n\t\t\t)"))
		break;

	    case T_KEY:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t0x%s", xtoa(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" %s", itoa(n)))

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" %s", itoa(n)))

		if (cp < eor)
		{
			register char *buf;
			register int size;

			n = eor - cp;
			buf = base_ntoa(cp, n);
			size = strlength(buf);
			cp += n;

			doprintf((" ("))
			while ((n = (size > 64) ? 64 : size) > 0)
			{
				doprintf(("\n\t%s", stoa((u_char *)buf, n, FALSE)))
				buf += n; size -= n;
			}
			doprintf(("\n\t\t\t)"))
		}
		break;

	    case T_NXT:
		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf(("\t%s", pr_name(dname)))
		cp += n;

		n = 0;
		while (cp < eor)
		{
		    c = *cp++;
		    do
		    {
 			if (c & 0200)
			{
			    doprintf((" %s", pr_type(n)))
			}
 			c <<= 1;
		    } while (++n & 07);
		}
		break;

	    case T_SRV:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf((" %s", itoa(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf((" %s", itoa(n)))
		cp += INT16SZ;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;

	    case T_EID:
	    case T_NIMLOC:
	    case T_ATMA:
		doprintf(("\t\"not yet implemented\""))
		cp += dlen;
		break;

	    case T_NAPTR:
		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf(("\t%s", itoa(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, INT16SZ) < 0)
			break;
		n = _getshort(cp);
		doprintf((" %s", itoa(n)))
		cp += INT16SZ;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" \"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" \"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf((" \"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		n = expand_name(rname, type, cp, msg, eom, dname);
		if (n < 0)
			break;
		doprintf((" %s", pr_name(dname)))
		cp += n;
		break;
#ifdef notyet
	    case T_TSIG:
		if (check_size(rname, type, cp, msg, eor, 1) < 0)
			break;
		n = *cp++;
		doprintf(("\t\"%s\"", stoa(cp, n, TRUE)))
		cp += n;

		while (cp < eor)
		{
			if (check_size(rname, type, cp, msg, eor, 1) < 0)
				break;
			n = *cp++;
			doprintf((" \"%s\"", stoa(cp, n, TRUE)))
			cp += n;
		}
		break;
#endif
	    default:
		doprintf(("\t\"???\""))
		cp += dlen;
		break;
	}

/*
 * End of specific data type processing.
 * Terminate resource record printout.
 */
	doprintf(("\n"))

/*
 * Check whether we have reached the exact end of this resource record.
 * If not, we cannot be sure that the record has been decoded correctly,
 * and therefore the subsequent tests will be skipped.
 */
	if (cp != eor)
	{
		pr_error("size error in %s record for %s, off by %s",
			pr_type(type), rname, itoa(cp - eor));

		/* we believe value of dlen; should perhaps return(NULL) */
		return(eor);
	}

/*
 * Save the CNAME alias for cname chain tracing.
 * Save the MR or MG alias for MB chain tracing.
 * These features can be enabled only in normal mode.
 */
	if (regular && classmatch)
	{
		if (type == T_CNAME)
			cname = strcpy(cnamebuf, dname);

		else if (type == T_MR || type == T_MG)
			mname = strcpy(mnamebuf, dname);
	}

/*
 * Suppress the subsequent checks in quiet mode.
 * This can safely be done as there are no side effects.
 * It may speedup things, and nothing would be printed anyway.
 */
	if (quiet)
		return(cp);

/*
 * In zone listings, resource records with the same name/type/class
 * must have the same ttl value. Maintain and check list of record info.
 * This is done on a per-zone basis.
 */
	if (listing && !check_ttl(rname, type, class, ttl))
	{
		pr_warning("%s %s records have different ttl within %s from %s",
			rname, pr_type(type), name, host);
	}

/*
 * Check validity of 'host' related domain names in certain resource records.
 * These include LHS record names and RHS domain names of selected records.
 * Currently underscores are not reported during deep recursive listings.
 */
	if (test_valid(type) && !valid_name(rname, TRUE, FALSE, recurskip))
	{
		pr_warning("%s %s record has illegal name",
			rname, pr_type(type));
	}

	if (test_canon(type) && !valid_name(dname, FALSE, FALSE, recurskip))
	{
		pr_warning("%s %s host %s has illegal name",
			rname, pr_type(type), dname);
	}

/*
 * The RHS of various resource records should refer to a canonical host name,
 * i.e. it should exist and have an A record and not be a CNAME.
 * Currently this test is suppressed during deep recursive zone listings.
 */
	if (!recurskip && test_canon(type) && ((n = check_canon(dname)) != 0))
	{
		/* only report definitive target host failures */
		if (n == HOST_NOT_FOUND)
			pr_warning("%s %s host %s does not exist",
				rname, pr_type(type), dname);
		else if (n == NO_DATA)
			pr_warning("%s %s host %s has no A record",
				rname, pr_type(type), dname);
		else if (n == HOST_NOT_CANON)
			pr_warning("%s %s host %s is not canonical",
				rname, pr_type(type), dname);

		/* authoritative failure to find nameserver target host */
		if (type == T_NS && (n == NO_DATA || n == HOST_NOT_FOUND))
		{
			if (server == NULL)
				errmsg("%s has lame delegation to %s",
					rname, dname);
		}
	}

/*
 * On request, reverse map the address of an A record, and verify that
 * it is registered and maps back to the name of the A record.
 * Currently this option has effect here only during zone listings.
 */
	if (addrmode && ((type == T_A) && !reverse) && !fakeaddr(address))
	{
		host = mapreverse(rname, inaddr);
		if (host == NULL)
			pr_warning("%s address %s is not registered",
				rname, inet_ntoa(inaddr));
		else if (host != rname)
			pr_warning("%s address %s maps to %s",
				rname, inet_ntoa(inaddr), host);
	}

/*
 * This record was processed successfully.
 */
	return(cp);
}

/*
** SKIP_QREC -- Skip the query record in the nameserver answer buffer
** ------------------------------------------------------------------
**
**	Returns:
**		Pointer to position in answer buffer after current record.
**		NULL if there was a format error in the current record.
*/

u_char *
skip_qrec(name, qtype, qclass, cp, msg, eom)
input char *name;			/* full name we are querying about */
input int qtype;			/* record type we are querying about */
input int qclass;			/* record class we are querying about */
register u_char *cp;			/* current position in answer buf */
input u_char *msg, *eom;		/* begin and end of answer buf */
{
	char rname[MAXDNAME+1];		/* record name in LHS */
	int type, class;		/* fixed values in query record */
	register int n;

/*
 * Pickup the standard values present in the query section.
 */
	n = expand_name(name, T_NONE, cp, msg, eom, rname);
	if (n < 0)
		return(NULL);
	cp += n;

	n = 2*INT16SZ;
	if (check_size(rname, T_NONE, cp, msg, eom, n) < 0)
		return(NULL);

	type = _getshort(cp);
	cp += INT16SZ;

	class = _getshort(cp);
	cp += INT16SZ;

#ifdef lint
	if (verbose)
		printf("%-20s\t%s\t%s\n",
			rname, pr_class(class), pr_type(type));
#endif

/*
 * The values in the answer should match those in the query.
 * If there is a mismatch, we just signal an error, but don't abort.
 * For regular queries there is exactly one record in the query section.
 */
	if (!sameword(rname, name))
		pr_error("invalid answer name %s after %s query for %s",
			rname, pr_type(qtype), name);

	if (type != qtype)
		pr_error("invalid answer type %s after %s query for %s",
			pr_type(type), pr_type(qtype), name);

	if (class != qclass)
		pr_error("invalid answer class %s after %s query for %s",
			pr_class(class), pr_type(qtype), name);

	return(cp);
}

/*
** GET_RECURSIVE -- Wrapper for get_hostinfo() during recursion
** ------------------------------------------------------------
**
**	Returns:
**		TRUE if requested info was obtained successfully.
**		FALSE otherwise.
*/

bool
get_recursive(name)
input char **name;			/* name to query about */
{
	static int level = 0;		/* recursion level */
	char newnamebuf[MAXDNAME+1];
	char *newname;			/* new name to look up */
	bool result;			/* result status of action taken */
	int save_errno;
	int save_herrno;

	if (level > MAXCHAIN)
	{
		errmsg("Recursion too deep");
		return(FALSE);
	}

	/* save local copy, and reset indicator */
	newname = strcpy(newnamebuf, *name);
	*name = NULL;

	save_errno = errno;
	save_herrno = h_errno;

	level++;
	result = get_hostinfo(newname, TRUE);
	level--;

	errno = save_errno;
	h_errno = save_herrno;

	return(result);
}


/*
 * Nameserver information.
 * Stores names and addresses of all servers that are to be queried
 * for a zone transfer of the desired zone. Normally these are the
 * authoritative primary and/or secondary nameservers for the zone.
 */

char nsname[MAXNSNAME][MAXDNAME+1];		/* nameserver host name */
struct in_addr ipaddr[MAXNSNAME][MAXIPADDR];	/* nameserver addresses */
int naddrs[MAXNSNAME];				/* count of addresses */
int nservers = 0;				/* count of nameservers */

#ifdef notyet
typedef struct srvr_data {
	char sd_nsname[MAXDNAME+1];		/* nameserver host name */
	struct in_addr sd_ipaddr[MAXIPADDR];	/* nameserver addresses */
	int sd_naddrs;				/* count of addresses */
} srvr_data_t;

srvr_data_t nsinfo[MAXNSNAME];	/* nameserver info */
#endif

bool authserver;		/* server is supposed to be authoritative */
bool lameserver;		/* server could not provide SOA service */

/*
 * Host information.
 * Stores names and (single) addresses encountered during the zone listing
 * of all A records that belong to the zone. Non-authoritative glue records
 * that do not belong to the zone are not stored. Glue records that belong
 * to a delegated zone will be filtered out later during the host count scan.
 * The host names are allocated dynamically.
 * The list itself is also allocated dynamically, to avoid static limits,
 * and to keep the initial bss of the executable to a reasonable size.
 * Allocation is done in chunks, to reduce considerable malloc overhead.
 * Note that the list will not shrink during recursive processing.
 */

#ifdef obsolete
char *hostname[MAXHOSTS];	/* host name of host in zone */
ipaddr_t hostaddr[MAXHOSTS];	/* first host address */
bool multaddr[MAXHOSTS];	/* set if this is a multiple address host */
#endif

typedef struct host_data {
	char *hd_hostname;	/* host name of host in zone */
	ipaddr_t hd_hostaddr;	/* first host address */
	bool hd_multaddr;	/* set if this is a multiple address host */
} host_data_t;

host_data_t *hostlist = NULL;	/* info on hosts in zone */
int hostcount = 0;		/* count of hosts in zone */

int maxhosts = 0;		/* number of allocated hostlist entries */

#define MAXHOSTINCR	4096	/* chunk size to increment hostlist */

#define hostname(i)	hostlist[i].hd_hostname
#define hostaddr(i)	hostlist[i].hd_hostaddr
#define multaddr(i)	hostlist[i].hd_multaddr

/*
 * Delegated zone information.
 * Stores the names of the delegated zones encountered during the zone
 * listing. The names and the list itself are allocated dynamically.
 */

char **zonename = NULL;		/* names of delegated zones within zone */
int zonecount = 0;		/* count of delegated zones within zone */

/*
 * Address information.
 * Stores the (single) addresses of hosts found in all zones traversed.
 * Used to search for duplicate hosts (same address but different name).
 * The list of addresses is allocated dynamically, and remains allocated.
 * This has now been implemented as a hashed list, using the low-order
 * address bits as the hash key.
 */

#ifdef obsolete
ipaddr_t *addrlist = NULL;	/* global list of addresses */
int addrcount = 0;		/* count of global addresses */
#endif

/*
 * SOA record information.
 */

soa_data_t soa;			/* buffer to store soa data */

int soacount = 0;		/* count of SOA records during listing */

/*
 * Nameserver preference.
 * As per BIND 4.9.* resource records may be returned after round-robin
 * reshuffling each time they are retrieved. For NS records, this may
 * lead to an unfavorable order for doing zone transfers.
 * We apply some heuristic to sort the NS records according to their
 * preference with respect to a given list of preferred server domains.
 */

int nsrank[MAXNSNAME];		/* nameserver ranking after sorting */
int nspref[MAXNSNAME];		/* nameserver preference value */

/*
** LIST_ZONE -- Basic routine to do complete zone listing and checking
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if the requested info was processed successfully.
**		FALSE otherwise.
*/

int total_calls = 0;		/* number of calls for zone processing */
int total_check = 0;		/* number of zones successfully processed */
int total_tries = 0;		/* number of zone transfer attempts */
int total_zones = 0;		/* number of successful zone transfers */
int total_hosts = 0;		/* number of hosts in all traversed zones */
int total_dupls = 0;		/* number of duplicates in all zones */

#ifdef justfun
char longname[MAXDNAME+1];	/* longest host name found */
int longsize = 0;		/* size of longest host name */
#endif

bool
list_zone(name)
input char *name;			/* name of zone to process */
{
	register int n;
	register int i;
	int nzones;			/* count of delegated zones */
	int nhosts;			/* count of real host names */
	int ndupls;			/* count of duplicate hosts */
	int nextrs;			/* count of extrazone hosts */
	int ngates;			/* count of gateway hosts */

	total_calls += 1;		/* update zone processing calls */

/*
 * Normalize to not have trailing dot, unless it is the root zone.
 */
	n = strlength(name);
	if (n > 1 && name[n-1] == '.')
		name[n-1] = '\0';

/*
 * Indicate whether we are processing an in-addr.arpa reverse zone.
 * In this case we will suppress accumulating host count statistics.
 */
	reverse = indomain(name, ARPA_ROOT, FALSE);

/*
 * Suppress various checks if working beyond the recursion skip level.
 * This affects processing in print_rrec(). It may need refinement.
 */
	recurskip = ((recursion_level > skip_level) && !addrmode) ? TRUE : FALSE;

/*
 * Find the nameservers for the given zone.
 */
	(void) find_servers(name);

	if (nservers < 1)
	{
		errmsg("No nameservers for %s found", name);
		return(FALSE);
	}

/*
 * Make sure we have an address for at least one nameserver.
 */
	for (n = 0; n < nservers; n++)
		if (naddrs[n] > 0)
			break;

	if (n >= nservers)
	{
		errmsg("No addresses of nameservers for %s found", name);
		return(FALSE);
	}

/*
 * Without an explicit server on the command line, the servers we
 * have looked up are supposed to be authoritative for the zone.
 */
	authserver = (server && !primary) ? FALSE : TRUE;

/*
 * Check SOA records at each of the nameservers if so requested.
 */
	if (checkmode)
	{
		do_check(name);

		total_check += 1;	/* update zones processed */

		/* all done if maximum recursion level reached */
		if (!recursive || (recursion_level >= recursive))
			return((errorcount == 0) ? TRUE : FALSE);
	}

/*
 * The zone transfer for certain zones can be skipped.
 * Currently this must be indicated on the command line.
 */
	if (skip_transfer(name))
	{
		if (verbose || statistics || checkmode || hostmode)
			printf("Skipping zone transfer for %s\n", name);
		return(FALSE);
	}

/*
 * Ask zone transfer to the nameservers, until one responds.
 */
	total_tries += 1;		/* update zone transfer attempts */

	if (!do_transfer(name))
		return(FALSE);

	total_zones += 1;		/* update successful zone transfers */

/*
 * Print resource record statistics if so requested.
 */
	if (statistics)
		print_statistics(name, querytype, queryclass);

/*
 * Accumulate host count statistics for this zone.
 * Do this only in modes in which such output would be printed.
 */
	nzones = zonecount;

	nhosts = 0, ndupls = 0, nextrs = 0, ngates = 0;

	i = (verbose || statistics || hostmode) ? 0 : hostcount;

	for (n = i; n < hostcount; n++)
	{
		/* skip fake hosts using a very rudimentary test */
		if (fakename(hostname(n)) || fakeaddr(hostaddr(n)))
			continue;
#ifdef justfun
		/* save longest host name encountered so far */
		if (verbose && ((i = strlength(hostname(n))) > longsize))
		{
			longsize = i;
			(void) strcpy(longname, hostname(n));
		}
#endif
		/* skip apparent glue records */
		if (gluerecord(hostname(n), name, zonename, nzones))
		{
			if (verbose > 1)
				printf("%s is glue record\n", hostname(n));
			continue;
		}

		/* otherwise count as host */
		nhosts++;

	/*
	 * Mark hosts not residing directly in the zone as extrazone host.
	 */
		if (!samedomain(hostname(n), name, TRUE))
		{
			nextrs++;
			if (extrmode || (verbose > 1))
				printf("%s is extrazone host\n", hostname(n));
		}

	/*
	 * Mark hosts with more than one address as gateway host.
	 * These are not checked for duplicate addresses.
	 */
		if (multaddr(n))
		{
			ngates++;
			if (gatemode || (verbose > 1))
				printf("%s is gateway host\n", hostname(n));
		}
		
	/*
	 * Compare single address hosts against global list of addresses.
	 * Multiple address hosts are too complicated to handle this way.
	 */
		else if (check_dupl(hostaddr(n)))
		{
			struct in_addr inaddr;
			inaddr.s_addr = hostaddr(n);

			ndupls++;
			if (duplmode || (verbose > 1))
				printf("%s is duplicate host with address %s\n",
					hostname(n), inet_ntoa(inaddr));
		}
	}

/*
 * Print statistics for this zone.
 */
	if (verbose || statistics || hostmode)
	{
		printf("Found %d host%s within %s\n",
			nhosts, plural(nhosts), name);

	    if ((ndupls > 0) || duplmode || (verbose > 1))
		printf("Found %d duplicate host%s within %s\n",
			ndupls, plural(ndupls), name);

	    if ((nextrs > 0) || extrmode || (verbose > 1))
		printf("Found %d extrazone host%s within %s\n",
			nextrs, plural(nextrs), name);

	    if ((ngates > 0) || gatemode || (verbose > 1))
		printf("Found %d gateway host%s within %s\n",
			ngates, plural(ngates), name);
	}

	total_hosts += nhosts;		/* update total number of hosts */
	total_dupls += ndupls;		/* update total number of duplicates */

	if (!checkmode)
		total_check += 1;	/* update zones processed */

	if (verbose || statistics)
		printf("Found %d delegated zone%s within %s\n",
			nzones, plural(nzones), name);

/*
 * Sort the encountered delegated zones alphabetically.
 * Note that this precludes further use of the zone_index() function.
 */
	if ((nzones > 1) && (recursive || listzones || mxdomains))
		qsort((ptr_t *)zonename, nzones, sizeof(char *), compare_name);

/*
 * The names of the hosts were allocated dynamically.
 */
	for (n = 0; n < hostcount; n++)
		xfree(hostname(n));

/*
 * Check for mailable delegated zones within this zone.
 * This is based on ordinary MX lookup, and not on the MX info
 * which may be present in the zone listing, to reduce zone transfers.
 */
	if (mxdomains)
	{
		if (recursion_level == 0)
		{
			if (verbose)
				printf("\n");

			if (!get_mxrec(name))
				ns_error(name, T_MX, queryclass, server);
		}

		for (n = 0; n < nzones; n++)
		{
			if (verbose)
				printf("\n");

			if (!get_mxrec(zonename[n]))
				ns_error(zonename[n], T_MX, queryclass, server);
		}
	}

/*
 * Do recursion on delegated zones if requested and any were found.
 * Temporarily save zonename list, and force allocation of new list.
 */
	if (recursive && (recursion_level < recursive))
	{
		for (n = 0; n < nzones; n++)
		{
			char **newzone;		/* local copy of list */

			newzone = zonename;
			zonename = NULL;	/* allocate new list */

			if (verbose || statistics || checkmode || hostmode)
				printf("\n");

			if (listzones)
			{
				for (i = 0; i <= recursion_level; i++)
					printf("%s", (i == 0) ? "\t" : "  ");
				printf("%s\n", newzone[n]);
			}

			if (verbose)
				printf("Entering zone %s\n", newzone[n]);

			recursion_level++;
			(void) list_zone(newzone[n]);
			recursion_level--;

			zonename = newzone;	/* restore */
		}
	}
	else if (listzones)
	{
		for (n = 0; n < nzones; n++)
		{
			for (i = 0; i <= recursion_level; i++)
				printf("%s", (i == 0) ? "\t" : "  ");
			printf("%s\n", zonename[n]);
		}
	}

/*
 * The names of the delegated zones were allocated dynamically.
 * The list of delegated zone names was also allocated dynamically.
 */
	for (n = 0; n < nzones; n++)
		xfree(zonename[n]);

	if (zonename != NULL)
		xfree(zonename);

	zonename = NULL;

/*
 * Print final overall statistics.
 */
	if (recursive && (recursion_level == 0))
	{
		if (verbose || statistics || checkmode || hostmode)
			printf("\n");

		if (verbose || statistics || hostmode)
			printf("Encountered %d host%s in %d zone%s within %s\n",
				total_hosts, plural(total_hosts),
				total_zones, plural(total_zones),
				name);

		if (verbose || statistics || hostmode)
			printf("Encountered %d duplicate host%s in %d zone%s within %s\n",
				total_dupls, plural(total_dupls),
				total_zones, plural(total_zones),
				name);

		if (verbose || statistics || checkmode)
			printf("Transferred %d zone%s out of %d attempt%s\n",
				total_zones, plural(total_zones),
				total_tries, plural(total_tries));

		if (verbose || statistics || checkmode)
			printf("Processed %d zone%s out of %d request%s\n",
				total_check, plural(total_check),
				total_calls, plural(total_calls));
#ifdef justfun
		if (verbose && (longsize > 0))
			printf("Longest hostname %s\t%d\n",
				longname, longsize);
#endif
	}

	/* indicate whether any errors were encountered */
	return((errorcount == 0) ? TRUE : FALSE);
}

/*
** FIND_SERVERS -- Fetch names and addresses of authoritative servers
** ------------------------------------------------------------------
**
**	Returns:
**		TRUE if servers could be determined successfully.
**		FALSE otherwise.
**
**	Inputs:
**		The global variable ``server'', if set, contains the
**		name of the explicit server to be contacted.
**		The global variable ``primary'', if set, indicates
**		that we must use the primary nameserver for the zone.
**		If both are set simultaneously, the explicit server
**		is contacted to retrieve the desired servers.
**
**	Outputs:
**		The count of nameservers is stored in ``nservers''.
**		Names are stored in the nsname[] database.
**		Addresses are stored in the ipaddr[] database.
**		Address counts are stored in the naddrs[] database.
*/

bool
find_servers(name)
input char *name;			/* name of zone to find servers for */
{
	struct hostent *hp;
	register int n, i;

/*
 * Use the explicit server if given on the command line.
 * Its addresses are stored in the resolver state struct.
 * This server may not be authoritative for the given zone.
 */
	if (server && !primary)
	{
		(void) strcpy(nsname[0], server);

		for (i = 0; i < MAXIPADDR && i < _res.nscount; i++)
			ipaddr[0][i] = nslist(i).sin_addr;
		naddrs[0] = i;

		nservers = 1;
		return(TRUE);
	}

/*
 * Fetch primary nameserver info if so requested.
 * Get its name from the SOA record for the zone, and do a regular
 * host lookup to fetch its addresses. We are assuming here that the
 * SOA record is a proper one. This is not necessarily true.
 * Obviously this server should be authoritative.
 */
	if (primary && !server)
	{
		char *primaryname;

		primaryname = get_primary(name);
		if (primaryname == NULL)
		{
			ns_error(name, T_SOA, queryclass, server);
			nservers = 0;
			return(FALSE);
		}

		hp = geth_byname(primaryname);
		if (hp == NULL)
		{
			ns_error(primaryname, T_A, C_IN, server);
			nservers = 0;
			return(FALSE);
		}

		primaryname = strncpy(nsname[0], hp->h_name, MAXDNAME);
		primaryname[MAXDNAME] = '\0';

		for (i = 0; i < MAXIPADDR && hp->h_addr_list[i]; i++)
			ipaddr[0][i] = incopy(hp->h_addr_list[i]);
		naddrs[0] = i;

		if (verbose)
			printf("Found %d address%s for %s\n",
				naddrs[0], plurale(naddrs[0]), nsname[0]);
		nservers = 1;
		return(TRUE);
	}

/*
 * Otherwise we have to find the nameservers for the zone.
 * These are supposed to be authoritative, but sometimes we
 * encounter lame delegations, perhaps due to misconfiguration.
 */
	if (!get_servers(name))
	{
		ns_error(name, T_NS, queryclass, server);
		nservers = 0;
		return(FALSE);
	}

/*
 * Usually we'll get addresses for all the servers in the additional
 * info section.  But in case we don't, look up their addresses.
 * Addresses could be missing because there is no room in the answer.
 * No address is present if the name of a server is not canonical.
 * If we get no addresses by extra query, and this is authoritative,
 * we flag a lame delegation to that server.
 */
	for (n = 0; n < nservers; n++)
	{
	    if (naddrs[n] == 0)
	    {
		hp = geth_byname(nsname[n]);
		if (hp != NULL)
		{
			for (i = 0; i < MAXIPADDR && hp->h_addr_list[i]; i++)
				ipaddr[n][i] = incopy(hp->h_addr_list[i]);
			naddrs[n] = i;
		}

		if (verbose)
			printf("Found %d address%s for %s by extra query\n",
				naddrs[n], plurale(naddrs[n]), nsname[n]);

		if (hp == NULL)
		{
			/* server name lookup failed */
			ns_error(nsname[n], T_A, C_IN, server);

			/* authoritative denial: probably misconfiguration */
			if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND)
			{
				if (server == NULL)
					errmsg("%s has lame delegation to %s",
						name, nsname[n]);
			}
		}

		if ((hp != NULL) && !sameword(hp->h_name, nsname[n]))
			pr_warning("%s nameserver %s is not canonical (%s)",
				name, nsname[n], hp->h_name);
	    }
	    else
	    {
		if (verbose)
			printf("Found %d address%s for %s\n",
				naddrs[n], plurale(naddrs[n]), nsname[n]);
	    }
	}

/*
 * Issue warning if only one server has been discovered.
 * This is not an error per se, but not much redundancy in that case.
 */
	if (nservers == 1)
		pr_warning("%s has only one nameserver %s",
			name, nsname[0]);

	return((nservers > 0) ? TRUE : FALSE);
}

/*
** GET_SERVERS -- Fetch names and addresses of authoritative servers
** -----------------------------------------------------------------
**
**	Returns:
**		TRUE if servers could be determined successfully.
**		FALSE otherwise.
**
**	Side effects:
**		The count of nameservers is stored in ``nservers''.
**		Names are stored in the nsname[] database.
**		Addresses are stored in the ipaddr[] database.
**		Address counts are stored in the naddrs[] database.
*/

bool
get_servers(name)
input char *name;			/* name of zone to find servers for */
{
	querybuf answer;
	register int n;
	bool result;			/* result status of action taken */

	if (verbose)
		printf("Finding nameservers for %s ...\n", name);

	n = get_info(&answer, name, T_NS, queryclass);
	if (n < 0)
		return(FALSE);

	if (verbose > 1)
		(void) print_info(&answer, n, name, T_NS, queryclass, FALSE);

	result = get_nsinfo(&answer, n, name);
	return(result);
}

/*
** GET_NSINFO -- Extract nameserver data from nameserver answer buffer
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if the answer buffer was processed successfully.
**		FALSE otherwise.
**
**	Outputs:
**		The count of nameservers is stored in ``nservers''.
**		Names are stored in the nsname[] database.
**		Addresses are stored in the ipaddr[] database.
**		Address counts are stored in the naddrs[] database.
*/

bool
get_nsinfo(answerbuf, answerlen, name)
input querybuf *answerbuf;		/* location of answer buffer */
input int answerlen;			/* length of answer buffer */
input char *name;			/* name of zone to find servers for */
{
	HEADER *bp;
	int qdcount, ancount, nscount, arcount, rrcount;
	u_char *msg, *eom;
	register u_char *cp;
	register int i;

	nservers = 0;			/* count of nameservers */

	bp = (HEADER *)answerbuf;
	qdcount = ntohs(bp->qdcount);
	ancount = ntohs(bp->ancount);
	nscount = ntohs(bp->nscount);
	arcount = ntohs(bp->arcount);

	msg = (u_char *)answerbuf;
	eom = (u_char *)answerbuf + answerlen;
	cp  = (u_char *)answerbuf + HFIXEDSZ;

	if (qdcount > 0 && cp < eom)	/* should be exactly one record */
	{
		cp = skip_qrec(name, T_NS, queryclass, cp, msg, eom);
		if (cp == NULL)
			return(FALSE);
		qdcount--;
	}

	if (qdcount)
	{
		pr_error("invalid qdcount after %s query for %s",
			pr_type(T_NS), name);
		h_errno = NO_RECOVERY;
		return(FALSE);
	}

/*
 * If the answer is authoritative, the names are found in the
 * answer section, and the nameserver section is empty.
 * If not, there may be duplicate names in both sections.
 * Addresses are found in the additional info section both cases.
 */
	rrcount = ancount + nscount + arcount;
	while (rrcount > 0 && cp < eom)
	{
		char rname[MAXDNAME+1];
		char dname[MAXDNAME+1];
		int type, class, ttl, dlen;
		u_char *eor;
		register int n;
		struct in_addr inaddr;

		n = expand_name(name, T_NONE, cp, msg, eom, rname);
		if (n < 0)
			return(FALSE);
		cp += n;

		n = 3*INT16SZ + INT32SZ;
		if (check_size(rname, T_NONE, cp, msg, eom, n) < 0)
			return(FALSE);

		type = _getshort(cp);
		cp += INT16SZ;

		class = _getshort(cp);
		cp += INT16SZ;

		ttl = _getlong(cp);
		cp += INT32SZ;

		dlen = _getshort(cp);
		cp += INT16SZ;

		eor = cp + dlen;
#ifdef lint
		if (verbose)
			printf("%-20s\t%d\t%s\t%s\n",
				rname, ttl, pr_class(class), pr_type(type));
#endif
		if ((type == T_NS) && sameword(rname, name))
		{
			n = expand_name(rname, type, cp, msg, eom, dname);
			if (n < 0)
				return(FALSE);
			cp += n;

			for (i = 0; i < nservers; i++)
				if (sameword(nsname[i], dname))
					break;	/* duplicate */

			if (i >= nservers && nservers < MAXNSNAME)
			{
				(void) strcpy(nsname[nservers], dname);
				naddrs[nservers] = 0;
				nservers++;
			}
		}
		else if ((type == T_A) && (dlen == INADDRSZ))
		{
			for (i = 0; i < nservers; i++)
				if (sameword(nsname[i], rname))
					break;	/* found */

			if (i < nservers && naddrs[i] < MAXIPADDR)
			{
				bcopy((char *)cp, (char *)&inaddr, INADDRSZ);
				ipaddr[i][naddrs[i]] = inaddr;
				naddrs[i]++;
			}

			cp += dlen;
		}
		else
		{
			/* just ignore other records */
			cp += dlen;
		}

		if (cp != eor)
		{
			pr_error("size error in %s record for %s, off by %s",
				pr_type(type), rname, itoa(cp - eor));
			h_errno = NO_RECOVERY;
			return(FALSE);
		}

		rrcount--;
	}

	if (rrcount)
	{
		pr_error("invalid rrcount after %s query for %s",
			pr_type(T_NS), name);
		h_errno = NO_RECOVERY;
		return(FALSE);
	}

	/* set proper status if no answers found */
	h_errno = (nservers > 0) ? 0 : TRY_AGAIN;
	return(TRUE);
}

/*
** SORT_SERVERS -- Sort set of nameservers according to preference
** ---------------------------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		Set of nameservers as determined by find_servers().
**		The global variable ``prefserver'', if set, contains
**		a list of preferred server domains to compare against.
**
**	Outputs:
**		Stores the preferred nameserver order in nsrank[].
*/

void
sort_servers()
{
	register int i, j;
	register int n, pref;
	register char *p, *q;

/*
 * Initialize the default ranking.
 */
	for (n = 0; n < nservers; n++)
	{
		nsrank[n] = n;
		nspref[n] = 0;
	}

/*
 * Determine the nameserver preference.
 * Compare against a list of comma-separated preferred server domains.
 * Use the maximum value of all comparisons.
 */
	for (q = NULL, p = prefserver; p != NULL; p = q)
	{
		q = index(p, ',');
		if (q != NULL)
			*q = '\0';

		for (n = 0; n < nservers; n++)
		{
			pref = matchlabels(nsname[n], p);
			if (pref > nspref[n])
				nspref[n] = pref;
		}

		if (q != NULL)
			*q++ = ',';
	}

/*
 * Sort the set according to preference.
 * Keep the rest as much as possible in original order.
 */
	for (i = 0; i < nservers; i++)
	{
		for (j = i + 1; j < nservers; j++)
		{
			if (nspref[j] > nspref[i])
			{
				pref = nspref[j];
				/* nspref[j] = nspref[i]; */
				for (n = j; n > i; n--)
					nspref[n] = nspref[n-1];
				nspref[i] = pref;

				pref = nsrank[j];
				/* nsrank[j] = nsrank[i]; */
				for (n = j; n > i; n--)
					nsrank[n] = nsrank[n-1];
				nsrank[i] = pref;
			}
		}
	}
}

/*
** SKIP_TRANSFER -- Check whether a zone transfer should be skipped
** ----------------------------------------------------------------
**
**	Returns:
**		TRUE if a transfer for this zone should be skipped.
**		FALSE if the zone transfer should proceed.
**
**	Inputs:
**		The global variable ``skipzone'', if set, contains
**		a list of zone names to be skipped.
**
**	Certain zones are known to contain bogus information, and
**	can be requested to be excluded from further processing.
**	The zone transfer for such zones and its delegated zones
**	will be skipped.
*/

bool
skip_transfer(name)
input char *name;			/* name of zone to process */
{
	register char *p, *q;
	bool skip = FALSE;

	for (q = NULL, p = skipzone; p != NULL; p = q)
	{
		q = index(p, ',');
		if (q != NULL)
			*q = '\0';

		if (sameword(name, p))
			skip = TRUE;

		if (q != NULL)
			*q++ = ',';
	}

	return(skip);
}

/*
** DO_CHECK -- Check SOA records at each of the nameservers
** --------------------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		The count of nameservers is stored in ``nservers''.
**		Names are stored in the nsname[] database.
**		Addresses are stored in the ipaddr[] database.
**		Address counts are stored in the naddrs[] database.
**
**	The SOA record of the zone is checked at each nameserver.
**	Nameserver recursion is turned off to make sure that the
**	answer is authoritative.
*/

void
do_check(name)
input char *name;			/* name of zone to process */
{
	res_state_t save_res;		/* saved copy of resolver database */
	char *save_server;		/* saved copy of server name */
	register int n;
	register int i;

	/* save resolver database */
	save_res = _res;
	save_server = server;

	/* turn off nameserver recursion */
	_res.options &= ~RES_RECURSE;

	for (n = 0; n < nservers; n++)
	{
		if (naddrs[n] < 1)
			continue;	/* shortcut */

		server = nsname[n];
		for (i = 0; i < MAXNS && i < naddrs[n]; i++)
		{
			nslist(i).sin_family = AF_INET;
			nslist(i).sin_port = htons(NAMESERVER_PORT);
			nslist(i).sin_addr = ipaddr[n][i];
		}
		_res.nscount = i;

		/* retrieve and check SOA */
		if (check_zone(name))
			continue;

		/* SOA query failed */
		ns_error(name, T_SOA, queryclass, server);

		/* explicit server failure: possibly data expired */
		lameserver = (h_errno == SERVER_FAILURE) ? TRUE : FALSE;

		/* non-authoritative denial: assume lame delegation */
		if (h_errno == NO_RREC || h_errno == NO_HOST)
			lameserver = TRUE;

		/* authoritative denial: probably misconfiguration */
		if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND)
			lameserver = TRUE;

		/* flag an error if server should not have failed */
		if (lameserver && authserver)
			errmsg("%s has lame delegation to %s",
				name, server);
	}

	/* restore resolver database */
	_res = save_res;
	server = save_server;
}

/*
** DO_TRANSFER -- Perform a zone transfer from any of its nameservers
** ------------------------------------------------------------------
**
**	Returns:
**		TRUE if the zone data have been retrieved successfully.
**		FALSE if none of the servers responded.
**
**	Inputs:
**		The count of nameservers is stored in ``nservers''.
**		Names are stored in the nsname[] database.
**		Addresses are stored in the ipaddr[] database.
**		Address counts are stored in the naddrs[] database.
**
**	Ask zone transfer to the nameservers, until one responds.
**	The list of nameservers is sorted according to preference.
**	An authoritative server should always respond positively.
**	If it responds with an error, we may have a lame delegation.
**	Always retry with the next server to avoid missing entire zones.
*/

bool
do_transfer(name)
input char *name;			/* name of zone to do zone xfer for */
{
	register int n, ns;
	register int i;

	for (sort_servers(), ns = 0; ns < nservers; ns++)
	{
	    for (n = nsrank[ns], i = 0; i < naddrs[n]; i++)
	    {
		if (verbose)
			printf("Trying server %s (%s) ...\n",
				inet_ntoa(ipaddr[n][i]), nsname[n]);

		if (transfer_zone(name, ipaddr[n][i], nsname[n]))
			goto done;	/* double break */

		/* zone transfer failed */
		if ((h_errno != TRY_AGAIN) || verbose)
			ns_error(name, T_AXFR, queryclass, nsname[n]);

		/* zone transfer request was explicitly refused */
		if (h_errno == QUERY_REFUSED)
			break;

		/* explicit server failure: possibly data expired */
		lameserver = (h_errno == SERVER_FAILURE) ? TRUE : FALSE;

		/* non-authoritative denial: assume lame delegation */
		if (h_errno == NO_RREC || h_errno == NO_HOST)
			lameserver = TRUE;

		/* authoritative denial: probably misconfiguration */
		if (h_errno == NO_DATA || h_errno == HOST_NOT_FOUND)
			lameserver = TRUE;

		/* flag an error if server should not have failed */
		if (lameserver && authserver)
			errmsg("%s has lame delegation to %s",
				name, nsname[n]);

		/* try next server if this one is sick */
		if (lameserver)
			break;

		/* terminate on irrecoverable errors */
		if (h_errno != TRY_AGAIN)
			return(FALSE);

		/* in case nameserver not present */
		if (errno == ECONNREFUSED)
			break;
	    }
	}
done:
	if (ns >= nservers)
	{
		if ((h_errno == TRY_AGAIN) && !verbose)
			ns_error(name, T_AXFR, queryclass, (char *)NULL);
		errmsg("No nameservers for %s responded", name);
		return(FALSE);
	}

	return(TRUE);
}

/*
** TRANSFER_ZONE -- Wrapper for get_zone() to hide administrative tasks
** --------------------------------------------------------------------
**
**	Returns:
**		See get_zone() for details.
**
**	Side effects:
**		See get_zone() for details.
**
**	This routine may be called repeatedly with different server
**	addresses, until one of the servers responds. Various items
**	must be reset on every try to continue with a clean slate.
*/

bool
transfer_zone(name, inaddr, host)
input char *name;			/* name of zone to do zone xfer for */
input struct in_addr inaddr;		/* address of server to be queried */
input char *host;			/* name of server to be queried */
{
	register int n;

/*
 * Reset the resource record statistics before each try.
 */
	clear_statistics();

/*
 * Reset the hash tables of saved resource record information.
 * These tables are used only during the zone transfer itself.
 */
	clear_ttltab();
	clear_hosttab();
	clear_zonetab();

/*
 * Perform the actual zone transfer.
 * All error reporting is done by get_zone().
 */
	if (get_zone(name, inaddr, host))
		return(TRUE);

/*
 * Failure to get the zone. Free any memory that may have been allocated.
 * On success it is the responsibility of the caller to free the memory.
 * The information gathered is used by list_zone() after the zone transfer.
 */
	for (n = 0; n < hostcount; n++)
		xfree(hostname(n));

	for (n = 0; n < zonecount; n++)
		xfree(zonename[n]);

	if (zonename != NULL)
		xfree(zonename);

	zonename = NULL;

	return(FALSE);
}

/*
** GET_ZONE -- Perform a zone transfer from server at specific address
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if the zone data have been retrieved successfully.
**		FALSE if an error occurred (h_errno is set appropriately).
**		Set TRY_AGAIN wherever possible to try the next server.
**
**	Side effects:
**		Stores list of delegated zones found in zonename[],
**		and the count of delegated zones in ``zonecount''.
**		Stores list of host names found in hostname[],
**		and the count of host names in ``hostcount''.
**		Updates resource record statistics in record_stats[].
**		This array must have been cleared before.
*/

bool
get_zone(name, inaddr, host)
input char *name;			/* name of zone to do zone xfer for */
input struct in_addr inaddr;		/* address of server to be queried */
input char *host;			/* name of server to be queried */
{
	querybuf query;
	querybuf answer;
	HEADER *bp;
	int ancount;
	int sock;
	struct sockaddr_in sin;
	register int n, i;
	int nrecords = 0;		/* number of records processed */
	int npackets = 0;		/* number of packets received */

	/* clear global counts */
	soacount = 0;			/* count of SOA records */
	zonecount = 0;			/* count of delegated zones */
	hostcount = 0;			/* count of host names */

/*
 * Construct query, and connect to the given server.
 */
	errno = 0;	/* reset before querying nameserver */

	n = res_mkquery(QUERY, name, queryclass, T_AXFR, (qbuf_t *)NULL, 0,
			(rrec_t *)NULL, (qbuf_t *)&query, sizeof(querybuf));
	if (n < 0)
	{
		if (debug)
			printf("%sres_mkquery failed\n", dbprefix);
		h_errno = NO_RECOVERY;
		return(FALSE);
	}

	if (debug)
	{
		printf("%sget_zone()\n", dbprefix);
		pr_query((qbuf_t *)&query, n, stdout);
	}

	/* setup destination address */
	bzero((char *)&sin, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons(NAMESERVER_PORT);
	sin.sin_addr = inaddr;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0)
	{
		_res_perror(&sin, host, "socket");
		h_errno = TRY_AGAIN;
		return(FALSE);
	}

	if (_res_connect(sock, &sin, sizeof(sin)) < 0)
	{
		if (verbose || debug)
			_res_perror(&sin, host, "connect");
		(void) close(sock);
		h_errno = TRY_AGAIN;
		return(FALSE);
	}

	if (verbose)
		printf("Asking zone transfer for %s ...\n", name);

/*
 * Send the query buffer.
 */
	if (_res_write(sock, &sin, host, (char *)&query, n) < 0)
	{
		(void) close(sock);
		h_errno = TRY_AGAIN;
		return(FALSE);
	}

/*
 * Process all incoming packets, usually one record in a separate packet.
 */
	while ((n = _res_read(sock, &sin, host, (char *)&answer, sizeof(querybuf))) != 0)
	{
		if (n < 0)
		{
			(void) close(sock);
			h_errno = TRY_AGAIN;
			return(FALSE);
		}

		errno = 0;	/* reset after we got an answer */

		if (n < HFIXEDSZ)
		{
			pr_error("answer length %s too short during %s for %s from %s",
				itoa(n), pr_type(T_AXFR), name, host);
			(void) close(sock);
			h_errno = TRY_AGAIN;
			return(FALSE);
		}

		if (debug > 1)
		{
			printf("%sgot answer, %d bytes:\n", dbprefix, n);
			pr_query((qbuf_t *)&answer, querysize(n), stdout);
		}

	/*
	 * Analyze the contents of the answer and check for errors.
	 * An error can be expected only in the very first packet.
	 * The query section should be empty except in the first packet.
	 * Note the special error status codes for specific failures.
	 */
		bp = (HEADER *)&answer;
		ancount = ntohs(bp->ancount);

		if (bp->rcode != NOERROR || ancount == 0)
		{
			if (verbose || debug)
				print_status(&answer, n);

			switch (bp->rcode)
			{
			    case NXDOMAIN:
				/* distinguish between authoritative or not */
				h_errno = bp->aa ? HOST_NOT_FOUND : NO_HOST;
				break;

			    case NOERROR:
				/* distinguish between authoritative or not */
				h_errno = bp->aa ? NO_DATA : NO_RREC;
				break;

			    case REFUSED:
				/* special status if zone transfer refused */
				h_errno = QUERY_REFUSED;
				break;

			    case SERVFAIL:
				/* special status upon explicit failure */
				h_errno = SERVER_FAILURE;
				break;

			    default:
				/* all other errors will cause a retry */
				h_errno = TRY_AGAIN;
				break;
			}

			if (npackets != 0)
				pr_error("unexpected error during %s for %s from %s",
					pr_type(T_AXFR), name, host);

			(void) close(sock);
			return(FALSE);
		}

		/* valid answer received, avoid buffer overrun */
		h_errno = 0;
		n = querysize(n);

	/*
	 * The nameserver and additional info section should be empty.
	 * There may be multiple answers in the answer section.
	 */
#ifdef obsolete
		if (ancount > 1)
			pr_error("multiple answers during %s for %s from %s",
				pr_type(T_AXFR), name, host);
#endif
		if (ntohs(bp->nscount) != 0)
			pr_error("nonzero nscount during %s for %s from %s",
				pr_type(T_AXFR), name, host);

		if (ntohs(bp->arcount) != 0)
			pr_error("nonzero arcount during %s for %s from %s",
				pr_type(T_AXFR), name, host);

	/*
	 * Valid packet received. Print contents if appropriate.
	 * Specific zone information will be saved by update_zone().
	 */
		npackets += 1;
		nrecords += ancount;

		soaname = NULL, subname = NULL, adrname = NULL, address = 0;
		listhost = host;

		(void) print_info(&answer, n, name, T_AXFR, queryclass, FALSE);

	/*
	 * Terminate upon the second SOA record for this zone.
	 */
		if (soacount > 1)
			break;
	}

/*
 * End of zone transfer at second SOA record or zero length read.
 */
	(void) close(sock);

/*
 * Check for the anomaly that the whole transfer consisted of the
 * SOA records only. Could occur if we queried the victim of a lame
 * delegation which happened to have the SOA record present.
 */
	if (nrecords <= soacount)
	{
		pr_error("empty zone transfer for %s from %s",
			name, host);
		h_errno = NO_RREC;
		return(FALSE);
	}

/*
 * Do an extra check for delegated zones that also have an A record.
 * Those may have been defined in the child zone, and crept in the
 * parent zone, or may have been defined as glue records.
 * This is not necessarily an error, but the host count may be wrong.
 * Note that an A record for the current zone has been ignored above.
 */
	for (n = 0; n < zonecount; n++)
	{
		i = host_index(zonename[n], FALSE);
#ifdef obsolete
		for (i = 0; i < hostcount; i++)
			if (sameword(hostname(i), zonename[n]))
				break;	/* found */
#endif
		if (i < hostcount)
			pr_warning("%s has both NS and A records within %s from %s",
				zonename[n], name, host);
	}

/*
 * The zone transfer has been successful.
 */
	if (verbose)
	{
		printf("Transfer complete, %d record%s received for %s\n",
			nrecords, plural(nrecords), name);
		if (npackets != nrecords)
			printf("Transfer consisted of %d packet%s from %s\n",
				npackets, plural(npackets), host);
	}

	return(TRUE);
}

/*
** UPDATE_ZONE -- Save zone information during zone listings
** ---------------------------------------------------------
**
**	Returns:
**		None.
**
**	Side effects:
**		Stores list of delegated zones found in zonename[],
**		and the count of delegated zones in ``zonecount''.
**		Stores list of host names found in hostname[],
**		and the count of host names in ``hostcount''.
**		Stores the count of SOA records in ``soacount''.
**
**	This routine is called by print_info() for each resource record.
*/

void
update_zone(name)
input char *name;			/* name of zone to do zone xfer for */
{
	char *host = listhost;		/* contacted host for zone listings */
	register int i;

/*
 * Terminate upon the second SOA record for this zone.
 */
	if (soaname && sameword(soaname, name))
		soacount++;

	/* the nameserver balks on this one */
	else if (soaname && !sameword(soaname, name))
		pr_warning("extraneous SOA record for %s within %s from %s",
			soaname, name, host);

/*
 * Save encountered delegated zone name for recursive listing.
 */
	if (subname && indomain(subname, name, FALSE))
	{
		i = zone_index(subname, TRUE);
#ifdef obsolete
		for (i = 0; i < zonecount; i++)
			if (sameword(zonename[i], subname))
				break;	/* duplicate */
#endif
		if (i >= zonecount)
		{
			zonename = newlist(zonename, zonecount+1, char *);
			zonename[zonecount] = newstr(subname);
			zonecount++;
		}
	}

	/* warn about strange delegated zones */
	else if (subname && !indomain(subname, name, TRUE))
		pr_warning("extraneous NS record for %s within %s from %s",
			subname, name, host);

/*
 * Save encountered name of A record for host name count.
 */
	if (adrname && indomain(adrname, name, FALSE) && !reverse)
	{
		i = host_index(adrname, TRUE);
#ifdef obsolete
		for (i = 0; i < hostcount; i++)
			if (sameword(hostname(i), adrname))
				break;	/* duplicate */
#endif
		if (i >= hostcount)
		{
			if (hostcount >= maxhosts)
			{
				maxhosts += MAXHOSTINCR;
				hostlist = newlist(hostlist, maxhosts, host_data_t);
			}
			hostname(hostcount) = newstr(adrname);
			hostaddr(hostcount) = address;
			multaddr(hostcount) = FALSE;
			hostcount++;
		}
		else if (address != hostaddr(i))
			multaddr(i) = TRUE;
	}

	/* check for unauthoritative glue records */
	else if (adrname && !indomain(adrname, name, TRUE))
		pr_warning("extraneous glue record for %s within %s from %s",
			adrname, name, host);
}

/*
** GET_MXREC -- Fetch MX records of a domain
** -----------------------------------------
**
**	Returns:
**		TRUE if MX records were found.
**		FALSE otherwise.
*/

bool
get_mxrec(name)
input char *name;			/* domain name to get mx for */
{
	querybuf answer;
	register int n;

	if (verbose)
		printf("Finding MX records for %s ...\n", name);

	n = get_info(&answer, name, T_MX, queryclass);
	if (n < 0)
		return(FALSE);

	(void) print_info(&answer, n, name, T_MX, queryclass, FALSE);

	return(TRUE);
}

/*
** GET_PRIMARY -- Fetch name of primary nameserver for a zone
** ----------------------------------------------------------
**
**	Returns:
**		Pointer to the name of the primary server, if found.
**		NULL if the server could not be determined.
*/

char *
get_primary(name)
input char *name;			/* name of zone to get soa for */
{
	querybuf answer;
	register int n;

	if (verbose)
		printf("Finding primary nameserver for %s ...\n", name);

	n = get_info(&answer, name, T_SOA, queryclass);
	if (n < 0)
		return(NULL);

	if (verbose > 1)
		(void) print_info(&answer, n, name, T_SOA, queryclass, FALSE);

	soaname = NULL;
	(void) get_soainfo(&answer, n, name);
	if (soaname == NULL)
		return(NULL);

	return(soa.primary);
}

/*
** CHECK_ZONE -- Fetch and analyze SOA record of a zone
** ----------------------------------------------------
**
**	Returns:
**		TRUE if the SOA record was found at the given server.
**		FALSE otherwise.
**
**	Inputs:
**		The global variable ``server'' must contain the name
**		of the server that was queried.
*/

bool
check_zone(name)
input char *name;			/* name of zone to get soa for */
{
	querybuf answer;
	register int n;

	if (verbose)
		printf("Checking SOA for %s at server %s ...\n", name, server);
	else if (authserver)
		printf("%-20s\tNS\t%s\n", name, server);
	else
		printf("%s\t(%s)\n", name, server);

	n = get_info(&answer, name, T_SOA, queryclass);
	if (n < 0)
		return(FALSE);

	if (verbose > 1)
		(void) print_info(&answer, n, name, T_SOA, queryclass, FALSE);

	soaname = NULL;
	(void) get_soainfo(&answer, n, name);
	if (soaname == NULL)
		return(FALSE);

	check_soa(&answer, name);

	return(TRUE);
}

/*
** GET_SOAINFO -- Extract SOA data from nameserver answer buffer
** -------------------------------------------------------------
**
**	Returns:
**		TRUE if the answer buffer was processed successfully.
**		FALSE otherwise.
**
**	Outputs:
**		The global struct ``soa'' is filled with the soa data.
**
**	Side effects:
**		Sets ``soaname'' if there is a valid SOA record.
**		This variable must have been cleared before calling
**		get_soainfo() and may be checked afterwards.
*/

bool
get_soainfo(answerbuf, answerlen, name)
input querybuf *answerbuf;		/* location of answer buffer */
input int answerlen;			/* length of answer buffer */
input char *name;			/* name of zone to get soa for */
{
	HEADER *bp;
	int qdcount, ancount;
	u_char *msg, *eom;
	register u_char *cp;

	bp = (HEADER *)answerbuf;
	qdcount = ntohs(bp->qdcount);
	ancount = ntohs(bp->ancount);

	msg = (u_char *)answerbuf;
	eom = (u_char *)answerbuf + answerlen;
	cp  = (u_char *)answerbuf + HFIXEDSZ;

	if (qdcount > 0 && cp < eom)	/* should be exactly one record */
	{
		cp = skip_qrec(name, T_SOA, queryclass, cp, msg, eom);
		if (cp == NULL)
			return(FALSE);
		qdcount--;
	}

	if (qdcount)
	{
		pr_error("invalid qdcount after %s query for %s",
			pr_type(T_SOA), name);
		h_errno = NO_RECOVERY;
		return(FALSE);
	}

/*
 * Check answer section only.
 * Check that answers match the requested zone. Ignore other entries.
 * The nameserver section may contain the nameservers for the zone,
 * and the additional section their addresses, but not guaranteed.
 * Those sections are usually empty for authoritative answers.
 */
	while (ancount > 0 && cp < eom)
	{
		char rname[MAXDNAME+1];
		int type, class, ttl, dlen;
		u_char *eor;
		register int n;

		n = expand_name(name, T_NONE, cp, msg, eom, rname);
		if (n < 0)
			return(FALSE);
		cp += n;

		n = 3*INT16SZ + INT32SZ;
		if (check_size(rname, T_NONE, cp, msg, eom, n) < 0)
			return(FALSE);

		type = _getshort(cp);
		cp += INT16SZ;

		class = _getshort(cp);
		cp += INT16SZ;

		ttl = _getlong(cp);
		cp += INT32SZ;

		dlen = _getshort(cp);
		cp += INT16SZ;

		eor = cp + dlen;
#ifdef lint
		if (verbose)
			printf("%-20s\t%d\t%s\t%s\n",
				rname, ttl, pr_class(class), pr_type(type));
#endif
		if ((type == T_SOA) && sameword(rname, name))
		{
			n = expand_name(rname, type, cp, msg, eom, soa.primary);
			if (n < 0)
				return(FALSE);
			cp += n;

			n = expand_name(rname, type, cp, msg, eom, soa.hostmaster);
			if (n < 0)
				return(FALSE);
			cp += n;

			n = 5*INT32SZ;
			if (check_size(rname, type, cp, msg, eor, n) < 0)
				return(FALSE);
			soa.serial = _getlong(cp);
			cp += INT32SZ;
			soa.refresh = _getlong(cp);
			cp += INT32SZ;
			soa.retry = _getlong(cp);
			cp += INT32SZ;
			soa.expire = _getlong(cp);
			cp += INT32SZ;
			soa.defttl = _getlong(cp);
			cp += INT32SZ;

			/* valid complete soa record found */
			soaname = strcpy(soanamebuf, rname);
		}
		else
		{
			/* just ignore other records */
			cp += dlen;
		}

		if (cp != eor)
		{
			pr_error("size error in %s record for %s, off by %s",
				pr_type(type), rname, itoa(cp - eor));
			h_errno = NO_RECOVERY;
			return(FALSE);
		}

		ancount--;
	}

	if (ancount)
	{
		pr_error("invalid ancount after %s query for %s",
			pr_type(T_SOA), name);
		h_errno = NO_RECOVERY;
		return(FALSE);
	}

	/* set proper status if no answers found */
	h_errno = (soaname != NULL) ? 0 : TRY_AGAIN;
	return(TRUE);
}

/*
** CHECK_SOA -- Analyze retrieved SOA records of a zone
** ----------------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		The global variable ``server'' must contain the
**		name of the server that was queried.
**		The global struct ``soa'' must contain the soa data.
*/

void
check_soa(answerbuf, name)
input querybuf *answerbuf;		/* location of answer buffer */
input char *name;			/* name of zone to check soa for */
{
	static char oldnamebuf[MAXDNAME+1];
	static char *oldname = NULL;	/* previous name of zone */
	static char *oldserver = NULL;	/* previous name of server */
	static soa_data_t oldsoa;	/* previous soa data */
	register int n;
	HEADER *bp;

/*
 * Print the various SOA fields in abbreviated form.
 * Values are actually unsigned, but we print them as signed integers,
 * apart from the serial which really becomes that big sometimes.
 * In the latter case we print a warning below.
 */
	printf("%s\t%s\t(%u %d %d %d %d)\n",
		soa.primary, soa.hostmaster, (unsigned)soa.serial,
		soa.refresh, soa.retry, soa.expire, soa.defttl);

/*
 * We are supposed to have queried an authoritative nameserver, and since
 * nameserver recursion has been turned off, answer must be authoritative.
 */
	bp = (HEADER *)answerbuf;
	if (!bp->aa)
	{
		if (authserver)
			pr_error("%s SOA record at %s is not authoritative",
				name, server);
		else
			pr_warning("%s SOA record at %s is not authoritative",
				name, server);

		if (authserver)
			errmsg("%s has lame delegation to %s",
				name, server);
	}

/*
 * Check whether we are switching to a new zone.
 * The old name must have been saved in static storage.
 */
	if ((oldname != NULL) && !sameword(name, oldname))
		oldname = NULL;

/*
 * Make few timer consistency checks only for the first one in a series.
 * Compare the primary field against the list of authoritative servers.
 * Explicitly check the hostmaster field for illegal characters ('@').
 * Yell if the serial has the high bit set (not always intentional).
 */
	if (oldname == NULL)
	{
		for (n = 0; n < nservers; n++)
			if (sameword(soa.primary, nsname[n]))
				break;	/* found */

		if ((n >= nservers) && authserver)
			pr_warning("%s SOA primary %s is not advertised via NS",
				name, soa.primary);

		if (!valid_name(soa.primary, FALSE, FALSE, FALSE))
			pr_warning("%s SOA primary %s has illegal name",
				name, soa.primary);

		if (!valid_name(soa.hostmaster, FALSE, TRUE, FALSE))
			pr_warning("%s SOA hostmaster %s has illegal mailbox",
				name, soa.hostmaster);

		if (bitset(0x80000000, soa.serial))
			pr_warning("%s SOA serial has high bit set",
				name);

		if (soa.retry > soa.refresh)
			pr_warning("%s SOA retry exceeds refresh",
				name);

		if (soa.refresh + soa.retry > soa.expire)
			pr_warning("%s SOA refresh+retry exceeds expire",
				name);
	}

/*
 * Compare various fields with those of the previous query, if any.
 * Different serial numbers may be present if secondaries have not yet
 * refreshed the data from the primary. Issue only a warning in that case.
 */
	if (oldname != NULL)
	{
		if (!sameword(soa.primary, oldsoa.primary))
			pr_error("%s and %s have different primary for %s",
				server, oldserver, name);

		if (!sameword(soa.hostmaster, oldsoa.hostmaster))
			pr_error("%s and %s have different hostmaster for %s",
				server, oldserver, name);

		if (soa.serial != oldsoa.serial)
			pr_warning("%s and %s have different serial for %s",
				server, oldserver, name);

		if (soa.refresh != oldsoa.refresh)
			pr_error("%s and %s have different refresh for %s",
				server, oldserver, name);

		if (soa.retry != oldsoa.retry)
			pr_error("%s and %s have different retry for %s",
				server, oldserver, name);

		if (soa.expire != oldsoa.expire)
			pr_error("%s and %s have different expire for %s",
				server, oldserver, name);

		if (soa.defttl != oldsoa.defttl)
			pr_error("%s and %s have different defttl for %s",
				server, oldserver, name);
	}

/*
 * Save the current information.
 */
	oldname = strcpy(oldnamebuf, name);
	oldserver = server;
	oldsoa = soa;
}

/*
** CHECK_DUPL -- Check global address list for duplicates
** ------------------------------------------------------
**
**	Returns:
**		TRUE if the given host address already exists.
**		FALSE otherwise.
**
**	Side effects:
**		Adds the host address to the list if not present.
**
**	The information in this table is global, and is not cleared.
*/

#define AHASHSIZE	0x2000
#define AHASHMASK	0x1fff

typedef struct addr_tab {
	ipaddr_t *addrlist;		/* global list of addresses */
	int addrcount;			/* count of global addresses */
} addr_tab_t;

addr_tab_t addrtab[AHASHSIZE];		/* hash list of global addresses */

bool
check_dupl(addr)
input ipaddr_t addr;			/* address of host to check */
{
 	register int i;
	register addr_tab_t *s;

	s = &addrtab[ntohl(addr) & AHASHMASK];

	for (i = 0; i < s->addrcount; i++)
		if (s->addrlist[i] == addr)
			return(TRUE);	/* duplicate */

	s->addrlist = newlist(s->addrlist, s->addrcount+1, ipaddr_t);
	s->addrlist[s->addrcount] = addr;
	s->addrcount++;
	return(FALSE);
}

/*
** CHECK_TTL -- Check list of records for different ttl values
** -----------------------------------------------------------
**
**	Returns:
**		TRUE if the ttl value matches the first record
**		already listed with the same name/type/class.
**		FALSE only when the first discrepancy is found.
**
**	Side effects:
**		Adds the record data to the list if not present.
*/

#define THASHSIZE	2003

typedef struct ttl_tab {
	struct ttl_tab *next;		/* next entry in chain */
	char *name;			/* name of resource record */
	int type;			/* resource record type */
	int class;			/* resource record class */
	int ttl;			/* time_to_live value */
	int count;			/* count of different ttl values */
} ttl_tab_t;

ttl_tab_t *ttltab[THASHSIZE];		/* hash list of record info */

bool
check_ttl(name, type, class, ttl)
input char *name;			/* resource record name */
input int type, class, ttl;		/* resource record fixed values */
{
	register ttl_tab_t *s;
	register ttl_tab_t **ps;
	register unsigned int hfunc;
	register char *p;
	register char c;

/*
 * Compute the hash function for this resource record.
 * Look it up in the appropriate hash chain.
 */
	for (hfunc = type, p = name; (c = *p) != '\0'; p++)
	{
		hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % THASHSIZE;
	}

	for (ps = &ttltab[hfunc]; (s = *ps) != NULL; ps = &s->next)
	{
		if (s->type != type || s->class != class)
			continue;
		if (sameword(s->name, name))
			break;
	}

/*
 * Allocate new entry if not found.
 */
	if (s == NULL)
	{
		/* ps = &ttltab[hfunc]; */
		s = newstruct(ttl_tab_t);

		/* initialize new entry */
		s->name = newstr(name);
		s->type = type;
		s->class = class;
		s->ttl = ttl;
		s->count = 0;

		/* link it in */
		s->next = *ps;
		*ps = s;
	}

/*
 * Check whether the ttl value matches the first recorded one.
 * If not, signal only the first discrepancy encountered, so
 * only one warning message will be printed.
 */
	if (s->ttl == ttl)
		return(TRUE);

	s->count += 1;
	return((s->count == 1) ? FALSE : TRUE);
}

/*
** CLEAR_TTLTAB -- Clear resource record list for ttl checking
** -----------------------------------------------------------
**
**	Returns:
**		None.
**
**	An entry on the hash list, and the host name in each
**	entry, have been allocated in dynamic memory.
**
**	The information in this table is on a per-zone basis.
**	It must be cleared before any subsequent zone transfers.
*/

void
clear_ttltab()
{
	register int i;
	register ttl_tab_t *s, *t;

	for (i = 0; i < THASHSIZE; i++)
	{
		if (ttltab[i] != NULL)
		{
			/* free chain of entries */
			for (t = NULL, s = ttltab[i]; s != NULL; s = t)
			{
				t = s->next;
				xfree(s->name);
				xfree(s);
			}

			/* reset hash chain */
			ttltab[i] = NULL;
		}
	}
}

/*
** HOST_INDEX -- Check list of host names for name being present
** -------------------------------------------------------------
**
**	Returns:
**		Index into hostname[] table, if found.
**		Current ``hostcount'' value, if not found.
**
**	Side effects:
**		May add an entry to the hash list if not present.
**
**	A linear search through the master table becomes very
**	costly for zones with more than a few thousand hosts.
**	Maintain a hash list with indexes into the master table.
**	Caller should update the master table after this call.
*/

#define HHASHSIZE	2003

typedef struct host_tab {
	struct host_tab *next;		/* next entry in chain */
	int slot;			/* slot in host name table */
} host_tab_t;

host_tab_t *hosttab[HHASHSIZE];		/* hash list of host name info */

int
host_index(name, enter)
input char *name;			/* the host name to check */
input bool enter;			/* add to table if not found */
{
	register host_tab_t *s;
	register host_tab_t **ps;
	register unsigned int hfunc;
	register char *p;
	register char c;

/*
 * Compute the hash function for this host name.
 * Look it up in the appropriate hash chain.
 */
	for (hfunc = 0, p = name; (c = *p) != '\0'; p++)
	{
		hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % HHASHSIZE;
	}

	for (ps = &hosttab[hfunc]; (s = *ps) != NULL; ps = &s->next)
	{
		if (s->slot >= hostcount)
			continue;
		if (sameword(hostname(s->slot), name))
			break;
	}

/*
 * Allocate new entry if not found.
 */
	if ((s == NULL) && enter)
	{
		/* ps = &hosttab[hfunc]; */
		s = newstruct(host_tab_t);

		/* initialize new entry */
		s->slot = hostcount;

		/* link it in */
		s->next = *ps;
		*ps = s;
	}

	return((s != NULL) ? s->slot : hostcount);
}

/*
** CLEAR_HOSTTAB -- Clear hash list for host name checking
** -------------------------------------------------------
**
**	Returns:
**		None.
**
**	A hash list entry has been allocated in dynamic memory.
**
**	The information in this table is on a per-zone basis.
**	It must be cleared before any subsequent zone transfers.
*/

void
clear_hosttab()
{
	register int i;
	register host_tab_t *s, *t;

	for (i = 0; i < HHASHSIZE; i++)
	{
		if (hosttab[i] != NULL)
		{
			/* free chain of entries */
			for (t = NULL, s = hosttab[i]; s != NULL; s = t)
			{
				t = s->next;
				xfree(s);
			}

			/* reset hash chain */
			hosttab[i] = NULL;
		}
	}
}

/*
** ZONE_INDEX -- Check list of zone names for name being present
** -------------------------------------------------------------
**
**	Returns:
**		Index into zonename[] table, if found.
**		Current ``zonecount'' value, if not found.
**
**	Side effects:
**		May add an entry to the hash list if not present.
**
**	A linear search through the master table becomes very
**	costly for more than a few thousand delegated zones.
**	Maintain a hash list with indexes into the master table.
**	Caller should update the master table after this call.
*/

#define ZHASHSIZE	2003

typedef struct zone_tab {
	struct zone_tab *next;		/* next entry in chain */
	int slot;			/* slot in zone name table */
} zone_tab_t;

zone_tab_t *zonetab[ZHASHSIZE];		/* hash list of zone name info */

int
zone_index(name, enter)
input char *name;			/* the zone name to check */
input bool enter;			/* add to table if not found */
{
	register zone_tab_t *s;
	register zone_tab_t **ps;
	register unsigned int hfunc;
	register char *p;
	register char c;

/*
 * Compute the hash function for this zone name.
 * Look it up in the appropriate hash chain.
 */
	for (hfunc = 0, p = name; (c = *p) != '\0'; p++)
	{
		hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % ZHASHSIZE;
	}

	for (ps = &zonetab[hfunc]; (s = *ps) != NULL; ps = &s->next)
	{
		if (s->slot >= zonecount)
			continue;
		if (sameword(zonename[s->slot], name))
			break;
	}

/*
 * Allocate new entry if not found.
 */
	if ((s == NULL) && enter)
	{
		/* ps = &zonetab[hfunc]; */
		s = newstruct(zone_tab_t);

		/* initialize new entry */
		s->slot = zonecount;

		/* link it in */
		s->next = *ps;
		*ps = s;
	}

	return((s != NULL) ? s->slot : zonecount);
}

/*
** CLEAR_ZONETAB -- Clear hash list for zone name checking
** -------------------------------------------------------
**
**	Returns:
**		None.
**
**	A hash list entry has been allocated in dynamic memory.
**
**	The information in this table is on a per-zone basis.
**	It must be cleared before any subsequent zone transfers.
*/

void
clear_zonetab()
{
	register int i;
	register zone_tab_t *s, *t;

	for (i = 0; i < ZHASHSIZE; i++)
	{
		if (zonetab[i] != NULL)
		{
			/* free chain of entries */
			for (t = NULL, s = zonetab[i]; s != NULL; s = t)
			{
				t = s->next;
				xfree(s);
			}

			/* reset hash chain */
			zonetab[i] = NULL;
		}
	}
}

/*
** CHECK_CANON -- Check list of domain names for name being canonical
** ------------------------------------------------------------------
**
**	Returns:
**		Nonzero if the name is definitely not canonical.
**		0 if it is canonical, or if it remains undecided.
**
**	Side effects:
**		Adds the domain name to the list if not present.
**
**	The information in this table is global, and is not cleared
**	(which may be necessary if the checking algorithm changes).
*/

#define CHASHSIZE	2003

typedef struct canon_tab {
	struct canon_tab *next;		/* next entry in chain */
	char *name;			/* domain name */
	int status;			/* nonzero if not canonical */
} canon_tab_t;

canon_tab_t *canontab[CHASHSIZE];	/* hash list of domain name info */

int
check_canon(name)
input char *name;			/* the domain name to check */
{
	register canon_tab_t *s;
	register canon_tab_t **ps;
	register unsigned int hfunc;
	register char *p;
	register char c;

/*
 * Compute the hash function for this domain name.
 * Look it up in the appropriate hash chain.
 */
	for (hfunc = 0, p = name; (c = *p) != '\0'; p++)
	{
		hfunc = ((hfunc << 1) ^ (lowercase(c) & 0377)) % CHASHSIZE;
	}

	for (ps = &canontab[hfunc]; (s = *ps) != NULL; ps = &s->next)
	{
		if (sameword(s->name, name))
			break;
	}

/*
 * Allocate new entry if not found.
 * Only then is the actual check carried out.
 */
	if (s == NULL)
	{
		/* ps = &canontab[hfunc]; */
		s = newstruct(canon_tab_t);

		/* initialize new entry */
		s->name = newstr(name);
		s->status = canonical(name);

		/* link it in */
		s->next = *ps;
		*ps = s;
	}

	return(s->status);
}

/*
** CHECK_ADDR -- Check whether reverse address mappings revert to host
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if all addresses of host map back to host.
**		FALSE otherwise.
*/

bool
check_addr(name)
input char *name;			/* host name to check addresses for */
{
	struct hostent *hp;
	register int i;
	struct in_addr inaddr[MAXADDRS];
	int naddress;
	char hnamebuf[MAXDNAME+1];
	char *hname;
	char inamebuf[MAXDNAME+1];
	char *iname;
	int matched;

/*
 * Look up the specified host to fetch its addresses.
 */
	hp = gethostbyname(name);
	if (hp == NULL)
	{
		ns_error(name, T_A, C_IN, server);
		return(FALSE);
	}

	hname = strncpy(hnamebuf, hp->h_name, MAXDNAME);
	hname[MAXDNAME] = '\0';

	for (i = 0; i < MAXADDRS && hp->h_addr_list[i]; i++)
		inaddr[i] = incopy(hp->h_addr_list[i]);
	naddress = i;

	if (verbose)
		printf("Found %d address%s for %s\n",
			naddress, plurale(naddress), hname);

/*
 * Map back the addresses found, and check whether they revert to host.
 */
	for (matched = 0, i = 0; i < naddress; i++)
	{
		iname = strcpy(inamebuf, inet_ntoa(inaddr[i]));

		if (verbose)
			printf("Checking %s address %s\n", hname, iname);

		hp = gethostbyaddr((char *)&inaddr[i], INADDRSZ, AF_INET);
		if (hp == NULL)
			ns_error(iname, T_PTR, C_IN, server);
		else if (!sameword(hp->h_name, hname))
			pr_warning("%s address %s maps to %s",
				hname, iname, hp->h_name);
		else
			matched++;
	}

	return((matched == naddress) ? TRUE : FALSE);
}

/*
** CHECK_NAME -- Check whether address belongs to host addresses
** -------------------------------------------------------------
**
**	Returns:
**		TRUE if given address was found among host addresses.
**		FALSE otherwise.
*/

bool
check_name(addr)
input ipaddr_t addr;			/* address of host to check */
{
	struct hostent *hp;
	register int i;
	struct in_addr inaddr;
	char hnamebuf[MAXDNAME+1];
	char *hname;
	char inamebuf[MAXDNAME+1];
	char *iname;
	int matched;

/*
 * Check whether the address is registered by fetching its host name.
 */
	inaddr.s_addr = addr;
	iname = strcpy(inamebuf, inet_ntoa(inaddr));

	hp = gethostbyaddr((char *)&inaddr, INADDRSZ, AF_INET);
	if (hp == NULL)
	{
		ns_error(iname, T_PTR, C_IN, server);
		return(FALSE);
	}

	hname = strncpy(hnamebuf, hp->h_name, MAXDNAME);
	hname[MAXDNAME] = '\0';

	if (verbose)
		printf("Address %s maps to %s\n", iname, hname);

/*
 * Lookup the host name found to fetch its addresses.
 */
	hp = gethostbyname(hname);
	if (hp == NULL)
	{
		ns_error(hname, T_A, C_IN, server);
		return(FALSE);
	}

/*
 * Verify whether the mapped host name is canonical.
 */
	if (!sameword(hp->h_name, hname))
		pr_warning("%s host %s is not canonical (%s)",
			iname, hname, hp->h_name);

/*
 * Check whether the given address is listed among the known addresses.
 */
	for (matched = 0, i = 0; hp->h_addr_list[i]; i++)
	{
		inaddr = incopy(hp->h_addr_list[i]);

		if (verbose)
			printf("Checking %s address %s\n",
				hname, inet_ntoa(inaddr));

		if (inaddr.s_addr == addr)
			matched++;
	}

	if (!matched)
		pr_error("address %s does not belong to %s",
			iname, hname);

	return(matched ? TRUE : FALSE);
}

/*
** GETH_BYNAME -- Wrapper for gethostbyname
** ----------------------------------------
**
**	Returns:
**		Pointer to struct hostent if lookup was successful.
**		NULL otherwise.
**
**	Note. This routine works for fully qualified names only.
**	The entire special res_search() processing can be skipped.
*/

struct hostent *
geth_byname(name)
input CONST char *name;			/* name to do forward lookup for */
{
	querybuf answer;
	struct hostent *hp;
	register int n;

	hp = gethostbyname(name);
	if (hp != NULL)
		return(hp);

	if (verbose > print_level)
		printf("Finding addresses for %s ...\n", name);

	n = get_info(&answer, name, T_A, C_IN);
	if (n < 0)
		return(NULL);

	if ((verbose > print_level + 1) && (print_level < 1))
		(void) print_info(&answer, n, name, T_A, C_IN, FALSE);

	hp = gethostbyname(name);
	return(hp);
}

/*
** GETH_BYADDR -- Wrapper for gethostbyaddr
** ----------------------------------------
**
**	Returns:
**		Pointer to struct hostent if lookup was successful.
**		NULL otherwise.
*/

struct hostent *
geth_byaddr(addr, size, family)
input CONST char *addr;			/* address to do reverse mapping for */
input int size;				/* size of the address */
input int family;			/* address family */
{
	char addrbuf[4*4 + sizeof(ARPA_ROOT) + 1];
	char *name = addrbuf;
	u_char *a = (u_char *)addr;
	querybuf answer;
	struct hostent *hp;
	register int n;

	if (size != INADDRSZ || family != AF_INET)
	{
		hp = gethostbyaddr(addr, size, family);
		return(hp);
	}

	hp = gethostbyaddr(addr, size, family);
	if (hp != NULL)
		return(hp);

	/* construct absolute reverse name *without* trailing dot */
	(void) sprintf(addrbuf, "%u.%u.%u.%u.%s",
		a[3]&0xff, a[2]&0xff, a[1]&0xff, a[0]&0xff, ARPA_ROOT);

	if (verbose > print_level)
		printf("Finding reverse mapping for %s ...\n",
			inet_ntoa(incopy(addr)));

	n = get_info(&answer, name, T_PTR, C_IN);
	if (n < 0)
		return(NULL);

	if ((verbose > print_level + 1) && (print_level < 1))
		(void) print_info(&answer, n, name, T_PTR, C_IN, FALSE);

	hp = gethostbyaddr(addr, size, family);
	return(hp);
}

/*
** PARSE_TYPE -- Decode rr type from input string
** ----------------------------------------------
**
**	Returns:
**		Value of resource record type.
**		-1 if specified record name is invalid.
**
**	Note.	Several types are deprecated or obsolete, but recognized.
**		T_AXFR/T_IXFR is not allowed to be specified as query type.
*/

int
parse_type(str)
input char *str;			/* input string with record type */
{
	register int type;

		/* standard types */

	if (sameword(str, "A"))		return(T_A);
	if (sameword(str, "NS"))	return(T_NS);
	if (sameword(str, "MD"))	return(T_MD);		/* obsolete */
	if (sameword(str, "MF"))	return(T_MF);		/* obsolete */
	if (sameword(str, "CNAME"))	return(T_CNAME);
	if (sameword(str, "SOA"))	return(T_SOA);
	if (sameword(str, "MB"))	return(T_MB);		/* deprecated */
	if (sameword(str, "MG"))	return(T_MG);		/* deprecated */
	if (sameword(str, "MR"))	return(T_MR);		/* deprecated */
	if (sameword(str, "NULL"))	return(T_NULL);		/* obsolete */
	if (sameword(str, "WKS"))	return(T_WKS);
	if (sameword(str, "PTR"))	return(T_PTR);
	if (sameword(str, "HINFO"))	return(T_HINFO);
	if (sameword(str, "MINFO"))	return(T_MINFO);	/* deprecated */
	if (sameword(str, "MX"))	return(T_MX);
	if (sameword(str, "TXT"))	return(T_TXT);

		/* new types */

	if (sameword(str, "RP"))	return(T_RP);
	if (sameword(str, "AFSDB"))	return(T_AFSDB);
	if (sameword(str, "X25"))	return(T_X25);
	if (sameword(str, "ISDN"))	return(T_ISDN);
	if (sameword(str, "RT"))	return(T_RT);
	if (sameword(str, "NSAP"))	return(T_NSAP);
	if (sameword(str, "NSAP-PTR"))	return(T_NSAPPTR);
	if (sameword(str, "SIG"))	return(T_SIG);
	if (sameword(str, "KEY"))	return(T_KEY);
	if (sameword(str, "PX"))	return(T_PX);
	if (sameword(str, "GPOS"))	return(T_GPOS);		/* withdrawn */
	if (sameword(str, "AAAA"))	return(T_AAAA);
	if (sameword(str, "LOC"))	return(T_LOC);
	if (sameword(str, "NXT"))	return(T_NXT);
	if (sameword(str, "EID"))	return(T_EID);
	if (sameword(str, "NIMLOC"))	return(T_NIMLOC);
	if (sameword(str, "SRV"))	return(T_SRV);
	if (sameword(str, "ATMA"))	return(T_ATMA);
	if (sameword(str, "NAPTR"))	return(T_NAPTR);

		/* nonstandard types */

	if (sameword(str, "UINFO"))	return(T_UINFO);
	if (sameword(str, "UID"))	return(T_UID);
	if (sameword(str, "GID"))	return(T_GID);
	if (sameword(str, "UNSPEC"))	return(T_UNSPEC);

		/* filters */

	if (sameword(str, "IXFR"))	return(-1);		/* illegal */
	if (sameword(str, "AXFR"))	return(-1);		/* illegal */
	if (sameword(str, "MAILB"))	return(T_MAILB);
	if (sameword(str, "MAILA"))	return(T_MAILA);	/* obsolete */
	if (sameword(str, "ANY"))	return(T_ANY);
	if (sameword(str, "*"))		return(T_ANY);

		/* unknown types */

	type = atoi(str);
	if (type >= T_FIRST && type <= T_LAST)
		return(type);

	return(-1);
}

/*
** PARSE_CLASS -- Decode rr class from input string
** ------------------------------------------------
**
**	Returns:
**		Value of resource class.
**		-1 if specified class name is invalid.
**
**	Note.	C_CSNET is obsolete, but recognized.
*/

int
parse_class(str)
input char *str;			/* input string with resource class */
{
	register int class;

	if (sameword(str, "IN"))	return(C_IN);
	if (sameword(str, "INTERNET"))	return(C_IN);
	if (sameword(str, "CS"))	return(C_CSNET);	/* obsolete */
	if (sameword(str, "CSNET"))	return(C_CSNET);	/* obsolete */
	if (sameword(str, "CH"))	return(C_CHAOS);
	if (sameword(str, "CHAOS"))	return(C_CHAOS);
	if (sameword(str, "HS"))	return(C_HS);
	if (sameword(str, "HESIOD"))	return(C_HS);

	if (sameword(str, "ANY"))	return(C_ANY);
	if (sameword(str, "*"))		return(C_ANY);

	class = atoi(str);
	if (class > 0)
		return(class);

	return(-1);
}

/*
** IN_ADDR_ARPA -- Convert dotted quad string to reverse in-addr.arpa
** ------------------------------------------------------------------
**
**	Returns:
**		Pointer to appropriate reverse in-addr.arpa name
**		with trailing dot to force absolute domain name.
**		NULL in case of invalid dotted quad input string.
*/

char *
in_addr_arpa(dottedquad)
input char *dottedquad;			/* input string with dotted quad */
{
	static char addrbuf[4*4 + sizeof(ARPA_ROOT) + 2];
	unsigned int a[4];
	register int n;

	n = sscanf(dottedquad, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]);
	switch (n)
	{
	    case 4:
		(void) sprintf(addrbuf, "%u.%u.%u.%u.%s.",
			a[3]&0xff, a[2]&0xff, a[1]&0xff, a[0]&0xff, ARPA_ROOT);
		break;

	    case 3:
		(void) sprintf(addrbuf, "%u.%u.%u.%s.",
			a[2]&0xff, a[1]&0xff, a[0]&0xff, ARPA_ROOT);
		break;

	    case 2:
		(void) sprintf(addrbuf, "%u.%u.%s.",
			a[1]&0xff, a[0]&0xff, ARPA_ROOT);
		break;

	    case 1:
		(void) sprintf(addrbuf, "%u.%s.",
			a[0]&0xff, ARPA_ROOT);
		break;

	    default:
		return(NULL);
	}

	while (--n >= 0)
		if (a[n] > 255)
			return(NULL);

	return(addrbuf);
}

/*
** NSAP_INT -- Convert dotted nsap address string to reverse nsap.int
** ------------------------------------------------------------------
**
**	Returns:
**		Pointer to appropriate reverse nsap.int name
**		with trailing dot to force absolute domain name.
**		NULL in case of invalid nsap address input string.
*/

char *
nsap_int(name)
input char *name;			/* input string with dotted nsap */
{
	static char addrbuf[4*MAXNSAP + sizeof(NSAP_ROOT) + 2];
	register int n;
	register int i;

	/* skip optional leading hex indicator */
	if (samehead(name, "0x"))
		name += 2;

	for (n = 0, i = strlength(name)-1; i >= 0; --i)
	{
		/* skip optional interspersed separators */
		if (name[i] == '.' || name[i] == '+' || name[i] == '/')
			continue;

		/* must consist of hex digits only */
		if (!is_xdigit(name[i]))
			return(NULL);

		/* but not too many */
		if (n >= 4*MAXNSAP)
			return(NULL);

		addrbuf[n++] = name[i];
		addrbuf[n++] = '.';
	}

	/* must have an even number of hex digits */ 
	if (n == 0 || (n % 4) != 0)
		return(NULL);

	(void) sprintf(&addrbuf[n], "%s.", NSAP_ROOT);
	return(addrbuf);
}

/*
** PRINT_HOST -- Print host name and address of hostent struct
** -----------------------------------------------------------
**
**	Returns:
**		None.
*/

void
print_host(heading, hp)
input char *heading;			/* header string */
input struct hostent *hp;		/* location of hostent struct */
{
	register char **ap;

	printf("%s: %s", heading, hp->h_name);

	for (ap = hp->h_addr_list; ap && *ap; ap++)
	{
		if (ap == hp->h_addr_list)
			printf("\nAddress:");

		printf(" %s", inet_ntoa(incopy(*ap)));
	}

	for (ap = hp->h_aliases; ap && *ap && **ap; ap++)
	{
		if (ap == hp->h_aliases)
			printf("\nAliases:");

		printf(" %s", *ap);
	}

	printf("\n\n");
}

/*
** SHOW_RES -- Show resolver database information
** ----------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		The resolver database _res is localized in the resolver.
*/

void
show_res()
{
	register int i;
	register char **domain;

/*
 * The default domain is defined by the "domain" entry in /etc/resolv.conf
 * if not overridden by the environment variable "LOCALDOMAIN".
 * If still not defined, gethostname() may yield a fully qualified host name.
 */
	printf("Default domain:");
	if (_res.defdname[0] != '\0')
		printf(" %s", _res.defdname);
	printf("\n");

/*
 * The search domains are extracted from the default domain components,
 * but may be overridden by "search" directives in /etc/resolv.conf
 * since 4.8.3.
 */
	printf("Search domains:");
	for (domain = _res.dnsrch; *domain; domain++)
		printf(" %s", *domain);
	printf("\n");

/*
 * The routine res_send() will do _res.retry tries to contact each of the
 * _res.nscount nameserver addresses before giving up when using datagrams.
 * The first try will timeout after _res.retrans seconds. Each following
 * try will timeout after ((_res.retrans << try) / _res.nscount) seconds.
 * Note. When we contact an explicit server the addresses will be replaced
 * by the multiple addresses of the same server.
 * When doing a zone transfer _res.retrans is used for the connect timeout.
 */
	printf("Timeout per retry: %d secs\n", _res.retrans);
	printf("Number of retries: %d\n", _res.retry);

	printf("Number of addresses: %d\n", _res.nscount);
	for (i = 0; i < _res.nscount; i++)
		printf("%s\n", inet_ntoa(nslist(i).sin_addr));

/*
 * The resolver options are initialized by res_init() to contain the
 * defaults settings (RES_RECURSE | RES_DEFNAMES | RES_DNSRCH)
 * The various options have the following meaning:
 *
 *	RES_INIT	set after res_init() has been called
 *	RES_DEBUG	let the resolver modules print debugging info
 *	RES_AAONLY	want authoritative answers only (not implemented)
 *	RES_USEVC	use tcp virtual circuit instead of udp datagrams
 *	RES_PRIMARY	use primary nameserver only (not implemented)
 *	RES_IGNTC	ignore datagram truncation; don't switch to tcp
 *	RES_RECURSE	forward query if answer not locally available
 *	RES_DEFNAMES	add default domain to queryname without dot
 *	RES_STAYOPEN	keep tcp socket open for subsequent queries
 *	RES_DNSRCH	append search domains even to queryname with dot
 */
	printf("Options set:");
	if (bitset(RES_INIT,      _res.options)) printf(" INIT");
	if (bitset(RES_DEBUG,     _res.options)) printf(" DEBUG");
	if (bitset(RES_AAONLY,    _res.options)) printf(" AAONLY");
	if (bitset(RES_USEVC,     _res.options)) printf(" USEVC");
	if (bitset(RES_PRIMARY,   _res.options)) printf(" PRIMARY");
	if (bitset(RES_IGNTC,     _res.options)) printf(" IGNTC");
	if (bitset(RES_RECURSE,   _res.options)) printf(" RECURSE");
	if (bitset(RES_DEFNAMES,  _res.options)) printf(" DEFNAMES");
	if (bitset(RES_STAYOPEN,  _res.options)) printf(" STAYOPEN");
	if (bitset(RES_DNSRCH,    _res.options)) printf(" DNSRCH");
	printf("\n");

	printf("Options clr:");
	if (!bitset(RES_INIT,     _res.options)) printf(" INIT");
	if (!bitset(RES_DEBUG,    _res.options)) printf(" DEBUG");
	if (!bitset(RES_AAONLY,   _res.options)) printf(" AAONLY");
	if (!bitset(RES_USEVC,    _res.options)) printf(" USEVC");
	if (!bitset(RES_PRIMARY,  _res.options)) printf(" PRIMARY");
	if (!bitset(RES_IGNTC,    _res.options)) printf(" IGNTC");
	if (!bitset(RES_RECURSE,  _res.options)) printf(" RECURSE");
	if (!bitset(RES_DEFNAMES, _res.options)) printf(" DEFNAMES");
	if (!bitset(RES_STAYOPEN, _res.options)) printf(" STAYOPEN");
	if (!bitset(RES_DNSRCH,   _res.options)) printf(" DNSRCH");
	printf("\n");

/*
 * The new BIND 4.9.3 has additional features which are not (yet) used.
 */
	printf("\n");
}

/*
** PRINT_STATISTICS -- Print resource record statistics
** ----------------------------------------------------
**
**	Returns:
**		None.
**
**	Inputs:
**		The record_stats[] counts have been updated by print_rrec().
*/

void
print_statistics(name, filter, class)
input char *name;			/* name of zone we are listing */
input int filter;			/* type of records we want to see */
input int class;			/* class of records we want to see */
{
	register int type;
	int nrecords;

	for (type = T_FIRST; type <= T_LAST; type++)
	{
		nrecords = record_stats[type];
		if (nrecords > 0 || ((filter != T_ANY) && want_type(type, filter)))
		{
			printf("Found %4d %-5s record%-1s", nrecords,
				pr_type(type), plural(nrecords));

			if (class != C_IN)
				printf(" in class %s", pr_class(class));

			printf(" within %s\n", name);
		}
	}
}


/*
** CLEAR_STATISTICS -- Clear resource record statistics
** ----------------------------------------------------
**
**	Returns:
**		None.
*/

void
clear_statistics()
{
	bzero((char *)record_stats, sizeof(record_stats));
}

/*
** SHOW_TYPES -- Show resource record types wanted
** -----------------------------------------------
**
**	Returns:
**		None.
*/

void
show_types(name, filter, class)
input char *name;			/* name we want to query about */
input int filter;			/* type of records we want to see */
input int class;			/* class of records we want to see */
{
	register int type;

	if (filter >= T_NONE)
	{
		printf("Query about %s for record types", name);

		if (filter == T_ANY)
			printf(" %s", pr_type(T_ANY));
		else
			for (type = T_FIRST; type <= T_LAST; type++)
				if (want_type(type, filter))
					printf(" %s", pr_type(type));

		if (class != C_IN)
			printf(" in class %s", pr_class(class));

		printf("\n");
	}
}

/*
** NS_ERROR -- Print error message from errno and h_errno
** ------------------------------------------------------
**
**	Returns:
**		None.
**
** If BIND res_send() fails, it will leave errno in either of the first
** two following states when using datagrams. Note that this depends on
** the proper handling of connected datagram sockets, which is usually
** true if BSD >= 43 (see res_send.c for details; it may need a patch).
** Note. If the 4.8 version succeeds, it may leave errno as EAFNOSUPPORT
** if it has disconnected a previously connected datagram socket, since
** the dummy address used to disconnect does not have a proper family set.
** Always clear errno after getting a reply, or patch res_send().
** Our private version of res_send() will leave also other error statuses.
*/

void
ns_error(name, type, class, host) 
input char *name;			/* full name we queried about */
input int type;				/* record type we queried about */
input int class;			/* record class we queried about */
input char *host;			/* set if explicit server was used */
{
	static char *auth = "Authoritative answer";

/*
 * Print the message associated with the network related errno values.
 */
	switch (errno)
	{
	    case ECONNREFUSED:
		/*
		 * The contacted host does not have a nameserver running.
		 * The standard res_send() also returns this if none of
		 * the intended hosts could be reached via datagrams.
		 */
		if (host != NULL)
			errmsg("Nameserver %s not running", host);
		else
			errmsg("Nameserver not running");
		break;

	    case ETIMEDOUT:
		/*
		 * The contacted server did not give any reply at all
		 * within the specified time frame.
		 */
		if (host != NULL)
			errmsg("Nameserver %s not responding", host);
		else
			errmsg("Nameserver not responding");
		break;

	    case ENETDOWN:
	    case ENETUNREACH:
	    case EHOSTDOWN:
	    case EHOSTUNREACH:
		/*
		 * The host to be contacted or its network can not be reached.
		 * Our private res_send() also returns this using datagrams.
		 */
		if (host != NULL)
			errmsg("Nameserver %s not reachable", host);
		else
			errmsg("Nameserver not reachable");
		break;
	}

/*
 * Print the message associated with the particular nameserver error.
 */
	switch (h_errno)
	{
	    case HOST_NOT_FOUND:
		/*
		 * The specified name does definitely not exist at all.
		 * In this case the answer is always authoritative.
		 * Nameserver status: NXDOMAIN
		 */
		if (class != C_IN)
			errmsg("%s does not exist in class %s (%s)",
				name, pr_class(class), auth);
		else if (host != NULL)
			errmsg("%s does not exist at %s (%s)",
				name, host, auth);
		else
			errmsg("%s does not exist (%s)",
				name, auth);
		break;

	    case NO_HOST:
		/*
		 * The specified name does not exist, but the answer
		 * was not authoritative, so it is still undecided.
		 * Nameserver status: NXDOMAIN
		 */
		if (class != C_IN)
			errmsg("%s does not exist in class %s, try again",
				name, pr_class(class));
		else if (host != NULL)
			errmsg("%s does not exist at %s, try again",
				name, host);
		else
			errmsg("%s does not exist, try again",
				name);
		break;

	    case NO_DATA:
		/*
		 * The name is valid, but the specified type does not exist.
		 * This status is here returned only in case authoritative.
		 * Nameserver status: NOERROR
		 */
		if (class != C_IN)
			errmsg("%s has no %s record in class %s (%s)",
				name, pr_type(type), pr_class(class), auth);
		else if (host != NULL)
			errmsg("%s has no %s record at %s (%s)",
				name, pr_type(type), host, auth);
		else
			errmsg("%s has no %s record (%s)",
				name, pr_type(type), auth);
		break;

	    case NO_RREC:
		/*
		 * The specified type does not exist, but we don't know whether
		 * the name is valid or not. The answer was not authoritative.
		 * Perhaps recursion was off, and no data was cached locally.
		 * Nameserver status: NOERROR
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s currently not present",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record currently not present at %s",
				name, pr_type(type), host);
		else
			errmsg("%s %s record currently not present",
				name, pr_type(type));
		break;

	    case TRY_AGAIN:
		/*
		 * Some intermediate failure, e.g. connect timeout,
		 * or some local operating system transient errors.
		 * General failure to reach any appropriate servers.
		 * The status SERVFAIL now yields a separate error code.
		 * Nameserver status: (SERVFAIL)
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s not found, try again",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record not found at %s, try again",
				name, pr_type(type), host);
		else
			errmsg("%s %s record not found, try again",
				name, pr_type(type));
		break;

	    case SERVER_FAILURE:
		/*
		 * Explicit server failure status. This will be returned upon
		 * some internal server errors, forwarding failures, or when
		 * the server is not authoritative for a specific class.
		 * Also if the zone data has expired at a secondary server.
		 * Nameserver status: SERVFAIL
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s not found, server failure",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record not found at %s, server failure",
				name, pr_type(type), host);
		else
			errmsg("%s %s record not found, server failure",
				name, pr_type(type));
		break;

	    case NO_RECOVERY:
		/*
		 * Some irrecoverable format error, or server refusal.
		 * The status REFUSED now yields a separate error code.
		 * Nameserver status: (REFUSED) FORMERR NOTIMP NOCHANGE
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s not found, no recovery",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record not found at %s, no recovery",
				name, pr_type(type), host);
		else
			errmsg("%s %s record not found, no recovery",
				name, pr_type(type));
		break;

	    case QUERY_REFUSED:
		/*
		 * The server explicitly refused to answer the query.
		 * Servers can be configured to disallow zone transfers.
		 * Nameserver status: REFUSED
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s query refused",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record query refused by %s",
				name, pr_type(type), host);
		else
			errmsg("%s %s record query refused",
				name, pr_type(type));
		break;

	    default:
		/*
		 * Unknown cause for server failure.
		 */
		if (class != C_IN)
			errmsg("%s %s record in class %s not found",
				name, pr_type(type), pr_class(class));
		else if (host != NULL)
			errmsg("%s %s record not found at %s",
				name, pr_type(type), host);
		else
			errmsg("%s %s record not found",
				name, pr_type(type));
		break;
	}
}

/*
** DECODE_ERROR -- Convert nameserver error code to error message
** --------------------------------------------------------------
**
**	Returns:
**		Pointer to appropriate error message.
*/

char *
decode_error(rcode)
input int rcode;			/* error code from bp->rcode */
{
	switch (rcode)
	{
	    case NOERROR: 	return("no error");
	    case FORMERR:	return("format error");
	    case SERVFAIL:	return("server failure");
	    case NXDOMAIN:	return("non-existent domain");
	    case NOTIMP:	return("not implemented");
	    case REFUSED:	return("query refused");
	    case NOCHANGE:	return("no change");
	}

	return("unknown error");
}

/*
** PRINT_STATUS -- Print result status after nameserver query
** ----------------------------------------------------------
**
**	Returns:
**		None.
**
**	Conditions:
**		The size of the answer buffer must have been
**		checked before to be of sufficient length,
**		i.e. to contain at least the buffer header.
*/

void
print_status(answerbuf, answerlen)
input querybuf *answerbuf;		/* location of answer buffer */
input int answerlen;			/* length of answer buffer */
{
	HEADER *bp;
	int ancount;
	bool failed;

	bp = (HEADER *)answerbuf;
	ancount = ntohs(bp->ancount);
	failed = (bp->rcode != NOERROR || ancount == 0);

	printf("%s", verbose ? "" : dbprefix);

	printf("Query %s", failed ? "failed" : "done");

	if (bp->tc || (answerlen > PACKETSZ))
		printf(", %d byte%s", answerlen, plural(answerlen));

	if (bp->tc)
	{
		if (answerlen > sizeof(querybuf))
			printf(" (truncated to %d)", sizeof(querybuf));
		else
			printf(" (truncated)");
	}

	printf(", %d answer%s", ancount, plural(ancount));

	printf(", %s", bp->aa ? "authoritative " : "");

	printf("status: %s\n", decode_error((int)bp->rcode));
}

/*
** PR_ERROR -- Print error message about encountered inconsistencies
** -----------------------------------------------------------------
**
**	We are supposed to have an error condition which is fatal
**	for normal continuation, and the message is always printed.
**
**	Returns:
**		None.
**
**	Side effects:
**		Increments the global error count.
*/

void /*VARARGS1*/
pr_error(fmt, a, b, c, d)
input char *fmt;			/* format of message */
input char *a, *b, *c, *d;		/* optional arguments */
{
	(void) fprintf(stderr, " *** ");
	(void) fprintf(stderr, fmt, a, b, c, d);
	(void) fprintf(stderr, "\n");

	/* flag an error */
	errorcount++;
}


/*
** PR_WARNING -- Print warning message about encountered inconsistencies
** ---------------------------------------------------------------------
**
**	We are supposed to have an error condition which is non-fatal
**	for normal continuation, and the message is suppressed in case
**	quiet mode has been selected.
**
**	Returns:
**		None.
*/

void /*VARARGS1*/
pr_warning(fmt, a, b, c, d)
input char *fmt;			/* format of message */
input char *a, *b, *c, *d;		/* optional arguments */
{
	if (!quiet)
	{
		(void) fprintf(stderr, " !!! ");
		(void) fprintf(stderr, fmt, a, b, c, d);
		(void) fprintf(stderr, "\n");
	}
}

/*
** WANT_TYPE -- Indicate whether the rr type matches the desired filter
** --------------------------------------------------------------------
**
**	Returns:
**		TRUE if the resource record type matches the filter.
**		FALSE otherwise.
**
**	In regular mode, the querytype is used to formulate the query,
**	and the filter is set to T_ANY to filter out any response.
**	In listmode, we get everything, so the filter is set to the
**	querytype to filter out the proper responses.
**	Note that T_NONE is the default querytype in listmode.
*/

bool
want_type(type, filter)
input int type;				/* resource record type */
input int filter;			/* type of records we want to see */
{
	if (type == filter)
		return(TRUE);

	if (filter == T_ANY)
		return(TRUE);

	if (filter == T_NONE &&
	   (type == T_A || type == T_NS || type == T_PTR))
		return(TRUE);

	if (filter == T_MAILB &&
	   (type == T_MB || type == T_MR || type == T_MG || type == T_MINFO))
		return(TRUE);

	if (filter == T_MAILA &&
	   (type == T_MD || type == T_MF))
		return(TRUE);

	return(FALSE);
}

/*
** WANT_CLASS -- Indicate whether the rr class matches the desired filter
** ----------------------------------------------------------------------
**
**	Returns:
**		TRUE if the resource record class matches the filter.
**		FALSE otherwise.
**
**	In regular mode, the queryclass is used to formulate the query,
**	and the filter is set to C_ANY to filter out any response.
**	In listmode, we get everything, so the filter is set to the
**	queryclass to filter out the proper responses.
**	Note that C_IN is the default queryclass in listmode.
*/

bool
want_class(class, filter)
input int class;			/* resource record class */
input int filter;			/* class of records we want to see */
{
	if (class == filter)
		return(TRUE);

	if (filter == C_ANY)
		return(TRUE);

	return(FALSE);
}

/*
** INDOMAIN -- Check whether a name belongs to a zone
** --------------------------------------------------
**
**	Returns:
**		TRUE if the given name lies anywhere in the zone, or
**		if the given name is the same as the zone and may be so.
**		FALSE otherwise.
*/

bool
indomain(name, domain, equal)
input char *name;			/* the name under consideration */
input char *domain;			/* the name of the zone */
input bool equal;			/* set if name may be same as zone */
{
	register char *dot;

	if (sameword(name, domain))
		return(equal);

	if (sameword(domain, "."))
		return(TRUE);

	dot = index(name, '.');
	while (dot != NULL)
	{
		if (!is_quoted(dot, name))
		{
			if (sameword(dot+1, domain))
				return(TRUE);
		}

		dot = index(dot+1, '.');
	}

	return(FALSE);
}

/*
** SAMEDOMAIN -- Check whether a name belongs to a zone
** ----------------------------------------------------
**
**	Returns:
**		TRUE if the given name lies directly in the zone, or
**		if the given name is the same as the zone and may be so.
**		FALSE otherwise.
*/

bool
samedomain(name, domain, equal)
input char *name;			/* the name under consideration */
input char *domain;			/* the name of the zone */
input bool equal;			/* set if name may be same as zone */
{
	register char *dot;

	if (sameword(name, domain))
		return(equal);

	dot = index(name, '.');
	while (dot != NULL)
	{
		if (!is_quoted(dot, name))
		{
			if (sameword(dot+1, domain))
				return(TRUE);

			return(FALSE);
		}

		dot = index(dot+1, '.');
	}

	if (sameword(domain, "."))
		return(TRUE);

	return(FALSE);
}

/*
** GLUERECORD -- Check whether a name is a glue record
** ---------------------------------------------------
**
**	Returns:
**		TRUE is this is a glue record.
**		FALSE otherwise.
**
**	The name is supposed to be the name of an address record.
**	If it lies directly in the given zone, it is considered
**	an ordinary host within that zone, and not a glue record.
**	If it does not belong to the given zone at all, is it
**	here considered to be a glue record.
**	If it lies in the given zone, but not directly, it is
**	considered a glue record if it belongs to any of the known
**	delegated zones of the given zone.
**	In the root zone itself are no hosts, only glue records.
*/

bool
gluerecord(name, domain, zone, nzones)
input char *name;			/* the name under consideration */
input char *domain;			/* name of zone being processed */
input char *zone[];			/* list of known delegated zones */
input int nzones;			/* number of known delegated zones */
{
	register int n;

	if (sameword(domain, "."))
		return(TRUE);

	if (samedomain(name, domain, TRUE))
		return(FALSE);

	if (!indomain(name, domain, TRUE))
		return(TRUE);

	for (n = 0; n < nzones; n++)
		if (indomain(name, zone[n], TRUE))
			return(TRUE);

	return(FALSE);
}

/*
** MATCHLABELS -- Determine number of matching domain name labels
** --------------------------------------------------------------
**
**	Returns:
**		Number of shared trailing components in both names.
**
**	Note. This routine is currently used only to compare nameserver
**	names in the RHS of NS records, so there is no need to check
**	for embedded quoted dots.
*/

int
matchlabels(name, domain)
input char *name;			/* domain name to check */
input char *domain;			/* domain name to compare against */
{
	register int i, j;
	int matched = 0;

	i = strlength(name);
	j = strlength(domain);

	while (--i >= 0 && --j >= 0)
	{
		if (lowercase(name[i]) != lowercase(domain[j]))
			break;
		if (domain[j] == '.')
			matched++;
		else if (j == 0 && (i == 0 || name[i-1] == '.'))
			matched++;
	}

	return(matched);
}

/*
** PR_DOMAIN -- Convert domain name according to printing options
** --------------------------------------------------------------
**
**	Returns:
**		Pointer to new domain name, if conversion was done.
**		Pointer to original name, if no conversion necessary.
*/

char *
pr_domain(name, listing)
input char *name;			/* domain name to be printed */
input bool listing;			/* set if this is a zone listing */
{
	char *newname;			/* converted domain name */

/*
 * Print reverse nsap.int name in forward notation, unless prohibited.
 */
	if (revnsap && !dotprint)
	{
		newname = pr_nsap(name);
		if (newname != name)
			return(newname);
	}

/*
 * Print domain names with trailing dot if necessary.
 */
	if (listing || dotprint)
	{
		newname = pr_dotname(name);
		if (newname != name)
			return(newname);
	}

/*
 * No conversion was required, use original name.
 */
	return(name);
}

/*
** PR_DOTNAME -- Return domain name with trailing dot
** --------------------------------------------------
**
**	Returns:
**		Pointer to new domain name, if dot was added.
**		Pointer to original name, if dot was already present.
*/

char *
pr_dotname(name)
input char *name;			/* domain name to append to */
{
	static char buf[MAXDNAME+2];	/* buffer to store new domain name */
	register int n;

	n = strlength(name);
	if (n > 0 && name[n-1] == '.')
		return(name);

	if (n > MAXDNAME)
		n = MAXDNAME;

#ifdef obsolete
	(void) sprintf(buf, "%.*s.", MAXDNAME, name);
#endif
	bcopy(name, buf, n);
	buf[n] = '.';
	buf[n+1] = '\0';
	return(buf);
}

/*
** PR_NSAP -- Convert reverse nsap.int to dotted forward notation
** --------------------------------------------------------------
**
**	Returns:
**		Pointer to new dotted nsap, if converted.
**		Pointer to original name otherwise.
*/

char *
pr_nsap(name)
input char *name;			/* potential reverse nsap.int name */
{
	static char buf[3*MAXNSAP+1];
	register char *p;
	register int n;
	register int i;

	/* must begin with single hex digits separated by dots */
	for (i = 0; is_xdigit(name[i]) && name[i+1] == '.'; i += 2)
		continue;

	/* must have an even number of hex digits */ 
	if (i == 0 || (i % 4) != 0)
		return(name);

	/* but not too many */
	if (i > 4*MAXNSAP)
		return(name);

	/* must end in the appropriate root domain */
	if (!sameword(&name[i], NSAP_ROOT))
		return(name);

	for (p = buf, n = 0; i >= 4; i -= 4, n++)
	{
		*p++ = name[i-2];
		*p++ = name[i-4];

		/* add dots for readability */
		if ((n % 2) == 0 && (i - 4) > 0)
			*p++ = '.';
	}
	*p = '\0';

	return(buf);
}

/*
** PR_TYPE -- Return name of resource record type
** ----------------------------------------------
**
**	Returns:
**		Pointer to name of resource record type.
**
**	Note.	All possible (even obsolete) types are recognized.
*/

char *
pr_type(type)
input int type;				/* resource record type */
{
	static char buf[30];		/* sufficient for 64-bit values */

	switch (type)
	{
		/* standard types */

	    case T_A:       return("A");	/* internet address */
	    case T_NS:      return("NS");	/* authoritative server */
	    case T_MD:      return("MD");	/* mail destination */
	    case T_MF:      return("MF");	/* mail forwarder */
	    case T_CNAME:   return("CNAME");	/* canonical name */
	    case T_SOA:     return("SOA");	/* start of auth zone */
	    case T_MB:      return("MB");	/* mailbox domain name */
	    case T_MG:      return("MG");	/* mail group member */
	    case T_MR:      return("MR");	/* mail rename name */
	    case T_NULL:    return("NULL");	/* null resource record */
	    case T_WKS:     return("WKS");	/* well known service */
	    case T_PTR:     return("PTR");	/* domain name pointer */
	    case T_HINFO:   return("HINFO");	/* host information */
	    case T_MINFO:   return("MINFO");	/* mailbox information */
	    case T_MX:      return("MX");	/* mail routing info */
	    case T_TXT:     return("TXT");	/* descriptive text */

		/* new types */

	    case T_RP:      return("RP");	/* responsible person */
	    case T_AFSDB:   return("AFSDB");	/* afs database location */
	    case T_X25:     return("X25");	/* x25 address */
	    case T_ISDN:    return("ISDN");	/* isdn address */
	    case T_RT:      return("RT");	/* route through host */
	    case T_NSAP:    return("NSAP");	/* nsap address */
	    case T_NSAPPTR: return("NSAP-PTR");	/* nsap pointer */
	    case T_SIG:     return("SIG");	/* security signature */
	    case T_KEY:     return("KEY");	/* security key */
	    case T_PX:      return("PX");	/* rfc822 - x400 mapping */
	    case T_GPOS:    return("GPOS");	/* geographical position */
	    case T_AAAA:    return("AAAA");	/* ip v6 address */
	    case T_LOC:     return("LOC");	/* geographical location */
	    case T_NXT:     return("NXT");	/* next valid name */
	    case T_EID:     return("EID");	/* endpoint identifier */
	    case T_NIMLOC:  return("NIMLOC");	/* nimrod locator */
	    case T_SRV:     return("SRV");	/* service info */
	    case T_ATMA:    return("ATMA");	/* atm address */
	    case T_NAPTR:   return("NAPTR");	/* naming authority urn */

		/* nonstandard types */

	    case T_UINFO:   return("UINFO");	/* user information */
	    case T_UID:     return("UID");	/* user ident */
	    case T_GID:     return("GID");	/* group ident */
	    case T_UNSPEC:  return("UNSPEC");	/* unspecified binary data */

		/* filters */

	    case T_IXFR:    return("IXFR");	/* incremental zone transfer */
	    case T_AXFR:    return("AXFR");	/* zone transfer */
	    case T_MAILB:   return("MAILB");	/* matches MB/MR/MG/MINFO */
	    case T_MAILA:   return("MAILA");	/* matches MD/MF */
	    case T_ANY:     return("ANY");	/* matches any type */

	    case T_NONE:    return("resource");	/* not yet determined */
	}

	/* unknown type */
	(void) sprintf(buf, "%d", type);
	return(buf);
}

/*
** PR_CLASS -- Return name of resource record class
** ------------------------------------------------
**
**	Returns:
**		Pointer to name of resource record class.
*/

char *
pr_class(class)
input int class;			/* resource record class */
{
	static char buf[30];		/* sufficient for 64-bit values */

	switch (class)
	{
	    case C_IN:      return("IN");	/* internet */
	    case C_CSNET:   return("CS");	/* csnet */
	    case C_CHAOS:   return("CH");	/* chaosnet */
	    case C_HS:      return("HS");	/* hesiod */
	    case C_ANY:     return("ANY");	/* any class */
	}

	/* unknown class */
	(void) sprintf(buf, "%d", class);
	return(buf);
}

/*
** EXPAND_NAME -- Expand compressed domain name in a resource record
** -----------------------------------------------------------------
**
**	Returns:
**		Number of bytes advanced in answer buffer.
**		-1 if there was a format error.
**
**	It is assumed that the specified buffer is of a fixed size
**	MAXDNAME+1 that should be sufficient to store the data.
*/

int
expand_name(name, type, cp, msg, eom, namebuf)
input char *name;			/* name of resource record */
input int type;				/* type of resource record */
input u_char *cp;			/* current position in answer buf */
input u_char *msg, *eom;		/* begin and end of answer buf */
output char *namebuf;			/* location of buf to expand name in */
{
	register int n;

	n = dn_expand(msg, eom, cp, (nbuf_t *)namebuf, MAXDNAME);
	if (n < 0)
	{
		pr_error("expand error in %s record for %s, offset %s",
			pr_type(type), name, itoa(cp - msg));
		h_errno = NO_RECOVERY;
		return(-1);
	}

	/* should not be necessary, but who knows */
	namebuf[MAXDNAME] = '\0';

	/* change root to single dot */
	if (namebuf[0] == '\0')
	{
		namebuf[0] = '.';
		namebuf[1] = '\0';
	}

	return(n);
}

/*
** CHECK_SIZE -- Check whether resource record is of sufficient length
** -------------------------------------------------------------------
**
**	Returns:
**		Requested size if current record is long enough.
**		-1 if current record does not have this many bytes.
**
**	Note that HINFO records are very often incomplete since only
**	one of the two data fields has been filled in and the second
**	field is missing. So we generate only a warning message.
*/

int
check_size(name, type, cp, msg, eor, size)
input char *name;			/* name of resource record */
input int type;				/* type of resource record */
input u_char *cp;			/* current position in answer buf */
input u_char *msg;			/* begin of answer buf */
input u_char *eor;			/* predicted position of next record */
input int size;				/* required record size remaining */
{
	if (cp + size > eor)
	{
		if (type != T_HINFO)
			pr_error("incomplete %s record for %s, offset %s",
				pr_type(type), name, itoa(cp - msg));
		else
			pr_warning("incomplete %s record for %s",
				pr_type(type), name);
		h_errno = NO_RECOVERY;
		return(-1);
	}

	return(size);
}

/*
** VALID_NAME -- Check whether domain name contains invalid characters
** -------------------------------------------------------------------
**
**	Returns:
**		TRUE if the name is valid.
**		FALSE otherwise.
**
**	The total size of a compound name should not exceed MAXDNAME.
**	We assume that this is true. Its individual components between
**	dots should not be longer than 64. This is not checked here.
**
**	Only alphanumeric characters and dash '-' may be used (dash
**	only in the middle). We only check the individual characters.
**	Strictly speaking, this restriction is only for ``host names''.
**	The underscore is illegal, at least not recommended, but is
**	so abundant that it requires special processing.
**
**	If the domain name represents a mailbox specification, the
**	first label up to the first (unquoted) dot is the local part
**	of a mail address, which should adhere to the RFC 822 specs.
**	This first dot takes the place of the RFC 822 '@' sign.
**
**	The label '*' can in principle be used anywhere to indicate
**	wildcarding. It is valid only in the LHS resource record name,
**	in definitions in zone files only as the first component.
**	Used primarily in wildcard MX record definitions.
**
**	Note. This routine is much too liberal.
*/

char *specials = ".()<>@,;:\\\"[]";	/* RFC 822 specials */

bool
valid_name(name, wildcard, localpart, underscore)
input char *name;			/* domain name to check */
input bool wildcard;			/* set if wildcard is allowed */
input bool localpart;			/* set if this is a mailbox spec */
input bool underscore;			/* set if underscores are allowed */
{
	bool backslash = FALSE;
	bool quoting = FALSE;
	register char *p;
	register char c;

	for (p = name; (c = *p) != '\0'; p++)
	{
		/* special check for local part in mailbox */
		if (localpart)
		{
			if (backslash)
				backslash = FALSE;	/* escape this char */
			else if (c == '\\')
				backslash = TRUE;	/* escape next char */
			else if (c == '"')
				quoting = !quoting;	/* start/stop quoting */
			else if (quoting)
				continue;		/* allow quoted chars */
			else if (c == '.')
				localpart = FALSE;	/* instead of '@' */
			else if (c == '@')
				return(FALSE);		/* should be '.' */
			else if (in_string(specials, c))
				return(FALSE);		/* must be escaped */
			else if (is_space(c))
				return(FALSE);		/* must be escaped */
			continue;
		}

		/* basic character set */
		if (is_alnum(c) || (c == '-'))
			continue;

		/* start of a new component */
		if (c == '.')
			continue;

		/* allow '*' for use in wildcard names */
		if ((c == '*') && (p == name && p[1] == '.') && wildcard)
			continue;

		/* ignore underscore in certain circumstances */
		if ((c == '_') && underscore && !illegal)
			continue;

		/* silently allowed widespread exceptions */
		if (illegal && in_string(illegal, c))
			continue;

		return(FALSE);
	}

	/* must be beyond the local part in a mailbox */
	if (localpart)
		return(FALSE);

	return(TRUE);
}

/* 
** CANONICAL -- Check whether domain name is a canonical host name
** ---------------------------------------------------------------
**
**	Returns:
**		Nonzero if the name is definitely not canonical.
**		0 if it is canonical, or if it remains undecided.
*/

int
canonical(name)
input char *name;			/* the domain name to check */
{
	struct hostent *hp;
	int status;
	int save_errno;
	int save_herrno;
	
/*
 * Preserve state when querying, to avoid clobbering current values.
 */
	save_errno = errno;
	save_herrno = h_errno;

	hp = geth_byname(name);
	status = h_errno;

	errno = save_errno;
	h_errno = save_herrno;

/*
 * Indicate negative result only after definitive lookup failures.
 */
	if (hp == NULL)
	{
		/* authoritative denial -- not existing or no A record */
		if (status == NO_DATA || status == HOST_NOT_FOUND)
			return(status);

		/* nameserver failure -- still undecided, assume ok */
		return(0);
	}

/*
 * The given name exists and there is an associated A record.
 * The name of this A record should be the name we queried about.
 * If this is not the case we probably supplied a CNAME.
 */
	status = sameword(hp->h_name, name) ? 0 : HOST_NOT_CANON;
	return(status);
}

/* 
** MAPREVERSE -- Check whether address maps back to given domain
** -------------------------------------------------------------
**
**	Returns:
**		NULL if address could definitively not be mapped.
**		Given name if the address maps back properly, or
**		in case of transient nameserver failures.
**		Reverse name if it differs from the given name.
*/

char *
mapreverse(name, inaddr)
input char *name;			/* domain name of A record */
input struct in_addr inaddr;		/* address of A record to check */
{
	struct hostent *hp;
	int status;
	int save_errno;
	int save_herrno;
	
/*
 * Preserve state when querying, to avoid clobbering current values.
 */
	save_errno = errno;
	save_herrno = h_errno;

	hp = geth_byaddr((char *)&inaddr, INADDRSZ, AF_INET);
	status = h_errno;

	errno = save_errno;
	h_errno = save_herrno;

/*
 * Indicate negative result only after definitive lookup failures.
 */
	if (hp == NULL)
	{
		/* authoritative denial -- not existing or no PTR record */
		if (status == NO_DATA || status == HOST_NOT_FOUND)
			return(NULL);

		/* nameserver failure -- still undecided, assume ok */
		return(name);
	}

/*
 * Indicate whether the reverse mapping yields the given name.
 */
	return(sameword(hp->h_name, name) ? name : hp->h_name);
}

/* 
** COMPARE_NAME -- Compare two names wrt alphabetical order
** --------------------------------------------------------
**
**	Returns:
**		Value of case-insensitive comparison.
*/

int
compare_name(a, b)
input const ptr_t *a;			/* first name */
input const ptr_t *b;			/* second name */
{
	return(strcasecmp(*(char **)a, *(char **)b));
}

/*
** XALLOC -- Allocate or reallocate additional memory
** --------------------------------------------------
**
**	Returns:
**		Pointer to (re)allocated buffer space.
**		Aborts if the requested memory could not be obtained.
*/

ptr_t *
xalloc(buf, size)
register ptr_t *buf;			/* current start of buffer space */
input siz_t size;			/* number of bytes to allocate */
{
	if (buf == NULL)
		buf = malloc(size);
	else
		buf = realloc(buf, size);

	if (buf == NULL)
	{
		errmsg("Out of memory");
		exit(EX_OSERR);
	}

	return(buf);
}

/*
** ITOA -- Convert value to decimal integer ascii string
** -----------------------------------------------------
**
**	Returns:
**		Pointer to string.
*/

char *
itoa(n)
input int n;				/* value to convert */
{
	static char buf[30];		/* sufficient for 64-bit values */

	(void) sprintf(buf, "%d", n);
	return(buf);
}

/*
** UTOA -- Convert value to unsigned decimal ascii string
** ------------------------------------------------------
**
**	Returns:
**		Pointer to string.
*/

char *
utoa(n)
input int n;				/* value to convert */
{
	static char buf[30];		/* sufficient for 64-bit values */

	(void) sprintf(buf, "%u", (unsigned)n);
	return(buf);
}

/*
** XTOA -- Convert value to hexadecimal ascii string
** -------------------------------------------------
**
**	Returns:
**		Pointer to string.
*/

char *
xtoa(n)
input int n;				/* value to convert */
{
	static char buf[17];		/* sufficient for 64-bit values */

	(void) sprintf(buf, "%X", (unsigned)n);
	return(buf);
}

/*
** STOA -- Extract partial ascii string, escape if necessary
** ---------------------------------------------------------
**
**	Returns:
**		Pointer to string.
*/

char *
stoa(cp, size, escape)
input u_char *cp;			/* current position in answer buf */
input int size;				/* number of bytes to extract */
input bool escape;			/* escape special characters if set */
{
	static char buf[2*MAXDLEN+1];
	register char *p;
	register char c;
	register int i;

	if (size > MAXDLEN)
		size = MAXDLEN;

#ifdef obsolete
	if (size > 0)
		(void) sprintf(buf, "%.*s", size, (char *)cp);
	else
		(void) sprintf(buf, "%s", "");
#endif

	for (p = buf, i = 0; i < size; i++)
	{
		c = *cp++;
		if (escape && (c == '\n' || c == '\\' || c == '"'))
			*p++ = '\\';
		*p++ = c;
	}
	*p = '\0';

	return(buf);
}

/*
** BASE_NTOA -- Convert binary data to base64 ascii
** ------------------------------------------------
**
**	Returns:
**		Pointer to string.
**
**	This routine is used to convert encoded keys and signatures
**	in T_KEY and T_SIG resource records.
*/

char b64tab[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

char *
base_ntoa(cp, size)
input u_char *cp;			/* current position in answer buf */
input int size;				/* number of bytes to extract */
{
	static char buf[MAXB64SIZE+1];
	register char *p;
	int c1, c2, c3, c4;

	if (size > MAXMD5SIZE)
		size = MAXMD5SIZE;

	for (p = buf; size > 2; cp += 3, size -= 3)
	{
		c1 = (((int)cp[0] >> 2) & 0x3f);
		c2 = (((int)cp[0] & 0x03) << 4) + (((int)cp[1] >> 4) & 0x0f);
		c3 = (((int)cp[1] & 0x0f) << 2) + (((int)cp[2] >> 6) & 0x03);
		c4 =  ((int)cp[2] & 0x3f);

		*p++ = b64tab[c1];
		*p++ = b64tab[c2];
		*p++ = b64tab[c3];
		*p++ = b64tab[c4];
	}
    
	if (size == 2)
	{
		c1 = (((int)cp[0] >> 2) & 0x3f);
		c2 = (((int)cp[0] & 0x03) << 4) + (((int)cp[1] >> 4) & 0x0f);
		c3 = (((int)cp[1] & 0x0f) << 2);

		*p++ = b64tab[c1];
		*p++ = b64tab[c2];
		*p++ = b64tab[c3];
		*p++ = '=';
	}
	else if (size == 1)
	{
		c1 = (((int)cp[0] >> 2) & 0x3f);
		c2 = (((int)cp[0] & 0x03) << 4);

		*p++ = b64tab[c1];
		*p++ = b64tab[c2];
		*p++ = '=';
		*p++ = '=';
	}
	*p = '\0';

	return(buf);
}

/*
** NSAP_NTOA -- Convert binary nsap address to ascii
** -------------------------------------------------
**
**	Returns:
**		Pointer to string.
**
**	As per RFC 1637 an nsap address is encoded in binary form
**	in the resource record. It was unclear from RFC 1348 how
**	the encoding should be. RFC 1629 defines an upper bound
**	of 20 bytes to the size of a binary nsap address.
*/

char *
nsap_ntoa(cp, size)
input u_char *cp;			/* current position in answer buf */
input int size;				/* number of bytes to extract */
{
	static char buf[3*MAXNSAP+1];
	register char *p;
	register int n;
	register int i;

	if (size > MAXNSAP)
		size = MAXNSAP;

	for (p = buf, i = 0; i < size; i++, cp++)
	{
		n = ((int)(*cp) >> 4) & 0x0f;
		*p++ = hexdigit(n);
		n = ((int)(*cp) >> 0) & 0x0f;
		*p++ = hexdigit(n);

		/* add dots for readability */
		if ((i % 2) == 0 && (i + 1) < size)
			*p++ = '.';
	}
	*p = '\0';

	return(buf);
}

/*
** IPNG_NTOA -- Convert binary ip v6 address to ascii
** --------------------------------------------------
**
**	Returns:
**		Pointer to string.
**
**	As per RFC 1886 an ip v6 address is encoded in binary form
**	in the resource record. The size is fixed.
*/

char *
ipng_ntoa(cp)
input u_char *cp;			/* current position in answer buf */
{
	static char buf[5*(IPNGSIZE/2)+1];
	register char *p;
	register int n;
	register int i;

	for (p = buf, i = 0; i < IPNGSIZE/2; i++)
	{
		n = _getshort(cp);
		cp += INT16SZ;

		(void) sprintf(p, ":%X", n);
		p += strlength(p);
	}
	*p = '\0';

	return(buf + 1);
}

/*
** PR_DATE -- Produce printable version of a clock value
** -----------------------------------------------------
**
**	Returns:
**		Pointer to string.
**
**	The value is a standard absolute clock value.
*/

char *
pr_date(value)
input int value;			/* the clock value to be converted */
{
	static char buf[sizeof("YYYYMMDDHHMMSS")+1];
	time_t clocktime = value;
	struct tm *t;
	
	t = gmtime(&clocktime);
	t->tm_year += 1900;
	t->tm_mon += 1;

	(void) sprintf(buf, "%04d%02d%02d%02d%02d%02d",
		t->tm_year, t->tm_mon, t->tm_mday,
		t->tm_hour, t->tm_min, t->tm_sec);

	return(buf);
}

/*
** PR_TIME -- Produce printable version of a time interval
** -------------------------------------------------------
**
**	Returns:
**		Pointer to a string version of interval.
**
**	The value is a time interval expressed in seconds.
*/

char *
pr_time(value, brief)
input int value;			/* the interval to be converted */
input bool brief;			/* use brief format if set */
{
	static char buf[256];
	register char *p = buf;
	int week, days, hour, mins, secs;

	/* special cases */
	if (value < 0)
		return("negative");
	if ((value == 0) && !brief)
		return("zero seconds");

/*
 * Decode the components.
 */
	secs = value % 60; value /= 60;
	mins = value % 60; value /= 60;
	hour = value % 24; value /= 24;
	days = value;

	if (!brief)
	{
		days = value % 7; value /= 7;
		week = value;
	}

/*
 * Now turn it into a sexy form.
 */
	if (brief)
	{
		if (days > 0)
		{
			(void) sprintf(p, "%d+", days);
			p += strlength(p);
		}

		(void) sprintf(p, "%02d:%02d:%02d", hour, mins, secs);
		return(buf);
	}

	if (week > 0)
	{
		(void) sprintf(p, ", %d week%s", week, plural(week));
		p += strlength(p);
	}

	if (days > 0)
	{
		(void) sprintf(p, ", %d day%s", days, plural(days));
		p += strlength(p);
	}

	if (hour > 0)
	{
		(void) sprintf(p, ", %d hour%s", hour, plural(hour));
		p += strlength(p);
	}

	if (mins > 0)
	{
		(void) sprintf(p, ", %d minute%s", mins, plural(mins));
		p += strlength(p);
	}

	if (secs > 0)
	{
		(void) sprintf(p, ", %d second%s", secs, plural(secs));
		/* p += strlength(p); */
	}

	return(buf + 2);
}

/*
** PR_SPHERICAL -- Produce printable version of a spherical location
** -----------------------------------------------------------------
**
**	Returns:
**		Pointer to a string version of location.
**
**	The value is a spherical location (latitude or longitude)
**	expressed in thousandths of a second of arc.
**	The value 2^31 represents zero (equator or prime meridian).
*/

char *
pr_spherical(value, pos, neg)
input int value;			/* the location to be converted */
input char *pos;			/* suffix if value positive */
input char *neg;			/* suffix if value negative */
{
	static char buf[256];
	register char *p = buf;
	char *direction;
	int degrees, minutes, seconds, fracsec;

/*
 * Normalize.
 */
	value -= (int)((unsigned)1 << 31);

	direction = pos;
	if (value < 0)
	{
		direction = neg;
		value = -value;
	}

/*
 * Decode the components.
 */
	fracsec = value % 1000; value /= 1000;
	seconds = value % 60;   value /= 60;
	minutes = value % 60;   value /= 60;
	degrees = value;

/*
 * Construct output string.
 */
	(void) sprintf(p, "%d", degrees);
	p += strlength(p);

	if (minutes > 0 || seconds > 0 || fracsec > 0)
	{
		(void) sprintf(p, " %02d", minutes);
		p += strlength(p);
	}

	if (seconds > 0 || fracsec > 0)
	{
		(void) sprintf(p, " %02d", seconds);
		p += strlength(p);
	}

	if (fracsec > 0)
	{
		(void) sprintf(p, ".%03d", fracsec);
		p += strlength(p);
	}

	(void) sprintf(p, " %s", direction);

#ifdef obsolete
	(void) sprintf(buf, "%d %02d %02d.%03d %s",
		degrees, minutes, seconds, fracsec, direction);
#endif
	return(buf);
}

/*
** PR_VERTICAL -- Produce printable version of a vertical location
** ---------------------------------------------------------------
**
**	Returns:
**		Pointer to a string version of location.
**
**	The value is an altitude expressed in centimeters, starting
**	from a base 100000 meters below the GPS reference spheroid.
**	This allows for the actual range [-10000000 .. 4293967296].
*/

char *
pr_vertical(value, pos, neg)
input int value;			/* the location to be converted */
input char *pos;			/* prefix if value positive */
input char *neg;			/* prefix if value negative */
{
	static char buf[256];
	register char *p = buf;
	char *direction;
	int meters, centimeters;
	unsigned int altitude;
	unsigned int reference;

/*
 * Normalize.
 */
	altitude = value;
	reference = 100000*100;

	if (altitude < reference)
	{
		direction = neg;
		altitude = reference - altitude;
	}
	else
	{
		direction = pos;
		altitude = altitude - reference;
	}

/*
 * Decode the components.
 */
	centimeters = altitude % 100; altitude /= 100;
	meters = altitude;

/*
 * Construct output string.
 */
	(void) sprintf(p, "%s%d", direction, meters);
	p += strlength(p);

	if (centimeters > 0)
		(void) sprintf(p, ".%02d", centimeters);

#ifdef obsolete
	(void) sprintf(buf, "%s%d.%02d", direction, meters, centimeters);
#endif
	return(buf);
}

/*
** PR_PRECISION -- Produce printable version of a location precision
** -----------------------------------------------------------------
**
**	Returns:
**		Pointer to a string version of precision.
**
**	The value is a precision expressed in centimeters, encoded
**	as 4-bit mantissa and 4-bit power of 10 (each ranging 0-9).
*/

unsigned int poweroften[10] =
{1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};

char *
pr_precision(value)
input int value;			/* the precision to be converted */
{
	static char buf[256];
	register char *p = buf;
	int meters, centimeters;
	unsigned int precision;
	register int mantissa;
	register int exponent;

/*
 * Normalize.
 */
	mantissa = ((value >> 4) & 0x0f) % 10;
	exponent = ((value >> 0) & 0x0f) % 10;
	precision = mantissa * poweroften[exponent];

/*
 * Decode the components.
 */
	centimeters = precision % 100; precision /= 100;
	meters = precision;

/*
 * Construct output string.
 */
	(void) sprintf(p, "%d", meters);
	p += strlength(p);

	if (centimeters > 0)
		(void) sprintf(p, ".%02d", centimeters);

#ifdef obsolete
	(void) sprintf(buf, "%d.%02d", meters, centimeters);
#endif
	return(buf);
}
