#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sockets.h"

#include "ssh.h"
#include "winsock.h"
#include "def.h"
#include "sshif.h"
#include "syslogif.h"
#include "hostkey.h"

SSHSession *session = NULL;

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

#if 0
void verify_ssh_host_key(WChar *host, struct RSAKey *key) {
  xsyslogf_irq(SYSLOG_FILE,LOG_ERROR,"verify_ssh_host_key: assuming host %s is valid - haven't checked key",host);
  return;
}
#endif


/* Coroutine mechanics for the sillier bits of the code */
/* Made even messier by Theo to make code reentrant     */
#define crBegin1 /* not used - done when a session is initialised */
#define crBegin2	switch(session[pN].crLine[FN]) { case 0:;\
        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crBegin2 line %d",pN,__LINE__);
#define crBegin		crBegin1; crBegin2;\
        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crBegin line %d",pN,__LINE__);
#define crFinish(z)	} {session[pN].crLine[FN] = 0;         xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crFinish line %d",pN,__LINE__);}\
return (z)
#define crFinishV	} {session[pN].crLine[FN] = 0;          xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crFinishV line %d",pN,__LINE__);}\
return NULL
#define crReturn(z)	\
         {xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crReturn line %d",pN,__LINE__);\
        /*do*/ {\
	    session[pN].crLine[FN]=__LINE__; return (z); case __LINE__:;\
	} /*while (0)*/;}
#define crReturnV	\
         {xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crReturnV line %d",pN,__LINE__);\
	/*do*/ {\
	    session[pN].crLine[FN]=__LINE__; return NULL; case __LINE__:;\
	} /*while (0)*/;}
#define crStop(z)	         {xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crStop line %d",pN,__LINE__);\
            /*do*/{ session[pN].crLine[FN] = 0; return (z); }/*while(0)*/;}
#define crStopV		         {xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: crStopV line %d",pN,__LINE__);\
/*do*/{ session[pN].crLine[FN] = 0; return NULL; }/*while(0)*/;}

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

/*static SOCKET s = INVALID_SOCKET;

static WUChar session_key[32];
static struct ssh_cipher *cipher = NULL;

static WChar *savedhost;

static enum {
    SSH_STATE_BEFORE_SIZE,
    SSH_STATE_INTERMED,
    SSH_STATE_SESSION
} ssh_state = SSH_STATE_BEFORE_SIZE;

static WInt size_needed = FALSE;*/

static void s_write (WInt pN, WChar *buf, WInt len) {
  WChar tmpbuf[256];
  WInt debugDispLen = 255;
SYSLOG_ENTRY("s_write");
if (len>0)
  {
    if (len<debugDispLen)
      debugDispLen = len;
    strncpy(tmpbuf,buf,debugDispLen);
    tmpbuf[debugDispLen] = '\0';
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: s_write: '%s', len %d\n",pN,tmpbuf,len);
  }
  else
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: s_write: no bytes\n",pN);

    while (len > 0) {
	WInt i = send (session[pN].s, buf, len, 0);
	if (i > 0)
	    len -= i, buf += i;
	if (len > 0)
	  sshinterface_poll_single(FALSE);
    }
SYSLOG_EXIT("s_write");
}

static WInt s_read (WInt pN, WChar *buf, WInt len) {
    WInt ret = 0;

    WChar *b=buf;
    WChar tmpb[256];
    WInt l=len,debugDispLen=0;
SYSLOG_ENTRY("s_read");

    while (len > 0) {
	WInt i = recv (session[pN].s, buf, len, 0);
	if (i > 0)
	    len -= i, buf += i, ret += i;
	else
	{
	  debugDispLen = ((l-len)>255) ? 255 : l-len;
	  strncpy(tmpb,b,debugDispLen);
	  xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: s_read error: got %d bytes: '%s'\n",pN,l-len,tmpb);
	    return i;
	}
    }
    debugDispLen = ((l-len)>255) ? 255 : l-len;
    strncpy(tmpb,b,debugDispLen);
    tmpb[debugDispLen]='\0';
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: s_read: got %d bytes: '%s'\n",pN,l-len,tmpb);
SYSLOG_EXIT("s_read");

    return ret;
}

static void c_write (WInt pN, WChar *buf, WInt len) {
/*    while (len--) {
	WInt new_head = (session[pN].inbuf_head + 1) & INBUF_MASK;
	WInt c = (WUChar) *buf;
	if (new_head != session[pN].inbuf_reap) {
	    session[pN].inbuf[session[pN].inbuf_head] = *buf++;
	    session[pN].inbuf_head = new_head;
	}
    }*/
    WChar tmpbuf[256];
    WInt debugDispLen = 255;
SYSLOG_ENTRY("c_write");
    if (len<256)
      debugDispLen = len;
    strncpy(tmpbuf,buf,debugDispLen);
    tmpbuf[debugDispLen] = '\0';
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: c_write: sending '%s' to telnet\n",pN,tmpbuf);
    while (len > 0) {
	WInt i = send (session[pN].telnetSocket, buf, len, 0);
	if (i > 0)
	    len -= i, buf += i;
	if (len > 0)
	  sshinterface_poll_single(FALSE);
    }
SYSLOG_EXIT("c_write");
}

static WChar *ssh_protocol(WInt pN, WUChar *in, WInt inlen, WInt ispkt);
static WChar *ssh_size(WInt pN);

#define FN 0 /* number of function using coroutine calls (urgh) */
static WChar *ssh_gotdata(WInt pN, WUChar *data, WInt datalen) {
/*    static WLong len, biglen, to_read;
    static WUChar c, *p;
    static WInt i, pad;
    static WChar padding[8];
    static WUChar word[4];*/
    WChar *error;
SYSLOG_ENTRY("ssh_gotdata");
    crBegin;
    while (1) {
	for (session[pN].gD.i = session[pN].gD.len = 0; session[pN].gD.i < 4; session[pN].gD.i++) {
	    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_gotdata: i=%d, len=%X,datalen=%X\n",pN,session[pN].gD.i,session[pN].gD.len,datalen);
	    while (datalen == 0)
		crReturnV;
	    session[pN].gD.len = (session[pN].gD.len << 8) + *data;
	    data++, datalen--;
	}

#ifdef FWHACK
        if (session[pN].gD.len == 0x52656d6f) {       /* "Remo"te server has closed ... */
            session[pN].gD.len = 0x300;               /* big enough to carry to end */
        }
#endif

	session[pN].gD.pad = 8 - (session[pN].gD.len%8);

	session[pN].gD.biglen = session[pN].gD.len + session[pN].gD.pad;

	session[pN].gD.len -= 5;		       /* type and CRC */

        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: len = %d, biglen = %d, session[Pn].pktin.data = %p\n",pN,session[pN].gD.len,session[pN].gD.biglen,session[pN].pktin.data);

	session[pN].pktin.length = session[pN].gD.len;
	if (session[pN].pktin.maxlen < session[pN].gD.biglen) {
	    session[pN].pktin.maxlen = session[pN].gD.biglen;
	    session[pN].pktin.data = (session[pN].pktin.data == NULL ? malloc(session[pN].gD.biglen) :
			realloc(session[pN].pktin.data, session[pN].gD.biglen));
	    if (!session[pN].pktin.data)
		return ("Out of memory");
	}

        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: alloced %d for session[pN].pktin.data\n",pN,session[pN].gD.biglen);

	session[pN].gD.p = session[pN].pktin.data, session[pN].gD.to_read = session[pN].gD.biglen;
	while (session[pN].gD.to_read > 0) {
	    /*static WInt chunk;*/
	    session[pN].gD.chunk = session[pN].gD.to_read;
	    while (datalen == 0)
		crReturnV;
	    if (session[pN].gD.chunk > datalen)
		session[pN].gD.chunk = datalen;
	    memcpy(session[pN].gD.p, data, session[pN].gD.chunk);
	    data += session[pN].gD.chunk;
	    datalen -= session[pN].gD.chunk;
	    session[pN].gD.p += session[pN].gD.chunk;
	    session[pN].gD.to_read -= session[pN].gD.chunk;
	}

        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: about to decrypt\n",pN);
	if (session[pN].cipher)
	{
	  xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: decrypting via routine %p\n",pN,session[pN].cipher);
	    session[pN].cipher->decrypt(pN, session[pN].pktin.data, session[pN].gD.biglen);
	}

        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: done decrypt:\n",pN);
	session[pN].pktin.type = session[pN].pktin.data[session[pN].gD.pad];
	session[pN].pktin.body = session[pN].pktin.data+session[pN].gD.pad+1;
	xsyslog_irq_logdata(SYSLOG_FILE,LOG_DEBUG_VERYLOW,session[pN].pktin.body,session[pN].gD.biglen,0);
	xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: type = %d, body = %p\n",pN,session[pN].pktin.type,session[pN].pktin.body);

	if (session[pN].pktin.type == 36) {	       /* SSH_MSG_DEBUG */
	    /* FIXME: log it */
	  xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: ssh_gotdata - received SSH_MSG_DEBUG:\n",pN);
	  xsyslog_irq_logdata(SYSLOG_FILE,LOG_CONNECTION_LOW,session[pN].pktin.body,session[pN].gD.biglen,0);

	} else
	    if ((error=ssh_protocol(pN, NULL, 0, 1))!=NULL)
	      return error;
    }
    crFinishV;
}
#undef FN

static WChar *s_wrpkt_start(WInt pN, WInt type, WInt len) {
    WInt pad, biglen;

SYSLOG_ENTRY("s_wrpkt_start");

    len += 5;			       /* type and CRC */
    pad = 8 - (len%8);
    biglen = len + pad;

    session[pN].pktout.length = len-5;
    if (session[pN].pktout.maxlen < biglen) {
	session[pN].pktout.maxlen = biglen;
	session[pN].pktout.data = ((session[pN].pktout.data == NULL) ? malloc(biglen+32 /* MEMORY LEAK FIX? */) :
		       realloc(session[pN].pktout.data, biglen));
	if (!session[pN].pktout.data)
	    return ("Out of memory");
    }

    session[pN].pktout.type = type;
    session[pN].pktout.body = session[pN].pktout.data+4+pad+1;

SYSLOG_EXIT("s_wrpkt_start");
    return NULL;
}

static void s_wrpkt(WInt pN) {
    WInt pad, len, biglen, i;
    WULong crc;

SYSLOG_ENTRY("s_wrpkt");

    len = session[pN].pktout.length + 5;	       /* type and CRC */
    pad = 8 - (len%8);
    biglen = len + pad;

    session[pN].pktout.body[-1] = session[pN].pktout.type;
    for (i=0; i<pad; i++)
	session[pN].pktout.data[i+4] = random_byte();
    crc = crc32(session[pN].pktout.data+4, biglen-4);

    session[pN].pktout.data[biglen+0] = (WUChar) ((crc >> 24) & 0xFF);
    session[pN].pktout.data[biglen+1] = (WUChar) ((crc >> 16) & 0xFF);
    session[pN].pktout.data[biglen+2] = (WUChar) ((crc >> 8) & 0xFF);
    session[pN].pktout.data[biglen+3] = (WUChar) (crc & 0xFF);

    session[pN].pktout.data[0] = (len >> 24) & 0xFF;
    session[pN].pktout.data[1] = (len >> 16) & 0xFF;
    session[pN].pktout.data[2] = (len >> 8) & 0xFF;
    session[pN].pktout.data[3] = len & 0xFF;

    if (session[pN].cipher)
	session[pN].cipher->encrypt(pN, session[pN].pktout.data+4, biglen);

    s_write(pN, (WChar *) session[pN].pktout.data, biglen+4);

SYSLOG_EXIT("s_wrpkt");
}

static WInt do_ssh_init(WInt pN) {
    WChar c;
    WChar version[10];
    WChar vstring[40];
    WInt i;

#ifdef FWHACK
    i = 0;
    while (s_read(pN, &c, 1) == 1) {
	if (c == 'S' && i < 2) i++;
	else if (c == 'S' && i == 2) i = 2;
	else if (c == 'H' && i == 2) break;
	else i = 0;
    }
#else
    if (s_read(pN, &c,1) != 1 || c != 'S') return 0;
    if (s_read(pN, &c,1) != 1 || c != 'S') return 0;
    if (s_read(pN, &c,1) != 1 || c != 'H') return 0;
#endif
    if (s_read(pN, &c,1) != 1 || c != '-') return 0;
    i = 0;
    while (1) {
	if (s_read(pN, &c,1) != 1)
	    return 0;
	if (i >= 0) {
	    if (c == '-') {
		version[i] = '\0';
		i = -1;
	    } else if (i < sizeof(version)-1)
		version[i++] = c;
	}
	else if (c == '\n')
	    break;
    }

    sprintf(vstring, "SSH-%s-7.7.7\n",
	    (strcmp(version, "1.5") <= 0 ? version : "1.5"));
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_HIGH,"%d: doing SSH v:%s\n",pN,vstring);
    s_write(pN, vstring, strlen(vstring));
    return 1;
}

/* redefine FN to slot the crLine values for this routine into
 * session.crLine[1], so they don't clash with ssh_gotdata which uses
 * session.crLine[0].  We can't use the original set of statics as that
 * means we can't reuse connections.
 *
 * Yuk.  Simon, did you _really_ have to do this?
 */
#define FN 1
WChar *ssh_protocol(WInt pN, WUChar *in, WInt inlen, WInt ispkt) {
    WInt i, j, len;
    WUChar session_id[16];
    WUChar *rsabuf, *keystr1, *keystr2;
    WUChar cookie[8];
    struct RSAKey servkey, hostkey;
    struct MD5Context md5c;
    WChar *error;
    WChar *verifyKnownHostResponse;

    extern struct ssh_cipher ssh_3des;
SYSLOG_ENTRY("ssh_protocol");
    crBegin;

    random_init();

    syslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: ispkt=%d\n",pN,ispkt);

    while (!ispkt)
	crReturnV;

    if (session[pN].pktin.type != 2)
	return "Public key packet not received";

    memcpy(cookie, session[pN].pktin.body, 8);

    MD5Init(&md5c);
    syslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: done MD5Init\n",pN);

    i = makekey(session[pN].pktin.body+8, &servkey, &keystr1);

    j = makekey(session[pN].pktin.body+8+i, &hostkey, &keystr2);

    MD5Update(&md5c, keystr2, hostkey.bytes);
    MD5Update(&md5c, keystr1, servkey.bytes);
    MD5Update(&md5c, session[pN].pktin.body, 8);

    MD5Final(session_id, &md5c);
    syslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: done MD5Final - hostkey.bytes = %d, servkey.bytes = %d\n",pN,hostkey.bytes,servkey.bytes);

    for (i=0; i<32; i++)
	session[pN].session_key[i] = random_byte();

    len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);

    rsabuf = malloc(len);
    if (!rsabuf)
    {
      syslogf_irq(SYSLOG_FILE,LOG_DEBUG_HIGH,"%d: ssh_protocol: out of mem for rsabuf",pN);
	return "Out of memory";
    }

    verifyKnownHostResponse = verify_ssh_host_key(/*(WChar *)*/ (session[pN].savedhost), /*(struct RSAKey *) */(&hostkey));
    c_write(pN,verifyKnownHostResponse,strlen(verifyKnownHostResponse));

    for (i=0; i<32; i++) {
	rsabuf[i] = session[pN].session_key[i];
	if (i < 16)
	    rsabuf[i] ^= session_id[i];
    }
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: got rsabuf",pN);

    if (hostkey.bytes > servkey.bytes) {
	rsaencrypt(rsabuf, 32, &servkey);
	rsaencrypt(rsabuf, servkey.bytes, &hostkey);
    } else {
	rsaencrypt(rsabuf, 32, &hostkey);
	rsaencrypt(rsabuf, hostkey.bytes, &servkey);
    }
    destroykey(&servkey);
    destroykey(&hostkey);
    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: encrypted host key",pN);

    if ((error=s_wrpkt_start(pN, 3, len+15))!=NULL)
      return error;
    session[pN].pktout.body[0] = 3;		       /* SSH_CIPHER_3DES */
    memcpy(session[pN].pktout.body+1, cookie, 8);
    session[pN].pktout.body[9] = (len*8) >> 8;
    session[pN].pktout.body[10] = (len*8) & 0xFF;
    memcpy(session[pN].pktout.body+11, rsabuf, len);
    session[pN].pktout.body[len+11] = session[pN].pktout.body[len+12] = 0;   /* protocol flags */
    session[pN].pktout.body[len+13] = session[pN].pktout.body[len+14] = 0;
    s_wrpkt(pN);

    xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: sent SSH_CIPHER_3DES flag",pN);

    free(rsabuf);

    session[pN].cipher = calloc(1,sizeof(ssh_3des)); /*&ssh_3des;*/
    if (!session[pN].cipher)
	return "Out of memory";
    memcpy(session[pN].cipher,&ssh_3des,sizeof(ssh_3des));
    (session[pN].cipher)->sesskey(pN,session[pN].session_key);

    do { crReturnV; } while (!ispkt);

    if (session[pN].pktin.type != 14)
	return "Encryption not successfully enabled";

    fflush(stdout);
    {
      xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_VERYLOW,"%d: ssh_protocol: login prompt",pN);
/*	static WChar username[100];
	static WInt pos = 0;
	static WChar c;*/
	if (!*session[pN].cfg.username) {
	    c_write(pN, "login as: ", 10);
	    while (session[pN].pr.uPos >= 0) {
		do { crReturnV; } while (ispkt);
		while (inlen--) switch (session[pN].pr.c = *in++) {
		  case 10: case 13:
		    session[pN].pr.username[session[pN].pr.uPos] = 0;
		    session[pN].pr.uPos = -1;
		    break;
		  case 8: case 127:
		    if (session[pN].pr.uPos > 0) {
			c_write(pN, "\b \b", 3);
			session[pN].pr.uPos--;
		    }
		    break;
		  case 21: case 27:
		    while (session[pN].pr.uPos > 0) {
			c_write(pN, "\b \b", 3);
			session[pN].pr.uPos--;
		    }
		    break;
		  case 3: case 4:
		    random_save_seed();
		    exit(0);
		    break;
		  default:
		    if (session[pN].pr.c >= ' ' && session[pN].pr.c <= '~' && session[pN].pr.uPos < 40) {
			session[pN].pr.username[session[pN].pr.uPos++] = session[pN].pr.c;
			c_write(pN, &session[pN].pr.c, 1);
		    }
		    break;
		}
	    }
	    c_write(pN, "\r\n", 2);
	    session[pN].pr.username[strcspn(session[pN].pr.username, "\n\r")] = '\0';
	} else {
	    WChar stuff[200];
	    strncpy(session[pN].pr.username, session[pN].cfg.username, 99);
	    session[pN].pr.username[99] = '\0';
	    sprintf(stuff, "Sent username \"%s\".\r\n", session[pN].pr.username);
	    c_write(pN, stuff, strlen(stuff));
	}
	if ((error=s_wrpkt_start(pN, 4, 4+strlen(session[pN].pr.username)))!=NULL)
	  return error;
	session[pN].pktout.body[0] = session[pN].pktout.body[1] = session[pN].pktout.body[2] = 0;
	session[pN].pktout.body[3] = strlen(session[pN].pr.username);
	memcpy(session[pN].pktout.body+4, session[pN].pr.username, strlen(session[pN].pr.username));
	s_wrpkt(pN);
    }

    do { crReturnV; } while (!ispkt);

    while (session[pN].pktin.type == 15) {
/*	static WChar password[100];
	static WInt pos;
	static WChar c;*/
	c_write(pN, "password: ", 10);
	session[pN].pr.pPos = 0;
	while (session[pN].pr.pPos >= 0) {
	    do { crReturnV; } while (ispkt);
	    while (inlen--) switch (session[pN].pr.c = *in++) {
	      case 10: case 13:
		session[pN].pr.password[session[pN].pr.pPos] = 0;
		session[pN].pr.pPos = -1;
		break;
	      case 8: case 127:
		if (session[pN].pr.pPos > 0)
		    session[pN].pr.pPos--;
		break;
	      case 21: case 27:
		session[pN].pr.pPos = 0;
		break;
	      case 3: case 4:
		random_save_seed();
		exit(0);
		break;
	      default:
		if (session[pN].pr.c >= ' ' && session[pN].pr.c <= '~' && session[pN].pr.pPos < 40)
		    session[pN].pr.password[session[pN].pr.pPos++] = session[pN].pr.c;
		break;
	    }
	}
	c_write(pN, "\r\n", 2);
	if ((error=s_wrpkt_start(pN, 9, 4+strlen(session[pN].pr.password)))!=NULL)
	  return error;
	session[pN].pktout.body[0] = session[pN].pktout.body[1] = session[pN].pktout.body[2] = 0;
	session[pN].pktout.body[3] = strlen(session[pN].pr.password);
	memcpy(session[pN].pktout.body+4, session[pN].pr.password, strlen(session[pN].pr.password));
	s_wrpkt(pN);
	memset(session[pN].pr.password, 0, strlen(session[pN].pr.password));
	do { crReturnV; } while (!ispkt);
	if (session[pN].pktin.type == 15) {
	    c_write(pN, "Access denied\r\n", 15);
	} else if (session[pN].pktin.type != 14) {
	    sprintf(errorString,"Strange packet received, type %d", session[pN].pktin.type);
	    return errorString;
	}
    }

    if (!session[pN].cfg.nopty) {
        i = strlen(session[pN].cfg.termtype);
        if ((error=s_wrpkt_start(pN, 10, i+5*4+1))!=NULL)
          return error;
        session[pN].pktout.body[0] = (i >> 24) & 0xFF;
        session[pN].pktout.body[1] = (i >> 16) & 0xFF;
        session[pN].pktout.body[2] = (i >> 8) & 0xFF;
        session[pN].pktout.body[3] = i & 0xFF;
        memcpy(session[pN].pktout.body+4, session[pN].cfg.termtype, i);
        i += 4;
        session[pN].pktout.body[i++] = (session[pN].rows >> 24) & 0xFF;
        session[pN].pktout.body[i++] = (session[pN].rows >> 16) & 0xFF;
        session[pN].pktout.body[i++] = (session[pN].rows >> 8) & 0xFF;
        session[pN].pktout.body[i++] = session[pN].rows & 0xFF;
        session[pN].pktout.body[i++] = (session[pN].cols >> 24) & 0xFF;
        session[pN].pktout.body[i++] = (session[pN].cols >> 16) & 0xFF;
        session[pN].pktout.body[i++] = (session[pN].cols >> 8) & 0xFF;
        session[pN].pktout.body[i++] = session[pN].cols & 0xFF;
        memset(session[pN].pktout.body+i, 0, 9);       /* 0 pixwidth, 0 pixheight, 0.b endofopt */
        s_wrpkt(pN);
        session[pN].ssh_state = SSH_STATE_INTERMED;
        do { crReturnV; } while (!ispkt);
        if (session[pN].pktin.type != 14 && session[pN].pktin.type != 15) {
            return "Protocol confusion";
        } else if (session[pN].pktin.type == 15) {
            c_write(pN, "Server refused to allocate pty\r\n", 32);
        }
    }

    if ((error=s_wrpkt_start(pN, 12, 0))!=NULL)
      return error;
    s_wrpkt(pN);

    session[pN].ssh_state = SSH_STATE_SESSION;
    if (session[pN].size_needed)
	if ((error=ssh_size(pN))!=NULL)
	  return error;

    while (1) {
	crReturnV;
	if (ispkt) {
	    if (session[pN].pktin.type == 17 || session[pN].pktin.type == 18) {
		WLong len = 0;
		for (i = 0; i < 4; i++)
		    len = (len << 8) + session[pN].pktin.body[i];
		c_write(pN, (WChar *)session[pN].pktin.body+4, len);
	    } else if (session[pN].pktin.type == 1) {
                xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: received SSH_MSG_DISCONNECT",pN);
		/* SSH_MSG_DISCONNECT: do nothing */
	    } else if (session[pN].pktin.type == 14) {
                xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: received SSH_MSG_SUCCESS",pN);
		/* SSH_MSG_SUCCESS: may be from EXEC_SHELL on some servers */
	    } else if (session[pN].pktin.type == 15) {
                xsyslogf_irq(SYSLOG_FILE,LOG_CONNECTION_LOW,"%d: received SSH_MSG_FAILURE",pN);
		/* SSH_MSG_FAILURE: may be from EXEC_SHELL on some servers
		 * if no pty is available or in other odd cases. Ignore */
	    } else if (session[pN].pktin.type == 20) {
		/* EXITSTATUS */
		if ((error=s_wrpkt_start(pN, 33, 0))!=NULL)
		  return error;
		s_wrpkt(pN);
	    } else if (session[pN].pktin.type == 32) {

	        /*sprintf(errorString,"SSH_MSG_IGNORE");
	        return errorString;*/

	    } else {
	      {
		sprintf(errorString,"Strange packet received: type %d", session[pN].pktin.type);
		return errorString;
	      }
	    }
	} else {
	    if ((error=s_wrpkt_start(pN, 16, 4+inlen))!=NULL)
	      return error;
	    session[pN].pktout.body[0] = (inlen >> 24) & 0xFF;
	    session[pN].pktout.body[1] = (inlen >> 16) & 0xFF;
	    session[pN].pktout.body[2] = (inlen >> 8) & 0xFF;
	    session[pN].pktout.body[3] = inlen & 0xFF;
	    memcpy(session[pN].pktout.body+4, in, inlen);
	    s_wrpkt(pN);
	}
    }

    crFinishV;
}
#undef FN

void ssh_close(WInt pN)
{
  if (session[pN].pktin.data != NULL)
  {
    free(session[pN].pktin.data);
    session[pN].pktin.data = NULL;
  }

  if (session[pN].pktout.data != NULL)
  {
    free(session[pN].pktout.data);
    session[pN].pktout.data = NULL;
  }

  if (session[pN].cipher != NULL)
  {
    free(session[pN].cipher);
    session[pN].cipher = NULL;
  }

  if (session[pN].savedhost != NULL)
  {
    free(session[pN].savedhost);
    session[pN].savedhost = NULL;
  }

  if (session[pN].s != INVALID_SOCKET)
  {
    socket_close(session[pN].s);
    session[pN].s = INVALID_SOCKET;
  }
}

/*
 * Called to set up the connection. Will arrange for WM_NETEVENT
 * messages to be passed to the specified window, whose window
 * procedure should then call telnet_msg().
 *
 * Returns an error message, or NULL on success.
 *
 * Also places the canonical host name into `realhost'.
 */
WChar *ssh_init (WInt pN, HWND hwnd, WChar *host, WInt port, WChar **realhost) {
    SOCKADDR_IN addr;
    struct hostent *h;
    WULong a;
    WChar waitingForGetHostByName[] = "Resolving...\r\n";
#ifdef FWHACK
    WChar *FWhost;
    WInt FWport;
#endif
    int tosFlag;

SYSLOG_ENTRY("ssh_init");
    session[pN].savedhost = malloc(1+strlen(host));
    if (!session[pN].savedhost)
	return "Out of memory";
    strcpy(session[pN].savedhost, host);

#ifdef FWHACK
    FWhost = host;
    FWport = port;
    host = FWSTR;
    port = 23;
#endif

    /*
     * Try to find host.
     */
    if ( (a = inet_addr(host)) == (WULong) INADDR_NONE) {
        c_write(pN,waitingForGetHostByName,sizeof(waitingForGetHostByName));
	if ( (h = gethostbyname(host)) == NULL)
	    switch (WSAGetLastError()) {
	      case WSAENETDOWN: return "Network is down";
	      case WSAHOST_NOT_FOUND: case WSANO_DATA:
		return "Host does not exist";
	      case WSATRY_AGAIN: return "Host not found";
	      default: return "gethostbyname: unknown error";
	    }
	memcpy (&a, h->h_addr, sizeof(a));
	*realhost = h->h_name;
    } else
	*realhost = host;
#ifdef FWHACK
    *realhost = FWhost;
#endif
/*    SYSLOG_EXIT("ssh_init - NOT: do_ssh_init ing");*/
    a = ntohl(a);

    if (port < 0)
	port = 22;		       /* default ssh port */

    /*
     * Open socket.
     */
    session[pN].s = socket(AF_INET, SOCK_STREAM, 0);
    if (session[pN].s == INVALID_SOCKET)
	switch (WSAGetLastError()) {
	  case WSAENETDOWN: return "Network is down";
	  case WSAEAFNOSUPPORT: return "TCP/IP support not present";
	  default: return "socket(): unknown error";
	}

    /*
     * Bind to local address.
     */
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(0);
    if (bind (session[pN].s, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
	switch (WSAGetLastError()) {
	  case WSAENETDOWN: return "Network is down";
	  default: return "bind(): unknown error";
	}

    /* set the IP Type of Service Low Delay bit, so we might get
     * expressed through over bulk data traffic */
    tosFlag = IPTOS_LOWDELAY;
    if (setsockopt(session[pN].s, 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 %d",pN, WSAGetLastError());

    /*
     * Connect to remote address.
     */
    addr.sin_addr.s_addr = htonl(a);
    addr.sin_port = htons((short)port);
    if (connect (session[pN].s, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
	switch (WSAGetLastError()) {
	  case WSAENETDOWN: return "Network is down";
	  case WSAECONNREFUSED: return "Connection refused";
	  case WSAENETUNREACH: return "Network is unreachable";
	  case WSAEHOSTUNREACH: return "No route to host";
	  default: return "connect(): unknown error";
	}

#ifdef FWHACK
    send(session[pN].s, "connect ", 8, 0);
    send(session[pN].s, FWhost, strlen(FWhost), 0);
    {
	WChar buf[20];
	sprintf(buf, " %d\n", FWport);
	send (session[pN].s, buf, strlen(buf), 0);
    }
#endif
    if (!do_ssh_init(pN))
	return "Protocol initialisation error";

    if (WSAAsyncSelect (session[pN].s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR)
	switch (WSAGetLastError()) {
	  case WSAENETDOWN: return "Network is down";
	  default: return "WSAAsyncSelect(): unknown error";
	}

SYSLOG_EXIT("ssh_init");
    return NULL;
}

/*
 * Process a WM_NETEVENT message. Will return 0 if the connection
 * has closed, or <0 for a socket error, or &FF for a fatal error
 * (error stored in global errorString)
 */
WInt ssh_msg (WInt pN, WPARAM wParam, LPARAM lParam) {
    WInt ret;
    WChar buf[2560];
    WChar *error;
    WChar *remoteClosed = "\r\n\r\nConnection closed by SSH server\r\n";
    WChar *quitBug = "\r\n\
Workaround for bug in SSHProxy - SSHProxy will now quit.\r\n\
For another session, rerun !SSHProxy.\r\n\
Giving it an argument of the port to listen on will allow\r\n\
multiple copies to run together\r\n";

    if (session[pN].s == INVALID_SOCKET)	       /* how the hell did we get here?! */
	return -5000;

    if (WSAGETSELECTERROR(lParam) != 0)
	return -WSAGETSELECTERROR(lParam);

    switch (WSAGETSELECTEVENT(lParam)) {
      case FD_READ:
        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: ssh_msg: FD_READ\n",pN);
	ret = recv(session[pN].s, buf, sizeof(buf), 0);
	xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: got %d bytes\n",pN,ret);
	if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
	    return 1;
	if (ret < 0)		       /* any _other_ error */
	    return -10000-WSAGetLastError();
	if (ret == 0) {
	    session[pN].s = INVALID_SOCKET;
	    return 0;		       /* can't happen, in theory */
	}
        /* this must pull everything out of the receive queue, so
         * we take a buffer's worth at a time and decode it, repeating
         * until the queue is empty.  Windows (apparently) will provide
         * a message to the app if the queue is _non empty_, while
         * RISC OS provides a message if the queue has _new _data_,
         * hence the addition of the while loop
         */
	while (ret>0)
	{
	  xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: got %d more bytes\n",pN,ret);
	  xsyslog_irq_logdata(SYSLOG_FILE,LOG_DEBUG_LOW,buf,ret,0);
	  if ((error=ssh_gotdata (pN, (WUChar *) buf, ret))!=NULL)
	  {
	    if (error!=errorString)/* if the error isn't already in the */
	      strcpy(errorString,error); /* global error buffer put it there */
	    return 0xff;
	  }
	  ret = recv(session[pN].s, buf, sizeof(buf), MSG_DONTWAIT);
	  /* if the server sends us a humungous packet, it may take a
	   * while to receive and decrypt it - this attempts to give the
	   * desktop back some control while doing so - only do this
	   * if the 2nd/3rd etc tries have some data for us
	   */
	  if (ret>0)
	    sshinterface_poll_single(FALSE);
	}
	return 1;
      case FD_CLOSE:
        xsyslogf_irq(SYSLOG_FILE,LOG_DEBUG_LOW,"%d: ssh_msg: FD_CLOSE\n",pN);
	c_write(pN,remoteClosed,strlen(remoteClosed));
#ifdef MEMORY_BUG_WORKAROUND
	c_write(pN,quitBug,strlen(quitBug));
#endif
	session[pN].s = INVALID_SOCKET;
	ssh_close(pN);
	sshinterface_connection_close(pN);
#ifdef MEMORY_BUG_WORKAROUND
	exit(-2);  /* workaround for memory bug somewhere */
#endif
	return 0;
    }
    return 1;			       /* shouldn't happen, but WTF */
}

/*
 * Called to send data down the Telnet connection.
 */
WChar *ssh_send (WInt pN, WChar *buf, WInt len) {
  WChar *error;

    if (session[pN].s == INVALID_SOCKET)
	return NULL;

    if ((error=ssh_protocol(pN, (WUChar *)buf, len, 0))!=NULL)
      return error;

    return NULL;
}

/*
 * Called to set the size of the window from Telnet's POV.
 */
WChar *ssh_size(WInt pN) {
  WChar *error;

    switch (session[pN].ssh_state) {
      case SSH_STATE_BEFORE_SIZE:
	break;			       /* do nothing */
      case SSH_STATE_INTERMED:
	session[pN].size_needed = TRUE;	       /* buffer for later */
	break;
      case SSH_STATE_SESSION:
        if (!session[pN].cfg.nopty) {
            if ((error=s_wrpkt_start(pN, 11, 16))!=NULL)
              return error;
            session[pN].pktout.body[0] = (session[pN].rows >> 24) & 0xFF;
            session[pN].pktout.body[1] = (session[pN].rows >> 16) & 0xFF;
            session[pN].pktout.body[2] = (session[pN].rows >> 8) & 0xFF;
            session[pN].pktout.body[3] = session[pN].rows & 0xFF;
            session[pN].pktout.body[4] = (session[pN].cols >> 24) & 0xFF;
            session[pN].pktout.body[5] = (session[pN].cols >> 16) & 0xFF;
            session[pN].pktout.body[6] = (session[pN].cols >> 8) & 0xFF;
            session[pN].pktout.body[7] = session[pN].cols & 0xFF;
            memset(session[pN].pktout.body+8, 0, 8);
            s_wrpkt(pN);
        }
    }
    return NULL;
}

/*
 * (Send Telnet special codes)
 */
void ssh_special (Telnet_Special code) {
    /* do nothing */
}
/*
Backend ssh_backend = {
    ssh_init,
    ssh_msg,
    ssh_send,
    ssh_size,
    ssh_special
};
*/
