/*	$NetBSD: list.c,v 1.1.2.1 1999/12/04 16:57:25 he Exp $	*/

/*
 * Copyright (c) 1985, 1989
 *    The Regents of the University of California.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies, and that
 * the name of Digital Equipment Corporation not be used in advertising or
 * publicity pertaining to distribution of the document or software without
 * specific, written prior permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#ifndef lint
static const char sccsid[] = "@(#)list.c	5.23 (Berkeley) 3/21/91";
static const char rcsid[] = "Id: list.c,v 8.21 1999/10/15 19:49:08 vixie Exp";
#endif /* not lint */

/*
 *******************************************************************************
 *
 *  list.c --
 *
 *	Routines to obtain info from name and finger servers.
 *
 *	Adapted from 4.3BSD BIND ns_init.c and from finger.c.
 *
 *******************************************************************************
 */

#include "port_before.h"

#include <sys/param.h>
#include <sys/socket.h>

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

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

#include "port_after.h"

#include "res.h"

extern char *pager;

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

extern HostInfo	*defaultPtr;
extern HostInfo	curHostInfo;
extern int	curHostValid;
extern int	queryType;
extern int	queryClass;

static int sockFD = -1;
int ListSubr();

/*
 *  During a listing to a file, hash marks are printed
 *  every HASH_SIZE records.
 */

#define HASH_SIZE 50


/*
 *******************************************************************************
 *
 *  ListHosts --
 *  ListHostsByType --
 *
 *	Requests the name server to do a zone transfer so we
 *	find out what hosts it knows about.
 *
 *	For ListHosts, there are five types of output:
 *	- Internet addresses (default)
 *	- cpu type and operating system (-h option)
 *	- canonical and alias names  (-a option)
 *	- well-known service names  (-s option)
 *	- ALL records (-d option)
 *	ListHostsByType prints records of the default type or of a speicific
 *	type.
 *
 *	To see all types of information sorted by name, do the following:
 *	  ls -d domain.edu > file
 *	  view file
 *
 *  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.
 *
 *******************************************************************************
 */

void
ListHostsByType(char *string, int putToFile) {
	char *namePtr, name[NAME_LEN], option[NAME_LEN];
	int i, j, qtype, result;

	/*
	 * Parse the command line. It maybe of the form "ls -t domain"
	 * or "ls -t type domain".
	 */

	/* simulate sscanf(string, " ls -t %s %s", option, name) */
	i = matchString(" ls -t ", string);
	if (i > 0) {
		j = pickString(string + i, option, sizeof option);
		if (j > 0) {
			j = pickString(string + i + j, name, sizeof name);
			if (j > 0)
				i = 2;
			else
				i = 1;
		} else {
			i = 0;
		}
	}
			
	if (putToFile && i == 2 && name[0] == '>')
		i--;
	if (i == 2) {
		qtype = StringToType(option, -1, stderr);
		if (qtype == -1)
			return;
		namePtr = name;
	} else if (i == 1) {
		namePtr = option;
		qtype = queryType;
	} else {
		fprintf(stderr, "*** ls: invalid request %s\n", string);
		return;
	}
	result = ListSubr(qtype, namePtr, putToFile ? string : NULL);
	if (result != SUCCESS)
		fprintf(stderr, "*** Can't list domain %s: %s\n", 
			namePtr, DecodeError(result));
}

void
ListHosts(char *string, int putToFile) {
	char *namePtr, name[NAME_LEN], option[NAME_LEN];
	int i, j, qtype, result;

	/*
	 *  Parse the command line. It maybe of the form "ls domain",
	 *  "ls -X domain".
	 */

	/* simulate i = sscanf(string, " ls %s %s", option, name) */
	i = matchString(" ls ", string);
	if (i > 0) {
		j = pickString(string + i, option, sizeof option);
		if (j > 0) {
			j = pickString(string + i + j, name, sizeof name);
			if (j > 0)
				i = 2;
			else
				i = 1;
		} else {
			i = 0;
		}
	}
	
	if (putToFile && i == 2 && name[0] == '>')
		i--;
	if (i == 2) {
		if (strcmp("-a", option) == 0)
			qtype = T_CNAME;
		else if (strcmp("-h", option) == 0)
			qtype = T_HINFO;
		else if (strcmp("-m", option) == 0)
			qtype = T_MX;
		else if (strcmp("-p", option) == 0)
			qtype = T_PX;
		else if (strcmp("-s", option) == 0)
			qtype = T_WKS;
		else if (strcmp("-d", option) == 0)
			qtype = T_ANY;
		else if (strcmp("-n", option) == 0)
			qtype = T_NAPTR;
		else
			qtype = T_A;
		namePtr = name;
	} else if (i == 1) {
		namePtr = option;
		qtype = T_A;
	} else {
		fprintf(stderr, "*** ls: invalid request %s\n",string);
		return;
	}
	result = ListSubr(qtype, namePtr, putToFile ? string : NULL);
	if (result != SUCCESS)
		fprintf(stderr, "*** Can't list domain %s: %s\n", 
			namePtr, DecodeError(result));
}

int
ListSubr(int qtype, char *domain, char *cmd) {
	static u_char *answer = NULL;
	static int answerLen = 0;

	ns_msg handle;
	querybuf buf;
	struct sockaddr_in sin;
	HEADER *headerPtr;
	int msglen, amtToRead, numRead, n, count, soacnt;
	u_int len;
	int numAnswers = 0;
	int numRecords = 0;
	u_char tmp[INT16SZ], *cp;
	char soaname[2][NAME_LEN], file[PATH_MAX];
	enum { NO_ERRORS, ERR_READING_LEN, ERR_READING_MSG, ERR_PRINTING }
		error = NO_ERRORS;

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

	memset(&sin, 0, sizeof sin);
	sin.sin_family = AF_INET;
	sin.sin_port = htons(nsport);

	/*
	 * Check to see if we have the address of the server or the
	 * address of a server who knows about this domain.
	 *
	 * For now, just use the first address in the list. XXX.
	 */

	if (defaultPtr->addrList != NULL)
		sin.sin_addr = *(struct in_addr *) defaultPtr->addrList[0];
	else
		sin.sin_addr = *(struct in_addr *)
			defaultPtr->servers[0]->addrList[0];

	/*
	 *  Set up a virtual circuit to the server.
	 */
	sockFD = socket(AF_INET, SOCK_STREAM, 0);
	if (sockFD < 0) {
		perror("ls: socket");
		return (ERROR);
	}
	if (connect(sockFD, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		int e;

		if (errno == ECONNREFUSED)
			e = NO_RESPONSE;
		else {
			perror("ls: connect");
			e = ERROR;
		}
		(void) close(sockFD);
		sockFD = -1;
		return (e);
	}

	/*
	 * Send length & message for zone transfer
	 */
	ns_put16(msglen, tmp);
        if (write(sockFD, (char *)tmp, INT16SZ) != INT16SZ ||
            write(sockFD, (char *)buf.qb2, msglen) != msglen) {
		perror("ls: write");
		(void) close(sockFD);
		sockFD = -1;
		return(ERROR);
	}

	fprintf(stdout,"[%s]\n", (defaultPtr->addrList != NULL)
		? defaultPtr->name : defaultPtr->servers[0]->name);

	if (cmd == NULL) {
		filePtr = stdout;
	} else {
		filePtr = OpenFile(cmd, file, sizeof file);
		if (filePtr == NULL) {
			fprintf(stderr, "*** Can't open %s for writing\n",
				file);
			(void) close(sockFD);
			sockFD = -1;
			return (ERROR);
		}
		fprintf(filePtr, "> %s\n", cmd);
		fprintf(filePtr, "[%s]\n", (defaultPtr->addrList != NULL)
			? defaultPtr->name : defaultPtr->servers[0]->name);
	}

	soacnt = 0;
	while (soacnt < 2) {
		/*
		 * Read the length of the response.
		 */

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

		len = ns_get16(tmp);
		if (len == 0)
			break;	/* nothing left to read */

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

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

		if (ns_initparse(answer, cp - answer, &handle) < 0) {
			perror("ns_initparse");
			error = ERR_PRINTING;
			break;
		}
		if (ns_msg_getflag(handle, ns_f_rcode) != ns_r_noerror ||
		    ns_msg_count(handle, ns_s_an) == 0) {
			/* Signalled protocol error, or empty message. */
			error = ERR_PRINTING;
			break;
		}

		for (;;) {
			static char origin[NS_MAXDNAME], name_ctx[NS_MAXDNAME];
			const char *name;
			char buf[2048];   /* XXX need to malloc/realloc. */
			ns_rr rr;

			if (ns_parserr(&handle, ns_s_an, -1, &rr)) {
				if (errno != ENODEV) {
					perror("ns_parserr");
					error = ERR_PRINTING;
				}
				break;
			}
			name = ns_rr_name(rr);
			if (origin[0] == '\0' && name[0] != '\0') {
				fprintf(filePtr, "$ORIGIN %s.\n", name);
				strcpy(origin, name);
			}
			if (qtype == T_ANY || ns_rr_type(rr) == qtype) {
				if (ns_sprintrr(&handle, &rr, name_ctx, origin,
						buf, sizeof buf) < 0) {
					perror("ns_sprintrr");
					error = ERR_PRINTING;
					break;
				}
				strcpy(name_ctx, name);
				numRecords++;
				fputs(buf, filePtr);
				fputc('\n', filePtr);
			}
			if (ns_rr_type(rr) == T_SOA) {
				strcpy(soaname[soacnt], name);
				if (soacnt == 0)
					soacnt = 1;
				else if (ns_samename(soaname[0],
						     soaname[1]) == 1) {
					soacnt = 2;
					/* This means we're finished.
					 * But we've to reset origin and
					 * name_ctx now !  */
					origin[0] = name_ctx[0] ='\0';
				}
			}
		}
		if (error != NO_ERRORS)
			break;
		numAnswers++;
		if (cmd != NULL && ((numAnswers % HASH_SIZE) == 0)) {
			fprintf(stdout, "#");
			fflush(stdout);
		}
	}

	if (cmd != NULL)
		fprintf(stdout, "%sReceived %d answer%s (%d record%s).\n",
			(numAnswers >= HASH_SIZE) ? "\n" : "",
			numAnswers, (numAnswers != 1) ? "s" : "",
			numRecords, (numRecords != 1) ? "s" : "");

	(void) close(sockFD);
	sockFD = -1;
	if (cmd != NULL && filePtr != NULL) {
		fclose(filePtr);
		filePtr = NULL;
	}

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

	case ERR_READING_LEN:
		return (ERROR);

	case ERR_PRINTING:
		return (ERROR);

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

/*
 *******************************************************************************
 *
 *  ViewList --
 *
 *	A hack to view the output of the ls command in sorted
 *	order using more.
 *
 *******************************************************************************
 */

void
ViewList(char *string) {
    char file[PATH_MAX];
    char command[PATH_MAX];
    int i, j;
    char soafile[PATH_MAX];

    /* sscanf(string, " view %s", file); */
    i = matchString(" view ", string);
    if (i > 0) {
	    j = pickString(string + i, file, sizeof file);
	    if (j == 0) {
		fprintf(stderr, "*** invalid file name: %s\n", string + i);
		return ;
	    }
    }

    if ( !mktemp(strcpy(soafile,"/var/tmp/nslookup_tmpXXXXXX"))) {
	fprintf(stderr, "*** cannot create temp file\n");
	return ;
	}
    (void)sprintf(command, "sed '\
/^$/,${\
/@/,$d\
}\
/^[^	]/{\
h\
s/^\\([^	]*	*\\).*/\\1/\
x\
}\
1,/^$/{\
w %s\
d\
}\
/^	/{\
G\
s/^	*//\
s/^\\(.*\\)\\n\\(.*\\)$/\\2\\1/\
}' %s | sort | (cat %s -; rm %s) | %s",
		  soafile, file, soafile, soafile, pager);
    system(command);
}

/*
 *******************************************************************************
 *
 *   Finger --
 *
 *	Connects with the finger server for the current host
 *	to request info on the specified person (long form)
 *	who is on the system (short form).
 *
 *  Results:
 *	SUCCESS		the finger server was contacted.
 *	ERROR		the server could not be contacted because
 *			a socket could not be obtained or connected
 *			to or the service could not be found.
 *
 *******************************************************************************
 */

Finger(string, putToFile)
    char *string;
    int  putToFile;
{
	struct servent		*sp;
	struct sockaddr_in	sin;
	FILE		*f;
	int		c;
	int		lastc;
	char			name[NAME_LEN];
	char			file[PATH_MAX];
	int		i;

	/*
	 *  We need a valid current host info to get an inet address.
	 */
	if (!curHostValid) {
	    fprintf(stderr, "Finger: no current host defined.\n");
	    return (ERROR);
	}

	/* simulate: sscanf("finger %s") ; */

	i = matchString(" finger ", string);
	if (i > 0) {
		i = pickString(string + i, name, sizeof name);
		if (i > 0) {
			i = 1 ;
		}
		/* note that if the argument to the finger command is
		   bigger than sizeof name it will be treated as if there
		   was no argument. */
	}
	
	if (i == 1) {
	    if (putToFile && (name[0] == '>')) {
		name[0] = '\0';
	    }
	} else {
	    name[0] = '\0';
	}

	sp = getservbyname("finger", "tcp");
	if (sp == 0) {
	    fprintf(stderr, "Finger: unknown service\n");
	    return (ERROR);
	}

	memset(&sin, 0, sizeof sin);
	sin.sin_family	= curHostInfo.addrType;
	sin.sin_port	= sp->s_port;
	memcpy(&sin.sin_addr, curHostInfo.addrList[0], curHostInfo.addrLen);

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

	sockFD = socket(curHostInfo.addrType, SOCK_STREAM, 0);
	if (sockFD < 0) {
	    fflush(stdout);
	    perror("finger: socket");
	    return (ERROR);
	}

	if (connect(sockFD, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
	    fflush(stdout);
	    perror("finger: connect");
	    close(sockFD);
	    sockFD = -1;
	    return (ERROR);
	}

	if (!putToFile) {
	    filePtr = stdout;
	} else {
	    filePtr = OpenFile(string, file, sizeof file);
	    if (filePtr == NULL) {
		fprintf(stderr, "*** Can't open %s for writing\n", file);
		close(sockFD);
		sockFD = -1;
		return(ERROR);
	    }
	    fprintf(filePtr,"> %s\n", string);
	}
	fprintf(filePtr, "[%s]\n", curHostInfo.name);

	if (name[0] != '\0') {
	    write(sockFD, "/W ", 3);
	}
	write(sockFD, name, strlen(name));
	write(sockFD, "\r\n", 2);
	f = fdopen(sockFD, "r");
	lastc = '\n';
	while ((c = getc(f)) != EOF) {
	    switch (c) {
		case 0210:
		case 0211:
		case 0212:
		case 0214:
			c -= 0200;
			break;
		case 0215:
			c = '\n';
			break;
	    }
	    putc(lastc = c, filePtr);
	}
	if (lastc != '\n') {
	    putc('\n', filePtr);
	}
	putc('\n', filePtr);

	close(sockFD);
	sockFD = -1;

	if (putToFile) {
	    fclose(filePtr);
	    filePtr = NULL;
	}
	return (SUCCESS);
}

void
ListHost_close()
{
    if (sockFD != -1) {
	(void) close(sockFD);
	sockFD = -1;
    }
}
