/* TN5250 - An implementation of the 5250 telnet protocol.
 * Copyright (C) 1997 Michael Madore
 * 
 * This file is part of TN5250.
 *
 * TN5250 is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1, or (at your option)
 * any later version.
 * 
 * TN5250 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA
 * 
 */
#include "tn5250-private.h"

static int telnet_stream_get_next(Tn5250Stream * This, unsigned char *buf, int size);
static void telnet_stream_do_verb(Tn5250Stream * This, unsigned char verb, unsigned char what);
static int telnet_stream_host_verb(Tn5250Stream * This, unsigned char verb,
	unsigned char what);
static void telnet_stream_sb_var_value(Tn5250Buffer * buf, unsigned char *var, unsigned char *value);
static void telnet_stream_sb(Tn5250Stream * This, unsigned char *sb_buf, int sb_len);
static void telnet_stream_escape(Tn5250Buffer * buffer);
static void telnet_stream_write(Tn5250Stream * This, unsigned char *data, int size);
static int telnet_stream_get_byte(Tn5250Stream * This);

static int telnet_stream_connect(Tn5250Stream * This, const char *to);
static int telnet_stream_accept(Tn5250Stream * This, SOCKET_TYPE masterSock);
static void telnet_stream_destroy(Tn5250Stream *This);
static void telnet_stream_disconnect(Tn5250Stream * This);
static int telnet_stream_handle_receive(Tn5250Stream * This);
static void telnet_stream_send_packet(Tn5250Stream * This, int length,
				      StreamHeader header,
				      unsigned char *data);
static void tn3270_stream_send_packet(Tn5250Stream * This, int length,
				      StreamHeader header,
				      unsigned char * data);

#define SEND    1
#define IS      0
#define INFO    2
#define VALUE   1
#define VAR     0
#define VALUE   1
#define USERVAR 3

#define TERMINAL 1
#define BINARY   2
#define RECORD   4
#define DONE     7
#define HOST     8

#define TRANSMIT_BINARY 0
#define END_OF_RECORD   25
#define TERMINAL_TYPE   24
#define TIMING_MARK     6
#define NEW_ENVIRON	39

#define TN3270E         40

/* Sub-Options for TN3270E negotiation */
#define TN3270E_ASSOCIATE   0
#define TN3270E_CONNECT     1
#define TN3270E_DEVICE_TYPE 2
#define TN3270E_FUNCTIONS   3
#define TN3270E_IS          4
#define TN3270E_REASON      5
#define TN3270E_REJECT      6
#define TN3270E_REQUEST     7
#define TN3270E_SEND        8

/* Reason codes for TN3270E negotiation */
#define TN3270E_CONN_PARTNER    0
#define TN3270E_DEVICE_IN_USE   1
#define TN3270E_INV_ASSOCIATE   2
#define TN3270E_INV_NAME        3
#define TN3270E_INV_DEVICE_TYPE 4
#define TN3270E_TYPE_NAME_ERROR 5
#define TN3270E_UNKNOWN_ERROR   6
#define TN3270E_UNSUPPORTED_REQ 7

/* Function names for TN3270E FUNCTIONS sub-option */
#define TN3270E_BIND_IMAGE      0
#define TN3270E_DATA_STREAM_CTL 1
#define TN3270E_RESPONSES       2
#define TN3270E_SCS_CTL_CODES   3
#define TN3270E_SYSREQ          4

#define EOR  239
#define SE   240
#define SB   250
#define WILL 251
#define WONT 252
#define DO   253
#define DONT 254
#define IAC  255

#define TN5250_STREAM_STATE_NO_DATA 	0	/* Dummy state */
#define TN5250_STREAM_STATE_DATA	1
#define TN5250_STREAM_STATE_HAVE_IAC	2
#define TN5250_STREAM_STATE_HAVE_VERB	3	/* e.g. DO, DONT, WILL, WONT */
#define TN5250_STREAM_STATE_HAVE_SB	4	/* SB data */
#define TN5250_STREAM_STATE_HAVE_SB_IAC	5

/* Internal Telnet option settings (bit-wise flags) */
#define RECV_BINARY	1
#define SEND_BINARY	2
#define RECV_EOR	4
#define SEND_EOR	8

#ifndef HAVE_UCHAR
typedef unsigned char UCHAR;
#endif

static const UCHAR hostInitStr[] = {IAC,DO,NEW_ENVIRON,IAC,DO,TERMINAL_TYPE};
static const UCHAR hostDoEOR[] = {IAC,DO,END_OF_RECORD};
static const UCHAR hostDoBinary[] = {IAC,DO,TRANSMIT_BINARY};
static const UCHAR hostDoTN3270E[] = {IAC,DO,TN3270E};
static const UCHAR hostSBDevice[] = {IAC,SB,TN3270E,TN3270E_SEND,TN3270E_DEVICE_TYPE,
			       IAC,SE};
typedef struct doTable_t {
   const UCHAR	*cmd;
   unsigned	len;
} DOTABLE;

static const DOTABLE host5250DoTable[] = {
  hostInitStr,	sizeof(hostInitStr),
  hostDoEOR,	sizeof(hostDoEOR),
  hostDoBinary,	sizeof(hostDoBinary),
  NULL,		0
};

static const DOTABLE host3270DoTable[] = {
  hostInitStr,  sizeof(hostInitStr),
  hostDoEOR,    sizeof(hostDoEOR),
  hostDoBinary, sizeof(hostDoBinary),
  NULL,         0
};

static const UCHAR SB_Str_NewEnv[]={IAC, SB, NEW_ENVIRON, SEND, USERVAR,
	'I','B','M','R','S','E','E','D', 0,1,2,3,4,5,6,7,
	VAR, USERVAR, IAC, SE};
static const UCHAR SB_Str_TermType[]={IAC, SB, TERMINAL_TYPE, SEND, IAC, SE};

#ifdef NDEBUG
 #define IACVERB_LOG(tag,verb,what)
 #define TNSB_LOG(sb_buf,sb_len)
 #define LOGERROR(tag, ecode)
#else
 #define IACVERB_LOG	log_IAC_verb
 #define TNSB_LOG	log_SB_buf
 #define LOGERROR	logError

static char *getTelOpt(what)
{
   char *wcp, wbuf[10];

   switch (what) {
      case TERMINAL_TYPE:
		wcp = "<TERMTYPE>";
		break;
      case END_OF_RECORD:
		wcp = "<END_OF_REC>";
		break;
      case TRANSMIT_BINARY:
		wcp = "<BINARY>";
		break;
      case NEW_ENVIRON:
		wcp = "<NEWENV>";
		break;
      case EOR:
		wcp = "<EOR>";
		break;
      default:
		sprintf(wcp=wbuf, "<%02X>", what);
		break;
   }
   return wcp;
}

static void logError(char *tag, int ecode)
{
   FILE *errfp = tn5250_logfile ? tn5250_logfile : stderr;

   fprintf(errfp,"%s: ERROR (code=%d) - %s\n", tag, ecode, strerror(ecode));
}

static void log_IAC_verb(char *tag, int verb, int what)
{
   char *vcp, vbuf[10];

   if (!tn5250_logfile)
      return;
   switch (verb) {
      case DO:	vcp = "<DO>";
		break;
      case DONT:
		vcp = "<DONT>";
		break;
      case WILL:
		vcp = "<WILL>";
		break;
      case WONT:
		vcp = "<WONT>";
		break;
      default:
		sprintf(vcp=vbuf, "<%02X>", verb);
		break;
   }
   fprintf(tn5250_logfile,"%s:<IAC>%s%s\n", tag, vcp, getTelOpt(what));
}

static int dumpVarVal(UCHAR *buf, int len)
{
   int c, i;

   for (c=buf[i=0]; i<len && c!=VAR && c!=VALUE && c!=USERVAR; c=buf[++i]) {
      if (isprint(c))
         putc(c, tn5250_logfile);
      else
         fprintf(tn5250_logfile,"<%02X>", c);
   }
   return i;
}

static int dumpNewEnv(unsigned char *buf, int len)
{
   int c, i=0, j;

   while (i<len) {
      switch (c=buf[i]) {
         case IAC:
		return i;
         case VAR:
		fputs("\n\t<VAR>",tn5250_logfile);
		if (++i<len && buf[i]==USERVAR) {
		   fputs("<USERVAR>",tn5250_logfile);
		   return i+1;
		}
		j = dumpVarVal(buf+i, len-i);
		i += j;
         case USERVAR:
		fputs("\n\t<USERVAR>",tn5250_logfile);
		if (!memcmp("IBMRSEED", &buf[++i], 8)) {
		   fputs("IBMRSEED",tn5250_logfile);
		   putc('<',tn5250_logfile);
		   for (j=0, i+=8; j<8; i++, j++) {
		      if (j)
		         putc(' ',tn5250_logfile);
		      fprintf(tn5250_logfile,"%02X",buf[i]);
		   }
		   putc('>',tn5250_logfile);
		} else {
		   j = dumpVarVal(buf+i, len-i);
		   i += j;
		}
		break;
         case VALUE:
		fputs("<VALUE>",tn5250_logfile);
		i++;
		j = dumpVarVal(buf+i, len-i);
		i += j;
		break;
         default:
		fputs(getTelOpt(c),tn5250_logfile);
      } /* switch */
   } /* while */
   return i;
}

static void log_SB_buf(unsigned char *buf, int len)
{
   int c, i, type;

   if (!tn5250_logfile)
      return;
   fprintf(tn5250_logfile,getTelOpt(type=*buf++));
   switch (c=*buf++) {
      case IS:
		fputs("<IS>",tn5250_logfile);
		break;
      case SEND:
		fputs("<SEND>",tn5250_logfile);
		break;
      default:
		fputs(getTelOpt(c),tn5250_logfile);
   }
   len -= 2;
   i = (type==NEW_ENVIRON) ? dumpNewEnv(buf,len) : 0;
   while (i<len) {
      switch(c=buf[i++]) {
         case IAC:
		fputs("<IAC>",tn5250_logfile);
		if (i<len)
		   fputs(getTelOpt(buf[i++]), tn5250_logfile);
		break;
         default:
		if (isprint(c))
		   putc(c, tn5250_logfile);
		else
		   fprintf(tn5250_logfile,"<%02X>", c);
      }
   }
}
#endif /* !NDEBUG */

/****f* lib5250/tn5250_telnet_stream_init
 * NAME
 *    tn5250_telnet_stream_init
 * SYNOPSIS
 *    ret = tn5250_telnet_stream_init (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    DOCUMENT ME!!!
 *****/
int tn5250_telnet_stream_init (Tn5250Stream *This)
{
   This->connect = telnet_stream_connect;
   This->accept = telnet_stream_accept;
   This->disconnect = telnet_stream_disconnect;
   This->handle_receive = telnet_stream_handle_receive;
   This->send_packet = telnet_stream_send_packet;
   This->destroy = telnet_stream_destroy;
   This->streamtype = TN5250_STREAM;
   return 0; /* Ok */
}

/****f* lib5250/tn3270_telnet_stream_init
 * NAME
 *    tn3270_telnet_stream_init
 * SYNOPSIS
 *    ret = tn3270_telnet_stream_init (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    DOCUMENT ME!!!
 *****/
int tn3270_telnet_stream_init (Tn5250Stream *This)
{
   This->connect = telnet_stream_connect;
   This->accept = telnet_stream_accept;
   This->disconnect = telnet_stream_disconnect;
   This->handle_receive = telnet_stream_handle_receive;
   This->send_packet = tn3270_stream_send_packet;
   This->destroy = telnet_stream_destroy;
   This->streamtype = TN3270E_STREAM;
   return 0; /* Ok */
}

/****i* lib5250/telnet_stream_connect
 * NAME
 *    telnet_stream_connect
 * SYNOPSIS
 *    ret = telnet_stream_connect (This, to);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    const char *         to         - 
 * DESCRIPTION
 *    Connects to server.  The `to' parameter is in the form
 *    host[:port].
 *****/
static int telnet_stream_connect(Tn5250Stream * This, const char *to)
{
   struct sockaddr_in serv_addr;
   u_long ioctlarg = 1;
   char *address;
   int r;

   memset((char *) &serv_addr, 0, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;

   /* Figure out the internet address. */
   address = (char *)malloc (strlen (to)+1);
   strcpy (address, to);
   if (strchr (address, ':'))
      *strchr (address, ':') = '\0';
   
   serv_addr.sin_addr.s_addr = inet_addr(address);
   if (serv_addr.sin_addr.s_addr == INADDR_NONE) {
      struct hostent *pent = TN_GETHOSTBYNAME(address);
      if (pent != NULL)
	 serv_addr.sin_addr.s_addr = *((u_long *) (pent->h_addr));
   }
   free (address);
   if (serv_addr.sin_addr.s_addr == INADDR_NONE)
      return LAST_ERROR;

   /* Figure out the port name. */
   if (strchr (to, ':') != NULL) {
      const char *port = strchr (to, ':') + 1;
      serv_addr.sin_port = htons((u_short) atoi(port));
      if (serv_addr.sin_port == 0) {
	 struct servent *pent = getservbyname(port, "tcp");
	 if (pent != NULL)
	    serv_addr.sin_port = pent->s_port;
      }
      if (serv_addr.sin_port == 0)
	 return LAST_ERROR;
   } else {
      /* No port specified ... use telnet port. */
      struct servent *pent = getservbyname ("telnet", "tcp");
      if (pent == NULL)
	 serv_addr.sin_port = htons(23);
      else
	 serv_addr.sin_port = pent->s_port;
   }

   This->sockfd = socket(PF_INET, SOCK_STREAM, 0);
   if (WAS_INVAL_SOCK(This->sockfd)) {
      return LAST_ERROR;
   }
   r = TN_CONNECT(This->sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
   if (WAS_ERROR_RET(r)) {
      return LAST_ERROR;
   }
   /* Set socket to non-blocking mode. */
#ifdef FIONBIO
   TN5250_LOG(("Non-Blocking\n"));
   TN_IOCTL(This->sockfd, FIONBIO, &ioctlarg);
#endif

   This->state = TN5250_STREAM_STATE_DATA;
   return 0;
}

/****i* lib5250/telnet_stream_accept
 * NAME
 *    telnet_stream_accept
 * SYNOPSIS
 *    ret = telnet_stream_accept (This, masterSock);
 * INPUTS
 *    Tn5250Stream *	This       - 
 *    SOCKET		masterSock -
 * DESCRIPTION
 *    Accepts a connection from the client.
 *****/
static int telnet_stream_accept(Tn5250Stream * This, SOCKET_TYPE masterfd)
{
   int i, retCode;
   /*
   int len;
   struct sockaddr_in serv_addr;
   */
   fd_set fdr;
   struct timeval tv;

#ifndef WINELIB
   u_long ioctlarg=1L;
#endif

   /*
   len = sizeof(serv_addr);
   This->sockfd = accept(masterSock, (struct sockaddr *) &serv_addr, &len);
   if (WAS_INVAL_SOCK(This->sockfd)) {
     return LAST_ERROR;
   }
   */
   printf("This->sockfd = %d\n", masterfd);
   This->sockfd = masterfd;

   /* Set socket to non-blocking mode. */
#ifndef WINELIB
   TN_IOCTL(This->sockfd, FIONBIO, &ioctlarg);
#endif

   This->state = TN5250_STREAM_STATE_DATA;
   This->status = HOST;

   /* Commence TN5250 negotiations...
      Send DO options (New Environment, Terminal Type, etc.) */

   if(This->streamtype == TN3270E_STREAM)
     {
       retCode = send(This->sockfd, hostDoTN3270E, sizeof(hostDoTN3270E), 0);
       if (WAS_ERROR_RET(retCode)) {
	 perror("telnetstr");
	 return LAST_ERROR;
       }

       FD_ZERO(&fdr);
       FD_SET(This->sockfd, &fdr);
       tv.tv_sec = 5;
       tv.tv_usec = 0;
       TN_SELECT(This->sockfd + 1, &fdr, NULL, NULL, &tv);
       if (FD_ISSET(This->sockfd, &fdr)) {
	   
	 if (!telnet_stream_handle_receive(This)) {
	   retCode = LAST_ERROR;
	   return retCode ? retCode : -1;
	 }
       } else {
	 return -1;
       }

       if(This->streamtype == TN3270E_STREAM)
	 {
	   retCode = send(This->sockfd, hostSBDevice, sizeof(hostSBDevice),0);

	   if (WAS_ERROR_RET(retCode)) {
	     perror("telnetstr");
	     return LAST_ERROR;
	   }

	   FD_ZERO(&fdr);
	   FD_SET(This->sockfd, &fdr);
	   tv.tv_sec = 5;
	   tv.tv_usec = 0;
	   TN_SELECT(This->sockfd + 1, &fdr, NULL, NULL, &tv);
	   if (FD_ISSET(This->sockfd, &fdr)) {
	     
	     if (!telnet_stream_handle_receive(This)) {
	       retCode = LAST_ERROR;
	       return retCode ? retCode : -1;
	     }
	   } else {
	     return -1;
	   }

	   FD_ZERO(&fdr);
	   FD_SET(This->sockfd, &fdr);
	   tv.tv_sec = 5;
	   tv.tv_usec = 0;
	   TN_SELECT(This->sockfd + 1, &fdr, NULL, NULL, &tv);
	   if (FD_ISSET(This->sockfd, &fdr)) {
	     
	     if (!telnet_stream_handle_receive(This)) {
	       retCode = LAST_ERROR;
	       return retCode ? retCode : -1;
	     }
	   } else {
	     return -1;
	   }
	 } 
       else 
	 {
	   goto neg5250;
	 }
     }
   else
     {
     neg5250:
       for (i=0; host5250DoTable[i].cmd; i++) {
	 retCode = send(This->sockfd, host5250DoTable[i].cmd, 
			host5250DoTable[i].len, 0);
	 if (WAS_ERROR_RET(retCode)) {
	   perror("telnetstr");
	   return LAST_ERROR;
	 }
	 
	 FD_ZERO(&fdr);
	 FD_SET(This->sockfd, &fdr);
	 tv.tv_sec = 5;
	 tv.tv_usec = 0;
	 TN_SELECT(This->sockfd + 1, &fdr, NULL, NULL, &tv);
	 if (FD_ISSET(This->sockfd, &fdr)) {
	   
	   if (!telnet_stream_handle_receive(This)) {
	     retCode = LAST_ERROR;
	     return retCode ? retCode : -1;
	   }
	 } else {
	   return -1;
	 }
       }
     }
   return 0;
}

/****i* lib5250/telnet_stream_disconnect
 * NAME
 *    telnet_stream_disconnect
 * SYNOPSIS
 *    telnet_stream_disconnect (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    Disconnect from the remote host.
 *****/
static void telnet_stream_disconnect(Tn5250Stream * This)
{
  printf("Closing...\n");
   TN_CLOSE(This->sockfd);
}

/****i* lib5250/telnet_stream_destroy
 * NAME
 *    telnet_stream_destroy
 * SYNOPSIS
 *    telnet_stream_destroy (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    DOCUMENT ME!!!
 *****/
static void telnet_stream_destroy(Tn5250Stream *This)
{
   /* noop */
}

/****i* lib5250/telnet_stream_get_next
 * NAME
 *    telnet_stream_get_next
 * SYNOPSIS
 *    ret = telnet_stream_get_next (This, buf, size);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    unsigned char *      buf        -
 *    int                  size       -
 * DESCRIPTION
 *    Gets the next buffer of data from the socket.  The
 *    return value is the length of the data received,
 *    or -1 for no data to receive, or -2 if disconnected
 *****/
static int telnet_stream_get_next(Tn5250Stream * This, unsigned char *buf, int size)
{
   int rc;
   fd_set fdr;
   struct timeval tv;

   FD_ZERO(&fdr);
   FD_SET(This->sockfd, &fdr);
   tv.tv_sec = This->msec_wait/1000;
   tv.tv_usec = (This->msec_wait%1000)*1000;
   TN_SELECT(This->sockfd + 1, &fdr, NULL, NULL, &tv);
   if (!FD_ISSET(This->sockfd, &fdr))
      return -1;		/* No data on socket. */

   rc = TN_RECV(This->sockfd, buf, size, 0);
   if (WAS_ERROR_RET(rc)) {
      if (LAST_ERROR != ERR_AGAIN && LAST_ERROR != ERR_INTR) {
	 TN5250_LOG(("Error reading from socket: %s\n", strerror(LAST_ERROR)));
	 return -2;
      } else
	 return -1;
   }
   /* If the socket was readable, but there is no data, that means that we
      have been disconnected. */
   if (rc == 0)
      return -2;

   return rc;
}

static int sendWill(SOCKET_TYPE sock, unsigned char what)
{
   UCHAR buff[3]={IAC,WILL};
   buff[2] = what;
   return send(sock, buff, 3, 0);
}

/****i* lib5250/telnet_stream_host_verb
 * NAME
 *    telnet_stream_host_verb
 * SYNOPSIS
 *    telnet_stream_host_verb (This, verb, what);
 * INPUTS
 *    SOCKET_TYPE	sock	-
 *    unsigned char	verb	-
 *    unsigned char	what	-
 * DESCRIPTION
 *    Process the telnet DO, DONT, WILL, or WONT escape sequence.
 *****/
static int telnet_stream_host_verb(Tn5250Stream * This, unsigned char verb,
		unsigned char what)
{
   int len, option=0, retval=0;
   SOCKET_TYPE sock;

   sock = This->sockfd;

   IACVERB_LOG("GotVerb(1)",verb,what);
   switch (verb) {
      case DO:
	switch (what) {
	   case END_OF_RECORD:
		option = SEND_EOR;
		break;

	   case TRANSMIT_BINARY:
		option = SEND_BINARY;
		break;

	   default:
		break;
	} /* DO: switch (what) */
	break;

      case DONT:
      case WONT:
	if(what == TN3270E) 
	  {
	    This->streamtype = TN3270_STREAM;
	  }
	break;

      case WILL:
	switch (what) {
	   case NEW_ENVIRON:
		len = sizeof(SB_Str_NewEnv);
		TN5250_LOG(("Sending SB NewEnv..\n"));
		retval = send(sock, SB_Str_NewEnv, len, 0);
		break;

	   case TERMINAL_TYPE:
		len = sizeof(SB_Str_TermType);
		TN5250_LOG(("Sending SB TermType..\n"));
		retval = send(sock, SB_Str_TermType, len, 0);
		break;

	   case END_OF_RECORD:
		option = RECV_EOR;
		retval = sendWill(sock, what);
		break;

	   case TRANSMIT_BINARY:
		option = RECV_BINARY;
		retval = sendWill(sock, what);
		break;

	   default:
		break;
	} /* WILL: switch (what) */
	break;

      default:
	break;
   } /* switch (verb) */

   return(WAS_ERROR_RET(retval) ? retval : option);
} /* telnet_stream_host_verb */


/****i* lib5250/telnet_stream_do_verb
 * NAME
 *    telnet_stream_do_verb
 * SYNOPSIS
 *    telnet_stream_do_verb (This, verb, what);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    unsigned char        verb       - 
 *    unsigned char        what       - 
 * DESCRIPTION
 *    Process the telnet DO, DONT, WILL, or WONT escape sequence.
 *****/
static void telnet_stream_do_verb(Tn5250Stream * This, unsigned char verb, unsigned char what)
{
   unsigned char reply[3];
   int ret;

   IACVERB_LOG("GotVerb(2)", verb, what);
   reply[0] = IAC;
   reply[2] = what;
   switch (verb) {
   case DO:
      switch (what) {
      case TERMINAL_TYPE:
      case END_OF_RECORD:
      case TRANSMIT_BINARY:
      case NEW_ENVIRON:
	 reply[1] = WILL;
	 break;

      default:
	 reply[1] = WONT;
	 break;
      }
      break;

   case DONT:
      break;

   case WILL:
      switch (what) {
      case TERMINAL_TYPE:
      case END_OF_RECORD:
      case TRANSMIT_BINARY:
      case NEW_ENVIRON:
	 reply[1] = DO;
	 break;

      case TIMING_MARK:
	 TN5250_LOG(("do_verb: IAC WILL TIMING_MARK received.\n"));
      default:
	 reply[1] = DONT;
	 break;
      }
      break;

   case WONT:
      break;
   }

   /* We should really keep track of states here, but the code has been
    * like this for some time, and no complaints.  
    *
    * Actually, I don't even remember what that comment means -JMF */

   IACVERB_LOG("GotVerb(3)",verb,what);
   ret = TN_SEND(This->sockfd, (char *) reply, 3, 0);
   if (WAS_ERROR_RET(ret)) {
      printf("Error writing to socket: %s\n", strerror(LAST_ERROR));
      exit(5);
   }
}

static void telnet_stream_host_sb(Tn5250Stream * This, UCHAR *sb_buf,
		int sb_len)
{
  int rc;
  int i;
  int sbType;
  int sbParm;
  Tn5250Buffer tbuf;
  UCHAR deviceResponse[] = {IAC,SB,TN3270E,TN3270E_DEVICE_TYPE,TN3270E_IS};
  UCHAR functionResponse[] = {IAC,SB,TN3270E,TN3270E_FUNCTIONS};
  char * dummyname = "TN3E002";
  
  if (sb_len <= 0)
    return;

  TN5250_LOG(("GotSB:<IAC><SB>"));
  TNSB_LOG(sb_buf,sb_len);
  TN5250_LOG(("<IAC><SE>\n"));
  sbType = sb_buf[0];
  switch (sbType) 
    {
    case TN3270E:
      sb_buf += 1;
      sb_len -= 1;
      sbParm = sb_buf[0];
      switch (sbParm)
	{
	case TN3270E_DEVICE_TYPE:
	  sb_buf += 2; /* Device string follows DEVICE_TYPE IS parameter */
	  sb_len -= 2;
	  tn5250_buffer_init(&tbuf);
	  tn5250_buffer_append_data(&tbuf, deviceResponse, 
				    sizeof(deviceResponse));
	  for(i=0; i<sb_len && sb_buf[i] != IAC; i++)
	    tn5250_buffer_append_byte(&tbuf, sb_buf[i]);
	  tn5250_buffer_append_byte(&tbuf, TN3270E_CONNECT);
	  tn5250_buffer_append_data(&tbuf, dummyname, strlen(dummyname));
	  tn5250_buffer_append_byte(&tbuf, IAC);
	  tn5250_buffer_append_byte(&tbuf, SE);
	  rc = TN_SEND(This->sockfd, (char *) tn5250_buffer_data(&tbuf),
		       tn5250_buffer_length(&tbuf), 0);
	  if (WAS_ERROR_RET(rc)) {
	    printf("Error writing to socket: %s\n", strerror(LAST_ERROR));
	    exit(5);
	  }
	  break;
	case TN3270E_FUNCTIONS:
	  sb_buf += 2; /* Function list follows FUNCTIONS REQUEST parameter */ 
	  sb_len -= 2;
	  tn5250_buffer_init(&tbuf);
	  tn5250_buffer_append_data(&tbuf, functionResponse, 
				    sizeof(functionResponse));
	  
	  tn5250_buffer_append_byte(&tbuf, TN3270E_IS);
	  for(i=0; i<sb_len && sb_buf[i] != IAC; i++)
	    {
	      tn5250_buffer_append_byte(&tbuf, sb_buf[i]);
	      This->options = This->options | (1 << (sb_buf[i]+1));
	    }
	  
	  tn5250_buffer_append_byte(&tbuf, IAC);
	  tn5250_buffer_append_byte(&tbuf, SE);
	  rc = TN_SEND(This->sockfd, (char *) tn5250_buffer_data(&tbuf),
		       tn5250_buffer_length(&tbuf), 0);
	  if (WAS_ERROR_RET(rc)) {
	    printf("Error writing to socket: %s\n", strerror(LAST_ERROR));
	    exit(5);
	  }
	  break;
	default:
	  break;
	}
      break;
    case TERMINAL_TYPE:
      sb_buf += 2;  /* Assume IS follows SB option type. */
      sb_len -= 2;
      tn5250_buffer_init(&tbuf);
      for (i=0; i<sb_len && sb_buf[i]!=IAC; i++)
	tn5250_buffer_append_byte(&tbuf, sb_buf[i]);
      tn5250_buffer_append_byte(&tbuf, 0);
      tn5250_stream_setenv(This, "TERM", (char *) tbuf.data);
      tn5250_buffer_free(&tbuf);
      break;
    case NEW_ENVIRON:
      /* TODO:
       * setNewEnvVars(This, sb_buf, sb_len);
       */
      break;
    default:
      break;
    } /* switch */
} /* telnet_stream_host_sb */

/****i* lib5250/telnet_stream_sb_var_value
 * NAME
 *    telnet_stream_sb_var_value
 * SYNOPSIS
 *    telnet_stream_sb_var_value (buf, var, value);
 * INPUTS
 *    Tn5250Buffer *       buf        - 
 *    unsigned char *      var        - 
 *    unsigned char *      value      - 
 * DESCRIPTION
 *    Utility function for constructing replies to NEW_ENVIRON requests.
 *****/
static void telnet_stream_sb_var_value(Tn5250Buffer * buf, unsigned char *var, unsigned char *value)
{
   tn5250_buffer_append_byte(buf, VAR);
   tn5250_buffer_append_data(buf, var, strlen((char *) var));
   tn5250_buffer_append_byte(buf, VALUE);
   tn5250_buffer_append_data(buf, value, strlen((char *) value));
}

/****i* lib5250/telnet_stream_sb
 * NAME
 *    telnet_stream_sb
 * SYNOPSIS
 *    telnet_stream_sb (This, sb_buf, sb_len);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    unsigned char *      sb_buf     - 
 *    int                  sb_len     - 
 * DESCRIPTION
 *    Handle telnet SB escapes, which are the option-specific negotiations.
 *****/
static void telnet_stream_sb(Tn5250Stream * This, unsigned char *sb_buf, int sb_len)
{
   Tn5250Buffer out_buf;
   int ret;

   TN5250_LOG(("GotSB:<IAC><SB>"));
   TNSB_LOG(sb_buf,sb_len);
   TN5250_LOG(("<IAC><SE>\n"));

   tn5250_buffer_init(&out_buf);

   if (sb_len <= 0)
      return;

   if (sb_buf[0] == TERMINAL_TYPE) {
      unsigned char *termtype;

      if (sb_buf[1] != SEND)
	 return;

      termtype = (unsigned char *) tn5250_stream_getenv(This, "TERM");

      tn5250_buffer_append_byte(&out_buf, IAC);
      tn5250_buffer_append_byte(&out_buf, SB);
      tn5250_buffer_append_byte(&out_buf, TERMINAL_TYPE);
      tn5250_buffer_append_byte(&out_buf, IS);
      tn5250_buffer_append_data(&out_buf, termtype, strlen((char *) termtype));
      tn5250_buffer_append_byte(&out_buf, IAC);
      tn5250_buffer_append_byte(&out_buf, SE);

      ret = TN_SEND(This->sockfd, (char *) tn5250_buffer_data(&out_buf),
		 tn5250_buffer_length(&out_buf), 0);
      if (WAS_ERROR_RET(ret)) {
	 printf("Error writing to socket: %s\n", strerror(LAST_ERROR));
	 exit(5);
      }
      TN5250_LOG(("SentSB:<IAC><SB><TERMTYPE><IS>%s<IAC><SE>\n", termtype));

      This->status = This->status | TERMINAL;
   } else if (sb_buf[0] == NEW_ENVIRON) {
     Tn5250ConfigStr *iter;
     tn5250_buffer_append_byte(&out_buf, IAC);
     tn5250_buffer_append_byte(&out_buf, SB);
     tn5250_buffer_append_byte(&out_buf, NEW_ENVIRON);
     tn5250_buffer_append_byte(&out_buf, IS);

      if (This->config != NULL) {
	 if ((iter = This->config->vars) != NULL) {
	    do {
	      if ((strlen (iter->name) > 4) && (!memcmp (iter->name, "env.", 4))) {
		telnet_stream_sb_var_value(&out_buf,
			(unsigned char *) iter->name + 4,
			(unsigned char *) iter->value);
	       }
	       iter = iter->next;
	    } while (iter != This->config->vars);
	 }
      }
      tn5250_buffer_append_byte(&out_buf, IAC);
      tn5250_buffer_append_byte(&out_buf, SE);

      ret = TN_SEND(This->sockfd, (char *) tn5250_buffer_data(&out_buf),
		 tn5250_buffer_length(&out_buf), 0);
      if (WAS_ERROR_RET(ret)) {
	 printf("Error writing to socket: %s\n", strerror(LAST_ERROR));
	 exit(5);
      }
      TN5250_LOG(("SentSB:<IAC><SB>"));
      TNSB_LOG(&out_buf.data[2], out_buf.len-4);
      TN5250_LOG(("<IAC><SE>\n"));
   }
   tn5250_buffer_free(&out_buf);
}

/****i* lib5250/telnet_stream_get_byte
 * NAME
 *    telnet_stream_get_byte
 * SYNOPSIS
 *    ret = telnet_stream_get_byte (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    Returns the next byte from the 5250 data stream, or return -1 if no data
 *    is waiting on the socket or -2 if disconnected, or -END_OF_RECORD if a 
 *    telnet EOR escape sequence was encountered.
 *****/
static int telnet_stream_get_byte(Tn5250Stream * This)
{
   int temp;
   unsigned char verb;

   do {
      if (This->state == TN5250_STREAM_STATE_NO_DATA)
	 This->state = TN5250_STREAM_STATE_DATA;

      This->rcvbufpos ++;
      if (This->rcvbufpos >= This->rcvbuflen) {
          This->rcvbufpos = 0;
          This->rcvbuflen = telnet_stream_get_next(This, This->rcvbuf, TN5250_RBSIZE);
          if (This->rcvbuflen<0) 
               return This->rcvbuflen;
      }
      temp = This->rcvbuf[This->rcvbufpos];

      switch (This->state) {
      case TN5250_STREAM_STATE_DATA:
	 if (temp == IAC)
	    This->state = TN5250_STREAM_STATE_HAVE_IAC;
	 break;

      case TN5250_STREAM_STATE_HAVE_IAC:
	switch(temp) {
	case IAC:
	  This->state = TN5250_STREAM_STATE_DATA;
	  break;

	 case DO:
	 case DONT:
	 case WILL:
	 case WONT:
	    verb = temp;
	    This->state = TN5250_STREAM_STATE_HAVE_VERB;
	    break;

	 case SB:
	    This->state = TN5250_STREAM_STATE_HAVE_SB;
	    tn5250_buffer_free(&(This->sb_buf));
	    break;

	 case EOR:
	    This->state = TN5250_STREAM_STATE_DATA;
	    return -END_OF_RECORD;

	 default:
	    TN5250_LOG(("GetByte: unknown escape 0x%02x in telnet stream.\n", temp));
	    This->state = TN5250_STREAM_STATE_NO_DATA;	/* Hopefully a good recovery. */
	 }
	 break;

      case TN5250_STREAM_STATE_HAVE_VERB:
	TN5250_LOG(("HOST, This->status  = %d %d\n", HOST, This->status));
	 if (This->status&HOST) {
	    temp = telnet_stream_host_verb(This, verb, (UCHAR) temp);
	    if (WAS_ERROR_RET(temp)) {
	       LOGERROR("send", LAST_ERROR);
	       return -2;
	    }
	    /* Implement later...
	    This->options |= temp;
	    */
	 } else
	    telnet_stream_do_verb(This, verb, (UCHAR) temp);
	 This->state = TN5250_STREAM_STATE_NO_DATA;
	 break;

      case TN5250_STREAM_STATE_HAVE_SB:
	 if (temp == IAC)
	    This->state = TN5250_STREAM_STATE_HAVE_SB_IAC;
	 else
	   tn5250_buffer_append_byte(&(This->sb_buf), (UCHAR) temp);
	 break;

      case TN5250_STREAM_STATE_HAVE_SB_IAC:
	 switch (temp) {
	 case IAC:
	    tn5250_buffer_append_byte(&(This->sb_buf), IAC);
	    /* Since the IAC code was escaped, shouldn't we be resetting the
	       state as in the following statement?  Please verify and
	       uncomment if applicable.  GJS 2/25/2000 */
	    /* This->state = TN5250_STREAM_STATE_HAVE_SB; */
	    break;

	 case SE:
	    if (This->status&HOST)
	       telnet_stream_host_sb(This, tn5250_buffer_data(&This->sb_buf),
			tn5250_buffer_length(&This->sb_buf));
	    else
	       telnet_stream_sb(This, tn5250_buffer_data(&(This->sb_buf)),
			tn5250_buffer_length(&(This->sb_buf)));

	    tn5250_buffer_free(&(This->sb_buf));
	    This->state = TN5250_STREAM_STATE_NO_DATA;
	    break;

	 default:		/* Should never happen -- server error */
	    TN5250_LOG(("GetByte: huh? Got IAC SB 0x%02X.\n", temp));
	    This->state = TN5250_STREAM_STATE_HAVE_SB;
	    break;
	 }
	 break;

      default:
	 TN5250_LOG(("GetByte: huh? Invalid state %d.\n", This->state));
	 TN5250_ASSERT(0);
	 break;			/* Avoid compiler warning. */
      }
   } while (This->state != TN5250_STREAM_STATE_DATA);
   return (int) temp;
}

/****i* lib5250/telnet_stream_write
 * NAME
 *    telnet_stream_write
 * SYNOPSIS
 *    telnet_stream_write (This, data, size);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    unsigned char *      data       - 
 *    int                  size       - 
 * DESCRIPTION
 *    Writes size bytes of data (pointed to by *data) to the 5250 data stream.
 *    This is also a temporary method to aid in the conversion process.  
 *****/
static void telnet_stream_write(Tn5250Stream * This, unsigned char *data, int size)
{
   int r;
   int last_error = 0;
   fd_set fdw;

   /* There was an easier way to do this, but I can't remember.  This
      just makes sure that non blocking writes that don't have enough
      buffer space get completed anyway. */
   do {
      FD_ZERO(&fdw);
      FD_SET(This->sockfd, &fdw);
      r = TN_SELECT(This->sockfd + 1, NULL, &fdw, NULL, NULL);
      if (WAS_ERROR_RET(r)) {
	 last_error = LAST_ERROR;
	 switch (last_error) {
	 case ERR_INTR:
	 case ERR_AGAIN:
	    r = 0;
	    continue;

	 default:
	    perror("select");
	    exit(5);
	 }
      }
      if (FD_ISSET(This->sockfd, &fdw)) {
	 r = TN_SEND(This->sockfd, (char *) data, size, 0);
	 if (WAS_ERROR_RET(r)) {
	    last_error = LAST_ERROR;
	    if (last_error != ERR_AGAIN) {
	       perror("Error writing to socket");
	       exit(5);
	    }
	 }
	 if (r > 0) {
	    data += r;
	    size -= r;
	 }
      }
   } while (size && (r >= 0 || last_error == ERR_AGAIN));
}

/****i* lib5250/telnet_stream_send_packet
 * NAME
 *    telnet_stream_send_packet
 * SYNOPSIS
 *    telnet_stream_send_packet (This, length, flowtype, flags, opcode, data);
 * INPUTS
 *    Tn5250Stream *       This       - 
 *    int                  length     - 
 *    int                  flowtype   -
 *    unsigned char        flags      -
 *    unsgined char        opcode     -
 *    unsigned char *      data       - 
 * DESCRIPTION
 *    Send a packet, prepending a header and escaping any naturally
 *    occuring IAC characters.
 *****/
static void telnet_stream_send_packet(Tn5250Stream * This, int length, 
				      StreamHeader header, unsigned char *data)
{
   Tn5250Buffer out_buf;
   int n;
   int flowtype;
   unsigned char flags;
   unsigned char opcode;

   flowtype = header.h5250.flowtype;
   flags = header.h5250.flags;
   opcode = header.h5250.opcode;

   length = length + 10;

   /* Fixed length portion of header */
   tn5250_buffer_init(&out_buf);
   tn5250_buffer_append_byte(&out_buf, (UCHAR) (((short)length)>>8));
   tn5250_buffer_append_byte(&out_buf, (UCHAR) (length & 0xff));
   tn5250_buffer_append_byte(&out_buf, 0x12);	/* Record type = General data stream (GDS) */
   tn5250_buffer_append_byte(&out_buf, 0xa0);
   tn5250_buffer_append_byte(&out_buf, (UCHAR)(flowtype >> 8));
   tn5250_buffer_append_byte(&out_buf, (UCHAR) (flowtype & 0xff));

   /* Variable length portion of header */
   tn5250_buffer_append_byte(&out_buf, 4);
   tn5250_buffer_append_byte(&out_buf, flags);
   tn5250_buffer_append_byte(&out_buf, 0);
   tn5250_buffer_append_byte(&out_buf, opcode);
   tn5250_buffer_append_data(&out_buf, data, length - 10);

   telnet_stream_escape(&out_buf);

   tn5250_buffer_append_byte(&out_buf, IAC);
   tn5250_buffer_append_byte(&out_buf, EOR);

#ifndef NDEBUG
   TN5250_LOG(("SendPacket: length = %d\nSendPacket: data follows.",
	tn5250_buffer_length(&out_buf)));
   for (n = 0; n < tn5250_buffer_length(&out_buf); n++) {
      if ((n % 16) == 0) {
	 TN5250_LOG(("\nSendPacket: data: "));
      }
      TN5250_LOG(("%02X ", tn5250_buffer_data(&out_buf)[n]));
   }
   TN5250_LOG(("\n"));
#endif

   telnet_stream_write(This, tn5250_buffer_data(&out_buf), tn5250_buffer_length(&out_buf));
   tn5250_buffer_free(&out_buf);
}

void
tn3270_stream_send_packet(Tn5250Stream * This, int length,
			  StreamHeader header,
			  unsigned char * data)
{
  Tn5250Buffer out_buf;

  tn5250_buffer_init(&out_buf);

  if(This->streamtype == TN3270E_STREAM)
    {
      tn5250_buffer_append_byte(&out_buf, header.h3270.data_type);
      tn5250_buffer_append_byte(&out_buf, header.h3270.request_flag);
      tn5250_buffer_append_byte(&out_buf, header.h3270.response_flag);

      tn5250_buffer_append_byte(&out_buf, header.h3270.sequence >> 8);
      tn5250_buffer_append_byte(&out_buf, header.h3270.sequence & 0x00ff);
    }
  
  tn5250_buffer_append_data(&out_buf, data, length);
      
  telnet_stream_escape(&out_buf);

  tn5250_buffer_append_byte(&out_buf, IAC);
  tn5250_buffer_append_byte(&out_buf, EOR);
   
  telnet_stream_write(This, tn5250_buffer_data(&out_buf), 
		      tn5250_buffer_length(&out_buf));

  tn5250_buffer_free(&out_buf);

}

/****f* lib5250/telnet_stream_handle_receive
 * NAME
 *    telnet_stream_handle_receive
 * SYNOPSIS
 *    ret = telnet_stream_handle_receive (This);
 * INPUTS
 *    Tn5250Stream *       This       - 
 * DESCRIPTION
 *    Read as much data as possible in a non-blocking fasion, form it
 *    into Tn5250Record structures and queue them for retrieval.
 *****/
int telnet_stream_handle_receive(Tn5250Stream * This)
{
   int c;

   /* -1 = no more data, -2 = we've been disconnected */
   while ((c = telnet_stream_get_byte(This)) != -1 && c != -2) {

      if (c == -END_OF_RECORD && This->current_record != NULL) {
	 /* End of current packet. */
#ifndef NDEBUG
         if (tn5250_logfile!=NULL) 
             tn5250_record_dump(This->current_record);
#endif
	 This->records = tn5250_record_list_add(This->records, This->current_record);
	 This->current_record = NULL;
	 This->record_count++;
	 continue;
      }
      if (This->current_record == NULL) {
	 /* Start of new packet. */
	 This->current_record = tn5250_record_new();
      }
      tn5250_record_append_byte(This->current_record, (unsigned char) c);
   }

   return (c != -2);
}

/****i* lib5250/telnet_stream_escape
 * NAME
 *    telnet_stream_escape
 * SYNOPSIS
 *    telnet_stream_escape (in);
 * INPUTS
 *    Tn5250Buffer *       in         - 
 * DESCRIPTION
 *    Escape IACs in data before sending it to the host.
 *****/
static void telnet_stream_escape(Tn5250Buffer * in)
{
   Tn5250Buffer out;
   register unsigned char c;
   int n;

   tn5250_buffer_init(&out);
   for (n = 0; n < tn5250_buffer_length(in); n++) {
      c = tn5250_buffer_data(in)[n];
      tn5250_buffer_append_byte(&out, c);
      if (c == IAC)
	 tn5250_buffer_append_byte(&out, IAC);
   }
   tn5250_buffer_free(in);
   memcpy(in, &out, sizeof(Tn5250Buffer));
}

/* vi:set sts=3 sw=3: */



















syntax highlighted by Code2HTML, v. 0.9.1