#include "kernel.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "oslib/os.h"
#include "sockets.h"

#include "syslogif.h"
#include "def.h"
#include "defstruc.h"
#include "connstrc.h"
#include "eventstrc.h"

#include "event.h"
#include "master.h"
#include "telnet.h"
#include "sock.h"
#include "buildmsg.h"
#include "ssh.h"
#include "sshfunc.h"
#include "sshif.h"
#include "stringop.h"
#include "winsock.h"
#include "tcpwrapif.h"

#define LOCALHOST_ONLY_HACK

int maxConnections = MAX_CONNECTIONS;

static ProxiedConnection *proxy=NULL;

OSERROR error_outOfMemory; /* fill these in */
OSERROR error_socket;

const static TelnetOption telnetOption_naws =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_NAWS,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_tspeed =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_TSPEED,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_ttype =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_TTYPE,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_oenv =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_OLD_ENVIRON,
    TELSTATE_INACTIVE};
const static TelnetOption telnetOption_nenv =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_NEW_ENVIRON,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_echo =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_ECHO,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_we_sga =
  {TELNET_WILL, TELNET_WONT, TELNET_DO, TELNET_DONT, TELOPT_SGA,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_they_sga =
  {TELNET_DO, TELNET_DONT, TELNET_WILL, TELNET_WONT, TELOPT_SGA,
    TELSTATE_REQUESTED};
const static TelnetOption telnetOption_linemode =
  {TELNET_DONT, TELNET_DO, TELNET_WONT, TELNET_WILL, TELOPT_LINEMODE,
    TELSTATE_REQUESTED};
/*const static TelnetOption telnetOption_they_sga =
  {TELNET_DO, TELNET_DONT, TELNET_WILL, TELNET_WONT, TELOPT_SGA,
    TELSTATE_REQUESTED};*/

/* these must match the positions in the array below */
#define TELNETOPTION_OENV 3
#define TELNETOPTION_NENV 4

const static TelnetOption *masterTelnetOptions[TELNET_OPTION_COUNT+1] = {
    &telnetOption_naws,
    &telnetOption_tspeed,
    &telnetOption_ttype,
    &telnetOption_oenv,
    &telnetOption_nenv,
    &telnetOption_echo,
    &telnetOption_we_sga,
    &telnetOption_they_sga,
    &telnetOption_linemode,
    NULL
};

static char *telnet_telopt(int opt) {
#define i(x) if (opt == TELOPT_ ## x) return #x;
    i(BINARY); i(ECHO); i(RCP); i(SGA); i(NAMS); i(STATUS); i(TM); i(RCTE);
    i(NAOL); i(NAOP); i(NAOCRD); i(NAOHTS); i(NAOHTD); i(NAOFFD); i(NAOVTS);
    i(NAOVTD); i(NAOLFD); i(XASCII); i(LOGOUT); i(BM); i(DET); i(SUPDUP);
    i(SUPDUPOUTPUT); i(SNDLOC); i(TTYPE); i(EOR); i(TUID); i(OUTMRK);
    i(TTYLOC); i(X3PAD); i(NAWS); i(TSPEED); i(LFLOW); i(LINEMODE);
    i(XDISPLOC); i(OLD_ENVIRON); i(AUTHENTICATION); i(ENCRYPT);
    i(NEW_ENVIRON); i(EXOPL);
#undef i
    return "<unknown>";
}

static void telnet_log_option (int s,char *sender, int cmd, int option) {
    unsigned char buf[50];
    xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION_LOW, "%d: %s:\t%s %s", s, sender,
	    (cmd == TELNET_WILL ? "WILL" : cmd == TELNET_WONT ? "WONT" :
	     cmd == TELNET_DO ? "DO" : cmd == TELNET_DONT ? "DONT" :
	     cmd == TELNET_SB ? "SB" : cmd == TELNET_SE ? "SE " : "<??>"),
	    telnet_telopt(option));
    /*lognegot(buf);*/
}

static void telnet_send_opt (int s, int cmd, int option) {
    unsigned char b[3];

    b[0] = TELNET_IAC; b[1] = cmd; b[2] = option;
    sock_send_block (proxy[s].telnetSocket, b, 3);
    telnet_log_option(s, "client", cmd, option);
}

static void telnet_deactivate_option (int s, TelnetOption *o) {
    if (o->state == TELSTATE_REQUESTED || o->state == TELSTATE_ACTIVE)
	telnet_send_opt (s, o->nsend, o->option);
    o->state = TELSTATE_REALLY_INACTIVE;
}

static void telnet_activate_option (int s, TelnetOption *o) {
    if (o->send == TELNET_WILL && o->option == TELOPT_NAWS)
	/*telnet_size()*/;
    if (o->send == TELNET_WILL &&
	(o->option == TELOPT_NEW_ENVIRON ||
	 o->option == TELOPT_OLD_ENVIRON)) {
	/*
	 * We may only have one kind of ENVIRON going at a time.
	 * This is a hack, but who cares.
	 */
	telnet_deactivate_option (s, o->option==TELOPT_NEW_ENVIRON ? &proxy[s].telnetOptions[TELNETOPTION_OENV] : &proxy[s].telnetOptions[TELNETOPTION_NENV]);
    }
}

static void telnet_refused_option (int s, TelnetOption *o) {
    if (o->send == TELNET_WILL && o->option == TELOPT_NEW_ENVIRON &&
	proxy[s].telnetOptions[TELNETOPTION_OENV].state == TELSTATE_INACTIVE) {
	telnet_send_opt (s, TELNET_WILL, TELOPT_OLD_ENVIRON);
	proxy[s].telnetOptions[TELNETOPTION_OENV].state = TELSTATE_REQUESTED;
    }
}

static void telnet_send_terminal_type_request(int s)
{
  unsigned char msg[50];

  msg[0] = TELNET_IAC;
  msg[1] = TELNET_SB;
  msg[2] = TELNET_TERMINALTYPE;
  msg[3] = 1; /* SEND */
  msg[4] = TELNET_IAC;
  msg[5] = TELNET_SE;
  sock_send_block (proxy[s].telnetSocket, msg, 6);

  xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION_LOW, "%d: sent SEND_TERMINAL_TYPE",s);
}

static void telnet_read_terminal_type(int s, unsigned char *inputBuffer, int *inputBufferPos, int inputBufferSize)
{
  int i=0;

  if (inputBuffer[*inputBufferPos+3] == 0) /* IS */
  {
    while ((inputBuffer[*inputBufferPos+3+i+1] != TELNET_IAC && i<32) && (*inputBufferPos+3+i+1)<inputBufferSize)
    {
      proxy[s].telnetSession.terminalType[i] = tolower(inputBuffer[*inputBufferPos+3+i+1]);
      /* some apps don't recognise upper case terminal names */
      i++;
    }
    proxy[s].telnetSession.terminalType[i] = '\0';
    if (*inputBufferPos+i+4>=inputBufferSize)
    {
      /* flag that we're in the middle of terminal type reading -
       * next time, read until we get an IAC, and add it to the string */
      proxy[s].telnetState = TELSTATEMACHINE_READTERMINALTYPE;
      *inputBufferPos = inputBufferSize;
    }
    else
    {
      *inputBufferPos+=i+6; /* lose trailing SE as well */
      proxy[s].telnetState = TELSTATEMACHINE_GOTTERMINALTYPE;

      /* Messy... have to do copy in here, as this may be called when we're
       * at the user/password prompts - actually started the SSH session
       * but haven't sent the terminal type yet.  If we get this past
       * the shell being opened, that's just tough - AIUI the shell will
       * only read the TERM variable on starting, so if we get it too late
       * then there's nowt we can do
       */
      strncpy(session[s].cfg.termtype,proxy[s].telnetSession.terminalType,32);
    }

    xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION_LOW, "%d: terminal type %s\n",s,proxy[s].telnetSession.terminalType);
  }
  else
  {
    *inputBufferPos+=3;
    proxy[s].telnetState = TELSTATEMACHINE_READTERMINALTYPE;
  }
}

static void telnet_terminaltype_readmore(int s, unsigned char *inputBuffer,
 int *inputBufferPos, int inputBufferSize)
{
  int i=0;
  int j=0;

  j=strlen(proxy[s].telnetSession.terminalType);

  xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: telnet_terminaltype_readmore: got %d bytes of term '%s' so far\n",s,j,proxy[s].telnetSession.terminalType);

  while ((inputBuffer[*inputBufferPos+i] != TELNET_IAC) && ((*inputBufferPos)+i<inputBufferSize))
  {
    if ((inputBuffer[*inputBufferPos+i])!='\0') /* IS */
    {
      proxy[s].telnetSession.terminalType[j] = tolower(inputBuffer[*inputBufferPos+i]);
      j++;
    }
    i++;
  }

  proxy[s].telnetSession.terminalType[j]='\0';
  if ((*inputBufferPos)+i>=inputBufferSize)
  {
    proxy[s].telnetState = TELSTATEMACHINE_READTERMINALTYPE;
    *inputBufferPos += i;
  }
  else
  {
    proxy[s].telnetState = TELSTATEMACHINE_GOTTERMINALTYPE;
    *inputBufferPos += i+2;
  }
  xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: telnet_terminaltype_readmore: finished with termtype %d bytes = '%s'\n",s,j,proxy[s].telnetSession.terminalType);

}


static void telnet_process_option (int s, int cmd, int option,
  unsigned char *inputBuffer, int *inputBufferPos, int inputBufferSize) {
    TelnetOption *o;
    int i=0;
    telnet_log_option (s, "server", cmd, option);
#if 0
    /*for (o = opts; ; o++) {*/
    for (i=0; i<TELNET_OPTION_COUNT; i++)
    {
        o = &(proxy[s].telnetOptions[i]);
	if (o->option == option && o->ack == cmd) {
	    switch (o->state) {
	      case TELSTATE_REQUESTED:
		o->state = TELSTATE_ACTIVE;
		telnet_activate_option (s, o);
		break;
	      case TELSTATE_ACTIVE:
		break;
	      case TELSTATE_INACTIVE:
		o->state = TELSTATE_ACTIVE;
		telnet_send_opt (s, o->send, option);
		telnet_activate_option (s, o);
		break;
	      case TELSTATE_REALLY_INACTIVE:
		telnet_send_opt (s, o->nsend, option);
		break;
	    }
	    return;
	} else if (o->option == option && o->nak == cmd) {
	    switch (o->state) {
	      case TELSTATE_REQUESTED:
		o->state = TELSTATE_INACTIVE;
		telnet_refused_option (s, o);
		break;
	      case TELSTATE_ACTIVE:
		o->state = TELSTATE_INACTIVE;
		telnet_send_opt (s, o->nsend, option);
		break;
	      case TELSTATE_INACTIVE:
	      case TELSTATE_REALLY_INACTIVE:
		break;
	    }
	    return;
	}
    }
    /*
     * If we reach here, the option was one we weren't prepared to
     * cope with. So send a negative ack.
     */
    telnet_send_opt (s, (cmd == TELNET_WILL ? TELNET_DONT : TELNET_WONT), option);
#endif
    switch (option)
    {
      case TELNET_TERMINALTYPE:
        switch (cmd)
        {
          case TELNET_WILL:
            telnet_send_terminal_type_request(s);
            *inputBufferPos+=3;
            break;
          case TELNET_SB:
            telnet_read_terminal_type(s, inputBuffer,inputBufferPos, inputBufferSize);
            break;
          default:
            *inputBufferPos+=3;
            break;
        }
        break;
      default:
        *inputBufferPos+=3; /* any further accesses to this may cause
                             * buffer overrun - make sure they don't happen */
        break;
    }
}


static void telnet_line_process(unsigned char *inputLine, unsigned char *buffer, int bufferLength)
{
  int i=0,inputLength=0,bufferPos=0;
  unsigned char c;

  i=0;
  bufferPos=0;

  inputLength = strlen((char *) inputLine);

  while (i<inputLength)
  {
    c=inputLine[i];
    switch (c)
    {
      case '\b':
      case '\x7f':
        if (bufferPos>0)
          bufferPos--;
        break;
      case '\a': /* ignore BEL */
        break;
      case '\f':
      case '\n':
      case '\0':
      case '\r':
      case '\v': /* end of line */
        buffer[bufferPos]='\0';
        return;
      case '\t': /* tab */
        if (bufferPos+1<bufferLength)
        {
          buffer[bufferPos]=' ';
          bufferPos++;
        }
        else
        {
          /* about to overflow buffer, so ignore char and finish */
          buffer[bufferPos]='\0';
          return;
        }
        break;
      case TELNET_IAC:
        i+=3;
      default: /* any other key that's character generating */
        if (c>'\x1f')
        {
          if (bufferPos+1<bufferLength)
          {
            buffer[bufferPos]=c;
            bufferPos++;
          }
          else
          {
            /* about to overflow buffer, so ignore char and finish */
            buffer[bufferPos]='\0';
            return;
          }
        }
        break;
    }
    i++;
  }

  buffer[bufferPos]='\0';
  return;
}

static OSERROR *telnet_parse_input(int s, unsigned char *inputBuffer,
               int inputBufferSize, unsigned char *outputBuffer,
               int outputBufferSize, int *outputBufferPtr, int *swallowUp)
{
  unsigned char *outPacket;
  int outPacketSize=0,inputBufferPos=0,outputBufferPos=0,i=0;
  outPacketSize = proxy[s].telnetSession.terminalWidth + 10;

  /* bodge - we only need to bother with telnet options once we're in
   * SSH mode if we're still waiting for the terminal type
  if (proxy[s].telnetState == TELSTATE_GOTTERMINALTYPE)
    return NO_OSERROR;*/

  outPacket = calloc(outPacketSize, sizeof(unsigned char));

  if (outPacket == NULL)
    return &error_outOfMemory;

  /* ignoring a previous telnet command sent may mean some bytes in this
   * block are to be skipped - in this case we skip as many as we can,
   * and return if there are still more to be skipped
   */
  if (*swallowUp > 0)
  {
    if (*swallowUp>inputBufferSize)
    {
      *swallowUp-=inputBufferSize;
      inputBufferSize = 0;
    }
    else
    {
      inputBufferSize -= *swallowUp;
      *swallowUp = 0;
    }
  }

  outputBufferPos=strlen((char *) outputBuffer);
  xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: buffer '%s', len %d, state=%d\n",s,inputBuffer,inputBufferSize,proxy[s].telnetState);

  if (proxy[s].telnetState == TELSTATEMACHINE_READTERMINALTYPE)
  {
    telnet_terminaltype_readmore(s,inputBuffer,&inputBufferPos,inputBufferSize);
  }
  if (inputBufferPos>inputBufferSize)
  {
    free(outPacket);
    return NO_OSERROR;
  }
  while (inputBufferPos<inputBufferSize)
  {
    if (proxy[s].proxyStatus == PROXY_INITSSHCONNECTION)
    {
      switch (inputBuffer[inputBufferPos])
      {
        case TELNET_IAC:
          xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: ssh connection TELNET_IAC, %d, %d, buf len %d\n",s,inputBuffer[inputBufferPos+1],inputBuffer[inputBufferPos+2],inputBufferSize);
          telnet_process_option(s, inputBuffer[inputBufferPos+1], inputBuffer[inputBufferPos+2],inputBuffer,&inputBufferPos,inputBufferSize);
          if (inputBufferPos>inputBufferSize)
            inputBufferPos = inputBufferSize;
          break;
        default:
          if (outputBufferPos<outputBufferSize)
          {
            /* if we've gone off the end of a line, start on the next */
/*            outPacket[0] = toupper(inputBuffer[inputBufferPos]);
            outPacket[1] = '\0';
            sock_send_string(proxy[s].telnetSocket,outPacket);*/
            outputBuffer[outputBufferPos] = inputBuffer[inputBufferPos];
            outputBufferPos++;
  /*          proxy[s].telnetSession.cursorX++;
            if (proxy[s].telnetSession.cursorX>=proxy[s].telnetSession.terminalWidth)
            {
              outPacket[0] = '\n';
              outPacket[1] = '\r';
              sock_send_block(proxy[s].telnetSocket,outPacket,2);
              proxy[s].telnetSession.cursorX = 0;
            }*/
          }
          inputBufferPos++;
          break;
      }
    }
    else
    {
      switch (inputBuffer[inputBufferPos])
      {
        case '\b':
        case '\x7f':
          if (outputBufferPos>0 && outputBufferPos<outputBufferSize)
          {
  #if 0
            outPacket[1] = TELNET_IAC;
            outPacket[2] = TELNET_EC;
            sock_send_block(proxy[s].telnetSocket,outPacket,2);
  #endif
            outputBufferPos--;
            if (proxy[s].telnetSession.cursorX>0)
            {
              proxy[s].telnetSession.cursorX--;
              outPacket[0] = '\x08';
              outPacket[1] = ' ';
              outPacket[2] = '\x08';
              sock_send_block(proxy[s].telnetSocket,outPacket,3);
            }
            else
            {
              /* go to the end of the previous line */
              proxy[s].telnetSession.cursorX =
                proxy[s].telnetSession.terminalWidth - 1;
              outPacket[0] = '\v'; /* up */
              for (i=0; i<proxy[s].telnetSession.terminalWidth-1; i++)
                outPacket[i+1] = '\t';
              outPacket[i+1] = ' ';
              outPacket[i+2] = '\v'; /* \x08 */
              sock_send_block(proxy[s].telnetSocket,outPacket,i+3);
            }
          }
          inputBufferPos++;
          break;
        case '\a': /* ignore BEL */
          inputBufferPos++;
          break;
        case '\f':
        case '\n':
        case '\0':
        case '\r':
        case '\v': /* end of line - mark with nl */
          outputBuffer[outputBufferPos]='\n';
          outputBuffer[outputBufferPos+1]='\0';
          outPacket[0]='\n';
          outPacket[1]='\r';
          sock_send_block(proxy[s].telnetSocket,outPacket,2);
          free(outPacket);
          return NO_OSERROR;
        case TELNET_IAC:
          xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: TELNET_IAC, %d, %d, buf len %d\n",s,inputBuffer[inputBufferPos+1],inputBuffer[inputBufferPos+2],inputBufferSize);
          telnet_process_option(s, inputBuffer[inputBufferPos+1], inputBuffer[inputBufferPos+2],inputBuffer,&inputBufferPos,inputBufferSize);
          if (inputBufferPos>inputBufferSize)
            inputBufferPos = inputBufferSize;
  #if 0
          if (inputBufferPos+3<=inputBufferSize)
            inputBufferPos+=3;
          else
          {
            /* lose the next few characters from the stream */
            *swallowUp=3-(inputBufferSize-inputBufferPos);
            inputBufferPos=inputBufferSize;
          }
  #endif
          break;
          /* should handle code overlapping end of buffer */
        case '\t': /* tab */
          inputBuffer[inputBufferPos] = ' ';
        default:
          if (outputBufferPos<outputBufferSize)
          {
            /* if we've gone off the end of a line, start on the next */
            outPacket[0] = inputBuffer[inputBufferPos];
            outPacket[1] = '\0';
            sock_send_string(proxy[s].telnetSocket,outPacket);
            outputBuffer[outputBufferPos] = inputBuffer[inputBufferPos];
            outputBufferPos++;
            proxy[s].telnetSession.cursorX++;
            if (proxy[s].telnetSession.cursorX>=proxy[s].telnetSession.terminalWidth)
            {
              outPacket[0] = '\n';
              outPacket[1] = '\r';
              sock_send_block(proxy[s].telnetSocket,outPacket,2);
              proxy[s].telnetSession.cursorX = 0;
            }
          }
          inputBufferPos++;
          break;
      }
    }

  }
  /* terminate the string we've got so far */
  outputBuffer[outputBufferPos] = '\0';
  *outputBufferPtr = outputBufferPos;
  free(outPacket);
  return NO_OSERROR;
}

static OSERROR *telnet_parse_host(int s, unsigned char *hostString, unsigned char *hostName, int hostLength, int *port)
{
  unsigned char lineBuffer[STRING_BUFFER_SIZE];
  struct hostent *host;
  unsigned char *portPtr;
  char *dummyEnd;

  telnet_line_process(hostString,lineBuffer,STRING_BUFFER_SIZE);
  /*strncpy((char *) lineBuffer,(char *) hostString,STRING_BUFFER_SIZE);*/
  sock_send_string(proxy[s].telnetSocket,lineBuffer);

/*  host = gethostbyname(lineBuffer);

  if (host == NULL)
    return &error_socket;

  sock_send_string(proxy[s].telnetSocket,host->h_name);
  sock_send_string(proxy[s].telnetSocket,inet_ntoa(*((struct in_addr *)host->h_addr)));*/

  strncpy((char *) hostName,(char *) lineBuffer,hostLength);
  xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: copied %s to %s, len %d\n",s,lineBuffer,hostName,hostLength);

  /* if the hostname is of the form host:port, separate them */
  portPtr=(unsigned char *) strchr((char *) hostName,':');
  if (portPtr!=NULL)
  {
    portPtr[0]='\0';
    /* Now we are host, \0, port \0 */

    *port = (int) strtol((char *) (portPtr+1),&dummyEnd,10 /* decimal */);
    if (*port == 0)
      *port = 22;
  }
  else
  {
    *port = 22;
  }

  return NO_OSERROR;
}

static void telnet_send_host_prompt(int s)
{

  /*sock_flush_incoming(proxy[s].telnetSocket);*/
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\r\nConnect to: ");
/*sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfb\x01");
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfd\x03");
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfe\x22");
*/
  proxy[s].telnetSession.cursorX = 12;
}

static int telnet_find_proxy_from_socket(SOCKET socket)
{
  int i=0;

  for (i=0; i<maxConnections; i++)
  {
    if (proxy[i].telnetSocket == socket)
      return i;
  }

  return -1;
}


static int telnet_handle_host_prompt(int s)
{
  /* swallowUp - count of bytes to skip in the next packet - to allow
   * skipping of telnet codes split across packets
   */
  /*static int swallowUp = 0;*/
  int receivedSize=0;
  OSERROR *result;
  unsigned char stringBuffer[STRING_BUFFER_SIZE],*realHost;
  unsigned char *badHost=(unsigned char *) "Invalid host.\r\n";
  unsigned char *connecting=(unsigned char *) "\r\nConnecting - resolving...\r\n";
  unsigned char *terminator=NULL;
  unsigned char *sshFatalError;
  struct hostent *remoteHostEnt;
  int port=0;

  result = sock_get_string(proxy[s].telnetSocket,stringBuffer,STRING_BUFFER_SIZE,&receivedSize);

  if (result!=NO_OSERROR)
  {
    /* something went wrong with the get - what do we do? */
    /* FTM, just ignore */
    xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION, "%d: telnet_handle_host_prompt: sock_get_string failed - error %X,%s\n",s,result->errnum,result->errmess);
    return EVENT_CLAIM;
  }

  if (receivedSize == 0)
  {
    /* do we send another prompt, or just keep waiting? */
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_HIGH,"%d: telnet_handle_host_prompt - no data on received message\n",s);
    return EVENT_CLAIM;
  }

  telnet_parse_input(s,stringBuffer,receivedSize,proxy[s].lineBuffer,STRING_BUFFER_SIZE,&receivedSize,&proxy[s].swallowUp);

  /* Need to implement swallowUp - if set, lose that many bytes from the
   * next block */

  /*strncat((char *) proxy[s].lineBuffer,(char *) stringBuffer,receivedSize);*/

  if ((terminator=(unsigned char *) strchr((char *) proxy[s].lineBuffer,'\n'))!=NULL)
  {
    *terminator='\0'; /* replace newline with zero */

    if (telnet_parse_host(s,proxy[s].lineBuffer,stringBuffer,STRING_BUFFER_SIZE,&port) != NO_OSERROR)
    {
      xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION, "%d: telnet_handle_host_prompt: unknown host %s\n",s,proxy[s].lineBuffer);

      proxy[s].lineBuffer[0] = '\0';
      sock_send_string(proxy[s].telnetSocket,badHost);
      telnet_send_host_prompt(s);
      proxy[s].proxyStatus = PROXY_HOSTPROMPT;
      return EVENT_CLAIM;
    }

    xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION, "%d: Connecting to host %s port %d\n",s,stringBuffer,port);

    /* look up host, and get a full hostname (needed for known host lookup) */
    /* Oh no we don't, this is a DNS insecurity
    remoteHostEnt = winsock_multitask_gethostbyname((char *) stringBuffer);
    if (remoteHostEnt!=NULL)
      strcpy((char *) stringBuffer,remoteHostEnt->h_name);*/

    /* if resolve failed, just carry on (dotted quad?) */

    proxy[s].lineBuffer[0] = '\0';
    proxy[s].proxyStatus = PROXY_INITSSHCONNECTION;
    sock_send_string(proxy[s].telnetSocket,connecting);
    sshinterface_sshsession_init(&(session[s]));
    session[s].telnetSocket = proxy[s].telnetSocket; /* should message pass,
      not write direct to socket */
    xsyslogf_irq(SYSLOG_FILE, LOG_DEBUG_LOW, "%d: session[%d].telnetSocket = %d\n",s,session[s].telnetSocket);

    strncpy(session[s].cfg.termtype,proxy[s].telnetSession.terminalType,32);

    sshFatalError=(unsigned char *) ssh_init(s,0,(char *) stringBuffer,port,(char **) &realHost);
    if (sshFatalError!=NULL)
    {
      xsyslogf_irq(SYSLOG_FILE, LOG_ERROR, "%d: ssh: %s",s,sshFatalError);
      /* kill this socket - ssh died horribly */
      sock_send_string(proxy[s].telnetSocket,(unsigned char *) "Fatal error from SSH: ");
      sock_send_string(proxy[s].telnetSocket,sshFatalError);
      sock_flush_incoming(proxy[s].telnetSocket);
      telnet_send_host_prompt(s);
      proxy[s].proxyStatus = PROXY_HOSTPROMPT;
      /*telnet_close_connection(s);*/
      return EVENT_CLAIM;
    }
  }

  return EVENT_CLAIM;

}

static int telnet_send_to_ssh(int s)
{
  char *error;
  unsigned char *buffer; /*[256];*/
  unsigned char *processedBuffer, *parsedBuffer;/*[256];*/
  unsigned char buffer2[256],processedBuffer2[256],parsedBuffer2[256];
  OSERROR *result;
  int receivedSize=0,oobSize=0,notDone=0,outputLength=0,inputLength=0;
  int bufferPos=0;

  /*SYSLOG_ENTRY("telnet_send_to_ssh");*/
  /* Obscure Norcroft bug fix */
  buffer = buffer2;
  processedBuffer = processedBuffer2;
  parsedBuffer = parsedBuffer2;

  result = sock_get_string(proxy[s].telnetSocket,buffer,sizeof(buffer),&receivedSize);

  if (result!=NO_OSERROR)
  {
    /* something went wrong with the get - what do we do? */
    /* FTM, just ignore */
    xsyslogf_irq(SYSLOG_FILE, LOG_CONNECTION, "%d: telnet_send_to_ssh: sock_get_string failed - error %X,%s\n",s,result->errnum,result->errmess);
    SYSLOG_EXIT("telnet_send_to_ssh");
    return EVENT_CLAIM;
  }

  result = sock_get_stringOOB(proxy[s].telnetSocket,buffer+receivedSize,sizeof(buffer)-receivedSize,&oobSize);

  if (oobSize > 0)
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: telnet_send_to_ssh: got %d bytes of OOB\n",oobSize);

  receivedSize+=oobSize;

  if (receivedSize == 0)
  {
    SYSLOG_EXIT("telnet_send_to_ssh");
    return EVENT_CLAIM;
  }


  inputLength = receivedSize;
  parsedBuffer[0] = '\0';

  telnet_parse_input(s, buffer,
               receivedSize, parsedBuffer,
               sizeof(parsedBuffer2), &inputLength, &proxy[s].swallowUp);

  do
  {
    /* for as many times as we need to process the whole string,
     * do the CR-LF to CR conversion, and send it out by SSH */
    outputLength = sizeof(processedBuffer2);
    xsyslogf_irq(SYSLOG_FILE, LOG_DEBUG_VERYLOW,"%d: telnet_send_to_ssh: buffer = %p, inputLength = %d, processedBuffer = %p, outputLength = %d\n",s,buffer,inputLength,processedBuffer,outputLength);
    stringop_crlfToCr(&parsedBuffer,&inputLength,processedBuffer,&outputLength,&notDone);
    xsyslogf_irq(SYSLOG_FILE, LOG_DEBUG_VERYLOW,"%d: telnet_send_to_ssh: after crlftocr buffer = %p, inputLength = %d, processedBuffer = %p, outputLength = %d\n",s,buffer,inputLength,processedBuffer,outputLength);

    error = ssh_send(s,(char *) processedBuffer,outputLength);
    if (error!=NULL)
    {
      xsyslogf_irq(SYSLOG_FILE, LOG_ERROR, "%d: ssh_send: %s",s,error);
      SYSLOG_EXIT("telnet_send_to_ssh");
      return EVENT_CLAIM;
    }

  } while (notDone!=0);

  /*SYSLOG_EXIT("telnet_send_to_ssh");*/
  return EVENT_CLAIM;
}

static int telnet_handler(SOCKET socket, socket_event_type reason)
{
  int s=0;
  SYSLOG_ENTRY("telnet_handler");

  if ((s=telnet_find_proxy_from_socket(socket))<0)
  {
    /* we got called for a socket we don't know about - just pass on */
    SYSLOG_EXIT("telnet_handler - unknown");
    return EVENT_PASSON;
  }

  if (reason==socket_BROKEN_EVENT)
  {
    /*socket_close(proxy[s].telnetSocket);*/
    /* tidy SSH too */
    telnet_close_connection(s);
    sshinterface_connection_close(s);
  }

  if (reason==socket_ASYNC_EVENT)
  {
    switch (proxy[s].proxyStatus)
    {
      case PROXY_DORMANT: /* no connection's made - just ignore */
        break;
      case PROXY_HOSTPROMPT:
        return telnet_handle_host_prompt(s);
        break;
      case PROXY_INITSSHCONNECTION:
        return telnet_send_to_ssh(s);
        break;
    }
  }
  SYSLOG_EXIT("telnet_handler");
  return EVENT_CLAIM;
}

void telnet_close_connection(int s)
{
  if (proxy[s].telnetStatus != SOCKET_READY)
  {
    event_deregister_handler(proxy[s].telnetSocket,telnet_handler);
    socket_close(proxy[s].telnetSocket);
    proxy[s].telnetSocket = INVALID_SOCKET;
    proxy[s].sshSocket = INVALID_SOCKET;
    proxy[s].sshStatus = SOCKET_READY;
    proxy[s].proxyStatus = PROXY_DORMANT;
    proxy[s].telnetSession.terminalWidth = 80;
    proxy[s].telnetSession.cursorX = 0;
    proxy[s].telnetSession.terminalType[0] = '\0';
  }
  proxy[s].telnetStatus = SOCKET_READY;
}

static int telnet_find_spare_proxy_slot(void)
{
  int i=0;

  for (i=0; i<maxConnections; i++)
  {
    if (proxy[i].telnetSocket == INVALID_SOCKET)
      return i;
  }

  return -1;
}


/* Accept a connection to the master socket and open a telnet connection.
 * Note this is called under events, so we can't return errors or
 * do printouts
 */

#ifndef LOCALHOST_ONLY_HACK
/* this'll make a declared but not used warning */
static const int WARNING_CompilingWithoutLocalhostOnlyHack = 0;
#endif

void telnet_open_connection(SOCKET masterSocket)
{
  int s=0,true=-1;
  int addressLength=0;
  SOCKET newSocket;
  struct sockaddr_in newAddress;
  struct sockaddr *telnetAddress;
  char *welcome = welcomeMessage;
  unsigned char *connectionLimit = (unsigned char *) "Limit of SSHProxy connections has been reached.\r\n\r\n";
  int tosFlag;

  addressLength = sizeof(newAddress);
  newSocket = accept(masterSocket, (struct sockaddr *)
                                 &(newAddress),
                                 &addressLength);

  telnetAddress = (struct sockaddr *) (&(newAddress));

  if (newSocket ==  EOF)
  {
    /* if it didn't work, just ignore the connection - we might log this,
     * but can't report back
     */
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"%d: Accept failed - error %X,%s\n",s,_inet_error.errnum,_inet_error.errmess);
    newSocket = INVALID_SOCKET;
    return;
  }

  if (!tcpwrapif_check_connection(telnetAddress))
  {
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"%d: Refused attempt to connect by %d.%d.%d.%d on port %d\n",s,telnetAddress->sa_data[2],telnetAddress->sa_data[3],telnetAddress->sa_data[4],telnetAddress->sa_data[5],masterPort);
    socket_close(newSocket);
    newSocket = INVALID_SOCKET;
    return;
  }

  s=telnet_find_spare_proxy_slot();

  /* if no slots are available, report and close */
  if (s<0)
  {
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"No more connections available\n");
    sock_send_string(newSocket,connectionLimit);
    socket_close(newSocket);
    return;
  }

  proxy[s].telnetSocket = newSocket;
  memcpy(&(proxy[s].telnetAddress),&newAddress,sizeof(proxy[s].telnetAddress));

  xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"%d: Successful connection by %d.%d.%d.%d on port %d\n",s,telnetAddress->sa_data[2],telnetAddress->sa_data[3],telnetAddress->sa_data[4],telnetAddress->sa_data[5],masterPort);

    proxy[s].telnetStatus = SOCKET_OPEN;

  if (socket_ioctl(proxy[s].telnetSocket,FIOASYNC,&true) == EOF)
  {
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"%d: Set as async failed - error %X,%s\n",s,_inet_error.errnum,_inet_error.errmess);
    socket_close(proxy[s].telnetSocket);
    proxy[s].telnetStatus = SOCKET_READY;
    return;
  }

  if (socket_ioctl(proxy[s].telnetSocket,FIONBIO ,&true) == EOF)
  {
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION,"%d: Set as non-blocking failed - error %X,%s\n",s,_inet_error.errnum,_inet_error.errmess);
    socket_close(proxy[s].telnetSocket);
    proxy[s].telnetStatus = SOCKET_READY;
    return;
  }

  /* set the IP Type of Service Low Delay bit, so we might get
   * expressed through over bulk data traffic */
  tosFlag = IPTOS_LOWDELAY;
  if (setsockopt(proxy[s].telnetSocket, IPPROTO_IP, IP_TOS, &tosFlag,sizeof(tosFlag))==EOF)
    xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: Failed to set IPTOS_LOWDELAY on ssh side - error %X,%s\n",s,_inet_error.errnum,_inet_error.errmess );


  event_register_handler(proxy[s].telnetSocket,telnet_handler);

  strcpy((char *) proxy[s].lineBuffer,"\0");
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) welcome);
  /* tell client we'll echo, not to do linemode and to suppress goahead */
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfb\x01");
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfd\x03");
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfe\x22");
  /* TELNET_IAC DO SEND_TERMINAL_TYPE */
  sock_send_string(proxy[s].telnetSocket,(unsigned char *) "\xff\xfd\x18");
  telnet_send_host_prompt(s);
  proxy[s].proxyStatus = PROXY_HOSTPROMPT;

  /* telnet_close_connection(s); temp */

}


OSERROR *telnet_initialise(void)
{
  int i=0,j=0;

  /* temporary until errors are sorted out */
  char *msg="out of mem\n";

  strcpy(error_outOfMemory.errmess,msg);
  error_outOfMemory.errnum=1234;

  proxy = calloc(maxConnections,sizeof(ProxiedConnection));

  if (proxy == NULL)
  {
    xsyslog_logmessage(SYSLOG_FILE,"telnet_initialise: out of memory\n",LOG_ERROR);
    return &error_outOfMemory;
  }

  for (i=0; i<maxConnections; i++)
  {
    proxy[i].telnetSocket = INVALID_SOCKET;
    proxy[i].telnetStatus = SOCKET_READY;
    proxy[i].sshSocket = INVALID_SOCKET;
    proxy[i].sshStatus = SOCKET_READY;
    proxy[i].proxyStatus = PROXY_DORMANT;
    proxy[i].swallowUp = 0;
    proxy[i].telnetState = TELSTATEMACHINE_CHARS;
    proxy[i].telnetSession.terminalWidth = 80;
    proxy[i].telnetSession.cursorX = 0;

    for (j=0; j<TELNET_OPTION_COUNT; j++)
      memcpy(&(proxy[i].telnetOptions[j]),masterTelnetOptions[j],sizeof(TelnetOption));

  }

  return NO_OSERROR;
}

OSERROR *telnet_finalise(void)
{
  int i=0;
SYSLOG_ENTRY("telnet_finalise");

  /* if proxy structure unallocated, we can't do anything */
  if (proxy==NULL)
    return NO_OSERROR;

  for (i=0; i<maxConnections; i++)
  {
    telnet_close_connection(i);

    /*if (proxy[i].sshSocket != INVALID_SOCKET)
      socket_close(proxy[i].sshSocket);*/

    proxy[i].telnetSocket = INVALID_SOCKET;
    proxy[i].telnetStatus = SOCKET_READY;
    proxy[i].sshSocket = INVALID_SOCKET;
    proxy[i].sshStatus = SOCKET_READY;
    proxy[i].proxyStatus = PROXY_DORMANT;
    proxy[i].telnetSession.terminalWidth = 80;
    proxy[i].telnetSession.cursorX = 0;
    proxy[i].telnetSession.terminalType[0] = '\0';
  }

  free(proxy);

  proxy = NULL;
SYSLOG_EXIT("telnet_finalise");

  return NO_OSERROR;
}
