#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "btelnet.c,v 1.1.1.1 1995/06/16 21:10:45 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * btelnet
 *
 * Brimstone proxy telnet.  Accept connections from network,
 * authenticate user, then allow user to pass on to final destination
 */

#include "bsClient.h"
#include <arpa/telnet.h>


#ifndef	VERSION
#define	VERSION	"1.1.1.1"
#endif

#define RELAY_BUFSIZ 1024
#define WHITESPACE " \t\n"
#define SMALLBUF 64
#define MAXSTOREOPT 128
#define CONNTIMEOUT	"15"

char *program;			/* Program name for messages/logging */
char *username = NULL;

int sfdin = 0;
int sfdout = 1;
FILE *sfilein;
FILE *sfileout;

int destfd = -1;
int debug = 0;
sos_config_t config;

#ifdef SO_REUSEADDR
int socopts = SO_REUSEADDR;	/* socket options */

#else				/*SO_REUSEADDR */
int socopts = 0;		/* socket options */

#endif				/*SO_REUSEADDR */

static unsigned char Outstanding[256];
static unsigned char StoredOpts[MAXSTOREOPT * 2];
static int numStoredOpts = 0;
static int should_store_options = 1;
static struct sos_readline_t readopts;


struct iostat
  {
    int starttime;
    int endtime;
    int bytes_from_source;
    int bytes_from_dest;
  }
ioinfo;

struct sos_conninfo sourceinfo =
{NULL, -1};
struct sos_conninfo destinfo =
{NULL, -1};
void exiterror(char *msg, int wanterrno);
char *greet();
void authenticate();
void exitcleanup();
void sigtimeout();
void sigchld();
int command(char *prompt);
int sub_close(char *line);
int sub_help(char *line);
int sub_open(char *line);
int sub_fork(char *line);
char *get_tel_arg(char *line, char **rest);
static int telnet_IAC(FILE * in, FILE * out, unsigned char escape);
static int tel_inv_opt(int fd, unsigned char cmd, unsigned char telopt);
static int tel_for_opt(int fd, unsigned char cmd, unsigned char telopt);
static int send_option(int fd, unsigned char cmd, unsigned char telopt);
static int saw_option(int fd, unsigned char cmd, unsigned char telopt);
static int storeopt(unsigned char cmd, unsigned char telopt);
static int outputopt(int fd);
int proxy_x(char *line);
int proxy_x_int(int argc, char *argv[]);
int proxy_relay(char *line);



struct telcmd
  {
    char *name;
    int may_open_connection;
    int (*sub) (char *line);
  }
telcmd[] =
{
  {
    "close", 0, sub_close
  }
  ,
  {
    "exit", 0, sub_close
  }
  ,
  {
    "quit", 0, sub_close
  }
  ,
  {
    "help", 0, sub_help
  }
  ,
  {
    "?", 0, sub_help
  }
  ,
  {
    "open", 1, sub_open
  }
  ,
  {
    "o", 1, sub_open
  }
  ,
  {
    "telnet", 1, sub_open
  }
  ,
  {
    "fork", 0, sub_fork
  }
  ,
  {
    NULL, 0, NULL
  }
  ,
};


struct proxyfork
  {
    char *name;
    int (*sub) (char *line);
  }
proxycmd[] =
{
  {
    "x", proxy_x
  }
  ,
  {
    "relay", proxy_relay
  }
  ,
  {
    NULL, NULL
  }
  ,
};


/* Common environment for children */
char *newenv[4] =
{ "BS_CALLED_FROM_TELNET=true", "HOME=/", NULL, NULL };
#define ENV_USER_LOC 2
char userenvbuf[BS_MAXUSERSIZE+10];


/* Timeouts */
static struct sigaction old_sigaction;	/* Caches old signal vec. */

static struct sigaction iotimeout = SIGACTIONDEFINE( sigtimeout, 0, _INTERRUPT_SYSCALLS_ );
static struct sigaction childdeath = SIGACTIONDEFINE( sigchld, 0, _INTERRUPT_SYSCALLS_ );
static struct sigaction defact = SIGACTIONDEFINE( SIG_DFL, 0, 0 );



void 
sigchld()
{
  int reaped;

  do
    {
      if ((reaped = waitpid(-1, NULL, WNOHANG)) < 0)
	break;

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, SOSLOG_NO_PERROR,
	     "reap_child(): child reaped[%d]", reaped);
    }
  while (reaped > 0);

}



int timedout = 0;
void 
sigtimeout()
{
  timedout = 1;
}



int
main(int argc, char *argv[], char *envp[])
{
  int from_stdin = 0;
  int tmpvar;
  int getopterr = 0;
  extern char *optarg;
  extern int optind;
  char *prompt;
  char *tmp;

  bs_initProcTitle(argc, &argv, &envp, &program);
  bs_setProcTitle(username, NULL, NULL, BS_SPTP_INIT);

  while ((tmpvar = getopt(argc, argv, "sd")) != -1)
    switch (tmpvar)
      {
      case 's':
	from_stdin = 1;
	break;
      case 'd':
	debug = 1;
	break;
      case '?':
	getopterr++;
      }

  if (getopterr)
    {
      fprintf(stderr, "Usage: %s [-sd]\n", program);
      exit(2);
    }

  open_soslog(program, debug);
  soslog(SOSLOG_DEBUG, SOSLOG_TYPE_EVENT, 0, "Opening with debug");

  /* set up readline information */
  readopts.echo = 0;
  readopts.wantalert = 1;
  readopts.wantedit = 1;
  readopts.wantnull = 0;
  readopts.escape_char = IAC;
  readopts.escape_mode = telnet_IAC;
  readopts.eolseq[0].str = "\r\n";
  readopts.eolseq[0].len = 2;
  readopts.eolseq[1].str = "\r\0";
  readopts.eolseq[1].len = 2;
  readopts.eolseq[2].str = "\n";
  readopts.eolseq[2].len = 1;
  readopts.numeolseq = 3;

  /* Find out who we are connected to */
  if (sos_getpeername(sfdin, &sourceinfo) < 0)
    {
      if (from_stdin)
	sourceinfo.printhost = strdup(".stdin. [0.0.0.0]");
      else
	exiterror("sos_getpeername", 0);
    }

  soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, 0, "connection from %s", sourceinfo.printhost);
  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_INIT);

  ioinfo.starttime = time(NULL);
  ioinfo.endtime = -1;
  ioinfo.bytes_from_source = 0;
  ioinfo.bytes_from_dest = 0;

  if ((sfilein = fdopen(sfdin, "r")) == NULL)
    exiterror("fdopen", 1);
  if ((sfileout = fdopen(sfdout, "w")) == NULL)
    exiterror("fdopen", 1);

  /* This is non-fatal */
  if ((config = sos_config_read(SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF))) == NULL)
    {
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 1, "Could not read config file %s", SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF));
    }


  /* Connection is all set up.  Let's try a login */
  prompt = greet();

  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);
  authenticate();

  /*
   * Go back into line-by-line local echo mode.
   *
   * Telnet is really stupid.  If we contact a dumb service (e.g. finger)
   * we need to go into this mode, but it is too late since the source host
   * will then start sending IACs back which will confuse fingerd.
   *
   * We have zero desire to muck with relay to censor IACs
   */
  should_store_options = 0;	/* Otherwise we might conflict with ourselves */
  if (send_option(sfdout, WONT, TELOPT_ECHO) < 0)
    exiterror("send_option", 1);
  if (send_option(sfdout, WONT, TELOPT_SGA) < 0)
    exiterror("send_option", 1);
  readopts.echo = -1;		/* Don't echo--we have local echo */

  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_CMD);
  destfd = command(prompt);

  {
    struct sos_bdr_fdpair relay[2], *prelay;
    struct sos_bdr_auxinfo auxinfo;
    int numfdpair = 2;

    auxinfo.veclen = 10;
    auxinfo.totbuf = 16;
    auxinfo.buflen = 16384;
    auxinfo.align = 16384;
    auxinfo.offset = 0;
    auxinfo.common_exit = 1;
    auxinfo.fullread = 0;

    relay[0].rinfo.fd = 0;
    relay[0].rinfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))read;
    relay[0].winfo.fd = destfd;
    relay[0].winfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))write;
    relay[0].curRbuf.iov_base = NULL;
    relay[0].curRbuf.iov_len = 0;
    relay[0].shutdown = 0;
    relay[0].nbytes = 0;
    relay[0].filter = NULL;
    relay[0].bd.cur_write_hand = 0;
    relay[0].bd.cur_read_hand = -1;
    if (!(relay[0].bd.curWbuf = (struct iovec *)malloc(sizeof(struct iovec) * auxinfo.veclen)))
        exiterror("malloc", 1);
    if (!(relay[0].bd.fullbuf = (struct iovec *)malloc(sizeof(struct iovec) * auxinfo.veclen)))
        exiterror("malloc", 1);

    relay[1].rinfo.fd = destfd;
    relay[1].rinfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))read;
    relay[1].winfo.fd = 1;
    relay[1].winfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))write;
    relay[1].curRbuf.iov_base = NULL;
    relay[1].curRbuf.iov_len = 0;
    relay[1].bd.cur_write_hand = 0;
    relay[1].bd.cur_read_hand = -1;
    relay[1].shutdown = 0;
    relay[1].nbytes = 0;
    relay[1].filter = NULL;
    if (!(relay[1].bd.curWbuf = (struct iovec *)malloc(sizeof(struct iovec) * auxinfo.veclen)))
        exiterror("malloc", 1);
    if (!(relay[1].bd.fullbuf = (struct iovec *)malloc(sizeof(struct iovec) * auxinfo.veclen)))
        exiterror("malloc", 1);

    prelay = relay;

    bs_setProcTitle(username, &sourceinfo, &destinfo, BS_SPTP_RELAY);
    if (sos_bdrelay(&prelay, &numfdpair, &auxinfo) < 0)
      exiterror("relay", 1);

    ioinfo.bytes_from_source += relay[0].nbytes;
    ioinfo.bytes_from_dest += relay[1].nbytes;
  }

  bs_setProcTitle(username, &sourceinfo, &destinfo, BS_SPTP_EXIT);
  exitcleanup();
}



/*
 * Exit with an error mesage
 */
void 
exiterror(char *msg, int wanterrno)
{
  char *errmsg = strerror(errno);

  alarm(0);
  soslog(SOSLOG_ERR, SOSLOG_TYPE_EVENT, wanterrno, msg);

#ifdef DEBUG
  fprintf(stderr, "Error: %s: %s: %s\r\n", program, msg, wanterrno ? errmsg : "");
#endif

  exitcleanup();
}



/*
 * Exit and log final information
 */
void 
exitcleanup()
{
  alarm(0);
  if (username)
    BS_Cleanup(username);

  ioinfo.endtime = time(NULL);
  soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, 0,
      "Exit: User :%s:, %d bytes from :%s:, %d bytes from :%s:, %d seconds",
	 username, ioinfo.bytes_from_source, sourceinfo.printhost,
	 ioinfo.bytes_from_dest, destinfo.printhost, ioinfo.endtime - ioinfo.starttime);

  exit(0);
}



/*
 * Greet remote user
 */
char *
greet()
{
  char *line;

  fprintf(sfileout, "%s proxy telnet (%s) ready at %s\r\n",
	  sos_fqhname(NULL, 0), VERSION, sos_prompttime(NULL, 0));

  while ((line = sos_config_getnext(config, "TELNET_GREET", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
    {
      fprintf(sfileout, "%s\r\n", line);
    }

  fflush(sfileout);

  return (SOS_GWD("TELNET_PROMPT", "telnet> "));
}



/*
 * Authenticate the user
 */
void 
authenticate()
{
  sos_string UNAME;
  sos_string INBUF;
  sos_string OUTBUF;
  char *line;
  int timeout;
  unsigned int nextstate = 0;
  unsigned int req_input = BS_NO_INPUT;
  int ret;
  int state_ret;

  if (!sos_allocstr(&UNAME, SMALLBUF) || !sos_allocstr(&OUTBUF, BS_IOLEN) ||
      !sos_allocstr(&INBUF, BS_IOLEN))
    exiterror("sos_allocstr", 1);

  timeout = atoi(SOS_GWD("TELNET_AUTHTIMEOUT", "90"));
  if (timeout > 0)
    {
      timedout = 0;
      sigaction(SIGALRM, (struct sigaction *)&iotimeout,
		(struct sigaction *)&old_sigaction);
      alarm(timeout);
    }

  /* Clear any options I know about */
  memset(Outstanding, 0, 256);

  send_option(sfdin, WILL, TELOPT_ECHO);
  send_option(sfdin, WILL, TELOPT_SGA);

  do
    {
      fprintf(sfileout, "\r\n%s", SOS_GWD("TELNET_USERNAME", "Username: "));
      fflush(sfileout);

      ret = sos_readline(sfilein, sfileout, &UNAME, &readopts);
      if (timedout)
	exiterror("timed out", 0);
      if (!ret)
	exiterror("Client aborted connection", 1);
      if (ret == -1)
	exiterror("readline", 1);
      if (ret < 0)
	{
	  fprintf(sfileout, "\r\n%s\r\n", SOS_GWD("TELNET_INVALID", "Invalid entry"));
	  ret = 0;
	  continue;
	}
      sos_rip(UNAME.str);
      if (strlen(UNAME.str) == 0)
	{
	  ret = 0;
	  continue;
	}
      username = strdup(UNAME.str);
    }
  while (ret == 0);

  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);

  /* Enter into getchallenge state engine */
  nextstate = 0;
  while (1)
    {
      req_input = BS_NO_INPUT;

      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_ACL);
      state_ret = BS_State(BS_GetChallenge, UNAME.str, &nextstate, &req_input, &OUTBUF, &INBUF);
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, 0,
	     "authenticate: getchallenge: %d = (%d, %d, %s)",
	     state_ret, nextstate, req_input, OUTBUF.str);
      if (OUTBUF.str[0])
	{
	  fprintf(sfileout, "%s\r\n", OUTBUF.str);
	}
      if (req_input != BS_NO_INPUT)
	{
	  while (1)
	    {
	      fputs(SOS_GWD("TELNET_RESPONSE", "Response: "), sfileout);
	      fflush(sfileout);

	      readopts.echo = (req_input == BS_SIMPLE_INPUT ? 0 : '*');
	      ret = sos_readline(sfilein, sfileout, &INBUF, &readopts);
	      readopts.echo = 0;
	      if (timedout)
		exiterror("timed out", 0);
	      if (!ret)
		exiterror("Client aborted connection", 1);
	      if (ret == -1)
		exiterror("readline", 1);
	      if (ret < 0)
		{
		  fprintf(sfileout, "\r\n%s\r\n", SOS_GWD("TELNET_INVALID", "Invalid entry"));
		  ret = 0;
		  continue;
		}
	      sos_rip(INBUF.str);
	      if (strlen(INBUF.str) == 0)
		{
		  ret = 0;
		  continue;
		}
	      break;
	    }
	}
      if (!nextstate)
	{
	  break;
	}
    }

  if (state_ret != AUTH_SUCCEED)
    {
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 0,
	     "authenticate: getchallenge: Error: %s (%d)",
	     bs_auth_extended, ret);
    }

  /* Enter into authentication state engine */
  nextstate = 0;
  while (1)
    {
      req_input = BS_NO_INPUT;

      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_ACL);
      state_ret = BS_State(BS_Authenticate, UNAME.str, &nextstate, &req_input, &OUTBUF, &INBUF);
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, 0,
	     "authenticate: authenticate: %d = (%d, %d, %s)",
	     state_ret, nextstate, req_input, OUTBUF.str);
      if (OUTBUF.str[0])
	{
	  fprintf(sfileout, "%s\r\n", OUTBUF.str);
	}
      if (req_input != BS_NO_INPUT)
	{
	  while (1)
	    {
	      fputs(SOS_GWD("TELNET_RESPONSE", "Response: "), sfileout);
	      fflush(sfileout);

	      readopts.echo = (req_input == BS_SIMPLE_INPUT ? 0 : '*');
	      ret = sos_readline(sfilein, sfileout, &INBUF, &readopts);
	      readopts.echo = 0;
	      if (timedout)
		exiterror("timed out", 0);
	      if (!ret)
		exiterror("Client aborted connection", 1);
	      if (ret == -1)
		exiterror("readline", 1);
	      if (ret < 0)
		{
		  fprintf(sfileout, "\r\n%s\r\n", SOS_GWD("TELNET_INVALID", "Invalid entry"));
		  ret = 0;
		  continue;
		}
	      sos_rip(INBUF.str);
	      if (strlen(INBUF.str) == 0)
		{
		  ret = 0;
		  continue;
		}
	      break;
	    }
	}
      if (!nextstate)
	{
	  break;
	}
    }

  if (state_ret != AUTH_SUCCEED)
    {
      while ((line = sos_config_getnext(config, "TELNET_FAIL", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	{
	  fprintf(sfileout, "%s\r\n", line);
	}
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_CONNCLOSE","Connection closed."));
      fflush(sfileout);

      if (state_ret == AUTH_FAIL)
	soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	       "authenticate: DENY: user %s from %s was not authenticated",
	       username, sourceinfo.printhost);
      else
	soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 0,
	       "authenticate: authenticate : Error: %s",
	       bs_auth_extended);
      exitcleanup();
    }

  alarm(0);
  sigaction(SIGALRM, (struct sigaction *)&old_sigaction,
	    (struct sigaction *)NULL);

  soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 0,
	 "authenticate: user %s from %s authenticated",
	 username, sourceinfo.printhost);
  while ((line = sos_config_getnext(config, "TELNET_WELCOME", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
    {
      fprintf(sfileout, "%s\r\n", line);
    }
  fflush(sfileout);

  sos_freestr(&UNAME);
  sos_freestr(&OUTBUF);
  sos_freestr(&INBUF);
}



/*
 * Process telnet proxy commands
 */
int 
command(char *prompt)
{
  int ret = -1;
  int timeout;
  sos_string INBUF;
  struct telcmd *curcmd;
  char *peekcmd;
  char *restline;

  if (!sos_allocstr(&INBUF, BS_IOLEN))
    exiterror("sos_allocstr", 1);

  timeout = atoi(SOS_GWD("TELNET_CMDTIMEOUT", "600"));
  if (timeout > 0)
    {
      timedout = 0;
      sigaction(SIGALRM, (struct sigaction *)&iotimeout,
		(struct sigaction *)&old_sigaction);
      alarm(timeout);
    }

  while (1)
    {
      fputs(prompt, sfileout);
      fflush(sfileout);

      ret = sos_readline(sfilein, sfileout, &INBUF, &readopts);
      if (timedout)
	exiterror("timed out", 0);
      if (!ret)
	exiterror("Client aborted connection", 1);
      if (ret == -1)
	exiterror("readline", 1);
      if (ret < 0)
	{
	  fprintf(sfileout, "\r\n%s\r\n", SOS_GWD("TELNET_INVALID", "Invalid entry"));
	  ret = 0;
	  continue;
	}
      sos_rip(INBUF.str);
      if (strlen(INBUF.str) == 0)
	{
	  ret = 0;
	  continue;
	}

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_EVENT, 0, "User command: %s", INBUF.str);

      peekcmd = get_tel_arg(INBUF.str, &restline);

      for (curcmd = telcmd; curcmd->name; curcmd++)
	{
	  if (!strcasecmp(peekcmd, curcmd->name))
	    break;
	}

      if (!curcmd->name)
	{
	  fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_UNKCMD", "Unknown command (try ``help'')"));
	  continue;
	}

      ret = (*curcmd->sub) (restline);
      fflush(sfileout);

      if (curcmd->may_open_connection && ret >= 0)
	break;			/* User won! */
    }

  alarm(0);
  sigaction(SIGALRM, (struct sigaction *)&old_sigaction,
	    (struct sigaction *)NULL);

  return (ret);
}



/*
 * The help command
 */
int 
sub_help(char *rest)
{
  char *line;

  while ((line = sos_config_getnext(config, "TELNET_HELP", SOS_CONFIG_FORWARD, SOS_CONFIG_AUTORESET)) != NULL)
    {
      fprintf(sfileout, "%s\r\n", line);
    }
  return (-1);
}



/*
 * The close command
 */
int 
sub_close(char *rest)
{
  char *line;

  while ((line = sos_config_getnext(config, "TELNET_CLOSE", SOS_CONFIG_FORWARD, SOS_CONFIG_AUTORESET)) != NULL)
    {
      fprintf(sfileout, "%s\r\n", line);
    }
  fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_CONNCLOSE","Connection closed."));
  fflush(sfileout);
  exitcleanup();
  abort();
}



/*
 * The fork command
 */
int 
sub_fork(char *rest)
{
  char *peekcmd;
  char *restline;
  struct proxyfork *curcmd;
 
  soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	 "fork(%s)",rest);

  if (strcasecmp(SOS_GWD("TELNET_FORK_CMD", "on"), "on"))
    {
      soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, 0, "CMDOFF: user %s from %s tried to use fork",username,sourceinfo.printhost);
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_FORK_CMD_MSG", "Fork command administratively turned off"));
      return (0);
    }

  peekcmd = get_tel_arg(rest, &restline);

  if (!peekcmd)
    {
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_UNKFCMD", "Unknown fork command"));
      return (0);
    }

  for (curcmd = proxycmd; curcmd->name; curcmd++)
    if (!strcasecmp(peekcmd, curcmd->name))
      break;

  if (!curcmd->name)
    {
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_UNKFCMD", "Unknown fork command"));
      return (0);
    }

  return ((*curcmd->sub) (restline));
}



/*
 * The open command
 */
int 
sub_open(char *rest)
{
  int numport = 23;
  char *port = NULL;
  char *host = NULL;
  char *other;
  char *other1;
  char *other2;
  int telnetsemantics = 0;

  if (!rest || !*rest)
    goto usage;

  host = get_tel_arg(rest, &other);

  if (!host || !*host)
    goto usage;

  if (other)
    {
      port = get_tel_arg(other, &other1);
      other = get_tel_arg(other1, &other2);

      if (other)
	goto usage;
    }

  if (port)
    {
      if (*port == '-')
	{
	  telnetsemantics = 1;
	  port++;
	}
      if (telnetsemantics && !*port)
	numport = 23;
      else
	numport = sos_getsbyfoo(port);
    }

  if (numport < 1)
    {
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_UNKNOWNPORT", "Unknown/invalid port"));
      fflush(sfileout);
      return (-1);
    }
  telnetsemantics = telnetsemantics ^ (numport == 23);

  destinfo.printhost = host;
  destinfo.port = numport;

  bs_setProcTitle(username, &sourceinfo, &destinfo, BS_SPTP_CONN);
  if ((destfd = bs_make_conn(host, numport, atoi(SOS_GWD("TELNET_CONNTIMEOUT", CONNTIMEOUT)),
			     socopts, username, &sourceinfo, sfileout)) < 0)
    {
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_CMD);

      if (destfd == -1)
	fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_CONNFAIL", "Connection failed"));
      else
	fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_UNKHOST", "Host unknown"));
      fflush(sfileout);
      return (-1);
    }

#ifdef TCP_NODELAY
  {
    struct protoent *p;

    p = getprotobyname("tcp");
    if (p && setsockopt(destfd, p->p_proto, TCP_NODELAY,
			(char *)&one, sizeof(one)) < 0)
      exiterror("setsockopt (NODELAY)", 1);
  }
#endif				/*NODELAY */


  /* We are talking to telnet */
  if (telnetsemantics)
    {
      if (outputopt(destfd) < 0)
	return (-1);
    }

  if (sos_getpeername(destfd, &destinfo) < 0)
    {
      fprintf(sfileout, "%s (%s)\r\n",
	 SOS_GWD("TELNET_CONNPEER", "Connection failed during getpeername"),
	      strerror(errno));
      fflush(sfileout);
      close(destfd);
      return (-1);
    }


  /*
   * XXX - this may be a bad ideas, but it is better than the
   * other options.  Basically, before we transition from
   * command mode to relay mode, we need to flush the input
   * stream (because there might be characters, like NULL,
   * which are left there because of telnet ``bugs''
   */
  {
    long available;
    int ret;
    char tmp[512];

    while (((ret = ioctl(sfdin, FIONREAD, &available)) == 0) && available)
      {
	ret = read(sfdin, tmp, 512);	/* Should be NBIO */
	if (ret < 0)
	  {
	    exiterror("FIONREAD read", 1);
	  }
#if 0
	syslog(LEVEL, "Flushed %d bytes from input stream", ret);
#endif
      }

    if (ret < 0)
      {
	exiterror("FIONREAD ioctl", 1);
      }
  }

  soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, 0, "User %s from %s port %d connected to %s port %d",
	 username, sourceinfo.printhost, sourceinfo.port, destinfo.printhost, destinfo.port);
  fprintf(sfileout, SOS_GWD("TELNET_CONNECTED", "Connected to %s port %d"),
	  destinfo.printhost, destinfo.port);
  fprintf(sfileout, "\r\n");
  fflush(sfileout);
  return (destfd);

usage:
  fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_OPENUSAGE", "Usage: open <host> [[-]port]"));
  fflush(sfileout);
  return (-1);
}



/*
 * Pull a token off the line
 */
char *
get_tel_arg(char *line, char **rest)
{
  char *ret;

  ret = strtok(line, WHITESPACE);
  *rest = strtok(NULL, "");

  return (ret);
}



/*
 * Break up a line into an array of tokens
 */
int 
tel_enargv(char *line, int *argc, char ***argv)
{
  char *tmparg[_POSIX_ARG_MAX];
  char *tmp = line;
  char *arg;
  int j, x = 0;

  if (!line)
    return (0);

  if (argc == NULL || argv == NULL)
    return (-1);

  if (*argv == NULL && *argc > 0)
    return (-1);

  if (*argv && *argc < 1)
    return (-1);

  while (arg = strtok(tmp, WHITESPACE))
    {
      tmparg[x++] = arg;
      tmp = NULL;

      if (x > _POSIX_ARG_MAX || ((*argc > 0) && x > *argc))
	return (-1);
    }

  tmparg[x] = NULL;

  if (!*argv)
    {
      *argv = (char **)malloc((sizeof(char **)) * (x + 1));

      if (!*argv)
	return (-1);
    }
  for (j = 0; j <= x; j++)
    (*argv)[j] = tmparg[j];

  *argc = x;

  return (x);
}



/*
 * fork's X command
 */
int 
proxy_x(char *line)
{
  int ret;
  char **argv = NULL;
  int argc = 0;

  if (strcasecmp(SOS_GWD("TELNET_X_FCMD", "on"), "on"))
    {
      soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT,0,"CMDOFF: user %s from %s tried to use x",username,sourceinfo.printhost);
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_X_FCMD_MSG", "Fork x command administratively turned off"));
      return (0);
    }

  if (!line)
    return (0);

  if (tel_enargv(line, &argc, &argv) < 0)
    return (-1);

  ret = proxy_x_int(argc, argv);

  free(argv);
  return (ret);
}



/*
 * Internal X stuff
 */
int 
proxy_x_int(int argc, char *argv[])
{
  char *xforw[22] =
  {"xforward", "-display", "", "-allow", NULL};

#define XFORW_DISPLAY 2
#define XFORW_HOST 4
#define XFORW_MAX 19
  char *xauth[] =
  {"xauth", "-q", "-f", "/Xauth/.Xauthority", "add", "", "", "", NULL};

#define XAUTH_DISPLAY 5
#define XAUTH_TYPE 6
#define XAUTH_KEY 7
  char **hostp = (argv + 2);
  char **hostx = (xforw + XFORW_HOST);
  int statusp;
  int io[2];
  FILE *fxfor;
  char buf[1024];
  int x;

  if (strlen(username) > BS_MAXUSERSIZE)
    return(0);			/* Something is dreadfully wrong */
  sprintf(userenvbuf,"BSUSER=%s", username);
  newenv[ENV_USER_LOC] = userenvbuf;

  if ((argc < 3) || (argc > XFORW_MAX) ||
      (strcmp(argv[1], "+") && (argc < 4)))
    {
      fprintf(sfileout, "%s\r\n",
	      SOS_GWD("TELNET_XERR", "Syntax error"));
      return (0);
    }

  sigaction(SIGCHLD, (struct sigaction *)&defact, NULL);
  if (strcmp(argv[1], "+"))
    {				/* USING XAUTH */

      xauth[XAUTH_DISPLAY] = argv[0];
      xauth[XAUTH_TYPE] = argv[1];
      xauth[XAUTH_KEY] = argv[2];
      hostp = (argv + 3);

      switch (fork())
	{
	case 0:		/* Child */
	  buf[0] = '\0';
	  for (x = 0; xauth[x]; x++)
	    sprintf(buf + strlen(buf), " %s", xauth[x]);
	  soslog(SOSLOG_DEBUG, SOSLOG_TYPE_EVENT, 0, "Running%s", buf);
	  execve("/meta/xauth", xauth, newenv);
	  exit(1);
	case -1:		/* Error */
	  return (-1);
	default:		/* Parent */
	  break;
	}

      /* Parent */
      if (wait(&statusp) < 0)
	{
	  return (-1);
	}

      if (statusp != 0)
	{
	  fprintf(sfileout, "%s: (%d)\r\n", SOS_GWD("TELNET_XAUTHERR", "Xauth failed"), statusp);
	  return (0);
	}
    }

  /* We are now set up with authentication.  Fire off the forwarding server */
  xforw[XFORW_DISPLAY] = argv[0];

  /* Copy allowed host list */
  for (; *hostp; hostp++, hostx++)
    {
      *hostx = *hostp;
    }
  *hostx = NULL;

  if (pipe(io) < 0)
    {
      exiterror("pipe", 1);
    }
  if ((fxfor = fdopen(io[0], "r")) == NULL)
    {
      exiterror("fdopen", 1);
    }

  sigaction(SIGCHLD, (struct sigaction *)&childdeath, NULL);
  switch (fork())
    {
    case 0:			/* Child */
      buf[0] = '\0';
      for (x = 0; xforw[x]; x++)
	sprintf(buf + strlen(buf), " %s", xforw[x]);
      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_EVENT, 0, "Running%s", buf);
      dup2(io[1], 1);
      dup2(io[1], 2);
      execve("/meta/xforward", xforw, newenv);
    case -1:			/* Error */
      exiterror("fork/exec", 1);
      exit(1);
    default:			/* Parent */
      break;
    }

  if (fgets(buf, 1024, fxfor) == NULL)
    {
      exiterror("fgets", 1);
      exit(1);
    }

  fputs(buf, sfileout);

#if 0
  while (fgets(buf, 1024, fxfor) != NULL)
    {
      fputs(buf, sfileout);
    }
#endif
  return (0);
}



/*
 * fork's relay command
 */
int 
proxy_relay(char *line)
{
  char **argv = NULL;
  int argc = 0;
  char *tmparg[_POSIX_ARG_MAX-1]; 
  char *arg;
  int x;
  char *tmp = line;
  int offset; 
  int child;			/* Child pid */
  int p2c[2];			/* Parent2child pipe */
  int c2p[2];			/* Child2parent pipe */
  FILE *relayin;		/* STREAM from child (bsrelay) */
  FILE *relayout;		/* STREAM to child (bsrelay) */
  char relayinbuf[RELAY_BUFSIZ];	/* Buffer to store data from bsrelay */

  if (strlen(username) > BS_MAXUSERSIZE)
    return(0);			/* Something is dreadfully wrong */
  sprintf(userenvbuf,"USER=%s", username);
  newenv[ENV_USER_LOC] = userenvbuf;


  if (strcasecmp(SOS_GWD("TELNET_RELAY_FCMD", "on"), "on"))
    {
      soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT,0,"CMDOFF: user %s from %s tried to use relay",username,sourceinfo.printhost);
      fprintf(sfileout, "%s\r\n", SOS_GWD("TELNET_RELAY_FCMD_MSG", "Fork relay command administratively turned off"));
      return (0);
    }

  if (!line)
    return (0);

  /* 
   * Arghh! I would like to simply call tel_enargv(), but I also want to
   * set argv[0] explicitly -- the two needs don't match well.  So we 
   * replicate the code. First count your args and set a pointer at 
   * each one. Make space for your arg array, and copy in the pointers.
   */
  while (arg = strtok(tmp, WHITESPACE))
    {
      tmparg[argc++] = arg;
      tmp = NULL;

      if (argc > _POSIX_ARG_MAX-1 )
	return (-1);
    }

  tmparg[argc] = NULL;

  /*
   * The logic of the malloc size calculation: 
   * 	The number of arguments supplied by the user *plus*
   *	The number of argumetns hardcoded in btelnet *plus*
   * 	one final slot to terminate the argv vector
   *
   */
  argv=(char **)malloc(sizeof(char *) * (argc +1 +1)  );
  memset(argv, (char)0, (sizeof(char *) * (argc +1 +1)));

  x = 0;
  if (!argv) return -1;
  argv[x]= strdup("bsrelay");
  argc++;
  x++;

  offset = x;
  
  for (; x < argc ; x++)
    {
      argv[x] = tmparg[x-offset];
    }

  if ( debug )
    {
      for (x=0; x < argc; x++)
	fprintf(sfileout,"proxy_relay(): argv[%d]: **%s**\n", x, argv[x]);
    }


    
  if ( pipe(p2c) < 0 )
    {
      soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
	     "pipe() failed for user: %s setting up relay", username);
      return -1;
    }

  if ( pipe(c2p) < 0 )
    {
      soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
	     "pipe() failed for user: %s setting up relay", username);

      close (p2c[0]);
      close (p2c[1]);
      return -1;
    }
  

  /* All the work gets done in bsrelay(1) */
  switch (  (child = fork()) )
    {
    case -1:	/* Error */
      soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
	     "fork() failed for user: %s attempting bsrelay", username);
      close (p2c[0]);
      close (p2c[1]);
      close (c2p[0]);
      close (c2p[1]);
      return -1;
      break;

    case 0:	/* Child */
      soslog (SOSLOG_DEBUG, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	      "Running bsrelay for user: %s", username);

      close (p2c[1]);
      close (c2p[0]);


      if (dup2(p2c[0],0) < 0 || dup2(c2p[1],1) < 0 || dup2(c2p[1],2) < 0 )
	{
	  exiterror("a dup2() failed while setting up bsrelay", 1);
	}

      /*
       * These are now redundant and they *must* be closed for two reasons:
       *
       * 	o it's tidy
       * 	o bsrelay will close stdin and stderr when it no longer needs
       * 		needs to communicate with btelnet. In order for 
       *		for btelnet to receive the EOF, these aliased 
       *		descriptors must be shut down.
       */
      close (p2c[0]);
      close (c2p[1]);

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
	     "Runing relay for user: %s", username);

      if ( execve("/meta/bsrelay", argv, newenv) < 0 )
	/*
	 * We've forked, so it's not only safe, but there isn't any real
	 * reason *not* to exit on errors
	 */
	exiterror("exec() failed while setting up bsrelay", 1);
      exit(0);
      break;

    default: /* Parent */

      /*
       * I just put these close()'s here for parallelism
       */
      close (p2c[0]);
      close (c2p[1]);

      /* Just fall through */
      break;

    }
  
  /* Parent continuing (Child never gets here). */
  if ( (relayout = fdopen (p2c[1], "w")) == NULL )
    {
      /*
       * We really must be able to use this chanel. Kill the child
       */
      kill (child, SIGTERM);
      free(argv); 
      close(p2c[1]);
      close(c2p[0]);
      return -1;
    }

  if ( relayin = fdopen (c2p[0], "r") ) 
    {
      while ( fgets(relayinbuf, RELAY_BUFSIZ, relayin) )
	{
	  sos_rip(relayinbuf);
	  fprintf(sfileout, "%s\n", relayinbuf);
	  fflush(sfileout);
	}
      fclose(relayin);
    }
  
  free(argv);
  return 0;
}



/**********************************************************************
 *
 *                   TELNET OPTION PROCESSING
 *
 *
 *  You are not expected to understand this and no-one should
 *  be forced to!
 *
 *
 * Our basic theory is that during initialization time, the telnet
 * client we are talking to is going to send us a bunch of options.
 * We, also, are going to request for remote side ECHO and NOSGA
 * (meaning we are doing echo, and we are in character-by-character
 * mode--this is so that we can handle passwords correctly)
 * We will save this flurry of options for future use, and turn
 * everything off except ECHO and NOSGA.
 *
 * Due to the duplex nature of telnet options processing, replies can
 * also be questions depending on what mode you are in, so we have to
 * be sure not to propagate option loops.
 *
 * If we are going to connect to a non-telnet protocol port
 * (e.g. finger), we must turn ECHO and SGA off since finger
 * cannot handle such telnet-specific options.  However, since we
 * will always get a reply to telnet requests, we cannot turn
 * options off at this point since the replies from the source
 * telnet would be forwarded to the dest service, which will get
 * *very* confused.
 *
 * So, what we will do is after we have logged in, we will turn ECHO
 * and NOSGA back off and let the command mode be done in local
 * echo/line-by-line mode so that non-telnet protocols will be happy
 * when you connect to them since it is already in the default mode;
 * telnet protocols will be handled as described later.  We are just
 * hoping (and trusting that people follow the RFC) that the telnet
 * client will send no more telnet options since they will only
 * confuse non-telnet clients.
 *
 * Now, for telnet aware clients (e.g. clients on port 23 or ones
 * where the user prepended the service name with a dash), we will
 * REPLAY the telnet options we recorded during initialization time.
 * The duplex nature of telnet will mean that even though the source
 * telnet did not send these option requests, the replies that it gets
 * will cause it to finish the negotiation.  This could potentially
 * cause a options loop if *both* the source and destination programs
 * don't follow the RFC, but anyone who does this should be shot
 * anyway. Non-RFC complient clients may also get confused.  All
 * telnets based on the reference (BSD) implementation are fine, but
 * some "dumb" DOS or other wierd implementations could have problems.
 * Be on the lookout.
 *
 * Lets talk about RFC1123:
 *
 *   3.5.  TELNET REQUIREMENTS SUMMARY
 * 
 *                                                  |        | | | |S| |
 *                                                  |        | | | |H| |F
 *                                                  |        | | | |O|M|o
 *                                                  |        | |S| |U|U|o
 *                                                  |        | |H| |L|S|t
 *                                                  |        |M|O| |D|T|n
 *                                                  |        |U|U|M| | |o
 *                                                  |        |S|L|A|N|N|t
 *                                                  |        |T|D|Y|O|O|t
 * FEATURE                                          |SECTION | | | |T|T|e
 * -------------------------------------------------|--------|-|-|-|-|-|--
 *                                                  |        | | | | | |
 * Option Negotiation                               |3.2.1   |x| | | | |
 *   Avoid negotiation loops                        |3.2.1   |x| | | | | DO
 *   Refuse unsupported options                     |3.2.1   |x| | | | | DO
 *   Negotiation OK anytime on connection           |3.2.1   | |x| | | | <X1>
 *   Default to NVT                                 |3.2.1   |x| | | | | DO
 *   Send official name in Term-Type option         |3.2.8   |x| | | | | N/A
 *   Accept any name in Term-Type option            |3.2.8   |x| | | | | N/A
 *   Implement Binary, Suppress-GA options          |3.3.3   |x| | | | | <X2>
 *   Echo, Status, EOL, Ext-Opt-List options        |3.3.3   | |x| | | | <X3>
 *   Implement Window-Size option if appropriate    |3.3.3   | |x| | | | <X4>
 *   Server initiate mode negotiations              |3.3.4   | |x| | | | DO
 *   User can enable/disable init negotiations      |3.3.4   | |x| | | | DO
 *                                                  |        | | | | | |
 * Go-Aheads                                        |        | | | | | |
 *   Non-GA server negotiate SUPPRESS-GA option     |3.2.2   |x| | | | | DO
 *   User or Server accept SUPPRESS-GA option       |3.2.2   |x| | | | | DO
 *   User Telnet ignore GA's                        |3.2.2   | | |x| | | N/A
 *                                                  |        | | | | | |
 * Control Functions                                |        | | | | | |
 *   Support SE NOP DM IP AO AYT SB                 |3.2.3   |x| | | | | <X4>
 *   Support EOR EC EL Break                        |3.2.3   | | |x| | | <X4>
 *   Ignore unsupported control functions           |3.2.3   |x| | | | | DO
 *   User, Server discard urgent data up to DM      |3.2.4   |x| | | | | <X5>
 *   User Telnet send "Synch" after IP, AO, AYT     |3.2.4   | |x| | | | N/A
 *   Server Telnet reply Synch to IP                |3.2.4   | | |x| | | <X4>
 *   Server Telnet reply Synch to AO                |3.2.4   |x| | | | | <X4>
 *   User Telnet can flush output when send IP      |3.2.4   | |x| | | | N/A
 *                                                  |        | | | | | |
 * Encoding                                         |        | | | | | | N/A
 *                                                  |        | | | | | |
 * End-of-Line                                      |        | | | | | |
 *   EOL at Server same as local end-of-line        |3.3.1   |x| | | | | DO
 *   ASCII Server accept CR LF or CR NUL for EOL    |3.3.1   |x| | | | | DO
 *   User Telnet able to send CR LF, CR NUL, or LF  |3.3.1   |x| | | | | N/A
 *   Non-interactive uses CR LF for EOL             |3.3.1   |x| | | | | N/A
 *                                                  |        | | | | | |
 * User Telnet interface                            |        | | | | | | N/A
 *                                                  |        | | | | | |
 * 
 *  <X1> - We allow negotiation until connection to remote service.  Whether
 *         negotiation is allowed then depends on whether the remote service
 *         allows it (e.g. telnet would, finger would not)
 * 
 *  <X2> - We do not implement binary (does not matter since this is only
 *	   name, password, and command mode).  Once connected to remote
 *	   service, binary may be negotiated iff remote telnet allows it.
 *	   SGA is negotiated on and off as needed.
 * 
 *  <X3> - We negotiate echo on and off as needed.  Other options initally
 * 	   unnegotiated until connect.  After then as remote side supports
 * 
 *  <X4> - We do not implement this--however, once connected to the remote
 *	   service, any options which it supports will work.
 * 
 *  <X5> - We do NOT implement urgent data since discarding data will cause
 *	   encrypting telnet and other such utilities to loose very much.
 * 
 * 
 **********************************************************************/
static int 
telnet_IAC(FILE * in, FILE * out, unsigned char escape)
{
  int fdout = fileno(out);
  unsigned char command;
  unsigned char telopt;

  fflush(out);

  /* Hmm.  Why are we here? */
  if (escape != IAC)
    return (escape);

  /* Get telnet command */
  if (fread(&command, 1, 1, in) != 1)
    return (-1);

  /* Quoting the escape character */
  if (command == escape)
    return (command);

  if (command == SB)
    {				/* We need to discard until IAC SE */
      char esb_seq[2];
      int esb_len = 2;
      int esb_stat = 0;
      char input;

#ifdef DEBUG2
      fprintf(stderr, "   IAC SB\r\n");
#endif

      esb_seq[0] = IAC;
      esb_seq[1] = SE;

      while (esb_stat < esb_len)
	{
	  if (fread(&input, 1, 1, in) != 1)
	    return (-1);
	  if (input == esb_seq[esb_stat])
	    esb_stat++;
	}
      return (0);		/* IAC SB ... IAC SE */
    }

  switch (command)
    {
    case DONT:			/* Three character sequence */
    case DO:
    case WONT:
    case WILL:
      break;
    default:			/* Two character sequence */

#ifdef DEBUG2
      fprintf(stderr, "   IAC foo (%d)\r\n", command);
#endif
      return (0);
    }

  /* We are in a telnet IAC [DONT|DO|WONT|WILL] <foo> sequence */

  /* Get telnet option */
  if (fread(&telopt, 1, 1, in) != 1)
    return (-1);

  if (should_store_options)
    storeopt(command, telopt);

  switch (telopt)
    {
    case TELOPT_ECHO:
#ifdef DEBUG2
      fprintf(stderr, "   IAC (%d) ECHO\r\n", command);
#endif
      if (Outstanding[telopt] == WONT)
	return (tel_inv_opt(fdout, command, telopt));
      return (tel_for_opt(fdout, command, telopt));
      break;
    case TELOPT_SGA:
#ifdef DEBUG2
      fprintf(stderr, "   IAC (%d) SGA\r\n", command);
#endif
      if (Outstanding[telopt] == WONT)
	return (tel_inv_opt(fdout, command, telopt));
      return (tel_for_opt(fdout, command, telopt));
      break;
    default:
#ifdef DEBUG2
      fprintf(stderr, "   IAC (%d) (%d) [[%d]]\r\n", command, telopt, TELOPT_SGA);
#endif
      return (tel_inv_opt(fdout, command, telopt));
    }

  return (0);
}



/*
 * Negotiate a telnet option off
 */
static int 
tel_inv_opt(int fd, unsigned char cmd, unsigned char telopt)
{
  unsigned char newcmd = -1;

  switch (cmd)
    {
    case WILL:
    case WONT:
      newcmd = DONT;
      break;
    case DO:
    case DONT:
      newcmd = WONT;
      break;
    }

  return (send_option(fd, newcmd, telopt));
}



/*
 * Negotiate an option on
 */
static int 
tel_for_opt(int fd, unsigned char cmd, unsigned char telopt)
{
  unsigned char newcmd = -1;

  switch (cmd)
    {
    case WILL:
    case WONT:
      newcmd = DO;
      break;
    case DO:
    case DONT:
      newcmd = WILL;
      break;
    }

  return (send_option(fd, newcmd, telopt));
}



/*
 * Send option over wire if we are not already in that mode (we sent that cmd last)
 */
static int 
send_option(int fd, unsigned char cmd, unsigned char telopt)
{
  unsigned char seq[3];


  seq[0] = IAC;
  seq[1] = cmd;
  seq[2] = telopt;

  if (Outstanding[telopt] == cmd)
    return (0);			/* We have sent this before */

  Outstanding[telopt] = cmd;

#ifdef DEBUG2
  fprintf(stderr, "Sending %d %d\r\n", cmd, telopt);
#endif				/*DEBUG */

  if (write(fd, seq, 3) != 3)
    return (-1);

  return (0);
}



/*
 * Not currently being used
 */
static int 
saw_option(int fd, unsigned char cmd, unsigned char telopt)
{
  Outstanding[telopt] = IAC - cmd;
  return (0);
}



/*
 * Store what options client has asked for
 */
static int 
storeopt(unsigned char cmd, unsigned char telopt)
{
  if (numStoredOpts >= MAXSTOREOPT)
    return (-1);

  StoredOpts[numStoredOpts * 2] = cmd;
  StoredOpts[numStoredOpts * 2 + 1] = telopt;

  numStoredOpts++;

  return(0);
}



/*
 * Output stored options
 */
static int 
outputopt(int fd)
{
  int c;

#ifdef DEBUG2
  fprintf(stderr, "Sending %d stored options\r\n", numStoredOpts);
#endif				/*DEBUG */

  for (c = 0; c < numStoredOpts; c++)
    {
      if (send_option(fd, StoredOpts[c * 2], StoredOpts[c * 2 + 1]) < 0)
	return (-1);
    }
  return (0);
}
