#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Copyright (c) 1988, 1990, 1993
 *	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.
 *
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#ifndef lint
static char sccsid[] = "@(#)commands.c	8.1 (Berkeley) 6/6/93";
#endif /* not lint */

#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <netinet/in.h>

#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <pwd.h>
#include <errno.h>
#include <strings.h>

#include <arpa/telnet.h>
#include <arpa/inet.h>

#include "general.h"

#include "ring.h"

#include "externs.h"
#include "defines.h"
#include "types.h"

extern	char *telnet_krb5_realm;
extern	void krb5_profile_get_options(char *, char *,
		profile_options_boolean*);

#include <k5-int.h>
#include <profile/prof_int.h>

profile_options_boolean config_file_options[] = {
	{ "forwardable", &forwardable_flag, 0},
	{ "forward", &forward_flag, 0},
	{ "encrypt", &encrypt_flag, 0 },
	{ "autologin", &autologin, 0 },
	{ NULL, NULL, 0}
};

#include <netinet/ip.h>

/*
 * Number of maximum IPv4 gateways user can specify. This number is limited by
 * the maximum size of the IPv4 options in the IPv4 header.
 */
#define	MAX_GATEWAY	8
/*
 * Number of maximum IPv6 gateways user can specify. This number is limited by
 * the maximum header extension length of the IPv6 routing header.
 */
#define	MAX_GATEWAY6	127
#define	MAXMAX_GATEWAY	MAX(MAX_GATEWAY, MAX_GATEWAY6)

/*
 * Depending on the address resolutions of the target and gateways,
 * we determine which addresses of the target we'll try connecting to.
 */
#define	ALL_ADDRS	0	/* try all addrs of target */
#define	ONLY_V4		1	/* try only IPv4 addrs of target */
#define	ONLY_V6		2	/* try only IPv6 addrs of target */

#if defined(USE_TOS)
int tos = -1;
#endif

char	*hostname;
static char _hostname[MAXHOSTNAMELEN];

static int send_tncmd(void (*func)(), char *, char *);
static void call(int n_ptrs, ...);
static int cmdrc(char *, char *);

typedef struct {
	char	*name;		/* command name */
	char	*help;		/* help string (NULL for no help) */
	int	(*handler)();	/* routine which executes command */
	int	needconnect;	/* Do we need to be connected to execute? */
} Command;

/*
 * storage for IPv6 and/or IPv4 addresses of gateways
 */
struct gateway {
	struct in6_addr	gw_addr6;
	struct in_addr	gw_addr;
};

/*
 * IPv4 source routing option.
 * In order to avoid padding for the alignment of IPv4 addresses, ipsr_addrs
 * is defined as a 2-D array of uint8_t, instead of 1-D array of struct in_addr.
 * If it were defined as "struct in_addr ipsr_addrs[1]", "ipsr_ptr" would be
 * followed by one byte of padding to avoid misaligned struct in_addr.
 */
struct ip_sourceroute {
	uint8_t ipsr_code;
	uint8_t ipsr_len;
	uint8_t ipsr_ptr;
	/* up to 9 IPv4 addresses */
	uint8_t ipsr_addrs[1][sizeof (struct in_addr)];
};

static char *line = NULL;
static unsigned linesize = 0;
static int margc;
static char **margv = NULL;
static unsigned margvlen = 0;
static int doing_rc = 0;   /* .telnetrc file is being read and processed */

static void
Close(int *fd)
{
	if (*fd != -1) {
		(void) close(*fd);
		*fd = -1;
	}
}

static void
Free(char **p)
{
	if (*p != NULL) {
		free(*p);
		*p = NULL;
	}
}

static void
FreeHostnameList(char *list[])
{
	unsigned i;
	for (i = 0; i <= MAXMAX_GATEWAY && list[i] != NULL; i++)
		Free(&list[i]);
}

#define	MARGV_CHUNK_SIZE 8

static void
set_argv(str)
char *str;
{
	if (margc == margvlen) {
		char **newmargv;

		margvlen += MARGV_CHUNK_SIZE;

		if ((newmargv = realloc(margv, margvlen * sizeof (char *)))
			== NULL)
			ExitString("telnet: no space for arguments",
				EXIT_FAILURE);

		margv = newmargv;
	}

	margv[margc] = str;
	if (str != NULL)
		margc++;
}

static void
makeargv()
{
	char *cp, *cp2, c;
	boolean_t shellcmd = B_FALSE;

	margc = 0;
	cp = line;
	if (*cp == '!') {		/* Special case shell escape */
		set_argv("!");		/* No room in string to get this */
		cp++;
		shellcmd = B_TRUE;
	}
	while ((c = *cp) != '\0') {
		register int inquote = 0;
		while (isspace(c))
			c = *++cp;
		if (c == '\0')
			break;
		set_argv(cp);
		/*
		 * For the shell escape, put the rest of the line, less
		 * leading space, into a single argument, breaking out from
		 * the loop to prevent the rest of the line being split up
		 * into smaller arguments.
		 */
		if (shellcmd)
			break;
		for (cp2 = cp; c != '\0'; c = *++cp) {
			if (inquote) {
				if (c == inquote) {
					inquote = 0;
					continue;
				}
			} else {
				if (c == '\\') {
					if ((c = *++cp) == '\0')
						break;
				} else if (c == '"') {
					inquote = '"';
					continue;
				} else if (c == '\'') {
					inquote = '\'';
					continue;
				} else if (isspace(c))
					break;
			}
			*cp2++ = c;
		}
		*cp2 = '\0';
		if (c == '\0')
			break;
		cp++;
	}
	set_argv((char *)NULL);
}

/*
 * Make a character string into a number.
 *
 * Todo:  1.  Could take random integers (12, 0x12, 012, 0b1).
 */

	static int
special(s)
	register char *s;
{
	register char c;
	char b;

	switch (*s) {
	case '^':
		b = *++s;
		if (b == '?') {
		    c = b | 0x40;		/* DEL */
		} else {
		    c = b & 0x1f;
		}
		break;
	default:
		c = *s;
		break;
	}
	return (c);
}

/*
 * Construct a control character sequence
 * for a special character.
 */
	static char *
control(c)
	register cc_t c;
{
	static char buf[5];
	/*
	 * The only way I could get the Sun 3.5 compiler
	 * to shut up about
	 *	if ((unsigned int)c >= 0x80)
	 * was to assign "c" to an unsigned int variable...
	 * Arggg....
	 */
	register unsigned int uic = (unsigned int)c;

	if (uic == 0x7f)
		return ("^?");
	if (c == (cc_t)_POSIX_VDISABLE) {
		return ("off");
	}
	if (uic >= 0x80) {
		buf[0] = '\\';
		buf[1] = ((c>>6)&07) + '0';
		buf[2] = ((c>>3)&07) + '0';
		buf[3] = (c&07) + '0';
		buf[4] = 0;
	} else if (uic >= 0x20) {
		buf[0] = c;
		buf[1] = 0;
	} else {
		buf[0] = '^';
		buf[1] = '@'+c;
		buf[2] = 0;
	}
	return (buf);
}

/*
 * Same as control() except that its only used for escape handling, which uses
 * _POSIX_VDISABLE differently and is aided by the use of the state variable
 * escape_valid.
 */
	static char *
esc_control(c)
	register cc_t c;
{
	static char buf[5];
	/*
	 * The only way I could get the Sun 3.5 compiler
	 * to shut up about
	 *	if ((unsigned int)c >= 0x80)
	 * was to assign "c" to an unsigned int variable...
	 * Arggg....
	 */
	register unsigned int uic = (unsigned int)c;

	if (escape_valid == B_FALSE)
		return ("off");
	if (uic == 0x7f)
		return ("^?");
	if (uic >= 0x80) {
		buf[0] = '\\';
		buf[1] = ((c>>6)&07) + '0';
		buf[2] = ((c>>3)&07) + '0';
		buf[3] = (c&07) + '0';
		buf[4] = 0;
	} else if (uic >= 0x20) {
		buf[0] = c;
		buf[1] = 0;
	} else {
		buf[0] = '^';
		buf[1] = '@'+c;
		buf[2] = 0;
	}
	return (buf);
}

/*
 *	The following are data structures and routines for
 *	the "send" command.
 *
 */

struct sendlist {
	char	*name;		/* How user refers to it (case independent) */
	char	*help;		/* Help information (0 ==> no help) */
	int	needconnect;	/* Need to be connected */
	int	narg;		/* Number of arguments */
	int	(*handler)();	/* Routine to perform (for special ops) */
	int	nbyte;		/* Number of bytes to send this command */
	int	what;		/* Character to be sent (<0 ==> special) */
};


static int send_esc(void);
static int send_help(void);
static int send_docmd(char *);
static int send_dontcmd(char *);
static int send_willcmd(char *);
static int send_wontcmd(char *);

static struct sendlist Sendlist[] = {
	{ "ao",	"Send Telnet Abort output",		1, 0, 0, 2, AO },
	{ "ayt", "Send Telnet 'Are You There'",		1, 0, 0, 2, AYT },
	{ "b", 0,					1, 0, 0, 2, BREAK },
	{ "br", 0,					1, 0, 0, 2, BREAK },
	{ "break", 0,					1, 0, 0, 2, BREAK },
	{ "brk", "Send Telnet Break",			1, 0, 0, 2, BREAK },
	{ "ec",	"Send Telnet Erase Character",		1, 0, 0, 2, EC },
	{ "el",	"Send Telnet Erase Line",		1, 0, 0, 2, EL },
	{ "escape", "Send current escape character",	1, 0, send_esc, 1, 0 },
	{ "ga",	"Send Telnet 'Go Ahead' sequence",	1, 0, 0, 2, GA },
	{ "ip",	"Send Telnet Interrupt Process",	1, 0, 0, 2, IP },
	{ "intp", 0,					1, 0, 0, 2, IP },
	{ "interrupt", 0,				1, 0, 0, 2, IP },
	{ "intr",	0,				1, 0, 0, 2, IP },
	{ "nop", "Send Telnet 'No operation'",		1, 0, 0, 2, NOP },
	{ "eor", "Send Telnet 'End of Record'",		1, 0, 0, 2, EOR },
	{ "abort", "Send Telnet 'Abort Process'",	1, 0, 0, 2, ABORT },
	{ "susp", "Send Telnet 'Suspend Process'",	1, 0, 0, 2, SUSP },
	{ "eof", "Send Telnet End of File Character",	1, 0, 0, 2, xEOF },
	{ "synch", "Perform Telnet 'Synch operation'",	1, 0, dosynch, 2, 0 },
	{ "getstatus", "Send request for STATUS", 1, 0, get_status, 6, 0 },
	{ "?",	"Display send options",			0, 0, send_help, 0, 0 },
	{ "help",	0,				0, 0, send_help, 0, 0 },
	{ "do",	0,				0, 1, send_docmd, 3, 0 },
	{ "dont", 0,				0, 1, send_dontcmd, 3, 0 },
	{ "will", 0,				0, 1, send_willcmd, 3, 0 },
	{ "wont", 0,				0, 1, send_wontcmd, 3, 0 },
	{ 0 }
};

#define	GETSEND(name) ((struct sendlist *)genget(name, (char **)Sendlist, \
				sizeof (struct sendlist)))

static int
sendcmd(argc, argv)
	int  argc;
	char **argv;
{
	int count;	/* how many bytes we are going to need to send */
	int i;
	struct sendlist *s;	/* pointer to current command */
	int success = 0;
	int needconnect = 0;

	if (argc < 2) {
		(void) printf(
		    "need at least one argument for 'send' command\n");
		(void) printf("'send ?' for help\n");
		return (0);
	}
	/*
	 * First, validate all the send arguments.
	 * In addition, we see how much space we are going to need, and
	 * whether or not we will be doing a "SYNCH" operation (which
	 * flushes the network queue).
	 */
	count = 0;
	for (i = 1; i < argc; i++) {
		s = GETSEND(argv[i]);
		if (s == 0) {
			(void) printf("Unknown send argument '%s'\n'send ?' "
			    "for help.\n", argv[i]);
			return (0);
		} else if (Ambiguous(s)) {
			(void) printf("Ambiguous send argument '%s'\n'send ?' "
			    "for help.\n", argv[i]);
			return (0);
		}
		if (i + s->narg >= argc) {
			(void) fprintf(stderr,
			    "Need %d argument%s to 'send %s' "
			    "command.  'send %s ?' for help.\n",
			    s->narg, s->narg == 1 ? "" : "s", s->name, s->name);
			return (0);
		}
		count += s->nbyte;
		if (s->handler == send_help) {
			(void) send_help();
			return (0);
		}

		i += s->narg;
		needconnect += s->needconnect;
	}
	if (!connected && needconnect) {
		(void) printf("?Need to be connected first.\n");
		(void) printf("'send ?' for help\n");
		return (0);
	}
	/* Now, do we have enough room? */
	if (NETROOM() < count) {
		(void) printf("There is not enough room in the buffer "
		    "TO the network\n");
		(void) printf(
		    "to process your request.  Nothing will be done.\n");
		(void) printf("('send synch' will throw away most "
		    "data in the network\n");
		(void) printf("buffer, if this might help.)\n");
		return (0);
	}
	/* OK, they are all OK, now go through again and actually send */
	count = 0;
	for (i = 1; i < argc; i++) {
		if ((s = GETSEND(argv[i])) == 0) {
			(void) fprintf(stderr,
			    "Telnet 'send' error - argument disappeared!\n");
			(void) quit();
			/*NOTREACHED*/
		}
		if (s->handler) {
			count++;
			success += (*s->handler)((s->narg > 0) ? argv[i+1] : 0,
			    (s->narg > 1) ? argv[i+2] : 0);
			i += s->narg;
		} else {
			NET2ADD(IAC, s->what);
			printoption("SENT", IAC, s->what);
		}
	}
	return (count == success);
}

static int
send_esc()
{
	NETADD(escape);
	return (1);
}

static int
send_docmd(name)
	char *name;
{
	return (send_tncmd(send_do, "do", name));
}

static int
send_dontcmd(name)
	char *name;
{
	return (send_tncmd(send_dont, "dont", name));
}

static int
send_willcmd(name)
	char *name;
{
	return (send_tncmd(send_will, "will", name));
}

static int
send_wontcmd(name)
	char *name;
{
	return (send_tncmd(send_wont, "wont", name));
}

int
send_tncmd(func, cmd, name)
	void	(*func)();
	char	*cmd, *name;
{
	char **cpp;
	extern char *telopts[];
	register int val = 0;

	if (isprefix(name, "help") || isprefix(name, "?")) {
		register int col, len;

		(void) printf("Usage: send %s <value|option>\n", cmd);
		(void) printf("\"value\" must be from 0 to 255\n");
		(void) printf("Valid options are:\n\t");

		col = 8;
		for (cpp = telopts; *cpp; cpp++) {
			len = strlen(*cpp) + 3;
			if (col + len > 65) {
				(void) printf("\n\t");
				col = 8;
			}
			(void) printf(" \"%s\"", *cpp);
			col += len;
		}
		(void) printf("\n");
		return (0);
	}
	cpp = (char **)genget(name, telopts, sizeof (char *));
	if (Ambiguous(cpp)) {
		(void) fprintf(stderr,
		    "'%s': ambiguous argument ('send %s ?' for help).\n",
		    name, cmd);
		return (0);
	}
	if (cpp) {
		val = cpp - telopts;
	} else {
		register char *cp = name;

		while (*cp >= '0' && *cp <= '9') {
			val *= 10;
			val += *cp - '0';
			cp++;
		}
		if (*cp != 0) {
			(void) fprintf(stderr,
			    "'%s': unknown argument ('send %s ?' for help).\n",
			    name, cmd);
			return (0);
		} else if (val < 0 || val > 255) {
			(void) fprintf(stderr,
			    "'%s': bad value ('send %s ?' for help).\n",
			    name, cmd);
			return (0);
		}
	}
	if (!connected) {
		(void) printf("?Need to be connected first.\n");
		return (0);
	}
	(*func)(val, 1);
	return (1);
}

static int
send_help()
{
	struct sendlist *s;	/* pointer to current command */
	for (s = Sendlist; s->name; s++) {
		if (s->help)
			(void) printf("%-15s %s\n", s->name, s->help);
	}
	return (0);
}

/*
 * The following are the routines and data structures referred
 * to by the arguments to the "toggle" command.
 */

static int
lclchars()
{
	donelclchars = 1;
	return (1);
}

static int
togdebug()
{
	if (net > 0 &&
	    (SetSockOpt(net, SOL_SOCKET, SO_DEBUG, debug)) < 0) {
		perror("setsockopt (SO_DEBUG)");
	}
	return (1);
}


static int
togcrlf()
{
	if (crlf) {
		(void) printf(
		    "Will send carriage returns as telnet <CR><LF>.\n");
	} else {
		(void) printf(
		    "Will send carriage returns as telnet <CR><NUL>.\n");
	}
	return (1);
}

static int binmode;

static int
togbinary(val)
	int val;
{
	donebinarytoggle = 1;

	if (val >= 0) {
		binmode = val;
	} else {
		if (my_want_state_is_will(TELOPT_BINARY) &&
		    my_want_state_is_do(TELOPT_BINARY)) {
			binmode = 1;
		} else if (my_want_state_is_wont(TELOPT_BINARY) &&
		    my_want_state_is_dont(TELOPT_BINARY)) {
			binmode = 0;
		}
		val = binmode ? 0 : 1;
	}

	if (val == 1) {
		if (my_want_state_is_will(TELOPT_BINARY) &&
		    my_want_state_is_do(TELOPT_BINARY)) {
			(void) printf("Already operating in binary mode "
			    "with remote host.\n");
		} else {
			(void) printf(
			    "Negotiating binary mode with remote host.\n");
			tel_enter_binary(3);
		}
	} else {
		if (my_want_state_is_wont(TELOPT_BINARY) &&
		    my_want_state_is_dont(TELOPT_BINARY)) {
			(void) printf("Already in network ascii mode "
			    "with remote host.\n");
		} else {
			(void) printf("Negotiating network ascii mode "
			    "with remote host.\n");
			tel_leave_binary(3);
		}
	}
	return (1);
}

static int
togrbinary(val)
	int val;
{
	donebinarytoggle = 1;

	if (val == -1)
		val = my_want_state_is_do(TELOPT_BINARY) ? 0 : 1;

	if (val == 1) {
		if (my_want_state_is_do(TELOPT_BINARY)) {
			(void) printf("Already receiving in binary mode.\n");
		} else {
			(void) printf("Negotiating binary mode on input.\n");
			tel_enter_binary(1);
		}
	} else {
		if (my_want_state_is_dont(TELOPT_BINARY)) {
			(void) printf(
			    "Already receiving in network ascii mode.\n");
		} else {
			(void) printf(
			    "Negotiating network ascii mode on input.\n");
			    tel_leave_binary(1);
		}
	}
	return (1);
}

static int
togxbinary(val)
	int val;
{
	donebinarytoggle = 1;

	if (val == -1)
		val = my_want_state_is_will(TELOPT_BINARY) ? 0 : 1;

	if (val == 1) {
		if (my_want_state_is_will(TELOPT_BINARY)) {
			(void) printf("Already transmitting in binary mode.\n");
		} else {
			(void) printf("Negotiating binary mode on output.\n");
			tel_enter_binary(2);
		}
	} else {
		if (my_want_state_is_wont(TELOPT_BINARY)) {
			(void) printf(
			    "Already transmitting in network ascii mode.\n");
		} else {
			(void) printf(
			    "Negotiating network ascii mode on output.\n");
			tel_leave_binary(2);
		}
	}
	return (1);
}


static int togglehelp(void);
extern int auth_togdebug(int);

struct togglelist {
	char	*name;		/* name of toggle */
	char	*help;		/* help message */
	int	(*handler)();	/* routine to do actual setting */
	int	*variable;
	char	*actionexplanation;
};

static struct togglelist Togglelist[] = {
	{ "autoflush",
	"flushing of output when sending interrupt characters",
	    0,
		&autoflush,
		    "flush output when sending interrupt characters" },
	{ "autosynch",
	"automatic sending of interrupt characters in urgent mode",
	    0,
		&autosynch,
		    "send interrupt characters in urgent mode" },
	{ "autologin",
	"automatic sending of login and/or authentication info",
	    0,
		&autologin,
		    "send login name and/or authentication information" },
	{ "authdebug",
	"authentication debugging",
	    auth_togdebug,
		0,
		    "print authentication debugging information" },
	{ "autoencrypt",
	"automatic encryption of data stream",
	    EncryptAutoEnc,
		0,
		    "automatically encrypt output" },
	{ "autodecrypt",
	"automatic decryption of data stream",
	    EncryptAutoDec,
		0,
		    "automatically decrypt input" },
	{ "verbose_encrypt",
	"verbose encryption output",
	    EncryptVerbose,
		0,
		    "print verbose encryption output" },
	{ "encdebug",
	"encryption debugging",
	    EncryptDebug,
		0,
		    "print encryption debugging information" },
	{ "skiprc",
	"don't read ~/.telnetrc file",
	    0,
		&skiprc,
		    "skip reading of ~/.telnetrc file" },
	{ "binary",
	"sending and receiving of binary data",
	    togbinary,
		0,
		    0 },
	{ "inbinary",
	"receiving of binary data",
	    togrbinary,
		0,
		    0 },
	{ "outbinary",
	"sending of binary data",
	    togxbinary,
		0,
		    0 },
	{ "crlf",
	"sending carriage returns as telnet <CR><LF>",
	    togcrlf,
		&crlf,
		    0 },
	{ "crmod",
	"mapping of received carriage returns",
	    0,
		&crmod,
		    "map carriage return on output" },
	{ "localchars",
	"local recognition of certain control characters",
	    lclchars,
		&localchars,
		    "recognize certain control characters" },
	{ " ", "", 0 },		/* empty line */
	{ "debug",
	"debugging",
	    togdebug,
		&debug,
		    "turn on socket level debugging" },
	{ "netdata",
	"printing of hexadecimal network data (debugging)",
	    0,
		&netdata,
		    "print hexadecimal representation of network traffic" },
	{ "prettydump",
	"output of \"netdata\" to user readable format (debugging)",
	    0,
		&prettydump,
		    "print user readable output for \"netdata\"" },
	{ "options",
	"viewing of options processing (debugging)",
	    0,
		&showoptions,
		    "show option processing" },
	{ "termdata",
	"(debugging) toggle printing of hexadecimal terminal data",
	    0,
		&termdata,
		    "print hexadecimal representation of terminal traffic" },
	{ "?",
	0,
	    togglehelp },
	{ "help",
	0,
	    togglehelp },
	{ 0 }
};

static int
togglehelp()
{
	struct togglelist *c;

	for (c = Togglelist; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf(
					"%-15s toggle %s\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
	(void) printf("\n");
	(void) printf("%-15s %s\n", "?", "display help information");
	return (0);
}

static void
settogglehelp(set)
	int set;
{
	struct togglelist *c;

	for (c = Togglelist; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s %s\n", c->name,
				    set ? "enable" : "disable", c->help);
			else
				(void) printf("\n");
		}
	}
}

#define	GETTOGGLE(name) (struct togglelist *) \
		genget(name, (char **)Togglelist, sizeof (struct togglelist))

static int
toggle(argc, argv)
	int  argc;
	char *argv[];
{
	int retval = 1;
	char *name;
	struct togglelist *c;

	if (argc < 2) {
		(void) fprintf(stderr,
		    "Need an argument to 'toggle' command.  "
		    "'toggle ?' for help.\n");
		return (0);
	}
	argc--;
	argv++;
	while (argc--) {
		name = *argv++;
		c = GETTOGGLE(name);
		if (Ambiguous(c)) {
			(void) fprintf(stderr, "'%s': ambiguous argument "
			    "('toggle ?' for help).\n", name);
			return (0);
		} else if (c == 0) {
			(void) fprintf(stderr, "'%s': unknown argument "
			    "('toggle ?' for help).\n", name);
			return (0);
		} else {
			if (c->variable) {
				*c->variable = !*c->variable;	/* invert it */
				if (c->actionexplanation) {
			(void) printf("%s %s.\n",
				*c->variable ? "Will" : "Won't",
							c->actionexplanation);
				}
			}
			if (c->handler) {
				retval &= (*c->handler)(-1);
			}
		}
	}
	return (retval);
}

/*
 * The following perform the "set" command.
 */

#ifdef	USE_TERMIO
struct termio new_tc = { 0 };
#endif

struct setlist {
	char *name;		/* name */
	char *help;		/* help information */
	void (*handler)();
	cc_t *charp;		/* where it is located at */
};

static struct setlist Setlist[] = {
#ifdef	KLUDGELINEMODE
	{ "echo",  "character to toggle local echoing on/off", 0, &echoc },
#endif
	{ "escape", "character to escape back to telnet command mode", 0,
	    &escape },
	{ "rlogin", "rlogin escape character", 0, &rlogin },
	{ "tracefile", "file to write trace information to", SetNetTrace,
	    (cc_t *)NetTraceFile},
	{ " ", "" },
	{ " ", "The following need 'localchars' to be toggled true", 0, 0 },
	{ "flushoutput", "character to cause an Abort Output", 0,
	    termFlushCharp },
	{ "interrupt", "character to cause an Interrupt Process", 0,
	    termIntCharp },
	{ "quit", "character to cause an Abort process", 0, termQuitCharp },
	{ "eof", "character to cause an EOF ", 0, termEofCharp },
	{ " ", "" },
	{ " ", "The following are for local editing in linemode", 0, 0 },
	{ "erase", "character to use to erase a character", 0, termEraseCharp },
	{ "kill", "character to use to erase a line", 0, termKillCharp },
	{ "lnext", "character to use for literal next", 0,
	    termLiteralNextCharp },
	{ "susp", "character to cause a Suspend Process", 0, termSuspCharp },
	{ "reprint", "character to use for line reprint", 0, termRprntCharp },
	{ "worderase", "character to use to erase a word", 0, termWerasCharp },
	{ "start",	"character to use for XON", 0, termStartCharp },
	{ "stop",	"character to use for XOFF", 0, termStopCharp },
	{ "forw1",	"alternate end of line character", 0, termForw1Charp },
	{ "forw2",	"alternate end of line character", 0, termForw2Charp },
	{ "ayt",	"alternate AYT character", 0, termAytCharp },
	{ 0 }
};

static struct setlist *
getset(name)
    char *name;
{
	return ((struct setlist *)
	    genget(name, (char **)Setlist, sizeof (struct setlist)));
}

    void
set_escape_char(s)
    char *s;
{
	if (rlogin != _POSIX_VDISABLE) {
		rlogin = (s && *s) ? special(s) : _POSIX_VDISABLE;
		(void) printf("Telnet rlogin escape character is '%s'.\n",
					control(rlogin));
	} else {
		escape = (s && *s) ? special(s) : _POSIX_VDISABLE;
		(void) printf("Telnet escape character is '%s'.\n",
		    esc_control(escape));
	}
}

static int
setcmd(argc, argv)
	int  argc;
	char *argv[];
{
	int value;
	struct setlist *ct;
	struct togglelist *c;

	if (argc < 2 || argc > 3) {
		(void) printf(
			"Format is 'set Name Value'\n'set ?' for help.\n");
		return (0);
	}
	if ((argc == 2) &&
	    (isprefix(argv[1], "?") || isprefix(argv[1], "help"))) {
		for (ct = Setlist; ct->name; ct++)
			(void) printf("%-15s %s\n", ct->name, ct->help);
		(void) printf("\n");
		settogglehelp(1);
		(void) printf("%-15s %s\n", "?", "display help information");
		return (0);
	}

	ct = getset(argv[1]);
	if (ct == 0) {
		c = GETTOGGLE(argv[1]);
		if (c == 0) {
			(void) fprintf(stderr, "'%s': unknown argument "
			    "('set ?' for help).\n", argv[1]);
			return (0);
		} else if (Ambiguous(c)) {
			(void) fprintf(stderr, "'%s': ambiguous argument "
			    "('set ?' for help).\n", argv[1]);
			return (0);
		}
		if (c->variable) {
			if ((argc == 2) || (strcmp("on", argv[2]) == 0))
				*c->variable = 1;
			else if (strcmp("off", argv[2]) == 0)
				*c->variable = 0;
			else {
				(void) printf(
				    "Format is 'set togglename [on|off]'\n"
				    "'set ?' for help.\n");
				return (0);
			}
			if (c->actionexplanation) {
				(void) printf("%s %s.\n",
				    *c->variable? "Will" : "Won't",
				    c->actionexplanation);
			}
		}
		if (c->handler)
			(*c->handler)(1);
	} else if (argc != 3) {
		(void) printf(
			"Format is 'set Name Value'\n'set ?' for help.\n");
		return (0);
	} else if (Ambiguous(ct)) {
		(void) fprintf(stderr,
		    "'%s': ambiguous argument ('set ?' for help).\n", argv[1]);
		return (0);
	} else if (ct->handler) {
		(*ct->handler)(argv[2]);
		(void) printf(
		    "%s set to \"%s\".\n", ct->name, (char *)ct->charp);
	} else {
		if (strcmp("off", argv[2])) {
			value = special(argv[2]);
		} else {
			value = _POSIX_VDISABLE;
		}
		*(ct->charp) = (cc_t)value;
		(void) printf("%s character is '%s'.\n", ct->name,
		    control(*(ct->charp)));
	}
	slc_check();
	return (1);
}

static int
unsetcmd(argc, argv)
	int  argc;
	char *argv[];
{
	struct setlist *ct;
	struct togglelist *c;
	register char *name;

	if (argc < 2) {
		(void) fprintf(stderr, "Need an argument to 'unset' command.  "
		    "'unset ?' for help.\n");
		return (0);
	}
	if (isprefix(argv[1], "?") || isprefix(argv[1], "help")) {
		for (ct = Setlist; ct->name; ct++)
			(void) printf("%-15s %s\n", ct->name, ct->help);
		(void) printf("\n");
		settogglehelp(0);
		(void) printf("%-15s %s\n", "?", "display help information");
		return (0);
	}

	argc--;
	argv++;
	while (argc--) {
		name = *argv++;
		ct = getset(name);
		if (ct == 0) {
			c = GETTOGGLE(name);
			if (c == 0) {
				(void) fprintf(stderr, "'%s': unknown argument "
				    "('unset ?' for help).\n", name);
				return (0);
			} else if (Ambiguous(c)) {
				(void) fprintf(stderr,
				    "'%s': ambiguous argument "
				    "('unset ?' for help).\n", name);
				return (0);
			}
			if (c->variable) {
				*c->variable = 0;
				if (c->actionexplanation) {
					(void) printf("%s %s.\n",
					    *c->variable? "Will" : "Won't",
					    c->actionexplanation);
				}
			}
			if (c->handler)
				(*c->handler)(0);
		} else if (Ambiguous(ct)) {
			(void) fprintf(stderr, "'%s': ambiguous argument "
			    "('unset ?' for help).\n", name);
			return (0);
		} else if (ct->handler) {
			(*ct->handler)(0);
			(void) printf("%s reset to \"%s\".\n", ct->name,
			    (char *)ct->charp);
		} else {
			*(ct->charp) = _POSIX_VDISABLE;
			(void) printf("%s character is '%s'.\n", ct->name,
			    control(*(ct->charp)));
		}
	}
	return (1);
}

/*
 * The following are the data structures and routines for the
 * 'mode' command.
 */
extern int reqd_linemode;

#ifdef	KLUDGELINEMODE
extern int kludgelinemode;

static int
dokludgemode()
{
	kludgelinemode = 1;
	send_wont(TELOPT_LINEMODE, 1);
	send_dont(TELOPT_SGA, 1);
	send_dont(TELOPT_ECHO, 1);
	/*
	 * If processing the .telnetrc file, keep track of linemode and/or
	 * kludgelinemode requests which are processed before initial option
	 * negotiations occur.
	 */
	if (doing_rc)
		reqd_linemode = 1;
	return (1);
}
#endif

static int
dolinemode()
{
#ifdef	KLUDGELINEMODE
	if (kludgelinemode)
		send_dont(TELOPT_SGA, 1);
#endif
	send_will(TELOPT_LINEMODE, 1);
	send_dont(TELOPT_ECHO, 1);

	/*
	 * If processing the .telnetrc file, keep track of linemode and/or
	 * kludgelinemode requests which are processed before initial option
	 * negotiations occur.
	 */
	if (doing_rc)
		reqd_linemode = 1;
	return (1);
}

static int
docharmode()
{
#ifdef	KLUDGELINEMODE
	if (kludgelinemode)
		send_do(TELOPT_SGA, 1);
	else
#endif
		send_wont(TELOPT_LINEMODE, 1);
	send_do(TELOPT_ECHO, 1);
	reqd_linemode = 0;
	return (1);
}

static int
dolmmode(bit, on)
	int bit, on;
{
	unsigned char c;
	extern int linemode;

	if (my_want_state_is_wont(TELOPT_LINEMODE)) {
		(void) printf("?Need to have LINEMODE option enabled first.\n");
		(void) printf("'mode ?' for help.\n");
		return (0);
	}

	if (on)
		c = (linemode | bit);
	else
		c = (linemode & ~bit);
	lm_mode(&c, 1, 1);
	return (1);
}

static int
setmode(bit)
{
	return (dolmmode(bit, 1));
}

static int
clearmode(bit)
{
	return (dolmmode(bit, 0));
}

struct modelist {
	char	*name;		/* command name */
	char	*help;		/* help string */
	int	(*handler)();	/* routine which executes command */
	int	needconnect;	/* Do we need to be connected to execute? */
	int	arg1;
};

static int modehelp();

static struct modelist ModeList[] = {
	{ "character", "Disable LINEMODE option",	docharmode, 1 },
#ifdef	KLUDGELINEMODE
	{ "",	"(or disable obsolete line-by-line mode)", 0 },
#endif
	{ "line",	"Enable LINEMODE option",	dolinemode, 1 },
#ifdef	KLUDGELINEMODE
	{ "",	"(or enable obsolete line-by-line mode)", 0 },
#endif
	{ "", "", 0 },
	{ "",	"These require the LINEMODE option to be enabled", 0 },
	{ "isig",	"Enable signal trapping", setmode, 1, MODE_TRAPSIG },
	{ "+isig",	0,			setmode, 1, MODE_TRAPSIG },
	{ "-isig",	"Disable signal trapping", clearmode, 1, MODE_TRAPSIG },
	{ "edit",	"Enable character editing",	setmode, 1, MODE_EDIT },
	{ "+edit",	0,			setmode, 1, MODE_EDIT },
	{ "-edit",	"Disable character editing", clearmode, 1, MODE_EDIT },
	{ "softtabs",	"Enable tab expansion",	setmode, 1, MODE_SOFT_TAB },
	{ "+softtabs",	0,			setmode, 1, MODE_SOFT_TAB },
	{ "-softtabs",	"Disable tab expansion",
						clearmode, 1, MODE_SOFT_TAB },
	{ "litecho",	"Enable literal character echo",
						setmode, 1, MODE_LIT_ECHO },
	{ "+litecho",	0,			setmode, 1, MODE_LIT_ECHO },
	{ "-litecho",	"Disable literal character echo", clearmode, 1,
		MODE_LIT_ECHO },
	{ "help",	0,			modehelp, 0 },
#ifdef	KLUDGELINEMODE
	{ "kludgeline", 0,				dokludgemode, 1 },
#endif
	{ "", "", 0 },
	{ "?",	"Print help information",	modehelp, 0 },
	{ 0 },
};


static int
modehelp()
{
	struct modelist *mt;

	(void) printf("format is:  'mode Mode', where 'Mode' is one of:\n\n");
	for (mt = ModeList; mt->name; mt++) {
		if (mt->help) {
			if (*mt->help)
				(void) printf("%-15s %s\n", mt->name, mt->help);
			else
				(void) printf("\n");
		}
	}
	return (0);
}

#define	GETMODECMD(name) (struct modelist *) \
		genget(name, (char **)ModeList, sizeof (struct modelist))

static int
modecmd(argc, argv)
	int  argc;
	char *argv[];
{
	struct modelist *mt;

	if (argc != 2) {
		(void) printf("'mode' command requires an argument\n");
		(void) printf("'mode ?' for help.\n");
	} else if ((mt = GETMODECMD(argv[1])) == 0) {
		(void) fprintf(stderr,
		    "Unknown mode '%s' ('mode ?' for help).\n", argv[1]);
	} else if (Ambiguous(mt)) {
		(void) fprintf(stderr,
		    "Ambiguous mode '%s' ('mode ?' for help).\n", argv[1]);
	} else if (mt->needconnect && !connected) {
		(void) printf("?Need to be connected first.\n");
		(void) printf("'mode ?' for help.\n");
	} else if (mt->handler) {
		return (*mt->handler)(mt->arg1);
	}
	return (0);
}

/*
 * The following data structures and routines implement the
 * "display" command.
 */

static int
display(argc, argv)
	int  argc;
	char *argv[];
{
	struct togglelist *tl;
	struct setlist *sl;

#define	dotog(tl)	if (tl->variable && tl->actionexplanation) { \
			    if (*tl->variable) { \
				(void) printf("will"); \
			    } else { \
				(void) printf("won't"); \
			    } \
			    (void) printf(" %s.\n", tl->actionexplanation); \
			}

#define	doset(sl)   if (sl->name && *sl->name != ' ') { \
			if (sl->handler == 0) \
			    (void) printf("%-15s [%s]\n", sl->name, \
			    control(*sl->charp)); \
			else \
			    (void) printf("%-15s \"%s\"\n", sl->name, \
			    (char *)sl->charp); \
		    }

	if (argc == 1) {
		for (tl = Togglelist; tl->name; tl++) {
			dotog(tl);
		}
		(void) printf("\n");
		for (sl = Setlist; sl->name; sl++) {
			doset(sl);
		}
	} else {
		int i;

		for (i = 1; i < argc; i++) {
			sl = getset(argv[i]);
			tl = GETTOGGLE(argv[i]);
			if (Ambiguous(sl) || Ambiguous(tl)) {
				(void) printf(
				    "?Ambiguous argument '%s'.\n", argv[i]);
				return (0);
			} else if (!sl && !tl) {
				(void) printf(
				    "?Unknown argument '%s'.\n", argv[i]);
				return (0);
			} else {
				if (tl) {
					dotog(tl);
				}
				if (sl) {
				    doset(sl);
				}
			}
		}
	}
	optionstatus();
	(void) EncryptStatus();
	return (1);
#undef	doset
#undef	dotog
}

/*
 * The following are the data structures, and many of the routines,
 * relating to command processing.
 */

/*
 * Set the escape character.
 */
	static int
setescape(argc, argv)
	int argc;
	char *argv[];
{
	register char *arg;
	char *buf = NULL;

	if (argc > 2)
		arg = argv[1];
	else {
		(void) printf("new escape character: ");
		if (GetString(&buf, NULL, stdin) == NULL) {
			if (!feof(stdin)) {
				perror("can't set escape character");
				goto setescape_exit;
			}
		}
		arg = buf;
	}
	/* we place no limitations on what escape can be. */
	escape = arg[0];
	(void) printf("Escape character is '%s'.\n", esc_control(escape));
	(void) fflush(stdout);
setescape_exit:
	Free(&buf);
	return (1);
}

/*ARGSUSED*/
static int
togcrmod(argc, argv)
	int argc;
	char *argv[];
{
	crmod = !crmod;
	(void) printf(
	    "%s map carriage return on output.\n", crmod ? "Will" : "Won't");
	(void) fflush(stdout);
	return (1);
}

/*ARGSUSED*/
static int
suspend(argc, argv)
	int argc;
	char *argv[];
{
	setcommandmode();
	{
		unsigned short oldrows, oldcols, newrows, newcols;
		int err;

		err = (TerminalWindowSize(&oldrows, &oldcols) == 0) ? 1 : 0;
		(void) kill(0, SIGTSTP);
		/*
		 * If we didn't get the window size before the SUSPEND, but we
		 * can get them now (?), then send the NAWS to make sure that
		 * we are set up for the right window size.
		 */
		if (TerminalWindowSize(&newrows, &newcols) && connected &&
		    (err || ((oldrows != newrows) || (oldcols != newcols)))) {
			sendnaws();
		}
	}
	/* reget parameters in case they were changed */
	TerminalSaveState();
	setconnmode(0);
	return (1);
}

/*ARGSUSED*/
static int
shell(argc, argv)
	int argc;
	char *argv[];
{
	unsigned short oldrows, oldcols, newrows, newcols;
	int err;

	setcommandmode();

	err = (TerminalWindowSize(&oldrows, &oldcols) == 0) ? 1 : 0;
	switch (vfork()) {
	case -1:
		perror("Fork failed\n");
		break;

	case 0:
		{
		/*
		 * Fire up the shell in the child.
		 */
		register char *shellp, *shellname;

		shellp = getenv("SHELL");
		if (shellp == NULL)
			shellp = "/bin/sh";
		if ((shellname = strrchr(shellp, '/')) == 0)
			shellname = shellp;
		else
			shellname++;
		if (argc > 1)
			(void) execl(shellp, shellname, "-c", argv[1], 0);
		else
			(void) execl(shellp, shellname, 0);
		perror("Execl");
		_exit(EXIT_FAILURE);
		}
	default:
		(void) wait((int *)0);	/* Wait for the shell to complete */

		if (TerminalWindowSize(&newrows, &newcols) && connected &&
		(err || ((oldrows != newrows) || (oldcols != newcols)))) {
			sendnaws();
		}
		break;
	}
	return (1);
}

static int
bye(argc, argv)
	int  argc;	/* Number of arguments */
	char *argv[];	/* arguments */
{
	extern int resettermname;

	if (connected) {
		(void) shutdown(net, 2);
		(void) printf("Connection to %.*s closed.\n", MAXHOSTNAMELEN,
		    hostname);
		Close(&net);
		connected = 0;
		resettermname = 1;
		/* reset options */
		(void) tninit();
	}
	if ((argc != 2) || (strcmp(argv[1], "fromquit") != 0)) {
		longjmp(toplevel, 1);
		/* NOTREACHED */
	}
	return (1);			/* Keep lint, etc., happy */
}

/*VARARGS*/
int
quit()
{
	(void) call(3, bye, "bye", "fromquit");
	Exit(EXIT_SUCCESS);
	/*NOTREACHED*/
	return (1);
}

/*ARGSUSED*/
static int
logout(argc, argv)
	int argc;
	char *argv[];
{
	send_do(TELOPT_LOGOUT, 1);
	(void) netflush();
	return (1);
}


/*
 * The SLC command.
 */

struct slclist {
	char	*name;
	char	*help;
	void	(*handler)();
	int	arg;
};

static void slc_help();

static struct slclist SlcList[] = {
	{ "export",	"Use local special character definitions",
						slc_mode_export,	0 },
	{ "import",	"Use remote special character definitions",
						slc_mode_import,	1 },
	{ "check",	"Verify remote special character definitions",
						slc_mode_import,	0 },
	{ "help",	0,			slc_help,		0 },
	{ "?",	"Print help information",	slc_help,		0 },
	{ 0 },
};

static void
slc_help()
{
	struct slclist *c;

	for (c = SlcList; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
}

static struct slclist *
getslc(name)
	char *name;
{
	return ((struct slclist *)
	    genget(name, (char **)SlcList, sizeof (struct slclist)));
}

static int
slccmd(argc, argv)
	int  argc;
	char *argv[];
{
	struct slclist *c;

	if (argc != 2) {
		(void) fprintf(stderr,
		    "Need an argument to 'slc' command.  'slc ?' for help.\n");
		return (0);
	}
	c = getslc(argv[1]);
	if (c == 0) {
		(void) fprintf(stderr,
		    "'%s': unknown argument ('slc ?' for help).\n",
		    argv[1]);
		return (0);
	}
	if (Ambiguous(c)) {
		(void) fprintf(stderr,
		    "'%s': ambiguous argument ('slc ?' for help).\n", argv[1]);
		return (0);
	}
	(*c->handler)(c->arg);
	slcstate();
	return (1);
}

/*
 * The ENVIRON command.
 */

struct envlist {
	char	*name;
	char	*help;
	void	(*handler)();
	int	narg;
};

static struct env_lst *env_define(unsigned char *, unsigned char *);
static void env_undefine(unsigned char *);
static void env_export(unsigned char *);
static void env_unexport(unsigned char *);
static void env_send(unsigned char *);
#if defined(OLD_ENVIRON) && defined(ENV_HACK)
static void env_varval(unsigned char *);
#endif
static void env_list(void);

static void env_help(void);

static struct envlist EnvList[] = {
	{ "define",	"Define an environment variable",
						(void (*)())env_define,	2 },
	{ "undefine", "Undefine an environment variable",
						env_undefine,	1 },
	{ "export",	"Mark an environment variable for automatic export",
						env_export,	1 },
	{ "unexport", "Don't mark an environment variable for automatic export",
						env_unexport,	1 },
	{ "send",	"Send an environment variable", env_send,	1 },
	{ "list",	"List the current environment variables",
						env_list,	0 },
#if defined(OLD_ENVIRON) && defined(ENV_HACK)
	{ "varval", "Reverse VAR and VALUE (auto, right, wrong, status)",
						env_varval,    1 },
#endif
	{ "help",	0,			env_help,		0 },
	{ "?",	"Print help information",	env_help,		0 },
	{ 0 },
};

static void
env_help()
{
	struct envlist *c;

	for (c = EnvList; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
}

static struct envlist *
getenvcmd(name)
    char *name;
{
	return ((struct envlist *)
	    genget(name, (char **)EnvList, sizeof (struct envlist)));
}

static int
env_cmd(argc, argv)
	int  argc;
	char *argv[];
{
	struct envlist *c;

	if (argc < 2) {
		(void) fprintf(stderr,
		    "Need an argument to 'environ' command.  "
		    "'environ ?' for help.\n");
		return (0);
	}
	c = getenvcmd(argv[1]);
	if (c == 0) {
		(void) fprintf(stderr, "'%s': unknown argument "
		    "('environ ?' for help).\n", argv[1]);
		return (0);
	}
	if (Ambiguous(c)) {
		(void) fprintf(stderr, "'%s': ambiguous argument "
		    "('environ ?' for help).\n", argv[1]);
		return (0);
	}
	if (c->narg + 2 != argc) {
		(void) fprintf(stderr,
		    "Need %s%d argument%s to 'environ %s' command.  "
		    "'environ ?' for help.\n",
		    c->narg + 2 < argc ? "only " : "",
		    c->narg, c->narg == 1 ? "" : "s", c->name);
		return (0);
	}
	(*c->handler)(argv[2], argv[3]);
	return (1);
}

struct env_lst {
	struct env_lst *next;	/* pointer to next structure */
	struct env_lst *prev;	/* pointer to previous structure */
	unsigned char *var;	/* pointer to variable name */
	unsigned char *value;	/* pointer to variable value */
	int export;		/* 1 -> export with default list of variables */
	int welldefined;	/* A well defined variable */
};

static struct env_lst envlisthead;

static struct env_lst *
env_find(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	for (ep = envlisthead.next; ep; ep = ep->next) {
		if (strcmp((char *)ep->var, (char *)var) == 0)
			return (ep);
	}
	return (NULL);
}

int
env_init()
{
#ifdef	lint
	char **environ = NULL;
#else	/* lint */
	extern char **environ;
#endif	/* lint */
	char **epp, *cp;
	struct env_lst *ep;

	for (epp = environ; *epp; epp++) {
		if (cp = strchr(*epp, '=')) {
			*cp = '\0';

			ep = env_define((unsigned char *)*epp,
					(unsigned char *)cp+1);
			if (ep == NULL)
				return (0);
			ep->export = 0;
			*cp = '=';
		}
	}
	/*
	 * Special case for DISPLAY variable.  If it is ":0.0" or
	 * "unix:0.0", we have to get rid of "unix" and insert our
	 * hostname.
	 */
	if (((ep = env_find((uchar_t *)"DISPLAY")) != NULL) &&
	    ((*ep->value == ':') ||
	    (strncmp((char *)ep->value, "unix:", 5) == 0))) {
		char hbuf[MAXHOSTNAMELEN];
		char *cp2 = strchr((char *)ep->value, ':');

		if (gethostname(hbuf, MAXHOSTNAMELEN) == -1) {
			perror("telnet: cannot get hostname");
			return (0);
		}
		hbuf[MAXHOSTNAMELEN-1] = '\0';
		cp = malloc(strlen(hbuf) + strlen(cp2) + 1);
		if (cp == NULL) {
			perror("telnet: cannot define DISPLAY variable");
			return (0);
		}
		(void) sprintf((char *)cp, "%s%s", hbuf, cp2);
		free(ep->value);
		ep->value = (unsigned char *)cp;
	}
	/*
	 * If LOGNAME is defined, but USER is not, then add
	 * USER with the value from LOGNAME.  We do this because the "accepted
	 * practice" is to always pass USER on the wire, but SVR4 uses
	 * LOGNAME by default.
	 */
	if ((ep = env_find((uchar_t *)"LOGNAME")) != NULL &&
		env_find((uchar_t *)"USER") == NULL) {
		if (env_define((unsigned char *)"USER", ep->value) != NULL)
			env_unexport((unsigned char *)"USER");
	}
	env_export((unsigned char *)"DISPLAY");
	env_export((unsigned char *)"PRINTER");

	return (1);
}

static struct env_lst *
env_define(var, value)
	unsigned char *var, *value;
{
	unsigned char *tmp_value;
	unsigned char *tmp_var;
	struct env_lst *ep;

	/*
	 * Allocate copies of arguments first, to make cleanup easier
	 * in the case of allocation errors.
	 */
	tmp_var = (unsigned char *)strdup((char *)var);
	if (tmp_var == NULL) {
		perror("telnet: can't copy environment variable name");
		return (NULL);
	}

	tmp_value = (unsigned char *)strdup((char *)value);
	if (tmp_value == NULL) {
		free(tmp_var);
		perror("telnet: can't copy environment variable value");
		return (NULL);
	}

	if (ep = env_find(var)) {
		if (ep->var)
			free(ep->var);
		if (ep->value)
			free(ep->value);
	} else {
		ep = malloc(sizeof (struct env_lst));
		if (ep == NULL) {
			perror("telnet: can't define environment variable");
			free(tmp_var);
			free(tmp_value);
			return (NULL);
		}

		ep->next = envlisthead.next;
		envlisthead.next = ep;
		ep->prev = &envlisthead;
		if (ep->next)
			ep->next->prev = ep;
	}
	ep->welldefined = opt_welldefined((char *)var);
	ep->export = 1;
	ep->var = tmp_var;
	ep->value = tmp_value;

	return (ep);
}

static void
env_undefine(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	if (ep = env_find(var)) {
		ep->prev->next = ep->next;
		if (ep->next)
			ep->next->prev = ep->prev;
		if (ep->var)
			free(ep->var);
		if (ep->value)
			free(ep->value);
		free(ep);
	}
}

static void
env_export(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	if (ep = env_find(var))
		ep->export = 1;
}

static void
env_unexport(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	if (ep = env_find(var))
		ep->export = 0;
}

static void
env_send(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	if (my_state_is_wont(TELOPT_NEW_ENVIRON)
#ifdef	OLD_ENVIRON
	    /* old style */ && my_state_is_wont(TELOPT_OLD_ENVIRON)
#endif
		/* no environ */) {
		(void) fprintf(stderr,
		    "Cannot send '%s': Telnet ENVIRON option not enabled\n",
									var);
		return;
	}
	ep = env_find(var);
	if (ep == 0) {
		(void) fprintf(stderr,
		    "Cannot send '%s': variable not defined\n", var);
		return;
	}
	env_opt_start_info();
	env_opt_add(ep->var);
	env_opt_end(0);
}

static void
env_list()
{
	register struct env_lst *ep;

	for (ep = envlisthead.next; ep; ep = ep->next) {
		(void) printf("%c %-20s %s\n", ep->export ? '*' : ' ',
					ep->var, ep->value);
	}
}

	unsigned char *
env_default(init, welldefined)
	int init;
{
	static struct env_lst *nep = NULL;

	if (init) {
		/* return value is not used */
		nep = &envlisthead;
		return (NULL);
	}
	if (nep) {
		while ((nep = nep->next) != NULL) {
			if (nep->export && (nep->welldefined == welldefined))
				return (nep->var);
		}
	}
	return (NULL);
}

	unsigned char *
env_getvalue(var)
	unsigned char *var;
{
	register struct env_lst *ep;

	if (ep = env_find(var))
		return (ep->value);
	return (NULL);
}

#if defined(OLD_ENVIRON) && defined(ENV_HACK)
static void
env_varval(what)
	unsigned char *what;
{
	extern int old_env_var, old_env_value, env_auto;
	int len = strlen((char *)what);

	if (len == 0)
		goto unknown;

	if (strncasecmp((char *)what, "status", len) == 0) {
		if (env_auto)
			(void) printf("%s%s", "VAR and VALUE are/will be ",
					"determined automatically\n");
		if (old_env_var == OLD_ENV_VAR)
			(void) printf(
			    "VAR and VALUE set to correct definitions\n");
		else
			(void) printf(
			    "VAR and VALUE definitions are reversed\n");
	} else if (strncasecmp((char *)what, "auto", len) == 0) {
		env_auto = 1;
		old_env_var = OLD_ENV_VALUE;
		old_env_value = OLD_ENV_VAR;
	} else if (strncasecmp((char *)what, "right", len) == 0) {
		env_auto = 0;
		old_env_var = OLD_ENV_VAR;
		old_env_value = OLD_ENV_VALUE;
	} else if (strncasecmp((char *)what, "wrong", len) == 0) {
		env_auto = 0;
		old_env_var = OLD_ENV_VALUE;
		old_env_value = OLD_ENV_VAR;
	} else {
unknown:
		(void) printf(
		    "Unknown \"varval\" command. (\"auto\", \"right\", "
		    "\"wrong\", \"status\")\n");
	}
}
#endif	/* OLD_ENVIRON && ENV_HACK */

/*
 * The AUTHENTICATE command.
 */

struct authlist {
	char	*name;
	char	*help;
	int	(*handler)();
	int	narg;
};

extern int auth_enable(char *);
extern int auth_disable(char *);
extern int auth_status(void);

static int auth_help(void);

static struct authlist AuthList[] = {
	{ "status",
	    "Display current status of authentication information",
	    auth_status,	0 },
	{ "disable",
	    "Disable an authentication type ('auth disable ?' for more)",
	    auth_disable,	1 },
	{ "enable",
	    "Enable an authentication type ('auth enable ?' for more)",
	    auth_enable,	1 },
	{ "help",	0,			auth_help,		0 },
	{ "?",	"Print help information",	auth_help,		0 },
	{ 0 },
};

static int
auth_help(void)
{
	struct authlist *c;

	for (c = AuthList; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
	return (0);
}


static int
auth_cmd(argc, argv)
	int  argc;
	char *argv[];
{
	struct authlist *c;

	if (argc < 2) {
		(void) fprintf(stderr, "Need an argument to 'auth' "
			"command.  'auth ?' for help.\n");
		return (0);
	}

	c = (struct authlist *)
		genget(argv[1], (char **)AuthList, sizeof (struct authlist));
	if (c == 0) {
		(void) fprintf(stderr,
		    "'%s': unknown argument ('auth ?' for help).\n",
		    argv[1]);
		return (0);
	}
	if (Ambiguous(c)) {
		(void) fprintf(stderr,
		    "'%s': ambiguous argument ('auth ?' for help).\n", argv[1]);
		return (0);
	}
	if (c->narg + 2 != argc) {
		(void) fprintf(stderr,
		    "Need %s%d argument%s to 'auth %s' command."
		    " 'auth ?' for help.\n",
		    c->narg + 2 < argc ? "only " : "",
		    c->narg, c->narg == 1 ? "" : "s", c->name);
		return (0);
	}
	return ((*c->handler)(argv[2], argv[3]));
}

/*
 * The FORWARD command.
 */

extern int forward_flags;

struct forwlist {
	char *name;
	char *help;
	int (*handler)();
	int f_flags;
};

static int forw_status(void);
static int forw_set(int);
static int forw_help(void);

static struct forwlist ForwList[] = {
	{"status",
		"Display current status of credential forwarding",
		forw_status, 0},
	{"disable",
		"Disable credential forwarding",
		forw_set, 0},
	{"enable",
		"Enable credential forwarding",
		forw_set, OPTS_FORWARD_CREDS},
	{"forwardable",
		"Enable credential forwarding of "
				"forwardable credentials",
		forw_set, OPTS_FORWARD_CREDS |	OPTS_FORWARDABLE_CREDS},
	{"help",
		0,
		forw_help, 0},
	{"?",
		"Print help information",
		forw_help, 0},
	{0},
};

static int
forw_status(void)
{
	if (forward_flags & OPTS_FORWARD_CREDS) {
		if (forward_flags & OPTS_FORWARDABLE_CREDS)
			(void) printf(gettext(
				"Credential forwarding of "
				"forwardable credentials enabled\n"));
		else
			(void) printf(gettext(
				"Credential forwarding enabled\n"));
	} else
		(void) printf(gettext("Credential forwarding disabled\n"));
	return (0);
}

static int
forw_set(int f_flags)
{
	forward_flags = f_flags;
	return (0);
}

static int
forw_help(void)
{
	struct forwlist *c;

	for (c = ForwList; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s\r\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
	return (0);
}

static int
forw_cmd(int argc, char *argv[])
{
	struct forwlist *c;

	if (argc < 2) {
		(void) fprintf(stderr, gettext(
			"Need an argument to 'forward' "
			"command.  'forward ?' for help.\n"));
		return (0);
	}
	c = (struct forwlist *)genget(argv[1], (char **)ForwList,
		sizeof (struct forwlist));
	if (c == 0) {
		(void) fprintf(stderr, gettext(
		    "'%s': unknown argument ('forward ?' for help).\n"),
		    argv[1]);
		return (0);
	}
	if (Ambiguous(c)) {
		(void) fprintf(stderr, gettext(
		    "'%s': ambiguous argument ('forward ?' for help).\n"),
		    argv[1]);
		return (0);
	}
	if (argc != 2) {
		(void) fprintf(stderr, gettext(
		    "No arguments needed to 'forward %s' command.  "
		    "'forward ?' for help.\n"), c->name);
		return (0);
	}
	return ((*c->handler) (c->f_flags));
}

/*
 * The ENCRYPT command.
 */

struct encryptlist {
	char	*name;
	char	*help;
	int	(*handler)();
	int	needconnect;
	int	minarg;
	int	maxarg;
};

static int EncryptHelp(void);

static struct encryptlist EncryptList[] = {
	{ "enable", "Enable encryption. ('encrypt enable ?' for more)",
						EncryptEnable, 1, 1, 2 },
	{ "disable", "Disable encryption. ('encrypt disable ?' for more)",
						EncryptDisable, 0, 1, 2 },
	{ "type", "Set encryption type. ('encrypt type ?' for more)",
						EncryptType, 0, 1, 2 },
	{ "start", "Start encryption. ('encrypt start ?' for more)",
						EncryptStart, 1, 0, 1 },
	{ "stop", "Stop encryption. ('encrypt stop ?' for more)",
						EncryptStop, 1, 0, 1 },
	{ "input", "Start encrypting the input stream",
						EncryptStartInput, 1, 0, 0 },
	{ "-input", "Stop encrypting the input stream",
						EncryptStopInput, 1, 0, 0 },
	{ "output", "Start encrypting the output stream",
						EncryptStartOutput, 1, 0, 0 },
	{ "-output", "Stop encrypting the output stream",
						EncryptStopOutput, 1, 0, 0 },

	{ "status",	"Display current status of encryption information",
						EncryptStatus,	0, 0, 0 },
	{ "help",	0,
						EncryptHelp,	0, 0, 0 },
	{ "?",	"Print help information",	EncryptHelp,	0, 0, 0 },
	{ 0 },
};

static int
EncryptHelp(void)
{
	struct encryptlist *c;

	for (c = EncryptList; c->name; c++) {
		if (c->help) {
			if (*c->help)
				(void) printf("%-15s %s\n", c->name, c->help);
			else
				(void) printf("\n");
		}
	}
	return (0);
}

static int
encrypt_cmd(int  argc, char *argv[])
{
	struct encryptlist *c;

	if (argc < 2) {
		(void) fprintf(stderr, gettext(
			"Need an argument to 'encrypt' command.  "
			"'encrypt ?' for help.\n"));
		return (0);
	}

	c = (struct encryptlist *)
	    genget(argv[1], (char **)EncryptList, sizeof (struct encryptlist));
	if (c == 0) {
		(void) fprintf(stderr, gettext(
		    "'%s': unknown argument ('encrypt ?' for help).\n"),
		    argv[1]);
		return (0);
	}
	if (Ambiguous(c)) {
		(void) fprintf(stderr, gettext(
		    "'%s': ambiguous argument ('encrypt ?' for help).\n"),
		    argv[1]);
		return (0);
	}
	argc -= 2;
	if (argc < c->minarg || argc > c->maxarg) {
		if (c->minarg == c->maxarg) {
			(void) fprintf(stderr, gettext("Need %s%d %s "),
			c->minarg < argc ?
			gettext("only ") : "", c->minarg,
			c->minarg == 1 ?
			gettext("argument") : gettext("arguments"));
		} else {
			(void) fprintf(stderr,
			    gettext("Need %s%d-%d arguments "),
			c->maxarg < argc ?
			gettext("only ") : "", c->minarg, c->maxarg);
		}
		(void) fprintf(stderr, gettext(
		    "to 'encrypt %s' command.  'encrypt ?' for help.\n"),
		    c->name);
		return (0);
	}
	if (c->needconnect && !connected) {
		if (!(argc &&
		    (isprefix(argv[2], "help") || isprefix(argv[2], "?")))) {
			(void) printf(
			    gettext("?Need to be connected first.\n"));
			return (0);
		}
	}
	return ((*c->handler)(argc > 0 ? argv[2] : 0,
	    argc > 1 ? argv[3] : 0, argc > 2 ? argv[4] : 0));
}

/*
 * Print status about the connection.
 */
static int
status(int argc, char *argv[])
{
	if (connected) {
		(void) printf("Connected to %s.\n", hostname);
		if ((argc < 2) || strcmp(argv[1], "notmuch")) {
			int mode = getconnmode();

			if (my_want_state_is_will(TELOPT_LINEMODE)) {
				(void) printf(
				    "Operating with LINEMODE option\n");
				(void) printf(
				    "%s line editing\n", (mode&MODE_EDIT) ?
				    "Local" : "No");
				(void) printf("%s catching of signals\n",
				    (mode&MODE_TRAPSIG) ? "Local" : "No");
				slcstate();
#ifdef	KLUDGELINEMODE
			} else if (kludgelinemode &&
			    my_want_state_is_dont(TELOPT_SGA)) {
				(void) printf(
				    "Operating in obsolete linemode\n");
#endif
			} else {
				(void) printf(
					"Operating in single character mode\n");
				if (localchars)
					(void) printf(
					"Catching signals locally\n");
			}
			(void) printf("%s character echo\n", (mode&MODE_ECHO) ?
			    "Local" : "Remote");
			if (my_want_state_is_will(TELOPT_LFLOW))
				(void) printf("%s flow control\n",
				    (mode&MODE_FLOW) ? "Local" : "No");

				encrypt_display();
		}
	} else {
		(void) printf("No connection.\n");
	}
	if (rlogin != _POSIX_VDISABLE)
		(void) printf("Escape character is '%s'.\n", control(rlogin));
	else
		(void) printf(
		    "Escape character is '%s'.\n", esc_control(escape));
	(void) fflush(stdout);
	return (1);
}

/*
 * Parse the user input (cmd_line_input) which should:
 * - start with the target host, or with "@" or "!@" followed by at least one
 *   gateway.
 * - each host (can be literal address or hostname) can be separated by ",",
 *   "@", or ",@".
 * Note that the last host is the target, all the others (if any ) are the
 * gateways.
 *
 * Returns:	-1	if a library call fails, too many gateways, or parse
 *			error
 *		num_gw	otherwise
 * On successful return, hostname_list points to a list of hosts (last one being
 * the target, others gateways), src_rtng_type points to the type of source
 * routing (strict vs. loose)
 */
static int
parse_input(char *cmd_line_input, char **hostname_list, uchar_t *src_rtng_type)
{
	char hname[MAXHOSTNAMELEN + 1];
	char *cp;
	int gw_count;
	int i;

	gw_count = 0;
	cp = cmd_line_input;

	/*
	 * Defining ICMD generates the Itelnet binary, the special version of
	 * telnet which is used with firewall proxy.
	 * If ICMD is defined, parse_input will treat the whole cmd_line_input
	 * as the target host and set the num_gw to 0. Therefore, none of the
	 * source routing related code paths will be executed.
	 */
#ifndef ICMD
	if (*cp == '@') {
		*src_rtng_type = IPOPT_LSRR;
		cp++;
	} else if (*cp == '!') {
		*src_rtng_type = IPOPT_SSRR;

		/* "!" must be followed by '@' */
		if (*(cp + 1) != '@')
			goto parse_error;
		cp += 2;
	} else {
#endif	/* ICMD */
		/* no gateways, just the target */
		hostname_list[0] = strdup(cp);
		if (hostname_list[0] == NULL) {
			perror("telnet: copying host name");
			return (-1);
		}
		return (0);
#ifndef ICMD
	}

	while (*cp != '\0') {
		/*
		 * Identify each gateway separated by ",", "@" or ",@" and
		 * store in hname[].
		 */
		i = 0;
		while (*cp != '@' && *cp != ',' && *cp != '\0') {
			hname[i++] = *cp++;
			if (i > MAXHOSTNAMELEN)
				goto parse_error;
		}
		hname[i] = '\0';

		/*
		 * Two consecutive delimiters which result in a 0 length hname
		 * is a parse error.
		 */
		if (i == 0)
			goto parse_error;

		hostname_list[gw_count] = strdup(hname);
		if (hostname_list[gw_count] == NULL) {
			perror("telnet: copying hostname from list");
			return (-1);
		}

		if (++gw_count > MAXMAX_GATEWAY) {
			(void) fprintf(stderr, "telnet: too many gateways\n");
			return (-1);
		}

		/* Jump over the next delimiter. */
		if (*cp != '\0') {
			/* ...gw1,@gw2... accepted */
			if (*cp == ',' && *(cp + 1) == '@')
				cp += 2;
			else
				cp++;
		}
	}

	/* discount the target */
	gw_count--;

	/* Any input starting with '!@' or '@' must have at least one gateway */
	if (gw_count <= 0)
		goto parse_error;

	return (gw_count);

parse_error:
	(void) printf("Bad source route option: %s\n", cmd_line_input);
	return (-1);
#endif	/* ICMD */
}

/*
 * Resolves the target and gateway addresses, determines what type of addresses
 * (ALL_ADDRS, ONLY_V6, ONLY_V4) telnet will be trying to connect.
 *
 * Returns:	pointer to resolved target	if name resolutions succeed
 *		NULL				if name resolutions fail or
 *						a library function call fails
 *
 * The last host in the hostname_list is the target. After resolving the target,
 * determines for what type of addresses it should try to resolve gateways. It
 * resolves gateway addresses and picks one address for each desired address
 * type and stores in the array pointed by gw_addrsp. Also, this 'type of
 * addresses' is pointed by addr_type argument on successful return.
 */
static struct addrinfo *
resolve_hosts(char **hostname_list, int num_gw, struct gateway **gw_addrsp,
    int *addr_type, const char *portp)
{
	struct gateway *gw_addrs = NULL;
	struct gateway *gw;
	/* whether we already picked an IPv4 address for the current gateway */
	boolean_t got_v4_addr;
	boolean_t got_v6_addr;
	/* whether we need to get an IPv4 address for the current gateway */
	boolean_t need_v4_addr = B_FALSE;
	boolean_t need_v6_addr = B_FALSE;
	int res_failed_at4;	/* save which gateway failed to resolve */
	int res_failed_at6;
	boolean_t is_v4mapped;
	struct in6_addr *v6addrp;
	struct in_addr *v4addrp;
	int error_num;
	int i;
	int rc;
	struct addrinfo *res, *host, *gateway, *addr;
	struct addrinfo hints;

	*addr_type = ALL_ADDRS;

	memset(&hints, 0, sizeof (hints));
	hints.ai_flags = AI_CANONNAME; /* used for config files, diags */
	hints.ai_socktype = SOCK_STREAM;
	rc = getaddrinfo(hostname_list[num_gw],
	    (portp != NULL) ? portp : "telnet", &hints, &res);
	if (rc != 0) {
		if (hostname_list[num_gw] != NULL &&
		    *hostname_list[num_gw] != '\0')
			(void) fprintf(stderr, "%s: ", hostname_list[num_gw]);
		(void) fprintf(stderr, "%s\n", gai_strerror(rc));
		return (NULL);
	}

	/*
	 * Let's see what type of addresses we got for the target. This
	 * determines what type of addresses we'd like to resolve gateways
	 * later.
	 */
	for (host = res; host != NULL; host = host->ai_next) {
		struct sockaddr_in6 *s6;

		s6 = (struct sockaddr_in6 *)host->ai_addr;

		if (host->ai_addr->sa_family == AF_INET ||
		    IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr))
			need_v4_addr = B_TRUE;
		else
			need_v6_addr = B_TRUE;

		/*
		 * Let's stop after seeing we need both IPv6 and IPv4.
		 */
		if (need_v4_addr && need_v6_addr)
			break;
	}

	if (num_gw > 0) {
		/*
		 * In the prepare_optbuf(), we'll store the IPv4 address of the
		 * target in the last slot of gw_addrs array. Therefore we need
		 * space for num_gw+1 hosts.
		 */
		gw_addrs = calloc(num_gw + 1, sizeof (struct gateway));
		if (gw_addrs == NULL) {
			perror("telnet: calloc");
			freeaddrinfo(res);
			return (NULL);
		}
	}

	/*
	 * Now we'll go through all the gateways and try to resolve them to
	 * the desired address types.
	 */
	gw = gw_addrs;

	/* -1 means 'no address resolution failure yet' */
	res_failed_at4 = -1;
	res_failed_at6 = -1;
	for (i = 0; i < num_gw; i++) {
		rc = getaddrinfo(hostname_list[i], NULL, NULL, &gateway);
		if (rc != 0) {
			if (hostname_list[i] != NULL &&
			    *hostname_list[i] != '\0')
				(void) fprintf(stderr, "%s: ",
				    hostname_list[i]);
			(void) fprintf(stderr, "bad address\n");
			return (NULL);
		}

		/*
		 * Initially we have no address of any type for this gateway.
		 */
		got_v6_addr = B_FALSE;
		got_v4_addr = B_FALSE;

		/*
		 * Let's go through all the addresses of this gateway.
		 * Use the first address which matches the needed family.
		 */
		for (addr = gateway; addr != NULL; addr = addr->ai_next) {
			/*LINTED*/
			v6addrp = &((struct sockaddr_in6 *)addr->ai_addr)->
			    sin6_addr;
			v4addrp = &((struct sockaddr_in *)addr->ai_addr)->
			    sin_addr;

			if (addr->ai_family == AF_INET6)
				is_v4mapped = IN6_IS_ADDR_V4MAPPED(v6addrp);
			else
				is_v4mapped = B_FALSE;

			/*
			 * If we need to determine an IPv4 address and haven't
			 * found one yet and this is a IPv4-mapped IPv6 address,
			 * then bingo!
			 */
			if (need_v4_addr && !got_v4_addr) {
				if (is_v4mapped) {
					IN6_V4MAPPED_TO_INADDR(v6addrp,
					    &gw->gw_addr);
					got_v4_addr = B_TRUE;
				} else if (addr->ai_family = AF_INET) {
					gw->gw_addr = *v4addrp;
					got_v4_addr = B_TRUE;
				}
			}

			if (need_v6_addr && !got_v6_addr &&
			    addr->ai_family == AF_INET6) {
				gw->gw_addr6 = *v6addrp;
				got_v6_addr = B_TRUE;
			}

			/*
			 * Let's stop if we got all what we looked for.
			 */
			if ((!need_v4_addr || got_v4_addr) &&
			    (!need_v6_addr || got_v6_addr))
				break;
		}

		/*
		 * We needed an IPv4 address for this gateway but couldn't
		 * find one.
		 */
		if (need_v4_addr && !got_v4_addr) {
			res_failed_at4 = i;
			/*
			 * Since we couldn't resolve a gateway to IPv4 address
			 * we can't use IPv4 at all. Therefore we no longer
			 * need IPv4 addresses for any of the gateways.
			 */
			need_v4_addr = B_FALSE;
		}

		if (need_v6_addr && !got_v6_addr) {
			res_failed_at6 = i;
			need_v6_addr = B_FALSE;
		}

		/*
		 * If some gateways don't resolve to any of the desired
		 * address types, we fail.
		 */
		if (!need_v4_addr && !need_v6_addr) {
			if (res_failed_at6 != -1) {
				(void) fprintf(stderr,
				    "%s: Host doesn't have any IPv6 address\n",
				    hostname_list[res_failed_at6]);
			}
			if (res_failed_at4 != -1) {
				(void) fprintf(stderr,
				    "%s: Host doesn't have any IPv4 address\n",
				    hostname_list[res_failed_at4]);
			}
			free(gw_addrs);
			return (NULL);
		}

		gw++;
	}

	*gw_addrsp = gw_addrs;

	/*
	 * When we get here, need_v4_addr and need_v6_addr have their final
	 * values based on the name resolution of the target and gateways.
	 */
	if (need_v4_addr && need_v6_addr)
		*addr_type = ALL_ADDRS;
	else if (need_v4_addr && !need_v6_addr)
		*addr_type = ONLY_V4;
	else if (!need_v4_addr && need_v6_addr)
		*addr_type = ONLY_V6;

	return (res);
}


/*
 * Initializes the buffer pointed by opt_bufpp for a IPv4 option of type
 * src_rtng_type using the gateway addresses stored in gw_addrs. If no buffer
 * is passed, it allocates one. If a buffer is passed, checks if it's big
 * enough.
 * On return opt_buf_len points to the buffer length which we need later for the
 * setsockopt() call, and opt_bufpp points to the newly allocated or already
 * passed buffer. Returns B_FALSE if a library function call fails or passed
 * buffer is not big enough, B_TRUE otherwise.
 */
static boolean_t
prepare_optbuf(struct gateway *gw_addrs, int num_gw, char **opt_bufpp,
    size_t *opt_buf_len, struct in_addr *target, uchar_t src_rtng_type)
{
	struct ip_sourceroute *sr_opt;
	size_t needed_buflen;
	int i;

	/*
	 * We have (num_gw + 1) IP addresses in the buffer because the number
	 * of gateway addresses we put in the option buffer includes the target
	 * address.
	 * At the time of setsockopt() call, passed option length needs to be
	 * multiple of 4 bytes. Therefore we need one IPOPT_NOP before (or
	 * after) IPOPT_LSRR.
	 * 1 = preceding 1 byte of IPOPT_NOP
	 * 3 = 1 (code) + 1 (len) + 1 (ptr)
	 */
	needed_buflen = 1 + 3 + (num_gw + 1) * sizeof (struct in_addr);

	if (*opt_bufpp != NULL) {
		/* check if the passed buffer is big enough */
		if (*opt_buf_len < needed_buflen) {
			(void) fprintf(stderr,
			    "telnet: buffer too small for IPv4 source routing "
			    "option\n");
			return (B_FALSE);
		}
	} else {
		*opt_bufpp = malloc(needed_buflen);
		if (*opt_bufpp == NULL) {
			perror("telnet: malloc");
			return (B_FALSE);
		}
	}

	*opt_buf_len = needed_buflen;

	/* final hop is the target */
	gw_addrs[num_gw].gw_addr = *target;

	*opt_bufpp[0] = IPOPT_NOP;
	/* IPOPT_LSRR starts right after IPOPT_NOP */
	sr_opt = (struct ip_sourceroute *)(*opt_bufpp + 1);
	sr_opt->ipsr_code = src_rtng_type;
	/* discount the 1 byte of IPOPT_NOP */
	sr_opt->ipsr_len = needed_buflen - 1;
	sr_opt->ipsr_ptr = IPOPT_MINOFF;

	/* copy the gateways into the optlist */
	for (i = 0; i < num_gw + 1; i++) {
		(void) bcopy(&gw_addrs[i].gw_addr, &sr_opt->ipsr_addrs[i],
		    sizeof (struct in_addr));
	}

	return (B_TRUE);
}

/*
 * Initializes the buffer pointed by opt_bufpp for a IPv6 routing header option
 * using the gateway addresses stored in gw_addrs. If no buffer is passed, it
 * allocates one. If a buffer is passed, checks if it's big enough.
 * On return opt_buf_len points to the buffer length which we need later for the
 * setsockopt() call, and opt_bufpp points to the newly allocated or already
 * passed buffer. Returns B_FALSE if a library function call fails or passed
 * buffer is not big enough, B_TRUE otherwise.
 */
static boolean_t
prepare_optbuf6(struct gateway *gw_addrs, int num_gw, char **opt_bufpp,
    size_t *opt_buf_len)
{
	char *opt_bufp;
	size_t needed_buflen;
	int i;

	needed_buflen = inet6_rth_space(IPV6_RTHDR_TYPE_0, num_gw);

	if (*opt_bufpp != NULL) {
		/* check if the passed buffer is big enough */
		if (*opt_buf_len < needed_buflen) {
			(void) fprintf(stderr,
			    "telnet: buffer too small for IPv6 routing "
			    "header option\n");
			return (B_FALSE);
		}
	} else {
		*opt_bufpp = malloc(needed_buflen);
		if (*opt_bufpp == NULL) {
			perror("telnet: malloc");
			return (B_FALSE);
		}
	}
	*opt_buf_len = needed_buflen;
	opt_bufp = *opt_bufpp;

	/*
	 * Initialize the buffer to be used for IPv6 routing header type 0.
	 */
	if (inet6_rth_init(opt_bufp, needed_buflen, IPV6_RTHDR_TYPE_0,
	    num_gw) == NULL) {
		perror("telnet: inet6_rth_init");
		return (B_FALSE);
	}

	/*
	 * Add gateways one by one.
	 */
	for (i = 0; i < num_gw; i++) {
		if (inet6_rth_add(opt_bufp, &gw_addrs[i].gw_addr6) == -1) {
			perror("telnet: inet6_rth_add");
			return (B_FALSE);
		}
	}

	/* successful operation */
	return (B_TRUE);
}

int
tn(argc, argv)
	int argc;
	char *argv[];
{
	struct addrinfo *host = NULL;
	struct addrinfo *h;
	struct sockaddr_in6 sin6;
	struct sockaddr_in sin;
	struct in6_addr addr6;
	struct in_addr addr;
	void *addrp;
	struct gateway *gw_addrs;
	char *hostname_list[MAXMAX_GATEWAY + 1] = {NULL};
	char *opt_buf6 = NULL;		/* used for IPv6 routing header */
	size_t opt_buf_len6 = 0;
	uchar_t src_rtng_type;		/* type of IPv4 source routing */
	struct servent *sp = 0;
	char *opt_buf = NULL;		/* used for IPv4 source routing */
	size_t opt_buf_len = 0;
	char *cmd;
	char *hostp = NULL;
	char *portp = NULL;
	char *user = NULL;
#ifdef	ICMD
	char *itelnet_host;
	char *real_host;
	unsigned short dest_port;
#endif	/* ICMD */
	/*
	 * The two strings at the end of this function are 24 and 39
	 * characters long (minus the %.*s in the format strings).  Add
	 * one for the null terminator making the longest print string 40.
	 */
	char buf[MAXHOSTNAMELEN+40];
	/*
	 * In the case of ICMD defined, dest_port will contain the real port
	 * we are trying to telnet to, and target_port will contain
	 * "telnet-passthru" port.
	 */
	unsigned short target_port;
	char abuf[INET6_ADDRSTRLEN];
	int num_gw;
	int ret_val;
	boolean_t is_v4mapped;
	/*
	 * Type of addresses we'll try to connect to (ALL_ADDRS, ONLY_V6,
	 * ONLY_V4).
	 */
	int addr_type;

	/* clear the socket address prior to use */
	(void) memset(&sin6, '\0', sizeof (sin6));
	sin6.sin6_family = AF_INET6;

	(void) memset(&sin, '\0', sizeof (sin));
	sin.sin_family = AF_INET;

	if (connected) {
		(void) printf("?Already connected to %s\n", hostname);
		return (0);
	}
#ifdef	ICMD
	itelnet_host = getenv("INTERNET_HOST");
	if (itelnet_host == NULL || itelnet_host[0] == '\0') {
		(void) printf("INTERNET_HOST environment variable undefined\n");
		goto tn_exit;
	}
#endif
	if (argc < 2) {
		(void) printf("(to) ");
		if (GetAndAppendString(&line, &linesize, "open ",
			stdin) == NULL) {
			if (!feof(stdin)) {
				perror("telnet");
				goto tn_exit;
			}
		}
		makeargv();
		argc = margc;
		argv = margv;
	}
	cmd = *argv;
	--argc; ++argv;
	while (argc) {
		if (isprefix(*argv, "help") == 4 || isprefix(*argv, "?") == 1)
			goto usage;
		if (strcmp(*argv, "-l") == 0) {
			--argc; ++argv;
			if (argc == 0)
				goto usage;
			user = *argv++;
			--argc;
			continue;
		}
		if (strcmp(*argv, "-a") == 0) {
			--argc; ++argv;
			autologin = autologin_set = 1;
			continue;
		}
		if (hostp == 0) {
			hostp = *argv++;
			--argc;
			continue;
		}
		if (portp == 0) {
			portp = *argv++;
			--argc;
			/*
			 * Do we treat this like a telnet port or raw?
			 */
			if (*portp == '-') {
				portp++;
				telnetport = 1;
			} else
				telnetport = 0;
			continue;
		}
usage:
		(void) printf(
		    "usage: %s [-l user] [-a] host-name [port]\n", cmd);
		goto tn_exit;
	}
	if (hostp == 0)
		goto usage;

#ifdef ICMD
	/*
	 * For setup phase treat the relay host as the target host.
	 */
	real_host = hostp;
	hostp = itelnet_host;
#endif
	num_gw = parse_input(hostp, hostname_list, &src_rtng_type);
	if (num_gw < 0) {
		goto tn_exit;
	}

	/* Last host in the hostname_list is the target */
	hostp = hostname_list[num_gw];

	host = resolve_hosts(hostname_list, num_gw, &gw_addrs, &addr_type,
	    portp);
	if (host == NULL) {
		goto tn_exit;
	}

	/*
	 * Check if number of gateways is less than max. available
	 */
	if ((addr_type == ALL_ADDRS || addr_type == ONLY_V6) &&
	    num_gw > MAX_GATEWAY6) {
		(void) fprintf(stderr, "telnet: too many IPv6 gateways\n");
		goto tn_exit;
	}

	if ((addr_type == ALL_ADDRS || addr_type == ONLY_V4) &&
	    num_gw > MAX_GATEWAY) {
		(void) fprintf(stderr, "telnet: too many IPv4 gateways\n");
		goto tn_exit;
	}

	/*
	 * If we pass a literal IPv4 address to getaddrinfo(), in the
	 * returned addrinfo structure, hostname is the IPv4-mapped IPv6
	 * address string. We prefer to preserve the literal IPv4 address
	 * string as the hostname.  Also, if the hostname entered by the
	 * user is IPv4-mapped IPv6 address, we'll downgrade it to IPv4
	 * address.
	 */
	if (inet_addr(hostp) != (in_addr_t)-1) {
		/* this is a literal IPv4 address */
		(void) strlcpy(_hostname, hostp, sizeof (_hostname));
	} else if ((inet_pton(AF_INET6, hostp, &addr6) > 0) &&
		    IN6_IS_ADDR_V4MAPPED(&addr6)) {
		/* this is a IPv4-mapped IPv6 address */
		IN6_V4MAPPED_TO_INADDR(&addr6, &addr);
		(void) inet_ntop(AF_INET, &addr, _hostname, sizeof (_hostname));
	} else {
		(void) strlcpy(_hostname, host->ai_canonname,
		    sizeof (_hostname));
	}
	hostname = _hostname;

	if (portp == NULL) {
		telnetport = 1;
	}

	if (host->ai_family == AF_INET) {
		target_port = ((struct sockaddr_in *)(host->ai_addr))->sin_port;
	} else {
		target_port = ((struct sockaddr_in6 *)(host->ai_addr))
		    ->sin6_port;
	}

#ifdef ICMD
	/*
	 * Since we pass the port number as an ascii string to the proxy,
	 *  we need it in host format.
	 */
	dest_port = ntohs(target_port);
	sp = getservbyname("telnet-passthru", "tcp");
	if (sp == 0) {
		(void) fprintf(stderr,
		    "telnet: tcp/telnet-passthru: unknown service\n");
			goto tn_exit;
	}
	target_port = sp->s_port;
#endif
	h = host;

	/*
	 * For IPv6 source routing, we need to initialize option buffer only
	 * once.
	 */
	if (num_gw > 0 && (addr_type == ALL_ADDRS || addr_type == ONLY_V6)) {
		if (!prepare_optbuf6(gw_addrs, num_gw, &opt_buf6,
		    &opt_buf_len6)) {
			goto tn_exit;
		}
	}

	/*
	 * We procure the Kerberos config files options only
	 * if the user has choosen Krb5 authentication.
	 */
	if (krb5auth_flag > 0) {
		krb5_profile_get_options(hostname, telnet_krb5_realm,
			config_file_options);
	}

	if (encrypt_flag) {
		extern boolean_t auth_enable_encrypt;
		if (krb5_privacy_allowed()) {
			encrypt_auto(1);
			decrypt_auto(1);
			wantencryption = B_TRUE;
			autologin = 1;
			auth_enable_encrypt = B_TRUE;
		} else {
			(void) fprintf(stderr, gettext(
			    "%s:Encryption not supported.\n"), prompt);
			exit(1);
			}
	}

	if (forward_flag && forwardable_flag) {
		(void) fprintf(stderr, gettext(
			"Error in krb5 configuration file. "
			"Both forward and forwardable are set.\n"));
		exit(1);
	}
	if (forwardable_flag) {
		forward_flags |= OPTS_FORWARD_CREDS | OPTS_FORWARDABLE_CREDS;
	} else if (forward_flag)
		forward_flags |= OPTS_FORWARD_CREDS;


	do {
		/*
		 * Search for an address of desired type in the IP address list
		 * of the target.
		 */
		while (h != NULL) {
			struct sockaddr_in6 *addr;

			addr = (struct sockaddr_in6 *)h->ai_addr;

			if (h->ai_family == AF_INET6)
				is_v4mapped =
				    IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr);
			else
				is_v4mapped = B_FALSE;

			if (addr_type == ALL_ADDRS ||
			    (addr_type == ONLY_V6 &&
				h->ai_family == AF_INET6) ||
			    (addr_type == ONLY_V4 &&
				(h->ai_family == AF_INET || is_v4mapped)))
				break;

			/* skip undesired typed addresses */
			h = h->ai_next;
		}

		if (h == NULL) {
			fprintf(stderr,
			    "telnet: Unable to connect to remote host");
			goto tn_exit;
		}

		/*
		 * We need to open a socket with a family matching the type of
		 * address we are trying to connect to. This is because we
		 * deal with IPv4 options and IPv6 extension headers.
		 */
		if (h->ai_family == AF_INET) {
			addrp = &((struct sockaddr_in *)(h->ai_addr))->sin_addr;
			((struct sockaddr_in *)(h->ai_addr))->sin_port =
			    target_port;
		} else {
			addrp = &((struct sockaddr_in6 *)(h->ai_addr))
			    ->sin6_addr;
			((struct sockaddr_in6 *)(h->ai_addr))->sin6_port =
			    target_port;
		}

		(void) printf("Trying %s...\n", inet_ntop(h->ai_family,
		    addrp, abuf, sizeof (abuf)));

		net = socket(h->ai_family, SOCK_STREAM, 0);

		if (net < 0) {
			perror("telnet: socket");
			goto tn_exit;
		}
#ifndef ICMD
		if (num_gw > 0) {
			if (h->ai_family == AF_INET || is_v4mapped) {
				if (!prepare_optbuf(gw_addrs, num_gw, &opt_buf,
				    &opt_buf_len, addrp, src_rtng_type)) {
					goto tn_exit;
				}

				if (setsockopt(net, IPPROTO_IP, IP_OPTIONS,
				    opt_buf, opt_buf_len) < 0)
					perror("setsockopt (IP_OPTIONS)");
			} else {
				if (setsockopt(net, IPPROTO_IPV6, IPV6_RTHDR,
				    opt_buf6, opt_buf_len6) < 0)
					perror("setsockopt (IPV6_RTHDR)");
			}
		}
#endif
#if	defined(USE_TOS)
		if (is_v4mapped) {
			if (tos < 0)
				tos = 020;	/* Low Delay bit */
			if (tos &&
			    (setsockopt(net, IPPROTO_IP, IP_TOS,
			    &tos, sizeof (int)) < 0) &&
			    (errno != ENOPROTOOPT))
				perror("telnet: setsockopt (IP_TOS) (ignored)");
		}
#endif	/* defined(USE_TOS) */

		if (debug && SetSockOpt(net, SOL_SOCKET, SO_DEBUG, 1) < 0) {
			perror("setsockopt (SO_DEBUG)");
		}

		ret_val = connect(net, h->ai_addr, h->ai_addrlen);

		/*
		 * If failed, try the next address of the target.
		 */
		if (ret_val < 0) {
			Close(&net);
			if (h->ai_next != NULL) {

				int oerrno = errno;

				(void) fprintf(stderr,
				    "telnet: connect to address %s: ", abuf);
				errno = oerrno;
				perror((char *)0);

				h = h->ai_next;
				continue;
			}
			perror("telnet: Unable to connect to remote host");
			goto tn_exit;
		}
		connected++;
	} while (connected == 0);
	freeaddrinfo(host);
	host = NULL;
#ifdef ICMD
	/*
	 * Do initial protocol to connect to farther end...
	 */
	{
		char buf[1024];
		(void) sprintf(buf, "%s %d\n", real_host, (int)dest_port);
		write(net, buf, strlen(buf));
	}
#endif
	if (cmdrc(hostp, hostname) != 0)
		goto tn_exit;
	FreeHostnameList(hostname_list);
	if (autologin && user == NULL) {
		struct passwd *pw;

		user = getenv("LOGNAME");
		if (user == NULL ||
		    ((pw = getpwnam(user)) != NULL) &&
		    pw->pw_uid != getuid()) {
			if (pw = getpwuid(getuid()))
				user = pw->pw_name;
			else
				user = NULL;
		}
	}

	if (user) {
		if (env_define((unsigned char *)"USER", (unsigned char *)user))
			env_export((unsigned char *)"USER");
		else {
			/* Clean up and exit. */
			Close(&net);
			(void) snprintf(buf, sizeof (buf),
			    "Connection to %.*s closed.\n",
			    MAXHOSTNAMELEN, hostname);
			ExitString(buf, EXIT_FAILURE);

			/* NOTREACHED */
		}
	}
	(void) call(3, status, "status", "notmuch");
	if (setjmp(peerdied) == 0)
		telnet(user);

	Close(&net);

	(void) snprintf(buf, sizeof (buf),
	    "Connection to %.*s closed by foreign host.\n",
	    MAXHOSTNAMELEN, hostname);
	ExitString(buf, EXIT_FAILURE);

	/*NOTREACHED*/

tn_exit:
	FreeHostnameList(hostname_list);
	Close(&net);
	connected = 0;
	if (host != NULL)
		freeaddrinfo(host);
	return (0);
}

#define	HELPINDENT (sizeof ("connect"))

static char openhelp[] = "connect to a site";
static char closehelp[] = "close current connection";
static char logouthelp[] =
	    "forcibly logout remote user and close the connection";
static char quithelp[] = "exit telnet";
static char statushelp[] = "print status information";
static char helphelp[] = "print help information";
static char sendhelp[] =
	    "transmit special characters ('send ?' for more)";
static char sethelp[] =  "set operating parameters ('set ?' for more)";
static char unsethelp[] = "unset operating parameters ('unset ?' for more)";
static char togglestring[] =
	    "toggle operating parameters ('toggle ?' for more)";
static char slchelp[] = "change state of special charaters ('slc ?' for more)";
static char displayhelp[] = "display operating parameters";
static char authhelp[] =
	    "turn on (off) authentication ('auth ?' for more)";
static char forwardhelp[] =
	    "turn on (off) credential forwarding ('forward ?' for more)";
static char encrypthelp[] =
	    "turn on (off) encryption ('encrypt ?' for more)";
static char zhelp[] = "suspend telnet";
static char shellhelp[] = "invoke a subshell";
static char envhelp[] = "change environment variables ('environ ?' for more)";
static char modestring[] =
	    "try to enter line or character mode ('mode ?' for more)";

static int	help();

static Command cmdtab[] = {
	{ "close",	closehelp,	bye,		1 },
	{ "logout",	logouthelp,	logout,		1 },
	{ "display",	displayhelp,	display,	0 },
	{ "mode",	modestring,	modecmd,	0 },
	{ "open",	openhelp,	tn,		0 },
	{ "quit",	quithelp,	quit,		0 },
	{ "send",	sendhelp,	sendcmd,	0 },
	{ "set",	sethelp,	setcmd,		0 },
	{ "unset",	unsethelp,	unsetcmd,	0 },
	{ "status",	statushelp,	status,		0 },
	{ "toggle",	togglestring,	toggle,		0 },
	{ "slc",	slchelp,	slccmd,		0 },
	{ "auth",	authhelp,	auth_cmd,	0 },
	{ "encrypt",	encrypthelp,	encrypt_cmd,	0 },
	{ "forward",	forwardhelp,	forw_cmd,	0 },
	{ "z",		zhelp,		suspend,	0 },
	{ "!",		shellhelp,	shell,		0 },
	{ "environ",	envhelp,	env_cmd,	0 },
	{ "?",		helphelp,	help,		0 },
	0
};


static Command cmdtab2[] = {
	{ "help",	0,		help,		0 },
	{ "escape",	0,		setescape,	0 },
	{ "crmod",	0,		togcrmod,	0 },
	0
};


/*
 * Call routine with argc, argv set from args.
 * Uses /usr/include/stdarg.h
 */
#define	MAXVARGS	100
/*VARARGS1*/
static void
call(int n_ptrs, ...)
{
	va_list ap;
	typedef int (*intrtn_t)();
	intrtn_t routine;
	char *args[MAXVARGS+1];	/* leave 1 for trailing NULL */
	int argno = 0;

	if (n_ptrs > MAXVARGS)
		n_ptrs = MAXVARGS;
	va_start(ap, n_ptrs);

	routine = (va_arg(ap, intrtn_t)); /* extract the routine's name */
	n_ptrs--;

	while (argno < n_ptrs)	/* extract the routine's args */
		args[argno++] = va_arg(ap, char *);
	args[argno] = NULL;	/* NULL terminate for good luck */
	va_end(ap);

	(*routine)(argno, args);
}


static Command *
getcmd(name)
	char *name;
{
	Command *cm;

	if (cm = (Command *) genget(name, (char **)cmdtab, sizeof (Command)))
		return (cm);
	return (Command *) genget(name, (char **)cmdtab2, sizeof (Command));
}

void
command(top, tbuf, cnt)
	int top;
	char *tbuf;
	int cnt;
{
	Command *c;

	setcommandmode();
	if (!top) {
		(void) putchar('\n');
	} else {
		(void) signal(SIGINT, SIG_DFL);
		(void) signal(SIGQUIT, SIG_DFL);
	}
	for (;;) {
		if (rlogin == _POSIX_VDISABLE)
			(void) printf("%s> ", prompt);
		if (tbuf) {
			char *cp;
			if (AllocStringBuffer(&line, &linesize, cnt) == NULL)
				goto command_exit;
			cp = line;
			while (cnt > 0 && (*cp++ = *tbuf++) != '\n')
				cnt--;
			tbuf = 0;
			if (cp == line || *--cp != '\n' || cp == line)
				goto getline;
			*cp = '\0';
			if (rlogin == _POSIX_VDISABLE)
				(void) printf("%s\n", line);
		} else {
getline:
			if (rlogin != _POSIX_VDISABLE)
				(void) printf("%s> ", prompt);
			if (GetString(&line, &linesize, stdin) == NULL) {
				if (!feof(stdin))
					perror("telnet");
				(void) quit();
				/*NOTREACHED*/
				break;
			}
		}
		if (line[0] == 0)
			break;
		makeargv();
		if (margv[0] == 0) {
			break;
		}
		c = getcmd(margv[0]);
		if (Ambiguous(c)) {
			(void) printf("?Ambiguous command\n");
			continue;
		}
		if (c == 0) {
			(void) printf("?Invalid command\n");
			continue;
		}
		if (c->needconnect && !connected) {
			(void) printf("?Need to be connected first.\n");
			continue;
		}
		if ((*c->handler)(margc, margv)) {
			break;
		}
	}
command_exit:
	if (!top) {
		if (!connected) {
			longjmp(toplevel, 1);
			/*NOTREACHED*/
		}
		setconnmode(0);
	}
}

/*
 * Help command.
 */
	static int
help(argc, argv)
	int argc;
	char *argv[];
{
	register Command *c;

	if (argc == 1) {
		(void) printf(
		    "Commands may be abbreviated.  Commands are:\n\n");
		for (c = cmdtab; c->name; c++)
			if (c->help) {
				(void) printf("%-*s\t%s\n", HELPINDENT,
					c->name, c->help);
			}
		(void) printf("<return>\tleave command mode\n");
		return (0);
	}
	while (--argc > 0) {
		register char *arg;
		arg = *++argv;
		c = getcmd(arg);
		if (Ambiguous(c))
			(void) printf("?Ambiguous help command %s\n", arg);
		else if (c == (Command *)0)
			(void) printf("?Invalid help command %s\n", arg);
		else if (c->help) {
			(void) printf("%s\n", c->help);
		} else  {
			(void) printf("No additional help on %s\n", arg);
		}
	}
	return (0);
}

static char *rcname = NULL;
#define	TELNETRC_NAME "telnetrc"
#define	TELNETRC_COMP "/." TELNETRC_NAME

static int
cmdrc(char *m1, char *m2)
{
	Command *c;
	FILE *rcfile = NULL;
	int gotmachine = 0;
	int l1 = strlen(m1);
	int l2 = strlen(m2);
	char m1save[MAXHOSTNAMELEN];
	int ret = 0;
	char def[] = "DEFAULT";

	if (skiprc)
		goto cmdrc_exit;

	doing_rc = 1;

	(void) strlcpy(m1save, m1, sizeof (m1save));
	m1 = m1save;

	if (rcname == NULL) {
		char *homedir;
		unsigned rcbuflen;

		if ((homedir = getenv("HOME")) == NULL)
			homedir = "";

		rcbuflen = strlen(homedir) + strlen(TELNETRC_COMP) + 1;
		if ((rcname = malloc(rcbuflen)) == NULL) {
			perror("telnet: can't process " TELNETRC_NAME);
			ret = 1;
			goto cmdrc_exit;
		}
		(void) strcpy(rcname, homedir);
		(void) strcat(rcname, TELNETRC_COMP);
	}

	if ((rcfile = fopen(rcname, "r")) == NULL)
		goto cmdrc_exit;

	for (;;) {
		if (GetString(&line, &linesize, rcfile) == NULL) {
			if (!feof(rcfile)) {
				perror("telnet: error reading " TELNETRC_NAME);
				ret = 1;
				goto cmdrc_exit;
			}
			break;
		}
		if (line[0] == 0)
			continue;
		if (line[0] == '#')
			continue;
		if (gotmachine) {
			if (!isspace(line[0]))
			gotmachine = 0;
		}
		if (gotmachine == 0) {
			if (isspace(line[0]))
				continue;
			if (strncasecmp(line, m1, l1) == 0)
				(void) strcpy(line, &line[l1]);
			else if (strncasecmp(line, m2, l2) == 0)
				(void) strcpy(line, &line[l2]);
			else if (strncasecmp(line, def, sizeof (def) - 1) == 0)
				(void) strcpy(line, &line[sizeof (def) - 1]);
			else
				continue;
			if (line[0] != ' ' && line[0] != '\t' &&
			    line[0] != '\n')
				continue;
			gotmachine = 1;
		}
		makeargv();
		if (margv[0] == 0)
			continue;
		c = getcmd(margv[0]);
		if (Ambiguous(c)) {
			(void) printf("?Ambiguous command: %s\n", margv[0]);
			continue;
		}
		if (c == 0) {
			(void) printf("?Invalid command: %s\n", margv[0]);
			continue;
		}
		/*
		 * This should never happen...
		 */
		if (c->needconnect && !connected) {
			(void) printf("?Need to be connected first for %s.\n",
			    margv[0]);
			continue;
		}
		(*c->handler)(margc, margv);
	}
cmdrc_exit:
	if (rcfile != NULL)
		(void) fclose(rcfile);
	doing_rc = 0;

	return (ret);
}