#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "bftp.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--
 */

/*
 * bftp.c
 *
 * Proxy FTP server.  Accept connections from network, authenticate
 * user, then allow user to pass on to final destination
 */


#include "bsClient.h"

#include <dict.h>
#include <dll.h>


/* #define BFTPDEBUG */
#undef DEBUG

#ifndef	VERSION
#define	VERSION	"1.1.1.1"
#endif



#define POS_PRELIM '1'
#define POS_COMPLETE '2'
#define POS_INTERMED '3'
#define NEG_TRANS '4'
#define NEG_COMPLETE '5'
#define READER 0
#define WRITER 1
#define SHORTBUF 512
#define SMALLBUF 512
#define WHITESPACE " "
#define NONE 0x0
#define IS_CONN 0x1
#define NOT_CONN 0x2
#define CONNTIMEOUT "15"


static int bftpport = 21;	/* Only used if setting up service ourselves */
static char *optstr = "dSf:hsp:";
static sos_config_t config;	/* Config file pointer */
int bftpdebug = 0;
char *program;			/* Program name. */
char *username = NULL;		/* Username after authentication. */
struct sos_conninfo sourceinfo =
{NULL, -1, 0L, -1};
struct sos_conninfo destinfo =
{NULL, -1, 0L, -1};
int sfdin = 0;
int sfdout = 1;
FILE *sfilein = NULL;
FILE *sfileout = NULL;
FILE *remfilein = NULL;
FILE *remfileout = NULL;
struct sos_readline_t readopts;
static int cmd_state = NOT_CONN;	/* Used to control valid commands */
static int IoTimeout = 0;
static char ioinfodata[SHORTBUF];	/* Buffer for child/parent communication */
static int child2parent[2];
static int can_report;		/* True if child can report stats to parent */
static struct sos_bdr_fdpair relay[2], *prelay;
static struct sos_bdr_auxinfo auxinfo;
static int peer_count = 2;	/* Number of peers in the relay set */

extern char *optarg;


static int standalone();	/* For use without inetd.  */
static void greet(FILE *);
static void authenticate(FILE *, FILE *);
static void exiterror(char *, int);
static void exitcleanup(int);
static void run_data_relay(int, int, int, int *, int *);
static int proc_control_cmd();
static int filtered_cmd(char *);
static int do_bridge(int, int);
static void read_io_stats();
static void unbatch(FILE * stream, int ret_code, dict_h * lines);
static void ftp_batch(dict_h * lbuf, char *line);
static int sub_user(char *);
static int sub_pasv(char *);
static int sub_port(char *);
static int sub_help(char *);
static int sub_quit(char *);
static int sub_noop(char *);



static struct ftpcmd
  {
    char *cmdstr;		/* Command string read from client */
    int is_restricted;		/* True if command is restricted to state */
    int state_restricted;	/* What state can system be in and use cmd */
    int (*sub) (char *data);
  }
ftpcmds[] =
{
  {
    "user", 1, NOT_CONN, sub_user
  }
  ,
  {
    "help", 1, NOT_CONN, sub_help
  }
  ,
  {
    "quit", 0, NOT_CONN, sub_quit
  }
  ,
  {
    "pasv", 1, IS_CONN, sub_pasv
  }
  ,
  {
    "port", 1, IS_CONN, sub_port
  }
  ,
  {
    "noop", 1, NOT_CONN, sub_noop
  }
};


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



/*
 * Timeouts
 */
int timedout = 0;
void 
sigtimeout()
{
  timedout = 1;

  soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, SOSLOG_NO_PERROR,
	 "Timing out due to inactivity");
  return;
}
static struct sigaction old_sigaction;	/* Caches old signal vec. */
static struct sigaction iotimeout = SIGACTIONDEFINE( sigtimeout, 0, _INTERRUPT_SYSCALLS_ );



/*
 * SIGCHLD handler
 */
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);

}
/*static struct sigaction childdeath = SIGACTIONDEFINE( sigchld, 0, _INTERRUPT_SYSCALLS_ );*/
static struct sigaction childdeath = SIGACTIONDEFINE( SIG_IGN, 0, _INTERRUPT_SYSCALLS_ );



int
main(int argc, char *argv[], char *envp[])
{

  int c;			/* Getopt() char */
  char *tmp;			/* Various uses */
  char *config_file;		/* Config file name */
  int ftpstandalone = 0;	/* True if not running with inetd */
  int from_stdin = 0;		/* True if running alone from stdin */

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

  config_file = SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF);

  while ((c = getopt(argc, argv, optstr)) != -1)
    switch (c)
      {
      case 'd':
	bftpdebug++;
	break;
      case 'p':
	bftpport = atoi(optarg);
	break;
      case 'f':
	config_file = optarg;
	break;
      case 'S':
	ftpstandalone++;
	break;
      case 's':
	from_stdin = 1;
	break;
      case 'h':
      default:
	fprintf(stderr, "usage: %s -[%s]\n", argv[0], optstr);
	exitcleanup(c != 'h');
      }


#ifdef BFTPDEBUG
  if (!bftpdebug)
    {
      FILE *fd;

      /*
       * PURE debugging code. You are not only not expected to understand 
       * this, it would only make you sick when you did :-)
       */
      bftpdebug++;		/* Turn on debugging */
      fclose(stderr);
      fd = freopen("/tmp/bftpout", "a+", stderr);
      fprintf(stderr, "\n\nBREAKER\n\n");
      setvbuf(stderr, NULL, _IONBF, 0);

    }
#endif


  open_soslog(program, bftpdebug);

  sigaction(SIGCHLD, (struct sigaction *)&childdeath, NULL);

  if (ftpstandalone && standalone() < 0)
    exiterror("Standalone startup failed", 0);

  if ((sfilein = fdopen(sfdin, "r+")) == NULL)
    exiterror("fdopen", 1);

  if ((sfileout = fdopen(sfdout, "w")) == NULL)
    exiterror("fdopen", 1);
  setbuf(sfileout, NULL);


  /*
   * Set up parent child comunication
   */
  can_report = SOS_TRUE;
  if (pipe(child2parent) < 0)
    {
      if (bftpdebug)
	perror("main(): Setting up child to parent pipe");
      soslog(SOSLOG_WARNING, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
	     "Data io statistics logging supressed. Communication chanel set up failed");
      can_report = SOS_FALSE;
    }


  /* Find out to whom we are connected */
  if (sos_getpeername(sfdin, &sourceinfo) < 0)
    {
      if (from_stdin)
	sourceinfo.printhost = strdup(".stdin. [0.0.0.0]");
      else
	{
	  if (bftpdebug)
	    fprintf(stderr, "bftp: sos_getpeername failed\n");
	  exiterror("sos_getpeername", 1);
	}
    }

  soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	 "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;

  auxinfo.veclen = 5;
  auxinfo.totbuf = 8;
  auxinfo.buflen = 16384;
  auxinfo.align = 16384;
  auxinfo.offset = 0;
  auxinfo.common_exit = 0;
  auxinfo.fullread = 0;

  prelay = &relay[0];
  relay[0].rinfo.fd = -1;
  relay[0].rinfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))read;
  relay[0].winfo.fd = -1;
  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 = -1;
  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);
  


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


  IoTimeout = atoi(SOS_GWD("FTP_CMDTIMEOUT", "600"));

  if (bftpdebug)
    fprintf(stderr, "main(): Setting global io timeout to %d\n", IoTimeout);

  /* set up readline information */
  readopts.echo = -1;
  readopts.wantalert = 0;	/* Telnet only (I think) */
  readopts.wantedit = 0;	/* Telnet only.  */
  readopts.wantnull = 0;
  readopts.escape_char = 0;
  readopts.escape_mode = 0;
  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;

  (void)greet(sfileout);

  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);
  (void)authenticate(sfilein, sfileout);

  bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_CMD);
  if (proc_control_cmd() < 0)
    exiterror("Error in proxy command loop", 1);

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



/*
 * Should be run in standalone mode
 */
int
standalone()
{
  int s;			/* temporary socket. */
  struct sockaddr_in sockname;	/* Name to bind to */
  int new;
  int len = sizeof(sockname);

  sockname.sin_family = AF_INET;
  sockname.sin_port = bftpport;
  sockname.sin_addr.s_addr = INADDR_ANY;


  if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      return -1;
    }

  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)NULL, 0) < 0)
    {
      return -1;
    }

  if (bind(s, (struct sockaddr *)&sockname, sizeof(sockname)) < 0)
    {
      return -1;
    }

  if (listen(s, 10) < 0)
    {
      return -1;
    }

  if ( (new=accept(s, (struct sockaddr *)&sockname, (int *)&len)) < 0 )
    {
      return -1;
    }

  if (dup2(new, 0) < 0 || dup2(new, 1)<0 )
    {
      return -1;
    }

  close(s);
  close (new);

  return 0;
}



/*
 * Print out queued output lines
 * with proper return codes
 */
void
bftp_speak(FILE * stream, int return_code, dict_h lines)
{
  char *line;			/* Line to output */

  while (line = (char *)dll_maximum(lines))
    {
      if (return_code)
	{
	  (void)fprintf(stream, "%d", return_code);
	  if (dll_predecessor(lines, line))
	    (void)fprintf(stream, "-");
	  (void)fprintf(stream, " ");
	}
      (void)fprintf(stream, "%s%s", line, readopts.eolseq[0].str);
      fflush(stdout);
      dll_delete(lines, line);
      free(line);
    }

  dll_destroy(lines);
}



/*
 * Print one line of out in ftp style
 */
void
bftp_speak1(FILE * stream, int return_code, char *line)
{
  if (return_code)
    (void)fprintf(stream, "%d %s%s", return_code, line, readopts.eolseq[0].str);
  else
    (void)fprintf(stream, "%s%s", line, readopts.eolseq[0].str);
  return;
}



/*
 * Greet the remote user
 */
void
greet(FILE * fileout)
 /* 
  * Just build the Greet sequence. 
  */
{
  dict_h greet_lines = NULL;
  char *tmp;
  char *line;
  char RealGreet[512];

  while ((tmp = sos_config_getnext(config, "FTP_GREET", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
    {
      line = strdup(tmp);
      if (bftpdebug)
	fprintf(stderr, "greet(): Reading: **%s**\n", tmp);
      (void)ftp_batch(&greet_lines, (dict_obj) line);
    }

  (void)memset(RealGreet, (char)0, 512);
  (void)sprintf(RealGreet, "%s proxy ftp version %s ready at %s",
		sos_fqhname(NULL, 0), VERSION, sos_prompttime(NULL, 0));

  (void)ftp_batch(&greet_lines, (dict_obj) strdup(RealGreet));
  unbatch(fileout, 220, &greet_lines);
}



/*
 * Authenticate the connecting user
 */
void 
authenticate(FILE * filein, FILE * fileout)
{
  sos_string UNAME;
  sos_string INBUF, tmpinbuf;
  sos_string OUTBUF;
  char *line;
  int timeout;
  unsigned int nextstate = 0;
  unsigned int req_input = BS_NO_INPUT;
  int state_ret;
  int ret;
  char *cmd;			/* Command string read on input line */
  int acctflag = 0;		/* True when comand for ACCT  */
  char *validpass[2] =
    {"pass", "acct"};
  int validpassresp[2] =
    {331, 332};
  dict_h batched_output = NULL;	/* See comment below decl section */
  char *tmp;			/* clc needs to strdup */

  if (bftpdebug)
    fprintf(stderr, "authenticate(): Entering\n");

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

  timeout = atoi(SOS_GWD("FTP_AUTHTIMEOUT", "90"));
  if (bftpdebug)
    fprintf(stderr, "authenticate(): Setting authenticate timeout to %d seconds", timeout);

user:
  do
    {
      ret = sos_readline_timeout(filein, fileout, &UNAME, &readopts, &iotimeout, timeout);
      if (timedout)
	exiterror("timed out", 0);
      if (!ret)
	exiterror("Client aborted connection", 0);
      if (ret == -1)
	exiterror("readline", 1);

      if (bftpdebug)
	fprintf(stderr, "authenticate(): USER read: **%s**\n", UNAME.str);
      if (ret < 0)
	{
	  /*
	   * Buffer overrun
	   */
	  bftp_speak1(fileout, 500, SOS_GWD("FTP_INVALID", "Invalid Entry"));
	  ret = 0;
	  continue;
	}
      sos_rip(UNAME.str);
      if (strlen(UNAME.str) == 0)
	{
	  ret = 0;
	  continue;
	}

      /*
       * Check for USER command
       */
      if (!(cmd = strtok(UNAME.str, WHITESPACE)))
	{
	  bftp_speak1(fileout, 500, "Syntax Error");
	  ret = 0;
	  continue;
	}
      sos_strtolower(cmd);

      if (bftpdebug)
	fprintf(stderr, "authenticate(): Parsing: command: **%s**\n", cmd);


      if (strcasecmp(cmd, "user") && strcasecmp(cmd, "quit"))
	{
	  /*
	   * XXX This violates RFC-959.
	   * USER should *not* return "comand out of sequence" but 
	   * clearly that's what would like to return.
	   */
	  bftp_speak1(fileout, 503, "Proxy requires USER command first");
	  ret = 0;
	  continue;
	}

      if (!strcasecmp(cmd, "quit"))
	sub_quit("");		/* Early withdrawl */

      if (username)
	free(username);

      username = strtok(NULL, WHITESPACE);

      if (!username || strtok(NULL, WHITESPACE))
	{
	  /*
	   * Buffer overrun
	   */
	  username = NULL;
	  bftp_speak1(fileout, 500, SOS_GWD("FTP_INVALID", "Invalid Entry"));
	  ret = 0;
	  continue;
	}

      username = strdup(username);
      if (bftpdebug)
	fprintf(stderr, "authenticate(): Parsing: username: **%s**\n", username);
    }
  while (ret == 0);

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

  /* Enter into getchallenge state engine */
  nextstate = 0;
  acctflag = 0;			/* RFC-959 requires strict reply code ordering */
  tmpinbuf.str = INBUF.str;
  tmpinbuf.len = INBUF.len;

  while (1)
    {
      req_input = BS_NO_INPUT;

      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_ACL);
      state_ret = BS_State(BS_GetChallenge, username, &nextstate,&req_input, &OUTBUF, &tmpinbuf);
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);

      if (OUTBUF.str[0])
	{
	  if (bftpdebug)
	    (void)fprintf(stderr, "authenticate(): Challenge read: %s\n",
			  OUTBUF.str);
	  tmp = strdup(OUTBUF.str);
	  ftp_batch(&batched_output, (dict_obj) tmp);
	}


      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, 0,
	     "authenticate: getchallenge: %d = (%d, %d, %s)",
	     state_ret, nextstate, req_input, OUTBUF.str);
      if (req_input != BS_NO_INPUT)
	{

	  if (bftpdebug)
	    (void)fprintf(stderr, "authenticate(): UNBATCHING\n");
	  unbatch(fileout, validpassresp[acctflag], &batched_output);

	  ret = sos_readline_timeout(filein, fileout, &INBUF, &readopts,
				     &iotimeout, timeout);
	  if (timedout)
	    exiterror("timed out", 0);
	  if (!ret)
	    exiterror("Client aborted connection", 0);
	  if (ret == -1)
	    exiterror("readline", 1);
	  if (ret < 0)
	    {
	      bftp_speak1(fileout, 500, SOS_GWD("FTP_INVALID", "Invalid entry"));
	      ret = 0;
	      continue;
	    }

	  sos_rip(INBUF.str);
	  if (strlen(INBUF.str) == 0)
	    {
	      ret = 0;
	      continue;
	    }

	  if (!(cmd = strtok(INBUF.str, WHITESPACE)))
	    {
	      bftp_speak1(fileout, 500, "Syntax Error");
	      ret = 0;
	      continue;
	    }
	  sos_strtolower(cmd);

	  if (bftpdebug)
	    fprintf(stderr, "authenticate(): Parsing: command: **%s**\n", cmd);

	  if (strcasecmp(cmd, validpass[acctflag]) && strcasecmp(cmd, "quit"))
	    {
	      /*
	       * XXX This violates RFC-959.
	       * USER should *not* return "comand out of sequence" but 
	       * clearly that's what would like to return.
	       */
	      bftp_speak1(fileout, 503, "Command out of sequence");
	      goto user;
	    }

	  /* Sigh.. quit can occur at any time */
	  if (!strcasecmp(cmd, "quit"))
	    sub_quit("");

	  if (!(tmpinbuf.str = strtok(NULL, "")))
	    {
	      bftp_speak1(fileout, 500, "Syntax Error");
	      ret = 0;
	      continue;
	    }
	  tmpinbuf.len = strlen(tmpinbuf.str) + 1;

	  acctflag = 1;

	}			/* End of collecting input. */

      if (!nextstate)
	{
	  break;
	}
    }

  if (state_ret != AUTH_SUCCEED)
    {

      if (sos_config_getnext(config, "FTP_FAIL", SOS_CONFIG_STATIC,
			     SOS_CONFIG_NULL))
	{
	  char *tmp;

	  while ((tmp = sos_config_getnext(config, "FTP_FAIL", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	    {
	      line = strdup(tmp);
	      if (bftpdebug)
		fprintf(stderr, "authenticate(): Failure reading: **%s**\n", tmp);
	      (void)ftp_batch(&batched_output, (dict_obj) line);
	    }
	}
      else
	{
	  ftp_batch(&batched_output, strdup("Authentication failed"));
	}

      /* 
       * UGH return value 500 is totally bogus but none of the acceptable
       * returns from 'USER' are any better. The '5' is the main thing 
       * at any rate.
       */
      unbatch(fileout, 500, &batched_output);

      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(0);
    }

  /* Enter into authenticate 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, username, &nextstate, &req_input, &OUTBUF, &tmpinbuf);
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_AUTH);

      if (OUTBUF.str[0])
	{
	  tmp = strdup(OUTBUF.str);
	  if (bftpdebug)
	    fprintf(stderr, "authenticate(): Authenticate read: **%s**\n",
		    OUTBUF.str);
	  ftp_batch(&batched_output, (dict_obj) tmp);
	}

      soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, 0,
	     "authenticate: authenticate: %d = (%d, %d, %s)",
	     state_ret, nextstate, req_input, OUTBUF.str);

      if (req_input != BS_NO_INPUT)
	{

	  if (bftpdebug)
	    fprintf(stderr, "authenticate(): UNBATCHING\n");
	  unbatch(fileout, validpassresp[acctflag], &batched_output);

	  ret = sos_readline_timeout(filein, fileout, &INBUF,
				     &readopts, &iotimeout, timeout);
	  if (timedout)
	    exiterror("timed out", 0);
	  if (!ret)
	    exiterror("Client aborted connection", 0);
	  if (ret == -1)
	    exiterror("readline", 1);
	  if (ret < 0)
	    {
	      bftp_speak1(fileout, 500, SOS_GWD("FTP_INVALID", "Invalid entry"));
	      ret = 0;
	      continue;
	    }
	  sos_rip(INBUF.str);
	  if (strlen(INBUF.str) == 0)
	    {
	      ret = 0;
	      continue;
	    }


	  /*
	   * Check for USER command
	   */
	  if (!(cmd = strtok(INBUF.str, WHITESPACE)))
	    {
	      bftp_speak1(fileout, 500, "Syntax Error");
	      ret = 0;
	      continue;
	    }
	  sos_strtolower(cmd);

	  if (bftpdebug)
	    fprintf(stderr, "authenticate(): Parsing: command: **%s**\n", cmd);

	  if (strcasecmp(cmd, validpass[acctflag]) && strcasecmp(cmd, "quit"))
	    {
	      bftp_speak1(fileout, 503, "Command out of sequence");
	      goto user;
	    }

	  /* Sigh.. Quit can come at any time. */
	  if (!strcasecmp(cmd, "quit"))
	    sub_quit("");


	  if (!(tmpinbuf.str = strtok(NULL, "")))
	    {
	      bftp_speak1(fileout, 500, "Syntax Error");
	      ret = 0;
	      continue;
	    }
	  tmpinbuf.len = strlen(tmpinbuf.str) + 1;

	  acctflag = 1;
	}

      else
	{
	  /*
	   * NO input necessary so send a full reply if there  was any
	   * output
	   */
	}
      if (!nextstate)
	{
	  break;
	}
    }

  if (state_ret != AUTH_SUCCEED)
    {

      if (sos_config_getnext(config, "FTP_FAIL", SOS_CONFIG_STATIC,
			     SOS_CONFIG_NULL))
	{
	  char *tmp;

	  while ((tmp = sos_config_getnext(config, "FTP_FAIL", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	    {
	      line = strdup(tmp);
	      if (bftpdebug)
		fprintf(stderr, "authenticate(): Failure reading: **%s**\n", tmp);
	      (void)ftp_batch(&batched_output, (dict_obj) line);
	    }
	}
      else
	{
	  ftp_batch(&batched_output, strdup("Authentication failed"));
	}

      /* 
       * output will always be batched at this point, but ...
       */
      unbatch(fileout, 531, &batched_output);

      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(0);
    }

  soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 0,
	 "authenticate: user %s from %s authenticated",
	 username, sourceinfo.printhost);

  if (sos_config_getnext(config, "FTP_WELCOME", SOS_CONFIG_STATIC,
			 SOS_CONFIG_NULL))
    {
      char *tmp;

      while ((tmp = sos_config_getnext(config, "FTP_WELCOME",
				       SOS_CONFIG_FORWARD,
				       SOS_CONFIG_LINEAR)) != NULL)
	{
	  line = strdup(tmp);
	  if (bftpdebug)
	    fprintf(stderr, "authenticate(): Welcome reading: **%s**\n", tmp);
	  (void)ftp_batch(&batched_output, (dict_obj) line);
	}

    }
  else
    {
      line = strdup("Welcome to Brimstone.");
      ftp_batch(&batched_output, (dict_obj) line);
    }

  /* 
   * Actually output should *always* be batched if we get here. But just
   * to keep the code looking consistent....
  if (bftpdebug)
    fprintf(stderr, "authenticate(): PANIC UNBATCH\n");
   */
  unbatch(fileout, 230, &batched_output);


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



/*
 * Exit while logging an error message
 */
void 
exiterror(char *msg, int wanterrno)
{
#ifdef DEBUG
  char *errmsg = (char *)strerror(errno);
#endif

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

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

  exitcleanup(1);
}



/*
 * Exit and log final statistics
 */
void 
exitcleanup(int status)
{
  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(status);
}



/*
 * Control connection commands and monitoring
 */
int
proc_control_cmd()
{
  sos_string INBUF;
  char *cmd_data;
  int ret;
  char buf[SMALLBUF];
  char *cmd;
  int cmd_index;
  fd_set readfd;
  struct timeval timeoutval;
  int ret_val;

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


  timeoutval.tv_sec = IoTimeout;
  timeoutval.tv_usec = 0;

user:
  do
    {
      if (bftpdebug)
	fprintf(stderr, "proc_control_cmd(): Loop top: io stats descriptor: %d\n",
		child2parent[READER]);

      FD_ZERO(&readfd);
      FD_SET(sfdin, &readfd);
      FD_SET(child2parent[READER], &readfd);
    restart:
      sigchld(); /* Clean up any children before reading */
      if ((ret_val = select(child2parent[READER] + 1, &readfd,
			    NULL, NULL, &timeoutval)) < 0)
	{
	  if (errno == EINTR)
	    goto restart;
	  if (bftpdebug)
	    perror("proc_control_cmd(): Select failed");
	  return -1;
	}
      else if (ret_val == 0)
	exiterror("time out", 0);

      if (FD_ISSET(child2parent[READER], &readfd))
	{
	  if (bftpdebug)
	    fprintf(stderr, "proc_control_cmd(): about to read io stats\n");
	  read_io_stats();
	  goto user;
	}

      if (bftpdebug)
	fprintf(stderr, "proc_control_cmd(): Select returned: %d\n",
		ret_val);

      sigchld(); /* Clean up any children before reading */
      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 (bftpdebug)
	fprintf(stderr, "proc_control_cmd(): INBUF read: **%s**\n", INBUF.str);

      if (ret < 0)
	{
	  /*
	   * Buffer overrun
	   */
	  bftp_speak1(sfileout, 500, SOS_GWD("FTP_INVALID", "Invalid Entry"));
	  continue;
	}
      /*
       * Dont forget you will have to restore this
       */
      sos_rip(INBUF.str);

      /*
       * Check for filtered  command
       */
      if (strlen(INBUF.str) == 0)
	{
	  /*
	   *  NULL Command will cause pain
	   */
	  bftp_speak1(sfileout, 500, SOS_GWD("FTP_BADINPUT", "Syntax Error or command not understood"));
	  continue;
	}

      /*
       * Copy inbuf to local buffer
       */
      (void)memset(buf, (char)0, SMALLBUF);
      (void)memcpy(buf, INBUF.str, strlen(INBUF.str));

      if (!(cmd = strtok(buf, WHITESPACE)))
	{
	  bftp_speak1(sfileout, 500, SOS_GWD("FTP_BADINPUT", "Syntax Error or command not understood"));
	  continue;
	}

      sos_strtolower(cmd);

      if (bftpdebug)
	fprintf(stderr, "proc_control_cmd(): Parsing: command: **%s**\n", cmd);

      /*
       * XXX Check for all possible supported commands here. 
       * Do the right thing.
       */
      if ((cmd_index = filtered_cmd(cmd)) == -1)
	{
	  if (cmd_state != IS_CONN)
	    /*
	     * Signal error since we are not connected. Otherwise let it 
	     * pass through.
	     */
	    bftp_speak1(sfileout, 500, SOS_GWD("FTP_BADINPUT", "Syntax Error or command not understood"));
	  else
	    {
	      /*
	       * COMMAND PASS TROUGH
	       * Write out current command, wait for and pass through reply
	       */
	      fprintf(remfileout, "%s%s", INBUF.str, readopts.eolseq[0].str);

	    reply_loop:
	      do
		{
		  sigchld(); /* Clean up any children before reading */
		  ret = sos_readline_timeout(remfilein, remfileout, &INBUF, &readopts, &iotimeout, IoTimeout);
		  if (timedout)
		    exiterror("timed out", 0);
		  if (!ret)
		    exiterror("Client aborted connection", 1);
		  if (ret == -1)
		    exiterror("readline", 1);
		  /*
		   * This is pendantic but we should only reply with the eoln seq
		   * expected from us
		   */
		  sos_rip(INBUF.str);
		  fprintf(sfileout, "%s%s", INBUF.str, readopts.eolseq[0].str);


		}
	      while (!(isdigit(INBUF.str[0]) &&
		       isdigit(INBUF.str[1]) &&
		       isdigit(INBUF.str[2]) &&
		       INBUF.str[3] == ' '));
	      if (*INBUF.str == POS_PRELIM)
		goto reply_loop;

	    }
	  continue;
	}
      /* 
       * Filtered data thread only. 
       */
      cmd_data = (cmd + strlen(cmd) + 1);
      if (*cmd_data && bftpdebug)
	fprintf(stderr, "proc_control_cmd(): cmd_data: **%s**\n", cmd_data);


      if ((ftpcmds[cmd_index].sub) (cmd_data) < 0)
	{
	  /*
	   * XXXX Log this error. This function should return a value.
	   */
	  return -1;
	}


    }
  while (1);

  return 0;


}



/*
 * Check to see if incoming command should be filtered by our server given
 * the state it is in right now. If so return index of match, if not return 
 * -1
 */
int
filtered_cmd(char *cmd)
{
  int num_cmds;
  int count;
  int ret = -1;

  num_cmds = sizeof(ftpcmds) / sizeof(ftpcmds[0]);

  if (bftpdebug)
    fprintf(stderr, "filtered_cmd(): Number of commands: %d\n", num_cmds);

  for (count = 0; count < num_cmds && ret == -1; count++)
    {
      if (!strcasecmp(cmd, ftpcmds[count].cmdstr))
	{
	  if (ftpcmds[count].is_restricted)
	    {
	      if (ftpcmds[count].state_restricted == cmd_state)
		ret = count;
	    }
	  else
	    ret = count;
	}
    }

  if (bftpdebug)
    fprintf(stderr, "filtered_cmd(): Returning %d\n", ret);
  return ret;
}



/*
 * User command
 */
int
sub_user(char *data)
{
  char *user;			/* Remote identity */
  char *remhostname;		/* Remote ftp server string. */
  int s;			/* Socket */
  int servport;			/* ftp port  */
  int ret;			/* Dumb return code variable */
  sos_string buf;		/* Buffer for reading and wrinting stdio */
  int done = 0;			/* Required for hack. */
  int ret_val;			/* Return value for acl check */
  dict_h batched_output = NULL;	/* Same use as in authenticate() */
  char *tmp;

  if (!sos_allocstr(&buf, SMALLBUF))
    return -1;

  if (!(user = strtok(data, "@")))
    {
      bftp_speak1(sfileout, 500, SOS_GWD("FTP_BADINPUT", "Syntax Error or command not understood"));
      return(0);
    }
  if (!(remhostname = strtok(NULL, WHITESPACE)) || strtok(NULL, WHITESPACE))
    {
      bftp_speak1(sfileout, 500, SOS_GWD("FTP_BADINPUT", "Syntax Error or command not understood"));
      return(0);
    }

  if (bftpdebug)
    fprintf(stderr, "sub_user(): user: **%s** -- hostname: **%s**\n",
	    user, remhostname);


  if ((servport = sos_getsbyfoo("ftp")) < 0)
    {
      bftp_speak1(sfileout, 500, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  destinfo.printhost = remhostname;
  destinfo.port = servport;

  bs_setProcTitle(username, &sourceinfo, &destinfo, BS_SPTP_CONN);
  if ((s = bs_make_conn(remhostname, servport, atoi(SOS_GWD("FTP_CONNTIMEOUT", CONNTIMEOUT)),
			     SO_REUSEADDR, username, &sourceinfo, NULL)) < 0)
    {
      bs_setProcTitle(username, &sourceinfo, NULL, BS_SPTP_CMD);

      if (s == -1)
	bftp_speak1(sfileout, 500, SOS_GWD("FTP_CONNFAIL","Connection failed."));
      else
	bftp_speak1(sfileout, 500, SOS_GWD("FTP_UNKHOST","Host unknown."));
      return (0);
    }

  if (bftpdebug)
    fprintf(stderr, "sub_user(): Connection established\n");

  /*
   * We are connected to remote server's ftp. 
   */
  if (sos_getpeername(s, &destinfo) < 0)
    {
      return -1;
    }

  soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR, "Control connection established: source: %s:%d  -- dest: %s:%d\n",
	 sourceinfo.printhost,
	 sourceinfo.port,
	 destinfo.printhost,
	 destinfo.port);


  if ((remfilein = fdopen(s, "r+")) == NULL)
    return -1;
  if ((remfileout = fdopen(s, "w")) == NULL)
    return -1;
  setvbuf(remfileout, NULL, _IONBF, 0);

  /*
   * Absorb greeting.
   * AIIEEE!!! GROSS HACK ALERT! But unavoidable. Since we're going to 
   * handle the first comand (user) we need to make sure that the ftp
   * client *only* processes the "331 Pass" at after the user command.
   * We do this by faking by replacing the 200 command code with a 
   * "331" *and* replacing the final "220 " with "331-"
   */

reply_loop1:
  do
    {
      int offset;		/* Cute hack so we can 'ovewrite' reply code */

      offset = 0;
      ret = sos_readline_timeout(remfilein, remfileout, &buf, &readopts, &iotimeout, IoTimeout);
      if (timedout)
	exiterror("timed out", 0);
      if (!ret)
	exiterror("Client aborted connection", 1);
      if (ret == -1)
	exiterror("readline", 1);
      /*
       * This is pendantic but we should only reply with the eoln seq
       * expected from us
       */
      sos_rip(buf.str);

      if (isdigit(buf.str[0]) &&
	  isdigit(buf.str[1]) &&
	  isdigit(buf.str[2]))
	{
	  if (buf.str[3] == ' ')
	    done = 1;

	  if (buf.str[3] == '-')
	    offset = 4;
	  else
	    offset = 3;
	}
      tmp = strdup(buf.str + offset);
      ftp_batch(&batched_output, tmp);

      if (bftpdebug)
	fprintf(stderr, "cmd_user(): Greet reading: **%s**\n", buf.str);
    }
  while (!done);


  /*
   * Login as user
   */
  fprintf(remfileout, "user %s%s", user, readopts.eolseq[0].str);
  fflush(remfileout);

  /*
   * If the site admin wants to cobble up a fake user line
   */
  if (SOS_GWD("FTP_FAKE_USER", NULL))
    {
      char tmpbuf[SHORTBUF];

      tmp = strdup(" ");
      ftp_batch(&batched_output, tmp);
      tmp = strdup(" ");
      ftp_batch(&batched_output, tmp);
      sprintf(tmpbuf, "Name (%s:%s): %s",
	      destinfo.printhost,
	      username, data);
      ftp_batch(&batched_output, strdup(tmpbuf));
    }

  /*
   * Absorb user reply
   */

reply_loop2:
  done = 0;
  do
    {
      int offset;

      offset = 0;

      ret = sos_readline_timeout(remfilein, remfileout, &buf, &readopts, &iotimeout, IoTimeout);
      if (timedout)
	exiterror("timed out", 0);
      if (!ret)
	exiterror("Client aborted connection", 1);
      if (ret == -1)
	exiterror("readline", 1);
      /*
       * This is pendantic but we should only reply with the eoln seq
       * expected from us
       */
      sos_rip(buf.str);


      if (isdigit(buf.str[0]) &&
	  isdigit(buf.str[1]) &&
	  isdigit(buf.str[2]))
	{
	  if (buf.str[3] == ' ')
	    done = 1;

	  if (buf.str[3] == '-')
	    offset = 4;
	  else
	    offset = 3;
	}


      tmp = strdup(buf.str + offset);
      ftp_batch(&batched_output, tmp);


      if (bftpdebug)
	fprintf(stderr, "cmd_user(): USER reading: **%s**\n", buf.str);
    } while (!done);

  if (*buf.str == POS_PRELIM)
    goto reply_loop2;

  {
    int ret_val;
    char *p;

    /* Parse reply code */
    p = strchr(buf.str, ' ');
    *p = '\0';
    ret_val = atoi(buf.str);
    if (bftpdebug)
      fprintf(stderr, "sub_user(): ABOUT TO UNBATCH: reply code: %d\n",
	      ret_val);
    *p = ' ';			/* Pedantic -- buf.str is redundant at this point */

    if (*buf.str != NEG_TRANS && *buf.str != NEG_COMPLETE)
      {
	char foo[128];

	/* XXX - THIS IS PRINTED BEFORE THE BATCHED INPUT */
	while ((tmp = sos_config_getnext(config, "FTP_CONNECT_REMOTE", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	  {
	    sprintf(foo, "%d-%s", ret_val, tmp);
	    bftp_speak1(sfileout, 0, foo);
	  }
      }
    else
      {
	/* XXX - This is part of the batched input (e.g. appended) */
	while ((tmp = sos_config_getnext(config, "FTP_CONNECT_FAIL", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	  {
	    (void)ftp_batch(&batched_output, strdup(tmp));
	  }
      }

    unbatch(sfileout, ret_val, &batched_output);
  }

  if (*buf.str == NEG_TRANS || *buf.str == NEG_COMPLETE)
    {
      close(s);
      return 0;
    }


  cmd_state = IS_CONN;
  bs_setProcTitle(username, &sourceinfo, &destinfo, BS_SPTP_RELAY);

  return s;

}



/*
 * Port command (during connected mode)
 */
int
sub_port(char *cmd_data)
{
  int a1, a2, a3, a4;		/* `a' for addr [a1.a2.a3.a4] */
  int p1, p2;			/* `p' for port (p1<<8 + p2) */
  char client_quad[SMALLBUF];	/* client addr as dotted quad */
  struct sos_conninfo client;	/* client info  */
  sos_string INBUF;		/* Used for readline stuff. */
  int server_sock;		/* Socket for upstream (server) */
  int client_sock;		/* Socket for downstream (client) */
  struct sockaddr_in me;	/* Sockaddr for local port  */
  struct sockaddr_in clntaddr;	/* Sockaddr for client connect */
  int addr_len;
  int read_retval = -1;		/* Return value from read (system stuff)  */
  int pid;			/* child pid */
  int sourcebytes = 0;		/* Bytes xfered from `source' */
  int destbytes = 0;		/* Bytes xfered from `dest' */


  if (bftpdebug)
    fprintf(stderr, "sub_port: Entering: **%s**\n", cmd_data);

  sscanf(cmd_data, "%d,%d,%d,%d,%d,%d",
	 &a1, &a2, &a3, &a4, &p1, &p2);

  (void)memset(client_quad, (char)0, SMALLBUF);
  (void)sprintf(client_quad, "%d.%d.%d.%d", a1, a2, a3, a4);

  (void)memset((void *)&client, (char)0, sizeof(client));
  if (sos_makeprinthost(-1, 0, client_quad, (struct sos_conninfo *)&client) < 0)
    {
      /*
       * XXX - What is the correct thing to do here?
       */
      if (bftpdebug)
	fprintf(stderr, "sub_port(): sos_makprinthost failed for host %s\n", 
		client_quad);
      return -1;
    }
  client.port = ((p1 << 8) | p2);

  if (bftpdebug)
    fprintf(stderr, "sub_port(): Parsed PORT comand: Client addr: %s ::: Client port: %d\n", client.printhost, client.port);

  /* 
   * Do a sanity check. Make sure the port is pointing at the same host
   * with which we are communicating.
   * NB we are going to reuse `me' for this quick test.
   */

  if ( bftpdebug )
    fprintf(stderr,"sub_port: sanity check: Real Source: %s  -- Purported Sources: %s\n",
	   sourceinfo.printhost, client.printhost);


  if ( memcmp ((struct in_addr *)&(sourceinfo.addr),
	       (struct in_addr *)&(client.addr),
	       sizeof(struct in_addr)) )
      {
	soslog(SOSLOG_WARNING, SOSLOG_TYPE_CONDITION, SOSLOG_NO_PERROR,
	       "Received bogus PORT command (pointing at %s) from %s",
	       client.printhost, sourceinfo.printhost);
	return -1;
      }
	     

  soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	 "Recieved PORT request: client addr: %s  -- client port: %d",
	 client.printhost, client.port);

  /*
   * OK hold on to that request and deal with establishing the upstream 
   * relay.  Uggh. set listener on any addr, but we don't want to get into
   * the business of interface determination.
   */
  if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      /*
       * Server timed out or buffer overrun. 
       * Fake a "service unavailable" to client.
       * Poor choice of return code, but the only even remotely relevent
       * one which is blessed by 959
       */
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }
  addr_len = sizeof(me);

  (void)memset((char *)&me, (char)0, sizeof(me));

  /* 
   * Get the address of the current control  connection 
   */
  if (getsockname(fileno(remfileout), (struct sockaddr *)&me, (int *)&addr_len) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  me.sin_port = htons(0);	/* Ask kernel for a new port. */

  if (bind(server_sock, (struct sockaddr *)&me, addr_len) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  (void)memset((char *)&me, (char)0, sizeof(me));
  if (getsockname(server_sock, (struct sockaddr *)&me, (int *)&addr_len) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  if (listen(server_sock, 10) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  /*
   * Now that listen is set up we can hang onto to the upstream connection
   * attempt until we are ready to deal with it.
   * So now take care of other business
   */
  a1 = ((me.sin_addr.s_addr >> 24) & 0xff);
  a2 = ((me.sin_addr.s_addr >> 16) & 0xff);
  a3 = ((me.sin_addr.s_addr >> 8) & 0xff);
  a4 = (me.sin_addr.s_addr & 0xff);

  p2 = (me.sin_port & 0xff);
  p1 = ((me.sin_port >> 8) & 0xff);

  if (bftpdebug)
    fprintf(stderr, "port_filter(): MY addr: %d.%d.%d.%d -- My port: %d\n",
	    a1, a2, a3, a4, me.sin_port);

  fprintf(remfileout, "port %d,%d,%d,%d,%d,%d%s", a1, a2, a3, a4, p1, p2,
	  readopts.eolseq[0].str);

  /*
   * Grab reply
   */
  if (!sos_allocstr(&INBUF, BS_IOLEN))
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_port(): sos_alloc failed");
      return -1;
    }

  read_retval = sos_readline_timeout(remfilein, remfileout, &INBUF, &readopts, &iotimeout, IoTimeout);
  if (timedout)
    exiterror("timed out", 0);

  if (read_retval <= 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_port(): readline failed\n");
      return -1;
    }
  if (bftpdebug)
    fprintf(stderr, "sub_port(): Buffer from server: **%s**\n",
	    INBUF.str);



  if (*INBUF.str != POS_COMPLETE)
    {
      /*
       * According to RFC959 port may receive 200, 421, or 500's
       * So only continuing on 200 is OK. Let the client know what the
       * server thought was wrong
       */
      close(server_sock);
      if (bftpdebug)
	fprintf(stderr, "sub_port(): Return value from server was not 200\n");
      fprintf(sfileout, INBUF.str);
      return -1;
    }

  /*
   * Dont send back that reply yet!
   * Connect first and send appropriate response
   */
  if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_port(): socket failed\n");
      return -1;
    }

  (void)memset((char *)&clntaddr, (char)0, sizeof(clntaddr));
  clntaddr.sin_family = AF_INET;
  clntaddr.sin_port = htons(client.port);
  (void)memcpy((char *)&clntaddr.sin_addr.s_addr,
	       (char *)&(client.addr),
	       sizeof(client.addr));

  if (connect(client_sock, (struct sockaddr *)&clntaddr, sizeof(clntaddr)) < 0)
    {
      close(server_sock);
      close(client_sock);
      if (bftpdebug)
	fprintf(stderr, "sub_port(): connect failed\n");
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  switch (pid = fork())
    {
    case 0:
      /*
       * Child
       */
      if (bftpdebug)
	fprintf(stderr, "sub_port(): Child heading to relay\n");

      run_data_relay(client_sock, server_sock, 1, &sourcebytes, &destbytes);

      if (bftpdebug)
	fprintf(stderr, "sub_port(): Data relay complete: Bytes from source: %d -- Bytes from remote: %d\n", sourcebytes, destbytes);


      if (can_report)
	{
	  /*
	   * Try to inform parent of bytes transferred
	   */

	  (void)memset(ioinfodata, (char)0, SHORTBUF);
	  (void)sprintf(ioinfodata, "%d %d %s",
			sourcebytes,
			destbytes,
			readopts.eolseq[0].str);
	  if (write(child2parent[WRITER], ioinfodata, strlen(ioinfodata)) < 0)
	    {
	      if (bftpdebug)
		perror("Child reporting iostats");
	      soslog(SOSLOG_WARNING, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
		     "Child [pid:%d] reporting iostats", getpid());
	    }
	  close(child2parent[WRITER]);

	}

      exit(0);
      break;

    case -1:
      /*
       * DOOH! This will really annoy *both* the server and the client.
       * No fork means these closes generate FINS.
       */
      if (bftpdebug)
	fprintf(stderr, "sub_port(): FORK FAILED\n");
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      close(server_sock);
      close(client_sock);
      return -1;
      break;

    default:
      if (bftpdebug)
	fprintf(stderr, "sub_port(): Parent cleaning up\n");
      /*
       * OK fork went fine.
       * Don't wait for the kid. Just get back to work.
       * BTW these closes don't generate FINS
       */
      bftp_speak1(sfileout, 200, SOS_GWD("FTP_PORTOK","PORT command successful"));
      close(server_sock);
      close(client_sock);
      break;
    }

  return 0;
}



/*
 *  Sets up the data relays. 
 *
 * input active: active descriptor
 * 	 passive: passive descriptor
 * 	 sourceactive: 1 if the source address is the active side, else 0
 *		(really means 1 if processing a PORT and 0 if PASV)
 * 
 * output: source_nbytes: number of bytes sent by the source.
 * 	   dest_nbytes: number of bytes sent by the destination.
 */
void
run_data_relay(int active, int passive, int sourceactive,
	       int *source_nbytes, int *dest_nbytes)
{
  struct sockaddr_in passive_peer;	/* Peer on passive port (confusing no?) */
  int addr_len;
  int tmp_passive;


  if (bftpdebug)
    fprintf(stderr, "run_data_relay(): Entering\n");

  addr_len = sizeof(passive_peer);
  (void)memset((void *)&passive_peer, (char)0, addr_len);
  if ((tmp_passive = accept(passive,
			    (struct sockaddr *)&passive_peer,
			    (int *)&addr_len)) < 0)
    {
      /*
       * Damn! Close up and die!
       */
      close(active);
      close(passive);
      return;
    }

  close(passive);
  passive = tmp_passive;

  if (bftpdebug)
    fprintf(stderr, "run_data_relay(): Accepted connection from [%s]\n",
	    inet_ntoa(passive_peer.sin_addr));

  if ( sourceactive )
    {
      relay[0].rinfo.fd = active;
      relay[0].winfo.fd = passive;
      relay[1].rinfo.fd = passive;
      relay[1].winfo.fd = active;
    }
  else
    {
      relay[0].rinfo.fd = passive;
      relay[0].winfo.fd = active;
      relay[1].rinfo.fd = active;
      relay[1].winfo.fd = passive;
    }
    

  if ( sos_bdrelay((struct sos_bdr_fdpair **)&prelay, (int*)&peer_count,
		   (struct sos_bdr_auxinfo *)&auxinfo)< 0 )
    exiterror("bsrelay did not complete", 1);

  if ( bftpdebug )
    fprintf(stderr,"run_data_relay(): bsrelay() returned alright\n");

  *source_nbytes = relay[0].nbytes;
  *dest_nbytes = relay[1].nbytes;
  
  return;

}



/*
 * The null command
 */
int
sub_noop(char *data)
{
  dict_h noop_lines = NULL;
  char *tmp;
  char *line;


  if (bftpdebug)
    fprintf(stderr, "noop: Entering: **%s**\n", data);


  if (tmp = sos_config_getnext(config, "FTP_NOOP", SOS_CONFIG_STATIC, SOS_CONFIG_NULL))
    {
      while ((tmp = sos_config_getnext(config, "FTP_NOOP", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
	{
	  line = strdup(tmp);
	  if (bftpdebug)
	    fprintf(stderr, "noop(): Reading: **%s**\n", tmp);
	  (void)ftp_batch(&noop_lines, (dict_obj) line);
	}

      unbatch(sfileout, 200, &noop_lines);
    }
  else
    bftp_speak1(sfileout, 220, "NOOP command successful");

  return 0;
}



/*
 * The quit command
 */
int
sub_quit(char *data)
{
  if (bftpdebug)
    fprintf(stderr, "quit: Entering\n");

  if (cmd_state == IS_CONN)
    {
      /* Pass the command along */
      bftp_speak1(remfileout, 0, "quit");
      /*
       * Ignore reply (which will come BTW
       */
    }
  exitcleanup(0);
  return 0;
}



/*
 * The help command
 */
int
sub_help(char *data)
{
  dict_h help_lines = NULL;
  char *tmp;
  char *line;

  if (bftpdebug)
    fprintf(stderr, "help: Entering: **%s**\n", data);

  if (tmp = sos_config_getnext(config, "FTP_HELP", SOS_CONFIG_STATIC, SOS_CONFIG_NULL))
    {
      while ((tmp = sos_config_getnext(config, "FTP_HELP", SOS_CONFIG_FORWARD, SOS_CONFIG_AUTORESET)) != NULL)
	{
	  line = strdup(tmp);
	  if (bftpdebug)
	    fprintf(stderr, "help(): Reading: **%s**\n", tmp);
	  (void)ftp_batch(&help_lines, (dict_obj) line);
	}
      unbatch(sfileout, 200, &help_lines);
    }
  else
    bftp_speak1(sfileout, 220, "HELP command successful");

  return 0;
}



/*
 * The pasv command (during connected mode)
 */
int
sub_pasv(char *cmd_data)
{
  unsigned a1, a2, a3, a4;	/* `a' for addr [a1.a2.a3.a4] */
  unsigned p1, p2;		/* `p' for port (p1<<8 + p2) */
  char server_quad[SMALLBUF];	/* server addr as dotted quad */
  struct sos_conninfo server;	/* client info  */
  sos_string INBUF;		/* Used for readline stuff. */
  int server_port;		/* Port to connect back on. */
  int server_sock = -1;		/* Socket for upstream (server) */
  int client_sock;		/* Socket for downstream (client) */
  struct sockaddr_in me;	/* Sockaddr for local port  */
  struct sockaddr_in serveraddr;	/* Sockaddr for client connect */
  int addr_len;
  int read_retval = -1;		/* Return value from read (system stuff)  */
  int pid;			/* child pid */
  int sourcebytes = 0;		/* Bytes xfered from `source' */
  int destbytes = 0;		/* Bytes xfered from `dest' */


  if (bftpdebug)
    fprintf(stderr, "sub_pasv: Entering\n");

  /*
   * First send up pasv command and generate reply
   */
  bftp_speak1(remfileout, 0, "pasv");

  /*
   * Grab reply
   */
  if (!sos_allocstr(&INBUF, BS_IOLEN))
    {
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): sos_alloc failed");
      return -1;
    }

  read_retval = sos_readline_timeout(remfilein, remfileout, &INBUF, &readopts, &iotimeout, IoTimeout);
  if (timedout)
    exiterror("timed out", 0);
  if (read_retval == -1)
    exiterror("readline", 1);

  sos_rip(INBUF.str);

  if (read_retval <= 0)
    {
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): readline failed\n");
      return -1;
    }
  if (bftpdebug)
    fprintf(stderr, "sub_pasv(): Buffer from server: **%s**\n",
	    INBUF.str);

  if (*INBUF.str != POS_COMPLETE)
    {
      /*
       * According to RFC959 port may receive 200, 421, or 500's
       * So only continuing on 200 is OK. Let the client know what the
       * server thought was wrong
       */
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Return value from server was not 200\n");
      fprintf(sfileout, INBUF.str);
      return -1;
    }

  (void)sscanf(INBUF.str, "%*d %*[^0-9]%d, %u, %u, %u, %u, %u",
	       &a1, &a2, &a3, &a4, &p1, &p2);

  server_port = ((p1 << 8) & 0xff | (p2 & 0xff));

  (void)sprintf(server_quad, "%d.%d.%d.%d", a1, a2, a3, a4);

  (void)memset((void *)&server, (char)0, sizeof(server));
  if (sos_makeprinthost(-1, server_port, server_quad, (struct sos_conninfo *)&server) < 0)
    {
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  /* 
   * Do a sanity check. Make sure the port is pointing at the same host
   * with which we are communicating.
   * NB we are going to reuse `me' for this quick test.
   */

  if ( bftpdebug )
    fprintf(stderr,"sub_pasv: sanity check: Real Dest: %s  -- Purported Dest: %s\n",
	   destinfo.printhost, server.printhost);


  if ( memcmp ((struct in_addr *)&(destinfo.addr),
	       (struct in_addr *)&(server.addr),
	       sizeof(struct in_addr)) )
      {
	soslog(SOSLOG_WARNING, SOSLOG_TYPE_CONDITION, SOSLOG_NO_PERROR,
	       "Received bogus PASV reply (pointing at %s) from %s",
	       server.printhost, destinfo.printhost);
	return -1;
      }



  if (bftpdebug)
    fprintf(stderr, "sub_pasv(): Connecting to server addr: %s ::: Server port: %d\n", server.printhost, server.port);


  addr_len = sizeof(struct sockaddr_in);

  (void)memset((void *)&serveraddr, (char)0, addr_len);
  serveraddr.sin_family = AF_INET;
  serveraddr.sin_port = htons((p1 << 8) + p2);
  (void)memcpy((char *)&serveraddr.sin_addr.s_addr,
	       (char *)&(server.addr),
	       sizeof(server.addr));

  if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      return -1;
    }

  if (connect(server_sock, (struct sockaddr *)&serveraddr, addr_len))
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Connect failed\n");
      return -1;
    }


  /*
   * Get the address of the interface over which we are communicating
   * with the client.
   */
  if (getsockname(fileno(sfileout), (struct sockaddr *)&me, (int *)&addr_len) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): getsockname failed for control socket\n");
      return -1;
    }

  a1 = ((me.sin_addr.s_addr >> 24) & 0xff);
  a2 = ((me.sin_addr.s_addr >> 16) & 0xff);
  a3 = ((me.sin_addr.s_addr >> 8) & 0xff);
  a4 = (me.sin_addr.s_addr & 0xff);


  (void)memset((void *)&me, (char)0, addr_len);
  me.sin_family = AF_INET;
  me.sin_port = 0;
  me.sin_addr.s_addr = INADDR_ANY;


  if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Passive socket failed\n");
      return -1;
    }


  if (bind(client_sock, (struct sockaddr *)&me, addr_len) < 0)
    {
      close(server_sock);
      close(client_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Passive bind failed\n");
      return -1;
    }


  if (listen(client_sock, 10) < 0)
    {
      close(client_sock);
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): listen() failed (is this is joke)?\n");
      return -1;
    }


  if (getsockname(client_sock, (struct sockaddr *)&me, (int *)&addr_len) < 0)
    {
      close(client_sock);
      close(server_sock);
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): getsockname() failed for passive socket\n");
      return -1;
    }

  /*
   * Now let the client know
   */
  p2 = (me.sin_port & 0xff);
  p1 = ((me.sin_port >> 8) & 0xff);

  if (bftpdebug)
    fprintf(stderr, "sub_pasv(): MY addr: %d.%d.%d.%d -- My port: %d\n",
	    a1, a2, a3, a4, me.sin_port);


  switch (pid = fork())
    {
    case 0:			/* Child */

      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Child heading to relay\n");

      run_data_relay(server_sock, client_sock, 0, &sourcebytes,&destbytes);
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Data relay complete: Bytes from source: %d -- Bytes from remote: %d\n",
		sourcebytes,destbytes);


      if (can_report)
	{
	  /*
	   * Try to report io stats to parent
	   */

	  (void)memset(ioinfodata, (char)0, SHORTBUF);
	  (void)sprintf(ioinfodata, "%d %d %s",
			sourcebytes,
			destbytes,
			readopts.eolseq[0].str);

	  if (write(child2parent[WRITER], ioinfodata, strlen(ioinfodata)) < 0)
	    {
	      if (bftpdebug)
		perror("Child reporting iostats");
	      soslog(SOSLOG_WARNING, SOSLOG_TYPE_CONDITION, SOSLOG_PERROR,
		     "Child [pid:%d] reporting iostats", getpid());
	    }

	  close(child2parent[WRITER]);
	}

      exit(0);
      break;

    case -1:			/* Error */
      /*
       * DOOH! This will really annoy *both* the server and the client.
       * No fork means these closes generate FINS.
       */
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): FORK FAILED\n");
      bftp_speak1(sfileout, 421, SOS_GWD("FTP_SERVFAIL","Service unavailable"));
      close(server_sock);
      close(client_sock);
      return -1;
      break;

    default:			/* Parent */
      if (bftpdebug)
	fprintf(stderr, "sub_pasv(): Parent cleaning up\n");
      /*
       * OK fork went fine.
       * Don't wait for the kid. Just get back to work.
       * BTW these closes don't generate FINS
       */
      fprintf(sfileout, "227 Entering passive mode (%d,%d,%d,%d,%d,%d)%s",
	      a1, a2, a3, a4, p1, p2, readopts.eolseq[0].str);
      close(server_sock);
      close(client_sock);
      break;

    }

  return 0;
}



/*
 * Read statistics about child's forwarded data
 */
void 
read_io_stats()
{
  int sourcebytes;
  int destbytes;

  if (bftpdebug)
    fprintf(stderr, "read_io_stats(): Entering\n");


  (void)memset(ioinfodata, (char)0, SHORTBUF);
  if (read(child2parent[READER], ioinfodata, SHORTBUF) < 0)
    {
      if (bftpdebug)
	perror("read_io_stats(): reading out io stats failed");
      return;

    }
  (void)sscanf(ioinfodata, "%d %d", &sourcebytes, &destbytes);

  if (bftpdebug)
    fprintf(stderr, "read_io_stats(): Source: %d  -- Destination: %d\n",
	    sourcebytes, destbytes);

  ioinfo.bytes_from_source += sourcebytes;
  ioinfo.bytes_from_dest += destbytes;

  return;
}



/*
 * batched_lines provides the glue will maintain the lock-step communication
 * between the proxy and the user.  Since dialog between the proxy and the
 * Brimstone (BS_) routines may not result in a well ordered communication,
 * some output from the brimstone routines may have to batched for a bit 
 * until a later reply/return from a BS_ routine determines both what the 
 * correct return code should be and when the output should occur.
 * 
 */
static void
ftp_batch(dict_h * lbuf, char *line)
{
  if (!*lbuf)
    {
      *lbuf = dll_create((int *)NULL, (int *)NULL, DICT_UNORDERED, (int *)NULL);
    }

  (void)dll_insert(*lbuf, (dict_obj) line);
  return;
}



/*
 * This is just a wrapper around bftp_speak, which destroys and recreates
 * the clc doubly linked list which batched the lines.
 */
void
unbatch(FILE * stream, int ret_code, dict_h * lines)
{
  if (!*lines)
    return;

  bftp_speak(stream, ret_code, *lines);

  /*
   * bftp_speak destoys the dict (old design decision)
   */
  *lines = NULL;
  return;
}
