/*
 * Copyright (c) 1986 Regents of the University of California
 * All Rights Reserved
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of California at Berkeley. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided as is'' without express or implied warranty.
 */

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

#include <stdio.h>
#include <sys/types.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <resolv.h>
#include <sys/param.h>
#include <string.h>
#include <ctype.h>

extern int h_errno;

#define NUMMX 50

#define  SUCCESS		0
#define  TIME_OUT		-1
#define  NO_INFO 		-2
#define  ERROR 			-3
#define  NONAUTH 		-4

#define NAME_LEN 256

/*
 * We define these T_* here so we can probe nameservers that support these
 * even if we don't have them defined in arpa/nameser.h.  Take them out if
 * they clash with your local mods.
 */
#ifndef T_TXT
# define T_TXT 16
#endif
#ifndef T_MP
# define T_MP 240
#endif
#ifndef T_UNAME
# define T_UNAME 110
#endif

#ifndef NO_DATA
# define NO_DATA NO_ADDRESS
#endif
#ifndef C_HS
# define C_HS 4
#endif

int sockFD;
FILE *filePtr;
char *DecodeError();
char *reqtype;

/* struct state orig; */
extern struct state _res;
extern char *_res_resultcodes[];
static char *cname = NULL;
extern char *inet_ntoa();
extern u_long inet_addr();
int gettype;
int verbose = 0;
int list = 0;
int server_specified = 0;

/* For getopt() */
extern int optind;
extern char *optarg;

typedef struct {
	int t_type;
	char *t_name;
	char *t_desc;
} QueryType;

static QueryType *query_type;
static QueryType qtypes[] = {
	{T_A, "a", "a host address"},
	{T_NS, "ns", "an authoritative server"},
	{T_MD, "md", "a mail destination"},
	{T_MF, "mf", "a mail forwarder"},
	{T_CNAME, "cname", "a canonical name"},
	{T_SOA, "soa", "a start of authority zone"},
	{T_MB, "mb", "a mailbox domain name"},
	{T_MG, "mg", "a mail group member"},
	{T_MR, "mr", "a mail rename name"},
	{T_NULL, "null", "a null resource record"},
	{T_WKS, "wks", "a well known service"},
	{T_PTR, "ptr", "a domain name pointer"},
	{T_HINFO, "hinfo", "host information"},
	{T_MINFO, "minfo", "mailbox information"},
	{T_MX, "mx", "mail routing information"},
#ifdef T_TXT	/* added in newer releases of bind */
	{T_TXT, "txt", "text strings"},
#endif
	{T_UINFO, "uinfo", "user (finger) information"},
	{T_UID, "uid", "a user ID"},
	{T_GID, "gid", "a group ID"},
	{T_UNSPEC, "unspec", "Unspecified format (binary data)"},
#ifdef T_UNAME	/* UToronto hack */
	{T_UNAME, "uname", "a uucp name for outgoing uucp"},
#endif
#ifdef T_MP	/* UToronto hack */
	{T_MP, "mp", "a mail path kludge"},
#endif
	{T_AXFR, "axfr", "a transfer zone of authority"},
	{T_MAILB, "mailb", "transfer mailbox records"},
	{T_MAILA, "maila", "transfer mail agent records"},
	{T_ANY, "any", "a wildcard match"},
	{T_ANY, "*", "a wildcard match"},
	/* do not add any types after this */
	{0, NULL, NULL}		/* sentinel -- t_type <= 0 */
};

usage()
{
	fprintf(stderr, 
"Usage: host [-l] [-w] [-v] [-r] [-d] [-t querytype] [-a] [-s server] host ...\n\
  -l for a complete listing of a domain (zone transfer)\n\
  -w to wait forever until reply\n\
  -v for verbose output\n\
  -r to disable recursive processing\n\
  -d to turn on debugging output\n\
  -t querytype to look for a specific type of information\n\
  -a is equivalent to '-v -t *'\n\
  -s server to use a different nameserver\n");
	exit(1);
}


main(c, v)
	char **v;
{
	int optch;
	unsigned addr;
	register struct hostent *hp;
	register char *s = NULL;
	register waitmode = 0;
	char *oldcname;
	int ncnames;
	int ret = 0;

	res_init();
	_res.retrans = 5;
	_res.options |= RES_USEVC;

	if (c < 2) {
		usage();
	}
	while((optch = getopt(c, v, "lwrdvt:as:")) != EOF) {
		/* optarg has the current argument if the option was followed by ':'*/
		switch (optch) {
		case 'w':
			_res.retry = 1;
			_res.retrans = 15;
			waitmode = 1;
			break;
		case 'r':
			_res.options &= ~RES_RECURSE;
			break;
		case 'd':
			_res.options |= RES_DEBUG;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'l':
			list = 1;
			break;
		case 't':
			gettype = parsetype(optarg);
			break;
		case 'a':
			verbose = 1;
			gettype = T_ANY;
			break;
		case 's':
			s = optarg;
			server_specified++;
			break;
		case '?':
			usage();
			break;
		}
	}
	if (optind >= c) {
		usage();
	}
	if (s) {
		addr = inet_addr(s);
		if (addr == (unsigned long) -1) {
		  hp = gethostbyname(s);
		  if (hp == NULL) {
		    fprintf(stderr,"Error in looking up server name:\n");
		    hperror(h_errno);
		    exit(1);
		  }
		  _res.nsaddr.sin_addr = *(struct in_addr *)hp->h_addr;
		  printf("Using domain server:\n");
		  printanswer(hp);
		}
		else {
		  _res.nsaddr.sin_family = AF_INET;
		  _res.nsaddr.sin_addr.s_addr = addr;
		  _res.nsaddr.sin_port = htons(NAMESERVER_PORT);
		  printf("Using domain server %s:\n",
			 inet_ntoa(_res.nsaddr.sin_addr));
		}
	}
	for(; optind < c; optind++) {
	  if (strcmp (v[optind], ".") == 0)
	    addr = (unsigned long) -1;
	  else
	    addr = inet_addr(v[optind]);
	  hp = NULL;
	  h_errno = TRY_AGAIN;

          if (list) {
	    ret += ListHosts(v[optind], gettype ? gettype : T_A);
	    continue;
	  }
	  oldcname = NULL;
	  ncnames = 5;
	  while (hp == NULL && h_errno == TRY_AGAIN) {
	    if (addr == (unsigned long) -1) {
		cname = NULL;
		if (oldcname == NULL)
		  hp = (struct hostent *)gethostinfo(v[optind]);
		else
		  hp = (struct hostent *)gethostinfo(oldcname);
		if (cname) {
		  if (ncnames-- == 0) {
		    printf("Too many cnames.  Possible loop.\n");
		    ret = 1;
		    break;
		  }
		  oldcname = cname;
		  hp = NULL;
		  h_errno = TRY_AGAIN;
		  continue;
		}
	    }
	    else {
	      hp = gethostbyaddr((char *) &addr, 4, AF_INET);
	      if (hp)
		printanswer(hp);
	    }
	    if (!waitmode)
	      break;
	  }

	  if (hp == NULL) {
	    hperror(h_errno);
	    ret = 1;
	  }
	}
	exit(ret);
}

parsetype(s)
	char *s;
{
	QueryType *qtp;

	if (isdigit(*s)) {
		int t = atoi(s);
		for(qtp = qtypes; qtp->t_type > 0; qtp++) {
			if (qtp->t_type == t)
				break;
		}
	} else {
		for(qtp = qtypes; qtp->t_type > 0; qtp++) {
			if (strcasecmp(s, qtp->t_name) == 0)
				break;
		}
	}
	if(qtp->t_type > 0) {
		query_type = qtp;
		return qtp->t_type;
	}
	fprintf(stderr, "Invalid query type: %s\n", s);
	fprintf(stderr, "Supported query types are:\n");
	for(qtp = qtypes; qtp->t_type > 0; qtp++) {
		fprintf(stderr, " %s\t%s\n", qtp->t_name, qtp->t_desc);
	}
	fprintf(stderr, "or any positive integer\n");
	exit(2);
	/*NOTREACHED*/
}

printanswer(hp)
	register struct hostent *hp;
{
	register char **cp;
#ifdef	h_addr
	register long **hptr;
#endif

	printf("Name: %s\n", hp->h_name);
	printf("Address:");
#ifndef	h_addr
	printf(" %s", inet_ntoa(*(struct in_addr *)hp->h_addr));
#else
	for (hptr = (long **)hp->h_addr_list; *hptr; hptr++)
	  printf(" %s", inet_ntoa(*(struct in_addr *)*hptr));
#endif
	printf("\nAliases:");
	for (cp = hp->h_aliases; cp && *cp && **cp; cp++)
		printf(" %s", *cp);
	printf("\n\n");
}

hperror(errno) 
int errno;
{
switch(errno) {
	case HOST_NOT_FOUND:
		fprintf(stderr,"Host not found.\n");
		break;
	case TRY_AGAIN:
		fprintf(stderr,"Host not found, try again.\n");
		break;
	case NO_RECOVERY:
		fprintf(stderr,"No recovery, Host not found.\n");
		break;
	case NO_ADDRESS:
		fprintf(stderr,"There is an entry for this host, but it doesn't have %s.\n",
			query_type ? query_type->t_desc :
				"either an address or a mail exchanger");
		break;
	}
}


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

/* static char hostbuf[BUFSIZ+1]; */


#if 0
char *hostalias();
#endif

gethostinfo(name)
	char *name;
{
	register char *cp, **domain;
	int n;
	int hp;
#ifdef	MAXDS
	int nDomain;
#endif

	if (strcmp(name, ".") == 0)
		return(getdomaininfo(name, NULL));
	for (cp = name, n = 0; *cp; cp++)
		if (*cp == '.')
			n++;
	if ((n && cp[-1] == '.') || (_res.options & RES_DEFNAMES) == 0) {
		if (cp[-1] == '.')
			cp[-1] = 0;
		_res.options &= ~RES_DEFNAMES;
		hp = getdomaininfo(name, (char *)NULL);
		if (cp[-1] == 0)
			cp[-1] = '.';
		return (hp);
	}
#if 0
	if (n == 0 && (cp = hostalias(name)))
		return (getdomaininfo(cp, (char *)NULL));
#endif
#ifdef	MAXDS
	for (nDomain = 0;
	     _res.defdname_list[nDomain][0] != 0;
	     nDomain++) {
	    for (domain = _res.dnsrch_list[nDomain]; *domain; domain++) {
	        if (verbose)
		    printf("Trying domain \"%s\"\n", *domain);
		hp = getdomaininfo(name, *domain);
		if (hp)
			return (hp);
	    }
	}
#else
	for (domain = _res.dnsrch; *domain; domain++) {
	  if (verbose)
	    printf("Trying domain \"%s\"\n", *domain);
	  hp = getdomaininfo(name, *domain);
	  if (hp)
	    return (hp);
	}
#endif
	if (h_errno == NO_RECOVERY ||
	    (_res.options & RES_DNSRCH) == 0)
		return (NULL);
	if (verbose)
	    printf("Trying null domain\n");
	return (getdomaininfo(name, (char *)NULL));
}

getdomaininfo(name, domain)
	char *name, *domain;
{
  int val1, val2;

  if (gettype)
    return getinfo(name, domain, gettype);
  else {
    val1 = getinfo(name, domain, T_A);
    if (cname || verbose)
      return val1;
    val2 = getinfo(name, domain, T_MX);
    return val1 || val2;
  }
}

getinfo(name, domain, type)
	char *name, *domain;
{

	HEADER *hp;
	char *eom, /* *bp, */ *cp;
	querybuf buf, answer;
	int n, /* nmx, */ ancount, nscount, arcount, qdcount /*, buflen */;
	char host[2*MAXDNAME+2];

	if (domain == NULL)
		(void)sprintf(host, "%.*s", MAXDNAME, name);
	else
		(void)sprintf(host, "%.*s.%.*s", MAXDNAME, name, MAXDNAME, domain);

	n = res_mkquery(QUERY, host, C_IN, type, (char *)NULL, 0, NULL,
		(char *)&buf, sizeof(buf));
	if (n < 0) {
		if (_res.options & RES_DEBUG)
			printf("res_mkquery failed\n");
		h_errno = NO_RECOVERY;
		return(0);
	}
	n = res_send((char *)&buf, n, (char *)&answer, sizeof(answer));
	if (n < 0) {
		if (_res.options & RES_DEBUG)
			printf("res_send failed\n");
		h_errno = TRY_AGAIN;
		return (0);
	}
	eom = (char *)&answer + n;
	return(printinfo(&answer, eom, T_ANY, 0));
      }

printinfo(answer, eom, filter, isls)
	querybuf *answer;
	char *eom;
        int filter;
        int isls;
{
	HEADER *hp;
	char *bp, *cp;
	int n, n1, i, j, nmx, ancount, nscount, arcount, qdcount, buflen;
	u_short pref, class;

	/*
	 * find first satisfactory answer
	 */
	hp = (HEADER *) answer;
	ancount = ntohs(hp->ancount);
	qdcount = ntohs(hp->qdcount);
	nscount = ntohs(hp->nscount);
	arcount = ntohs(hp->arcount);
	if (_res.options & RES_DEBUG || (verbose && isls == 0))
		printf("rcode = %d (%s), ancount=%d\n", 
		       hp->rcode, DecodeError(hp->rcode), ancount);
	if (hp->rcode != NOERROR || (ancount+nscount+arcount) == 0) {
		switch (hp->rcode) {
			case NXDOMAIN:
				/* Check if it's an authoritive answer */
				if (hp->aa) {
					h_errno = HOST_NOT_FOUND;
					return(0);
				} else {
					h_errno = TRY_AGAIN;
					return(0);
				}
			case SERVFAIL:
				h_errno = TRY_AGAIN;
				return(0);
#ifdef OLDJEEVES
			/*
			 * Jeeves (TOPS-20 server) still does not
			 * support MX records.  For the time being,
			 * we must accept FORMERRs as the same as
			 * NOERROR.
			 */
			case FORMERR:
#endif OLDJEEVES
			case NOERROR:
/* TpB - set a return error for this case. NO_DATA */
				h_errno = NO_DATA;
				return(0); /* was 1,but now indicates exception */
#ifndef OLDJEEVES
			case FORMERR:
#endif OLDJEEVES
			case NOTIMP:
			case REFUSED:
				h_errno = NO_RECOVERY;
				return(0);
		}
		return (0);
	}
	/* bp = hostbuf; */
	/* nmx = 0; */
	/* buflen = sizeof(hostbuf); */
	cp = (char *)answer + sizeof(HEADER);
	if (qdcount) {
		cp += dn_skipname(cp,eom) + QFIXEDSZ;
		while (--qdcount > 0)
			cp += dn_skipname(cp,eom) + QFIXEDSZ;
	}
	if (ancount) {
	  if (!hp->aa)
	    if (verbose && isls == 0)
	      printf("The following answer is not authoritative:\n");
	  while (--ancount >= 0 && cp && cp < eom) {
	    cp = p_rr(cp, (char *) answer, stdout, filter);
/*
 * When we ask for address and there is a CNAME, it seems to return
 * both the CNAME and the address.  Since we trace down the CNAME
 * chain ourselves, we don't really want to print the address at
 * this point.
 */
	    if (cname && ! verbose)
	      return (1);
	  }
	}
	if (! verbose)
	  return (1);
	if (nscount) {
	  printf("For authoritative answers, see:\n");
	  while (--nscount >= 0 && cp && cp < eom) {
	    cp = p_rr(cp, (char *) answer, stdout, filter);
	  }
	}
	if (arcount) {
	  printf("Additional information:\n");
	  while (--arcount >= 0 && cp && cp < eom) {
	    cp = p_rr(cp, (char *) answer, stdout, filter);
	  }
	}
	return(1);
 }

static char cnamebuf[MAXDNAME];

/*
 * Print resource record fields in human readable form.
 */
char *
p_rr(cp, msg, file, filter)
	char *cp, *msg;
	FILE *file;
        int filter;
{
	int type, class, dlen, n, c, proto, ttl;
	struct in_addr inaddr;
	char *cp1;
	struct protoent *protop;
	struct servent *servp;
	char punc;
	int doprint;
	char name[MAXDNAME];

	if ((cp = p_cdname(cp, msg, name, sizeof(name))) == NULL)
		return (NULL);			/* compression error */

	type = _getshort(cp);
	cp += sizeof(u_short);

	class = _getshort(cp);
	cp += sizeof(u_short);

	ttl = _getlong(cp);
	cp += sizeof(u_long);

	if (filter == type || filter == T_ANY ||
	    (filter == T_A && (type == T_PTR || type == T_NS)))
	  doprint = 1;
	else
	  doprint = 0;

	if (doprint)
	  if (verbose)
	    fprintf(file,"%s\t%d%s\t%s",
		    name, ttl, p_class(class), p_type(type));
	  else
	    fprintf(file,"%s%s %s",name, p_class(class), p_type(type));
	if (verbose)
	  punc = '\t';
	else
	  punc = ' ';

	dlen = _getshort(cp);
	cp += sizeof(u_short);
	cp1 = cp;
	/*
	 * Print type specific data, if appropriate
	 */
	switch (type) {
	case T_A:
		switch (class) {
		case C_IN:
			bcopy(cp, (char *)&inaddr, sizeof(inaddr));
			if (dlen == 4) {
			        if (doprint)
				  fprintf(file,"%c%s", punc,
					inet_ntoa(inaddr));
				cp += dlen;
			} else if (dlen == 7) {
			        if (doprint) {
				  fprintf(file,"%c%s", punc,
					  inet_ntoa(inaddr));
				  fprintf(file,", protocol = %d", cp[4]);
				  fprintf(file,", port = %d",
					  (cp[5] << 8) + cp[6]);
				}
				cp += dlen;
			}
			break;
		}
		break;
	case T_CNAME:
		if (dn_expand(msg, msg + 512, cp, cnamebuf, 
			      sizeof(cnamebuf)) >= 0)
		  cname = cnamebuf;				
	case T_MB:
#ifdef OLDRR
	case T_MD:
	case T_MF:
#endif /* OLDRR */
	case T_MG:
	case T_MR:
	case T_NS:
	case T_PTR:
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file,"%c%s",punc, name);
		break;

	case T_HINFO:
		if (n = *cp++) {
			if (doprint)
			  fprintf(file,"%c%.*s", punc, n, cp);
			cp += n;
		}
		if (n = *cp++) {
			if (doprint)
			  fprintf(file,"%c%.*s", punc, n, cp);
			cp += n;
		}
		break;

	case T_SOA:
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file,"\t%s", name);
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file," %s", name);
		if (doprint)
		  fprintf(file,"(\n\t\t\t%ld\t;serial (version)", _getlong(cp));
		cp += sizeof(u_long);
		if (doprint)
		  fprintf(file,"\n\t\t\t%ld\t;refresh period", _getlong(cp));
		cp += sizeof(u_long);
		if (doprint)
		  fprintf(file,"\n\t\t\t%ld\t;retry refresh this often", _getlong(cp));
		cp += sizeof(u_long);
		if (doprint)
		  fprintf(file,"\n\t\t\t%ld\t;expiration period", _getlong(cp));
		cp += sizeof(u_long);
		if (doprint)
		  fprintf(file,"\n\t\t\t%ld\t;minimum TTL\n\t\t\t)", _getlong(cp));
		cp += sizeof(u_long);
		break;

	case T_MX:
		if (doprint)
		  if (verbose)
		    fprintf(file,"\t%ld ",_getshort(cp));
		  else
		    fprintf(file," ");
		cp += sizeof(u_short);
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file, "%s", name);
		break;

	case T_MINFO:
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file,"%c%s",punc, name);
		cp = p_cdname(cp, msg, name, sizeof(name));
		if (doprint)
		  fprintf(file," %s", name);
		break;

#ifdef T_MP
	case T_MP:
		fprintf(file,"%c%.*s", punc, dlen, cp);
		cp += dlen;
		break;
#endif
#ifdef	T_TXT
	case T_TXT:	/* Roy start */
		fprintf(file,"%c%.*s", punc, dlen, cp);
		cp += dlen;
		break;
		/* Roy end */
#endif
		
	case T_UINFO:
		if (doprint)
		  fprintf(file,"%c%s", punc, cp);
		cp += dlen;
		break;

	case T_UID:
	case T_GID:
		if (dlen == 4) {
			if (doprint)
			  fprintf(file,"%c%ld", punc, _getlong(cp));
			cp += sizeof(int);
		}
		break;

	case T_WKS:
		if (dlen < sizeof(u_long) + 1)
			break;
		bcopy(cp, (char *)&inaddr, sizeof(inaddr));
		cp += sizeof(u_long);
		proto = *cp++;
		protop = getprotobynumber(proto);
		if (doprint)
		  if (protop)
		    fprintf(file,"%c%s %s", punc,
			    inet_ntoa(inaddr), protop->p_name);
		  else
		    fprintf(file,"%c%s %d", punc,
			    inet_ntoa(inaddr), proto);

		n = 0;
		while (cp < cp1 + dlen) {
			c = *cp++;
			do {
 				if (c & 0200) {
				  servp = NULL;
				  if (protop)
				    servp = getservbyport(htons(n),
							  protop->p_name);
				  if (doprint)
				    if (servp)
				      fprintf(file, " %s", servp->s_name);
				    else
				      fprintf(file, " %d", n);
				}
 				c <<= 1;
			} while (++n & 07);
		}
		break;

	default:
		if (doprint)
		  fprintf(file,"%c???", punc);
		cp += dlen;
	}
	if (cp != cp1 + dlen)
		fprintf(file,"packet size error (%#x != %#x)\n", cp, cp1+dlen);
	if (doprint)
	  fprintf(file,"\n");
	return (cp);
}

static	char nbuf[20];

/*
 * Return a string for the type. !! Should put this in the qtypes table.
 */
char *
p_type(type)
	int type;
{
	switch (type) {
	case T_A:
		return(verbose? "A" : "has address");
	case T_NS:		/* authoritative server */
		return("NS");
#ifdef OLDRR
	case T_MD:		/* mail destination */
		return("MD");
	case T_MF:		/* mail forwarder */
		return("MF");
#endif /* OLDRR */
	case T_CNAME:		/* connonical name */
		return(verbose? "CNAME" : "is a nickname for");
	case T_SOA:		/* start of authority zone */
		return("SOA");
	case T_MB:		/* mailbox domain name */
		return("MB");
	case T_MG:		/* mail group member */
		return("MG");
	case T_MX:		/* mail routing info */
		return(verbose? "MX" : "mail is handled by");
#ifdef	T_MP
	case T_MP:
		return(verbose? "MP" : "route mail via");
#endif
#ifdef	T_TXT
	/* Roy start */
	case T_TXT:		/* TXT - descriptive info */
		return(verbose? "TXT" : "descriptive text");
	/* Roy end */
#endif
	case T_MR:		/* mail rename name */
		return("MR");
	case T_NULL:		/* null resource record */
		return("NULL");
	case T_WKS:		/* well known service */
		return("WKS");
	case T_PTR:		/* domain name pointer */
		return("PTR");
	case T_HINFO:		/* host information */
		return("HINFO");
	case T_MINFO:		/* mailbox information */
		return("MINFO");
	case T_AXFR:		/* zone transfer */
		return("AXFR");
	case T_MAILB:		/* mail box */
		return("MAILB");
	case T_MAILA:		/* mail address */
		return("MAILA");
	case T_ANY:		/* matches any type */
		return("ANY");
	case T_UINFO:
		return("UINFO");
	case T_UID:
		return("UID");
	case T_GID:
		return("GID");
	default:
		(void) sprintf(nbuf, "%d", type);
		return nbuf;
	}
}

/*
 * Return a mnemonic for class
 */
char *
p_class(class)
	int class;
{

	switch (class) {
	case C_IN:		/* internet class */
		return(verbose? " IN" : "");
	case C_ANY:		/* matches any class */
		return(" ANY");
	default:
		(void) sprintf(nbuf, " %d", class);
		return nbuf;
	}
}

char *
p_cdname(cp, msg, name, namelen)
	char *cp, *msg;
        char *name;
        int namelen;
{
	int n;

	if ((n = dn_expand(msg, msg + 512, cp, name, namelen - 2)) < 0)
		return (NULL);
	if (name[0] == '\0') {
		name[0] = '.';
		name[1] = '\0';
	}
	return (cp + n);
}

char *_res_opcodes[] = {
	"QUERY",
	"IQUERY",
	"CQUERYM",
	"CQUERYU",
	"4",
	"5",
	"6",
	"7",
	"8",
	"UPDATEA",
	"UPDATED",
	"UPDATEDA",
	"UPDATEM",
	"UPDATEMA",
	"ZONEINIT",
	"ZONEREF",
};

char *_res_resultcodes[] = {
	"NOERROR",
	"FORMERR",
	"SERVFAIL",
	"NXDOMAIN",
	"NOTIMP",
	"REFUSED",
	"6",
	"7",
	"8",
	"9",
	"10",
	"11",
	"12",
	"13",
	"14",
	"NOCHANGE",
};

/*ARGSUSED*/
p_query(msg)
	char *msg;
{
}

/*
 ******************************************************************************
 *
 *  ListHosts --
 *
 *	Requests the name server to do a zone transfer so we
 *	find out what hosts it knows about.
 *
 *  Results:
 *	SUCCESS		the listing was successful.
 *	ERROR		the server could not be contacted because 
 *			a socket could not be obtained or an error
 *			occured while receiving, or the output file
 *			could not be opened.
 *
 ******************************************************************************
 */

int
ListHosts(namePtr, queryType)
    char *namePtr;
    int  queryType;  /* e.g. T_A */
{
	querybuf 		buf, answer;
	struct sockaddr_in 	sin;
	HEADER 			*headerPtr;

	int 			msglen;
	int 			amtToRead;
	int 			numRead;
	int 			i;
	int 			numAnswers = 0;
	int 			result;
	int 			soacnt = 0;
	u_short 		len;
	int			dlen;
	int			type;
	int			nscount;
	char 			*cp, *nmp;
	char 			name[NAME_LEN];
	char 			dname[2][NAME_LEN];
	char 			domain[NAME_LEN];
/* names and addresses of name servers to try */
#define NUMNS 8
	char			nsname[NUMNS][NAME_LEN];
	int			nshaveaddr[NUMNS];
#define IPADDRSIZE 4
#define NUMNSADDR 16
	char	 		nsipaddr[NUMNSADDR][IPADDRSIZE];
	int			numns;
	int			numnsaddr;
	int			thisns;
	struct hostent		*hp;
	enum {
	    NO_ERRORS, 
	    ERR_READING_LEN, 
	    ERR_READING_MSG,
	    ERR_PRINTING,
	} error = NO_ERRORS;
	extern int errno;


	if (server_specified) {
	  bcopy(&_res.nsaddr.sin_addr, nsipaddr[0], IPADDRSIZE);
	  numnsaddr = 1;
	}
	else {

/*
 * First we have to find out where to look.  This needs a NS query,
 * possibly followed by looking up addresses for some of the names.
 */

	msglen = res_mkquery(QUERY, namePtr, C_IN, T_NS,
				(char *)0, 0, (char *)0, 
				(char *) &buf, sizeof(buf));

	if (msglen < 0) {
		printf("res_mkquery failed\n");
		return (ERROR);
	}

	msglen = res_send((char *)&buf,msglen,(char *)&answer, sizeof(answer));
	
	if (msglen < 0) {
		printf("Unable to get to nameserver -- try again later\n");
		return (ERROR);
	}
	if (_res.options & RES_DEBUG || verbose)
		printf("rcode = %d (%s), ancount=%d\n", 
		       answer.qb1.rcode, DecodeError(answer.qb1.rcode),
		       ntohs(answer.qb1.ancount));

/*
 * Analyze response to our NS lookup
 */

	nscount = ntohs(answer.qb1.ancount) + ntohs(answer.qb1.nscount) +
		  ntohs(answer.qb1.arcount);

	if (answer.qb1.rcode != NOERROR || nscount == 0) {
		switch (answer.qb1.rcode) {
			case NXDOMAIN:
				/* Check if it's an authoritive answer */
				if (answer.qb1.aa) {
					printf("No such domain\n");
				} else {
					printf("Unable to get information about domain -- try again later.\n");
				}
				break;
			case SERVFAIL:
				printf("Unable to get information about that domain -- try again later.\n");
				break;
			case NOERROR:
				printf("That domain exists, but seems to be a leaf node.\n");
				break;
			case FORMERR:
			case NOTIMP:
			case REFUSED:
				printf("Unrecoverable error looking up domain name.\n");
				break;
		}
		return (0);
	}

	cp = answer.qb2 + sizeof(HEADER);
	if (ntohs(answer.qb1.qdcount) > 0)
	  cp += dn_skipname(cp, answer.qb2 + msglen) + QFIXEDSZ;

	numns = 0;
	numnsaddr = 0;

/*
 * Look at response from NS lookup for NS and A records.
 */

	for (;nscount; nscount--) {
	  cp += dn_expand(answer.qb2, answer.qb2 + msglen, cp,
			  domain, sizeof(domain));
	  type = _getshort(cp);
	  cp += sizeof(u_short) + sizeof(u_short) + sizeof(u_long);
	  dlen = _getshort(cp);
	  cp += sizeof(u_short);
	  if (type == T_NS) {
	    if (dn_expand(answer.qb2, answer.qb2 + msglen, cp, 
			  name, sizeof(name)) >= 0) {
	      if (numns < NUMNS) {
		for (i = 0; i < numns; i++)
		  if (strcasecmp(nsname[i], name) == 0)
		    break;  /* duplicate */
		if (i >= numns) {
		  strncpy(nsname[numns], name, sizeof(name));
		  nshaveaddr[numns] = 0;
		  numns++;
		}
	      }
	    }
	  }
	  else if (type == T_A) {
	    if (numnsaddr < NUMNSADDR)
	      for (i = 0; i < numns; i++) {
		if (strcasecmp(nsname[i], domain) == 0) {
		  nshaveaddr[i]++;
		  bcopy(cp, nsipaddr[numnsaddr],IPADDRSIZE);
		  numnsaddr++;
		  break;
		}
	      }
	  }
	  cp += dlen;
	}

/*
 * Usually we'll get addresses for all the servers in the additional
 * info section.  But in case we don't, look up their addresses.
 */

	for (i = 0; i < numns; i++) {
	  if (! nshaveaddr[i]) {
	    register long **hptr;
	    int numaddrs = 0;

	    hp = gethostbyname(nsname[i]);
	    if (hp) {
#ifndef h_addr
	      hptr = (long **) &hp->h_addr;
#else
	      for (hptr = (long **)hp->h_addr_list; *hptr; hptr++)
#endif
		if (numnsaddr < NUMNSADDR) {
		  bcopy((char *)*hptr, nsipaddr[numnsaddr],IPADDRSIZE);
		  numnsaddr++;
		  numaddrs++;
		}
	    }
	    if (_res.options & RES_DEBUG || verbose)
	      printf("Found %d addresses for %s by extra query\n",
		     numaddrs, nsname[i]);
	  }
	  else
	    if (_res.options & RES_DEBUG || verbose)
	      printf("Found %d addresses for %s\n",
		     nshaveaddr[i], nsname[i]);
	}
        }
/*
 * Now nsipaddr has numnsaddr addresses for name servers that
 * serve the requested domain.  Now try to find one that will
 * accept a zone transfer.
 */

	thisns = 0;

again:

	numAnswers = 0;
	soacnt = 0;

	/*
	 *  Create a query packet for the requested domain name.
	 *
	 */
	msglen = res_mkquery(QUERY, namePtr, C_IN, T_AXFR,
				(char *)0, 0, (char *)0, 
				(char *) &buf, sizeof(buf));
	if (msglen < 0) {
	    if (_res.options & RES_DEBUG) {
		fprintf(stderr, "ListHosts: Res_mkquery failed\n");
	    }
	    return (ERROR);
	}

	bzero((char *)&sin, sizeof(sin));
	sin.sin_family	= AF_INET;
	sin.sin_port	=  htons(NAMESERVER_PORT);

	/*
	 *  Set up a virtual circuit to the server.
	 */

	for (;thisns < numnsaddr; thisns++) {
	  if ((sockFD = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	    perror("ListHosts(socket)");
	    return(ERROR);
	  }
	  bcopy(nsipaddr[thisns], &sin.sin_addr, IPADDRSIZE);
	  if (_res.options & RES_DEBUG || verbose)
	    printf("Trying %s\n", inet_ntoa(sin.sin_addr));
	  if (connect(sockFD, &sin, sizeof(sin)) >= 0)
	    break;
	  i = errno;		/* Save errno across close() */
	  (void) close(sockFD);
	  errno = i;
	  sockFD = -1;
	}	
	if (thisns >= numnsaddr) {
	  perror("ListHosts(connect)");
	  return(ERROR);
	}

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

        len = htons(msglen);

        if (write(sockFD, (char *)&len, sizeof(len)) != sizeof(len) ||
            write(sockFD, (char *) &buf, msglen) != msglen) {
		perror("ListHosts(write)");
		(void) close(sockFD);
		sockFD = -1;
		return(ERROR);
	}

	filePtr = stdout;

	while (1) {

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

	    cp = (char *) &buf;
	    amtToRead = sizeof(u_short);
	    while(amtToRead > 0 && (numRead = read(sockFD, cp, amtToRead)) > 0){
		cp 	  += numRead;
		amtToRead -= numRead;
	    }
	    if (numRead <= 0) {
		error = ERR_READING_LEN;
		break;
	    }	

	    if ((len = htons(*(u_short *)&buf)) == 0) {
		break;	/* nothing left to read */
	    }

	    /*
	     * Read the response.
	     */

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

	    result = printinfo(&buf, cp, queryType, 1);
	    if (! result) {
		error = ERR_PRINTING;
		break;
	    }
	    numAnswers++;
	    cp = buf.qb2 + sizeof(HEADER);
	    if (ntohs(buf.qb1.qdcount) > 0)
		cp += dn_skipname(cp, buf.qb2 + len) + QFIXEDSZ;

	    i = buf.qb1.rcode;
	    if (i != NOERROR || ntohs(buf.qb1.ancount) == 0) {
	      if ((thisns+1) < numnsaddr &&
		  (i == SERVFAIL || i == NOTIMP || i == REFUSED)) {
		if (_res.options & RES_DEBUG || verbose)
		  printf("Server failed, trying next server: %s\n",
			 i != NOERROR ? 
			 DecodeError(i) : "Premature end of data");
		(void) close(sockFD);
		sockFD = -1;
		thisns++;
		goto again;
	      }
	      printf("Server failed: %s\n",
		     i != NOERROR ? DecodeError(i) : "Premature end of data");
	      break;
	    }

	    nmp = cp;
	    cp += dn_skipname(cp, (u_char *)&buf + len);
	    if ((_getshort(cp) == T_SOA)) {
		dn_expand(buf.qb2, buf.qb2 + len, nmp, dname[soacnt],
			sizeof(dname[0]));
	        if (soacnt) {
		    if (strcmp(dname[0], dname[1]) == 0)
			break;
		} else
		    soacnt++;
	    }
        }

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

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

	    case ERR_READING_LEN:
		return(ERROR);

	    case ERR_PRINTING:
		fprintf(stderr,"*** Error during listing of %s: %s\n", 
				namePtr, DecodeError(result));
		return(result);

	    case ERR_READING_MSG:
		headerPtr = (HEADER *) &buf;
		fprintf(stderr,"ListHosts: error receiving zone transfer:\n");
		fprintf(stderr,
	       "  result: %s, answers = %d, authority = %d, additional = %d\n", 
		    	_res_resultcodes[headerPtr->rcode], 
		    	ntohs(headerPtr->ancount), ntohs(headerPtr->nscount), 
			ntohs(headerPtr->arcount));
		return(ERROR);
	    default:
		return(ERROR);
	}
}

char *
DecodeError(result)
    int result;
{
	switch(result) {
	    case NOERROR: 	return("Success"); break;
	    case FORMERR:	return("Format error"); break;
	    case SERVFAIL:	return("Server failed"); break;
	    case NXDOMAIN:	return("Non-existent domain"); break;
	    case NOTIMP:	return("Not implemented"); break;
	    case REFUSED:	return("Query refused"); break;
	    case NOCHANGE:	return("No change"); break;
	    case NO_INFO: 	return("No information"); break;
	    case ERROR: 	return("Unspecified error"); break;
	    case TIME_OUT: 	return("Timed out"); break;
	    case NONAUTH: 	return("Non-authoritative answer"); break;
	    default: 		break;
	}
	return("BAD ERROR VALUE"); 
}
